Skip to content

Latest commit

 

History

History
183 lines (110 loc) · 14.3 KB

File metadata and controls

183 lines (110 loc) · 14.3 KB

Changelog

All notable changes to sibujs-ui will be documented in this file.

This project follows Semantic Versioning.


[1.4.2] — 2026-06-05

Changed

  • Bumped the sibujs dependency to 3.2.2. No component changes — sibujs-ui does not use the router, so this is a maintenance bump to track the latest core release.

[1.4.1] — 2026-06-01

Fixed — Portaled content leaked on unmount (dropdown-menu, menubar, tooltip)

Components that teleport their content to document.body to escape overflow clipping registered their cleanup on the portaled node. Because dispose() only traverses the owner's subtree, a disposer on a node that has been moved to <body> never runs on unmount — so the portaled DOM node was orphaned in <body> indefinitely, and any document mousedown/keydown listeners and effect() subscriptions leaked with it.

Cleanup is now anchored on the in-tree root element (which dispose() does reach), so on unmount the positioning effect is torn down, global listeners are removed, and the portaled node is removed:

  • DropdownMenuContent — was leaking the node and (when open) the outside-click/keydown listeners; the positioning effect was also never disposed.
  • DropdownMenuSubContent and MenubarSubContent — had no disposer at all; the portaled node and the open-state effect leaked on every unmount.
  • TooltipContent (with portal: true) — already removed the node, but its hover timer and display effect leaked when portaled; both are now torn down too.

No API or behavior changes for mounted components — this only affects teardown. Verified against sibujs@3.2.0.


[1.4.0] — 2026-05-29

Added

  • Dev warning for a misplaced lone class string — a lone string passed to a component (Skeleton("h-6 w-48")) still renders as a text child (unchanged behavior), but development builds now warn when that string looks like a CSS class list, hinting the { class: "…" } form. This matches the core tag() behavior and prevents class names from silently rendering as visible text in styled wrappers. Prose strings (Badge("New"), Empty("No results")) never trigger the warning.

[1.3.2] — 2026-04-19

Changed — peer range widened to sibujs ^3.0.0

peerDependencies.sibujs bumped from ^2.0.0 to ^3.0.0 so the package installs cleanly alongside sibujs 3.x. sibujs-ui does not use ErrorBoundary (the only public API that changed in sibujs 3.0.0) — every component relies exclusively on signal, derived, effect, tag factories, and cnReactive, all of which are unchanged. No source changes, no behavior changes.


[1.3.1] — 2026-04-18

Changed — peer range widened to sibujs ^2.0.0

peerDependencies.sibujs relaxed from >=2.2.0 to ^2.0.0. The package only uses the stable public API (signal, derived, effect, tag factories, cnReactive) — none of which has changed since 2.0.0 — so the previous floor unnecessarily excluded consumers on sibujs 2.0.x and 2.1.x. No source changes, no behavior changes. Install compatibility gained, nothing lost.


[1.3.0] — 2026-04-16

Added — TooltipContent.portal

TooltipContent now accepts a portal?: boolean prop. When set, the content element is teleported to document.body on mount and switched from position: absolute (anchored to the tooltip parent) to position: fixed with coordinates recomputed from the trigger's getBoundingClientRect() every time the tooltip opens. Use this whenever the tooltip's nearest scrolling ancestor has overflow: hidden and would otherwise clip the content. Cleanup is wired through registerDisposer so the teleported node is removed when the owning tooltip is disposed.

Fixed — Sidebar tooltip race in expanded mode

SidebarMenuButton previously tried to suppress the icon-mode tooltip when the sidebar was expanded by toggling display on the content element from a one-shot effect. TooltipContent's own display effect re-asserted display = "" on every hover, so the suppression effect was overwritten and the tooltip became visible in expanded mode (and could also leak into the collapsed-without-hover state). The suppression has been replaced with an open() interceptor that wraps the tooltip context so the tooltip simply does not open while the sidebar is expanded or in mobile mode — no more inline-style tug-of-war with TooltipContent.

Fixed — Sidebar tooltip clipped in icon-collapsed mode

SidebarContent carries overflow: hidden in group-data-[collapsible=icon] to prevent horizontal scrollbars in icon mode. That clipping also hid the menu-button tooltip, which renders to the right of the (≈48px-wide) icon column. SidebarMenuButton now passes portal: true to its TooltipContent, so the label escapes the sidebar's overflow box and renders correctly next to the icon.


[1.1.0] — 2026-04-11

Changed — shorthand-only authoring internally

Every component in sibujs-ui has been migrated to the positional shorthand form (tag(children), tag("className", children), tag({ props }, children)). The previous { class, nodes: [...] } object form is gone from the package source — 132 call sites across 34 component files were rewritten in a single AST-driven pass so the internal style is uniform. This is purely an internal refactor: every public component API, prop name, className contract, and DOM output is unchanged, and no runtime behavior is affected.

Why the minor bump: while consumers see no API change, the shorthand is now the canonical authoring style across the SibuJS ecosystem (documented that way in the framework docs and reference site), and aligning the UI package with that canonical form is the kind of cross-cutting source change that deserves a visible version marker rather than a silent patch.

normalizeArgs continues to accept the legacy { nodes: ... } prop form from user code, so existing consumer call sites keep working exactly as before — the change is in how the package's own components are written, not in what shapes they accept.


[1.0.12] — 2026-04-11

Fixed — reactive controlled props

Previously every stateful component inlined signal(controlled ?? default) to seed its internal state. When the caller passed a reactive getter (() => T), the function itself was stored as the signal value, so every read returned the function rather than the unwrapped value — data-state, aria-*, and change callbacks all broke silently. A new shared bindControlled<T>() helper handles all three shapes (undefined / literal / getter) in one place, and every stateful component has been migrated to use it:

  • Toggle.pressed — now reactive.
  • Tooltip.open — now accepts boolean | (() => boolean) (type signature widened + runtime fix).
  • HoverCard.open — same fix as Tooltip.
  • Checkbox.checked, Switch.checked, Tabs.value, RadioGroup.value, Slider.value, Collapsible.open — migrated off the inline pattern onto bindControlled.
  • Select.value, Dialog.open, AlertDialog.open, Accordion.value — the inline fixes shipped earlier have been replaced with the shared helper for consistency.

Fixed — unmount leaks

Many overlay/menu components attached document/window listeners, ResizeObservers, and setTimeout callbacks inside queueMicrotask or effect() without cleanup paths for the unmount-while-open case. Every confirmed leak is now tied to registerDisposer:

  • Dialog / AlertDialog / Drawer / Sheet — detaches document keydown listeners, clears pending close timers, and restores document.body.style.overflow if the overlay is disposed while still open.
  • Popover / DropdownMenu / ContextMenu / Menubar / Select / NavigationMenu / Calendar — detaches document mousedown/keydown outside-click-and-escape listeners on dispose.
  • Combobox — the outside-click listener was previously leaked on every effect() re-run because effect() does not honor return-value cleanups. The handler is now hoisted and attach/detach is driven by isOpen() state, with a final registerDisposer safety net.
  • Sidebarwindow.matchMedia('change') and window keydown (Ctrl/Cmd+B shortcut) listeners are now detached on dispose.
  • Accordion — content ResizeObserver, close-fallback setTimeout, and the state effect() share a single disposer.
  • ScrollArea — viewport ResizeObserver is disconnected on dispose.
  • Tooltip / HoverCard — pending open/close setTimeouts and the content effect() are cleaned up on dispose.

Fixed — miscellaneous

  • Label.htmlFor aliasLabel and FieldLabel now accept both for and htmlFor as the associated-element id. Previously only for was honored; htmlFor call sites silently dropped the prop.
  • normalizeArgs props-plus-children shorthandComponent({ ...props }, children) (sibujs 1.3.0's canonical form) is now recognised alongside the positional Component("className", children) form. The two are disambiguated by the type of the first argument; when a props object is passed, the positional second argument wins over any nodes key already on the object.

Added

  • bindControlled<T>() helper — shared controlled-prop utility exported from src/lib/controlled.ts.
  • aria-describedby for Tooltip and HoverCard — triggers expose a stable aria-describedby resolving to the content id so screen readers can associate the content with the focusable trigger.
  • Keyboard dismissal for Tooltip and HoverCard — pressing Escape while the trigger is focused closes the overlay.
  • Touch support for Tooltip and HoverCardpointerenter/pointerleave replace the previous mouse-only wiring, so tap devices can surface both overlays.
  • FOUC prevention for Tooltip and HoverCard — content elements seed data-state="closed" and display: none at creation, so closed-state CSS applies on the first paint.

Security

  • Sidebar cookie hardening — the sidebar persistence cookie now emits SameSite=Lax (CSRF hardening) and Secure when the page is served over HTTPS. The stored value is non-sensitive (just open/closed), but defense-in-depth.

Dependencies

  • sibujs peer and dev dependency bumped to ^1.3.0 — the new bindControlled helper and disposer-tied resource cleanup depend on createId, registerDisposer, and the tag(props, children) shorthand introduced in sibujs 1.3.0. Consumers must upgrade sibujs alongside sibujs-ui 1.0.12.

Tests

  • New test infrastructurevitest.config.ts (jsdom environment) plus three test files:
    • tests/smoke.test.ts — 64 tests; every exported component constructs without throwing.
    • tests/bindControlled.test.ts — 5 unit tests for the helper.
    • tests/regressions.test.ts — 23 historical-regression tests covering Checkbox/Switch/Tabs/Toggle reactive controlled props, Label/FieldLabel for/htmlFor compat, Tooltip/HoverCard reactive open + aria-describedby + Escape + initial data-state="closed", and Accordion/ScrollArea ResizeObserver cleanup on dispose (via a mocked observer).
  • 92 tests total, all passing under jsdom.

[1.0.11] — 2026-04-04

Fixed

  • Slider value prop is reactive — The implementation now handles the () => number[] getter that the type signature already accepted. Passing a signal getter reactively syncs the slider values via effect(), aligning with the pattern used in Select.

[1.0.10] — 2026-04-03

Fixed

  • Dialog/AlertDialog __dialog/__alertDialog available during onElement — The imperative API is now assigned inside an internal onElement callback before the user's fires, so el.__dialog.open() works immediately without setTimeout workarounds.
  • Dialog/AlertDialog open prop is reactiveopen now accepts boolean | (() => boolean). Passing a signal getter reactively syncs the internal state via effect(), enabling controlled dialog state without imperative hacks.
  • Select value prop is reactive — The implementation now handles the () => string getter that the type signature already accepted. Passing a signal getter reactively syncs the selected value via effect().
  • DropdownMenuContent no longer clipped by ancestor overflow — Content is now portaled to document.body with position: fixed and positioned via getBoundingClientRect(), avoiding all stacking context and overflow clipping issues. DropdownMenuItem and DropdownMenuRadioItem updated to resolve the root menu via __dropdownRoot when inside portaled content.

Changed

  • Enforce LF line endings — Added .gitattributes with * text=auto eol=lf to prevent CRLF formatting drift on Windows.

[1.0.9] — 2026-04-03

Fixed

  • SelectValue renders full node tree — Previously, selecting a SelectItem with complex children (multiple spans, icons, nested markup) flattened everything to .textContent, losing structure and styling. SelectValue now clones the selected item's DOM subtree via cloneNode(true) and renders it in the trigger, preserving formatting. Falls back to plain text for simple items.

Changed

  • Input accepts reactive valueInputProps.value now accepts string | (() => string). The runtime already handled reactive getters via bindAttribute; this fixes the TypeScript type to match.
  • Textarea accepts reactive value — Same fix as Input: TextareaProps.value now accepts string | (() => string).
  • Badge accepts reactive variantBadgeProps.variant now accepts BadgeVariant | (() => BadgeVariant). When a getter is passed, the CVA class computation is wrapped in a reactive function so the badge re-styles when the variant changes. A new BadgeVariant type is exported.
  • Reactive controlled props across all stateful components — The following props now accept T | (() => T) reactive getters in addition to plain values:
    • Select.valuestring | (() => string)
    • Tabs.valuestring | (() => string)
    • RadioGroup.valuestring | (() => string)
    • Accordion.valuestring | string[] | (() => string | string[])
    • Slider.valuenumber[] | (() => number[])
    • Toggle.pressedboolean | (() => boolean)
    • Switch.checkedboolean | (() => boolean)
    • Checkbox.checkedboolean | (() => boolean)
    • Collapsible.openboolean | (() => boolean)