Changelog · latest
@codeatlasdev/ui 4.4.0
27 changes. This release has no written post; the list below is generated from the changelog.
In short
- NavItem in the AppShell panel gets color (a decorative dot), l... `NavItem` in the AppShell panel gets `color` (a decorative dot), `leading` (a snippet that replaces the icon), `count` (a neutral total, announced as a bare number instead of "N unread", `1.2k` above 999) and `actions` (a snippet of `DropdownMenuItem`s behind a "…" button named "Actions for <label>") (#275).
- AppShellLayout takes panelWidth (px, held between 160 and 480,... `AppShellLayout` takes `panelWidth` (px, held between 160 and 480, default 200) and `resizablePanel` (#292).
- Combobox: multiple turns it into a searchable multi-select (va... `Combobox`: `multiple` turns it into a searchable multi-select (`value` is `string[]`, `aria-multiselectable` listbox, list stays open, removable chips, `Backspace` on an empty query removes the last chip) and `creatable` works in both modes.
Upgrade
Published under the latest dist-tag.
bun
npm
pnpm
yarn
Then run your checks. Moving between majors? bunx @codeatlasdev/ui migrate --from <major> --to <major> lists what to change, and agents get the same data from the MCP tool get_migration. The migration notes per major are inUpgrading.
Changesets
### Minor Changes
- 1c6653e: `NavItem` in the AppShell panel gets `color` (a decorative dot), `leading` (a snippet that replaces the icon), `count` (a neutral total, announced as a bare number instead of "N unread", `1.2k` above 999) and `actions` (a snippet of `DropdownMenuItem`s behind a "…" button named "Actions for <label>") (#275). `NavSection.onReorder` and `NavModule.onReorder` make the rows reorderable with the `useDnd` primitive: a grip per row, mouse, touch press-and-hold and keyboard, with announcements; the shell reports `{ id, from, to }` (new `NavReorderEvent` type) and never reorders `items` itself. `PanelNavItem` now renders a row that contains the `<a class="ca-nav-item__link">` (a link cannot hold the actions button or the grip), so `.ca-nav-item` is the row, not the anchor. New messages `appShell.itemActions` and `appShell.moveItem`.
- 1c6653e: `AppShellLayout` takes `panelWidth` (px, held between 160 and 480, default 200) and `resizablePanel` (#292). The content offset and the open and close animation follow the width, so a panel for long labels no longer misaligns the page. `resizablePanel` adds a keyboard-operable drag handle on the panel's trailing edge (Arrow keys 8px, Shift 32px, Home and End for the bounds); with `persistPanel` the chosen width is stored in `ca-shell` and restored by the inline script before the first paint. New `appShell.resizePanel` message and `.ca-panel__resize` class.
- ca80cfd: `Combobox`: `multiple` turns it into a searchable multi-select (`value` is `string[]`, `aria-multiselectable` listbox, list stays open, removable chips, `Backspace` on an empty query removes the last chip) and `creatable` works in both modes. `CommandPalette`: `search(query, signal)` adds debounced (`searchDebounce`), abortable server-side results with loading, empty and failed states announced through `announce()`, plus `onSelect` and the exported `CommandResult` type.
- 49116d1: `DataTable` gains row groups, column resize, row reorder and safer interactive cells (#283). `groupBy` renders one `<tbody>` per key with a collapsible header (`aria-expanded`, `groupLabel` to customize). `meta.resizable` on a column adds a window-splitter handle (mouse, touch, keyboard arrows, Shift, Home, End, Enter) driven by `size`, `minSize` and `maxSize`; the new `DataTableColumnResize` is exported for hand-built headers. `onReorder` adds a drag grip per row on top of the new `useDnd` primitive, moves rows between groups and reports `{ id, from, to }` (`DataTableReorderEvent`); `rowId` gives rows stable ids and `pageSize` sets the page size in simple mode. A click on a link, button, input or other control inside a cell no longer fires `onRowClick` (add `data-no-row-click` to opt other elements out). The new labels (Reorder, Move, Resize) follow the active locale (en, pt-BR). Needs the drag and drop primitive from #284.
- d138bd8: New drag and drop primitive, `useDnd()`, `applyMove` and `<DndLayer>`, for sortable lists, moves between lists and drop zones (#284). It works with mouse and pen (4px threshold), touch (press and hold, so the page still scrolls) and keyboard (Space lifts, arrows move, Home/End jump, Enter drops, Escape cancels), speaks every step through `announce()` (English and Portuguese built in, `useDnd({ messages })` for others), auto-scrolls near the edges of a scrolling container and drops its animations under `prefers-reduced-motion`. The engine measures the boxes once per drag and only moves a clone, a drop line and `data-dnd-*` attributes, so the list does not re-render until `onDrop`. `<DndLayer {dnd} />` renders the keyboard instructions and the forced-colors aware styles (`.ca-dnd-overlay`, `.ca-dnd-indicator`), so apps that never drag add no bytes to `core.css` or to the i18n catalog.
- 946d69d: `Drawer` can be named without its default title: pass `aria-label`, `aria-labelledby` (your own header) or `hideTitle`, and `id`, `data-*` and other attributes now reach the `<dialog>` (#285). `AlertDialog` gains `confirmProps` and `cancelProps` (attributes for the two buttons) and `cancelVariant` (#287), plus a `children` body, a `description` that accepts a snippet and `confirmDisabled` for type-to-confirm (#278).
- e983caa: `DropdownMenu` is controllable and can open at a point. `bind:open` and `onOpenChange` read and set the open state; `anchor={{ x, y }}` opens the menu at a viewport point (a right-click menu, no `trigger` needed) and changing it while open moves the menu instead of toggling it. A menu opened from code starts on its first item and returns focus to the element that had it. On the built-in trigger Shift+F10 and the ContextMenu key open the menu like ArrowDown.
- e983caa: `DropdownMenu` gains selection items. `DropdownMenuCheckboxItem` (`role="menuitemcheckbox"`, `bind:checked`, `onCheckedChange`) and `DropdownMenuRadioGroup` + `DropdownMenuRadioItem` (`role="menuitemradio"`, `bind:value`, `onValueChange`) render `aria-checked` and a check indicator, stay open after a pick (`closeOnSelect` closes), and join arrow, Home, End and typeahead navigation (the menu now also recognizes `menuitemradio`).
- e983caa: `DropdownMenu` gains submenus. `DropdownMenuSub` takes a `trigger` snippet holding a `DropdownMenuSubTrigger` item (`aria-haspopup="menu"`, `aria-expanded`, chevron) and the submenu items as children; they nest to any depth. ArrowRight, Enter or Space opens the submenu on its first item, ArrowLeft or Escape closes only that level, hovering the trigger opens it after a short intent delay and the pointer can cross into it. Selecting an item closes every level. A popover layer that is closing no longer owns Escape or outside presses, so a closing submenu hands them to its parent.
- ff460ad: Form controls forward native attributes and expose their element, with one vocabulary (`ref`, `hideLabel`, rest props on the native element):
- `Checkbox`, `TextField`, `TextArea` and `FileUpload` spread every other attribute and handler (`data-*`, `maxlength`, `inputmode`, `onclick`, `onkeydown`, `name`, `capture`...) on the native `<input>`/`<textarea>`, and take a bindable `ref` with that element. `Checkbox` `onchange` now receives the native event as a second argument, and its forwarded `onclick` makes Shift range selection possible. `TextField`/`TextArea` gain `onfocus` and `hideLabel` (label kept for screen readers with `.ca-sr-only`; `aria-label` also works without a label).
- `Select` accepts `aria-label` and `aria-labelledby` for a compact select without a visible label. The trigger is named by that text followed by the current value (visible text stays in the name); without either, the placeholder names it as before.
- `FileUpload` gains `ref`, an `open()` instance method for a button outside the dropzone, and `list={false}`: a plain picker whose `onchange` receives only the files just picked, with no built-in list and nothing kept in `files` (the native input is cleared, so the same file can be picked again).
- 8c24c31: Theme option `motion: { enter: "none" }` (token `--ca-enter-scale: 0`) turns off entrance and exit animations only: `Dialog`, `AlertDialog`, `Drawer`, `Sheet`, `Lightbox`, `CommandPalette`, `DocsSearch`, date pickers, `DropdownMenu`, `Popover`, `Tooltip` and `Toast` appear and leave in place, while hover and focus color transitions keep `--ca-duration-*`. New derived tokens `--ca-enter-fast`, `--ca-enter-base` and `--ca-enter-slow`; `MotionStage` demos render their final state when the theme turns entrances off, as under `prefers-reduced-motion` (#268).
- 16f89dd: `Popover` is controllable. `bind:open` and `onOpenChange` read and set the open state, and the `children` snippet receives `close`, which closes the popover and returns focus to the trigger, so a choice made inside it (a `Calendar` day, an option) can dismiss it. Existing popovers are unchanged.
- 8c24c31: New `--ca-radius-round` token (`999px × --ca-radius-scale`): "fully rounded at the current scale". `SwatchGroup` swatches, the AppShell active pips (`.ca-nav-item__pip`, `.ca-rail__pip`) and the new-item dot, and `.ca-nav-item__badge` use it, so a theme with `radius: 0` squares them with no app CSS (#267). `--ca-radius-chip` now aliases it. `--ca-radius-pill` stays a fixed 999px for functional shapes (Switch, Slider, Progress, scrollbars) and for `controlRadius: "pill"`.
- 946d69d: `Tabs` gains `variant="line"` (bare text tabs with an underline on the active one, for page headers), and `TabsList` and `TabsTrigger` forward rest attributes: `aria-label` names the tablist, `data-*`, `id` and event handlers reach the trigger button (#286). A `TabsTrigger` now points `aria-controls` at its panel only while a `TabsContent` with the same `value` is mounted, so tabs used as view switchers no longer reference missing elements.
- 2ab0d93: `ToggleGroup` gets `required`: pressing the active item no longer clears the selection (radio-like, `onchange` stays silent; `type="multiple"` keeps its last pressed item). An empty group still selects on the first press. Closes the gap with `SegmentedControl` for filters that must look like contiguous buttons.
### Patch Changes
- 05cfbfc: `AppShellLayout`: an item at `path: "/"` is active on `/` only, no longer on every page, and a module at `basePath: "/"` only gets the pages no other module claims. Module prefixes (`basePath`, `pathPrefixes`) match whole path segments and the longest one wins, whatever the module order (`/settingsfoo` no longer matches `/settings`).
- 98d2ace: Astro: the theme now survives `<ClientRouter />` (View Transitions) navigations. `caUI` registers an `astro:before-swap` handler that carries `#ca-theme`, `#ca-runtime-tokens`, the `theme-color` meta and the theme attributes onto the incoming document (#265). `codeatlas-ui init` on Astro also adds `svelte()` from `@astrojs/svelte` to `integrations`, and notes `npx astro add svelte` when the dependency is missing (#273).
- 3ae1f41: `Button` with `href` now types the anchor's own attributes: `download`, `rel`, `hreflang`, `ping`, `referrerpolicy` and `type` (the linked resource's MIME type) are accepted instead of rejected, as `...rest` was already spread on the `<a>`. The props are a union keyed on `href`: a link takes anchor attributes, a button takes button attributes (`form`, `formaction`, `name`, `value`...), and `<Button download>` without `href` is a type error. At run time a `type` passed with `href` reaches the `<a>`, and a caller `rel` is kept and merged with `noopener noreferrer` when `target="_blank"` (it used to be overwritten).
- 5c9ac81: CommandPalette: new `shortcut` prop (default `true`). With two palettes on one page, every instance opened on Mod+K; set `shortcut={false}` on all but one, or when the app binds its own shortcut.
- a61874b: Calendar, RangeCalendar, DatePicker and DateRangePicker: fast arrow-key sequences no longer lose keys. The roving focus now moves synchronously instead of after `tick()`, so the next key always starts from the day it lands on, with or without Svelte's `experimental.async`.
- e983caa: `DropdownMenuItem` forwards its remaining attributes (`data-*`, `id`, `aria-*`, `title`, ...) to the `menuitem` element. `data-testid` and `aria-keyshortcuts` used to be dropped. `role`, `tabindex` and the selection handler stay owned by the item.
- ff8686f: `DropdownMenuItem` and `TabsTrigger` icon slots now size any `svg` or `img` inside the 14px box, not only a direct child. A `SchemeAsset` logo (its variants sit in `display: contents` spans) used to collapse to 0×0 when the svg had no `width`/`height`, and an svg with a fixed `width="24"` overflowed the box; both now fill the box.
- d01c039: `LoadingBoundary` counts `minDuration` from the moment the placeholder reaches the DOM instead of from the state change that schedules it, so a busy main thread no longer shortens the time the placeholder is visible.
- 23276cf: Fix Escape going to the wrong layer. A Dialog, AlertDialog, Drawer, Sheet, Lightbox, CommandPalette or DocsSearch that is playing its exit animation no longer takes Escape or presses: they reach the layer below, so closing a confirm dialog and pressing Escape right after closes the Drawer under it (#288). A menu or popover that was open when a modal opened now ranks above that modal, so Escape closes the menu first and the dialog second instead of closing the dialog and leaving the menu open (#299). Exit animations of components that use `createPresence` (Dialog, Drawer, AlertDialog, menus, popovers) now keep the node mounted through the exit instead of removing it in the flush that closes it, and a menu hosted in a dialog is no longer left stranded after its exit.
- 05cfbfc: `Rail`: the dock magnification scales the module icon (the glyph), not the whole button, so the hovered background no longer covers the active neighbour's background and pip. The effect follows `--ca-motion-scale`: it is off with `motion: { scale: 0 }` and under `prefers-reduced-motion`.
- 05cfbfc: Native `button`, `input`, `select` and `textarea` inherit the page font (`font: inherit` in the new `ca.reset` layer, `styles/reset.css`) instead of the browser's UA font. The AppShell section header (`.ca-section__trigger` / `.ca-section__label`) and any control without its own font rule now render in `--ca-font-sans`. Component and app rules still win: the layer is the first one.
- 8c24c31: `Skeleton` and the `skeleton` prop follow the theme surface style: under `flat`, `solid` and `outlined` no gradient band is drawn, the placeholder is a flat block that pulses its opacity (`ca-skeleton-pulse`, stopped under reduced motion). Glass, frosted, neumorphic, clay, chrome and tonal keep the shimmer sweep (#280).