@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.
- package/CHANGELOG.md +53 -0
- package/README.md +7 -7
- package/behaviors/glyph.d.ts +7 -0
- package/behaviors/glyph.d.ts.map +1 -1
- package/behaviors/glyph.js +58 -4
- package/behaviors/index.d.ts +2 -0
- package/behaviors/index.d.ts.map +1 -1
- package/behaviors/index.js +2 -0
- package/behaviors/sources.d.ts +28 -0
- package/behaviors/sources.d.ts.map +1 -0
- package/behaviors/sources.js +158 -0
- package/classes/classes.json +210 -4
- package/classes/index.d.ts +75 -1
- package/classes/index.js +95 -1
- package/css/dots.css +210 -3
- package/css/report.css +359 -7
- package/css/skins.css +9 -0
- package/css/sources.css +18 -0
- package/css/spark.css +14 -0
- package/css/table.css +7 -1
- package/css/tokens.css +8 -1
- package/dist/bronto.css +1 -1
- package/dist/css/dots.css +1 -1
- package/dist/css/report.css +1 -1
- package/dist/css/skins.css +1 -1
- package/dist/css/sources.css +1 -1
- package/dist/css/spark.css +1 -1
- package/dist/css/table.css +1 -1
- package/dist/css/tokens.css +1 -1
- package/docs/dots.md +146 -0
- package/docs/frontier-primitives.md +262 -0
- package/docs/glyphs.md +114 -0
- package/docs/package-contract.md +263 -0
- package/docs/reference.md +115 -1
- package/docs/reporting.md +296 -16
- package/docs/sources.md +32 -0
- package/docs/stability.md +6 -3
- package/glyphs/glyphs.d.ts +61 -0
- package/glyphs/glyphs.js +593 -30
- package/llms.txt +79 -25
- package/package.json +13 -3
- package/qwik/index.d.ts +1 -0
- package/qwik/index.d.ts.map +1 -1
- package/qwik/index.js +5 -0
- package/react/index.d.ts +1 -0
- package/react/index.d.ts.map +1 -1
- package/react/index.js +3 -0
- package/solid/index.d.ts +2 -0
- package/solid/index.d.ts.map +1 -1
- package/solid/index.js +3 -0
- 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
|
[](https://www.npmjs.com/package/@ponchia/ui)
|
|
4
4
|
[](https://www.npmjs.com/package/@ponchia/ui#provenance)
|
|
5
5
|
[](https://github.com/Ponchia/bronto-ui/blob/main/package.json)
|
|
6
|
-
[](https://github.com/Ponchia/bronto-ui/blob/main/scripts/check-dist.mjs)
|
|
7
7
|
[](https://github.com/Ponchia/bronto-ui/actions/workflows/ci.yml)
|
|
8
8
|
[](https://scorecard.dev/viewer/?uri=github.com/Ponchia/bronto-ui)
|
|
9
9
|
[](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 (~
|
|
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
|
|
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
|
|
package/behaviors/glyph.d.ts
CHANGED
|
@@ -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
|
*/
|
package/behaviors/glyph.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["glyph.js"],"names":[],"mappings":"
|
|
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"}
|
package/behaviors/glyph.js
CHANGED
|
@@ -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(
|
|
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');
|
package/behaviors/index.d.ts
CHANGED
|
@@ -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
|
package/behaviors/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":"
|
|
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"}
|
package/behaviors/index.js
CHANGED
|
@@ -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
|
+
}
|