@issuegraph/viewer 0.1.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 +201 -0
- package/README.md +156 -0
- package/dist/clusters.d.ts +34 -0
- package/dist/clusters.d.ts.map +1 -0
- package/dist/clusters.js +195 -0
- package/dist/clusters.js.map +1 -0
- package/dist/document.d.ts +190 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +308 -0
- package/dist/document.js.map +1 -0
- package/dist/element.d.ts +95 -0
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +174 -0
- package/dist/element.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +122 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +297 -0
- package/dist/layout.js.map +1 -0
- package/dist/mount.d.ts +64 -0
- package/dist/mount.d.ts.map +1 -0
- package/dist/mount.js +402 -0
- package/dist/mount.js.map +1 -0
- package/dist/navigation.d.ts +56 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +121 -0
- package/dist/navigation.js.map +1 -0
- package/dist/parts.d.ts +89 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +214 -0
- package/dist/parts.js.map +1 -0
- package/dist/projections/graph.d.ts +30 -0
- package/dist/projections/graph.d.ts.map +1 -0
- package/dist/projections/graph.js +644 -0
- package/dist/projections/graph.js.map +1 -0
- package/dist/projections/linear.d.ts +35 -0
- package/dist/projections/linear.d.ts.map +1 -0
- package/dist/projections/linear.js +157 -0
- package/dist/projections/linear.js.map +1 -0
- package/dist/projections/tree.d.ts +17 -0
- package/dist/projections/tree.d.ts.map +1 -0
- package/dist/projections/tree.js +209 -0
- package/dist/projections/tree.js.map +1 -0
- package/dist/render.d.ts +35 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +45 -0
- package/dist/render.js.map +1 -0
- package/dist/scene.d.ts +94 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +48 -0
- package/dist/scene.js.map +1 -0
- package/dist/styles.d.ts +17 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +389 -0
- package/dist/styles.js.map +1 -0
- package/dist/theme.d.ts +71 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +169 -0
- package/dist/theme.js.map +1 -0
- package/dist/vocabulary.d.ts +107 -0
- package/dist/vocabulary.d.ts.map +1 -0
- package/dist/vocabulary.js +98 -0
- package/dist/vocabulary.js.map +1 -0
- package/package.json +56 -0
package/dist/scene.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a projection returns.
|
|
3
|
+
*
|
|
4
|
+
* A scene is the drawn tree PLUS the traversal it implies. Keeping the two
|
|
5
|
+
* together is what lets keyboard navigation walk the order rather than the
|
|
6
|
+
* picture: the graph projection places stations by geometry but publishes its
|
|
7
|
+
* `focusOrder` in rank order, and `navigate` reads only the published order. A
|
|
8
|
+
* projection that returned markup alone would leave every consumer to recover
|
|
9
|
+
* an order from coordinates, which is exactly the rule the design forbids.
|
|
10
|
+
*/
|
|
11
|
+
import type { ElementSpec } from './element.ts';
|
|
12
|
+
/** Which projection produced a scene. */
|
|
13
|
+
export type Projection = 'linear' | 'graph' | 'tree';
|
|
14
|
+
/** The neighbours a lateral key press moves to, when there are any. */
|
|
15
|
+
export interface LateralNeighbours {
|
|
16
|
+
readonly left?: string | undefined;
|
|
17
|
+
readonly right?: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
export interface Scene {
|
|
20
|
+
readonly projection: Projection;
|
|
21
|
+
readonly root: ElementSpec;
|
|
22
|
+
/**
|
|
23
|
+
* Every key the vertical keys walk, in the order they walk them. For the
|
|
24
|
+
* spine that is RANK ORDER — never the order boxes happen to sit in.
|
|
25
|
+
*/
|
|
26
|
+
readonly focusOrder: readonly string[];
|
|
27
|
+
/**
|
|
28
|
+
* Every key that can hold focus at all — `focusOrder` FIRST, then any key
|
|
29
|
+
* reachable only sideways.
|
|
30
|
+
*
|
|
31
|
+
* The two differ because a lateral move is not a position in the order: a
|
|
32
|
+
* gutter node is somewhere focus can GO without being somewhere `ArrowDown`
|
|
33
|
+
* ever stops. Resolving focus against `focusOrder` alone bounced focus off
|
|
34
|
+
* every such node on the next redraw, and rendered no tab stop for it — so
|
|
35
|
+
* membership questions ask this, and `focusOrder` answers ordering questions.
|
|
36
|
+
* `focusOrder` leads, so the first entry is the same under either.
|
|
37
|
+
*/
|
|
38
|
+
readonly navigable: readonly string[];
|
|
39
|
+
/** Key -> its lateral neighbours. Absent for keys with none. */
|
|
40
|
+
readonly lateral: ReadonlyMap<string, LateralNeighbours>;
|
|
41
|
+
/**
|
|
42
|
+
* Key -> the key that REPRESENTS it in this projection. Absent for a key
|
|
43
|
+
* that represents itself, which is almost all of them.
|
|
44
|
+
*
|
|
45
|
+
* A `together-with` unit is one station in the linear and graph projections
|
|
46
|
+
* and its partners get no row of their own, so `navigable` deliberately does
|
|
47
|
+
* not hold them. That made a selection of a partner unstateable: the key is a
|
|
48
|
+
* real issue, so every entry point accepted it, and then focus could not
|
|
49
|
+
* follow it anywhere — `resolveFocusKey` found it in no order and fell to the
|
|
50
|
+
* FIRST entry, an unrelated issue. The tree publishes an EMPTY map, because
|
|
51
|
+
* it draws every key its own row and a partner there is its own subject.
|
|
52
|
+
*
|
|
53
|
+
* THIS IS NOT A DROP. `reconcile` carries a selection across a projection
|
|
54
|
+
* switch whole, deliberately — a switch changes representation, never
|
|
55
|
+
* subject. Mapping a partner to its lead keeps that promise rather than
|
|
56
|
+
* breaking it: the unit IS the subject, and this is the name this projection
|
|
57
|
+
* has for it.
|
|
58
|
+
*/
|
|
59
|
+
readonly stationOf: ReadonlyMap<string, string>;
|
|
60
|
+
/** Anything the projection had to drop or refuse, for the host to surface. */
|
|
61
|
+
readonly diagnostics: readonly string[];
|
|
62
|
+
}
|
|
63
|
+
/** The attribute every focusable element carries, and the key it announces. */
|
|
64
|
+
export declare const KEY_ATTRIBUTE = "data-ig-key";
|
|
65
|
+
/**
|
|
66
|
+
* The attribute DECORATION carries — a `together-with` enclosure, its connector.
|
|
67
|
+
*
|
|
68
|
+
* Two different questions were being answered by one attribute. The FOCUS INDEX
|
|
69
|
+
* needs exactly one element per key, or `focus()` lands on whichever the
|
|
70
|
+
* renderer happened to emit first — which for the enclosure is a non-tabbable
|
|
71
|
+
* `<rect>` painted deliberately behind the node. POINTER identity is the other
|
|
72
|
+
* question, and the design fixes its answer: the connector IS a click target.
|
|
73
|
+
*
|
|
74
|
+
* Separating the attributes answers both. `keyAt` reads this one as a fallback
|
|
75
|
+
* so a click or hover on the enclosure still names its slot, while the index
|
|
76
|
+
* that drives focus sees only {@link KEY_ATTRIBUTE}.
|
|
77
|
+
*/
|
|
78
|
+
export declare const GROUP_ATTRIBUTE = "data-ig-group";
|
|
79
|
+
/**
|
|
80
|
+
* Which key holds the roving tab stop.
|
|
81
|
+
*
|
|
82
|
+
* ONE RULE, USED BY THE PROJECTIONS AND BY `reconcile`. They have to agree: the
|
|
83
|
+
* projections decide which element renders `tabindex="0"`, `reconcile` decides
|
|
84
|
+
* what `handle.state.focused` reports, and a viewer whose markup and whose
|
|
85
|
+
* reported state disagree is one a keyboard cannot enter. Two spellings of
|
|
86
|
+
* "which key is focused" is exactly how they came to disagree — a requested key
|
|
87
|
+
* the projection no longer draws rendered NO tab stop at all.
|
|
88
|
+
*
|
|
89
|
+
* Selection is the fallback before the first item because a switch changes
|
|
90
|
+
* representation, never subject: landing focus on what the reader was looking
|
|
91
|
+
* at beats landing it at the top.
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolveFocusKey(navigable: readonly string[], focused: string | null | undefined, selected: string | null | undefined): string | null;
|
|
94
|
+
//# sourceMappingURL=scene.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene.d.ts","sourceRoot":"","sources":["../src/scene.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,yCAAyC;AACzC,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAErD,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IACzD;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,+EAA+E;AAC/E,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAE3C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAE/C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAClC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,MAAM,GAAG,IAAI,CAIf"}
|
package/dist/scene.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a projection returns.
|
|
3
|
+
*
|
|
4
|
+
* A scene is the drawn tree PLUS the traversal it implies. Keeping the two
|
|
5
|
+
* together is what lets keyboard navigation walk the order rather than the
|
|
6
|
+
* picture: the graph projection places stations by geometry but publishes its
|
|
7
|
+
* `focusOrder` in rank order, and `navigate` reads only the published order. A
|
|
8
|
+
* projection that returned markup alone would leave every consumer to recover
|
|
9
|
+
* an order from coordinates, which is exactly the rule the design forbids.
|
|
10
|
+
*/
|
|
11
|
+
/** The attribute every focusable element carries, and the key it announces. */
|
|
12
|
+
export const KEY_ATTRIBUTE = 'data-ig-key';
|
|
13
|
+
/**
|
|
14
|
+
* The attribute DECORATION carries — a `together-with` enclosure, its connector.
|
|
15
|
+
*
|
|
16
|
+
* Two different questions were being answered by one attribute. The FOCUS INDEX
|
|
17
|
+
* needs exactly one element per key, or `focus()` lands on whichever the
|
|
18
|
+
* renderer happened to emit first — which for the enclosure is a non-tabbable
|
|
19
|
+
* `<rect>` painted deliberately behind the node. POINTER identity is the other
|
|
20
|
+
* question, and the design fixes its answer: the connector IS a click target.
|
|
21
|
+
*
|
|
22
|
+
* Separating the attributes answers both. `keyAt` reads this one as a fallback
|
|
23
|
+
* so a click or hover on the enclosure still names its slot, while the index
|
|
24
|
+
* that drives focus sees only {@link KEY_ATTRIBUTE}.
|
|
25
|
+
*/
|
|
26
|
+
export const GROUP_ATTRIBUTE = 'data-ig-group';
|
|
27
|
+
/**
|
|
28
|
+
* Which key holds the roving tab stop.
|
|
29
|
+
*
|
|
30
|
+
* ONE RULE, USED BY THE PROJECTIONS AND BY `reconcile`. They have to agree: the
|
|
31
|
+
* projections decide which element renders `tabindex="0"`, `reconcile` decides
|
|
32
|
+
* what `handle.state.focused` reports, and a viewer whose markup and whose
|
|
33
|
+
* reported state disagree is one a keyboard cannot enter. Two spellings of
|
|
34
|
+
* "which key is focused" is exactly how they came to disagree — a requested key
|
|
35
|
+
* the projection no longer draws rendered NO tab stop at all.
|
|
36
|
+
*
|
|
37
|
+
* Selection is the fallback before the first item because a switch changes
|
|
38
|
+
* representation, never subject: landing focus on what the reader was looking
|
|
39
|
+
* at beats landing it at the top.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveFocusKey(navigable, focused, selected) {
|
|
42
|
+
if (focused !== null && focused !== undefined && navigable.includes(focused))
|
|
43
|
+
return focused;
|
|
44
|
+
if (selected !== null && selected !== undefined && navigable.includes(selected))
|
|
45
|
+
return selected;
|
|
46
|
+
return navigable[0] ?? null;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=scene.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene.js","sourceRoot":"","sources":["../src/scene.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA0DH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;AAE3C;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,eAAe,CAAC;AAE/C;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAC7B,SAA4B,EAC5B,OAAkC,EAClC,QAAmC;IAEnC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAC7F,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IACjG,OAAO,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC9B,CAAC"}
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The structural stylesheet.
|
|
3
|
+
*
|
|
4
|
+
* It carries layout, weight and state — never a value. Every colour, size and
|
|
5
|
+
* spacing here is a `var(--ig-…)` reference resolved by whatever theme the host
|
|
6
|
+
* installs, which is what makes "supply a second theme through custom
|
|
7
|
+
* properties" a real capability rather than a claim. `styles.test.ts` scans
|
|
8
|
+
* this string for a literal colour or a fixed pixel length and fails on either,
|
|
9
|
+
* so the rule is enforced against the bytes rather than remembered.
|
|
10
|
+
*
|
|
11
|
+
* Shipped as a string, not a `.css` file: an entry that imports CSS cannot be
|
|
12
|
+
* loaded by a bare Node runtime (`ERR_UNKNOWN_FILE_EXTENSION`), and this
|
|
13
|
+
* package's floor is checked by a smoke test that imports the built entry. A
|
|
14
|
+
* string also means no consumer needs a bundler to use it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const viewerStylesheet = "\n.ig-viewer {\n background: var(--ig-bg);\n color: var(--ig-text-body);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size);\n line-height: var(--ig-line-height);\n position: relative;\n}\n\n.ig-viewer *,\n.ig-viewer *::before,\n.ig-viewer *::after {\n box-sizing: border-box;\n}\n\n.ig-viewer :focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-space-tight);\n}\n\n.ig-list {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n/* \u2500\u2500 the spine \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.ig-slot {\n align-items: center;\n background: var(--ig-surface);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n column-gap: var(--ig-space);\n display: grid;\n grid-template-columns: auto auto 1fr auto;\n margin-bottom: var(--ig-space-tight);\n min-height: var(--ig-row-height);\n padding: var(--ig-space-tight) var(--ig-space);\n}\n\n.ig-slot[data-held='true'] {\n background: var(--ig-surface-2);\n border-style: dashed;\n}\n\n.ig-slot[aria-current='true'] {\n border-color: var(--ig-accent);\n}\n\n/* A CANVAS-OWNED NODE IS SELECTABLE TOO, and only the rail rows had a selected\n look \u2014 so clicking a gutter, excluded or tracker-held node set aria-current on\n the group and changed nothing a reader could see. A pointer does not normally\n raise :focus-visible either, so those selections had no visible state at all\n on the channel most likely to make them. Same accent the rail uses, so one\n selection reads the same whichever surface drew it. */\n.ig-node-group[aria-current='true'] .ig-node {\n stroke: var(--ig-accent);\n stroke-width: calc(var(--ig-stroke) * 2);\n}\n\n.ig-rank {\n color: var(--ig-text);\n font-family: var(--ig-font-mono);\n font-variant-numeric: tabular-nums;\n min-width: calc(var(--ig-char-width) * 4);\n text-align: right;\n}\n\n.ig-rank[data-held='true'] {\n color: var(--ig-text-muted);\n}\n\n/* \u2500\u2500 readiness stations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.ig-station {\n block-size: var(--ig-station-size);\n border: var(--ig-stroke) solid var(--ig-station-held);\n border-radius: 50%;\n box-shadow: 0 0 0 var(--ig-station-halo) var(--ig-bg);\n display: inline-block;\n inline-size: var(--ig-station-size);\n}\n\n.ig-station[data-fill='filled'] {\n background: var(--ig-station-ready);\n border-color: var(--ig-station-ready);\n}\n\n.ig-station[data-fill='hollow'] {\n background: transparent;\n border-color: var(--ig-station-pending);\n}\n\n.ig-station[data-fill='dashed'] {\n background: transparent;\n border-style: dashed;\n border-color: var(--ig-station-held);\n}\n\n/* \u2500\u2500 titles, identity, provenance \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.ig-title {\n color: var(--ig-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.ig-id,\n.ig-count {\n color: var(--ig-text-muted);\n font-family: var(--ig-font-mono);\n font-variant-numeric: tabular-nums;\n}\n\n.ig-link {\n color: var(--ig-accent);\n text-decoration: none;\n}\n\n.ig-link:hover {\n text-decoration: underline;\n}\n\n.ig-provenance,\n.ig-hold {\n color: var(--ig-text-muted);\n font-size: var(--ig-font-size-small);\n grid-column: 3 / -1;\n}\n\n.ig-strike {\n text-decoration: line-through;\n}\n\n/* \u2500\u2500 edge badges \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* SPANNED FROM THE TITLE COLUMN, LIKE EVERY OTHER FULL-WIDTH ROW MEMBER. Left\n unplaced this was the fifth child of a four-column grid, so it auto-placed\n into column ONE of a second row \u2014 the rank track, an auto-sized column that\n takes the width of its widest item. One relationship badge then set the width\n of the rank column for the whole row, pushing the station and title across\n and squeezing the title to pay for it. Badges belong with the row's other\n metadata, which is what 3 / -1 already means here for provenance and holds.\n (No backticks in this file: the stylesheet is a template literal.) */\n.ig-badges {\n display: flex;\n flex-wrap: wrap;\n gap: var(--ig-space-tight);\n grid-column: 3 / -1;\n}\n\n.ig-badge {\n align-items: center;\n /* Not currentColor any more: the label and the border now carry DIFFERENT\n colours deliberately \u2014 see below. Left as currentColor the border would\n follow the text to --ig-text and the hue channel would vanish from the\n badge entirely, which is the opposite of the fix. */\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n color: var(--ig-text);\n display: inline-flex;\n font-size: var(--ig-font-size-small);\n gap: var(--ig-space-tight);\n padding: 0 var(--ig-space-tight);\n}\n\n/* THE HUE IS A BORDER COLOUR HERE, NOT A TEXT COLOUR \u2014 the theme holds the\n edge hues to the 3:1 NON-TEXT bar, and says so where it defines them, so\n painting badge LABELS with them contradicted the palette's own claim. It was\n not merely theoretical: at this size duplicate-of measured 3.98:1 on\n --ig-surface and decomposed-from 4.37:1, both under the 4.5:1 the text test\n asserts for every text colour. The label now takes --ig-text, which that\n test already proves on all three surfaces, and the hue keeps the non-text\n use it was measured for.\n NO CHANNEL IS LOST. Hue is one of four redundant channels and it is still\n carried by the border; the dash pattern and the glyph are untouched; and the\n vocabulary test independently proves all five stay distinguishable with hue\n removed ENTIRELY, which is the stronger claim. */\n.ig-badge[data-edge='blocked-by'] { border-color: var(--ig-edge-blocked-by); border-style: solid; }\n.ig-badge[data-edge='serialize-with'] { border-color: var(--ig-edge-serialize-with); border-style: double; }\n.ig-badge[data-edge='together-with'] { border-color: var(--ig-edge-together-with); border-style: solid; }\n.ig-badge[data-edge='duplicate-of'] { border-color: var(--ig-edge-duplicate-of); border-style: dotted; }\n.ig-badge[data-edge='decomposed-from'] { border-color: var(--ig-edge-decomposed-from); border-style: dashed; }\n\n.ig-glyph {\n font-family: var(--ig-font-mono);\n}\n\n/* \u2500\u2500 the footer group: holds that earn no rank slot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.ig-footer {\n border-top: var(--ig-stroke) solid var(--ig-line);\n margin-top: var(--ig-space);\n padding-top: var(--ig-space);\n}\n\n.ig-footer-title {\n color: var(--ig-text-muted);\n font-size: var(--ig-font-size-small);\n margin: 0 0 var(--ig-space-tight);\n}\n\n/* \u2500\u2500 the graph canvas \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* The stage carries the LAYOUT's own size, so one SVG unit is one CSS pixel\n and an absolutely-positioned rail row lands on the node it names. A\n percentage-width canvas would rescale under the rail and drift. It scrolls\n rather than shrinking, because shrinking would silently break that. */\n.ig-stage {\n block-size: var(--ig-stage-h);\n inline-size: var(--ig-stage-w);\n max-inline-size: 100%;\n overflow-x: auto;\n overflow-y: hidden;\n position: relative;\n}\n\n.ig-canvas {\n block-size: var(--ig-stage-h);\n display: block;\n inline-size: var(--ig-stage-w);\n}\n\n/* The ranks and stations FOR the spine nodes, sitting on them. */\n.ig-rail {\n inset: 0;\n pointer-events: none;\n position: absolute;\n}\n\n.ig-rail-row {\n block-size: var(--ig-row-h);\n inline-size: var(--ig-row-w);\n inset-block-start: var(--ig-row-y);\n inset-inline-start: var(--ig-row-x);\n margin-bottom: 0;\n min-block-size: 0;\n pointer-events: auto;\n position: absolute;\n}\n\n.ig-node {\n fill: var(--ig-surface);\n stroke: var(--ig-line);\n stroke-width: var(--ig-stroke);\n}\n\n.ig-node[data-held='true'] {\n fill: var(--ig-surface-2);\n stroke-dasharray: 4 3;\n}\n\n.ig-node-label {\n fill: var(--ig-text);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size-small);\n}\n\n.ig-edge {\n fill: none;\n stroke-width: var(--ig-stroke);\n}\n\n.ig-edge[data-edge='blocked-by'] { stroke: var(--ig-edge-blocked-by); }\n.ig-edge[data-edge='serialize-with'] { stroke: var(--ig-edge-serialize-with); }\n.ig-edge[data-edge='together-with'] { stroke: var(--ig-edge-together-with); }\n.ig-edge[data-edge='duplicate-of'] { stroke: var(--ig-edge-duplicate-of); }\n.ig-edge[data-edge='decomposed-from'] { stroke: var(--ig-edge-decomposed-from); }\n\n/* The dash pattern is set per element from the edge vocabulary, never here \u2014\n one source for the channel the colour-blind-safety claim rests on. */\n.ig-enclosure {\n fill: none;\n stroke: var(--ig-edge-together-with);\n stroke-width: var(--ig-stroke);\n}\n\n.ig-connector {\n stroke: var(--ig-edge-together-with);\n stroke-width: var(--ig-stroke-connector);\n}\n\n/* currentColor on the marker resolves to the inherited text colour, not to the\n edge's stroke \u2014 so a terminal has to be given the hue explicitly or it renders\n in body text and the fourth channel silently collapses. */\n.ig-terminal {\n color: var(--ig-text-body);\n stroke-width: var(--ig-stroke);\n}\n\n.ig-terminal[data-edge='blocked-by'] { color: var(--ig-edge-blocked-by); }\n.ig-terminal[data-edge='serialize-with'] { color: var(--ig-edge-serialize-with); }\n.ig-terminal[data-edge='together-with'] { color: var(--ig-edge-together-with); }\n.ig-terminal[data-edge='duplicate-of'] { color: var(--ig-edge-duplicate-of); }\n.ig-terminal[data-edge='decomposed-from'] { color: var(--ig-edge-decomposed-from); }\n\n/* \u2500\u2500 refusals and empty states \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.ig-refusal,\n.ig-empty {\n background: var(--ig-surface-2);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n color: var(--ig-text-body);\n padding: var(--ig-space);\n}\n\n.ig-refusal-next {\n color: var(--ig-text);\n margin: var(--ig-space-tight) 0 0;\n}\n\n/* The capsule is INFORMATIONAL \u2014 a plain list item again. It was briefly a\n button, to make the refusal's advertised action keyboard-reachable; the\n action itself has since gone, because this package cannot narrow a document\n and so could never complete it. With the control removed the UA button reset\n goes too: there is no button look left to undo. */\n.ig-capsule {\n align-items: baseline;\n background: var(--ig-surface);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n display: flex;\n gap: var(--ig-space);\n margin-top: var(--ig-space-tight);\n padding: var(--ig-space-tight) var(--ig-space);\n}\n\n.ig-refusal-omitted {\n color: var(--ig-text-muted);\n font-size: var(--ig-font-size-small);\n margin: var(--ig-space-tight) 0 0;\n}\n\n/* \u2500\u2500 the decomposition tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.ig-tree,\n.ig-tree .ig-list {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.ig-tree .ig-list {\n border-left: var(--ig-stroke) dashed var(--ig-edge-decomposed-from);\n margin-left: var(--ig-space);\n padding-left: var(--ig-space);\n}\n\n.ig-tree-item {\n padding: var(--ig-space-tight) 0;\n}\n\n/* \u2500\u2500 legend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.ig-legend {\n border: 0;\n display: flex;\n flex-wrap: wrap;\n gap: var(--ig-space);\n margin: 0 0 var(--ig-space);\n padding: 0;\n}\n\n.ig-legend-caption {\n color: var(--ig-text-muted);\n font-size: var(--ig-font-size-small);\n padding: 0;\n}\n";
|
|
17
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,gBAAgB,i6aAoX5B,CAAC"}
|
package/dist/styles.js
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The structural stylesheet.
|
|
3
|
+
*
|
|
4
|
+
* It carries layout, weight and state — never a value. Every colour, size and
|
|
5
|
+
* spacing here is a `var(--ig-…)` reference resolved by whatever theme the host
|
|
6
|
+
* installs, which is what makes "supply a second theme through custom
|
|
7
|
+
* properties" a real capability rather than a claim. `styles.test.ts` scans
|
|
8
|
+
* this string for a literal colour or a fixed pixel length and fails on either,
|
|
9
|
+
* so the rule is enforced against the bytes rather than remembered.
|
|
10
|
+
*
|
|
11
|
+
* Shipped as a string, not a `.css` file: an entry that imports CSS cannot be
|
|
12
|
+
* loaded by a bare Node runtime (`ERR_UNKNOWN_FILE_EXTENSION`), and this
|
|
13
|
+
* package's floor is checked by a smoke test that imports the built entry. A
|
|
14
|
+
* string also means no consumer needs a bundler to use it.
|
|
15
|
+
*/
|
|
16
|
+
export const viewerStylesheet = `
|
|
17
|
+
.ig-viewer {
|
|
18
|
+
background: var(--ig-bg);
|
|
19
|
+
color: var(--ig-text-body);
|
|
20
|
+
font-family: var(--ig-font-ui);
|
|
21
|
+
font-size: var(--ig-font-size);
|
|
22
|
+
line-height: var(--ig-line-height);
|
|
23
|
+
position: relative;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.ig-viewer *,
|
|
27
|
+
.ig-viewer *::before,
|
|
28
|
+
.ig-viewer *::after {
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ig-viewer :focus-visible {
|
|
33
|
+
outline: var(--ig-focus-ring) solid var(--ig-focus);
|
|
34
|
+
outline-offset: var(--ig-space-tight);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.ig-list {
|
|
38
|
+
list-style: none;
|
|
39
|
+
margin: 0;
|
|
40
|
+
padding: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ── the spine ─────────────────────────────────────────────────────────── */
|
|
44
|
+
|
|
45
|
+
.ig-slot {
|
|
46
|
+
align-items: center;
|
|
47
|
+
background: var(--ig-surface);
|
|
48
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
49
|
+
border-radius: var(--ig-radius);
|
|
50
|
+
column-gap: var(--ig-space);
|
|
51
|
+
display: grid;
|
|
52
|
+
grid-template-columns: auto auto 1fr auto;
|
|
53
|
+
margin-bottom: var(--ig-space-tight);
|
|
54
|
+
min-height: var(--ig-row-height);
|
|
55
|
+
padding: var(--ig-space-tight) var(--ig-space);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.ig-slot[data-held='true'] {
|
|
59
|
+
background: var(--ig-surface-2);
|
|
60
|
+
border-style: dashed;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.ig-slot[aria-current='true'] {
|
|
64
|
+
border-color: var(--ig-accent);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* A CANVAS-OWNED NODE IS SELECTABLE TOO, and only the rail rows had a selected
|
|
68
|
+
look — so clicking a gutter, excluded or tracker-held node set aria-current on
|
|
69
|
+
the group and changed nothing a reader could see. A pointer does not normally
|
|
70
|
+
raise :focus-visible either, so those selections had no visible state at all
|
|
71
|
+
on the channel most likely to make them. Same accent the rail uses, so one
|
|
72
|
+
selection reads the same whichever surface drew it. */
|
|
73
|
+
.ig-node-group[aria-current='true'] .ig-node {
|
|
74
|
+
stroke: var(--ig-accent);
|
|
75
|
+
stroke-width: calc(var(--ig-stroke) * 2);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.ig-rank {
|
|
79
|
+
color: var(--ig-text);
|
|
80
|
+
font-family: var(--ig-font-mono);
|
|
81
|
+
font-variant-numeric: tabular-nums;
|
|
82
|
+
min-width: calc(var(--ig-char-width) * 4);
|
|
83
|
+
text-align: right;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.ig-rank[data-held='true'] {
|
|
87
|
+
color: var(--ig-text-muted);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* ── readiness stations ────────────────────────────────────────────────── */
|
|
91
|
+
|
|
92
|
+
.ig-station {
|
|
93
|
+
block-size: var(--ig-station-size);
|
|
94
|
+
border: var(--ig-stroke) solid var(--ig-station-held);
|
|
95
|
+
border-radius: 50%;
|
|
96
|
+
box-shadow: 0 0 0 var(--ig-station-halo) var(--ig-bg);
|
|
97
|
+
display: inline-block;
|
|
98
|
+
inline-size: var(--ig-station-size);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.ig-station[data-fill='filled'] {
|
|
102
|
+
background: var(--ig-station-ready);
|
|
103
|
+
border-color: var(--ig-station-ready);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.ig-station[data-fill='hollow'] {
|
|
107
|
+
background: transparent;
|
|
108
|
+
border-color: var(--ig-station-pending);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.ig-station[data-fill='dashed'] {
|
|
112
|
+
background: transparent;
|
|
113
|
+
border-style: dashed;
|
|
114
|
+
border-color: var(--ig-station-held);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* ── titles, identity, provenance ──────────────────────────────────────── */
|
|
118
|
+
|
|
119
|
+
.ig-title {
|
|
120
|
+
color: var(--ig-text);
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
text-overflow: ellipsis;
|
|
123
|
+
white-space: nowrap;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.ig-id,
|
|
127
|
+
.ig-count {
|
|
128
|
+
color: var(--ig-text-muted);
|
|
129
|
+
font-family: var(--ig-font-mono);
|
|
130
|
+
font-variant-numeric: tabular-nums;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.ig-link {
|
|
134
|
+
color: var(--ig-accent);
|
|
135
|
+
text-decoration: none;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.ig-link:hover {
|
|
139
|
+
text-decoration: underline;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.ig-provenance,
|
|
143
|
+
.ig-hold {
|
|
144
|
+
color: var(--ig-text-muted);
|
|
145
|
+
font-size: var(--ig-font-size-small);
|
|
146
|
+
grid-column: 3 / -1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.ig-strike {
|
|
150
|
+
text-decoration: line-through;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* ── edge badges ───────────────────────────────────────────────────────── */
|
|
154
|
+
|
|
155
|
+
/* SPANNED FROM THE TITLE COLUMN, LIKE EVERY OTHER FULL-WIDTH ROW MEMBER. Left
|
|
156
|
+
unplaced this was the fifth child of a four-column grid, so it auto-placed
|
|
157
|
+
into column ONE of a second row — the rank track, an auto-sized column that
|
|
158
|
+
takes the width of its widest item. One relationship badge then set the width
|
|
159
|
+
of the rank column for the whole row, pushing the station and title across
|
|
160
|
+
and squeezing the title to pay for it. Badges belong with the row's other
|
|
161
|
+
metadata, which is what 3 / -1 already means here for provenance and holds.
|
|
162
|
+
(No backticks in this file: the stylesheet is a template literal.) */
|
|
163
|
+
.ig-badges {
|
|
164
|
+
display: flex;
|
|
165
|
+
flex-wrap: wrap;
|
|
166
|
+
gap: var(--ig-space-tight);
|
|
167
|
+
grid-column: 3 / -1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.ig-badge {
|
|
171
|
+
align-items: center;
|
|
172
|
+
/* Not currentColor any more: the label and the border now carry DIFFERENT
|
|
173
|
+
colours deliberately — see below. Left as currentColor the border would
|
|
174
|
+
follow the text to --ig-text and the hue channel would vanish from the
|
|
175
|
+
badge entirely, which is the opposite of the fix. */
|
|
176
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
177
|
+
border-radius: var(--ig-radius);
|
|
178
|
+
color: var(--ig-text);
|
|
179
|
+
display: inline-flex;
|
|
180
|
+
font-size: var(--ig-font-size-small);
|
|
181
|
+
gap: var(--ig-space-tight);
|
|
182
|
+
padding: 0 var(--ig-space-tight);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* THE HUE IS A BORDER COLOUR HERE, NOT A TEXT COLOUR — the theme holds the
|
|
186
|
+
edge hues to the 3:1 NON-TEXT bar, and says so where it defines them, so
|
|
187
|
+
painting badge LABELS with them contradicted the palette's own claim. It was
|
|
188
|
+
not merely theoretical: at this size duplicate-of measured 3.98:1 on
|
|
189
|
+
--ig-surface and decomposed-from 4.37:1, both under the 4.5:1 the text test
|
|
190
|
+
asserts for every text colour. The label now takes --ig-text, which that
|
|
191
|
+
test already proves on all three surfaces, and the hue keeps the non-text
|
|
192
|
+
use it was measured for.
|
|
193
|
+
NO CHANNEL IS LOST. Hue is one of four redundant channels and it is still
|
|
194
|
+
carried by the border; the dash pattern and the glyph are untouched; and the
|
|
195
|
+
vocabulary test independently proves all five stay distinguishable with hue
|
|
196
|
+
removed ENTIRELY, which is the stronger claim. */
|
|
197
|
+
.ig-badge[data-edge='blocked-by'] { border-color: var(--ig-edge-blocked-by); border-style: solid; }
|
|
198
|
+
.ig-badge[data-edge='serialize-with'] { border-color: var(--ig-edge-serialize-with); border-style: double; }
|
|
199
|
+
.ig-badge[data-edge='together-with'] { border-color: var(--ig-edge-together-with); border-style: solid; }
|
|
200
|
+
.ig-badge[data-edge='duplicate-of'] { border-color: var(--ig-edge-duplicate-of); border-style: dotted; }
|
|
201
|
+
.ig-badge[data-edge='decomposed-from'] { border-color: var(--ig-edge-decomposed-from); border-style: dashed; }
|
|
202
|
+
|
|
203
|
+
.ig-glyph {
|
|
204
|
+
font-family: var(--ig-font-mono);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* ── the footer group: holds that earn no rank slot ────────────────────── */
|
|
208
|
+
|
|
209
|
+
.ig-footer {
|
|
210
|
+
border-top: var(--ig-stroke) solid var(--ig-line);
|
|
211
|
+
margin-top: var(--ig-space);
|
|
212
|
+
padding-top: var(--ig-space);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.ig-footer-title {
|
|
216
|
+
color: var(--ig-text-muted);
|
|
217
|
+
font-size: var(--ig-font-size-small);
|
|
218
|
+
margin: 0 0 var(--ig-space-tight);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* ── the graph canvas ──────────────────────────────────────────────────── */
|
|
222
|
+
|
|
223
|
+
/* The stage carries the LAYOUT's own size, so one SVG unit is one CSS pixel
|
|
224
|
+
and an absolutely-positioned rail row lands on the node it names. A
|
|
225
|
+
percentage-width canvas would rescale under the rail and drift. It scrolls
|
|
226
|
+
rather than shrinking, because shrinking would silently break that. */
|
|
227
|
+
.ig-stage {
|
|
228
|
+
block-size: var(--ig-stage-h);
|
|
229
|
+
inline-size: var(--ig-stage-w);
|
|
230
|
+
max-inline-size: 100%;
|
|
231
|
+
overflow-x: auto;
|
|
232
|
+
overflow-y: hidden;
|
|
233
|
+
position: relative;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.ig-canvas {
|
|
237
|
+
block-size: var(--ig-stage-h);
|
|
238
|
+
display: block;
|
|
239
|
+
inline-size: var(--ig-stage-w);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* The ranks and stations FOR the spine nodes, sitting on them. */
|
|
243
|
+
.ig-rail {
|
|
244
|
+
inset: 0;
|
|
245
|
+
pointer-events: none;
|
|
246
|
+
position: absolute;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.ig-rail-row {
|
|
250
|
+
block-size: var(--ig-row-h);
|
|
251
|
+
inline-size: var(--ig-row-w);
|
|
252
|
+
inset-block-start: var(--ig-row-y);
|
|
253
|
+
inset-inline-start: var(--ig-row-x);
|
|
254
|
+
margin-bottom: 0;
|
|
255
|
+
min-block-size: 0;
|
|
256
|
+
pointer-events: auto;
|
|
257
|
+
position: absolute;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.ig-node {
|
|
261
|
+
fill: var(--ig-surface);
|
|
262
|
+
stroke: var(--ig-line);
|
|
263
|
+
stroke-width: var(--ig-stroke);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.ig-node[data-held='true'] {
|
|
267
|
+
fill: var(--ig-surface-2);
|
|
268
|
+
stroke-dasharray: 4 3;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.ig-node-label {
|
|
272
|
+
fill: var(--ig-text);
|
|
273
|
+
font-family: var(--ig-font-ui);
|
|
274
|
+
font-size: var(--ig-font-size-small);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.ig-edge {
|
|
278
|
+
fill: none;
|
|
279
|
+
stroke-width: var(--ig-stroke);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.ig-edge[data-edge='blocked-by'] { stroke: var(--ig-edge-blocked-by); }
|
|
283
|
+
.ig-edge[data-edge='serialize-with'] { stroke: var(--ig-edge-serialize-with); }
|
|
284
|
+
.ig-edge[data-edge='together-with'] { stroke: var(--ig-edge-together-with); }
|
|
285
|
+
.ig-edge[data-edge='duplicate-of'] { stroke: var(--ig-edge-duplicate-of); }
|
|
286
|
+
.ig-edge[data-edge='decomposed-from'] { stroke: var(--ig-edge-decomposed-from); }
|
|
287
|
+
|
|
288
|
+
/* The dash pattern is set per element from the edge vocabulary, never here —
|
|
289
|
+
one source for the channel the colour-blind-safety claim rests on. */
|
|
290
|
+
.ig-enclosure {
|
|
291
|
+
fill: none;
|
|
292
|
+
stroke: var(--ig-edge-together-with);
|
|
293
|
+
stroke-width: var(--ig-stroke);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.ig-connector {
|
|
297
|
+
stroke: var(--ig-edge-together-with);
|
|
298
|
+
stroke-width: var(--ig-stroke-connector);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* currentColor on the marker resolves to the inherited text colour, not to the
|
|
302
|
+
edge's stroke — so a terminal has to be given the hue explicitly or it renders
|
|
303
|
+
in body text and the fourth channel silently collapses. */
|
|
304
|
+
.ig-terminal {
|
|
305
|
+
color: var(--ig-text-body);
|
|
306
|
+
stroke-width: var(--ig-stroke);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.ig-terminal[data-edge='blocked-by'] { color: var(--ig-edge-blocked-by); }
|
|
310
|
+
.ig-terminal[data-edge='serialize-with'] { color: var(--ig-edge-serialize-with); }
|
|
311
|
+
.ig-terminal[data-edge='together-with'] { color: var(--ig-edge-together-with); }
|
|
312
|
+
.ig-terminal[data-edge='duplicate-of'] { color: var(--ig-edge-duplicate-of); }
|
|
313
|
+
.ig-terminal[data-edge='decomposed-from'] { color: var(--ig-edge-decomposed-from); }
|
|
314
|
+
|
|
315
|
+
/* ── refusals and empty states ─────────────────────────────────────────── */
|
|
316
|
+
|
|
317
|
+
.ig-refusal,
|
|
318
|
+
.ig-empty {
|
|
319
|
+
background: var(--ig-surface-2);
|
|
320
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
321
|
+
border-radius: var(--ig-radius);
|
|
322
|
+
color: var(--ig-text-body);
|
|
323
|
+
padding: var(--ig-space);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.ig-refusal-next {
|
|
327
|
+
color: var(--ig-text);
|
|
328
|
+
margin: var(--ig-space-tight) 0 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* The capsule is INFORMATIONAL — a plain list item again. It was briefly a
|
|
332
|
+
button, to make the refusal's advertised action keyboard-reachable; the
|
|
333
|
+
action itself has since gone, because this package cannot narrow a document
|
|
334
|
+
and so could never complete it. With the control removed the UA button reset
|
|
335
|
+
goes too: there is no button look left to undo. */
|
|
336
|
+
.ig-capsule {
|
|
337
|
+
align-items: baseline;
|
|
338
|
+
background: var(--ig-surface);
|
|
339
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
340
|
+
border-radius: var(--ig-radius);
|
|
341
|
+
display: flex;
|
|
342
|
+
gap: var(--ig-space);
|
|
343
|
+
margin-top: var(--ig-space-tight);
|
|
344
|
+
padding: var(--ig-space-tight) var(--ig-space);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.ig-refusal-omitted {
|
|
348
|
+
color: var(--ig-text-muted);
|
|
349
|
+
font-size: var(--ig-font-size-small);
|
|
350
|
+
margin: var(--ig-space-tight) 0 0;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* ── the decomposition tree ────────────────────────────────────────────── */
|
|
354
|
+
|
|
355
|
+
.ig-tree,
|
|
356
|
+
.ig-tree .ig-list {
|
|
357
|
+
list-style: none;
|
|
358
|
+
margin: 0;
|
|
359
|
+
padding: 0;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.ig-tree .ig-list {
|
|
363
|
+
border-left: var(--ig-stroke) dashed var(--ig-edge-decomposed-from);
|
|
364
|
+
margin-left: var(--ig-space);
|
|
365
|
+
padding-left: var(--ig-space);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.ig-tree-item {
|
|
369
|
+
padding: var(--ig-space-tight) 0;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* ── legend ────────────────────────────────────────────────────────────── */
|
|
373
|
+
|
|
374
|
+
.ig-legend {
|
|
375
|
+
border: 0;
|
|
376
|
+
display: flex;
|
|
377
|
+
flex-wrap: wrap;
|
|
378
|
+
gap: var(--ig-space);
|
|
379
|
+
margin: 0 0 var(--ig-space);
|
|
380
|
+
padding: 0;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.ig-legend-caption {
|
|
384
|
+
color: var(--ig-text-muted);
|
|
385
|
+
font-size: var(--ig-font-size-small);
|
|
386
|
+
padding: 0;
|
|
387
|
+
}
|
|
388
|
+
`;
|
|
389
|
+
//# sourceMappingURL=styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoX/B,CAAC"}
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The theme: every value the viewer draws with, in one place, emitted as CSS
|
|
3
|
+
* custom properties.
|
|
4
|
+
*
|
|
5
|
+
* THE SHIPPED PALETTE IS THE DEFAULT THEME, NOT THE STYLING. It is dark, and
|
|
6
|
+
* light mode is deliberately absent rather than forgotten — a host that wants
|
|
7
|
+
* one supplies it through these same properties, which is exactly what makes
|
|
8
|
+
* this a theme rather than a look. The forcing function is that the package has
|
|
9
|
+
* to render two of them from one set of components; if a value cannot be
|
|
10
|
+
* overridden here, it is a bug in this file and not a styling choice elsewhere.
|
|
11
|
+
*
|
|
12
|
+
* GEOMETRY IS SINGLE-SOURCED AS NUMBERS. Layout maths needs real numbers (an
|
|
13
|
+
* SVG endpoint is a coordinate, not a `var()`), and CSS needs custom
|
|
14
|
+
* properties. Declaring both by hand would be two sources that drift, so the
|
|
15
|
+
* numbers are canonical and {@link themeCss} renders the properties FROM them.
|
|
16
|
+
* Retheming geometry therefore moves the drawing and the stylesheet together.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Every colour the viewer uses, by token name. Declared as a tuple so the
|
|
20
|
+
* token list and the theme type cannot disagree: a colour added here is a
|
|
21
|
+
* compile error in every theme that does not supply it.
|
|
22
|
+
*/
|
|
23
|
+
export declare const COLOR_TOKENS: readonly ["--ig-bg", "--ig-surface", "--ig-surface-2", "--ig-line", "--ig-text", "--ig-text-body", "--ig-text-muted", "--ig-accent", "--ig-focus", "--ig-station-ready", "--ig-station-pending", "--ig-station-held", "--ig-edge-blocked-by", "--ig-edge-serialize-with", "--ig-edge-together-with", "--ig-edge-duplicate-of", "--ig-edge-decomposed-from"];
|
|
24
|
+
export type ColorToken = (typeof COLOR_TOKENS)[number];
|
|
25
|
+
/** Type tokens. `lineHeight` is unitless; the rest carry their own units. */
|
|
26
|
+
export declare const TYPE_TOKENS: readonly ["--ig-font-ui", "--ig-font-mono", "--ig-font-size", "--ig-font-size-small", "--ig-line-height"];
|
|
27
|
+
export type TypeToken = (typeof TYPE_TOKENS)[number];
|
|
28
|
+
/** Geometry tokens. Every value is a number of CSS pixels. */
|
|
29
|
+
export declare const METRIC_TOKENS: readonly ["--ig-space", "--ig-space-tight", "--ig-radius", "--ig-row-height", "--ig-station-size", "--ig-station-halo", "--ig-stroke", "--ig-stroke-connector", "--ig-terminal-length", "--ig-terminal-width", "--ig-gutter-width", "--ig-spine-width", "--ig-char-width", "--ig-focus-ring"];
|
|
30
|
+
export type MetricToken = (typeof METRIC_TOKENS)[number];
|
|
31
|
+
/** Every custom property the stylesheet may reference. */
|
|
32
|
+
export declare const THEME_TOKENS: readonly string[];
|
|
33
|
+
export interface Theme {
|
|
34
|
+
readonly colors: Readonly<Record<ColorToken, string>>;
|
|
35
|
+
readonly type: Readonly<Record<TypeToken, string>>;
|
|
36
|
+
/** Numbers, in CSS pixels. Layout reads these; `themeCss` renders them. */
|
|
37
|
+
readonly metrics: Readonly<Record<MetricToken, number>>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The default theme.
|
|
41
|
+
*
|
|
42
|
+
* Contrast is a claim this palette makes and `theme.test.ts` measures: every
|
|
43
|
+
* text colour clears WCAG AA's 4.5:1 against all three surfaces, and every edge
|
|
44
|
+
* hue clears the 3:1 non-text bar that applies to a line or a badge outline.
|
|
45
|
+
* `--ig-text-muted` is the tight one — it carries sentence-length copy at small
|
|
46
|
+
* sizes, which is why it is the value the tests pin most precisely.
|
|
47
|
+
*/
|
|
48
|
+
export declare const defaultTheme: Theme;
|
|
49
|
+
/** Everything a caller may override, with every field optional. */
|
|
50
|
+
export interface ThemeOverride {
|
|
51
|
+
readonly colors?: Partial<Record<ColorToken, string>> | undefined;
|
|
52
|
+
readonly type?: Partial<Record<TypeToken, string>> | undefined;
|
|
53
|
+
readonly metrics?: Partial<Record<MetricToken, number>> | undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Merge an override onto a base theme, per token.
|
|
57
|
+
*
|
|
58
|
+
* A partial override is the shape a second theme actually takes — changing a
|
|
59
|
+
* palette rarely means restating the geometry — and requiring a whole `Theme`
|
|
60
|
+
* would make the cheap case impossible to express.
|
|
61
|
+
*/
|
|
62
|
+
export declare function extendTheme(base: Theme, override: ThemeOverride): Theme;
|
|
63
|
+
/**
|
|
64
|
+
* Render a theme as one CSS rule of custom properties.
|
|
65
|
+
*
|
|
66
|
+
* Values are emitted verbatim for colours and type — a theme's author owns
|
|
67
|
+
* their spelling — and metrics gain a `px` unit here, which is the single place
|
|
68
|
+
* the numbers become CSS.
|
|
69
|
+
*/
|
|
70
|
+
export declare function themeCss(theme: Theme, selector?: string): string;
|
|
71
|
+
//# sourceMappingURL=theme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,YAAY,6VAkBd,CAAC;AAEZ,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,6EAA6E;AAC7E,eAAO,MAAM,WAAW,2GAMb,CAAC;AAEZ,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,8DAA8D;AAC9D,eAAO,MAAM,aAAa,+RAef,CAAC;AAEZ,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,0DAA0D;AAC1D,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAIxC,CAAC;AAEH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;CACzD;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,KAuDzB,CAAC;AAEH,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;CACrE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,GAAG,KAAK,CAMvE;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,SAAU,GAAG,MAAM,CAMjE"}
|