@ponchia/ui 0.3.6 → 0.4.1
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 +1063 -0
- package/README.md +31 -14
- package/behaviors/index.d.ts +3 -1
- package/behaviors/index.js +179 -106
- package/classes/index.d.ts +41 -0
- package/classes/index.js +42 -0
- package/classes/vscode.css-custom-data.json +18 -22
- package/css/dataviz.css +96 -0
- package/css/disclosure.css +29 -0
- package/css/dots.css +32 -2
- package/css/feedback.css +53 -0
- package/css/motion.css +88 -0
- package/css/overlay.css +52 -1
- package/css/report.css +382 -0
- package/css/skins.css +54 -0
- package/css/tokens.css +56 -32
- package/dist/bronto.css +1 -1
- package/dist/css/dataviz.css +1 -0
- package/dist/css/disclosure.css +1 -1
- package/dist/css/dots.css +1 -1
- package/dist/css/feedback.css +1 -1
- package/dist/css/motion.css +1 -1
- package/dist/css/overlay.css +1 -1
- package/dist/css/report.css +1 -0
- package/dist/css/skins.css +1 -0
- package/dist/css/tokens.css +1 -1
- package/docs/adr/0001-color-system.md +271 -0
- package/docs/adr/0002-scope-and-2026-baseline.md +104 -0
- package/docs/adr/0003-theme-model.md +94 -0
- package/docs/contrast.md +170 -51
- package/docs/reference.md +118 -19
- package/docs/reporting.md +270 -0
- package/docs/stability.md +37 -0
- package/docs/theming.md +84 -7
- package/docs/usage.md +67 -8
- package/glyphs/glyphs.d.ts +14 -1
- package/glyphs/glyphs.js +143 -2
- package/llms.txt +101 -5
- package/package.json +79 -5
- package/qwik/index.d.ts +55 -0
- package/qwik/index.js +129 -0
- package/react/index.d.ts +57 -0
- package/react/index.js +84 -0
- package/solid/index.d.ts +57 -0
- package/solid/index.js +85 -0
- package/tokens/charts.d.ts +37 -0
- package/tokens/charts.js +96 -0
- package/tokens/charts.json +61 -0
- package/tokens/index.d.ts +3 -3
- package/tokens/index.js +16 -18
- package/tokens/index.json +32 -36
- package/tokens/resolved.json +39 -41
- package/tokens/skins.d.ts +27 -0
- package/tokens/skins.js +62 -0
- package/tokens/tokens.dtcg.json +22 -34
package/README.md
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
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)
|
|
10
10
|
|
|
11
|
-
**A CSS-first, framework-agnostic UI framework
|
|
11
|
+
**A CSS-first, framework-agnostic UI framework built on token-driven restraint — monochrome by default, one rationed accent, dot-matrix display type, hairline borders. Color is _tiered_: we don't decorate with it, we signal with it. The "Nothing"-inspired look is the default skin, not the architecture — opt-in colorways (amber CRT · phosphor green · e-ink), a dot-matrix icon set, and a colourblind-safe data-viz palette prove the one knob is real. Zero runtime dependencies; re-brand the whole thing with one CSS variable.**
|
|
12
12
|
|
|
13
13
|
### [Live demo →](https://ponchia.github.io/bronto-ui/) · [Theme playground →](https://ponchia.github.io/bronto-ui/demo/theme-playground.html)
|
|
14
14
|
|
|
@@ -34,7 +34,7 @@ Or drop it in with no build step, straight from a CDN:
|
|
|
34
34
|
|
|
35
35
|
## Quick start
|
|
36
36
|
|
|
37
|
-
**1. Load the CSS.** One flattened, minified bundle — the whole framework, one request (~
|
|
37
|
+
**1. Load the CSS.** One flattened, minified bundle — the whole framework, one request (~75 kB raw / ~13 kB gzip):
|
|
38
38
|
|
|
39
39
|
```css
|
|
40
40
|
@import '@ponchia/ui'; /* via a bundler */
|
|
@@ -79,7 +79,7 @@ toast('Saved', { tone: 'success' }); // body-anchored stack, no markup needed
|
|
|
79
79
|
|
|
80
80
|
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.
|
|
81
81
|
|
|
82
|
-
**5. (Optional) display glyphs — a
|
|
82
|
+
**5. (Optional) display glyphs — a 48-glyph dot-matrix icon set:**
|
|
83
83
|
|
|
84
84
|
```js
|
|
85
85
|
import { renderGlyph } from '@ponchia/ui/glyphs';
|
|
@@ -89,7 +89,7 @@ el.innerHTML = renderGlyph('search', { label: 'Search' }); // role="img"
|
|
|
89
89
|
// import { initDotGlyph } from '@ponchia/ui/behaviors'; initDotGlyph();
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
Arrows, chevrons, check/close/plus/minus, search/menu/gear, info/warning/bell/lock, home/user/heart/star/spark
|
|
92
|
+
Arrows, chevrons, check/close/plus/minus, search/menu/gear, info/warning/bell/lock, home/user/heart/star/spark and circle-family marks are rendered on the same `.ui-dotmatrix` primitive and re-skinned by the `--field-dot*` tokens. Default dot look is for **display** sizes; pass **`{ solid: true }`** to fuse the cells into a crisp pixel glyph that works as an **inline icon down to ~16px**. For dense lists/tables, `renderGlyph(name, { render: 'mask' })` emits one `.ui-icon` element backed by an internal CSS mask.
|
|
93
93
|
|
|
94
94
|
## What's in the box
|
|
95
95
|
|
|
@@ -100,7 +100,11 @@ Arrows, chevrons, check/close/plus/minus, search/menu/gear, info/warning/bell/lo
|
|
|
100
100
|
- **Disclosure & nav** — tabs, accordion, segmented, breadcrumb, pagination, `ui-steps`, `ui-timeline`, `ui-pagehead`, `ui-kbd`.
|
|
101
101
|
- **Shells** — an admin dashboard shell (`ui-app-*`) and a content/marketing site shell (`ui-site*`, `ui-container`).
|
|
102
102
|
- **Prose** — `.ui-prose` styles raw, unclassed semantic HTML (Markdown / CMS / LLM output) with zero classes.
|
|
103
|
+
- **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.
|
|
103
104
|
- **Motion & dots** — the dot-matrix motif kit: dot grid, status dots, dot loaders, the orbital spinner, matrix reveal — all reduced-motion aware.
|
|
105
|
+
- **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).
|
|
106
|
+
- **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.
|
|
107
|
+
- **Data-viz** _(opt-in)_ — a colourblind-safe chart palette (`--chart-*` + dot-matrix pattern fills, resolved hex in `charts.json`), gated under simulated protan/deutan/tritan vision. Charts only, never UI chrome.
|
|
104
108
|
|
|
105
109
|
Full generated catalog of every class: **[docs/reference.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/reference.md)**. The decision guide (which primitive when): **[docs/usage.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/usage.md)**.
|
|
106
110
|
|
|
@@ -115,11 +119,17 @@ Everything accent-colored derives from a single `--accent` variable via `color-m
|
|
|
115
119
|
|
|
116
120
|
Buttons, focus rings, dot motifs, accent borders and soft fills all follow automatically. Light/dark is `data-theme="light"` / `"dark"` on `<html>` (defaults to `prefers-color-scheme`); `data-density` and `data-contrast` give density and contrast presets. A full re-skin (radius, display face, dot density, surfaces) is a handful more token overrides — the "Nothing" look is the **default skin, not the architecture**.
|
|
117
121
|
|
|
122
|
+
**One system, many skins.** That the knob is real isn't a claim — it ships: drop in `@ponchia/ui/css/skins.css` and set `data-bronto-skin="amber-crt | phosphor-green | e-ink"` on `<html>` for a complete, contrast-gated recolour (the derived accent family, focus ring, dot-matrix and glyphs all follow). Colour is governed in **tiers** — neutral canvas · one accent · locked status · display expression · opt-in data-viz — so it always earns its place; the full constitution is **[ADR-0001](https://github.com/Ponchia/bronto-ui/blob/main/docs/adr/0001-color-system.md)**.
|
|
123
|
+
|
|
118
124
|
> When you change `--accent`, contrast becomes yours: verify your hue in the **[theme playground](https://ponchia.github.io/bronto-ui/demo/theme-playground.html)** (it shows the derived family and computed WCAG ratios). Full contract: **[docs/theming.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/theming.md)**.
|
|
119
125
|
|
|
126
|
+
## Accessibility
|
|
127
|
+
|
|
128
|
+
Not an afterthought — a gate. Every contractual token pairing has a declared WCAG 2.1 conformance level the build **fails** below (`docs/contrast.md`, regenerated + CI-checked), and the shipped colorways are held to the same floors. The data-viz palette is gated for distinctness under simulated protanopia/deuteranopia/tritanopia, and never relies on colour alone (dot-matrix pattern fills). APCA `Lc` is published advisory alongside WCAG. Components ship ARIA-correct markup and SSR-safe keyboard behaviors; `prefers-reduced-motion`, `prefers-contrast` and `forced-colors` are all honored.
|
|
129
|
+
|
|
120
130
|
## Works with anything
|
|
121
131
|
|
|
122
|
-
The CSS is the framework, so it works with React, Svelte/SvelteKit, Astro, Vue, Solid or plain HTML — there's no component runtime to adopt. The optional `classes` and `behaviors` entrypoints pull in **no** UI framework and are SSR-safe.
|
|
132
|
+
The CSS is the framework, so it works with React, Svelte/SvelteKit, Astro, Vue, Solid, Qwik or plain HTML — there's no component runtime to adopt. The optional `classes` and `behaviors` entrypoints pull in **no** UI framework and are SSR-safe. For React, Solid and Qwik there are also **optional thin bindings** — `@ponchia/ui/react`, `@ponchia/ui/solid` and `@ponchia/ui/qwik` wrap the behaviors as hooks (`useDialog`, `useToast`, …); `react`/`solid-js`/`@builder.io/qwik` are optional peer deps, so the core stays zero-dependency.
|
|
123
133
|
|
|
124
134
|
Per-framework getting-started guides + runnable example apps live in the repo:
|
|
125
135
|
|
|
@@ -128,36 +138,43 @@ Per-framework getting-started guides + runnable example apps live in the repo:
|
|
|
128
138
|
| Vanilla / Vite / plain HTML | [vanilla.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/getting-started/vanilla.md) | [`examples/vanilla-vite`](https://github.com/Ponchia/bronto-ui/tree/main/examples/vanilla-vite) |
|
|
129
139
|
| Astro | [astro.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/getting-started/astro.md) | [`examples/astro`](https://github.com/Ponchia/bronto-ui/tree/main/examples/astro) |
|
|
130
140
|
| SvelteKit | [sveltekit.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/getting-started/sveltekit.md) | [`examples/sveltekit`](https://github.com/Ponchia/bronto-ui/tree/main/examples/sveltekit) |
|
|
131
|
-
| React
|
|
141
|
+
| React | [react-solid.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/getting-started/react-solid.md) | [`examples/react-vite`](https://github.com/Ponchia/bronto-ui/tree/main/examples/react-vite) |
|
|
142
|
+
| Solid | [react-solid.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/getting-started/react-solid.md) | [`examples/solid-vite`](https://github.com/Ponchia/bronto-ui/tree/main/examples/solid-vite) |
|
|
143
|
+
| Qwik | [react-solid.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/getting-started/react-solid.md) | [`examples/qwik-vite`](https://github.com/Ponchia/bronto-ui/tree/main/examples/qwik-vite) |
|
|
132
144
|
| Tailwind / cascade-layer interop | [tailwind.md](https://github.com/Ponchia/bronto-ui/blob/main/docs/interop/tailwind.md) | — |
|
|
133
145
|
|
|
134
146
|
## Extras
|
|
135
147
|
|
|
136
|
-
- **Tokens as data** — `import tokens, { themeColor, cssVars } from '@ponchia/ui/tokens'` (plus `tokens.json`, W3C DTCG `tokens.dtcg.json`, and `tokens/resolved.json` for
|
|
148
|
+
- **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).
|
|
149
|
+
- **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`.
|
|
150
|
+
- **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`.
|
|
151
|
+
- **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; }`.
|
|
137
152
|
- **Editor IntelliSense** — point VS Code at the shipped custom-data file so every token autocompletes in `var(--…)`:
|
|
138
153
|
```json
|
|
139
154
|
{ "css.customData": ["node_modules/@ponchia/ui/classes/vscode.css-custom-data.json"] }
|
|
140
155
|
```
|
|
141
|
-
- **For AI coding agents** — the package ships `llms.txt` at its root plus `docs/reference.md`, `docs/usage.md`
|
|
156
|
+
- **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.
|
|
142
157
|
|
|
143
|
-
> The package root is **CSS-only
|
|
158
|
+
> 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`.
|
|
159
|
+
> JS subpaths are **ESM-only**. CommonJS consumers should use dynamic
|
|
160
|
+
> `import('@ponchia/ui/behaviors')`.
|
|
144
161
|
|
|
145
162
|
## Browser support
|
|
146
163
|
|
|
147
|
-
|
|
164
|
+
Recent-evergreen, by design. The framework targets the modern web platform — cascade layers, `:has()`, `color-mix()`/`oklch()`, logical properties, native `<dialog>`, and the 2026 interaction primitives (`@starting-style`, `transition-behavior: allow-discrete`, `light-dark()`). Floor: **Chrome/Edge 125+, Safari 18+, Firefox 129+** (early–mid 2025). No fallback ships below this — pin an older tag if you need it. See [ADR-0002](https://github.com/Ponchia/bronto-ui/blob/main/docs/adr/0002-scope-and-2026-baseline.md) for the scope/greenfield rationale.
|
|
148
165
|
|
|
149
166
|
## Versioning
|
|
150
167
|
|
|
151
|
-
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.
|
|
168
|
+
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.4.0` (and equivalently `^0.4.0`) resolves to `>=0.4.0 <0.5.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.
|
|
152
169
|
|
|
153
|
-
Contractual (changes are breaking):
|
|
170
|
+
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.
|
|
154
171
|
|
|
155
172
|
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.
|
|
156
173
|
|
|
157
174
|
## Links
|
|
158
175
|
|
|
159
176
|
- **[Live demo](https://ponchia.github.io/bronto-ui/)** · **[Theme playground](https://ponchia.github.io/bronto-ui/demo/theme-playground.html)**
|
|
160
|
-
- **[Class reference](https://github.com/Ponchia/bronto-ui/blob/main/docs/reference.md)** · **[Usage guide](https://github.com/Ponchia/bronto-ui/blob/main/docs/usage.md)** · **[Theming](https://github.com/Ponchia/bronto-ui/blob/main/docs/theming.md)** · **[Contrast](https://github.com/Ponchia/bronto-ui/blob/main/docs/contrast.md)**
|
|
177
|
+
- **[Class reference](https://github.com/Ponchia/bronto-ui/blob/main/docs/reference.md)** · **[Usage guide](https://github.com/Ponchia/bronto-ui/blob/main/docs/usage.md)** · **[Theming](https://github.com/Ponchia/bronto-ui/blob/main/docs/theming.md)** · **[Contrast](https://github.com/Ponchia/bronto-ui/blob/main/docs/contrast.md)** · **[Color system (ADR-0001)](https://github.com/Ponchia/bronto-ui/blob/main/docs/adr/0001-color-system.md)** · **[Scope & 2026 baseline (ADR-0002)](https://github.com/Ponchia/bronto-ui/blob/main/docs/adr/0002-scope-and-2026-baseline.md)**
|
|
161
178
|
- **[CHANGELOG](https://github.com/Ponchia/bronto-ui/blob/main/CHANGELOG.md)** · **[Roadmap](https://github.com/Ponchia/bronto-ui/blob/main/ROADMAP.md)** · **[Contributing](https://github.com/Ponchia/bronto-ui/blob/main/CONTRIBUTING.md)**
|
|
162
179
|
|
|
163
180
|
## License
|
package/behaviors/index.d.ts
CHANGED
|
@@ -102,7 +102,9 @@ export declare function initTabs(opts?: DelegateOpts): Cleanup;
|
|
|
102
102
|
/**
|
|
103
103
|
* Wire native <dialog> open/close glue: `[data-bronto-open="id"]`,
|
|
104
104
|
* `[data-bronto-close]`, and backdrop light-dismiss for dialogs marked
|
|
105
|
-
* `[data-bronto-dialog-light]`.
|
|
105
|
+
* `[data-bronto-dialog-light]`. `root` scopes delegated controls; dialog ids
|
|
106
|
+
* resolve root-first, then document-wide for body/portal-mounted overlays.
|
|
107
|
+
* Returns a cleanup function.
|
|
106
108
|
*/
|
|
107
109
|
export declare function initDialog(opts?: DelegateOpts): Cleanup;
|
|
108
110
|
|
package/behaviors/index.js
CHANGED
|
@@ -54,6 +54,24 @@ function bindOnce(target, key, add) {
|
|
|
54
54
|
return cleanup;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function byIdInHost(host, id) {
|
|
58
|
+
if (!id) return null;
|
|
59
|
+
if (host === document) return document.getElementById(id);
|
|
60
|
+
if (host.id === id) return host;
|
|
61
|
+
return (
|
|
62
|
+
Array.from(host.querySelectorAll?.('[id]') || []).find((el) => el.id === id) ||
|
|
63
|
+
document.getElementById(id)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function closestSafe(el, selector) {
|
|
68
|
+
try {
|
|
69
|
+
return el.closest(selector);
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
/**
|
|
58
76
|
* Apply the persisted theme to <html data-theme>. Call as early as
|
|
59
77
|
* possible (an inline module in <head>) to avoid a flash before the
|
|
@@ -145,7 +163,7 @@ export function dismissible({ root } = {}) {
|
|
|
145
163
|
const btn = e.target.closest('[data-bronto-dismiss]');
|
|
146
164
|
if (!btn || !host.contains(btn)) return;
|
|
147
165
|
const sel = btn.getAttribute('data-bronto-dismiss');
|
|
148
|
-
const target = sel ? btn
|
|
166
|
+
const target = sel ? closestSafe(btn, sel) : btn.closest('[data-bronto-dismissible]');
|
|
149
167
|
if (!target) return;
|
|
150
168
|
const ev = new CustomEvent('bronto:dismiss', { bubbles: true, cancelable: true });
|
|
151
169
|
if (target.dispatchEvent(ev)) target.remove();
|
|
@@ -264,81 +282,73 @@ export function initTabs({ root } = {}) {
|
|
|
264
282
|
* `close` event, so keyboard/SR users are never dropped at `<body>`.
|
|
265
283
|
* SSR-safe and idempotent; returns cleanup.
|
|
266
284
|
*
|
|
267
|
-
* `root` scopes
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
* (same model as `initThemeToggle`, where `root` scopes controls but the
|
|
271
|
-
* theme applies to <html>).
|
|
285
|
+
* `root` scopes delegated triggers (default `document`). Controlled targets are
|
|
286
|
+
* resolved root-first, then document-wide, so scoped islands win duplicate-id
|
|
287
|
+
* conflicts without breaking body/portal-mounted overlays.
|
|
272
288
|
*/
|
|
273
289
|
export function initDialog({ root } = {}) {
|
|
274
290
|
if (!hasDom()) return noop;
|
|
275
291
|
const host = root || document;
|
|
292
|
+
const managedDialogs = new WeakSet();
|
|
293
|
+
const canManageDialog = (dlg, origin) => host.contains(origin) || managedDialogs.has(dlg);
|
|
294
|
+
|
|
295
|
+
const openFrom = (opener) => {
|
|
296
|
+
const dlg = byIdInHost(host, opener.getAttribute('data-bronto-open'));
|
|
297
|
+
if (!dlg || typeof dlg.showModal !== 'function' || dlg.open) return;
|
|
298
|
+
managedDialogs.add(dlg);
|
|
299
|
+
dlg.addEventListener(
|
|
300
|
+
'close',
|
|
301
|
+
() => {
|
|
302
|
+
if (opener.isConnected && typeof opener.focus === 'function') opener.focus();
|
|
303
|
+
},
|
|
304
|
+
{ once: true },
|
|
305
|
+
);
|
|
306
|
+
dlg.showModal();
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const closeFrom = (closer) => {
|
|
310
|
+
const dlg = closer.closest('dialog');
|
|
311
|
+
if (dlg && dlg.open && canManageDialog(dlg, closer)) dlg.close();
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const lightDismiss = (dlg) => {
|
|
315
|
+
if (
|
|
316
|
+
dlg.tagName === 'DIALOG' &&
|
|
317
|
+
dlg.open &&
|
|
318
|
+
dlg.hasAttribute('data-bronto-dialog-light') &&
|
|
319
|
+
canManageDialog(dlg, dlg)
|
|
320
|
+
) {
|
|
321
|
+
dlg.close();
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
276
325
|
const onClick = (e) => {
|
|
277
326
|
const opener = e.target.closest('[data-bronto-open]');
|
|
278
327
|
if (opener && host.contains(opener)) {
|
|
279
|
-
|
|
280
|
-
if (dlg && typeof dlg.showModal === 'function' && !dlg.open) {
|
|
281
|
-
dlg.addEventListener(
|
|
282
|
-
'close',
|
|
283
|
-
() => {
|
|
284
|
-
if (opener.isConnected && typeof opener.focus === 'function') opener.focus();
|
|
285
|
-
},
|
|
286
|
-
{ once: true },
|
|
287
|
-
);
|
|
288
|
-
dlg.showModal();
|
|
289
|
-
}
|
|
328
|
+
openFrom(opener);
|
|
290
329
|
return;
|
|
291
330
|
}
|
|
292
331
|
const closer = e.target.closest('[data-bronto-close]');
|
|
293
|
-
if (closer
|
|
294
|
-
|
|
295
|
-
if (dlg && dlg.open) dlg.close();
|
|
332
|
+
if (closer) {
|
|
333
|
+
closeFrom(closer);
|
|
296
334
|
return;
|
|
297
335
|
}
|
|
298
336
|
// Light-dismiss: a click whose target is the <dialog> itself is the
|
|
299
337
|
// backdrop (content sits in child elements).
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
dlg.tagName === 'DIALOG' &&
|
|
303
|
-
dlg.open &&
|
|
304
|
-
dlg.hasAttribute('data-bronto-dialog-light') &&
|
|
305
|
-
host.contains(dlg)
|
|
306
|
-
) {
|
|
307
|
-
dlg.close();
|
|
308
|
-
}
|
|
338
|
+
lightDismiss(e.target);
|
|
309
339
|
};
|
|
310
340
|
return bindOnce(host, 'dialog', () => {
|
|
311
|
-
|
|
312
|
-
return () =>
|
|
341
|
+
document.addEventListener('click', onClick);
|
|
342
|
+
return () => document.removeEventListener('click', onClick);
|
|
313
343
|
});
|
|
314
344
|
}
|
|
315
345
|
|
|
316
|
-
|
|
317
|
-
* Push a transient toast into a shared, screen-anchored stack. The stack
|
|
318
|
-
* is the `aria-live="polite"` region: it is created once, appended to
|
|
319
|
-
* <body>, and **kept resident even when empty** so the live region is
|
|
320
|
-
* always present before content is inserted (a freshly created region
|
|
321
|
-
* that receives its first child in the same tick is not reliably
|
|
322
|
-
* announced by VoiceOver/NVDA). On first creation the empty region is
|
|
323
|
-
* inserted and the toast is appended on the next frame for the same
|
|
324
|
-
* reason. `tone` is accent/success/warning/danger/info; `title` is an
|
|
325
|
-
* optional uppercase label; `duration` ms before auto-dismiss (0 keeps
|
|
326
|
-
* it until dismissed). Returns a function that dismisses the toast
|
|
327
|
-
* early. SSR-safe (no-op).
|
|
328
|
-
*/
|
|
329
|
-
export function toast(message, { tone, title, duration = 4000, assertive, closable } = {}) {
|
|
330
|
-
if (!hasDom()) return noop;
|
|
331
|
-
// Errors must interrupt: danger toasts (or an explicit `assertive`)
|
|
332
|
-
// go to a SEPARATE assertive region so they announce immediately,
|
|
333
|
-
// while status toasts stay polite. Two regions — not a per-item
|
|
334
|
-
// role=alert nested in a polite parent — avoids the double
|
|
335
|
-
// announcement that nesting causes in some screen readers.
|
|
336
|
-
const isAssertive = assertive ?? tone === 'danger';
|
|
346
|
+
function toastStack(isAssertive) {
|
|
337
347
|
const stackSel = isAssertive
|
|
338
348
|
? '.ui-toast-stack--assertive'
|
|
339
349
|
: '.ui-toast-stack:not(.ui-toast-stack--assertive)';
|
|
340
350
|
let stack = document.querySelector(stackSel);
|
|
341
|
-
const
|
|
351
|
+
const fresh = !stack;
|
|
342
352
|
if (!stack) {
|
|
343
353
|
stack = document.createElement('div');
|
|
344
354
|
stack.className = isAssertive ? 'ui-toast-stack ui-toast-stack--assertive' : 'ui-toast-stack';
|
|
@@ -346,6 +356,26 @@ export function toast(message, { tone, title, duration = 4000, assertive, closab
|
|
|
346
356
|
if (isAssertive) stack.setAttribute('role', 'alert');
|
|
347
357
|
document.body.appendChild(stack);
|
|
348
358
|
}
|
|
359
|
+
return { stack, fresh };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function enqueueToast(place, freshStack) {
|
|
363
|
+
const canDefer = typeof requestAnimationFrame === 'function';
|
|
364
|
+
if (freshStack && canDefer) {
|
|
365
|
+
toastQueue.push(place);
|
|
366
|
+
toastFlushScheduled = true;
|
|
367
|
+
requestAnimationFrame(() => {
|
|
368
|
+
toastFlushScheduled = false;
|
|
369
|
+
for (const fn of toastQueue.splice(0)) fn();
|
|
370
|
+
});
|
|
371
|
+
} else if (toastFlushScheduled) {
|
|
372
|
+
toastQueue.push(place);
|
|
373
|
+
} else {
|
|
374
|
+
place();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function toastElement(message, { tone, title }) {
|
|
349
379
|
const el = document.createElement('div');
|
|
350
380
|
el.className = tone ? `ui-toast ui-toast--${tone}` : 'ui-toast';
|
|
351
381
|
// No per-item role: the stack itself is the live region; a nested
|
|
@@ -359,6 +389,72 @@ export function toast(message, { tone, title, duration = 4000, assertive, closab
|
|
|
359
389
|
const body = document.createElement('div');
|
|
360
390
|
body.textContent = message;
|
|
361
391
|
el.appendChild(body);
|
|
392
|
+
return el;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Remove a toast, animating its exit when — and only when — a transition
|
|
396
|
+
// is actually in effect. Detached nodes, reduced-motion, and the no-CSS
|
|
397
|
+
// test/SSR env all resolve to instant removal, so the dismiss contract
|
|
398
|
+
// (toast gone now, the aria-live stack stays resident) is unchanged there;
|
|
399
|
+
// a real browser with motion gets the CSS `.is-leaving` fade-out, with a
|
|
400
|
+
// timeout fallback so an interrupted/never-firing transitionend can't strand
|
|
401
|
+
// a toast in the live region.
|
|
402
|
+
function removeToast(el) {
|
|
403
|
+
const reduce =
|
|
404
|
+
typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
405
|
+
const cs =
|
|
406
|
+
!reduce && el.isConnected && typeof getComputedStyle === 'function'
|
|
407
|
+
? getComputedStyle(el)
|
|
408
|
+
: null;
|
|
409
|
+
const dur = cs ? parseFloat(cs.transitionDuration) || 0 : 0;
|
|
410
|
+
if (dur <= 0) {
|
|
411
|
+
el.remove();
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
el.classList.add('is-leaving');
|
|
415
|
+
let done = false;
|
|
416
|
+
const finish = () => {
|
|
417
|
+
if (done) return;
|
|
418
|
+
done = true;
|
|
419
|
+
el.remove();
|
|
420
|
+
};
|
|
421
|
+
el.addEventListener('transitionend', finish, { once: true });
|
|
422
|
+
const timer = setTimeout(finish, dur * 1000 + 120);
|
|
423
|
+
timer?.unref?.(); // don't keep a Node test process alive
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function addToastClose(el, dismiss) {
|
|
427
|
+
const close = document.createElement('button');
|
|
428
|
+
close.type = 'button';
|
|
429
|
+
close.className = 'ui-toast__close';
|
|
430
|
+
close.setAttribute('aria-label', 'Dismiss');
|
|
431
|
+
close.addEventListener('click', dismiss);
|
|
432
|
+
el.appendChild(close);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Push a transient toast into a shared, screen-anchored stack. The stack
|
|
437
|
+
* is the `aria-live="polite"` region: it is created once, appended to
|
|
438
|
+
* <body>, and **kept resident even when empty** so the live region is
|
|
439
|
+
* always present before content is inserted (a freshly created region
|
|
440
|
+
* that receives its first child in the same tick is not reliably
|
|
441
|
+
* announced by VoiceOver/NVDA). On first creation the empty region is
|
|
442
|
+
* inserted and the toast is appended on the next frame for the same
|
|
443
|
+
* reason. `tone` is accent/success/warning/danger/info; `title` is an
|
|
444
|
+
* optional uppercase label; `duration` ms before auto-dismiss (0 keeps
|
|
445
|
+
* it until dismissed). Returns a function that dismisses the toast
|
|
446
|
+
* early. SSR-safe (no-op).
|
|
447
|
+
*/
|
|
448
|
+
export function toast(message, { tone, title, duration = 4000, assertive, closable } = {}) {
|
|
449
|
+
if (!hasDom()) return noop;
|
|
450
|
+
// Errors must interrupt: danger toasts (or an explicit `assertive`)
|
|
451
|
+
// go to a SEPARATE assertive region so they announce immediately,
|
|
452
|
+
// while status toasts stay polite. Two regions — not a per-item
|
|
453
|
+
// role=alert nested in a polite parent — avoids the double
|
|
454
|
+
// announcement that nesting causes in some screen readers.
|
|
455
|
+
const isAssertive = assertive ?? tone === 'danger';
|
|
456
|
+
const { stack, fresh: freshStack } = toastStack(isAssertive);
|
|
457
|
+
const el = toastElement(message, { tone, title });
|
|
362
458
|
// Append after a frame the *first* time so the empty live region is
|
|
363
459
|
// observed by AT before its first child arrives; once the region has
|
|
364
460
|
// been observed, later toasts append synchronously.
|
|
@@ -369,28 +465,14 @@ export function toast(message, { tone, title, duration = 4000, assertive, closab
|
|
|
369
465
|
const place = () => {
|
|
370
466
|
if (!dismissed) stack.appendChild(el);
|
|
371
467
|
};
|
|
372
|
-
|
|
373
|
-
if (freshStack && canDefer) {
|
|
374
|
-
toastQueue.push(place);
|
|
375
|
-
toastFlushScheduled = true;
|
|
376
|
-
requestAnimationFrame(() => {
|
|
377
|
-
toastFlushScheduled = false;
|
|
378
|
-
for (const fn of toastQueue.splice(0)) fn();
|
|
379
|
-
});
|
|
380
|
-
} else if (toastFlushScheduled) {
|
|
381
|
-
// A first-frame deferral is in flight — queue behind it so FIFO
|
|
382
|
-
// order holds and the region still isn't populated synchronously.
|
|
383
|
-
toastQueue.push(place);
|
|
384
|
-
} else {
|
|
385
|
-
place();
|
|
386
|
-
}
|
|
468
|
+
enqueueToast(place, freshStack);
|
|
387
469
|
|
|
388
470
|
let timer;
|
|
389
471
|
const dismiss = () => {
|
|
390
472
|
if (dismissed) return;
|
|
391
473
|
dismissed = true;
|
|
392
474
|
if (timer) clearTimeout(timer);
|
|
393
|
-
el
|
|
475
|
+
removeToast(el);
|
|
394
476
|
// The stack is a persistent live region — never removed on drain, so
|
|
395
477
|
// the next toast does not recreate (and thus mis-announce) it.
|
|
396
478
|
};
|
|
@@ -398,14 +480,7 @@ export function toast(message, { tone, title, duration = 4000, assertive, closab
|
|
|
398
480
|
// it gets a dismiss affordance by default; any toast can opt in via
|
|
399
481
|
// `closable`. The button carries no text node (glyph is a CSS
|
|
400
482
|
// ::before) so the toast's announced/textContent stays the message.
|
|
401
|
-
if (closable ?? duration === 0)
|
|
402
|
-
const close = document.createElement('button');
|
|
403
|
-
close.type = 'button';
|
|
404
|
-
close.className = 'ui-toast__close';
|
|
405
|
-
close.setAttribute('aria-label', 'Dismiss');
|
|
406
|
-
close.addEventListener('click', dismiss);
|
|
407
|
-
el.appendChild(close);
|
|
408
|
-
}
|
|
483
|
+
if (closable ?? duration === 0) addToastClose(el, dismiss);
|
|
409
484
|
if (duration > 0) timer = setTimeout(dismiss, duration);
|
|
410
485
|
return dismiss;
|
|
411
486
|
}
|
|
@@ -422,7 +497,7 @@ export function initDisclosure({ root } = {}) {
|
|
|
422
497
|
const trigger = e.target.closest('[data-bronto-disclosure]');
|
|
423
498
|
if (!trigger || !host.contains(trigger)) return;
|
|
424
499
|
const id = trigger.getAttribute('aria-controls');
|
|
425
|
-
const panel =
|
|
500
|
+
const panel = byIdInHost(host, id);
|
|
426
501
|
if (!panel) return;
|
|
427
502
|
const open = trigger.getAttribute('aria-expanded') === 'true';
|
|
428
503
|
trigger.setAttribute('aria-expanded', String(!open));
|
|
@@ -755,6 +830,24 @@ export function initCombobox({ root } = {}) {
|
|
|
755
830
|
active = options.indexOf(vis[next]);
|
|
756
831
|
setActive(options[active]);
|
|
757
832
|
};
|
|
833
|
+
const activateEdge = (which) => {
|
|
834
|
+
if (list.hidden) return false;
|
|
835
|
+
const v = visible();
|
|
836
|
+
if (!v.length) return true;
|
|
837
|
+
active = options.indexOf(which === 'first' ? v[0] : v[v.length - 1]);
|
|
838
|
+
setActive(options[active]);
|
|
839
|
+
return true;
|
|
840
|
+
};
|
|
841
|
+
const selectActive = () => {
|
|
842
|
+
if (list.hidden || active < 0 || options[active].hidden) return false;
|
|
843
|
+
select(options[active]);
|
|
844
|
+
return true;
|
|
845
|
+
};
|
|
846
|
+
const closeIfOpen = () => {
|
|
847
|
+
if (list.hidden) return false;
|
|
848
|
+
close();
|
|
849
|
+
return true;
|
|
850
|
+
};
|
|
758
851
|
|
|
759
852
|
const onInput = () => filter();
|
|
760
853
|
const onKey = (e) => {
|
|
@@ -768,36 +861,16 @@ export function initCombobox({ root } = {}) {
|
|
|
768
861
|
move(-1);
|
|
769
862
|
break;
|
|
770
863
|
case 'Home':
|
|
771
|
-
if (
|
|
772
|
-
e.preventDefault();
|
|
773
|
-
const v = visible();
|
|
774
|
-
if (v.length) {
|
|
775
|
-
active = options.indexOf(v[0]);
|
|
776
|
-
setActive(options[active]);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
864
|
+
if (activateEdge('first')) e.preventDefault();
|
|
779
865
|
break;
|
|
780
866
|
case 'End':
|
|
781
|
-
if (
|
|
782
|
-
e.preventDefault();
|
|
783
|
-
const v = visible();
|
|
784
|
-
if (v.length) {
|
|
785
|
-
active = options.indexOf(v[v.length - 1]);
|
|
786
|
-
setActive(options[active]);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
867
|
+
if (activateEdge('last')) e.preventDefault();
|
|
789
868
|
break;
|
|
790
869
|
case 'Enter':
|
|
791
|
-
if (
|
|
792
|
-
e.preventDefault();
|
|
793
|
-
select(options[active]);
|
|
794
|
-
}
|
|
870
|
+
if (selectActive()) e.preventDefault();
|
|
795
871
|
break;
|
|
796
872
|
case 'Escape':
|
|
797
|
-
if (
|
|
798
|
-
e.preventDefault();
|
|
799
|
-
close();
|
|
800
|
-
}
|
|
873
|
+
if (closeIfOpen()) e.preventDefault();
|
|
801
874
|
break;
|
|
802
875
|
case 'Tab':
|
|
803
876
|
close();
|
|
@@ -904,8 +977,8 @@ export function initPopover({ root } = {}) {
|
|
|
904
977
|
|
|
905
978
|
const onClick = (e) => {
|
|
906
979
|
const trigger = e.target.closest?.('[data-bronto-popover]');
|
|
907
|
-
if (trigger) {
|
|
908
|
-
const panel =
|
|
980
|
+
if (trigger && host.contains(trigger)) {
|
|
981
|
+
const panel = byIdInHost(host, trigger.getAttribute('data-bronto-popover'));
|
|
909
982
|
if (!panel) return;
|
|
910
983
|
e.preventDefault();
|
|
911
984
|
if (openPanel === panel) close();
|
|
@@ -926,12 +999,12 @@ export function initPopover({ root } = {}) {
|
|
|
926
999
|
};
|
|
927
1000
|
|
|
928
1001
|
return bindOnce(host, 'popover', () => {
|
|
929
|
-
|
|
1002
|
+
document.addEventListener('click', onClick);
|
|
930
1003
|
document.addEventListener('keydown', onKey);
|
|
931
1004
|
view?.addEventListener('scroll', onReflow, true);
|
|
932
1005
|
view?.addEventListener('resize', onReflow);
|
|
933
1006
|
return () => {
|
|
934
|
-
|
|
1007
|
+
document.removeEventListener('click', onClick);
|
|
935
1008
|
document.removeEventListener('keydown', onKey);
|
|
936
1009
|
view?.removeEventListener('scroll', onReflow, true);
|
|
937
1010
|
view?.removeEventListener('resize', onReflow);
|
package/classes/index.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ export declare const cls: {
|
|
|
65
65
|
readonly dotmatrixCellAccent: 'ui-dotmatrix__cell--accent';
|
|
66
66
|
readonly dotmatrixReveal: 'ui-dotmatrix--reveal';
|
|
67
67
|
readonly dotmatrixPulse: 'ui-dotmatrix--pulse';
|
|
68
|
+
readonly icon: 'ui-icon';
|
|
68
69
|
readonly dotfield: 'ui-dotfield';
|
|
69
70
|
readonly dotrule: 'ui-dotrule';
|
|
70
71
|
readonly dotbar: 'ui-dotbar';
|
|
@@ -231,6 +232,43 @@ export declare const cls: {
|
|
|
231
232
|
readonly timeline: 'ui-timeline';
|
|
232
233
|
readonly timelineItem: 'ui-timeline__item';
|
|
233
234
|
readonly timelineTime: 'ui-timeline__time';
|
|
235
|
+
readonly report: 'ui-report';
|
|
236
|
+
readonly reportCompact: 'ui-report--compact';
|
|
237
|
+
readonly reportNumbered: 'ui-report--numbered';
|
|
238
|
+
readonly reportCover: 'ui-report__cover';
|
|
239
|
+
readonly reportCoverCompact: 'ui-report__cover--compact';
|
|
240
|
+
readonly reportHeader: 'ui-report__header';
|
|
241
|
+
readonly reportTitle: 'ui-report__title';
|
|
242
|
+
readonly reportSubtitle: 'ui-report__subtitle';
|
|
243
|
+
readonly reportMeta: 'ui-report__meta';
|
|
244
|
+
readonly reportToc: 'ui-report__toc';
|
|
245
|
+
readonly reportSummary: 'ui-report__summary';
|
|
246
|
+
readonly reportSection: 'ui-report__section';
|
|
247
|
+
readonly reportSectionUnnumbered: 'ui-report__section--unnumbered';
|
|
248
|
+
readonly reportSectionHead: 'ui-report__section-head';
|
|
249
|
+
readonly reportFinding: 'ui-report__finding';
|
|
250
|
+
readonly reportEvidence: 'ui-report__evidence';
|
|
251
|
+
readonly reportFigure: 'ui-report__figure';
|
|
252
|
+
readonly reportCaption: 'ui-report__caption';
|
|
253
|
+
readonly reportSources: 'ui-report__sources';
|
|
254
|
+
readonly reportAppendix: 'ui-report__appendix';
|
|
255
|
+
readonly reportFootnotes: 'ui-report__footnotes';
|
|
256
|
+
readonly chart: 'ui-chart';
|
|
257
|
+
readonly chartLegend: 'ui-chart__legend';
|
|
258
|
+
readonly chartSwatch: 'ui-chart__swatch';
|
|
259
|
+
readonly chartCaption: 'ui-chart__caption';
|
|
260
|
+
readonly chartPlot: 'ui-chart__plot';
|
|
261
|
+
readonly chartBar: 'ui-chart__bar';
|
|
262
|
+
readonly chartLabel: 'ui-chart__label';
|
|
263
|
+
readonly chartTrack: 'ui-chart__track';
|
|
264
|
+
readonly chartFill: 'ui-chart__fill';
|
|
265
|
+
readonly chartFallback: 'ui-chart__fallback';
|
|
266
|
+
readonly printOnly: 'ui-print-only';
|
|
267
|
+
readonly screenOnly: 'ui-screen-only';
|
|
268
|
+
readonly breakBefore: 'ui-break-before';
|
|
269
|
+
readonly breakAfter: 'ui-break-after';
|
|
270
|
+
readonly keep: 'ui-keep';
|
|
271
|
+
readonly printExact: 'ui-print-exact';
|
|
234
272
|
readonly kbd: 'ui-kbd';
|
|
235
273
|
readonly display: 'ui-display';
|
|
236
274
|
readonly mono: 'ui-mono';
|
|
@@ -247,6 +285,9 @@ export declare const cls: {
|
|
|
247
285
|
readonly animateFade: 'ui-animate-fade';
|
|
248
286
|
readonly animateDot: 'ui-animate-dot';
|
|
249
287
|
readonly animateMatrix: 'ui-animate-matrix';
|
|
288
|
+
readonly scrollProgress: 'ui-scroll-progress';
|
|
289
|
+
readonly scrollReveal: 'ui-scroll-reveal';
|
|
290
|
+
readonly vt: 'ui-vt';
|
|
250
291
|
readonly appShell: 'ui-app-shell';
|
|
251
292
|
readonly appShellFull: 'ui-app-shell--full';
|
|
252
293
|
readonly appRail: 'ui-app-rail';
|