Skip to content

Commit 82b9cdd

Browse files
JosunLPclaude
andauthored
release: @bquery/ui 1.15.0 (#18)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent e922231 commit 82b9cdd

152 files changed

Lines changed: 17736 additions & 1545 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENT.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,167 @@ This repository is `@bquery/ui`, a framework-agnostic Web Components library bui
2222
5. Treat import side effects (`import '@bquery/ui'`) and per-component entrypoints (`@bquery/ui/components/<name>`) as the canonical registration model; `registerAll()` is deprecated compatibility-only.
2323
6. When touching versioned install snippets or release-facing docs, keep pinned CDN examples aligned with the current package version in `package.json`.
2424

25+
## Component conventions
26+
27+
### Styles
28+
29+
Declare styles with the `css` tagged template from `@bquery/bquery/component` and
30+
interpolate the shared fragments from `src/utils/styles.ts`:
31+
32+
```ts
33+
import { component, css, html } from '@bquery/bquery/component';
34+
import { baseStyles, fieldStyles, focusRing, reset, srOnly } from '../../utils/styles.js';
35+
36+
styles: css`
37+
${baseStyles}
38+
${reset}
39+
${focusRing}
40+
:host { display: block; }
41+
`
42+
```
43+
44+
The fragments, and when to reach for each:
45+
46+
| Fragment | Provides |
47+
| -------------- | ------------------------------------------------------------------- |
48+
| `baseStyles` | Tokens + both colour schemes. Every component. |
49+
| `reset` | `box-sizing`, `[hidden]`, and the reduced-motion opt-out. |
50+
| `focusRing` | The standard ring on the usual focusable elements, at zero specificity. |
51+
| `fieldStyles` | `.field` / `.label` / `.hint` / `.error-msg` / `.required-mark` for form controls. |
52+
| `srOnly` | `.sr-only`. |
53+
54+
**A backtick inside a CSS comment terminates the template literal.** Two
55+
components have been broken this way; write ``accent-color`` without the
56+
backticks inside `css` blocks.
57+
58+
This matters for more than tidiness. A `css` payload is handed to
59+
`adoptedStyleSheets`, so the stylesheet is constructed **once per component and
60+
shared by every instance**, and re-renders no longer rewrite it. A plain string
61+
falls back to a per-instance `<style>` element whose ~5.6 KB of token/theme CSS
62+
is duplicated per element and rewritten on every render.
63+
64+
Only `ComponentStyles` values survive interpolation intact — `css` escapes
65+
interpolated *strings*, which would corrupt raw CSS. Use `rawCss()` from
66+
`src/utils/styles.ts` to wrap CSS text generated at runtime.
67+
68+
### Lifecycle and teardown
69+
70+
Use the helpers in `src/utils/component.ts` rather than stashing handlers on the
71+
host element:
72+
73+
```ts
74+
connected() {
75+
const el = host<MyState>(this); // typed setState/getState/setProp
76+
const scope = bind(el);
77+
scope.on(document, 'keydown', onKeyDown); // removal registered for you
78+
scope.timeout(fn, 200); // cleared on disconnect
79+
},
80+
disconnected() {
81+
release(this); // unwinds everything
82+
},
83+
```
84+
85+
Anything reaching outside the component — `document`/`window` listeners, timers,
86+
`MutationObserver`s, form proxies, `aria-*` written onto slotted light-DOM
87+
elements — must be registered on the scope. `tests/component-utils.test.ts`
88+
asserts that the overlay components leave no document listeners behind.
89+
90+
**`connected()` runs twice.** The runtime mounts an element from
91+
`attributeChangedCallback` during upgrade, and the `connectedCallback` that
92+
follows sees it already mounted and takes its reconnect path — so any element
93+
carrying an observed attribute in the initial HTML runs `connected()` a second
94+
time. `bind()` absorbs that: calling it again unwinds the previous scope, so
95+
exactly one set of listeners stays live. Consequences to respect:
96+
97+
- Call `bind()` **once**, at the top of `connected()`. Use `scopeOf(owner)` to
98+
add teardown from another hook.
99+
- `connected()` must be safe to run twice for everything it does *besides*
100+
registering on the scope. Anything not on the scope — a `queueMicrotask` that
101+
mutates light DOM, an event dispatched on connect — will happen twice.
102+
103+
This is what made `bq-dropdown-menu` toggle twice per click and never open.
104+
105+
Use `store(owner, init)` when a value created in `connected()` has to stay
106+
reachable from `updated()`.
107+
108+
### Icons
109+
110+
`svg` is on the sanitizer's *forbidden* list, which `sanitize.allowTags` cannot
111+
re-open — a component can never emit inline SVG. Icons therefore live in the
112+
stylesheet, as `mask-image` data URIs painted with `currentColor`:
113+
114+
```ts
115+
import { iconCss } from '../../utils/icons.js';
116+
117+
styles: css`
118+
${baseStyles}
119+
${iconCss('chevron-down', 'x')}
120+
`,
121+
// render: <span class="icon" data-icon="x" aria-hidden="true"></span>
122+
```
123+
124+
Name only the icons the component draws; `iconCss` emits one rule each, and the
125+
full set is far too heavy to embed everywhere (`bq-icon` is the exception).
126+
`iconMask(name)` returns just the mask declarations, for a `::before`/`::after`
127+
on an element the component already renders.
128+
129+
`iconCss` defines a global `.icon` box inside the shadow root. A component that
130+
already uses `.icon` for something else — a slot wrapper, say — must rename it,
131+
or the wrapper inherits the mask box. `bq-banner` and `bq-file-upload` hit this.
132+
133+
### Sanitizer allowlist
134+
135+
Rendered markup is sanitized. The framework's base allowlist covers `part`,
136+
`aria-*`, `data-*` and the usual form attributes, but not everything — `accept`,
137+
`datetime`, `inputmode`, `scope`, `colspan`, `spellcheck` and `style` each
138+
needed an explicit `sanitize.allowAttributes` entry. If an attribute silently
139+
disappears from rendered output, this is why.
140+
141+
`tests/sanitizer-allowlist.test.ts` parses every render template and fails on an
142+
attribute the sanitizer would drop, so this can no longer ship unnoticed.
143+
144+
### Focus across re-renders
145+
146+
Rendering assigns `shadowRoot.innerHTML`, so every render destroys the node the
147+
user is in. Any component with a focusable element inside its shadow root wants
148+
the packaged treatment:
149+
150+
```ts
151+
connected() { trackFocus(host(this), bind(this)); },
152+
beforeUpdate() { markFocus(this); },
153+
updated() { restorePreservedFocus(this); },
154+
```
155+
156+
`trackFocus` records the focus position from a handful of events in the
157+
**capture** phase, so the snapshot is taken before the component's own handler
158+
runs. `beforeUpdate` covers attribute-driven renders, which no event precedes.
159+
`setState` renders skip `beforeUpdate` entirely, which is why the event-driven
160+
half is needed at all.
161+
162+
### Tinted surfaces
163+
164+
Never paint a background from a 50/100/200 palette step. Those are *light*
165+
colours in both schemes, so a badge or an alert built on them stays near-white
166+
on a dark page — which is how alerts, badges, chips, avatars and tags all
167+
shipped broken in dark mode. Use `--bq-intent-{primary|success|danger|warning|
168+
info|neutral}-{bg|fg|border}`, which flip with the scheme.
169+
`tests/component-css.test.ts` fails the build on a violation.
170+
171+
### Theming across the shadow boundary
172+
173+
Semantic tokens resolve through a scheme channel — `--bq-bg-base:
174+
var(--bq-scheme-bg-base, #fff)` — so a document-level `data-theme` can reach
175+
into shadow roots. Do not "simplify" that away: a plain `:host` definition
176+
cannot be overridden by an ancestor, and `:host-context()` only exists in
177+
Chromium. See `src/theme/scheme.ts`.
178+
179+
### Shadow-DOM state and re-renders
180+
181+
Every render replaces the shadow root's contents. Anything written into the
182+
shadow root imperatively (for example the file-upload file list, or the
183+
avatar-group overflow counter, which depend on light-DOM children that `render`
184+
cannot see) must be repainted from `updated()`.
185+
25186
## Setup and validation
26187

27188
Install dependencies with Bun:

README.md

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ It is designed to give teams the kind of component coverage, polish, accessibili
1919
- **Reusable UI components** spanning actions, forms, navigation, data display, overlays, and feedback
2020
- **Framework-agnostic** usage in plain HTML, React, Vue, Angular, Svelte, and other Custom Element-capable runtimes
2121
- **Accessible by default** with keyboard support, ARIA roles, focus management, and screen reader announcements
22-
- **Themeable via design tokens** and CSS custom properties
22+
- **Themeable via design tokens** and CSS custom properties, switchable from the document in every browser
23+
- **A built-in icon set** rendered as CSS masks, so glyphs inherit text colour and stay crisp at any size
2324
- **Tree-shakeable ESM exports** with per-component imports
2425
- **Browser-ready UMD and IIFE bundles** for direct CDN delivery
2526
- **Built-in dark mode, i18n, and event-driven APIs**
@@ -28,14 +29,14 @@ It is designed to give teams the kind of component coverage, polish, accessibili
2829

2930
The current library covers the core component categories developers expect from modern UI libraries:
3031

31-
| Category | Components |
32-
| ---------------- | -------------------------------------------------------------- |
33-
| **Actions** | Button, Icon Button |
34-
| **Forms** | Input, Textarea, Select, Checkbox, Radio, Switch, Slider, Chip |
35-
| **Navigation** | Tabs, Accordion, Breadcrumbs, Pagination |
36-
| **Data Display** | Card, Badge, Avatar, Table, Divider, Empty State, Stat Card |
37-
| **Feedback** | Alert, Progress, Spinner, Skeleton, Tooltip, Toast |
38-
| **Overlays** | Dialog, Drawer, Dropdown Menu |
32+
| Category | Components |
33+
| ---------------- | --------------------------------------------------------------------------------------------------------------------- |
34+
| **Actions** | Button, Icon Button, Button Group, Copy Button |
35+
| **Forms** | Input, Number Input, Textarea, Select, Combobox, Tag Input, Pin Input, Date Picker, Segmented Control, Checkbox, Radio, Switch, Slider, Chip, Rating, File Upload |
36+
| **Navigation** | Tabs, Accordion, Breadcrumbs, Pagination, Stepper, Tree |
37+
| **Data Display** | Card, Badge, Avatar, Avatar Group, Table, Divider, Empty State, Stat Card, Timeline, Kbd, Icon, Meter |
38+
| **Feedback** | Alert, Banner, Progress, Spinner, Skeleton, Tooltip, Toast |
39+
| **Overlays** | Dialog, Drawer, Dropdown Menu, Popover |
3940

4041
For the full catalog and feature coverage, see [`docs/components/index.md`](./docs/components/index.md).
4142

@@ -69,24 +70,25 @@ import '@bquery/ui';
6970

7071
```html
7172
<!-- UMD -->
72-
<script src="https://cdn.jsdelivr.net/npm/@bquery/ui@1.10.0/dist/index.umd.js"></script>
73+
<script src="https://cdn.jsdelivr.net/npm/@bquery/ui@1.15.0/dist/index.umd.js"></script>
7374

7475
<!-- IIFE -->
75-
<script src="https://cdn.jsdelivr.net/npm/@bquery/ui@1.10.0/dist/index.iife.js"></script>
76+
<script src="https://cdn.jsdelivr.net/npm/@bquery/ui@1.15.0/dist/index.iife.js"></script>
7677
```
7778

7879
The UMD and IIFE bundles register all components on load and expose the library on `window.BQueryUI`.
7980
For ESM-based app builds, prefer importing `@bquery/ui` from your bundler or other module-aware build tool.
8081

8182
## Current release snapshot
8283

83-
The current release (`1.10.0`) emphasizes:
84+
The current release (`1.15.0`) emphasizes:
8485

85-
- **Import-based registration as the canonical integration path.** Import `@bquery/ui` once to register all components, or import `@bquery/ui/components/<name>` to register only the wrappers you need.
86-
- **A clearer shared package surface.** `@bquery/ui/tokens`, `@bquery/ui/theme`, `@bquery/ui/i18n`, `@bquery/ui/utils`, and `@bquery/ui/register` are all explicit entry points.
87-
- **Broader accessibility and localization coverage.** Recent releases improved accordion semantics, live form-field counters, localized table states, chip keyboard behavior, and reduced-motion-aware overlay transitions.
88-
- **A larger production-ready catalog.** The package now spans 31 web components across actions, forms, navigation, data display, feedback, and overlays, including `bq-dropdown-menu`, `bq-stat-card`, and the imperative toast API.
89-
- **Aligned docs and browser bundles.** Version-pinned CDN snippets, migration guidance, and Storybook/VitePress references now target `1.10.0`.
86+
- **A built-in icon set.** Interface glyphs are rendered as CSS masks rather than text characters, so they inherit `currentColor`, scale with `font-size`, and look the same on every platform. Available as `<bq-icon>` and to your own components through `iconCss()`.
87+
- **Dark mode that works in every browser.** The scheme now travels through an inherited custom-property channel instead of `:host-context()`, which exists only in Chromium — and which, sitting in a selector list, previously took the whole dark theme down with it in Firefox and Safari. `data-theme` also works on any subtree, not just the root.
88+
- **A deeper token layer.** Semantic surfaces (`--bq-surface-raised`, `--bq-surface-overlay`), translucent interaction states, per-intent focus rings, a shared control-height scale, and dark-mode elevation.
89+
- **Focus that survives a re-render.** Rendering replaces the shadow tree; text fields, calendars and tree views now keep focus and the caret across the renders their own interactions cause.
90+
- **A larger production-ready catalog.** The package spans 49 web components across actions, forms, navigation, data display, feedback, and overlays — including `bq-date-picker`, `bq-tree`, `bq-pin-input`, `bq-meter`, `bq-copy-button`, and `bq-icon`.
91+
- **Aligned docs and browser bundles.** Version-pinned CDN snippets, migration guidance, and Storybook/VitePress references target `1.15.0`.
9092

9193
## Tree-Shakeable Usage
9294

@@ -153,9 +155,16 @@ For migration guidance from older registration patterns, see [`docs/guide/migrat
153155
### Theming
154156

155157
- CSS custom properties for colors, spacing, typography, radius, shadows, motion, and z-index
156-
- Light and dark themes
158+
- Semantic surface, interaction, and focus-ring tokens on top of the raw palette
159+
- Light and dark themes, switchable per document *or* per subtree with `data-theme`
157160
- `::part()` support for targeted customization
158161

162+
### Icons
163+
164+
- A built-in set of interface glyphs, available as `<bq-icon name="…">`
165+
- Rendered through CSS masks, so an icon inherits `currentColor` and scales with `font-size`
166+
- `iconCss()` lets your own components pull in only the glyphs they draw
167+
159168
### Internationalization
160169

161170
- User-facing strings run through the library i18n system

0 commit comments

Comments
 (0)