@ponchia/ui 0.6.0 → 0.6.3

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 (156) hide show
  1. package/CHANGELOG.md +64 -4
  2. package/README.md +1 -1
  3. package/annotations/index.d.ts.map +1 -1
  4. package/annotations/index.js +36 -33
  5. package/behaviors/carousel.d.ts +28 -0
  6. package/behaviors/carousel.d.ts.map +1 -0
  7. package/behaviors/carousel.js +3 -0
  8. package/behaviors/combobox.d.ts +40 -0
  9. package/behaviors/combobox.d.ts.map +1 -0
  10. package/behaviors/combobox.js +71 -20
  11. package/behaviors/command.d.ts +41 -0
  12. package/behaviors/command.d.ts.map +1 -0
  13. package/behaviors/command.js +9 -0
  14. package/behaviors/connectors.d.ts +17 -0
  15. package/behaviors/connectors.d.ts.map +1 -0
  16. package/behaviors/connectors.js +3 -0
  17. package/behaviors/crosshair.d.ts +42 -0
  18. package/behaviors/crosshair.d.ts.map +1 -0
  19. package/behaviors/crosshair.js +19 -1
  20. package/behaviors/dialog.d.ts +20 -0
  21. package/behaviors/dialog.d.ts.map +1 -0
  22. package/behaviors/dialog.js +3 -0
  23. package/behaviors/disclosure.d.ts +10 -0
  24. package/behaviors/disclosure.d.ts.map +1 -0
  25. package/behaviors/disclosure.js +3 -0
  26. package/behaviors/dismissible.d.ts +10 -0
  27. package/behaviors/dismissible.d.ts.map +1 -0
  28. package/behaviors/dismissible.js +3 -0
  29. package/behaviors/forms.d.ts +27 -0
  30. package/behaviors/forms.d.ts.map +1 -0
  31. package/behaviors/forms.js +18 -5
  32. package/behaviors/glyph.d.ts +14 -0
  33. package/behaviors/glyph.d.ts.map +1 -0
  34. package/behaviors/glyph.js +24 -0
  35. package/behaviors/index.d.ts +31 -237
  36. package/behaviors/index.d.ts.map +1 -0
  37. package/behaviors/index.js +17 -0
  38. package/behaviors/inert.d.ts +20 -0
  39. package/behaviors/inert.d.ts.map +1 -0
  40. package/behaviors/inert.js +46 -0
  41. package/behaviors/internal.d.ts +25 -0
  42. package/behaviors/internal.d.ts.map +1 -0
  43. package/behaviors/internal.js +30 -1
  44. package/behaviors/legend.d.ts +35 -0
  45. package/behaviors/legend.d.ts.map +1 -0
  46. package/behaviors/legend.js +9 -0
  47. package/behaviors/menu.d.ts +16 -0
  48. package/behaviors/menu.d.ts.map +1 -0
  49. package/behaviors/menu.js +3 -0
  50. package/behaviors/modal.d.ts +41 -0
  51. package/behaviors/modal.d.ts.map +1 -0
  52. package/behaviors/modal.js +124 -0
  53. package/behaviors/popover.d.ts +28 -0
  54. package/behaviors/popover.d.ts.map +1 -0
  55. package/behaviors/popover.js +17 -17
  56. package/behaviors/spotlight.d.ts +17 -0
  57. package/behaviors/spotlight.d.ts.map +1 -0
  58. package/behaviors/spotlight.js +3 -0
  59. package/behaviors/table.d.ts +36 -0
  60. package/behaviors/table.d.ts.map +1 -0
  61. package/behaviors/table.js +48 -8
  62. package/behaviors/tabs.d.ts +20 -0
  63. package/behaviors/tabs.d.ts.map +1 -0
  64. package/behaviors/tabs.js +3 -0
  65. package/behaviors/theme.d.ts +54 -0
  66. package/behaviors/theme.d.ts.map +1 -0
  67. package/behaviors/theme.js +17 -0
  68. package/behaviors/toast.d.ts +49 -0
  69. package/behaviors/toast.d.ts.map +1 -0
  70. package/behaviors/toast.js +34 -2
  71. package/classes/classes.json +683 -13
  72. package/classes/index.d.ts +106 -2
  73. package/classes/index.js +249 -65
  74. package/connectors/index.d.ts +12 -0
  75. package/connectors/index.d.ts.map +1 -1
  76. package/connectors/index.js +23 -2
  77. package/css/app.css +26 -0
  78. package/css/bullet.css +108 -0
  79. package/css/code.css +98 -0
  80. package/css/content.css +15 -2
  81. package/css/crosshair.css +7 -7
  82. package/css/diff.css +153 -0
  83. package/css/disclosure.css +18 -4
  84. package/css/dots.css +37 -7
  85. package/css/feedback.css +39 -7
  86. package/css/forms.css +71 -3
  87. package/css/legend.css +5 -2
  88. package/css/motion.css +79 -14
  89. package/css/overlay.css +59 -2
  90. package/css/primitives.css +67 -8
  91. package/css/report.css +40 -0
  92. package/css/sidenote.css +67 -0
  93. package/css/spark.css +62 -0
  94. package/css/table.css +9 -2
  95. package/css/term.css +110 -0
  96. package/css/textref.css +63 -0
  97. package/css/toc.css +91 -0
  98. package/css/tokens.css +14 -1
  99. package/css/tree.css +134 -0
  100. package/dist/bronto.css +1 -1
  101. package/dist/css/analytical.css +1 -1
  102. package/dist/css/app.css +1 -1
  103. package/dist/css/bullet.css +1 -0
  104. package/dist/css/code.css +1 -0
  105. package/dist/css/content.css +1 -1
  106. package/dist/css/crosshair.css +1 -1
  107. package/dist/css/diff.css +1 -0
  108. package/dist/css/disclosure.css +1 -1
  109. package/dist/css/dots.css +1 -1
  110. package/dist/css/feedback.css +1 -1
  111. package/dist/css/forms.css +1 -1
  112. package/dist/css/legend.css +1 -1
  113. package/dist/css/motion.css +1 -1
  114. package/dist/css/overlay.css +1 -1
  115. package/dist/css/primitives.css +1 -1
  116. package/dist/css/report.css +1 -1
  117. package/dist/css/sidenote.css +1 -0
  118. package/dist/css/spark.css +1 -0
  119. package/dist/css/table.css +1 -1
  120. package/dist/css/term.css +1 -0
  121. package/dist/css/textref.css +1 -0
  122. package/dist/css/toc.css +1 -0
  123. package/dist/css/tokens.css +1 -1
  124. package/dist/css/tree.css +1 -0
  125. package/docs/annotations.md +39 -0
  126. package/docs/architecture.md +2 -3
  127. package/docs/bullet.md +78 -0
  128. package/docs/code.md +76 -0
  129. package/docs/d2.md +4 -3
  130. package/docs/diff.md +146 -0
  131. package/docs/legends.md +8 -4
  132. package/docs/mermaid.md +21 -4
  133. package/docs/reference.md +127 -8
  134. package/docs/reporting.md +35 -14
  135. package/docs/sidenote.md +64 -0
  136. package/docs/spark.md +78 -0
  137. package/docs/stability.md +1 -0
  138. package/docs/term.md +81 -0
  139. package/docs/textref.md +78 -0
  140. package/docs/theming.md +44 -5
  141. package/docs/toc.md +83 -0
  142. package/docs/tree.md +74 -0
  143. package/docs/usage.md +264 -23
  144. package/docs/vega.md +22 -3
  145. package/glyphs/glyphs.js +7 -1
  146. package/llms.txt +159 -13
  147. package/package.json +47 -7
  148. package/qwik/index.d.ts +4 -2
  149. package/qwik/index.d.ts.map +1 -1
  150. package/qwik/index.js +10 -0
  151. package/react/index.d.ts +4 -2
  152. package/react/index.d.ts.map +1 -1
  153. package/react/index.js +6 -0
  154. package/solid/index.d.ts +6 -2
  155. package/solid/index.d.ts.map +1 -1
  156. package/solid/index.js +6 -0
@@ -0,0 +1,64 @@
1
+ # Sidenote
2
+
3
+ `@ponchia/ui/css/sidenote.css` is an opt-in **margin-note** grammar in the Tufte
4
+ tradition — the channel for evidence, caveats, and provenance asides that belong
5
+ *beside* the prose, not interrupting it. `ui-sidenote` is numbered;
6
+ `ui-marginnote` is the unnumbered variant.
7
+
8
+ ```css
9
+ @import '@ponchia/ui';
10
+ @import '@ponchia/ui/css/sidenote.css';
11
+ ```
12
+
13
+ ## How it behaves
14
+
15
+ - **Wide viewports** (≥ 60rem) float the note into the inline-end margin.
16
+ - **Narrow viewports** collapse it to an indented inline block right after its
17
+ anchor, so nothing is lost on a phone or in a narrow column.
18
+ - **Numbering** is a CSS counter — no host bookkeeping.
19
+
20
+ ## Wiring — two things the host owns
21
+
22
+ 1. **Where numbering restarts.** Put `counter-reset: ui-sidenote` on the article
23
+ (or a section) so the count starts at 1 there.
24
+ 2. **The margin gutter.** At the same breakpoint, give that container
25
+ `padding-inline-end: calc(var(--sidenote-width) + var(--sidenote-gap))` so the
26
+ floated note has room. `--sidenote-width` defaults to `12rem`.
27
+
28
+ ```html
29
+ <article style="counter-reset: ui-sidenote">
30
+ <p>
31
+ The migration cut p95 latency by 38%<label class="ui-sidenote__ref"></label>.
32
+ <span class="ui-sidenote">
33
+ Measured over the first 24h after rollout; see the incident review §4.
34
+ </span>
35
+ The error budget held<span class="ui-marginnote">No paging events fired.</span>.
36
+ </p>
37
+ </article>
38
+ ```
39
+
40
+ The `.ui-sidenote__ref` renders the superscript number and increments the
41
+ counter; place the `.ui-sidenote` immediately after it in the DOM so it reads
42
+ the same number.
43
+
44
+ ## Class reference
45
+
46
+ | Class | Role |
47
+ | --- | --- |
48
+ | `.ui-sidenote` | A numbered margin note. |
49
+ | `.ui-marginnote` | An unnumbered margin note (no `__ref`). |
50
+ | `.ui-sidenote__ref` | The inline superscript anchor; increments + prints the number. |
51
+
52
+ | Custom property | On | Meaning |
53
+ | --- | --- | --- |
54
+ | `--sidenote-width` | `.ui-sidenote` / `.ui-marginnote` | Floated width on wide viewports (default `12rem`). |
55
+ | `--sidenote-gap` | same | Gap between the text column and the note (default `2rem`). |
56
+
57
+ ## Accessibility & robustness
58
+
59
+ - The note is **real DOM in reading order**, so a screen reader encounters it
60
+ right after the anchor — no off-screen trickery.
61
+ - On narrow viewports the note is always visible (an indented block with an
62
+ inline-start rule), so there is no hidden-content trap.
63
+ - Keep the note's meaning in its text; the number is a wayfinding aid, not the
64
+ content.
package/docs/spark.md ADDED
@@ -0,0 +1,78 @@
1
+ # Spark
2
+
3
+ `@ponchia/ui/css/spark.css` is an opt-in **inline dataword** — a word-sized
4
+ microchart you drop into a sentence or a dense table cell to show a trend
5
+ in-line. It is the inline counterpart to the scalar `ui-delta` / `ui-num` /
6
+ `ui-stat`, and it fills a gap a block-level chart cannot.
7
+
8
+ ```css
9
+ @import '@ponchia/ui';
10
+ @import '@ponchia/ui/css/spark.css';
11
+ ```
12
+
13
+ ## Boundary — the host owns the numbers
14
+
15
+ Bronto paints bars; it does **not** compute scales. The host normalises each
16
+ data point to `0..1` and sets it as `--v` on a `.ui-spark__bar`. Bronto refuses
17
+ raw values and min/max — that is the same boundary that keeps the analytical
18
+ layer from owning chart state.
19
+
20
+ ## Markup
21
+
22
+ ```html
23
+ <!-- "weekly signups, trending up" — the label IS the accessible content -->
24
+ <span class="ui-spark" role="img" aria-label="weekly signups, trending up">
25
+ <span class="ui-spark__bar" style="--v: 0.2"></span>
26
+ <span class="ui-spark__bar" style="--v: 0.4"></span>
27
+ <span class="ui-spark__bar" style="--v: 0.35"></span>
28
+ <span class="ui-spark__bar" style="--v: 0.7"></span>
29
+ <span class="ui-spark__bar ui-spark__bar--accent" style="--v: 1"></span>
30
+ </span>
31
+ ```
32
+
33
+ A **win/loss** strip uses fixed-height bars with `--pos` / `--neg` tones:
34
+
35
+ ```html
36
+ <span class="ui-spark" role="img" aria-label="last 5 deploys: 4 passed, 1 failed">
37
+ <span class="ui-spark__bar ui-spark__bar--pos" style="--v: 1"></span>
38
+ <span class="ui-spark__bar ui-spark__bar--pos" style="--v: 1"></span>
39
+ <span class="ui-spark__bar ui-spark__bar--neg" style="--v: 1"></span>
40
+ <span class="ui-spark__bar ui-spark__bar--pos" style="--v: 1"></span>
41
+ <span class="ui-spark__bar ui-spark__bar--pos" style="--v: 1"></span>
42
+ </span>
43
+ ```
44
+
45
+ ## Class reference
46
+
47
+ | Class | Role |
48
+ | --- | --- |
49
+ | `.ui-spark` | The inline container (sizes to ~1em tall, `max-content` wide). |
50
+ | `.ui-spark__bar` | One bar — height from `--v` (`0..1`). Defaults to `currentColor`. |
51
+ | `.ui-spark__bar--accent` | Emphasise one bar with the rationed accent. |
52
+ | `.ui-spark__bar--pos` | Success tone (win). |
53
+ | `.ui-spark__bar--neg` | Danger tone (loss). |
54
+
55
+ | Custom property | On | Meaning |
56
+ | --- | --- | --- |
57
+ | `--v` | `.ui-spark__bar` | **Required.** Normalised height `0..1`; the host computes it. A bar with no `--v` collapses to a 1px floor. |
58
+
59
+ ## Recipes
60
+
61
+ ```js
62
+ import { ui } from '@ponchia/ui/classes';
63
+
64
+ ui.sparkBar({ tone: 'accent' }); // "ui-spark__bar ui-spark__bar--accent"
65
+ ui.sparkBar({ tone: 'pos' }); // "ui-spark__bar ui-spark__bar--pos"
66
+ ui.sparkBar(); // "ui-spark__bar"
67
+ ```
68
+
69
+ ## Accessibility & robustness
70
+
71
+ - **A bare spark is opaque.** The `.ui-spark` container **must** carry
72
+ `role="img"` and an `aria-label` that states the trend in words — colour and
73
+ shape are never the only channel (WCAG 1.4.1, 1.1.1).
74
+ - **Forced colours:** the bars repaint in the system text colour so the shape
75
+ survives (the tone distinction lives in the label).
76
+ - **Print:** bars are forced through `print-color-adjust: exact`.
77
+ - No JS, no measurement — it renders identically server-side and in a static
78
+ report.
package/docs/stability.md CHANGED
@@ -40,6 +40,7 @@ until `1.0.0` is tagged. This matrix defines what counts as public API.
40
40
  | Generated / AI-trust (`css/generated.css`, `.ui-generated*`, `.ui-origin-label*`, `.ui-reasoning*`, `.ui-tool-log`, `.ui-tool-call*`) | Stable additive | The generated-content, origin-label (incl. `--ai`), reasoning-trace and tool-log/tool-call class names and the `ui.originLabel` recipe are public. Opt-in, not in the default bundle. Not a chat kit; no confidence widget. |
41
41
  | Workbench (`css/workbench.css`, `.ui-inspector*`, `.ui-property*`, `.ui-selectionbar*`) | Stable additive | Inspector, property-row and selection-bar class + BEM part names are public (class-only — no recipe). Opt-in, not in the default bundle. Splitters/drag handles are out of scope. |
42
42
  | Command palette (`css/command.css`, `.ui-command*`, `initCommand`, `useCommand`) | Stable additive | Command class/part names, the `data-bronto-command` attribute, and the event contract — `bronto:command:select` (`detail: { value, label }`) and `bronto:command:close` — are public, plus the `useCommand` binding hook. Bronto filters + navigates (APG combobox/listbox); the host owns the action registry/execution. Opt-in, not in the default bundle, no global hotkey. |
43
+ | Controlled-modal focus trap (`initModal`, `useModal`, `data-bronto-modal`) | Stable additive | For the `.ui-modal.is-open` (non-`<dialog>`) path: the `data-bronto-modal` opt-in marker, the `inert`-based focus trap + focus-return, and the cancelable `bronto:modal:close` (`detail: { reason }`) event are public. The consumer still owns the `is-open` class; the behavior never changes visibility. The native `<dialog>` path (`initDialog`) is the default and gets the trap for free. |
43
44
  | Keyboard-shortcut hint (`.ui-shortcut`, `.ui-shortcut__sep`) | Stable additive | Class names for the chord/sequence hint over `.ui-kbd` are public. Ships in the core layer (class-only, no recipe). |
44
45
  | Generated docs shipped in npm | Stable paths | `llms.txt` and exported docs paths stay shipped and resolvable within a compatible minor. Markdown/text assets are for reading unless your runtime has a loader. Generated content may change with the source contract. |
45
46
  | Demo, examples, tests, scripts | Internal | Useful for learning and verification, but not shipped runtime API unless a path is explicitly exported in `package.json`. |
package/docs/term.md ADDED
@@ -0,0 +1,81 @@
1
+ # Term & Glossary
2
+
3
+ `@ponchia/ui/css/term.css` is an opt-in **inline glossary** — the accessible
4
+ upgrade of the famously touch/keyboard-broken `abbr[title]`. A dotted-underline
5
+ term whose definition lives in real, reachable DOM via the native `[popover]` +
6
+ `popovertarget` pairing, plus a `ui-glossary` `<dl>` that collects every term at
7
+ the end of a document. Jargon that explains itself inline and gathers into a
8
+ reference.
9
+
10
+ ```css
11
+ @import '@ponchia/ui';
12
+ @import '@ponchia/ui/css/term.css';
13
+ ```
14
+
15
+ ## How it behaves
16
+
17
+ - The term is a real `<button>`, so it is keyboard- and touch-reachable — the
18
+ exact failure of `abbr[title]`, which only reveals on desktop hover.
19
+ - The definition is a native popover: `popovertarget` gives open/close, Esc, and
20
+ light-dismiss for free, with no JavaScript.
21
+ - Where CSS anchor positioning exists the definition opens beside the word and
22
+ flips at the viewport edge; otherwise it falls back to a centred top-layer card.
23
+
24
+ ## Wiring — native popover pairing
25
+
26
+ The host owns the `popovertarget` ↔ `id` wiring. No Bronto kernel is involved.
27
+
28
+ ```html
29
+ <p>
30
+ We held the
31
+ <button class="ui-term" popovertarget="def-eb">error budget</button>
32
+ through the change.
33
+ <span class="ui-def" popover id="def-eb">
34
+ The amount of unreliability a service is allowed before its SLO is breached.
35
+ </span>
36
+ </p>
37
+ ```
38
+
39
+ To anchor the definition to its term where supported, set a matching
40
+ `anchor-name` on the button and `position-anchor` on the `.ui-def` (a
41
+ progressive enhancement; the centred fallback works everywhere):
42
+
43
+ ```css
44
+ #def-eb {
45
+ position-anchor: --eb;
46
+ }
47
+ .ui-term[popovertarget='def-eb'] {
48
+ anchor-name: --eb;
49
+ }
50
+ ```
51
+
52
+ The end-of-document glossary is a plain `<dl>`:
53
+
54
+ ```html
55
+ <dl class="ui-glossary">
56
+ <dt class="ui-glossary__term">Error budget</dt>
57
+ <dd class="ui-glossary__def">The unreliability a service may spend before its SLO breaches.</dd>
58
+ <dt class="ui-glossary__term">p95</dt>
59
+ <dd class="ui-glossary__def">The 95th-percentile value of a latency distribution.</dd>
60
+ </dl>
61
+ ```
62
+
63
+ ## Class reference
64
+
65
+ | Class | Role |
66
+ | --------------------- | ---------------------------------------------------------------- |
67
+ | `.ui-term` | The inline term `<button>` (dotted underline "has a definition"). |
68
+ | `.ui-def` | The definition body — a native `[popover]` styled as a raised card. |
69
+ | `.ui-glossary` | The end-of-document `<dl>` two-column glossary block. |
70
+ | `.ui-glossary__term` | A glossary `<dt>` (mono, the term). |
71
+ | `.ui-glossary__def` | A glossary `<dd>` (the definition). |
72
+
73
+ ## Accessibility & robustness
74
+
75
+ - Because the term is a `<button popovertarget>`, the definition is reachable by
76
+ **keyboard and touch**, not just hover — the whole point versus `abbr[title]`.
77
+ - The definition is real DOM in reading order, so a screen reader encounters it
78
+ right after the term.
79
+ - Popovers don't print; the **printed document leans on the `ui-glossary` block**,
80
+ so include it at the end of any report that uses inline terms.
81
+ - On narrow viewports the glossary stacks each term over its definition.
@@ -0,0 +1,78 @@
1
+ # Textref
2
+
3
+ `@ponchia/ui/css/textref.css` is an opt-in **deep-link-to-the-cited-sentence**
4
+ provenance primitive. A citation whose `href` is a URL [Text
5
+ Fragment](https://developer.mozilla.org/en-US/docs/Web/Text_fragments)
6
+ (`#…:~:text=`): the browser scrolls to the exact quoted text and highlights it,
7
+ and Bronto owns the on-brand `::target-text` paint. It is the inline counterpart
8
+ to the static `ui-src` / `ui-citation` trust layer (`sources.css`), which can
9
+ label a source but cannot point *inside* it.
10
+
11
+ ```css
12
+ @import '@ponchia/ui';
13
+ @import '@ponchia/ui/css/textref.css';
14
+ ```
15
+
16
+ ## How it behaves
17
+
18
+ - The link navigates to the document and the browser scrolls to + highlights the
19
+ first match of the quoted text — no script, no anchors to pre-place.
20
+ - Bronto repaints that browser highlight (`::target-text`) in the rationed
21
+ accent wash so it matches the rest of the trust layer.
22
+ - On engines without Text Fragments the link still navigates to the page (or its
23
+ `#section` anchor); the highlight is purely additive, so nothing breaks.
24
+
25
+ ## Wiring — the host builds the fragment URL
26
+
27
+ `::target-text` highlighting is driven entirely by the URL; Bronto ships no
28
+ runtime for it. Build the `href` with a three-line pure helper and drop it on the
29
+ `.ui-textref` link:
30
+
31
+ ```js
32
+ // Encode a quote as a URL Text Fragment directive.
33
+ // encodeTextFragment('p95 latency fell 38%') -> '#:~:text=p95%20latency%20fell%2038%25'
34
+ export function encodeTextFragment(quote, { prefix } = {}) {
35
+ const enc = (s) => encodeURIComponent(s).replace(/-/g, '%2D');
36
+ const text = prefix ? `${enc(prefix)}-,${enc(quote)}` : enc(quote);
37
+ return `#:~:text=${text}`;
38
+ }
39
+ ```
40
+
41
+ ```html
42
+ <p>
43
+ The migration cut p95 latency by 38%
44
+ <a
45
+ class="ui-textref"
46
+ href="https://example.com/incident-review#:~:text=p95%20latency%20fell%2038%25"
47
+ >jump to the source</a
48
+ >.
49
+ </p>
50
+ ```
51
+
52
+ Notes for an autonomous author:
53
+
54
+ - Text Fragment navigation requires a **user activation** (a real click) on most
55
+ engines — it will not fire from a programmatic `location.assign`.
56
+ - Keep the quote short and verbatim; a fuzzy or paraphrased quote won't match.
57
+ - Use the optional `prefix` (a `prefix-,` directive) to disambiguate a quote that
58
+ appears more than once on the target page.
59
+
60
+ ## Class reference
61
+
62
+ | Class | Role |
63
+ | ------------- | ------------------------------------------------------------------- |
64
+ | `.ui-textref` | A citation link whose `href` is a `#:~:text=` fragment; dotted underline + quote-jump cue. |
65
+
66
+ | Custom property | On | Meaning |
67
+ | ---------------------- | ------------- | ------------------------------------------------------------------------- |
68
+ | `--textref-highlight` | `.ui-textref` | The `::target-text` wash for the matched sentence (default `var(--accent-soft)`). |
69
+
70
+ ## Accessibility & robustness
71
+
72
+ - The link is a real `<a href>` — keyboard- and screen-reader-reachable, and it
73
+ degrades to ordinary navigation everywhere.
74
+ - `::target-text` is repainted with the system highlight colours under
75
+ `forced-colors`, so the landed-on sentence stays visible in high-contrast mode.
76
+ - The highlight is **global** once this leaf is imported: any text-fragment
77
+ landing on the page gets the brand wash, which keeps provenance highlighting
78
+ consistent across a report.
package/docs/theming.md CHANGED
@@ -19,17 +19,40 @@ theme-owned neutral ramp endpoint:
19
19
  | `--field-dot-accent` | `--accent` at 78% / 82% | form dot indicators |
20
20
  | `--focus-ring` | `var(--accent)` (solid) | **every focus outline** — override to tune the ring alone |
21
21
 
22
- So a full re-brand is one declaration globally or on any subtree:
22
+ So a full re-brand is one declaration **at `:root` (or a theme root)**:
23
23
 
24
24
  ```css
25
25
  :root { --accent: #2f6df6; } /* brand the whole app blue */
26
- .promo { --accent: #16a34a; } /* …or just this section green */
27
26
  :root[data-theme='dark'] { --accent: #6ea8ff; } /* per-theme tuning */
28
27
  ```
29
28
 
30
29
  Everything — buttons, focus rings, dot motifs, accent borders, soft
31
30
  fills — follows automatically, in both light and dark.
32
31
 
32
+ > **Re-branding a subtree (not `:root`) is only a *partial* re-brand.**
33
+ > The derived family (`--accent-soft`, `--accent-strong`, `--accent-text`,
34
+ > `--bg-accent`, the `--accent-1…6` ramp) is computed from `--accent` via
35
+ > `color-mix()` **at `:root`**, where it resolves once. A custom property's
36
+ > value is substituted where it's declared, so overriding only `--accent`
37
+ > on a `.promo` subtree re-brands the surfaces that read raw `var(--accent)`
38
+ > (focus rings, dot motifs, some borders) but leaves every *derived* surface
39
+ > (soft fills, accent-as-text, the ramp) at the root hue — a visibly broken
40
+ > half-rebrand. To re-brand a subtree fully, set the derived tokens you use
41
+ > too, e.g.:
42
+ >
43
+ > ```css
44
+ > .promo {
45
+ > --accent: #16a34a;
46
+ > --accent-strong: color-mix(in srgb, var(--accent) 83%, #000);
47
+ > --accent-text: var(--accent-strong);
48
+ > --accent-soft: color-mix(in srgb, var(--accent) 10%, transparent);
49
+ > --bg-accent: color-mix(in srgb, var(--accent) 6%, transparent);
50
+ > }
51
+ > ```
52
+ >
53
+ > When in doubt, re-brand at `:root`/`[data-theme]` — that path re-derives
54
+ > the whole family for you.
55
+
33
56
  > **Two contrast obligations when you change `--accent`:**
34
57
  >
35
58
  > 1. **Buttons** — pick an `--accent` with ≥ 4.5:1 against `--button-text`
@@ -46,6 +69,13 @@ fills — follows automatically, in both light and dark.
46
69
  > ring is solid `--accent` (≥ 3:1 non-text) — re-brand to a near-`--bg`
47
70
  > hue and you must also raise `--focus-ring` (it's an independent knob).
48
71
 
72
+ > **Re-inking accent fills: use `--button-text`, not `--on-accent`.** In-DOM
73
+ > components (buttons, chips, accent fills) paint their on-accent ink from
74
+ > `--button-text`. `--on-accent` is a **read-only export** of that resolved ink
75
+ > for *foreign renderers* (charts, canvas, a non-DOM target reading the token) —
76
+ > overriding it in CSS validates but does **not** change any in-DOM component. To
77
+ > change the ink on a re-brand, set `--button-text`.
78
+
49
79
  ## Other supported knobs
50
80
 
51
81
  - **Spacing** — override the `--space-2xs … --space-2xl` scale, or use a
@@ -182,9 +212,12 @@ you have, so the one-accent discipline holds.
182
212
  skin on a subtree would leave that family stale, so the selectors are
183
213
  `:root`-anchored and a subtree skin simply no-ops.
184
214
  - **Phosphor bloom.** The `amber-crt` / `phosphor-green` skins set
185
- `--dotmatrix-glow` (a Tier-3 display knob, default `0`) in dark, so the
186
- dot-matrix gains a CRT-style glow. Set it yourself on any `.ui-dotmatrix`
187
- to tune the bloom.
215
+ `--dotmatrix-glow` in dark, so the dot-matrix gains a CRT-style glow. It is a
216
+ Tier-3 *display knob* — a `--dotmatrix-*` CSS custom property with an inline
217
+ default of `0` (off), like `--dotmatrix-dot`/`--dotmatrix-gap`, **not** a token
218
+ in the palette export (Tier-3 display expression lives as `--dotmatrix-*` knobs
219
+ in `css/dots.css`, by ADR-0001). Set it yourself on any `.ui-dotmatrix` to tune
220
+ the bloom.
188
221
  - **Contrast-gated.** Every shipped skin accent meets the same WCAG AA / 3:1
189
222
  floors as the core palette — see [contrast.md](contrast.md) → "Display
190
223
  colorways". (Your _own_ `--accent` re-brand is still your obligation; the
@@ -210,6 +243,12 @@ const series = charts.dark.categorical; // ['#ff3b41', '#e69f00', …] — serie
210
243
  `var(--accent)` (your brand leads), series 2–8 are the Okabe-Ito
211
244
  colourblind-safe set. The set is **gated for mutual distinguishability under
212
245
  normal + simulated protanopia/deuteranopia/tritanopia** (OKLab ΔE).
246
+ **Caveat — the CVD gate measures the SHIPPED default accent.** Series 1 is
247
+ `var(--accent)`, so if you re-skin `--accent` you change series 1 but not the
248
+ Okabe-Ito 2–8, and the gate never re-checks your custom hue: a brand close to
249
+ series 3's orange can collide for a deuteranope. If a re-brand drives data-viz,
250
+ re-verify your accent against the set, or pin `--chart-1` to a fixed Okabe-Ito
251
+ value (`--chart-1: #0072b2`) and let your brand lead the UI only.
213
252
  - **Sequential `--chart-seq-1..6`** — single-hue light→dark, for
214
253
  heatmaps/intensity. **Diverging `--chart-div-1..7`** — blue↔neutral↔orange,
215
254
  for ±/gains-losses.
package/docs/toc.md ADDED
@@ -0,0 +1,83 @@
1
+ # Table of contents (scrollspy)
2
+
3
+ `@ponchia/ui/css/toc.css` is an opt-in **sticky contents rail** for long
4
+ generated reports. The entry for the section currently in view is highlighted, so
5
+ a reader always knows where they are. It degrades to a plain anchored list with
6
+ zero JavaScript.
7
+
8
+ ```css
9
+ @import '@ponchia/ui';
10
+ @import '@ponchia/ui/css/toc.css';
11
+ ```
12
+
13
+ ## How it behaves
14
+
15
+ - The rail sticks within its scroll container (`--toc-top` sets the inset).
16
+ - The active entry keys on the standard `aria-current="true"` hook — the same
17
+ rule every other nav surface here uses.
18
+ - Nested lists indent for sub-sections.
19
+
20
+ ## Wiring — the host mirrors the in-view section
21
+
22
+ CSS alone cannot know which section is on screen, so the host sets
23
+ `aria-current="true"` on the link for the current section. Two ways:
24
+
25
+ 1. **Static** — server-render `aria-current` on the section you're rendering a
26
+ "current page" for. No script at all.
27
+ 2. **Live scrollspy** — a tiny `IntersectionObserver` (~15 lines, copy-paste
28
+ below) mirrors the in-view section onto its link. No Bronto kernel ships for
29
+ this; the rail is fully useful as a static sticky list without it.
30
+
31
+ ```html
32
+ <nav class="ui-toc" aria-label="Contents">
33
+ <p class="ui-toc__title">Contents</p>
34
+ <ul class="ui-toc__list">
35
+ <li><a class="ui-toc__link" href="#intro" aria-current="true">Introduction</a></li>
36
+ <li><a class="ui-toc__link" href="#method">Method</a></li>
37
+ <li><a class="ui-toc__link" href="#results">Results</a></li>
38
+ </ul>
39
+ </nav>
40
+ ```
41
+
42
+ ```js
43
+ // Optional live scrollspy — mirror the in-view section onto its TOC link.
44
+ const links = new Map(
45
+ [...document.querySelectorAll('.ui-toc__link')].map((a) => [a.hash.slice(1), a]),
46
+ );
47
+ const spy = new IntersectionObserver(
48
+ (entries) => {
49
+ for (const e of entries) {
50
+ if (!e.isIntersecting) continue;
51
+ for (const a of links.values()) a.removeAttribute('aria-current');
52
+ links.get(e.target.id)?.setAttribute('aria-current', 'true');
53
+ }
54
+ },
55
+ { rootMargin: '0px 0px -70% 0px' },
56
+ );
57
+ for (const id of links.keys()) {
58
+ const section = document.getElementById(id);
59
+ if (section) spy.observe(section);
60
+ }
61
+ ```
62
+
63
+ ## Class reference
64
+
65
+ | Class | Role |
66
+ | ---------------- | ---------------------------------------------------------- |
67
+ | `.ui-toc` | The sticky contents rail (`<nav>`). |
68
+ | `.ui-toc__title` | Optional eyebrow heading for the rail. |
69
+ | `.ui-toc__list` | The list of entries (`<ul>`/`<ol>`; nests for sub-sections). |
70
+ | `.ui-toc__link` | An entry link; `aria-current="true"` marks the active one. |
71
+
72
+ | Custom property | On | Meaning |
73
+ | --------------- | --------- | ------------------------------------------------------------- |
74
+ | `--toc-top` | `.ui-toc` | Sticky inset from the top of the scroll container (default `var(--space-md)`). |
75
+
76
+ ## Accessibility & robustness
77
+
78
+ - The active cue is repainted with the system highlight under `forced-colors`,
79
+ so it survives high-contrast mode.
80
+ - Wrap the rail in a `<nav aria-label="Contents">` so it is announced as a
81
+ navigation landmark.
82
+ - Without the optional observer the rail is a normal anchored list — every link
83
+ still jumps to its section.
package/docs/tree.md ADDED
@@ -0,0 +1,74 @@
1
+ # Tree (hierarchy outline)
2
+
3
+ `@ponchia/ui/css/tree.css` is an opt-in **hierarchy outline** — a depth-indented
4
+ view of nested structure (file trees, report contents, nested generated-content
5
+ provenance, object graphs). It is built on nested native `<details>` (optionally
6
+ `name` exclusive-accordion groups), so open/close, keyboard toggling and the
7
+ open/close animation come from the platform. This leaf is the hierarchy *layer*
8
+ (rails, indent, chevron); it does **not** reinvent the disclosure grammar
9
+ `ui-accordion` already owns.
10
+
11
+ ```css
12
+ @import '@ponchia/ui';
13
+ @import '@ponchia/ui/css/tree.css';
14
+ ```
15
+
16
+ ## How it behaves
17
+
18
+ - A **branch** is a `<details class="ui-tree__branch">`; a **leaf** is a plain
19
+ `.ui-tree__leaf` row with no disclosure.
20
+ - Nested rows indent and carry a hairline guide rail back to their parent.
21
+ - The summary's chevron rotates open; auto-height open/close is gated exactly like
22
+ `ui-accordion` (Chrome 131+/Safari 18.4+; Firefox snaps).
23
+
24
+ ## Wiring
25
+
26
+ ```html
27
+ <div class="ui-tree">
28
+ <details class="ui-tree__branch" open>
29
+ <summary class="ui-tree__summary"><span class="ui-tree__label">src</span></summary>
30
+ <details class="ui-tree__branch">
31
+ <summary class="ui-tree__summary"><span class="ui-tree__label">components</span></summary>
32
+ <div class="ui-tree__leaf"><span class="ui-tree__label">button.css</span></div>
33
+ <div class="ui-tree__leaf"><span class="ui-tree__label">card.css</span></div>
34
+ </details>
35
+ <div class="ui-tree__leaf"><span class="ui-tree__label">index.js</span></div>
36
+ </details>
37
+ </div>
38
+ ```
39
+
40
+ Add `name="…"` to sibling `<details class="ui-tree__branch">` to make them an
41
+ exclusive-accordion group (only one open at a time) — a platform behaviour, no
42
+ script.
43
+
44
+ ## A11y honesty — disclosure group, not an ARIA tree
45
+
46
+ A native `<details>` group is a **disclosure group**, not an ARIA `tree`. Do not
47
+ bolt `role="tree"` / `role="treeitem"` / `aria-level` onto this markup: those
48
+ roles imply a roving-focus keyboard model (Up/Down to move, Left/Right to
49
+ collapse/expand, typeahead) that native `<details>` does not provide, so the
50
+ result would announce a contract the keyboard can't honour.
51
+
52
+ That roving-focus kernel is intentionally **not shipped** with this leaf. It
53
+ lands behind a real consumer that needs the full APG tree interaction (e.g. a
54
+ workbench file pane). Until then, the disclosure semantics are correct and
55
+ honest as-is.
56
+
57
+ ## Class reference
58
+
59
+ | Class | Role |
60
+ | -------------------- | ---------------------------------------------------------- |
61
+ | `.ui-tree` | The outline container. |
62
+ | `.ui-tree__branch` | A `<details>` node with children (twist chevron). |
63
+ | `.ui-tree__leaf` | A leaf row with no disclosure (chevron-aligned spacer). |
64
+ | `.ui-tree__summary` | The branch's `<summary>` toggle row. |
65
+ | `.ui-tree__label` | The row label (truncates with ellipsis). |
66
+
67
+ ## Accessibility & robustness
68
+
69
+ - Open/close, keyboard toggling (Enter/Space on the summary) and the exclusive
70
+ `name` grouping are all native `<details>` behaviour.
71
+ - The chevron is `currentColor` geometry, so it survives `forced-colors`; the
72
+ open-branch accent cue is re-asserted with the system highlight.
73
+ - The chevron spin and the auto-height animation both respect
74
+ `prefers-reduced-motion`.