@ponchia/ui 0.6.3 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +7 -7
  3. package/behaviors/glyph.d.ts +7 -0
  4. package/behaviors/glyph.d.ts.map +1 -1
  5. package/behaviors/glyph.js +58 -4
  6. package/behaviors/index.d.ts +2 -0
  7. package/behaviors/index.d.ts.map +1 -1
  8. package/behaviors/index.js +2 -0
  9. package/behaviors/sources.d.ts +28 -0
  10. package/behaviors/sources.d.ts.map +1 -0
  11. package/behaviors/sources.js +158 -0
  12. package/classes/classes.json +210 -4
  13. package/classes/index.d.ts +75 -1
  14. package/classes/index.js +95 -1
  15. package/css/dots.css +210 -3
  16. package/css/report.css +359 -7
  17. package/css/skins.css +9 -0
  18. package/css/sources.css +18 -0
  19. package/css/spark.css +14 -0
  20. package/css/table.css +7 -1
  21. package/css/tokens.css +8 -1
  22. package/dist/bronto.css +1 -1
  23. package/dist/css/dots.css +1 -1
  24. package/dist/css/report.css +1 -1
  25. package/dist/css/skins.css +1 -1
  26. package/dist/css/sources.css +1 -1
  27. package/dist/css/spark.css +1 -1
  28. package/dist/css/table.css +1 -1
  29. package/dist/css/tokens.css +1 -1
  30. package/docs/dots.md +146 -0
  31. package/docs/frontier-primitives.md +262 -0
  32. package/docs/glyphs.md +114 -0
  33. package/docs/package-contract.md +263 -0
  34. package/docs/reference.md +115 -1
  35. package/docs/reporting.md +296 -16
  36. package/docs/sources.md +32 -0
  37. package/docs/stability.md +6 -3
  38. package/glyphs/glyphs.d.ts +61 -0
  39. package/glyphs/glyphs.js +593 -30
  40. package/llms.txt +79 -25
  41. package/package.json +13 -3
  42. package/qwik/index.d.ts +1 -0
  43. package/qwik/index.d.ts.map +1 -1
  44. package/qwik/index.js +5 -0
  45. package/react/index.d.ts +1 -0
  46. package/react/index.d.ts.map +1 -1
  47. package/react/index.js +3 -0
  48. package/solid/index.d.ts +2 -0
  49. package/solid/index.d.ts.map +1 -1
  50. package/solid/index.js +3 -0
  51. package/tokens/skins.js +22 -9
package/CHANGELOG.md CHANGED
@@ -5,6 +5,59 @@
5
5
  |> `^0` / `*` wildcard does **not** protect you. See README → Versioning, and
6
6
  |> the deprecation policy in CONTRIBUTING.md.
7
7
 
8
+ ## 0.6.5 — 2026-06-09
9
+
10
+ Patch release: the source-backref behavior, the report decision/evidence
11
+ grammar, and package/release hardening. No breaking changes, no
12
+ `MIGRATIONS.json` entry.
13
+
14
+ ### Added
15
+
16
+ - **`initSources` behavior** — optional citation→source backref focus:
17
+ inside a `data-bronto-sources` host, a `.ui-citation[href^="#"]` (or an
18
+ explicit `data-bronto-source-ref`) click scrolls to its source card and
19
+ marks it `is-source-active`, emitting a cancellable focus event with the
20
+ `SourceFocusDetail` payload. Progressive enhancement over authored ids —
21
+ no DOM is generated; framework bindings re-export it.
22
+ - **Report decision/evidence grammar** — adds public, static report primitives
23
+ for decision blocks, severity-labelled findings, compact evidence packets and
24
+ follow-up action rows.
25
+ - **Claim and action-register grammar** — adds claim status blocks, structured
26
+ finding parts, evidence-method parts, evidence ledgers, decision detail rows,
27
+ and action owner/due/criteria/source parts for more auditable reports.
28
+ - **Package contract doc** — `docs/package-contract.md` pins the published
29
+ surface (exports map, files allowlist, generated-artifact freshness) that
30
+ `check:pack`/`check:publint`/`check:attw` enforce.
31
+
32
+ ### Changed
33
+
34
+ - **Report fixtures and local guardrails** — expands the standalone and full
35
+ report demos with source/provenance examples, SVG accessibility details and a
36
+ local-only public-boundary terms gate for `check:report`.
37
+ - **Report shape checks** — `check:report` now parses report demos with a DOM
38
+ instead of regexes and validates the public claim/action contracts.
39
+ - **Release observability** — the release workflow gains a metadata gate and
40
+ theme-axis verification; the examples consumer build broadens its smoke
41
+ coverage.
42
+
43
+ ## 0.6.4 — 2026-06-08
44
+
45
+ Patch release for the dot-matrix expansion and static report hardening shipped
46
+ in #116. No breaking changes, no `MIGRATIONS.json` entry.
47
+
48
+ ### Added
49
+
50
+ - **Dot-matrix/glyph expansion** — adds the generated glyph metadata surface,
51
+ docs, and contract tests for the expanded dot/readout vocabulary.
52
+ - **Report authoring guidance** — documents the static report grammar around
53
+ captions, alert bodies, table wrapping, and local/CDN asset handling.
54
+
55
+ ### Fixed
56
+
57
+ - **Static report print/layout behavior** — hardens report print margins and
58
+ table wrapping so standalone HTML reports and PDF exports degrade more
59
+ predictably across long tokens and normal prose.
60
+
8
61
  ## 0.6.3 — 2026-06-08
9
62
 
10
63
  Patch release to publish the WebKit release fix after the `v0.6.1` and `v0.6.2`
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm](https://img.shields.io/npm/v/@ponchia/ui?logo=npm)](https://www.npmjs.com/package/@ponchia/ui)
4
4
  [![npm provenance](https://img.shields.io/badge/npm-provenance-blue?logo=npm)](https://www.npmjs.com/package/@ponchia/ui#provenance)
5
5
  [![runtime deps](https://img.shields.io/badge/runtime%20deps-0-brightgreen)](https://github.com/Ponchia/bronto-ui/blob/main/package.json)
6
- [![dist](https://img.shields.io/badge/dist-~76kB%20%2F%20~13kB%20gzip-informational)](https://github.com/Ponchia/bronto-ui/blob/main/scripts/check-dist.mjs)
6
+ [![dist](https://img.shields.io/badge/dist-~87kB%20%2F%20~15kB%20gzip-informational)](https://github.com/Ponchia/bronto-ui/blob/main/scripts/check-dist.mjs)
7
7
  [![CI](https://github.com/Ponchia/bronto-ui/actions/workflows/ci.yml/badge.svg)](https://github.com/Ponchia/bronto-ui/actions/workflows/ci.yml)
8
8
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Ponchia/bronto-ui/badge)](https://scorecard.dev/viewer/?uri=github.com/Ponchia/bronto-ui)
9
9
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/Ponchia/bronto-ui/blob/main/LICENSE)
@@ -57,7 +57,7 @@ Or drop it in with no build step, straight from a CDN:
57
57
 
58
58
  ## Quick start
59
59
 
60
- **1. Load the CSS.** One flattened, minified bundle — the whole standard component set, one request (~76 kB raw / ~13 kB gzip):
60
+ **1. Load the CSS.** One flattened, minified bundle — the whole standard component set, one request (~87 kB raw / ~15 kB gzip):
61
61
 
62
62
  ```css
63
63
  @import '@ponchia/ui'; /* via a bundler */
@@ -102,7 +102,7 @@ initDialog(); // native <dialog> glue: [data-bronto-open] / [data-bronto-
102
102
  toast('Saved', { tone: 'success' }); // body-anchored stack, no markup needed
103
103
  ```
104
104
 
105
- Behaviors cover theme persistence, disclosure, dropdown menus, native-`<dialog>` modals/drawers, tabs, combobox, form validation, table sort, carousel and toasts — wired by `data-bronto-*` attributes.
105
+ Behaviors cover theme persistence, disclosure, dropdown menus, native-`<dialog>` modals/drawers, tabs, combobox, form validation, table sort, carousel, source backrefs and toasts — wired by `data-bronto-*` attributes.
106
106
 
107
107
  **5. (Optional) display glyphs — a 48-glyph dot-matrix icon set:**
108
108
 
@@ -126,7 +126,7 @@ Arrows, chevrons, check/close/plus/minus, search/menu/gear, info/warning/bell/lo
126
126
  - **Shells** — an admin dashboard shell (`ui-app-*`) and a content/marketing site shell (`ui-site*`, `ui-container`).
127
127
  - **Prose** — `.ui-prose` styles raw, unclassed semantic HTML (Markdown / CMS / LLM output) with zero classes.
128
128
  - **Analytical & communication primitives** _(opt-in)_ — `@ponchia/ui/css/analytical.css`: SVG **annotations** (subject/connector/note), standalone **legends**/data-keys, text/evidence **marks**, leader-line **connectors** (+ a pure `@ponchia/ui/connectors` geometry kernel), a guided-focus **spotlight**, a **crosshair**/readout, and a cross-cutting **selection** vocabulary. Each owns its visual grammar + pure geometry and refuses scales/state/hit-testing — figures that explain themselves, not a chart engine. Plus standalone **`source`/provenance** (trust) and **lifecycle `state`** leaves.
129
- - **Reports** _(opt-in)_ — `@ponchia/ui/css/report.css`, a static/PDF-first report grammar for LLM-authored HTML: covers, sections, findings, evidence, figures, chart wrappers and print utilities.
129
+ - **Reports** _(opt-in)_ — `@ponchia/ui/css/report.css`, a static/PDF-first report grammar for LLM-authored HTML: covers, decisions, claims, sections, severity-labelled findings, evidence packets, evidence ledgers, action registers, source-card bindings, figures, chart wrappers and print utilities.
130
130
  - **Motion & dots** — the dot-matrix motif kit: dot grid, status dots, dot loaders, the orbital spinner, matrix reveal — all reduced-motion aware.
131
131
  - **Glyphs** — `@ponchia/ui/glyphs`, a 48-glyph dot-matrix icon set on the `.ui-dotmatrix` primitive (display marks + crisp `solid` inline icons + one-node `.ui-icon` mask rendering).
132
132
  - **Colorways** _(opt-in)_ — `data-bronto-skin="amber-crt | phosphor-green | e-ink"`: a root-level recolour of the one accent (OKLCH, per-theme, contrast-gated), never in the default bundle.
@@ -173,13 +173,13 @@ Per-framework getting-started guides + runnable example apps live in the repo:
173
173
 
174
174
  - **Tokens as data** — `import tokens, { themeColor, cssVars } from '@ponchia/ui/tokens'` (plus `tokens.json`, W3C DTCG `tokens.dtcg.json`, and `tokens/resolved.json` — concrete hex per theme for canvas/SVG/MapLibre).
175
175
  - **Chart colours for dashboards** — `import charts from '@ponchia/ui/charts.json' with { type: 'json' }` in Node ESM, or the same path through a bundler JSON import (resolved hex per theme; series 1 = your accent) plus the opt-in `@ponchia/ui/css/dataviz.css`.
176
- - **Static reports for LLMs** — add `@ponchia/ui/css/report.css` for report structure and print utilities; pair with `@ponchia/ui/css/dataviz.css` only when the report contains charts. Full cookbook: `docs/reporting.md`.
176
+ - **Static reports for LLMs** — add `@ponchia/ui/css/report.css` for report structure, claim/evidence/action grammar, source-card bindings, and print utilities; pair with `@ponchia/ui/css/dataviz.css` only when the report contains charts. Full cookbook: `docs/reporting.md`.
177
177
  - **Modern-platform motion** — overlays (modal/drawer/popover), toasts and the `<details>` accordion animate **in and out** with zero JS (`@starting-style` + `allow-discrete`, `::details-content` + `interpolate-size`). Progressive-enhancement extras: `.ui-scroll-progress` / `.ui-scroll-reveal` (scroll-driven, no JS) and `.ui-vt` for View Transitions. All degrade to a static end-state and respect `prefers-reduced-motion`. For smooth **cross-document** navigations, add the document-global one-liner to your own top-level (unlayered) CSS: `@view-transition { navigation: auto; }`.
178
178
  - **Editor IntelliSense** — point VS Code at the shipped custom-data file so every token autocompletes in `var(--…)`:
179
179
  ```json
180
180
  { "css.customData": ["node_modules/@ponchia/ui/classes/vscode.css-custom-data.json"] }
181
181
  ```
182
- - **For AI coding agents** — the package ships `llms.txt` at its root plus `docs/reference.md`, `docs/usage.md`, `docs/reporting.md`, `docs/theming.md`, `docs/contrast.md`, `docs/stability.md`, the color constitution `docs/adr/0001-color-system.md` and the `CHANGELOG` inside the tarball, so an offline agent has the full API and rationale without guessing.
182
+ - **For AI coding agents** — the package ships `llms.txt` at its root plus `docs/reference.md`, `docs/usage.md`, `docs/reporting.md`, `docs/theming.md`, `docs/contrast.md`, `docs/stability.md`, `docs/package-contract.md`, the color constitution `docs/adr/0001-color-system.md` and the `CHANGELOG` inside the tarball, so an offline agent has the full API and rationale without guessing.
183
183
 
184
184
  > The package root is **CSS-only**. Use `@import '@ponchia/ui'` in CSS, or `import '@ponchia/ui'` only as a CSS side-effect import in a CSS-aware bundler (Vite, Astro, SvelteKit, webpack). Do not import the package root from Node/runtime JS. JS entrypoints are explicit subpaths: `/tokens`, `/classes`, `/behaviors`, `/glyphs`, `/react`, `/solid`, `/qwik`, `/skins`, and `/charts`.
185
185
  > JS subpaths are **ESM-only**. CommonJS consumers should use dynamic
@@ -193,7 +193,7 @@ Recent-evergreen, by design. The framework targets the modern web platform — c
193
193
 
194
194
  Pre-1.0 and deliberately so. **Until `1.0.0`, breaking changes ship in the _minor_** (`0.x.0`); patches (`0.x.y`) are always non-breaking. Pin with the patch range — at `0.x`, `~0.6.0` (and equivalently `^0.6.0`) resolves to `>=0.6.0 <0.7.0`, giving you safe patches while holding back the next breaking minor. Every breaking change is called out under a **BREAKING** heading in the **[CHANGELOG](https://github.com/Ponchia/bronto-ui/blob/main/CHANGELOG.md)** with a migration note.
195
195
 
196
- Contractual (changes are breaking): token **names** and documented token roles, `.ui-*` class and recipe names, `data-bronto-*` attributes, exported behavior/glyph/binding function names and each behavior's cleanup contract. Not contractual (may change any release): exact token **values** and generated colour math outputs (visual tuning) unless a doc explicitly says the value is stable, plus internal leaf-file / `@layer` boundaries. See **[docs/stability.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/stability.md)** for the full public-surface matrix.
196
+ Contractual (changes are breaking): token **names** and documented token roles, `.ui-*` class and recipe names, `data-bronto-*` attributes, exported behavior/glyph/binding function names and each behavior's cleanup contract. Not contractual (may change any release): exact token **values** and generated colour math outputs (visual tuning) unless a doc explicitly says the value is stable, plus internal leaf-file / `@layer` boundaries. See **[docs/stability.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/stability.md)** for the semantic public-surface matrix and **[docs/package-contract.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/package-contract.md)** for the generated export/file/provenance inventory.
197
197
 
198
198
  Release candidates publish to the `next` dist-tag, never to `latest` — opt in with `npm i @ponchia/ui@next` to try an upcoming version early. A plain `npm i @ponchia/ui` only ever resolves a stable release.
199
199
 
@@ -7,6 +7,13 @@
7
7
  * name is left untouched. Idempotent (skips an already-expanded host); the
8
8
  * returned cleanup removes the cells and restores the original attributes.
9
9
  *
10
+ * `data-bronto-glyph-render="mask"` takes the cheap one-node path instead:
11
+ * the host becomes a single `.ui-icon` masked by the glyph (no GLYPH_SIZE²
12
+ * cells), inheriting `currentColor` and scaling with the text — the DOM
13
+ * counterpart to renderGlyph's `render: 'mask'`, for an icon in every table
14
+ * row where 256 cells per glyph is too heavy. `data-bronto-glyph-size` sets
15
+ * `--icon-size`. The cell-mode attributes (solid/anim) don't apply.
16
+ *
10
17
  * @param {import('./internal.js').DelegateOpts} [opts]
11
18
  * @returns {import('./internal.js').Cleanup}
12
19
  */
@@ -1 +1 @@
1
- {"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["glyph.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;GAWG;AACH,wCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAiH3C"}
1
+ {"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["glyph.js"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAyJ3C"}
@@ -1,11 +1,18 @@
1
1
  import { hasDom, resolveHost, noop, collectHosts } from './internal.js';
2
- import { GLYPH_SIZE, glyphCells } from '../glyphs/glyphs.js';
2
+ import { GLYPH_SIZE, glyphCells, glyphMask } from '../glyphs/glyphs.js';
3
3
 
4
4
  function restoreAttr(el, name, prev) {
5
5
  if (prev === null) el.removeAttribute(name);
6
6
  else el.setAttribute(name, prev);
7
7
  }
8
8
 
9
+ // `dot`/`gap`/`size` land in inline CSS, so allow only length/calc syntax —
10
+ // drop anything with a `;`/`{` that could open a second declaration (mirrors
11
+ // glyphs.js cssLen). Used for the mask path's --icon-size.
12
+ function cssLen(v) {
13
+ return v && /^[\w.%+\-*/()\s,]+$/.test(v) ? v : '';
14
+ }
15
+
9
16
  /**
10
17
  * Expand `[data-bronto-glyph="name"]` placeholders into a `.ui-dotmatrix`
11
18
  * grid of GLYPH_SIZE² cells — the DOM counterpart to renderGlyph() from
@@ -15,6 +22,13 @@ function restoreAttr(el, name, prev) {
15
22
  * name is left untouched. Idempotent (skips an already-expanded host); the
16
23
  * returned cleanup removes the cells and restores the original attributes.
17
24
  *
25
+ * `data-bronto-glyph-render="mask"` takes the cheap one-node path instead:
26
+ * the host becomes a single `.ui-icon` masked by the glyph (no GLYPH_SIZE²
27
+ * cells), inheriting `currentColor` and scaling with the text — the DOM
28
+ * counterpart to renderGlyph's `render: 'mask'`, for an icon in every table
29
+ * row where 256 cells per glyph is too heavy. `data-bronto-glyph-size` sets
30
+ * `--icon-size`. The cell-mode attributes (solid/anim) don't apply.
31
+ *
18
32
  * @param {import('./internal.js').DelegateOpts} [opts]
19
33
  * @returns {import('./internal.js').Cleanup}
20
34
  */
@@ -26,14 +40,54 @@ export function initDotGlyph({ root } = {}) {
26
40
  const cleanups = [];
27
41
 
28
42
  for (const el of els) {
43
+ const name = el.getAttribute('data-bronto-glyph');
44
+ const label = el.getAttribute('data-bronto-glyph-label');
45
+
46
+ // One-node mask path — the icon-at-scale counterpart to the 256-cell grid.
47
+ if (el.getAttribute('data-bronto-glyph-render') === 'mask') {
48
+ if (el.classList.contains('ui-icon') && el.style.getPropertyValue('--icon-mask')) continue;
49
+ const mask = glyphMask(name);
50
+ if (!mask) continue; // unknown glyph — leave the placeholder as-is
51
+ const hadIcon = el.classList.contains('ui-icon');
52
+ const hadMask = el.style.getPropertyValue('--icon-mask');
53
+ const hadSize = el.style.getPropertyValue('--icon-size');
54
+ const hadAriaHiddenM = el.getAttribute('aria-hidden');
55
+ const hadRoleM = el.getAttribute('role');
56
+ const hadAriaLabelM = el.getAttribute('aria-label');
57
+ const sizeM = cssLen(el.getAttribute('data-bronto-glyph-size'));
58
+
59
+ el.classList.add('ui-icon');
60
+ el.style.setProperty('--icon-mask', mask);
61
+ if (sizeM) el.style.setProperty('--icon-size', sizeM);
62
+ if (label) {
63
+ el.setAttribute('role', 'img');
64
+ el.setAttribute('aria-label', label);
65
+ el.removeAttribute('aria-hidden');
66
+ } else {
67
+ el.setAttribute('aria-hidden', 'true');
68
+ }
69
+
70
+ cleanups.push(() => {
71
+ if (!hadIcon) el.classList.remove('ui-icon');
72
+ if (hadMask) el.style.setProperty('--icon-mask', hadMask);
73
+ else el.style.removeProperty('--icon-mask');
74
+ if (sizeM && !hadSize) el.style.removeProperty('--icon-size');
75
+ else if (hadSize) el.style.setProperty('--icon-size', hadSize);
76
+ restoreAttr(el, 'aria-hidden', hadAriaHiddenM);
77
+ restoreAttr(el, 'role', hadRoleM);
78
+ restoreAttr(el, 'aria-label', hadAriaLabelM);
79
+ if (el.getAttribute('class') === '') el.removeAttribute('class');
80
+ if (el.getAttribute('style') === '') el.removeAttribute('style');
81
+ });
82
+ continue;
83
+ }
84
+
29
85
  // Scope to DIRECT-child cells (the ones we append) — so a placeholder that
30
86
  // legitimately nests its own .ui-dotmatrix is neither mis-read as already
31
87
  // expanded here nor have its inner cells removed by cleanup below.
32
88
  if (el.querySelector(':scope > .ui-dotmatrix__cell')) continue; // already expanded
33
- const cells = glyphCells(el.getAttribute('data-bronto-glyph'));
89
+ const cells = glyphCells(name);
34
90
  if (!cells.length) continue; // unknown glyph — leave the placeholder as-is
35
-
36
- const label = el.getAttribute('data-bronto-glyph-label');
37
91
  // `data-bronto-glyph-solid` → square, gapless pixel glyph (legible small),
38
92
  // the DOM counterpart to renderGlyph's `solid` option. Implies glyph-only.
39
93
  const solid = el.hasAttribute('data-bronto-glyph-solid');
@@ -17,6 +17,7 @@ export { initConnectors } from "./connectors.js";
17
17
  export { initSpotlight } from "./spotlight.js";
18
18
  export { initCrosshair } from "./crosshair.js";
19
19
  export { initCommand } from "./command.js";
20
+ export { initSources } from "./sources.js";
20
21
  export type Cleanup = import("./internal.js").Cleanup;
21
22
  export type DelegateOpts = import("./internal.js").DelegateOpts;
22
23
  export type ThemeStorageOpts = import("./theme.js").ThemeStorageOpts;
@@ -27,5 +28,6 @@ export type ModalCloseDetail = import("./modal.js").ModalCloseDetail;
27
28
  export type LegendToggleDetail = import("./legend.js").LegendToggleDetail;
28
29
  export type CrosshairMoveDetail = import("./crosshair.js").CrosshairMoveDetail;
29
30
  export type CommandSelectDetail = import("./command.js").CommandSelectDetail;
31
+ export type SourceFocusDetail = import("./sources.js").SourceFocusDetail;
30
32
  export { applyStoredTheme, initThemeToggle } from "./theme.js";
31
33
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;sBAmBa,OAAO,eAAe,EAAE,OAAO;2BAC/B,OAAO,eAAe,EAAE,YAAY;+BACpC,OAAO,YAAY,EAAE,gBAAgB;6BACrC,OAAO,YAAY,EAAE,cAAc;gCACnC,OAAO,YAAY,EAAE,iBAAiB;wBACtC,OAAO,YAAY,EAAE,SAAS;+BAC9B,OAAO,YAAY,EAAE,gBAAgB;iCACrC,OAAO,aAAa,EAAE,kBAAkB;kCACxC,OAAO,gBAAgB,EAAE,mBAAmB;kCAC5C,OAAO,cAAc,EAAE,mBAAmB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;sBAmBa,OAAO,eAAe,EAAE,OAAO;2BAC/B,OAAO,eAAe,EAAE,YAAY;+BACpC,OAAO,YAAY,EAAE,gBAAgB;6BACrC,OAAO,YAAY,EAAE,cAAc;gCACnC,OAAO,YAAY,EAAE,iBAAiB;wBACtC,OAAO,YAAY,EAAE,SAAS;+BAC9B,OAAO,YAAY,EAAE,gBAAgB;iCACrC,OAAO,aAAa,EAAE,kBAAkB;kCACxC,OAAO,gBAAgB,EAAE,mBAAmB;kCAC5C,OAAO,cAAc,EAAE,mBAAmB;gCAC1C,OAAO,cAAc,EAAE,iBAAiB"}
@@ -27,6 +27,7 @@
27
27
  * @typedef {import('./legend.js').LegendToggleDetail} LegendToggleDetail
28
28
  * @typedef {import('./crosshair.js').CrosshairMoveDetail} CrosshairMoveDetail
29
29
  * @typedef {import('./command.js').CommandSelectDetail} CommandSelectDetail
30
+ * @typedef {import('./sources.js').SourceFocusDetail} SourceFocusDetail
30
31
  */
31
32
  export { applyStoredTheme, initThemeToggle } from './theme.js';
32
33
  export { dismissible } from './dismissible.js';
@@ -48,3 +49,4 @@ export { initConnectors } from './connectors.js';
48
49
  export { initSpotlight } from './spotlight.js';
49
50
  export { initCrosshair } from './crosshair.js';
50
51
  export { initCommand } from './command.js';
52
+ export { initSources } from './sources.js';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Source/citation affordances for the `sources.css` trust layer. The behavior
3
+ * is deliberately small: within each `[data-bronto-sources]` island it resolves
4
+ * `.ui-citation[href^="#"]` and `[data-bronto-source-ref]` controls to source
5
+ * cards, adds non-visual preview metadata (`title` + `aria-describedby`), and
6
+ * on activation moves focus to the source card with a temporary
7
+ * `.is-source-active` highlight. The host still owns fetching, numbering,
8
+ * trust decisions, and any rich preview UI.
9
+ *
10
+ * @param {import('./internal.js').DelegateOpts} [opts]
11
+ * @returns {import('./internal.js').Cleanup}
12
+ */
13
+ export function initSources({ root }?: import("./internal.js").DelegateOpts): import("./internal.js").Cleanup;
14
+ export type SourceFocusDetail = {
15
+ /**
16
+ * The focused source-card id.
17
+ */
18
+ id: string;
19
+ /**
20
+ * The citation/control that requested the source.
21
+ */
22
+ citation: Element;
23
+ /**
24
+ * The target source card or source element.
25
+ */
26
+ source: Element;
27
+ };
28
+ //# sourceMappingURL=sources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["sources.js"],"names":[],"mappings":"AA2CA;;;;;;;;;;;GAWG;AACH,uCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAwG3C;;;;;QAjJa,MAAM;;;;cACN,OAAO;;;;YACP,OAAO"}
@@ -0,0 +1,158 @@
1
+ import {
2
+ hasDom,
3
+ resolveHost,
4
+ noop,
5
+ bindOnce,
6
+ byIdInHost,
7
+ collectHosts,
8
+ scrollIntoViewSafe,
9
+ } from './internal.js';
10
+
11
+ /**
12
+ * @typedef {object} SourceFocusDetail
13
+ * @property {string} id The focused source-card id.
14
+ * @property {Element} citation The citation/control that requested the source.
15
+ * @property {Element} source The target source card or source element.
16
+ */
17
+
18
+ const REF_SELECTOR = '[data-bronto-source-ref], .ui-citation[href^="#"]';
19
+ const ACTIVE = 'is-source-active';
20
+
21
+ function sourceId(ref) {
22
+ const explicit = ref.getAttribute('data-bronto-source-ref');
23
+ if (explicit) return explicit.replace(/^#/, '');
24
+ const href = ref.getAttribute('href') || '';
25
+ if (!href.startsWith('#') || href === '#') return '';
26
+ try {
27
+ return decodeURIComponent(href.slice(1));
28
+ } catch {
29
+ return href.slice(1);
30
+ }
31
+ }
32
+
33
+ function sourcePreview(source) {
34
+ const text = (selector) =>
35
+ source.querySelector(selector)?.textContent?.replace(/\s+/g, ' ').trim() || '';
36
+ const title = text('.ui-source-card__title');
37
+ const meta = [text('.ui-source-card__origin'), text('.ui-source-card__time')]
38
+ .filter(Boolean)
39
+ .join(' · ');
40
+ const excerpt = text('.ui-source-card__excerpt');
41
+ return [title, meta, excerpt].filter(Boolean).join(' — ');
42
+ }
43
+
44
+ /**
45
+ * Source/citation affordances for the `sources.css` trust layer. The behavior
46
+ * is deliberately small: within each `[data-bronto-sources]` island it resolves
47
+ * `.ui-citation[href^="#"]` and `[data-bronto-source-ref]` controls to source
48
+ * cards, adds non-visual preview metadata (`title` + `aria-describedby`), and
49
+ * on activation moves focus to the source card with a temporary
50
+ * `.is-source-active` highlight. The host still owns fetching, numbering,
51
+ * trust decisions, and any rich preview UI.
52
+ *
53
+ * @param {import('./internal.js').DelegateOpts} [opts]
54
+ * @returns {import('./internal.js').Cleanup}
55
+ */
56
+ export function initSources({ root } = {}) {
57
+ if (!hasDom()) return noop;
58
+ const host = resolveHost(root);
59
+ if (!host) return noop;
60
+
61
+ const islands = collectHosts(host, '[data-bronto-sources]');
62
+ const cleanups = [];
63
+
64
+ for (const island of islands) {
65
+ const timers = new Set();
66
+ const seeded = [];
67
+
68
+ const targetFor = (ref) => {
69
+ const id = sourceId(ref);
70
+ if (!id) return null;
71
+ return byIdInHost(island, id);
72
+ };
73
+
74
+ const seed = () => {
75
+ for (const ref of island.querySelectorAll(REF_SELECTOR)) {
76
+ const source = targetFor(ref);
77
+ if (!source?.id) continue;
78
+
79
+ const describedBy = ref.getAttribute('aria-describedby') || '';
80
+ const describedIds = describedBy.split(/\s+/).filter(Boolean);
81
+ const title = ref.getAttribute('title');
82
+ const preview = sourcePreview(source);
83
+ const prior = {
84
+ ref,
85
+ describedBy,
86
+ hadDescribedBy: ref.hasAttribute('aria-describedby'),
87
+ title,
88
+ hadTitle: ref.hasAttribute('title'),
89
+ };
90
+
91
+ if (!describedIds.includes(source.id)) {
92
+ ref.setAttribute('aria-describedby', [...describedIds, source.id].join(' '));
93
+ }
94
+ if (!title && preview) ref.setAttribute('title', preview);
95
+ if (!source.hasAttribute('tabindex')) {
96
+ prior.source = source;
97
+ prior.hadTabindex = false;
98
+ source.setAttribute('tabindex', '-1');
99
+ }
100
+ seeded.push(prior);
101
+ }
102
+ };
103
+
104
+ const focusSource = (ref, source) => {
105
+ for (const card of island.querySelectorAll(`.${ACTIVE}`)) card.classList.remove(ACTIVE);
106
+ for (const timer of timers) clearTimeout(timer);
107
+ timers.clear();
108
+
109
+ source.classList.add(ACTIVE);
110
+ source.focus?.({ preventScroll: true });
111
+ scrollIntoViewSafe(source);
112
+
113
+ const timer = setTimeout(() => {
114
+ source.classList.remove(ACTIVE);
115
+ timers.delete(timer);
116
+ }, 1600);
117
+ timers.add(timer);
118
+
119
+ island.dispatchEvent(
120
+ new CustomEvent('bronto:source:focus', {
121
+ detail: { id: source.id, citation: ref, source },
122
+ bubbles: true,
123
+ }),
124
+ );
125
+ };
126
+
127
+ const onClick = (e) => {
128
+ const ref = e.target.closest?.(REF_SELECTOR);
129
+ if (!ref || !island.contains(ref)) return;
130
+ const source = targetFor(ref);
131
+ if (!source) return;
132
+ if (!ref.matches('a[href]')) e.preventDefault();
133
+ focusSource(ref, source);
134
+ };
135
+
136
+ const cleanup = bindOnce(island, 'sources', () => {
137
+ seed();
138
+ island.addEventListener('click', onClick);
139
+ return () => {
140
+ island.removeEventListener('click', onClick);
141
+ for (const timer of timers) clearTimeout(timer);
142
+ timers.clear();
143
+ for (const item of seeded.splice(0)) {
144
+ if (item.hadDescribedBy) item.ref.setAttribute('aria-describedby', item.describedBy);
145
+ else item.ref.removeAttribute('aria-describedby');
146
+ if (item.hadTitle) item.ref.setAttribute('title', item.title);
147
+ else item.ref.removeAttribute('title');
148
+ if (item.source && item.hadTabindex === false) item.source.removeAttribute('tabindex');
149
+ }
150
+ for (const card of island.querySelectorAll(`.${ACTIVE}`)) card.classList.remove(ACTIVE);
151
+ };
152
+ });
153
+
154
+ cleanups.push(cleanup);
155
+ }
156
+
157
+ return () => cleanups.forEach((fn) => fn());
158
+ }