@issuegraph/viewer 0.9.1 → 0.11.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 (51) hide show
  1. package/dist/clusters.d.ts +1 -1
  2. package/dist/clusters.d.ts.map +1 -1
  3. package/dist/clusters.js +13 -12
  4. package/dist/clusters.js.map +1 -1
  5. package/dist/document.d.ts +27 -0
  6. package/dist/document.d.ts.map +1 -1
  7. package/dist/document.js +10 -2
  8. package/dist/document.js.map +1 -1
  9. package/dist/element.d.ts +10 -2
  10. package/dist/element.d.ts.map +1 -1
  11. package/dist/element.js.map +1 -1
  12. package/dist/index.d.ts +4 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +11 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/layout.d.ts.map +1 -1
  17. package/dist/layout.js +7 -2
  18. package/dist/layout.js.map +1 -1
  19. package/dist/mount.d.ts +2 -2
  20. package/dist/mount.d.ts.map +1 -1
  21. package/dist/mount.js +6 -0
  22. package/dist/mount.js.map +1 -1
  23. package/dist/parts.d.ts +0 -7
  24. package/dist/parts.d.ts.map +1 -1
  25. package/dist/parts.js +24 -42
  26. package/dist/parts.js.map +1 -1
  27. package/dist/projections/graph.d.ts +50 -2
  28. package/dist/projections/graph.d.ts.map +1 -1
  29. package/dist/projections/graph.js +140 -49
  30. package/dist/projections/graph.js.map +1 -1
  31. package/dist/projections/linear.d.ts +105 -15
  32. package/dist/projections/linear.d.ts.map +1 -1
  33. package/dist/projections/linear.js +132 -58
  34. package/dist/projections/linear.js.map +1 -1
  35. package/dist/projections/tree.d.ts.map +1 -1
  36. package/dist/projections/tree.js +17 -4
  37. package/dist/projections/tree.js.map +1 -1
  38. package/dist/render.d.ts +5 -0
  39. package/dist/render.d.ts.map +1 -1
  40. package/dist/render.js.map +1 -1
  41. package/dist/scene.d.ts +22 -0
  42. package/dist/scene.d.ts.map +1 -1
  43. package/dist/scene.js.map +1 -1
  44. package/dist/skeleton.d.ts +63 -0
  45. package/dist/skeleton.d.ts.map +1 -0
  46. package/dist/skeleton.js +108 -0
  47. package/dist/skeleton.js.map +1 -0
  48. package/dist/styles.d.ts.map +1 -1
  49. package/dist/styles.js +236 -69
  50. package/dist/styles.js.map +1 -1
  51. package/package.json +1 -1
@@ -0,0 +1,108 @@
1
+ /**
2
+ * `renderViewerSkeleton` — what the viewer draws before there is a document.
3
+ *
4
+ * ## Why the package draws it, and the host only styles it
5
+ *
6
+ * A viewer mounted before its data arrives had two choices and both were wrong.
7
+ * Rendered over an empty document it said "nothing is in the order", which is
8
+ * a claim about a backlog nobody has read yet. Held back by the host instead,
9
+ * the host drew its own placeholder — a guess at this package's layout that
10
+ * jumped when the real rows landed and that every host had to rewrite.
11
+ *
12
+ * So the SHAPE is this package's: the skeleton rows use the same `.ig-slot`
13
+ * geometry the order uses, so a placeholder row is exactly as tall as a real
14
+ * one at every density the container picks, and nothing moves on arrival. It
15
+ * is a pure function over no document, so a host can render it on the server
16
+ * — in a route's loading state — and the page-load placeholder, the
17
+ * waiting-for-data placeholder and the data all line up.
18
+ *
19
+ * The LOOK is the host's. Every placeholder is an `.ig-skeleton-block`,
20
+ * filled with `--ig-surface-2` and carrying no animation. This package ships
21
+ * no motion anywhere — the editor's sheet says why — so a host that wants its
22
+ * own pulse adds it to that one class, gated on its own reduced-motion rule,
23
+ * and the fill follows the host's theme like every other surface here.
24
+ *
25
+ * ## No word of its own
26
+ *
27
+ * The root is `aria-busy` always. It is a labelled `status` region only when
28
+ * the host supplies `label` — "Loading your issues" is a sentence in the
29
+ * host's language, and this package does not write one.
30
+ */
31
+ import { element, renderMarkup } from "./element.js";
32
+ import { resolveTheme, themeCss } from "./theme.js";
33
+ import { viewerStylesheet } from "./styles.js";
34
+ const DEFAULT_ROWS = 8;
35
+ const DEFAULT_CARDS = 3;
36
+ /** Title widths cycle through a few lengths so the column reads as text, not as a bar chart. */
37
+ const TITLE_WIDTHS = ['0', '1', '2', '1'];
38
+ function block(shape, extra = {}) {
39
+ return element('span', { class: 'ig-skeleton-block', 'data-shape': shape, ...extra });
40
+ }
41
+ function headerSkeleton() {
42
+ return element('header', { class: 'ig-header' }, [
43
+ element('div', { class: 'ig-header-top' }, [block('label')]),
44
+ element('div', { class: 'ig-skeleton-chips' }, [block('chip'), block('chip'), block('chip')]),
45
+ ]);
46
+ }
47
+ function rowSkeleton(index) {
48
+ return element('li', { class: 'ig-slot ig-skeleton-row' }, [
49
+ // THE STATION ALONE IN THE RANK TRACK. A rank figure over it made the cell
50
+ // taller than a real row's, and the whole placeholder a few pixels a row
51
+ // too tall — the jump this file exists to prevent. Measured against the
52
+ // real rail at its dense density: 53 and 53.
53
+ element('div', { class: 'ig-rank-cell' }, [block('station')]),
54
+ element('div', { class: 'ig-row-body' }, [
55
+ // ONE WRAPPER FOR BOTH LINES, as the real row has, so the body's own gap
56
+ // is not spent between the title and the meta line a second time.
57
+ element('div', { class: 'ig-skeleton-lines' }, [
58
+ block('title', { 'data-width': TITLE_WIDTHS[index % TITLE_WIDTHS.length] ?? '0' }),
59
+ block('meta'),
60
+ ]),
61
+ ]),
62
+ ]);
63
+ }
64
+ /**
65
+ * A spine card's placeholder: ONE filled block at the card's width and height,
66
+ * not an outlined card with bars inside. Solid rounded blocks are how a
67
+ * skeleton reads at a glance — the same shape host dashboards draw for their
68
+ * own cards — and it keeps every placeholder on the one hook a host styles.
69
+ */
70
+ function cardSkeleton() {
71
+ return block('card');
72
+ }
73
+ /**
74
+ * The placeholder's root, as a spec — exported so a composing surface (the
75
+ * editor's workspace skeleton) can place it inside its own zones.
76
+ */
77
+ export function skeletonSpec(options = {}) {
78
+ const projection = options.projection ?? 'linear';
79
+ const graph = projection === 'graph';
80
+ const count = Math.max(1, Math.floor(options.rows ?? (graph ? DEFAULT_CARDS : DEFAULT_ROWS)));
81
+ const body = graph
82
+ ? element('div', { class: 'ig-skeleton-stage' }, Array.from({ length: count }, () => cardSkeleton()))
83
+ : element('ol', { class: 'ig-list' }, Array.from({ length: count }, (_, index) => rowSkeleton(index)));
84
+ return element('section', {
85
+ class: `ig-viewer ${graph ? 'ig-graph' : projection === 'tree' ? 'ig-tree-view' : 'ig-linear'}`,
86
+ 'data-projection': projection,
87
+ 'data-frame': options.frame === false ? 'none' : undefined,
88
+ 'data-ig-loading': 'true',
89
+ 'aria-busy': 'true',
90
+ role: options.label === undefined ? undefined : 'status',
91
+ 'aria-label': options.label,
92
+ }, [
93
+ options.chrome === false || graph ? null : headerSkeleton(),
94
+ // THE PLACEHOLDERS SAY NOTHING. They are shapes, not content, and a
95
+ // screen reader walking eight empty list items learns only that there
96
+ // are eight of them — the region's busy state and label already say it.
97
+ element('div', { 'aria-hidden': 'true' }, [body]),
98
+ ]);
99
+ }
100
+ /** Render the placeholder a host shows before the viewer has a document. Pure and total. */
101
+ export function renderViewerSkeleton(options = {}) {
102
+ const theme = resolveTheme(options.theme);
103
+ return {
104
+ markup: renderMarkup(skeletonSpec(options)),
105
+ styles: `${viewerStylesheet}\n${themeCss(theme, options.themeSelector ?? ':root')}`,
106
+ };
107
+ }
108
+ //# sourceMappingURL=skeleton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skeleton.js","sourceRoot":"","sources":["../src/skeleton.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AA0RH,eAAO,MAAM,gBAAgB,QAiyC5B,CAAC"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AA+PH,eAAO,MAAM,gBAAgB,QAm+C5B,CAAC"}
package/dist/styles.js CHANGED
@@ -169,18 +169,10 @@ ${scope} .ig-unit-count {
169
169
  margin-right: var(--ig-space-tight);
170
170
  }
171
171
 
172
- /* THE FOOTER ROWS TOO, AND THIS IS THE POPULATION THE ROW FIX MISSED.
173
- A footer entry is deliberately ONE LINE and shorter than a ranked row -
174
- 16a gives it no rank, no station and no explanation block, because it is
175
- not a fact about the work. That part was right. What was not governed is
176
- its TITLE: it wrapped, and the excluded row carrying a canonical reference
177
- ran to 73px while its siblings sat at 36. A footer row taller than a
178
- ranked row inverts the whole point of the group.
179
-
180
- THE DEFECT IS THE ONE ALREADY FIXED ON .ig-slot, ONE ROW KIND OVER: a
181
- title with nothing stopping it wrapping takes the row with it. Fixed at
182
- the class this time rather than at the site - the rail renders three row
183
- kinds and the first pass governed two. */
172
+ /* THE FOOTER ROW'S TITLE STAYS ON ONE LINE, as a ranked row's does. The row
173
+ is taller than a ranked row now by design — its status and its reason are
174
+ what it exists to show — but a title that wraps still takes the row with it,
175
+ so the name ellipsizes and the reason below it carries the length. */
184
176
  ${scope} .ig-footer-row .ig-title {
185
177
  line-height: var(--ig-row-title-line);
186
178
  min-width: 0;
@@ -189,39 +181,7 @@ ${scope} .ig-footer-row .ig-title {
189
181
  white-space: nowrap;
190
182
  }
191
183
 
192
- ${scope} .ig-footer-row .ig-badges {
193
- flex-wrap: nowrap;
194
- min-width: 0;
195
- overflow: hidden;
196
- }
197
-
198
- /* AND THE CHIPS LOSE THEIR CHROME HERE TOO, which is what actually makes a
199
- footer entry ONE LINE. 16a draws it as "a label chip, a title and an
200
- identity, on one line"; a bordered chip is padding plus stroke plus line
201
- box, so a row carrying any relationship badge stood at 36px against 22px
202
- for one carrying none - and the badge block, not the title, was the last
203
- thing driving it.
204
-
205
- MEASURED IN TWO PASSES, WHICH IS THE POINT. Stopping after the title fix
206
- left the footer group at two heights and looked finished: the row that had
207
- been 73px was down to 36 and matched its siblings, so the obvious check
208
- passed. It was the SECOND measurement - why 22 and why 36 - that found the
209
- badges. A class is not fixed until nothing in it varies for a reason you
210
- have not named. */
211
- ${scope} .ig-footer-row .ig-badge {
212
- background: none;
213
- border: 0;
214
- border-radius: 0;
215
- padding: 0;
216
- white-space: nowrap;
217
- }
218
-
219
- /* THE THIRD AND LAST MEMBER OF THE CLASS: the identity. An excluded row
220
- carries TWO - its own, and the canonical it defers to ('455 -> 512') - and
221
- the second wrapped, which is why that row alone stood at 30px when the
222
- others reached 17. Same defect as the title and the chips, third element,
223
- found by asking the same question a third time rather than by stopping at
224
- the first uniform-looking answer. */
184
+ /* The identity never wraps mid-reference. */
225
185
  ${scope} .ig-footer-row .ig-id {
226
186
  white-space: nowrap;
227
187
  }
@@ -299,6 +259,19 @@ ${scope} .ig-slot[aria-expanded='true'] {
299
259
  ${scope} .ig-slot[aria-expanded='true'] .ig-provenance {
300
260
  display: block;
301
261
  }
262
+
263
+ /* THE QUERY CHIP COMES BACK WHERE ITS LINE GOES (#131). At this density the
264
+ provenance line is hidden, so the chip is the only mark on the row naming
265
+ WHICH ordered query placed it — and step 3 below keeps it for exactly that
266
+ reason. A row the reader opened draws the line again, and the chip leaves
267
+ with it: one fact, said once, at every density. */
268
+ ${scope} .ig-slot .ig-badge[data-priority='query'] {
269
+ display: inline-flex;
270
+ }
271
+
272
+ ${scope} .ig-slot[aria-expanded='true'] .ig-badge[data-priority='query'] {
273
+ display: none;
274
+ }
302
275
  `;
303
276
  }
304
277
  export const viewerStylesheet = `
@@ -314,6 +287,51 @@ export const viewerStylesheet = `
314
287
  position: relative;
315
288
  }
316
289
 
290
+ /* THE FRAME IS THE STANDALONE CASE'S, AND A CONTAINER CAN DECLINE IT.
291
+ Alone in a host's page the viewer is a card and draws its own edge. Composed
292
+ into a surface that already rules its own zones off, that edge lands beside
293
+ the container's and the seam reads as a doubled hairline — so the container
294
+ passes frame: false and takes responsibility for every line on the
295
+ surface. See SceneOptions.frame for why this is an option rather than a
296
+ host stylesheet unsetting the properties above. */
297
+ .ig-viewer[data-frame='none'] {
298
+ border: 0;
299
+ border-radius: 0;
300
+ }
301
+
302
+ /* THE LEGEND DOCKS AGAINST THE CONTAINER'S SCROLLPORT, NOT THIS ROOT'S.
303
+ See SceneOptions.dockLegend for why only a container can ask for this.
304
+
305
+ THE overflow LINE IS THE WHOLE MECHANISM AND LOOKS LIKE A TIDY-UP. A sticky
306
+ element positions against its nearest SCROLLPORT, and an ancestor with
307
+ overflow: hidden is one — so with the root left as it is above, the legend
308
+ sticks to a box that never scrolls, which is the box it already sat at the
309
+ bottom of. Nothing moves, nothing errors, and the declaration below reads as
310
+ though it works. Lifting the clip hands the legend the container's scroller
311
+ instead, which is the only one that scrolls.
312
+
313
+ WHICH IS ALSO WHY THIS IS SAFE ONLY WITH THE FRAME DECLINED. The clip exists
314
+ so a drawn radius cuts its own corners; a root that has stopped clipping must
315
+ not be drawing one. The rule above and this one are written to be passed
316
+ together, and the workspace passes both.
317
+
318
+ NO z-index, DELIBERATELY. The legend is the root's last child, so it already
319
+ paints over the order it overlaps; a layer number here would be a claim about
320
+ a stacking order this package does not own. */
321
+ .ig-viewer[data-legend='docked'] {
322
+ overflow: visible;
323
+ }
324
+
325
+ /* bottom: 0 IS A DEFAULT THE CONTAINER OVERRIDES, not a fixed position. A
326
+ container with something of its own already pinned to that edge — the
327
+ grooming workspace's rail footer — sets this element's bottom to clear it,
328
+ from a height it measures rather than from a number either package wrote
329
+ down and both would have to keep true. */
330
+ .ig-viewer[data-legend='docked'] > .ig-legend {
331
+ position: sticky;
332
+ bottom: 0;
333
+ }
334
+
317
335
  .ig-viewer *,
318
336
  .ig-viewer *::before,
319
337
  .ig-viewer *::after {
@@ -1045,6 +1063,20 @@ ${railDensity(".ig-viewer[data-density='dense']")}
1045
1063
  padding: var(--ig-space-micro) var(--ig-space-tight);
1046
1064
  }
1047
1065
 
1066
+ /* A MATCHED QUERY IS SAID ONCE ON A LIST ROW, BY ITS LINE (#131). The row's
1067
+ provenance line already reads "Picked by query 1 · label:P0", so a chip
1068
+ carrying "label:P0" above it printed the same label twice on every row a
1069
+ query placed — and no §16 frame draws that chip; §16h gives a row "a single
1070
+ provenance line". Hidden by CSS rather than dropped from the markup, for the
1071
+ reason the dense rail hides the line instead: the markup stays whole, and
1072
+ at the dense density, where the LINE is the one hidden, the rail block
1073
+ brings the chip back. A graph card is not an .ig-slot and draws no line, so
1074
+ its chip is untouched. A declared tier and a promotion keep their chips:
1075
+ those are the scanning read §16a draws beside the sentence. */
1076
+ .ig-slot .ig-badge[data-priority='query'] {
1077
+ display: none;
1078
+ }
1079
+
1048
1080
  /* THE HUE IS A BORDER AND A FILL HERE, NOT A TEXT COLOUR — the theme holds the
1049
1081
  edge hues to the 3:1 NON-TEXT bar, and says so where it defines them, so
1050
1082
  painting badge LABELS with them contradicts the palette's own claim. It was
@@ -1188,57 +1220,83 @@ ${railDensity(".ig-viewer[data-density='dense']")}
1188
1220
 
1189
1221
  /* ── the footer group: holds that earn no rank slot ────────────────────── */
1190
1222
 
1191
- .ig-footer {
1192
- background: var(--ig-surface-2);
1193
- padding: var(--ig-space) var(--ig-space-wide);
1223
+ /* NO RULE OF ITS OWN ON TOP. The last ranked row above already draws its
1224
+ bottom rule, and a second one here stacked on it as a doubled line. */
1225
+ /* THE PANEL HEADER'S LAYOUT, ONE LEVEL DOWN: a kicker and a count chip on one
1226
+ line, a sentence under them. The kicker and the chip are the header's own
1227
+ classes (see footerHead), so this rule only places them. */
1228
+ .ig-footer-head {
1229
+ display: flex;
1230
+ flex-direction: column;
1231
+ gap: var(--ig-space-tight);
1232
+ padding: var(--ig-row-padding-block-dense) var(--ig-space-wide);
1194
1233
  }
1195
1234
 
1196
- .ig-footer-head {
1235
+ .ig-footer-title-row {
1197
1236
  align-items: center;
1198
1237
  display: flex;
1199
1238
  gap: var(--ig-space);
1200
- justify-content: space-between;
1201
1239
  }
1202
1240
 
1203
1241
  .ig-footer-title {
1204
- color: var(--ig-text-body);
1205
- font-size: var(--ig-font-size-compact);
1206
1242
  margin: 0;
1207
1243
  }
1208
1244
 
1209
- .ig-footer-labels {
1245
+ .ig-footer-note {
1210
1246
  color: var(--ig-text-muted);
1211
- font-family: var(--ig-font-mono);
1212
- font-size: var(--ig-font-size-meta);
1247
+ font-size: var(--ig-font-size-compact);
1248
+ line-height: var(--ig-row-meta-line);
1249
+ margin: 0;
1213
1250
  }
1214
1251
 
1215
- /* ONE LINE EACH, and the frame is explicit about why: these are not facts
1216
- about the work, so they earn no rank slot and no explanation block. A full
1217
- row here — station, badges, provenance — was claiming the opposite. */
1218
1252
  .ig-footer .ig-list {
1219
1253
  display: flex;
1220
1254
  flex-direction: column;
1221
- gap: var(--ig-space-snug);
1222
- margin-top: var(--ig-space);
1223
1255
  }
1224
1256
 
1257
+ /* A RANKED ROW'S GRID, PADDING AND RULE, so the group is the rest of the same
1258
+ list rather than a panel of its own: the rank track holds the held station,
1259
+ and it lines up under the stations above because it is the same track.
1260
+
1261
+ ON THE PANEL'S OWN GROUND, NOT A SECOND SURFACE, and at full strength. The
1262
+ group used to sit on --ig-surface-2 with every row at 0.72 opacity, which
1263
+ read as disabled — on the rows whose whole purpose is "open me to see why". */
1225
1264
  .ig-footer-row {
1226
- align-items: center;
1227
- display: flex;
1228
- gap: var(--ig-space-snug);
1229
- opacity: 0.72;
1265
+ border-top: var(--ig-stroke) solid var(--ig-line);
1266
+ column-gap: var(--ig-space);
1267
+ cursor: pointer;
1268
+ display: grid;
1269
+ grid-template-columns: var(--ig-rank-column) 1fr;
1270
+ /* THE DENSE RANKED ROW'S PADDING, at every width: these rows carry three
1271
+ lines where a ranked row carries two, and the wider padding on top of that
1272
+ made each one twice the height of its neighbours above. */
1273
+ padding: var(--ig-row-padding-block-dense) var(--ig-space-wide);
1274
+ }
1275
+
1276
+ .ig-footer-row:hover {
1277
+ background: color-mix(in srgb, var(--ig-text) var(--ig-tint-unit), transparent);
1278
+ }
1279
+
1280
+ /* THE RANKED ROW'S SELECTED BAND, and its fill: one selection, one look,
1281
+ whichever part of the list it was made in. */
1282
+ .ig-footer-row[aria-current='true'] {
1283
+ background: color-mix(in srgb, var(--ig-accent) var(--ig-tint-fill), transparent);
1284
+ box-shadow: inset var(--ig-band-rail) 0 0 var(--ig-accent);
1230
1285
  }
1231
1286
 
1232
1287
  .ig-footer-row .ig-title {
1233
- font-size: var(--ig-font-size-compact);
1288
+ color: var(--ig-text-body);
1234
1289
  }
1235
1290
 
1236
- .ig-footer-row .ig-id {
1237
- font-size: var(--ig-font-size-meta);
1291
+ .ig-footer-row[aria-current='true'] .ig-title {
1292
+ color: var(--ig-text);
1238
1293
  }
1239
1294
 
1240
- .ig-footer-row[aria-current='true'] {
1241
- opacity: 1;
1295
+ .ig-footer-why {
1296
+ color: var(--ig-text-muted);
1297
+ font-size: var(--ig-font-size-compact);
1298
+ line-height: var(--ig-row-meta-line);
1299
+ margin: 0;
1242
1300
  }
1243
1301
 
1244
1302
  /* ── the graph canvas ──────────────────────────────────────────────────── */
@@ -1573,6 +1631,94 @@ ${railDensity(".ig-viewer[data-density='dense']")}
1573
1631
  padding: var(--ig-space-tight) 0;
1574
1632
  }
1575
1633
 
1634
+ /* ── loading placeholder ───────────────────────────────────────────────── */
1635
+
1636
+ /* THE HOST'S HOOK, AND THE ONLY ONE. Every placeholder shape is one of these,
1637
+ filled with the second surface and holding still: this package ships no
1638
+ motion, and the fill already follows the host's theme. A host that wants a
1639
+ pulse animates this class under its own reduced-motion rule. */
1640
+ .ig-skeleton-block {
1641
+ background: var(--ig-surface-2);
1642
+ border-radius: var(--ig-radius);
1643
+ display: block;
1644
+ }
1645
+
1646
+ /* ON THE HEADER'S OWN GROUND the second surface is the ground, so the shapes
1647
+ there take the hairline colour instead, or they vanish into it. */
1648
+ .ig-header .ig-skeleton-block {
1649
+ background: var(--ig-line);
1650
+ }
1651
+
1652
+ .ig-skeleton-block[data-shape='label'] {
1653
+ block-size: var(--ig-space);
1654
+ inline-size: calc(var(--ig-char-width) * 14);
1655
+ }
1656
+
1657
+ .ig-skeleton-chips {
1658
+ display: flex;
1659
+ gap: var(--ig-space-snug);
1660
+ }
1661
+
1662
+ .ig-skeleton-block[data-shape='chip'] {
1663
+ block-size: calc(var(--ig-space) * 2);
1664
+ border-radius: var(--ig-radius);
1665
+ inline-size: calc(var(--ig-char-width) * 9);
1666
+ }
1667
+
1668
+ .ig-skeleton-block[data-shape='station'] {
1669
+ block-size: var(--ig-space);
1670
+ border-radius: 50%;
1671
+ inline-size: var(--ig-space);
1672
+ }
1673
+
1674
+ /* THE ROW'S OWN LINE HEIGHTS, so a placeholder row and a real row are the same
1675
+ height at every density the container picks. */
1676
+ .ig-skeleton-block[data-shape='title'] {
1677
+ block-size: var(--ig-row-title-line);
1678
+ inline-size: 68%;
1679
+ }
1680
+
1681
+ .ig-skeleton-block[data-shape='title'][data-width='1'] {
1682
+ inline-size: 54%;
1683
+ }
1684
+
1685
+ .ig-skeleton-block[data-shape='title'][data-width='2'] {
1686
+ inline-size: 82%;
1687
+ }
1688
+
1689
+ .ig-skeleton-block[data-shape='meta'] {
1690
+ block-size: var(--ig-row-meta-line);
1691
+ inline-size: 36%;
1692
+ }
1693
+
1694
+ /* THE TWO LINES OF A ROW, stacked with no gap of their own: each block is its
1695
+ line's full height, which already carries the leading a real line has. */
1696
+ .ig-skeleton-lines {
1697
+ display: flex;
1698
+ flex-direction: column;
1699
+ }
1700
+
1701
+ /* A placeholder row points at nothing, so it takes no pointer and no hover. */
1702
+ .ig-skeleton-row {
1703
+ pointer-events: none;
1704
+ }
1705
+
1706
+ .ig-skeleton-stage {
1707
+ align-items: center;
1708
+ display: flex;
1709
+ flex-direction: column;
1710
+ gap: var(--ig-space-wide);
1711
+ padding: var(--ig-space-wide);
1712
+ }
1713
+
1714
+ /* THE SPINE CARD'S OWN WIDTH, so the graph's placeholder sits where its cards
1715
+ will, as one solid block with the large radius the panel's cards use. */
1716
+ .ig-skeleton-block[data-shape='card'] {
1717
+ block-size: calc(var(--ig-station-box) * 4);
1718
+ border-radius: var(--ig-radius-large);
1719
+ inline-size: min(100%, var(--ig-spine-width));
1720
+ }
1721
+
1576
1722
  /* ── legend ────────────────────────────────────────────────────────────── */
1577
1723
 
1578
1724
  /* A FOOTER BAR, WHERE §16b PUTS IT, and worded rather than glyph-only. It sits
@@ -1591,8 +1737,29 @@ ${railDensity(".ig-viewer[data-density='dense']")}
1591
1737
  padding: var(--ig-space) var(--ig-space-wide);
1592
1738
  }
1593
1739
 
1740
+ /* FLOATED SO THE BROWSER STOPS TREATING IT AS A RENDERED LEGEND, which is the
1741
+ only reason this declaration exists. HTML gives the first in-flow legend of a
1742
+ fieldset a layout nothing else in CSS has: it is lifted OUT of the fieldset's
1743
+ content box, painted across the top border with a notch cut through the rule
1744
+ either side of it, and the content box then starts below it. On this bar that
1745
+ drew the caption sitting on the rule, a gap of bare ground under the caption
1746
+ where the fieldset's own surface should have been, and a top rule broken in
1747
+ the middle — all three of which looked like a styling bug and none of which
1748
+ any rule here asked for.
1749
+
1750
+ The spec's own escape hatch is the float: a legend whose computed float is
1751
+ not none is not the rendered legend, and becomes an ordinary child. This bar
1752
+ is a flex container, where float does not apply to an item at all, so the
1753
+ caption simply lays out as the first item on the row — which is what the
1754
+ flex rule above, its muted uppercase micro type and its centred alignment
1755
+ were always written for.
1756
+
1757
+ IT IS LOAD-BEARING AND LOOKS DECORATIVE, which is why it is recorded here at
1758
+ this length: delete it and the notch, the gap and the broken rule all come
1759
+ back, with nothing else in this file changed. */
1594
1760
  .ig-legend-caption {
1595
1761
  color: var(--ig-text-muted);
1762
+ float: left;
1596
1763
  font-family: var(--ig-font-mono);
1597
1764
  font-size: var(--ig-font-size-pill);
1598
1765
  letter-spacing: var(--ig-tracking-group);
@@ -1 +1 @@
1
- {"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO;EACP,KAAK;;;;;;;;;;EAUL,KAAK;;;;EAIL,KAAK;;;;;;;;;EASL,KAAK;;;;;;;;;;EAUL,KAAK;;;;;;;;;;;;;EAaL,KAAK;;;;;;;;EAQL,KAAK;;;;;;;;;;;;;;;;;;EAkBL,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BL,KAAK;;;;EAIL,KAAK;EACL,KAAK;;;;;;;EAOL,KAAK;;;;;;;;;;;;;;;;;;;EAmBL,KAAK;;;;;;;;EAQL,KAAK;;;;;;;;;;;;;;;;;;;EAmBL,KAAK;;;;;;;;;;;;;;EAcL,KAAK;;;;;;;;;;;;EAYL,KAAK;;;;;EAKL,KAAK;;;;;EAKL,KAAK;;;;;;;EAOL,KAAK;;;;;;;;;;;;;;;;;;;;;;;EAuBL,KAAK;EACL,KAAK;EACL,KAAK;;;;;;;;;;;;;;;;EAgBL,KAAK;;;;EAIL,KAAK;;;CAGN,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsc9B,WAAW,CAAC,gCAAgC,CAAC;;;;;;;;;;;;;EAa7C,WAAW,CAAC,kCAAkC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA80BhD,CAAC"}
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO;EACP,KAAK;;;;;;;;;;EAUL,KAAK;;;;EAIL,KAAK;;;;;;;;;EASL,KAAK;;;;;;;;;;EAUL,KAAK;;;;;;;;;;;;;EAaL,KAAK;;;;;;;;EAQL,KAAK;;;;;;;;;;;;;;;;;;EAkBL,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BL,KAAK;;;;EAIL,KAAK;EACL,KAAK;;;;;;;EAOL,KAAK;;;;;;;;;;;EAWL,KAAK;;;;;;;;;EASL,KAAK;;;;;;;;;;;;EAYL,KAAK;;;;;EAKL,KAAK;;;;;EAKL,KAAK;;;;;;;EAOL,KAAK;;;;;;;;;;;;;;;;;;;;;;;EAuBL,KAAK;EACL,KAAK;EACL,KAAK;;;;;;;;;;;;;;;;EAgBL,KAAK;;;;EAIL,KAAK;;;;;;;;;EASL,KAAK;;;;EAIL,KAAK;;;CAGN,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmf9B,WAAW,CAAC,gCAAgC,CAAC;;;;;;;;;;;;;EAa7C,WAAW,CAAC,kCAAkC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAm+BhD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@issuegraph/viewer",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "description": "Render an Issuegraph document as a work order — the ordered spine with readiness stations, the five edge treatments, and three projections. Rendering only: no fetching, no mutation, no auth, and every value themeable through CSS custom properties.",
5
5
  "keywords": [
6
6
  "issuegraph",