Skip to content

release: @bquery/ui 1.15.0 - #18

Merged
JosunLP merged 5 commits into
mainfrom
update/1.5.0
Jul 31, 2026
Merged

release: @bquery/ui 1.15.0#18
JosunLP merged 5 commits into
mainfrom
update/1.5.0

Conversation

@JosunLP

@JosunLP JosunLP commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Releases @bquery/ui 1.15.0: the catalog grows from 31 to 49 components, the theme layer gains a semantic tier that actually survives the shadow boundary, and four defects that made components unusable in a real browser are fixed.

package.json is already at 1.15.0 and every pinned CDN snippet in the README and docs matches, so no version bump is part of this PR. npm currently serves 1.11.0; 1.121.14 were never published, so this release closes that gap.

⚠️ Merging publishes. .github/workflows/npm-publish.yml runs on push: [main], so merging this PR publishes 1.15.0 to npm.

Motivation

Three things were wrong that no test could see, because the suite runs in happy-dom and these only appear in a real browser:

  • bq-tabs froze the page. updated() writes panel.id onto its light-DOM panels while a MutationObserver watches the same subtree for id. setAttribute emits a mutation record even when the value is unchanged, so the observer re-entered its own render and pinned the main thread. Any page using the documented panel markup hung on load — DOMContentLoaded never fired.
  • connected() runs twice for most elements. The runtime mounts an element from attributeChangedCallback during upgrade, and the connectedCallback that follows sees it already mounted and takes its reconnect path. Every listener, observer and timer was registered twice, and the runtime replaced its own scope reference in between, so the first set could never be released. bq-dropdown-menu toggled twice per click and therefore never opened.
  • Dark mode was broken in Firefox and Safari, and half-broken everywhere. :host-context() exists only in Chromium, and it sat in a selector list next to :host([data-theme="dark"]) — one unknown selector invalidates the whole list, so those browsers dropped the entire dark theme. Separately, alerts, badges, chips, avatars and tags painted from --bq-color-*-100/700; the palette is scheme-independent, so those surfaces stayed near-white on a dark page.

Changes included

Fixes

  • bq-tabs: light-DOM sync now pauses the observer and writes only what changes.
  • utils/component.ts: bind() unwinds a previous scope on re-entry, collapsing the double connected() onto one live set of registrations. Adds scopeOf() for hooks that need to append teardown without resetting.
  • Theming: semantic tokens resolve through an inherited --bq-scheme-* channel instead of :host-context(), so data-theme works in every browser and on any subtree, not just the root. auto is resolved in CSS, so an OS-level switch repaints with no matchMedia listener.
  • bq-switch: the track is a sibling of the input again, so input:checked ~ .track matches and the on-state renders.
  • bq-card: header and footer bands are hidden when nothing is slotted (:empty cannot see this — the band always contains a <slot>).
  • bq-radio: radios sharing a name now clear each other, scoped to the nearest <form>. Each radio lives in its own shadow root, so the platform's one-per-name rule could not see the siblings.
  • Sanitizer: scope, colspan, style and spellcheck were being stripped silently — the table's "no data" cell spanned one column instead of the row.
  • Focus: rendering assigns shadowRoot.innerHTML, so every render destroyed the focused field. Focus and caret now survive the renders a component's own interactions cause, including the very first one (focusing a combobox opened it, which re-rendered it away).

Additions

  • 6 components: bq-date-picker (UTC date maths, Intl-localised, full keyboard grid), bq-tree (ARIA tree pattern, roving tab stop, typeahead), bq-pin-input, bq-meter, bq-copy-button, bq-icon — plus bq-combobox, bq-tag-input, bq-file-upload, bq-popover, bq-timeline, bq-stepper, bq-rating, bq-number-input, bq-kbd, bq-banner, bq-button-group, bq-avatar-group from earlier work on this branch.
  • Icon set: 57 glyphs delivered as CSS masks. Inline <svg> is impossible — the sanitizer forbids svg and allowTags cannot re-open it — which is why the library drew its affordances with text characters (&times;, &#9660;, ) whose weight and baseline were up to the platform font. Icons now inherit currentColor and stay a single path at any DPI. iconCss() lets a component pull in only the glyphs it draws.
  • Token layer: semantic surfaces (--bq-surface-raised/overlay/sunken), translucent interaction states, per-intent focus rings, --bq-intent-* for tinted status surfaces, a shared control-height scale, and dark-mode elevation.
  • Custom-drawn controls: checkbox, radio, switch and slider were native inputs styled with accent-color — the only hook the platform offers. They are drawn now, with forced-colors support.

Guards

New tests that fail against the previous code:

  • tests/render-loops.test.ts — asserts a second pass over already-correct DOM writes nothing, which is the invariant that makes the tabs hang impossible.
  • tests/component-utils.test.tsbind() re-entry.
  • tests/sanitizer-allowlist.test.ts — parses every render template and fails on an attribute the sanitizer would drop.
  • tests/component-css.test.ts — parses each component's shipped stylesheet, and rejects any background painted from a 50/100/200 palette step.

Validation

  • bun run lint
  • bun run lint:types
  • bun test — 657 passing across 42 files
  • bun run build (lib, CDN, types) and bun run build:docs

Also rendered in headless Chromium across every component in light and dark mode, plus the overlays driven open with real clicks. That is how the four defects above were found; the existing suite passed the whole time.

Checklist

  • I followed the contributing guidelines.
  • I added or updated tests where behaviour changed.
  • I updated Storybook stories and documentation for the new public API.
  • I confirmed the change does not introduce security-sensitive regressions.

Notes for reviewers

  • Merging publishes to npm. Confirm 1.15.0 is the intended version before merging.
  • src/utils/styles.ts installs a document stylesheet on import. It is the one module every entrypoint pulls in, so it is the only place the theme channel can be registered regardless of how the package is imported. It defines custom properties under [data-theme] and is inert until a page sets the attribute — but it is a side effect that did not exist before.
  • bind() changed contract: calling it twice for one owner now unwinds the first scope. Every component calls it exactly once at the top of connected(), so nothing in-tree is affected, but the docstring and AGENT.md spell out the new rule.
  • The double connected() is a @bquery/bquery behaviour, not this repo's. bind() neutralises it for anything registered on the scope; anything else in connected() still runs twice. Fixing it properly belongs in the framework.
  • Bundle size: 53.8 → ~72 kB gzip for the full CDN bundle, for 18 more components plus the icon set. Per-component entrypoints stay tree-shakeable — @bquery/ui/components/badge does not pull in the icons.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 151 files, which is 51 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e7adf863-709b-4758-a8a0-f9e3feef9452

📥 Commits

Reviewing files that changed from the base of the PR and between e922231 and 9200406.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (151)
  • AGENT.md
  • README.md
  • docs/.vitepress/config.ts
  • docs/components/avatar-group.md
  • docs/components/banner.md
  • docs/components/button-group.md
  • docs/components/button.md
  • docs/components/copy-button.md
  • docs/components/date-picker.md
  • docs/components/file-upload.md
  • docs/components/icon.md
  • docs/components/index.md
  • docs/components/input.md
  • docs/components/kbd.md
  • docs/components/meter.md
  • docs/components/number-input.md
  • docs/components/pin-input.md
  • docs/components/popover.md
  • docs/components/radio.md
  • docs/components/rating.md
  • docs/components/stepper.md
  • docs/components/switch.md
  • docs/components/timeline.md
  • docs/components/tree.md
  • docs/guide/dark-mode.md
  • docs/guide/installation.md
  • docs/guide/migration-from-1-1-0.md
  • docs/guide/theming.md
  • package.json
  • src/components/accordion/BqAccordion.ts
  • src/components/alert/BqAlert.ts
  • src/components/avatar-group/BqAvatarGroup.ts
  • src/components/avatar-group/index.ts
  • src/components/avatar/BqAvatar.ts
  • src/components/badge/BqBadge.ts
  • src/components/banner/BqBanner.ts
  • src/components/banner/index.ts
  • src/components/breadcrumbs/BqBreadcrumbs.ts
  • src/components/button-group/BqButtonGroup.ts
  • src/components/button-group/index.ts
  • src/components/button/BqButton.ts
  • src/components/card/BqCard.ts
  • src/components/checkbox/BqCheckbox.ts
  • src/components/chip/BqChip.ts
  • src/components/combobox/BqCombobox.ts
  • src/components/combobox/index.ts
  • src/components/copy-button/BqCopyButton.ts
  • src/components/copy-button/index.ts
  • src/components/date-picker/BqDatePicker.ts
  • src/components/date-picker/index.ts
  • src/components/dialog/BqDialog.ts
  • src/components/divider/BqDivider.ts
  • src/components/drawer/BqDrawer.ts
  • src/components/dropdown-menu/BqDropdownMenu.ts
  • src/components/empty-state/BqEmptyState.ts
  • src/components/file-upload/BqFileUpload.ts
  • src/components/file-upload/index.ts
  • src/components/icon-button/BqIconButton.ts
  • src/components/icon/BqIcon.ts
  • src/components/icon/index.ts
  • src/components/index.ts
  • src/components/input/BqInput.ts
  • src/components/kbd/BqKbd.ts
  • src/components/kbd/index.ts
  • src/components/meter/BqMeter.ts
  • src/components/meter/index.ts
  • src/components/number-input/BqNumberInput.ts
  • src/components/number-input/index.ts
  • src/components/pagination/BqPagination.ts
  • src/components/pin-input/BqPinInput.ts
  • src/components/pin-input/index.ts
  • src/components/popover/BqPopover.ts
  • src/components/popover/index.ts
  • src/components/progress/BqProgress.ts
  • src/components/radio/BqRadio.ts
  • src/components/rating/BqRating.ts
  • src/components/rating/index.ts
  • src/components/segmented-control/BqSegmentedControl.ts
  • src/components/select/BqSelect.ts
  • src/components/skeleton/BqSkeleton.ts
  • src/components/slider/BqSlider.ts
  • src/components/spinner/BqSpinner.ts
  • src/components/stat-card/BqStatCard.ts
  • src/components/stepper/BqStepper.ts
  • src/components/stepper/index.ts
  • src/components/switch/BqSwitch.ts
  • src/components/table/BqTable.ts
  • src/components/tabs/BqTabs.ts
  • src/components/tag-input/BqTagInput.ts
  • src/components/tag-input/index.ts
  • src/components/textarea/BqTextarea.ts
  • src/components/timeline/BqTimeline.ts
  • src/components/timeline/index.ts
  • src/components/toast/BqToast.ts
  • src/components/tooltip/BqTooltip.ts
  • src/components/tree/BqTree.ts
  • src/components/tree/index.ts
  • src/i18n/en.ts
  • src/index.ts
  • src/theme/dark.ts
  • src/theme/default.ts
  • src/theme/index.ts
  • src/theme/root.ts
  • src/theme/scheme.ts
  • src/tokens/index.ts
  • src/utils/component.ts
  • src/utils/dom.ts
  • src/utils/focus.ts
  • src/utils/form.ts
  • src/utils/icons.ts
  • src/utils/index.ts
  • src/utils/position.ts
  • src/utils/styles.ts
  • src/utils/validation.ts
  • stories/avatar-group.stories.ts
  • stories/banner.stories.ts
  • stories/button-group.stories.ts
  • stories/copy-button.stories.ts
  • stories/date-picker.stories.ts
  • stories/file-upload.stories.ts
  • stories/icon.stories.ts
  • stories/kbd.stories.ts
  • stories/meter.stories.ts
  • stories/number-input.stories.ts
  • stories/pin-input.stories.ts
  • stories/popover.stories.ts
  • stories/rating.stories.ts
  • stories/stepper.stories.ts
  • stories/timeline.stories.ts
  • stories/tree.stories.ts
  • tests/checkbox-radio-switch.test.ts
  • tests/combobox.test.ts
  • tests/component-css.test.ts
  • tests/component-utils.test.ts
  • tests/date-picker.test.ts
  • tests/focus-preservation.test.ts
  • tests/icons.test.ts
  • tests/input-enhancements.test.ts
  • tests/meter-copy-button.test.ts
  • tests/new-components.test.ts
  • tests/pin-input.test.ts
  • tests/position.test.ts
  • tests/render-loops.test.ts
  • tests/sanitizer-allowlist.test.ts
  • tests/segmented-control.test.ts
  • tests/slider-pagination.test.ts
  • tests/styles.test.ts
  • tests/tag-input.test.ts
  • tests/theme-scheme.test.ts
  • tests/tree.test.ts
  • tests/validation.test.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Comment thread tests/sanitizer-allowlist.test.ts Fixed
…regex

`[^<>]` already matches newlines — a character class is unaffected by the `s`
flag — so `(?:[^<>]|\n)*?` gave the group two ways to match the same character.
CodeQL flagged it: an unterminated tag such as `<A` followed by many newlines
backtracks exponentially.

One unambiguous class matches the same text in linear time; the audit still
finds the attributes it is meant to catch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@JosunLP
JosunLP merged commit 82b9cdd into main Jul 31, 2026
11 checks passed
@JosunLP
JosunLP deleted the update/1.5.0 branch July 31, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants