@ponchia/ui 0.4.1 → 0.5.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 (105) hide show
  1. package/CHANGELOG.md +230 -8
  2. package/MIGRATIONS.json +92 -0
  3. package/README.md +9 -6
  4. package/annotations/index.d.ts +280 -0
  5. package/annotations/index.js +522 -0
  6. package/behaviors/carousel.js +197 -0
  7. package/behaviors/combobox.js +195 -0
  8. package/behaviors/command.js +187 -0
  9. package/behaviors/connectors.js +96 -0
  10. package/behaviors/crosshair.js +58 -0
  11. package/behaviors/dialog.js +73 -0
  12. package/behaviors/disclosure.js +25 -0
  13. package/behaviors/dismissible.js +24 -0
  14. package/behaviors/forms.js +158 -0
  15. package/behaviors/glyph.js +109 -0
  16. package/behaviors/index.d.ts +79 -0
  17. package/behaviors/index.js +18 -1409
  18. package/behaviors/internal.js +50 -0
  19. package/behaviors/legend.js +46 -0
  20. package/behaviors/menu.js +46 -0
  21. package/behaviors/popover.js +108 -0
  22. package/behaviors/spotlight.js +53 -0
  23. package/behaviors/table.js +109 -0
  24. package/behaviors/tabs.js +103 -0
  25. package/behaviors/theme.js +82 -0
  26. package/behaviors/toast.js +152 -0
  27. package/classes/index.d.ts +280 -2
  28. package/classes/index.js +313 -2
  29. package/connectors/index.d.ts +71 -0
  30. package/connectors/index.js +179 -0
  31. package/css/analytical.css +21 -0
  32. package/css/annotations.css +292 -0
  33. package/css/command.css +97 -0
  34. package/css/connectors.css +93 -0
  35. package/css/crosshair.css +100 -0
  36. package/css/feedback.css +51 -0
  37. package/css/fonts.css +11 -7
  38. package/css/generated.css +117 -0
  39. package/css/legend.css +268 -0
  40. package/css/marks.css +144 -0
  41. package/css/primitives.css +18 -0
  42. package/css/report.css +12 -31
  43. package/css/selection.css +46 -0
  44. package/css/sources.css +179 -0
  45. package/css/spotlight.css +104 -0
  46. package/css/state.css +121 -0
  47. package/css/tokens.css +25 -37
  48. package/css/workbench.css +83 -0
  49. package/dist/bronto.css +1 -1
  50. package/dist/css/analytical.css +1 -0
  51. package/dist/css/annotations.css +1 -0
  52. package/dist/css/command.css +1 -0
  53. package/dist/css/connectors.css +1 -0
  54. package/dist/css/crosshair.css +1 -0
  55. package/dist/css/feedback.css +1 -1
  56. package/dist/css/fonts.css +1 -1
  57. package/dist/css/generated.css +1 -0
  58. package/dist/css/legend.css +1 -0
  59. package/dist/css/marks.css +1 -0
  60. package/dist/css/primitives.css +1 -1
  61. package/dist/css/report.css +1 -1
  62. package/dist/css/selection.css +1 -0
  63. package/dist/css/sources.css +1 -0
  64. package/dist/css/spotlight.css +1 -0
  65. package/dist/css/state.css +1 -0
  66. package/dist/css/workbench.css +1 -0
  67. package/docs/adr/0003-theme-model.md +7 -4
  68. package/docs/annotations.md +345 -0
  69. package/docs/architecture.md +202 -0
  70. package/docs/command.md +95 -0
  71. package/docs/connectors.md +91 -0
  72. package/docs/crosshair.md +63 -0
  73. package/docs/generated.md +91 -0
  74. package/docs/legends.md +168 -0
  75. package/docs/marks.md +86 -0
  76. package/docs/reference.md +309 -3
  77. package/docs/reporting.md +49 -14
  78. package/docs/selection.md +40 -0
  79. package/docs/sources.md +110 -0
  80. package/docs/spotlight.md +78 -0
  81. package/docs/stability.md +16 -1
  82. package/docs/state.md +85 -0
  83. package/docs/usage.md +22 -0
  84. package/docs/workbench.md +72 -0
  85. package/fonts/doto-400.woff2 +0 -0
  86. package/fonts/doto-500.woff2 +0 -0
  87. package/fonts/doto-600.woff2 +0 -0
  88. package/fonts/doto-700.woff2 +0 -0
  89. package/fonts/doto-800.woff2 +0 -0
  90. package/fonts/doto-900.woff2 +0 -0
  91. package/llms.txt +229 -6
  92. package/package.json +69 -4
  93. package/qwik/index.d.ts +5 -0
  94. package/qwik/index.js +20 -0
  95. package/react/index.d.ts +5 -0
  96. package/react/index.js +10 -0
  97. package/solid/index.d.ts +5 -0
  98. package/solid/index.js +10 -0
  99. package/tokens/index.js +9 -5
  100. package/fonts/doto-400.ttf +0 -0
  101. package/fonts/doto-500.ttf +0 -0
  102. package/fonts/doto-600.ttf +0 -0
  103. package/fonts/doto-700.ttf +0 -0
  104. package/fonts/doto-800.ttf +0 -0
  105. package/fonts/doto-900.ttf +0 -0
@@ -0,0 +1,95 @@
1
+ # Command palette
2
+
3
+ `@ponchia/ui/css/command.css` + `initCommand` are an opt-in **command palette**:
4
+ a filter input over a grouped listbox of commands with shortcut hints. Command
5
+ palettes turn a product from a page collection into a tool. Existing libraries
6
+ (cmdk, kbar) are good — Bronto owns the *design-system contract* (the shell,
7
+ shortcuts, groups, meta) and a small navigation behavior, not the action registry.
8
+
9
+ ```css
10
+ @import '@ponchia/ui';
11
+ @import '@ponchia/ui/css/command.css';
12
+ ```
13
+
14
+ ```js
15
+ import { initCommand, initDialog } from '@ponchia/ui/behaviors';
16
+ initDialog(); // open/close the dialog the palette lives in
17
+ initCommand(); // filter + keyboard-navigate the list
18
+ ```
19
+
20
+ **Bronto** filters and keyboard-navigates a DOM-authored list. **The host** owns
21
+ the action registry, permission checks, routing, async effects, and execution —
22
+ it listens for `bronto:command:select` and runs the command. There is **no global
23
+ Cmd/Ctrl+K**; you open the palette yourself (e.g. a `<dialog>` opened by a button
24
+ or your own shortcut). Pairs with the [`ui-shortcut`](./reference.md) hint. Not in
25
+ the core bundle.
26
+
27
+ ## Markup
28
+
29
+ ```html
30
+ <dialog class="ui-modal" id="cmdk" data-bronto-dialog-light aria-label="Command palette">
31
+ <div class="ui-command" data-bronto-command>
32
+ <input class="ui-command__input" aria-label="Command" placeholder="Type a command…" />
33
+ <ul class="ui-command__list">
34
+ <li class="ui-command__group">Navigation</li>
35
+ <li class="ui-command__item" data-value="dashboard">
36
+ <span>Go to dashboard</span>
37
+ <span class="ui-command__shortcut"><kbd class="ui-kbd">G</kbd> <kbd class="ui-kbd">D</kbd></span>
38
+ </li>
39
+ <li class="ui-command__group">Actions</li>
40
+ <li class="ui-command__item" data-value="invoice">
41
+ <span>New invoice</span>
42
+ <span class="ui-command__meta">Create</span>
43
+ </li>
44
+ </ul>
45
+ <p class="ui-command__empty" hidden>No commands</p>
46
+ </div>
47
+ </dialog>
48
+ <button class="ui-button" data-bronto-open="cmdk" type="button">Commands</button>
49
+ ```
50
+
51
+ | Class | Role |
52
+ | --- | --- |
53
+ | `ui-command` | The palette shell (input + list + empty). |
54
+ | `ui-command__input` | The filter input (becomes `role="combobox"`). |
55
+ | `ui-command__list` | The listbox of commands. |
56
+ | `ui-command__group` | A non-selectable group label; auto-hidden when its items all filter out. |
57
+ | `ui-command__item` | A command row (`role="option"`); optional `data-value`. |
58
+ | `ui-command__shortcut` | A trailing shortcut hint (use `ui-kbd`). |
59
+ | `ui-command__meta` | Trailing secondary text (category, hint). |
60
+ | `ui-command__empty` | Shown when nothing matches. |
61
+
62
+ ## Behavior & events
63
+
64
+ `initCommand()` owns ids, `role`/`aria-activedescendant`, a roving active item,
65
+ substring filtering, the full keyboard (Down/Up/Home/End/Enter/Escape), and
66
+ pointer select. It emits:
67
+
68
+ - `bronto:command:select` — `{ value, label }`. The host executes and closes.
69
+ - `bronto:command:close` — on Escape. The host closes the dialog.
70
+
71
+ > **Permission boundary:** `initCommand()` owns the `hidden` attribute on items
72
+ > (that is how it filters), so it will reveal any item you pre-hid the moment the
73
+ > query changes. To gate a command by permission, **omit it from the DOM** —
74
+ > don't render it hidden.
75
+
76
+ ```js
77
+ const dialog = document.getElementById('cmdk');
78
+ document.querySelector('[data-bronto-command]').addEventListener('bronto:command:select', (e) => {
79
+ run(e.detail.value); // YOUR action registry
80
+ dialog.close();
81
+ });
82
+ document.querySelector('[data-bronto-command]').addEventListener('bronto:command:close', () =>
83
+ dialog.close(),
84
+ );
85
+ ```
86
+
87
+ Framework hook: `useCommand()` in `@ponchia/ui/react` · `/solid` · `/qwik`.
88
+
89
+ ## Accessibility
90
+
91
+ - The input is a `combobox`, the list a `listbox`, items `option`s, with
92
+ `aria-activedescendant` tracking the active row — standard APG listbox semantics.
93
+ - Focus stays in the input while arrows move the active item; Enter selects it.
94
+ - Open the palette in a focus-trapping `<dialog>` (Bronto's `initDialog`) so focus
95
+ returns to the trigger on close.
@@ -0,0 +1,91 @@
1
+ # Connectors (leader lines)
2
+
3
+ `@ponchia/ui/css/connectors.css` + `@ponchia/ui/connectors` draw a **leader line
4
+ between two DOM elements** — connect a note to a card, a card to a chart point,
5
+ or two related regions. This is the page-coordinate, element-to-element cousin
6
+ of the figure-coordinate [annotations](./annotations.md) layer.
7
+
8
+ ```css
9
+ @import '@ponchia/ui';
10
+ @import '@ponchia/ui/css/connectors.css';
11
+ ```
12
+
13
+ Bronto computes the **geometry** (pure functions) and styles the line; the
14
+ optional `initConnectors` behavior draws and keeps it in sync. It owns no
15
+ layout, no scales — just the path between two rects.
16
+
17
+ ## Markup
18
+
19
+ An `.ui-connector` is an SVG that overlays a **positioned** container. Give it
20
+ the ids of the two elements to connect; `initConnectors` fills in the path.
21
+
22
+ ```html
23
+ <div style="position: relative">
24
+ <div id="card-a" class="ui-card">…</div>
25
+ <div id="note-b" class="ui-card">…</div>
26
+
27
+ <svg
28
+ class="ui-connector ui-connector--accent"
29
+ data-bronto-connector
30
+ data-from="card-a"
31
+ data-to="note-b"
32
+ data-shape="elbow"
33
+ data-end="arrow"
34
+ aria-hidden="true"
35
+ ></svg>
36
+ </div>
37
+ ```
38
+
39
+ ```js
40
+ import { initConnectors } from '@ponchia/ui/behaviors';
41
+ const stop = initConnectors(); // redraws on resize/scroll; returns a cleanup
42
+ ```
43
+
44
+ A connector is decorative — mark it `aria-hidden="true"` and make sure the
45
+ relationship it depicts is also clear from the content/DOM order.
46
+
47
+ | Attribute | Values |
48
+ | --- | --- |
49
+ | `data-from` / `data-to` | ids of the elements to connect (required). |
50
+ | `data-shape` | `straight` (default), `elbow`, `curve`. |
51
+ | `data-from-side` / `data-to-side` | `top`/`right`/`bottom`/`left`/`center`. Omit a side to auto-pick it (set neither for fully automatic facing edges). |
52
+ | `data-end` | `arrow` (default), `dot`, `none`. |
53
+
54
+ Tones: `ui-connector--accent` / `--muted` / `--success` / `--warning` /
55
+ `--danger` / `--info` (monochrome by default). `ui-connector--dashed` for a
56
+ dashed line; `ui-connector--draw` strokes it in once (reduced-motion-safe).
57
+ `ui.connector({ tone, dashed, motion })` builds the class string.
58
+
59
+ ## Geometry helpers (no DOM)
60
+
61
+ For SSR, canvas, or your own renderer, the pure helpers return SVG strings /
62
+ coordinates and never touch the DOM:
63
+
64
+ ```js
65
+ import { connectRects, arrowHead } from '@ponchia/ui/connectors';
66
+
67
+ const { d, to, angle } = connectRects({
68
+ fromRect: { x: 0, y: 0, width: 80, height: 32 },
69
+ toRect: { x: 220, y: 120, width: 80, height: 32 },
70
+ shape: 'curve',
71
+ });
72
+ const head = arrowHead(to, angle); // place at the endpoint
73
+ ```
74
+
75
+ - `anchorPoint(rect, side)` — a point on a rect's edge.
76
+ - `connectorPath({ from, to, shape, curvature, mid })` — path between two points.
77
+ - `straightPath` / `elbowPath` / `curvePath` — the individual shapes.
78
+ - `connectRects(opts)` → `{ d, from, to, angle }`.
79
+ - `arrowHead(point, angle, size)` / `dotMark(point, radius)` — end markers.
80
+ - `autoSides(fromRect, toRect)` / `angleBetween(from, to)`.
81
+ - `endTangentAngle(from, to, shape)` — the angle the path *arrives* at `to`
82
+ (chord for `straight`, axis-aligned for `elbow`/`curve`); rotate an end marker
83
+ by this so it points along the path. `connectRects().angle` already uses it.
84
+
85
+ ## Coordinate model
86
+
87
+ `initConnectors` measures the from/to elements relative to the connector SVG's
88
+ own box (via `getBoundingClientRect`), so place the SVG as an overlay
89
+ (`position: absolute; inset: 0`, which `.ui-connector` sets) inside a
90
+ `position: relative` container that holds both endpoints. The SVG has no
91
+ `viewBox`, so its user units are CSS pixels.
@@ -0,0 +1,63 @@
1
+ # Crosshair & readout
2
+
3
+ `@ponchia/ui/css/crosshair.css` is an opt-in **ruler + readout** for reading a
4
+ value off a plot: pointer-tracking crosshair rules, axis value badges, and a
5
+ pinned readout chip.
6
+
7
+ ```css
8
+ @import '@ponchia/ui';
9
+ @import '@ponchia/ui/css/crosshair.css';
10
+ ```
11
+
12
+ Bronto draws the rules and (via `initCrosshair`) tracks the pointer — it reports
13
+ **where** the pointer is, as pixels and 0–1 fractions. It does **not** find the
14
+ nearest data point or map pixels to data values; that needs your scales. So this
15
+ is the *visual + pointer* layer, not a chart engine.
16
+
17
+ ## Markup
18
+
19
+ The plot is `[data-bronto-crosshair]` (a `position: relative` box); it contains a
20
+ `.ui-crosshair` overlay with the rule(s) you want.
21
+
22
+ ```html
23
+ <figure data-bronto-crosshair style="position: relative">
24
+ <!-- your chart / image / canvas -->
25
+ <div class="ui-crosshair" aria-hidden="true">
26
+ <div class="ui-crosshair__line ui-crosshair__line--x"></div>
27
+ <div class="ui-crosshair__line ui-crosshair__line--y"></div>
28
+ <div class="ui-readout" id="readout"></div>
29
+ </div>
30
+ </figure>
31
+ ```
32
+
33
+ ```js
34
+ import { initCrosshair } from '@ponchia/ui/behaviors';
35
+ const stop = initCrosshair(); // returns a cleanup
36
+ document.querySelector('[data-bronto-crosshair]').addEventListener(
37
+ 'bronto:crosshair:move',
38
+ (e) => {
39
+ const { x, y, fx, fy } = e.detail; // px + 0..1 fractions
40
+ // YOU map the fraction to a data value with your scale:
41
+ readout.textContent = `${(fx * 100).toFixed(0)}%`;
42
+ },
43
+ );
44
+ ```
45
+
46
+ | Class | Role |
47
+ | --- | --- |
48
+ | `ui-crosshair` | The overlay. Hidden until `.is-active` (set on the first pointer move over the plot). |
49
+ | `ui-crosshair__line` + `--x` / `--y` | Vertical / horizontal rule, positioned by `--crosshair-x` / `--crosshair-y`. |
50
+ | `ui-crosshair__badge` | An axis value chip (you set its text + edge). |
51
+ | `ui-readout` | A pinned readout chip; follows the crosshair point. |
52
+
53
+ `ui-crosshair--muted` is a subtler neutral crosshair. `ui.crosshair({ muted })`
54
+ builds the class string. Include only the lines you need (just `--x` for a
55
+ vertical scrubber, etc.).
56
+
57
+ ## Events
58
+
59
+ - `bronto:crosshair:move` — `{ x, y, fx, fy }` (px within the plot + fractions).
60
+ - `bronto:crosshair:leave` — pointer left the plot.
61
+
62
+ The crosshair is decorative (`aria-hidden`); expose the underlying values to
63
+ assistive tech through a data table or text, not the rules.
@@ -0,0 +1,91 @@
1
+ # Generated content & AI trust
2
+
3
+ `@ponchia/ui/css/generated.css` is an opt-in layer of **trust surfaces for AI /
4
+ system-generated content**. Bronto is not a chat framework — it does not ship
5
+ chat bubbles. It ships the surfaces that make generated content legible and
6
+ accountable: a marked region, an origin label, and quiet collapsible reasoning
7
+ and tool-call logs. It pairs with the [source & provenance layer](./sources.md).
8
+
9
+ ```css
10
+ @import '@ponchia/ui';
11
+ @import '@ponchia/ui/css/generated.css';
12
+ ```
13
+
14
+ Bronto styles the disclosure, origin, and trace surfaces. The host owns model
15
+ metadata, reasoning-visibility policy, tool execution, redaction, and safety.
16
+ Prefer evidence and source links over decorative "AI sparkle" styling. Not in
17
+ the core bundle.
18
+
19
+ ## Generated region — `.ui-generated`
20
+
21
+ Wraps a block of machine-authored content with a quiet accent edge and an
22
+ optional origin `__label`.
23
+
24
+ ```html
25
+ <section class="ui-generated">
26
+ <p class="ui-generated__label">Model output</p>
27
+ <p>The migration cut p95 latency by 38% in the first hour.</p>
28
+ </section>
29
+ ```
30
+
31
+ ## Origin label — `.ui-origin-label`
32
+
33
+ A small tag for "AI assisted", "model output", "tool output", or "human
34
+ reviewed". `--ai` accent-tints it for model-generated origin; the default is a
35
+ neutral tag. The words carry the meaning — the tint is reinforcement.
36
+
37
+ ```html
38
+ <span class="ui-origin-label ui-origin-label--ai">AI generated</span>
39
+ <span class="ui-origin-label">Human reviewed</span>
40
+ ```
41
+
42
+ ## Reasoning — `.ui-reasoning`
43
+
44
+ A quiet, collapsible "how this was produced" block on a native `<details>` (zero
45
+ JS). Keep it closed by default; the host decides what reasoning is safe to show.
46
+
47
+ ```html
48
+ <details class="ui-reasoning">
49
+ <summary>Reasoning</summary>
50
+ <p class="ui-reasoning__body">Compared the Q2 and Q3 latency tables, then…</p>
51
+ </details>
52
+ ```
53
+
54
+ ## Tool log — `.ui-tool-log` / `.ui-tool-call`
55
+
56
+ A document-grade record of tool invocations. Each `.ui-tool-call` is a
57
+ collapsible `<details>`: a `__name`, an optional `__status` (put a `.ui-dot` or
58
+ text), and a `__body` with the arguments or output.
59
+
60
+ ```html
61
+ <div class="ui-tool-log">
62
+ <details class="ui-tool-call">
63
+ <summary>
64
+ <span class="ui-tool-call__name">search_docs(query)</span>
65
+ <span class="ui-tool-call__status"><span class="ui-dot ui-dot--live"></span> ok</span>
66
+ </summary>
67
+ <pre class="ui-tool-call__body">{ "query": "p95 latency", "hits": 3 }</pre>
68
+ </details>
69
+ </div>
70
+ ```
71
+
72
+ ## Recipe
73
+
74
+ ```js
75
+ import { ui } from '@ponchia/ui/classes';
76
+
77
+ ui.originLabel({ ai: true }); // "ui-origin-label ui-origin-label--ai"
78
+ ```
79
+
80
+ The other parts are class-only (`cls.generated`, `cls.toolCall`, …) — they have
81
+ no options, so apply the class directly.
82
+
83
+ ## Accessibility
84
+
85
+ - The reasoning and tool-call disclosures are native `<details>` — keyboard- and
86
+ screen-reader-accessible with no JS.
87
+ - An origin label must read as text ("AI generated"); the accent tint is not the
88
+ only signal.
89
+ - A confidence / verdict widget is intentionally **not** shipped: don't
90
+ fabricate precision. Add a confidence surface only when the product has a real
91
+ signal to show.
@@ -0,0 +1,168 @@
1
+ # Legends & data keys
2
+
3
+ `@ponchia/ui/css/legend.css` is an opt-in layer of **data keys** for charts,
4
+ reports, and analytical figures. A legend maps a visual encoding (colour, a
5
+ pattern, a shape, a gradient) to its meaning. It reads the same Tier-4
6
+ `--chart-*` tokens the [data-viz palette](./reference.md) ships, so a key never
7
+ drifts from the series it describes.
8
+
9
+ ```css
10
+ @import '@ponchia/ui';
11
+ @import '@ponchia/ui/css/dataviz.css';
12
+ @import '@ponchia/ui/css/legend.css';
13
+ ```
14
+
15
+ Bronto **paints and positions** the key; it owns no scales, no data→pixel
16
+ mapping, and no series state. Pair it with Bronto's own `.ui-chart` bars, an
17
+ SVG/canvas figure, or any external chart engine — the host owns the chart.
18
+
19
+ ## What it is not
20
+
21
+ - Not a chart engine. It does not compute tick positions, "nice" numbers,
22
+ bin thresholds, or bubble radii — you supply the tick/range **text**; the
23
+ CSS positions the slots you give it.
24
+ - Not a colour source. Swatch colour always comes from a `--chart-*` token
25
+ (enforced by `check:legend`), never a hand-rolled hex.
26
+
27
+ ## Accessibility: colour is never the only channel
28
+
29
+ A legend that distinguishes series by **colour alone** fails
30
+ [WCAG 1.4.1 Use of Color](https://www.w3.org/WAI/WCAG21/Understanding/use-of-color.html).
31
+ In a legend, the **text label** is the required non-colour channel — every
32
+ entry carries its name, so the meaning survives even if colour is lost
33
+ (`forced-colors`, monochrome print, colour-vision deficiency). Where the chart
34
+ *mark itself* is colour-only (a bare area or line), also pair the swatch with
35
+ its `--chart-pattern-*` so the figure — not just the legend — stays readable.
36
+
37
+ Recommended structure: wrap the figure and its key in a `<figure>` with a
38
+ `<figcaption>`, and give the legend its own group label.
39
+
40
+ ```html
41
+ <figure role="group" aria-labelledby="fig-1-title">
42
+ <figcaption id="fig-1-title">Fig 1 — Weekly focus split</figcaption>
43
+
44
+ <!-- … the chart … -->
45
+
46
+ <ul class="ui-legend" aria-label="Series">
47
+ <li class="ui-legend__item">
48
+ <span
49
+ class="ui-legend__swatch"
50
+ style="--chart-color: var(--chart-1); --chart-pattern: var(--chart-pattern-1)"
51
+ aria-hidden="true"
52
+ ></span>
53
+ <span class="ui-legend__label">Research</span>
54
+ </li>
55
+ <li class="ui-legend__item">
56
+ <span class="ui-legend__swatch ui-legend__swatch--2" aria-hidden="true"></span>
57
+ <span class="ui-legend__label">Delivery</span>
58
+ </li>
59
+ </ul>
60
+ </figure>
61
+ ```
62
+
63
+ The swatch is decorative (`aria-hidden="true"`) — the meaning is the label
64
+ text. Set its colour either inline (`--chart-color`) to mirror exactly what the
65
+ chart mark uses, or with a `--N` index helper for the categorical palette.
66
+
67
+ ## Parts
68
+
69
+ | Class | Role |
70
+ | --- | --- |
71
+ | `ui-legend` | The container (a wrapping inline row by default). |
72
+ | `ui-legend__title` | Optional heading for the key. |
73
+ | `ui-legend__item` | One entry — swatch + label (+ value). |
74
+ | `ui-legend__swatch` | The colour/pattern chip. |
75
+ | `ui-legend__symbol` | A glyph/shape chip (fill an `.ui-icon` mask). |
76
+ | `ui-legend__label` | The series name (the non-colour channel). |
77
+ | `ui-legend__value` | Optional trailing value/range. |
78
+ | `ui-legend__caption` | Optional footnote (units, source). |
79
+ | `ui-legend__track` | The gradient bar (continuous keys). |
80
+ | `ui-legend__ticks` / `ui-legend__tick` | Tick labels under the track. |
81
+
82
+ ### Swatch colour
83
+
84
+ | Approach | Use |
85
+ | --- | --- |
86
+ | `style="--chart-color: var(--chart-3)"` | Mirror any token, any order; add `--chart-pattern` to match a patterned mark. |
87
+ | `class="… ui-legend__swatch--3"` | Categorical palette series 1–8 — sets `--chart-3` for you. |
88
+
89
+ `ui-legend__swatch--circle` and `ui-legend__swatch--line` change the chip shape
90
+ (dot series, line series).
91
+
92
+ ## Variants
93
+
94
+ | Modifier | Effect |
95
+ | --- | --- |
96
+ | `ui-legend--vertical` | Stack entries instead of the wrapping row. |
97
+ | `ui-legend--compact` | Denser type and gaps. |
98
+ | `ui-legend--with-values` | Align a trailing `__value` column across rows. |
99
+ | `ui-legend--gradient` | Continuous colour ramp (`__track` + `__ticks`). |
100
+ | `ui-legend--diverging` | Use the 7-stop diverging ramp (with `--gradient`). |
101
+ | `ui-legend--threshold` | Binned `swatch │ range-label` grid. |
102
+ | `ui-legend--interactive` | Entries are toggle controls (see below). |
103
+
104
+ The recipe mirrors this surface:
105
+
106
+ ```js
107
+ import { ui } from '@ponchia/ui/classes';
108
+
109
+ ui.legend({ type: 'gradient', diverging: true });
110
+ // "ui-legend ui-legend--gradient ui-legend--diverging"
111
+ ui.legendSwatch({ series: 3, shape: 'circle' });
112
+ // "ui-legend__swatch ui-legend__swatch--3 ui-legend__swatch--circle"
113
+ ```
114
+
115
+ ### Continuous ramp
116
+
117
+ Supply the min/mid/max tick **text**; the track interpolates the sequential
118
+ ramp in OKLCH (`--diverging` swaps in the diverging ramp around its neutral
119
+ centre).
120
+
121
+ ```html
122
+ <div class="ui-legend ui-legend--gradient" role="group" aria-label="Density">
123
+ <span class="ui-legend__track" aria-hidden="true"></span>
124
+ <span class="ui-legend__ticks">
125
+ <span class="ui-legend__tick">0</span>
126
+ <span class="ui-legend__tick">50</span>
127
+ <span class="ui-legend__tick">100</span>
128
+ </span>
129
+ </div>
130
+ ```
131
+
132
+ ## Interactive legends (optional)
133
+
134
+ An interactive legend toggles a series on or off. Bronto ships the **control
135
+ surface** only; the **host owns the data**. The split is deliberate (it keeps
136
+ the legend out of chart-engine territory):
137
+
138
+ - **Bronto:** each entry is a `<button aria-pressed>`. The optional
139
+ `initLegend` behavior flips `aria-pressed`, toggles `.is-inactive`, and
140
+ dispatches `bronto:legend:toggle` with `{ detail: { series, active } }`.
141
+ - **You:** listen for the event, hide/show your own series, and announce the
142
+ change through an `aria-live` region you own.
143
+
144
+ ```html
145
+ <ul class="ui-legend ui-legend--interactive" data-bronto-legend aria-label="Series">
146
+ <li>
147
+ <button type="button" class="ui-legend__item" aria-pressed="true" data-series="research">
148
+ <span class="ui-legend__swatch ui-legend__swatch--1" aria-hidden="true"></span>
149
+ <span class="ui-legend__label">Research</span>
150
+ </button>
151
+ </li>
152
+ </ul>
153
+ ```
154
+
155
+ ```js
156
+ import { initLegend } from '@ponchia/ui/behaviors';
157
+ const stop = initLegend(); // returns a cleanup fn
158
+ document.addEventListener('bronto:legend:toggle', (e) => {
159
+ const { series, active } = e.detail;
160
+ // hide/show your series, then announce it in your own aria-live region
161
+ });
162
+ ```
163
+
164
+ Convention: `aria-pressed="true"` means the series is **shown** (the default).
165
+ The entry's label never changes on toggle — only `aria-pressed` and
166
+ `.is-inactive` flip, so a screen reader reads a stable name with a clear
167
+ pressed state. React/Solid/Qwik consumers can use `useLegend()` instead of
168
+ calling `initLegend` directly.
package/docs/marks.md ADDED
@@ -0,0 +1,86 @@
1
+ # Text marks & evidence
2
+
3
+ `@ponchia/ui/css/marks.css` is an opt-in layer of **evidence and emphasis marks
4
+ for running text** — the prose counterpart to the [SVG annotations](./annotations.md)
5
+ layer. Annotations call out a figure; marks call out a sentence. The look is
6
+ sober and report-grade (good for docs, audits, and generated/LLM reports), not
7
+ a hand-drawn highlighter.
8
+
9
+ ```css
10
+ @import '@ponchia/ui';
11
+ @import '@ponchia/ui/css/marks.css';
12
+ ```
13
+
14
+ Monochrome by default — the rationed accent is opt-in via `--accent`, and the
15
+ status tones are only for status-bearing emphasis.
16
+
17
+ ## Inline marks — `.ui-mark`
18
+
19
+ Put `.ui-mark` on a `<mark>` so the emphasis is semantic, not just visual.
20
+
21
+ ```html
22
+ <p>
23
+ The migration <mark class="ui-mark ui-mark--accent">cut p95 latency by 38%</mark>,
24
+ but <mark class="ui-mark ui-mark--danger ui-mark--underline">error rate doubled</mark>
25
+ in the first hour.
26
+ </p>
27
+ ```
28
+
29
+ | Style | Effect |
30
+ | --- | --- |
31
+ | _(default)_ | Highlighter fill. |
32
+ | `ui-mark--underline` | Coloured underline (no fill). |
33
+ | `ui-mark--box` | Outlined box. |
34
+ | `ui-mark--strike` | Strikethrough (removed/superseded text). |
35
+
36
+ | Tone | Use |
37
+ | --- | --- |
38
+ | _(default)_ | Neutral ink emphasis (monochrome). |
39
+ | `ui-mark--accent` | The rationed accent — "this is the proof". |
40
+ | `ui-mark--success` / `--warning` / `--danger` / `--info` | Status-bearing emphasis only. |
41
+ | `ui-mark--muted` | De-emphasis. |
42
+
43
+ `ui-mark--draw` sweeps the highlight in once on load; it respects
44
+ `prefers-reduced-motion` (reduced motion shows the resting full highlight). It
45
+ applies to the highlight fill, so pair it with the default (no style modifier).
46
+
47
+ ## Passage bracket — `.ui-bracket-note`
48
+
49
+ Brackets a whole block and optionally labels it — the prose analogue of
50
+ `ui-annotation--bracket`. Useful for "this paragraph is the evidence/caveat".
51
+
52
+ ```html
53
+ <blockquote class="ui-bracket-note ui-bracket-note--accent">
54
+ <span class="ui-bracket-note__label">Source</span>
55
+ Q3 incident review, §4 — sustained for 47 minutes before rollback.
56
+ </blockquote>
57
+ ```
58
+
59
+ Tones: `--accent` (the rationed accent), `--warning`, `--danger`, `--info`. The
60
+ default is a neutral bracket.
61
+
62
+ ## Recipes
63
+
64
+ ```js
65
+ import { ui } from '@ponchia/ui/classes';
66
+
67
+ ui.mark({ tone: 'accent', motion: 'draw' });
68
+ // "ui-mark ui-mark--accent ui-mark--draw"
69
+ ui.mark({ style: 'underline', tone: 'danger' });
70
+ // "ui-mark ui-mark--underline ui-mark--danger"
71
+ ui.bracketNote({ tone: 'warning' });
72
+ // "ui-bracket-note ui-bracket-note--warning"
73
+ ```
74
+
75
+ ## Accessibility
76
+
77
+ - A `.ui-mark` is **visual emphasis**; it does not announce itself to screen
78
+ readers. When the emphasis carries meaning that the surrounding words don't,
79
+ state it in the text (a screen-reader user can't see the highlight) — the same
80
+ rule as colour (WCAG 1.4.1). Use a native `<mark>` so the relationship is at
81
+ least semantic.
82
+ - In `forced-colors` mode a highlight `background` is dropped, so marks add an
83
+ underline to keep the emphasis visible; the `--box`/`--underline`/`--strike`
84
+ styles already survive as a system colour.
85
+ - `.ui-bracket-note` is a plain block; wrap a quotation in `<blockquote>` (or a
86
+ region with its own heading/label) so its role is conveyed without the border.