@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.
- package/CHANGELOG.md +230 -8
- package/MIGRATIONS.json +92 -0
- package/README.md +9 -6
- package/annotations/index.d.ts +280 -0
- package/annotations/index.js +522 -0
- package/behaviors/carousel.js +197 -0
- package/behaviors/combobox.js +195 -0
- package/behaviors/command.js +187 -0
- package/behaviors/connectors.js +96 -0
- package/behaviors/crosshair.js +58 -0
- package/behaviors/dialog.js +73 -0
- package/behaviors/disclosure.js +25 -0
- package/behaviors/dismissible.js +24 -0
- package/behaviors/forms.js +158 -0
- package/behaviors/glyph.js +109 -0
- package/behaviors/index.d.ts +79 -0
- package/behaviors/index.js +18 -1409
- package/behaviors/internal.js +50 -0
- package/behaviors/legend.js +46 -0
- package/behaviors/menu.js +46 -0
- package/behaviors/popover.js +108 -0
- package/behaviors/spotlight.js +53 -0
- package/behaviors/table.js +109 -0
- package/behaviors/tabs.js +103 -0
- package/behaviors/theme.js +82 -0
- package/behaviors/toast.js +152 -0
- package/classes/index.d.ts +280 -2
- package/classes/index.js +313 -2
- package/connectors/index.d.ts +71 -0
- package/connectors/index.js +179 -0
- package/css/analytical.css +21 -0
- package/css/annotations.css +292 -0
- package/css/command.css +97 -0
- package/css/connectors.css +93 -0
- package/css/crosshair.css +100 -0
- package/css/feedback.css +51 -0
- package/css/fonts.css +11 -7
- package/css/generated.css +117 -0
- package/css/legend.css +268 -0
- package/css/marks.css +144 -0
- package/css/primitives.css +18 -0
- package/css/report.css +12 -31
- package/css/selection.css +46 -0
- package/css/sources.css +179 -0
- package/css/spotlight.css +104 -0
- package/css/state.css +121 -0
- package/css/tokens.css +25 -37
- package/css/workbench.css +83 -0
- package/dist/bronto.css +1 -1
- package/dist/css/analytical.css +1 -0
- package/dist/css/annotations.css +1 -0
- package/dist/css/command.css +1 -0
- package/dist/css/connectors.css +1 -0
- package/dist/css/crosshair.css +1 -0
- package/dist/css/feedback.css +1 -1
- package/dist/css/fonts.css +1 -1
- package/dist/css/generated.css +1 -0
- package/dist/css/legend.css +1 -0
- package/dist/css/marks.css +1 -0
- package/dist/css/primitives.css +1 -1
- package/dist/css/report.css +1 -1
- package/dist/css/selection.css +1 -0
- package/dist/css/sources.css +1 -0
- package/dist/css/spotlight.css +1 -0
- package/dist/css/state.css +1 -0
- package/dist/css/workbench.css +1 -0
- package/docs/adr/0003-theme-model.md +7 -4
- package/docs/annotations.md +345 -0
- package/docs/architecture.md +202 -0
- package/docs/command.md +95 -0
- package/docs/connectors.md +91 -0
- package/docs/crosshair.md +63 -0
- package/docs/generated.md +91 -0
- package/docs/legends.md +168 -0
- package/docs/marks.md +86 -0
- package/docs/reference.md +309 -3
- package/docs/reporting.md +49 -14
- package/docs/selection.md +40 -0
- package/docs/sources.md +110 -0
- package/docs/spotlight.md +78 -0
- package/docs/stability.md +16 -1
- package/docs/state.md +85 -0
- package/docs/usage.md +22 -0
- package/docs/workbench.md +72 -0
- package/fonts/doto-400.woff2 +0 -0
- package/fonts/doto-500.woff2 +0 -0
- package/fonts/doto-600.woff2 +0 -0
- package/fonts/doto-700.woff2 +0 -0
- package/fonts/doto-800.woff2 +0 -0
- package/fonts/doto-900.woff2 +0 -0
- package/llms.txt +229 -6
- package/package.json +69 -4
- package/qwik/index.d.ts +5 -0
- package/qwik/index.js +20 -0
- package/react/index.d.ts +5 -0
- package/react/index.js +10 -0
- package/solid/index.d.ts +5 -0
- package/solid/index.js +10 -0
- package/tokens/index.js +9 -5
- package/fonts/doto-400.ttf +0 -0
- package/fonts/doto-500.ttf +0 -0
- package/fonts/doto-600.ttf +0 -0
- package/fonts/doto-700.ttf +0 -0
- package/fonts/doto-800.ttf +0 -0
- package/fonts/doto-900.ttf +0 -0
package/docs/command.md
ADDED
|
@@ -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.
|
package/docs/legends.md
ADDED
|
@@ -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.
|