Skip to content

@chassis-ui/react v0.6.0

Latest

Choose a tag to compare

@github-actions github-actions released this 28 Oct 19:48

Minor Changes

  • d88791c: Autocomplete takes selectedText, a function of the count that words the "N selected" text a multiple autocomplete's toggle shows, so the toggle can follow the page's language. The text was English with no override.

  • 5cd6580: Breaking: @chassis-ui/css 0.12 is required (the peer range is >=0.12.0 <0.13.0). No prop, export
    or class name of this package changes, and no component renders a class whose rule 0.12 changes;
    a page changes where its own classes do. A border width or style class, such as .border-xl or
    .border-style-dashed, sets border-width and border-style on its element only, no longer the
    inherited --cx-border-width and --cx-border-style that reached every border inside it; on a
    component it changes the outline, and the component's variable, such as --cx-card-border-width,
    changes its dividers too. In chassis.css a longhand utility wins over a shorthand on the same
    element: place-items-center align-items-baseline aligns to the baseline, and -m-2xl mb-0 gives
    a bottom margin of 0. The dividers of a card, popover, toast, tabs and table read
    --cx-border-style, and the inner corners of a card, accordion, popover and toast follow the
    component's own radius and width. The changelog of @chassis-ui/css 0.12.0 has the full list.

  • 5c03ff5: Table, StaticTable and DataGrid with sm now render compact, with half the cell padding: chassis-css has no size modifier for a table, so the .sm rule ships in this package's style.css. Before, sm rendered a class nothing styled.

    Breaking: Drawer no longer renders a nonmodal class, and NavTitle no longer renders nav-title. chassis-css styles neither (its .nonmodal applies to .dialog only). A stylesheet that targets either class should select the drawer by .drawer:not(:modal) and the title by a class of its own passed through className.

  • 6fd1de8: The native fields are typed with the attributes their element has (#88).

    • Breaking: SelectProps extends SelectHTMLAttributes, not InputHTMLAttributes: readOnly, type, pattern, min, max, checked and the other input attributes a <select> ignores are type errors now. readOnly rendered a readonly attribute the select didn't honour: it still changed and fired onChange.
    • Breaking: FileInputProps has no value or defaultValue. A file input's value is the files chosen, and a string given threw InvalidStateError at mount.
    • Breaking: ColorInputProps and RangeInputProps have no placeholder or readOnly, which HTML doesn't apply to type="color" or type="range"; RangeInput documented a read-only state it never had.
  • c6878bf: NavItem and Pagination put className, the ref and every other attribute on one element, as the other components do. NavItem gains linkProps for its link's own attributes, and Pagination gains listClassName for its .pagination list.

    Breaking: a NavItem with a link now puts its attributes (id, data-*, aria-*, style, …) on its <li>, beside className and the ref; only href, active, disabled, component, asChild and onClick still go to the link, and anything else for it, such as target or a router link's to, moves to linkProps. Pagination's className now goes to the <nav>, beside the ref and the other attributes; pass the list's classes as listClassName.

  • 2229e27: A Nav items entry without href now renders its link as a <button>, so active and disabled apply to it, and entries take an onClick. Such an entry rendered plain text that dropped both.

  • e7eae98: TableRow takes an id, the row's key as it is given, as DataGridRow and TreeItem already do. A row's only key was React's key, which React makes a string, so with the docs' key={row.id} on rows whose ids are numbers, selectedKeys={new Set([1])} selected nothing, disabledKeys={[2]} disabled nothing and onSelectionChange reported "1". Write <TableRow id={row.id}>: id wins over a key given beside it. The docs' examples now use it.

Patch Changes

  • 23f9bdb: A Modal, Drawer or Popover given aria-labelledby={undefined} keeps its automatic name (#66): the ModalTitle or DrawerTitle, or a Popover's title or trigger. A wrapper that forwards an optional prop (<Modal aria-labelledby={props.labelId}>) passes the key with no value, and that undefined wrote over the computed label, leaving an unnamed dialog. A caller's own aria-labelledby still names the dialog in place of the title.

  • cf85250: A Toast or Notification that autohides now resumes its countdown after a hover or focus with the time that was left, as the docs describe. Leaving restarted the whole delay, so a toast hovered 200 ms before it would have hidden stayed for another full delay.

  • 473139d: <Button asChild pressed> around a link given role="button" now renders aria-pressed, as <Button component="a" role="button" pressed> does. That is the documented way to make a router link a toggle; Button looked for the role in its own props, where asChild never puts the child's, so the pressed state was not announced.

  • cd8c7e6: A Button keeps an aria-pressed of the caller's when pressed isn't given: <Button aria-pressed="mixed"> is a tri-state toggle again, and a toggle whose state is kept outside pressed is announced as one. pressed was written after the caller's attributes as aria-pressed={undefined}, which removed the caller's on a <button>, an <input> and an element given button semantics. pressed still wins when both are given.

  • 47f3e51: Button, CloseButton and a clickable Link rendered on a plain tag (component="span", "div") keep a tabIndex and a role the caller passes, as they already did on a <button> or an <a>. The role="button" and tabIndex={0} they add to such a tag were written over the caller's, so a tabIndex={-1} for a roving tab stop in a toolbar, or a role="menuitem", was lost. A disabled one takes tabIndex={-1} for any tabIndex given, as a disabled <button> stays out of the tab order.

  • 2db54ba: A Calendar or RangeCalendar given an aria-label keeps the name react-aria composes for it with the visible month: aria-label="Event date" names the widget "Event date, March 2024" again, as a screen reader announces on entering it. The raw prop was spread after react-aria's and replaced that name with "Event date" alone.

  • 3859a34: A Carousel no longer calls onSlid or announces "Slide N of M" when it mounts under React.StrictMode (the default in development for Next.js and Vite templates). StrictMode runs mount effects twice, and the second run took the mount for a slide change: screen readers heard the position of every carousel on page load, and onSlid received { from: 0, to: 0 }. Both now fire only when the active slide changes.

  • 49d57dd: readOnly on a Checkbox or Switch makes it read-only: it keeps its state, onChange doesn't fire, and it carries aria-readonly="true". The prop went to the input as the native readonly attribute, which browsers ignore on a checkbox or radio, so the control still toggled and nothing said it was read-only. It works inside a CheckboxGroup and for the radio-backed Switch too, and the control can still be focused.

  • ab4534c: A ChipInput with no chips is one tab stop, its text input. react-aria makes an empty tag group focusable so that a message for the empty state can be reached; ChipInput renders none, so Tab from the field before it landed on a zero-size element, and a second Tab was needed to reach the input. Removing the last chip, with Backspace or its close button, moved the focus onto the same invisible element. The empty list is now focusable by script only, and the focus react-aria gives it after the last chip goes moves on to the input.

  • 670cab7: A CloseButton whose icon is swapped through children keeps its "Close" name. Any child used to cancel the fallback, an aria-hidden Icon or <svg> included, so every close button with a custom icon and no label was a button with no accessible name. The fallback now gives way only to children that name the button themselves: text, an element that holds text or carries its own aria-label, aria-labelledby or title, or a component other than Icon whose output it can't read (a <FormattedMessage>). Give an icon component from another library aria-hidden.

  • 91ec10b: On Android, the first tap on a ContextMenu item after a long press now chooses it. The click that iOS emits when the finger lifts is swallowed so it doesn't land on the item that has just opened under it; Android emits contextmenu instead and no click, so the swallow stayed armed for a second and ate the user's real tap. A long press that the browser reports with contextmenu, or a cancelled pointer, now leaves nothing to swallow.

  • ecc2e88: A ContextMenu under a right-to-left locale opens leftwards from the pointer, its start edge at the pointer as the docs describe. It asked for bottom-end under RTL, which react-aria mirrors again, so the menu extended rightwards as in a left-to-right page.

  • 3e97d53: DataGrid's align aligns the body cells' content (#101): align="middle" on a fixed rowHeight grid centres it, as Table's align does. The prop set a vertical-align class on the grid alone, which reached none of the virtualizer's absolutely positioned cells. A cell under a grid with align renders its content in one .datagrid-cell-content, which the stylesheet places; the header row keeps its own alignment, as a table's does.

  • 3e97d53: <DataGrid hover> highlights body rows alone (#103). The hover rule matched every [role="row"], the header row included, where Table hovers tbody rows only.

  • 526c637: DataGrid's pinned-column scroll cue follows the content width (#102). It was computed on mount, on scroll and on a resize of the grid, so a column set that changed without either (a column added, a defaultWidth or a pin changed) left the pin="end" shadow in its previous state until the user scrolled. The grid observes its scroll content as well.

  • 3ed4c88: DatePicker and DateRangePicker now portal their calendar popover, as Combobox, Autocomplete and Popover portal their panels: to document.body, or into the open <dialog> the picker is in. Rendered in place, the calendar was clipped by any ancestor that scrolls (an overflow: auto box, a table-responsive, a scrolling card or dialog body), and that ancestor scrolled instead of the calendar covering what is under it. The .datepicker popover is no longer a sibling of the field in the DOM, and a picker rendered open sends no calendar in its server HTML: it appears once the page has hydrated.

    The segments of DatePicker, DateRangePicker and TimeField sit closer together: each pads its sides by --cx-space-4xs instead of --cx-space-3xs, so a time reads 9:30 PM rather than 9 : 30 PM. The dash between the two dates of a DateRangePicker is spaced by --cx-space-xs instead of --cx-gap.

  • 9ed518a: A Modal, Alert or Drawer with keyboard={false} stays open however many times Escape is pressed. Chromium honours a prevented cancel once per user activation, so a second Escape in a row closed the dialog anyway. The Escape keydown of a modal dialog that keeps Escape out is now prevented too, which sends no cancel at all, and the dialog bounces as it does for the first press. An overlay open inside the dialog still takes the key first.

  • 015bfd3: A Modal, Drawer or Alert stays open when a press that starts inside it is released over the backdrop (#122): selecting text in a field and letting go outside it, or dragging a crop handle past the dialog's edge. The browser fires the click of such a press on the nearest common ancestor of its two ends, the <dialog> itself, which was taken for a click on the backdrop and closed the dialog, or bounced it with backdrop="static". Only a press that goes down and comes up on the backdrop closes or bounces it now, as in @chassis-ui/css's own dialogs.

  • 7f28789: A Modal, Drawer or Alert shown again before the browser delivers the close event of its last close stays open (#65). Browsers fire that event from a queued task, after whatever ran in the same task as the close: a wizard that opens its next step from onHidden, or a visible set to false and back in one event, reopened the dialog first, then took the late event for the browser's own close, fired onClose and onVisibleChange(false), and closed it. A Popover or Tooltip open in a dialog reopened that way closed too. The event is now left alone when the dialog is open again by the time it arrives; a close the browser makes by itself (a <form method="dialog">, a second Escape) still closes the dialog.

  • 5214071: The server HTML of a Modal or Drawer no longer points aria-labelledby at an id it doesn't hold (#67). Before hydration every dialog was labelled by the id a title takes by default, whether a title rendered or not and whatever id it had, so a dialog with no title, or with <ModalTitle id="...">, shipped a dangling reference in every page, closed dialogs included. A dialog rendered with open is now labelled in its server HTML by the title found among its children, under the id that title renders (its own id, the asChild element's, or the generated one), and by none when it has no title; a closed dialog has no aria-labelledby until hydration. After hydration nothing changes: the dialog is labelled by the title that renders.

  • af5212e: A Modal, Drawer or Alert is no longer a stop of its own in the Tab order in Firefox and Safari, which take in a <dialog> without a tabindex and focus it with no ring. A Navbar's responsive drawer shown inline was an invisible stop between the brand and the links, and a non-modal Modal or Drawer one before its first control. The dialog now renders tabIndex={-1}. A caller's tabIndex is kept, including when the dialog takes the focus on opening.

  • acef387: Of two Drawers mounted open in the same commit (two defaultVisible or visible drawers restored from the URL, say), only the last stays open (#68), as the docs promise of a second drawer that opens while one is open. Both stayed open before, two modal dialogs in the top layer at once, each holding a scroll lock: a drawer registered itself for the others to close only after the commit, by when both had already opened. The first now gets a close request (onClose, onVisibleChange(false)), as it does when the second opens later.

  • de8fb7c: A FloatingInput shows its invalidFeedback when a submit is held back by the field inside it: <FloatingInput invalidFeedback="Enter your email." label="Email"><TextInput name="email" required /></FloatingInput> marked the empty input as invalid on submit and said nothing. The message now appears with the mark and describes the input until the field has a value or the form is reset, and a Form with validated shows the validFeedback of a valid field the same way. An invalid or valid set on the FloatingInput still decides on its own.

  • e88ec2c: A form reset puts every field back to its first value, controlled or not, and reports it through onChange (#86), as the text fields already did. A controlled Select went to its first option while its state kept the edited value, which the next render put back; a controlled ColorInput or RangeInput stayed as edited; a controlled OtpInput reset to "" instead of its first code; and DatePicker, DateRangePicker, Autocomplete and ChipInput kept the edited value in what the form submitted.

  • a7b0166: Grid and GridItem render no class for a value @chassis-ui/css has no rule for, and warn in development (part of #116): a span, start or end past twelve columns, a rowSpan, rowStart or rowEnd past six rows, a breakpoint's columns or rows past them, and a count that isn't a whole number from 1 up (rows={2.5}), which set an invalid repeat() a browser drops. GridItem also warns for rows or gap without subgrid, which set nothing. The base columns of a Grid past twelve still falls back to --cx-grid-columns.

  • 2f2c71d: I18nProviderProps is exported beside I18nProvider (#118). A consumer wrapping the provider in one of their own had to type its props from react-aria's package, the copy the I18nProvider comment says not to use, since the one this package inlines is what DatePicker and the other fields read their locale from.

  • 3b698b8: A text field, <select>, <textarea>, radio, range or editable element inside a Menu, MenuSubmenu or ContextMenu list keeps its own arrow, Home and End keys. The menu's navigation took them from it: Home and End no longer moved the caret, and ArrowDown or ArrowUp left the field for the first or last item, so a <select> in the list could not be operated with the arrows. Escape still closes the menu from inside a field, and a checkbox, which has no use for the arrows, still steps on to the items.

  • 98dc4e6: A click on the <label> of a form control inside a Menu's or ContextMenu's list keeps the menu open, as one on the control already did. The click lands on the label, or the text inside it, before the browser passes it on to the control, and the check for a form control looked at that element's tag name alone, so the docs' own Forms example closed the menu when "Remember me" was ticked. A click is now used in place when it lands in an input, select, option, textarea, label or editable element inside the list, or on a form there itself: a button in that form still closes the menu. A menu that sits inside a page's form still closes on an item.

  • 5def6c9: A Menu or ContextMenu closes on a click outside it on a react-aria control too: a Tab, a Checkbox, a Select or Combobox trigger, a calendar cell, a Table or DataGrid row. react-aria's press handling stops the pointerdown and click of the element it is on, and the menus heard outside presses on window once they had bubbled there, so the menu stayed open beside the control just used. They now see those events in the capture phase too, and handle one that never bubbles up once its dispatch is over.

  • 65456a5: Choosing a Menu item with the mouse no longer leaves a focus ring on the MenuToggle. Focus still returns to the toggle, so it doesn't fall to the page from the closed list, but without the ring, which comes back with the next key press. A choice made with Enter or Space, and Escape, return focus with the ring as before. The browser decides whether focus moved by script shows a ring from what was focused before, so the ring used to appear only sometimes.

  • e943df1: A Menu no longer calls onHide and onHidden (or onShow and onShown with defaultVisible) when it mounts under React.StrictMode. StrictMode runs mount effects twice, and the second run reported the menu's initial state as a change, so every menu on a page in development sent a spurious hide on load. The four callbacks now fire only when the menu opens or closes.

  • cf391e5: A disabled MenuToggle never opens its menu. disabled reached only the rendered element, and react-aria's trigger opened on a press and on Enter, Space and the arrow keys all the same. A NavLink or <a> root (the navbar pattern), or the default Button with href, opened on a click as well, and the default <button> on a pointerdown. The toggle now passes disabled to react-aria too. A click on a disabled <a> toggle runs no onClick either.

  • a440a9b: Select, FileInput, ColorInput and RangeInput keep their visible label in the accessible name when given an aria-labelledby of their own, as TextInput does: <Select label="Country" aria-labelledby="extra" /> is named "Country Extra". The caller's aria-labelledby outranked the <label for>, and the name became "Extra". The label now carries an id, and the control is labelled by it and the caller's ids together; without an aria-labelledby of the caller's, the control is labelled by the <label for> as before.

  • 579e9b2: An Alert, Modal or Drawer open inside another dialog closes when that dialog closes (#69), as a Popover or Tooltip in it already did. Left open, it stayed in the top layer under a dialog no longer displayed: invisible, still holding the scroll lock, and, opened as a modal, with the rest of the page inert, where the default keyboard={false} of an Alert left Firefox and Safari users no way out. It now gets a close request (onClose, onVisibleChange(false)) and hides at once, with onHidden.

  • 4ed5734: OtpInput: Backspace on a filled box clears that box in place, as otp-input.mdx says, and a key other than a digit does nothing. Both used to rebuild the boxes from the code as one string with no gaps, so every later digit moved one box left: correcting the third digit of 123456 gave 1 2 4 5 6 _. The boxes now keep what was typed in each, and the digits after a box cleared inside the code can still be focused to correct them. onChange still reports the digits as one string, 12456 for 1 2 _ 4 5 6, and a code that comes from elsewhere (a controlled value that differs from it, a form reset) fills the boxes from the first. A non-digit key no longer calls onChange with the unchanged code.

  • 278a571: OtpInput takes form (#91), the id of a form elsewhere on the page: the boxes and the hidden name input join it, a submit of it carries the code and a reset of it puts the code back. The other fields already took it; this one typed form as an error and, cast through, put it on the group's <div>.

  • 22d9307: A Placeholder with src and no alt renders alt="", a decorative image. It took the generated graphic's label as alt text, so <Placeholder src="/hero.jpg" width={100} height={100} /> was announced as "Placeholder: 100x100", a graphic that isn't shown. Pass alt to describe the real image.

  • c00402f: A Popover with a title and an aria-label of the caller's is named by the aria-label, as a Modal or Drawer is. The panel kept the title's aria-labelledby beside it, which outranks aria-label, so the caller's name was ignored. The title still shows.

  • 5d3d6ee: Popover and Tooltip panels are as tall as their content (#71). react-aria wrote the room it found beside the trigger as an inline max-height on the panel, which nothing scrolled: a popover taller than the room, a form in a popover on a short viewport, overflowed its own border, and a tooltip placed before it had a size kept max-height: 0px. The menus keep the room and scroll inside it since 0.5.1; a popover's arrow sits at its edge, which a scrolling panel would clip, so these release it, as chassis-css's own popovers have no cap.

  • 6762a96: RadioGroup takes value={null} and defaultValue={null} (#94): null clears a controlled selection, as it does for Combobox, Autocomplete and the date and time fields. The props were typed string, and undefined, the only value the type allowed for "none", makes react-stately treat the group as uncontrolled and keep the previous choice, so a form reset in TypeScript had no legal way to unselect a controlled group.

  • 839bbb4: Table, DataGrid and Tree with selectionMode="single" now hold one selected key at most. A selection of several, kept from selectionMode="multiple" or passed with "single", stayed selected in full and was never reported; the first key now stays, and onSelectionChange reports the trimmed selection once.

  • 106dad1: A stacked MenuSubmenu is no longer clipped by the list it is in above the sm breakpoint, a regression of 0.5.1. Since then every Menu list kept the room react-aria finds beside its trigger as an inline max-height and scrolled inside it; a stacked panel renders inside its parent list, so that list cut it off and scrolled instead of letting it sit beside its trigger. A list that holds a stacked submenu now keeps the theme's --cx-menu-max-height only above that breakpoint, as before 0.5.1. Below it the panel opens in the list's flow, and the list still keeps the room and scrolls.

  • 1038e04: <Switch type="radio"> renders a checkbox with role="switch", which ARIA allows, instead of a radio input with that role, which failed axe's aria-allowed-role. It still behaves as one of a radio group: switches of the same name have one on at a time, a click leaves the one that is on, required asks for one of the group, and the form submits the value of the one that is on.

  • 5c9e548: A Table row listed in disabledKeys is skipped by the arrow keys and carries aria-disabled="true", as disabledKeys promises and as DataGrid already did. Under react-stately's default disabledBehavior the row could only not be selected: it still took focus from the keyboard, and nothing told a screen reader it was disabled.

  • 32943b7: <Toast animation={false}> appears at once (#108). Without fade there is no transition to run, but the toast still went through the showing phase for the 50 ms fallback wait, and chassis-css paints .toast.showing at opacity 0 whether or not .fade is there: the toast was invisible for a tick and then popped in. It hides at once too, and onClose fires then.

  • 9a34213: Closing one of several toasts or notifications with the mouse no longer keeps the rest on screen. The click focused the close button, and when the toast went the region handed focus on to the next toast, whose autohide paused for as long as it held it: it stayed until the user clicked somewhere else. As in react-aria's own region, a toast closed with the pointer now gives focus back to the element that had it before it entered the region, and the others hide after their delay. Closed from the keyboard, a toast still hands focus to the next one.

  • b865a30: A Toaster or NotificationStack given its own onFocus, onBlur, onKeyDown or pointer handlers keeps the region's: both run. The caller's props were spread after react-aria's and the region's, and replaced them, so with an onFocus of the caller's, closing a toast from the keyboard dropped focus to <body> instead of handing it to the next toast.

  • 76ba0b6: A Tree keeps its tab stop when a controlled expandedKeys collapses the row above the focused one (a "Collapse all" button, for example). react-aria-components kept the hidden row as the tree's focused key, so no rendered row could be tabbed to: Tab no longer entered the tree until a row was clicked, and when the hidden row had the focus, it fell to <body>. The collapsed row now takes the focused key, as it does when its own chevron collapses it, and the focus too when the hidden row had it.