Astryx v0.7.9
Astryx 0.7.9 — all @astryxdesign/* packages ship at this version.
OLD_VERSION=0.7.8 # replace with the Core version your project uses today
npm install @astryxdesign/core@0.7.9 @astryxdesign/cli@0.7.9 # plus every other @astryxdesign/* package you use, at 0.7.9
npx @astryxdesign/cli upgrade --from "$OLD_VERSION" --apply --path .
astryx discoverlists the experimental NavigationStack and NavigationScreen components from@astryxdesign/lab.
astryx discoverlists the experimental CircleCheckIndicator component from@astryxdesign/lab.astryx theme buildkeeps a theme'scomponentIconsslot functions. The built module imports each slot function from the module the theme source imports it from, writes static slot values beside it as JSON, and reads back the same staticcomponentIconsview, so component icon slots resolve the same for the built theme as for the runtime theme. This includes slots inherited from a built, package, or family base. A slot function defined in the theme file still fails the build: move it to its own module and import it. In--familyoutput, a member supplies its own slot functions through a named import of its wholecomponentIconsmap. The module keeps each slot import as the theme source spells it (--icons-specifierrewrites only the icon registry import), so with--outin another directory, a module must answer to that specifier from the output directory.- A theme chooses which shared icon a component's own icon role uses, by slot, with
defineTheme({componentIcons}). The first slot iscode-block-copy: CodeBlock's copy button passes{copied}and, with no theme entry, drawscopy, thencheckwhile copied. A slot value is a shared icon name,nullto hide the icon, a partial{icon, variant, size}request, or a pure function of the slot's state that returns one; a field the request leaves out keeps the component's own value. For the copy button,nullhides only the glyph.theme.componentIconsreads back the static values.ComponentIconSlotMapand the slot value types are exported from@astryxdesign/core/Icon.astryx theme buildcarries static slot values into built themes and fails on a slot function, andastryx componentlists a component's slots. createPanGesture(element, {axis?, onPan})andusePanGesture(ref, {axis?, onPan})report a drag on an element as values:onPangets{state, translation, velocity}as the drag begins, changes, ends at the release, or is cancelled, and a finger, a mouse and a pen all drive it. A mouse or pen drag that starts on selectable text selects it instead; give text the drag should moveuser-select: none.axislimits the drag to'x'or'y'; a drag along the other axis is not reported.translationcounts from where the drag began, so an element that follows it does not jump, andvelocityatendedis the release velocity in pixels per second. The drag claims its touch at UIKit's 10 pt and leaves presses and long presses alone.destroy()removes every listener; the hook creates one pan for the element behindrefand destroys it at unmount.- Motion: a theme's
temponow times the springs of touch motion as well as its CSS durations. Each spring keeps its shape and takestempotimes as long, and so does the animated-layout glide. Finger tracking and busy loops stay as measured. A theme with only a duration scale keeps its springs at tempo 1, even inside a theme with a tempo. The springs don't followcharacteryet. The motion doc says so.
astryx docs <namespace> <section>loads the project once instead of once for every guide below the namespace, so on a namespace with many guides, such asastryx docs authoring <section>, the read itself takes tens of milliseconds instead of hundreds. The answer is unchanged.- A run that takes over a crashed run's release-cache lock now also recognizes the npm install that run left behind by its original command line, as npm shows it before renaming its process, when Node's own file is not named
node, such asnodejsornode-linux-x64. - The lexical ranker behind
astryx build's search ranks the candidates search gathers, reused the same way. A later query in the same folder, for the sametype, gathers again only when an input changed. In a long-running caller, a build that follows a build or a search no longer reads every component, hook, doc, template and theme again. A search that follows a build no longer reads them again either. Results are unchanged. - Under the Neutral theme, a selected row, a calendar range and the other selected fills on Neutral's surfaces are visible again, and highlighted code lines are stronger.
--color-accent-mutedmatched the surface in dark mode and barely differed from the hover tint in light mode; it is now#d4d4d4in light and#474747in dark. In dark mode a selected Item, TreeListItem or checked CheckboxListItem row, a numbered Citation and a FileInput dropzone lift their secondary text so it stays readable on the fill; a dropzone's hint is slightly lighter at rest too. Neutral's code comments, operators and punctuation are slightly less muted so they stay readable on a highlighted line. The CLI's Neutral theme template carries the same values. - The stock theme's dark-mode accent (
--color-accent) is now#0171E3instead of#2694FE, so white text on an accent fill, such as a primary Button's label at rest, meets 4.5:1 (4.70:1, was 3.11:1). Hovered and pressed primary Buttons improve too (about 4.36:1 and 4.03:1, from under 3:1) but stay under 4.5:1. Light mode keeps#0064E0. Everything else painted with--color-accentin dark mode also darkens: accent fills, focus rings and accent icons. MetadataList's "Show more", Table's sort rank and DateRangeInput's applied preset now use--color-text-accent, so their text stays readable on dark surfaces. The theme template thatastryx theme templatewrites uses the same dark accent, and the generated color token docs list it. To keep the previous look, a theme can set'--color-accent': ['#0064E0', '#2694FE']. astryx swizzleno longer runsgh auth statusbefore it reports: the readygh issue createline appears whenghis on your PATH, found without running it, so a copy never waits on a process start or on GitHub. The line now also appears whenghis installed but not logged in; running it, gh asks you to log in.astryx swizzlenow copies the private attribute owner modules a component imports, and the unexported style modules they need, beside the copy in the package's layout. A swizzledItem,CheckboxList,RadioListorTreeListno longer imports aprivateAttributes/*.stylexsubpath that@astryxdesign/coredoes not export. A second swizzle reuses an identical copy and refuses a changed one without--overwrite. The receipt'sfilesandfilesCopiedinclude the modules it copied.astryx theme buildreads the theme's icon import from the source it loaded: a theme saved in place while it builds now builds with its import, or fails and builds on the next save, instead of producing a module without the import.- theme: support
CustomTextTypesmodule augmentation for custom Text variants (#5013)
astryx searchandastryx buildread Core's exports map once per run, not once per component. With the other readers, a search now reads Core's package.json 10 times instead of 183. Results are unchanged.- When no loaded integration ships templates, the integration-issue check no longer imports every Core template doc.
astryx doctorin a project whose integrations only ship icon sets now imports none of them. - With no integration loaded, the integration-issue check no longer imports every Core template doc.
astryx discover, andastryx componentwithout--showcaseor--blocks, now import none of them. - The integration manifest writer loads the code-rewrite toolkit only when it patches a manifest.
astryx component,astryx theme listandastryx doctoroutside a project no longer load it, and a component lookup loads about 70 fewer modules. astryx searchandastryx buildlook for a component's doc only in the Core folders whose listing holds its name, not in every folder. A search makes about a quarter of the file checks it made before. Results are unchanged.- Outside a project whose config names
debugorintegrations,astryx --versionno longer loads the project, integration and code-rewrite modules. In a folder with no project config, it uses about three quarters less CPU. When no integration is installed,astryx docsandastryx hook --listload the project but not the code-rewrite modules. Output and exit codes are unchanged. astryx searchandastryx buildwork out a query word's forms once per entry they score, not once per word compared. Answers are unchanged.astryx searchandastryx buildcompute a typo's edit distance only up to the most edits that can count. A fuzzy match now computes far fewer table cells, with unchanged answers.- A standalone
astryx theme buildagainst the current Core loads Core's root entry only when the theme imports it. A theme that imports only@astryxdesign/core/themeno longer loads all of Core and its components. Family builds and builds against an older Core still load the root. Some builds hand the theme its Core: family builds, builds against an older Core, and themes whose folder has no Core. There, a theme that imports a Core rootrequire()cannot load, such as one that awaits at its top level, fails withERR_THEME_LOAD. The error names that root. astryx doctor's theme checks, family builds and builds against an older Core defer an installed Core's root entry. It loads only when the theme or one of its.cjsdependencies imports it. A theme that does not import Core's root no longer loads all of Core and its components for them.
- The calendars guide says
Calendar,DateInput,DateRangeInput, andDateTimeInputshow the Gregorian calendar unless the application chooses another one. (#9842) - Hook docs for
useCalendarDays, soastryx hook useCalendarDaysandastryx searchreturn it, and auseCalendarDays — Month Gridexample block: a compact month table built from the hook'sweeksanddayNames, with the previous and next months' days dimmed and a Week starts on control (Sunday or Monday) that reorders both the header and the grid. Copy it withastryx template useCalendarDaysHookUsage <path>. - Hook docs for
useIcon, soastryx hook useIconandastryx searchreturn it, and auseIcon — Status Rowexample block: a custom status row draws the success, warning and error icons withuseIcon, and under a Theme that overrides those icons the same row draws the theme's icons. Copy it withastryx template useIconHookUsage <path>. - Hook docs for
useLinkComponent, soastryx hook useLinkComponentandastryx searchreturn it, and auseLinkComponent — Article Cardsexample block: custom Article cards render their title link with the component the hook resolves. Inside a LinkProvider wired to a client-side router the title navigates without a page load,as="a"gives a native anchor even inside the provider, and outside the provider the title falls back to the next provider up or a native anchor. Copy it withastryx template useLinkComponentHookUsage <path>. - Hook docs for
useOverlay, soastryx hook useOverlayandastryx searchreturn it, and auseOverlay — Product Quick Viewexample block: a product Card spreads the hook'scontainerRefandcontainerPropsand renders the returned scrim once over its image. Hovering the card, or moving keyboard focus into it, reveals a Quick view button, and the image link before it lets Tab reach the button. Copy it withastryx template useOverlayHookUsage <path>. - Hook docs for
useTableSortableState, soastryx hook useTableSortableStateandastryx searchreturn it, and auseTableSortableState — Multi-Sort Ordersexample block: an orders Table with no sort code of its own, where clicking Customer and then Shift-clicking Total sorts by customer and then by total. Copy it withastryx template useTableSortableStateHookUsage <path>. - Internationalization gains a short Other calendars guide: choose a calendar on
InternationalizationProviderorCalendar, values stay Gregorian ISO dates, and most calendars needTemporal(loadtemporal-polyfill/full/globalwhere it is missing). A newastryx docs calendarsnamespace has the detail: the supported calendars, loadingTemporalon the server and in the browser, why the lunisolar calendars (chinese,dangi) can differ by a day between runtimes, and ISO 8601 week numbers. The guide andCalendar'scalendarprop docs say how to get identical server and browser markup for those calendars:installImplementation()fromtemporal-polyfill/full/shimon both sides, since thefull/globalimport keeps a nativeTemporal. - InfoTip: the component page,
astryx component InfoTip --showcase, andastryx component InfoTip --blocksnow have worked examples — a showcase metric label, a row of metric definitions, and read-only settings with permission notes. InfoTip's docs list its anatomy (trigger button, info icon, help surface) and say it follows the toggletip pattern: the button reportsaria-expanded="true"while the help is pinned, and a screen reader hears the help once when it pins. - The
usePanGesturepage opens with a draggable box in two forms side by side: the hook andcreatePanGesture, which takes any element, with the same StyleX styles and Astryx tokens. The playing card follows as the advanced example. ThecreatePanGesturepage points to that example, and theusePanGestureguidance warns against holding the drag offset in a large component's state. - A new
useClickableContainer — Clickable Project Cardexample block makes a project card clickable anywhere withuseClickableContainer: a click on the card follows its title link, while the Star button, the owner link and a text selection keep their own behavior, and Ctrl/Cmd+click or middle-click opens the project in a new tab. Copy it withastryx template useClickableContainerHookUsage <path>. - A new
useImageMode — Photo Captionexample block shows a caption over a photo's sky.useImageModesamples only the strip under the caption, andMediaThemegives the caption light or dark text from the result. A Beach photo switch swaps a night photo for a bright beach photo, and the caption keeps the dark fallback while the new photo loads. Copy it withastryx template useImageModeHookUsage <path>. - A new
useInputContainer — Search Field Wrapperexample block builds a custom search field with a leading search icon, padding and a trailing Clear button.useInputContainerputs focus in the input when the icon or the padding is clicked, and a click on Clear stays with Clear. Copy it withastryx template useInputContainerHookUsage <path>. - A
useMergedRefs — AutoGrowTextAreaexample block: an AutoGrowTextArea forwards the caller's ref and keeps its own ref to measure its height, and the merged ref attaches both to the same textarea, so it resizes itself while a parent button focuses it through the forwarded ref. Copy it withastryx template useMergedRefsHookUsage <path>. - A new
useScrollLock — Full-screen Mobile Menuexample block opens a custom full-screen menu from a Menu button and locks the page behind it withuseScrollLock, so the page neither scrolls nor shifts sideways, and closing the menu returns the reader to the same scroll position. Copy it withastryx template useScrollLockHookUsage <path>. - A new
useScrollOverflow — Filter Strip Edge Fadesexample block shows a row of active filters that scrolls sideways and fades only the edge whereuseScrollOverflowreports hidden filters. Adding or removing a filter updates the fades, and a right-to-left switch moves the start edge to the right. Copy it withastryx template useScrollOverflowHookUsage <path>.
- Internal: the search gather runs in its own listing-cache operation, so a caller that gathers candidates directly lists each folder about once, as
astryx searchalready does. The command's results and output are unchanged. Not exported from the package entry point. - Internal: the CLI's in-process test helper builds only the commands its arguments need, as a run of the bin does.
- The CLI's test setup rebuilds Core when its build is older than Core's sources or translations. It does so before any test starts, so a reused checkout no longer tests the CLI against an old Core build. A test waiting on another's build reads it only once that build is finished. One checkout's build no longer holds up another's tests. A run that cannot rebuild a stale Core, while an earlier run in the checkout uses it, stops with an error naming that run.
- The PowerSearch blocks fit a phone-sized screen. Each sizes its field through a grid column,
minmax(0, <width>): the old width where there is room, and the container's width where there is not. The Search with Table block's table scrolls inside its own frame on a narrow screen instead of widening the example. - Internal: the main searcher's test checks the count contract for each query, not a byte total over eight queries.
- The Tokenizer blocks and the table-inbox and work-item-detail page templates follow Tokenizer's
'adaptive'default, so on a compact touch screen they show the bottom sheet. TheirendContentandrenderTokenwork there as in the inline field;hasClearand the overflow modes apply to the inline field, and the Clear and Overflow blocks say so. The rule: docs examples followadaptive; the stories that are fixtures for inline-field geometry (Caret placement, Logical order, RTL end lane, Long token beside the end lane) keeppresentation="popover". The table-inbox and work-item-detail back arrows mirror in right-to-left.
Thanks to everyone who contributed to this release:
- @cixzhang
- @imdreamrunner
- @jibin7jose
- @MannXo
- @nynexman4464
- @Sainath-Reddy7
- @vjeux
- Under the touch presentation, ButtonGroup tracks a finger's or a pen's press across its buttons, like a menu. Press a button and slide across the others: the highlight follows, and the button you lift on runs. Lifting outside the group runs nothing. A mouse press is unchanged: it acts as a native button's, on the button it began on. With a finger, each button's target grows toward 44 px, up to half the gap to a neighbour, without the buttons growing. A menu trigger in the group keeps its own opening, and the keyboard is unchanged. Before, each button tracked only its own press, and sliding off it ran nothing. An enabled ButtonGroup root is position: relative under a coarse pointer, so absolutely positioned children anchor to the group. With a fine primary pointer, the group is unchanged.
- DateInput, DateTimeInput: the bottom sheet shows the calendar. With a
calendarsuch as'persian','hebrew', or'japanese', the bottom sheet shows months of the calendar, with its month names and the locale's digits, in panes of one fixed height, and its closed field writes the date in the calendar. Its month wheel lists the months of the selected calendar year, including leap months such as Adar I and Adar II, and a year change keeps the month or moves to its common counterpart (Adar I becomes Adar). Year labels carry the era, such as令和8, and a year in which the era changes is labeled with the era of the shown month, as on iOS: 2019 reads平成31in January and令和元in July, and the month heading follows the same date, so January 3, 1989 reads昭和64年1月. A provider locale with a-u-ca-keyword keeps the Gregorian sheet; setcalendarto choose a calendar. Values stay Gregorian ISO dates. (#9842) - Core gains an internal parser for dates typed in a calendar system. It reads dates typed in any
CalendarSystem, in any decimal digits, with month and era names, leap months such as Adar I, and Japanese eras such as令和元年, and returns a Gregorian date. A date the calendar does not have is rejected and never adjusted. It also writes the committed text and the format hint, and reads them back as the same date in each calendar's own locale, in English and Swedish, and in locales whose weekday names are also month or era names. No component reads typed text with it yet, and no public API changes.Calendaroutput does not change. useCalendarDaysanduseCalendarNavigationtake an optionalcalendar(defaultgregory) for custom month views in other calendar systems; they never read the provider's calendar.- DateInput, DateTimeInput: never show the native date control in a calendar it cannot show. With a
calendarother thangregoryoriso8601, such as'persian'or'hebrew',adaptive-native(the default), the deprecatednativePickervalues, andpresentation="native"use the calendar popover on a mouse and the bottom sheet on a touch screen, because the native date control shows only Gregorian dates. An explicitpresentation="native"also warns in development. A provider locale with a-u-ca-keyword keeps Gregorian dates and the native control; setcalendarto choose a calendar. Values stay Gregorian ISO dates. WithoutTemporal, calendars other thangregory,iso8601,buddhist,roc, andjapaneseshow Gregorian dates and the native control as before. (#9842) - DateRangeInput: show the range in the calendar. DateRangeInput takes a
calendarprop, such as'persian'or'hebrew', defaulting to theInternationalizationProvidercalendar (a provider locale with a-u-ca-keyword keeps Gregorian dates; setcalendarto choose one), and writes its trigger text and shows its popover calendar in it. Values stay Gregorian ISO dates. WithoutTemporal, calendars other thangregory,iso8601,buddhist,roc, andjapaneseshow Gregorian dates. (#9842) - InternationalizationProvider takes a
calendarprop (CalendarSystem, exported from@astryxdesign/core/i18n), such as'persian','hebrew','islamic-umalqura', or'buddhist', and exposes the resolved calendar onInternationalizationContextascalendar. A locale never selects the calendar:fa-IRresolves to Gregorian until you setcalendar="persian", and a provider locale with a-u-ca-keyword, such asth-TH-u-ca-buddhist, keeps Gregorian dates; setcalendarto choose one. An unknown identifier resolves to Gregorian and warns once in development. The Gregorian-aligned calendars (gregory,iso8601,buddhist,roc,japanese) work everywhere; the others needTemporal. The context keeps the requested calendar; whereTemporalis missing, a date component that resolves it shows Gregorian dates and warns once in development. Astryx date components keep showing Gregorian dates, and date values stay Gregorian ISO strings. - Calendar shows dates in other calendar systems.
Calendartakes acalendarprop (CalendarSystem), such as'persian','hebrew','islamic-umalqura', or'buddhist', and otherwise shows theInternationalizationProvidercalendar. A provider locale with a-u-ca-keyword, such asen-u-ca-buddhist, keeps Gregorian dates; setcalendarto choose one. Values do not change:value,min,max,focusDate, and every callback stay Gregorian ISO dates.DateInput,DateTimeInput, andDateRangeInputshow dates in the same calendar. In the selected calendar, the grid shows that calendar's months, including leap months such as Adar I and Adar II and the five- or six-day thirteenth month of the Ethiopic and Coptic calendars. Its heading, day names, and announcements use the calendar; Previous, Next, PageUp, and PageDown move one calendar month; and Shift+PageUp and Shift+PageDown move one calendar year to the same month. Day numbers use the locale's digits, and week numbers stay ISO 8601. The Gregorian-aligned calendars (gregory,iso8601,buddhist,roc,japanese) work everywhere. The others needTemporal; where the runtime lacks it, such as Safari or Node.js 24, load a polyfill liketemporal-polyfill/full/globalon the server and in the browser, or those calendars show Gregorian dates and warn once in development. A server-rendered app loadsTemporalon the server too, since a server without it renders Gregorian markup. - Built-in date formats follow the calendar of the
InternationalizationProvider. Withcalendar="persian", Timestamp'sdate,date_long,date_weekday, anddate_timeformats, the full date in its hover card and accessible name, and PowerSearch date values are written in that calendar, the same oneCalendarshows. A provider locale with a-u-ca-keyword keeps Gregorian dates; setcalendarto choose one.date_timekeeps its time of day.time, the relative formats,system_*, andunix_secondsdo not change, and without a provider calendar every output stays the same.formatSharedDatefrom@astryxdesign/core/utilsandformatFilterValuefrom@astryxdesign/core/PowerSearch/utilstake an optional trailingcalendarargument that defaults to'gregory'. Each of these dates names the month and day thatCalendarshows for the same day, as doesplainDateFormatwith acalendaroption other than'gregory'. A calendar that needsTemporalshows Gregorian dates where the runtime lacks it, as the date components do. - DateInput, DateTimeInput: type dates in other calendar systems. Both take a
calendarprop, such as'persian','hebrew','japanese', or'buddhist', and otherwise use theInternationalizationProvidercalendar. A provider locale with a-u-ca-keyword keeps Gregorian dates; setcalendarto choose one.value,onChange,min, andmaxstay Gregorian ISO dates. People type a date the way the locale writes it in that calendar, in any decimal digits, such as۱۴۰۵/۸/۱۱,1 Adar 5784, or令和元年5月1日; the committed text, the popover calendar, and thedate,date_long, anddate_weekdayformats use the calendar, andsystem_datestays the ISO date. Years are years of the calendar, so a Buddhist field reads2026as Buddhist year 2026: setminandmaxon Buddhist and Minguo calendar fields to catch a Gregorian year typed by mistake. A date the calendar does not have, such as 30 Esfand in a common Persian year, is named and never adjusted. An empty focused field without aplaceholdershows an example date that the field accepts, and its description names the calendar when it differs from theInternationalizationProvidercalendar. Calendars other thangregory,iso8601,buddhist,roc, andjapaneseneedTemporal, native or a polyfill you load; without it the field shows Gregorian dates. (#9842) Typing changes for Gregorian fields too: - ClickableCard and SelectableCard no longer show their pressed look when a finger press starts on a control inside them, such as an info button, a button, or a link. A mouse press is unchanged.
useClickableContainerreturnsonPointerDown; on a container that paints its press withusePressFeedback, spreading it drops a touch press that starts on a nested control. A mouse:activepress paint is unchanged. A container that does not spread it works as before. - Table: the row-expansion toggle resolves its glyph through the
table-row-expandcomponent icon slot with state{expanded}. With no theme entry it drawschevronRight, as before. The toggle still turns the glyph a quarter turn while expanded and mirrors it in right-to-left layouts, whatever the slot resolves. A theme maps it withdefineTheme({componentIcons: {'table-row-expand': …}});nullhides only the glyph, and the toggle keeps its name,aria-expanded, and its toggling. - Table: a sortable column's sort indicator resolves its glyph through the
table-sort-indicatorcomponent icon slot with state{direction}('ascending' | 'descending' | 'none'). With no theme entry it drawsarrowUp,arrowDown, orarrowsUpDown, as before. A theme maps it withdefineTheme({componentIcons: {'table-sort-indicator': …}});nullhides only the glyph, and the sort control keeps its label, its sorting, and the header'saria-sort. - Timestamp: the hover card's copy button resolves its glyph through the
timestamp-copycomponent icon slot with state{copied}. With no theme entry it drawscopy, thencheckwhile copied, as before. A theme maps it withdefineTheme({componentIcons: {'timestamp-copy': …}});nullhides only the glyph, and the button keeps its name, tooltip, and copy action. - A theme chooses which shared icon a component's own icon role uses, by slot, with
defineTheme({componentIcons}). The first slot iscode-block-copy: CodeBlock's copy button passes{copied}and, with no theme entry, drawscopy, thencheckwhile copied. A slot value is a shared icon name,nullto hide the icon, a partial{icon, variant, size}request, or a pure function of the slot's state that returns one; a field the request leaves out keeps the component's own value. For the copy button,nullhides only the glyph.theme.componentIconsreads back the static values.ComponentIconSlotMapand the slot value types are exported from@astryxdesign/core/Icon.astryx theme buildcarries static slot values into built themes and fails on a slot function, andastryx componentlists a component's slots. - Under a finger (a coarse primary pointer), an interactive
Listrow's hit target grows toward 44px where its neighbors leave room: the shared touch hit area measures each row and stops at half the gap to the nearest neighboring row or control. Rows look and lay out exactly as before, and a mouse is unchanged. Under a finger a list row's::afteris that invisible layer, so decorate a row with a child instead. AnItemthat stops delegating to a nested control now drops its pressable-container marker, so a clickable card around it gets a mouse click in the row's padding again.InfoTip's button takes taps through the same layer: it still grows toward 44px where its neighbors leave room, and waits for motion around it to end before it grows; it clears when it stops taking taps. A labelledCheckboxInputand aRadioListItemoption keep their band's insets through the same hook, so the insets clear when the control stops taking taps. - Under a finger, on every device, the finger that opened a DropdownMenu or MoreMenu now picks a row only after it has moved 10 px (
spec:AST-094FR29, FR43), so a menu that appears under a resting finger never picks the row beneath it, however long the finger rests. To keep the previous behavior, where that finger's lift picked once the press had lasted 300 ms, passpresentation="popover". A mouse and the publicuseMenuPresshook are unchanged. - Holding a NumberInput stepper with a mouse or a pen repeats the step: the first repeat comes after 0.5 s, and later ones come faster. The field shows each repeated value as its draft and calls
onChangeonce, when the hold ends by release or pointer cancel. A click still steps and commits at once. createPanGesture(element, {axis?, onPan})andusePanGesture(ref, {axis?, onPan})report a drag on an element as values:onPangets{state, translation, velocity}as the drag begins, changes, ends at the release, or is cancelled, and a finger, a mouse and a pen all drive it. A mouse or pen drag that starts on selectable text selects it instead; give text the drag should moveuser-select: none.axislimits the drag to'x'or'y'; a drag along the other axis is not reported.translationcounts from where the drag began, so an element that follows it does not jump, andvelocityatendedis the release velocity in pixels per second. The drag claims its touch at UIKit's 10 pt and leaves presses and long presses alone.destroy()removes every listener; the hook creates one pan for the element behindrefand destroys it at unmount.LayoutPanel,ResizeHandleandSideNavtakeuseResizable's output directly in theirresizableprop:const resizable = useResizable({…}); <LayoutPanel resizable={resizable}>…</LayoutPanel> <ResizeHandle resizable={resizable} />. The output'spropsstill works, andSideNav'strueand config object are unchanged. Given the output,SideNav's rail follows the region: its width and collapse state come from the region, its toggle calls the region'scollapseandexpand, and the region'sautoSaveIdsaves the width. Whencollapsiblealso sets a collapsed state, the region wins andSideNavwarns in development. Any other element can use the hook'ssize; the Resizable docs show one sized with a StyleX dynamic style.- Under a finger, a labelled CheckboxInput and each RadioList option get an invisible hit area. It grows toward 44 px, never makes the target smaller than the control’s own box, and stops at half the gap to a closer neighbour, so neighbouring targets never overlap. Nothing changes size. Under a finger a RadioListItem row’s
::afteris that hit area, so decorate a row with a child. A label-hidden checkbox inside another component’s row keeps that row’s target. With a mouse or trackpad nothing changes. - Motion: a theme's
temponow times the springs of touch motion as well as its CSS durations. Each spring keeps its shape and takestempotimes as long, and so does the animated-layout glide. Finger tracking and busy loops stay as measured. A theme with only a duration scale keeps its springs at tempo 1, even inside a theme with a tempo. The springs don't followcharacteryet. The motion doc says so.
- A backdrop press that closes a menu or popover open inside a
Dialog(purpose="info"), aDrawerwith its scrim, orCommandPalettenow closes only that menu or popover. The next backdrop press closes the dialog, as a second Escape does. Fixes #9920. - BottomSheet: a finger's pull up past the top stretches the sheet on every pull, on iOS's rubber band, with no 48 px cap, unless the caller sizes or styles the sheet (an inline block size, any
xstyleor class name), which keeps it. The sheet keeps enough of its surface below the screen that the page never shows. A pull on the content stretches it too once the content has no room left to scroll: at once for content that fits, and after a long list reaches its end. A mouse or a pen keeps its current stretch. - BottomSheet, Tokenizer, PowerSearch: a swipe that asks to close a sheet whose owner keeps it open — the Tokenizer and PowerSearch sheets ask before a changed draft is discarded — no longer leaves the page behind the sheet undimmed. The scrim the drag thinned returns with the sheet to its resting detent, so the page stays dimmed while the confirmation is open and after Keep editing; Discard still closes the sheet with its exit.
- BottomSheet: a standalone sheet (not a
BottomSheetSwitcherstep) removed while it is open (rendered only while open, or removed with the rest of a view) returns focus where a close would send it, while that place is still on the page: tofinalFocusRef, else the control that opened it, or into the sheet beneath. A whole stack of standalone sheets removed at once returns focus to the first sheet'sfinalFocusRefor opener. When none of these is left on the page, for example when the opener is removed with the sheet, focus stays where the browser leaves it. Focus the person had already moved out of a sheet, to the page body, stays where it is. A sheet that opens in the same update keeps focus, and removing a covered sheet under a top sheet that stays moves no focus. - The internal calendar date parser reads back the text it writes in more locales. It reads month names that start with a number, such as Latvian
1. adarsfor Adar I and1. rabīfor Rabi' I, and still reads1. adarsas day 1 of Adar when no other number can be the day. It reads a numeric pattern that uses two separators, such as the Hindi Buddhist format hint. In the Japanese and Minguo calendars, whose era changes between 1900 and 2100, a year without its era can read as the wrong date. There, a format hint that would otherwise leave out the era shows the narrow era when it keeps the locale's field order, such as Amharic1/1/8 R. Where every era width moves the fields, a date of an earlier era adds the era, such as ColognianBROC 1. Jan. 12, which no longer reads as a date in 1923. Calendars with one era in that range, such as Buddhist, Ethiopic or Hijri, write the same text as before. No public API changes. useChatNewMessages(whichChatLayoutuses),useSpeechRecognitionanduseChatDictationread their callbacks and other latest values as last committed. A render that React discards, as a suspended transition does, no longer has its callbacks called on the next resize or recognition event. Its audio context is no longer used either. After such a render,useChatNewMessagesalso no longer flags new messages while the list follows the bottom.- A checked, enabled, editable
CheckboxListItemrow now reflectsdata-selected="selected"on its row, the element that carries itsitemandlist-itemtargets, so a theme'sselectedrules for those targets restyle checked rows and the text on their fill. - On a touch screen, a CheckboxList row now answers a finger the same way wherever it lands. Under a finger, pressing a row's label painted the whole row while pressing its box painted only the box; under a coarse primary pointer the row is now the one touch press surface, so the row and its checkbox show the pressed state together whether the finger lands on the label, the description or the box. Where the primary pointer is fine (a mouse, a trackpad, or a pen on a desktop page), the box keeps its own press and hover, as before.
- A disabled
CheckboxInputno longer runs a caller'sonKeyDown,onKeyUp,onPointerDown,onMouseDownor any otheron*prop exceptonFocusandonBlur. Before, a disabled checkbox stayed focusable and clickable, so a key or pointer press on it still reached them; onlyonClickwas withheld. Now a disabledCheckboxInputruns no caller handler other thanonFocusandonBlur, as a disabledSlider,TextInputandTextAreado (spec:AST-085FR2). An enabledCheckboxInputis unchanged. No API changes. - When a
LayoutPanelbeside aResizeHandlecollapses with focus inside it, focus moves to the handle, and the collapsed panel is inert and hidden from assistive technology until it opens, instead of keeping focus, and a Tab stop, in a panel of no width. This holds for a panel that takes auseResizableregion'sresizableprops, whether the hook's own or a hand-built object, and for a panel at the region's size beside a handle given them. A collapse started from the handle or from outside the panel keeps focus where it is. ComplexSelectorwith a status and the defaultattachedplacement now shows the status icon on its trigger in place of the chevron, asSelectorandMultiSelectordo, so an error is no longer shown by color alone on the field.- The transient copy a layout motion holds of old content (an in-place swap's old content, the SideNav rail's and AppShell content's old state during a collapse or expand) carries on the CSS animations and transitions running in it from where they were. It no longer starts each CSS animation again (a press highlight lit again, an entrance that had played playing again, whatever its fill) or shows a transition's end at once.
- DateInput and DateTimeInput keep typed text that has not reported a date, without an error, while disabled or busy, and commit it on the next Enter or blur once editable. A new value still replaces it. Typed text whose date
onChangealready reported gives way to the field's value once the field is editable again, on the next Enter or blur if the field has focus. (#9842) - DateTimeInput: a press that never becomes a click no longer leaves its document click watchers behind when the field unmounts (#10155)
- On a phone or tablet (a coarse primary pointer), a tap or a drag that lands while a
Dialogor aDraweris still entering no longer closes it, runs one of its actions, moves focus or scrolls the page: it reaches nothing until the entrance has played, and the dialog or drawer finishes entering and stays open. Around aDrawerwithout a scrim only a tap or a drag on the panel is held, and the page stays live. Keys and assistive technology stay live during the entrance, the content stays in the accessibility tree, a pinch still zooms the page, and a mouse or a trackpad still clicks: at once when it was the last pointer used, otherwise once it moves. - On a phone or a tablet, a finger's tap or drag that lands while a DropdownMenu is still opening is now ignored, as on iOS: the menu finishes opening, stays open and keeps focus, and nothing under the tap reacts. The finger that opened the menu still picks a row. Keys and assistive technology stay live. A mouse, a finger on a touchscreen laptop and
presentation="popover"are unchanged. - DropdownMenu trigger tooltips stay closed after pointer selection and still reappear for returning keyboard focus.
- FileInput: a busy or disabled dropzone now shows the no-drop cursor for a dragged file instead of the copy cursor, and the browser no longer delivers a drop the zone would ignore.
- Focusing the clear or status button of a ghost or secondary
Selectorno longer also rings the whole trigger: each tab stop shows one focus ring. - Under a finger, a Switch or InfoTip measured while an ancestor is scaled keeps the touch insets of its unscaled layout. Before, that measurement kept the scaled frame's insets until the next resize.
- InputGroup: a focused input now stays above an unfocused input beside it, so the neighbour's border no longer covers part of its focus ring.
- A selected interactive
ItemorListItemrow now shows hover and mouse-press paint over its fill, and a highlightedItemrow shows mouse-press paint. Before, the fill hid both. A highlighted row shows no extra hover, because its fill is already the hover tone. - Item: the
itemtheme target now reflects the row's state asdata-highlighted,data-selected, anddata-disabled, so a theme can restyle a highlighted, selected, or disabled row. A row that composes Item reflects the states it passes to Item: for example, ListItem reflects selected and disabled, and DropdownMenuItem reflects disabled. The docs listclassNameandstyle, giverefthe root element type, and givexstyleits standard description. - Keep TabMenu arrow-key navigation from moving focus to the outer tab strip.
- A layer's trigger (Tooltip, Popover, HoverCard, DropdownMenu, ContextMenu and every other
useLayercaller) now carries its CSSanchor-nameonly while the layer is shown or its exit animation runs. A closed trigger is no anchor, so in WebKit's Linux ports a transform animation on an element that contains a closed trigger keeps running while the main thread is busy. A layer that fades out keeps its trigger's anchor name until the fade ends. If you write a layer'sanchorIdas an anchor name yourself, write it only while the layer is shown or its exit animation runs. - An anchored layer (a Popover, a menu, a Selector's or MultiSelector's panel, a Typeahead's list) that would run under the software keyboard, in Safari on iOS, now keeps its top and shrinks to end 10 px above the keyboard, its content scrolling, and grows back when the keyboard goes down; before, the keyboard covered its lower part and a button there could not be reached. A layer clear of the keyboard, or placed above its trigger, does not change.
- A
LayoutPanelwhose width is the number 0, such as one given a collapseduseResizableregion'sresizableprops orwidth={0}, now takes no space and shows none of its children. Before, its padding kept it about 32px wide and its children showed squeezed into that strip, one letter per line. This is visual only: keyboard and assistive-technology hiding for a collapsed panel is separate. - Link: the label now takes the link's own color, at rest and on hover, so on hover a
primary,secondary, oraccentLink tints its text with its underline and external-link icon. Before, only the underline and the icon took the hover tint. A theme override on thetexttarget (for exampletext[data-color="accent"]) no longer reaches Link labels: restyle Link labels through thelinktarget. A theme that styles thetexttarget's color now also keeps any Text withcolor="inherit"on its parent's color. - A list row's touch press release keeps fading while the page runs script, once the release has started.
ListItem,ItemandTreeListrows used to freeze mid-fade until the main thread was free, and every frame of the fade repainted the row; the release now runs on the compositor, with the same color as before. Script that runs in the tap's own handlers, before the next frame, still holds the row pressed until it ends. Under a finger, while a row is pressed and through its release, the press paints on the row's::beforeand the row is its own stacking context; at rest nothing of it applies, so a row's own::beforedecoration and its children's stacking are as they were. During a press the layer is the row's::before, and any::beforeproperty a caller sets, fromxstyle(whichItemcomposes after the layer) or from a stylesheet, wins over the layer's: a decoration with its own size and background jumps to the row's corner, and the row shows no pressed fill until the release ends. Decoration that should stay through a press belongs on::afteror a child element. With a mouse or trackpad the row is unchanged: no layer and no new stacking context. ChatMessageList,TooltipandHoverCard: the chat log and the span thatTooltipandHoverCardwrap around text-only children draw the theme's focus ring instead of the browser's default. A theme's--focus-outline-*tokens now reach them. The chat log's ring sits inside its box, so the area that scrolls it no longer cuts it off.- Markdown's GFM autolink option (
autolink: 'gfm') no longer slows to seconds on long text. The bare-email scan started over at every character of a long run, so its time grew with the square of the run: a 100,000-character word took over 10 seconds. It now starts from each@, and trailing)are peeled off a link in one pass, so a long run costs about as much as a plain parse. Linked emails and URLs are unchanged. - While a finger or a pen is held in a DropdownMenu, MoreMenu or ContextMenu, the row under it now takes the pressed overlay, and no other row keeps it, so the highlight can be seen following it (
spec:AST-094FR27, FR43). A held mouse, the keyboard and a hover keep the hover overlay. - On a phone or tablet (a coarse primary pointer), a tap or a drag during MobileNav's slide-in reaches nothing; keys and assistive technology stay live, and a mouse or a trackpad still clicks: at once when it was the last pointer used, otherwise once it moves.
- MobileNav: Tab now wraps inside the open navigation drawer. Tab from the last control moves to the first, and Shift+Tab from the first moves to the last, so keyboard users no longer move from inside the drawer to the browser's toolbar. The drawer uses the same focus-region module as Dialog, BottomSheet and Lightbox.
- Dialog, AlertDialog, CommandPalette, BottomSheet, Lightbox: Tab now wraps inside an open modal Dialog, AlertDialog, CommandPalette, modal BottomSheet or Lightbox. Tab from the last control moves to the first, and Shift+Tab from the first moves to the last, so keyboard and screen-reader users no longer land in the browser's toolbar from inside these modals. They now use the same focus-region module that Drawer and Popover use. When modals nest, the innermost one handles Tab. Non-modal sheets and inline dialogs do not trap focus. Fixes #8841.
- MultiSelector: in the open list, a repeated letter now moves to the next option that starts with it, so
t,t,tover Tea, Toast and Tofu walks Toast, Tofu, Tea instead of staying on Tea. A single letter typed after a pause searches from the option after the highlighted one, not from the top of the list, as in Selector. Space still toggles the highlighted option, so a typed Space does not extend the prefix as it does in Selector.useMultiComboboxgets the same behavior. (#9921) - With a finger, a press that starts on a control inside a list row with
onClickorhref, a TreeList row withonClickor children, or a clickable or link Token withonRemoveno longer makes the row or token look pressed, since it does not run the row's or token's action. A press on the row or token itself still looks pressed. Mouse presses are unchanged. - When an
OverflowList(or auseOverflowhost) resizes, it now shows the new visible set in the same frame. Before, the resize rendered after the frame painted, so one frame showed the old set at the new width: items spilled past the edge as the box shrank, or stayed collapsed as it grew. A resize now measures again in a microtask before paint, every list that one resize reports renders together, and a resize that changes nothing renders nothing. A resize whose new set can change the list's own height (a taller indicator appearing, a multi-row list filling a different number of rows or measuring a new row height, or, withbehavior="observeParent", the indicator appearing or disappearing, which changes how the list fills its parent, or a list sized from its items, such as a flex item beside a menu that can shrink) still renders one frame later, as before, because rendering it before paint would make the browser report a ResizeObserver loop error. A resize that folds the list before paint can make Chromium report "ResizeObserver loop completed with undelivered notifications" once in that frame when another observer watches an outer element (Storybook's preview observes<body>); no notification is lost, and an error monitor should ignore this message. Tests: a resize the observer reports now applies in a microtask, still before paint. A test that calls a mockedResizeObservercallback and asserts synchronously right after it should first await the update by wrapping the call inawait act(async () => { … }). - BottomSheet and Dialog: the page no longer jumps after a sheet or a dialog closes in Safari and on iOS. Returning focus to the control that opened the overlay asks the browser not to scroll. WebKit focuses the nearest focusable ancestor of a tapped button, such as an app's
tabindex="-1"main region, and records it as the opener; the focus return then scrolled the page to that region's top edge. The focus ring still follows the last input. - Pagination: the dots variant now exposes the APG radio group it already behaves as. Before, the dots were a
groupofbuttons named "Go to page N", witharia-current="page"on the current page. Now the container is aradiogroupnamed by the page-indicators label, and each dot is aradionamed "Page N" witharia-checkedon the current page and noaria-current. Keyboard behavior is unchanged. A press on a dot now chooses its page on release, so sliding off before release takes the press back. - PowerSearch: in the sheet presentation, a placeholder longer than the trigger stays on one line and ends in an ellipsis. Before, it ran under the result count and, on a narrow field, past the field's border.
- A press's release now fades on the theme's exit motion tokens,
--duration-exitand--ease-exit, instead of 200 ms, scaled by the theme'stempo, on a curve of its own. This covers every release that fades: a finger, mouse, pen or key press on a phone or tablet, and a pen press on a desktop, on buttons, cards, switches, checkboxes, list and tree rows, tabs, menu items and action groups. With the default tokens the release is 175 ms and holds its colour a little longer before it goes. A theme that changes--duration-exitor--ease-exitnow changes the release too; a theme'stempostill does, through--duration-exit. Under Reduce Motion a finger's release still fades for no more than 200 ms. A mouse press on a desktop is unchanged. - Under a finger, a pressable (a button, card, link, list row or any other surface) outside a scroll view now highlights in the frame the finger lands, as UIKit's controls do, instead of after a 150 ms delay. Inside a scroll view it still waits that delay, so a flick paints nothing, and a scroll that takes a held press cancels it with no fade. A page that scrolls counts as a scroll view, so on a page taller than the screen a press still waits; a control in a fixed bar, or on a page that fits, highlights at once. A mouse on a desktop keeps
:active. - Under reduced motion, a pressed Button no longer shrinks, and ClickableCard, SelectableCard and Thumbnail change their hover, border and selection colors at once on every pointer, whatever the theme's duration. A finger's press highlight still fades out on release, within 200 ms.
- LayoutContent, LayoutPanel and a scrollable
divorspanStackItem name their region while it scrolls. Before, a scrolling region was a nameless element that Chromium could make a Tab stop, so a screen reader announced nothing about it. It is now a group named by the caller's label, or by a translated "Content" (accessibility target: LayoutContent AR3, LayoutPanel AR3 and Stack AR2). A caller's role stays with only the caller's name, and Tab behavior stays the browser's. - Selector: after keyboard input, the active option draws the standard focus ring inside its edge, so people moving through the list with the keyboard can see where they are. The overlay fill alone measured about 1.09:1 against the listbox in light mode and 1.15:1 in dark, under the 3:1 a state indicator needs; with the stock theme the ring measures 5.39:1 (light) and 3.10:1 (dark). The ring takes its width, style and color from the
--focus-outline-*tokens, like every other focus ring. After pointer input the active option keeps the fill alone, as a focused DropdownMenu row does. This includes a mouse that moves onto another option after keyboard input, without a click. - A disabled
SideNavItemno longer runs a caller'sonKeyDown,onKeyUp,onPointerDown,onMouseDownor any otheron*prop exceptonFocusandonBlur. Before, a key other than Enter or Space on a disabled item still reachedonKeyDown, every key reachedonKeyUp, and pointer or mouse input that bypassespointer-events: none(programmatic or assistive-technology input) reachedonPointerDownandonMouseDown. This covers the plain, link, collapsed leaf and split-row forms; the flyout trigger of a collapsed parent item is not covered. Now a disabledSideNavItemruns no caller handler other thanonFocusandonBlur, as a disabledSlider,TextInputandTextAreado (spec:AST-085FR2). An enabledSideNavItemis unchanged. No API changes. - Spinner: a turning ring now wakes the main thread once every 100 turns instead of every turn. In a React app, Chromium recalculated style at the end of each turn, about seven times a second with one ring at a 525 ms turn. The ring looks and turns the same, and reduced motion still slows it to 3 seconds a turn.
- A standalone Spinner used to mount
role="status"already holding its name, so screen readers often said nothing when it appeared. It is now an indeterminate progress indicator (role="progressbar"with noaria-valuenow, named by its visible string label, itsaria-labelor a translated "Loading") that announces once through the shared polite channel when it appears after page load, reading its rendered text in order with stacked lines kept apart, or its accessible name (AST-020 FR15; AST-086 FR2, FR7, FR11–FR12; WCAG 2.2 4.1.3). BaseTypeahead, Typeahead and Tokenizer keep their search Spinner named and never live while they announce the search themselves, and app code silences a composed Spinner witharia-hidden. No app change is needed (AST-017 FR53): a Spinner given aroleother thanprogressbarstays silent, as before. The lab EmojiPicker and Schedule now hide their loading rings and announce their own busy start once, and the Comments history dialog announces it inside the dialog (AST-086 FR6, FR8, FR14); their loading words stay readable. Every Spinner's indicator changes fromstatustoprogressbar, so a test that queries it by role changes, including the hidden rings inside Button, CheckboxInput, CheckboxList, CheckIndicator, ChatMessageList, ChatToolCalls, CommandPaletteInput, ComplexSelector, DateInput, DateRangeInput, DateTimeInput, DropdownMenu's sub-menus, FileInput, MultiSelector, Selector, Switch, TextArea, TextInput, Thumbnail and TimeInput, and lab's EmojiPicker, Comments and Schedule. - The stock theme's dark-mode accent (
--color-accent) is now#0171E3instead of#2694FE, so white text on an accent fill, such as a primary Button's label at rest, meets 4.5:1 (4.70:1, was 3.11:1). Hovered and pressed primary Buttons improve too (about 4.36:1 and 4.03:1, from under 3:1) but stay under 4.5:1. Light mode keeps#0064E0. Everything else painted with--color-accentin dark mode also darkens: accent fills, focus rings and accent icons. MetadataList's "Show more", Table's sort rank and DateRangeInput's applied preset now use--color-text-accent, so their text stays readable on dark surfaces. The theme template thatastryx theme templatewrites uses the same dark accent, and the generated color token docs list it. To keep the previous look, a theme can set'--color-accent': ['#0064E0', '#2694FE']. - A disabled
Tabno longer runs a caller'sonPointerDown,onMouseDown,onKeyDownor any otheron*prop exceptonFocusandonBlur. Before, a press or key on a disabled tab still reached them, for both the button and the link form; onlyonClickwas withheld. Now a disabledTabruns no caller handler other thanonFocusandonBlur, as a disabledSlider,TextInputandTextAreado (spec:AST-085FR2). An enabledTabis unchanged. No API changes. - A toast plays its entrance once. When a
Dialog, a modalDraweror a bottom sheet with a scrim opens or closes while toasts show, the viewport moves into it or back out, and a toast already on screen now stays where it is instead of sliding in again. - Toast: without a LayerProvider, a toast dismissed before the fallback viewport mounts no longer shows, and its
onHideruns once with'manual'. - Tokenizer: in the sheet presentation,
endContentrenders at the field's inline end andrenderTokenrenders the field's tokens, as in the inline field, so the same props work on a phone under the'adaptive'default. Their own controls act without opening the sheet: an end-content button runs its handler, and a custom token's remove control commitsonChangeat once and moves focus to the trigger. A tap on a token, the placeholder or the field's empty space still opens the sheet. Before, the sheet dropped both props with a development warning.renderItem,hasClear(the sheet has its own Clear all),hasEntriesOnFocus,menuWidth,tokenOverflowBehaviorandhasAutoFocusstill apply to the inline field only. - A disabled
TopNavItemno longer runs a caller'sonPointerDown,onMouseDown,onKeyDownor any otheron*prop exceptonFocusandonBlur. Before, a key pressed while script had focused a disabled item, or an event dispatched to it, still reached them, in both the default and the drawer form; onlyonClickwas withheld. Now a disabledTopNavItemruns no caller handler other thanonFocusandonBlur, as a disabledSlider,TextInputandTextAreado (spec:AST-085FR2). An enabledTopNavItemis unchanged. No API changes. - DateInput: under a finger, every row in view of the bottom-sheet picker's columns meets WCAG AA contrast; their edge fade is shallower.
- Typeahead, Tokenizer: picking a result now calls
onChangeQuery(''). The pick empties the input but never reported it, so an app that mirrored the query kept the text typed before the pick. A pick made while the query was already empty reports nothing, as nothing changed. (#8929) useMediaQuerynow shares one listener per query among all its callers, so a long list of components that ask the same query adds one listener, not one per component. Every caller still reads the same answer. It also listens on Safari before 14, which has only the older listener pair, where it threw before.- Gestures: a late or merged trackpad wheel that keeps the glide falling stays in the flick. When the next wheel grows instead, the new gesture keeps the late wheel's travel. A mouse wheel's notches keep their rules.
- Under a finger, the touch-inset pass of a Switch or InfoTip resolves a control's style only when a transform scales it.
- LayerProvider no longer carries the toast viewport. An app that imports
LayerProviderfrom@astryxdesign/core/Layerand never imports Toast loads about 45 KB (46,161 bytes) less JavaScript (gzip). Toast registers its viewport when it is imported, and LayerProvider then renders it after the app. Without a Toast import, LayerProvider renders no empty viewport. A toast shown before the viewport mounts waits, then shows. On the server, LayerProvider renders no viewport; it mounts after hydration. - A single-line
OverflowListno longer renders again after mount only to record an item height it never shows: it does not track the row count and item height that only its multi-row form (maxRowsabove 1) renders from. One whose items all fit now renders once on mount; one that collapses items still renders again for the new visible count. The publicuseOverflowhook is unchanged and still reports both on every path.
- A BottomSheet source comment names the pen beside the mouse for the resisted stretch past fully open. Nothing a consumer imports or renders changes.
astryx hook --listgroups the motion hooks under one category, "Motion & gestures":useAnimatedLayout,useAnimator,useEntryAnimation,usePanGestureandusePressFeedback; the docs ofcreateAnimatorandcreatePanGesturecarry the same category. The "Animation" group goes away.useLongPress,useMenuPressand the other interaction hooks stay under "Interaction".- Hook docs for
useCalendarDays, soastryx hook useCalendarDaysandastryx searchreturn it, and auseCalendarDays — Month Gridexample block: a compact month table built from the hook'sweeksanddayNames, with the previous and next months' days dimmed and a Week starts on control (Sunday or Monday) that reorders both the header and the grid. Copy it withastryx template useCalendarDaysHookUsage <path>. - Hook docs for
useIcon, soastryx hook useIconandastryx searchreturn it, and auseIcon — Status Rowexample block: a custom status row draws the success, warning and error icons withuseIcon, and under a Theme that overrides those icons the same row draws the theme's icons. Copy it withastryx template useIconHookUsage <path>. - Hook docs for
useLinkComponent, soastryx hook useLinkComponentandastryx searchreturn it, and auseLinkComponent — Article Cardsexample block: custom Article cards render their title link with the component the hook resolves. Inside a LinkProvider wired to a client-side router the title navigates without a page load,as="a"gives a native anchor even inside the provider, and outside the provider the title falls back to the next provider up or a native anchor. Copy it withastryx template useLinkComponentHookUsage <path>. - Hook docs for
useOverlay, soastryx hook useOverlayandastryx searchreturn it, and auseOverlay — Product Quick Viewexample block: a product Card spreads the hook'scontainerRefandcontainerPropsand renders the returned scrim once over its image. Hovering the card, or moving keyboard focus into it, reveals a Quick view button, and the image link before it lets Tab reach the button. Copy it withastryx template useOverlayHookUsage <path>. - Hook docs for
useTableSortableState, soastryx hook useTableSortableStateandastryx searchreturn it, and auseTableSortableState — Multi-Sort Ordersexample block: an orders Table with no sort code of its own, where clicking Customer and then Shift-clicking Total sorts by customer and then by total. Copy it withastryx template useTableSortableStateHookUsage <path>. - Internationalization gains a short Other calendars guide: choose a calendar on
InternationalizationProviderorCalendar, values stay Gregorian ISO dates, and most calendars needTemporal(loadtemporal-polyfill/full/globalwhere it is missing). A newastryx docs calendarsnamespace has the detail: the supported calendars, loadingTemporalon the server and in the browser, why the lunisolar calendars (chinese,dangi) can differ by a day between runtimes, and ISO 8601 week numbers. The guide andCalendar'scalendarprop docs say how to get identical server and browser markup for those calendars:installImplementation()fromtemporal-polyfill/full/shimon both sides, since thefull/globalimport keeps a nativeTemporal. - InfoTip: the component page,
astryx component InfoTip --showcase, andastryx component InfoTip --blocksnow have worked examples — a showcase metric label, a row of metric definitions, and read-only settings with permission notes. InfoTip's docs list its anatomy (trigger button, info icon, help surface) and say it follows the toggletip pattern: the button reportsaria-expanded="true"while the help is pinned, and a screen reader hears the help once when it pins. - InputGroup: the docs no longer say
statuspaints the group border or that the group shares one focus ring.statusshows its message below the group and reflects its type asdata-status; a focused member stays above its neighbours.isOptionalnames the "Optional" indicator it shows. - InputGroupText: the docs now say when to use a text addon and what it is not: short fixed text or a decorative icon, read as plain text and not part of the input's accessible name. They document
ref, givexstyleits standard description, and the code example no longer gives the group and its input the same label, which made a screen reader read the name twice. - Long comment sentences in some menu press sources and tests are split into shorter ones, and DropdownMenu's
presentationdescription no longer says "as before". Nothing a consumer imports or renders changes. - The
usePanGesturepage opens with a draggable box in two forms side by side: the hook andcreatePanGesture, which takes any element, with the same StyleX styles and Astryx tokens. The playing card follows as the advanced example. ThecreatePanGesturepage points to that example, and theusePanGestureguidance warns against holding the drag offset in a large component's state.
-
Internal: the undocumented
@astryxdesign/core/internal/calendar-systementry gives Lab's Schedule the calendar that Core's date components resolve, and the helpers that name its months, days, and date ranges asCalendardoes. Internal entries are outside the public API and its compatibility promise. Lab's Schedule follows that calendar. Without a provider calendar, the accessible label of a timed event that spans days now writes its dates in the Gregorian calendar; before, the dates followed the locale's default calendar, such as the Persian calendar underfa-IR. A locale never selects the calendar. -
onChangefires while typing only once the date is complete, and once for each date, so partial input such as12/0or12/01/202no longer reports wrong dates. A two-digit year, such as12/01/90, or a date without a year commits on Enter or blur, and is read in the locale's field order. A one-digit or three-digit year, such as1/5/2or12/01/202, is not a date: Enter or blur keeps it and shows it as invalid.parseDateInputstill reads, on Enter or blur, text the field cannot otherwise read and a date the locale's field order does not have, such as13/5/2026inen-US. -
A typed date outside
min,max, ordateConstraintsstays in the field with an error that names the allowed range, or says the date is not available when only adateConstraintsrule rejects it, instead of reverting on blur; the error shows even when you setstatus. Unreadable text, a date that does not exist, and an out-of-range date each announce their own message, once.Fixes #8958.
-
Internal: Core's internal touch entry also names, for Lab, the screen-edge pan recognizer, the spring solver, the motion resolver, UIKit's navigation table and four navigation springs, and the focus helpers that follow the last input. Core's English catalog gains
@astryx.navigationStack.back. Apps import nothing new. -
Internal: Core's internal motion engine factory is renamed
createMotionEngine(fromcreateMotionAnimator), with its typesMotionEngineandMotionEngineOptions, at its source and in every internal caller. Core's internal touch entry also re-exports, by name, the UIKit gesture values, the right-to-left check, and the touch-presentation hook and query that Lab components read. Lab'suseMotionEnginehook wraps the engine, and Lab's refresh control uses it. Apps import nothing new. -
Internal: Core's internal touch entry re-exports what Lab's useRefreshControl reads. That is the pan recognizer, its hook and the gesture types, and the motion animator. It is also the motion math, the map of a companion curve, the UIKit scroll table of its pull, and the motion-role parsers. None is in the public entry. Apps import nothing new.
-
Internal: one input hold for every layer (
Layer/inputHold). While a layer presents, a finger's or a pen's touch, press or click reaches nothing: anywhere around a modal layer, and only on the layer itself around a non-modal one, whose page stays live. A mouse passes once it moves, at once when the page last heard a mouse (keys and assistive technology still act, and a pinch still zooms the page). While it dismisses, every press and click reaches nothing, and the layer's content is inert. The gesture host asks the hold about each touch (holdsTouch), andisInputShieldlets a hit test see past a hold's shield. Not exported from the package entry point; Dialog's and Drawer's entrance holds and MobileNav's drawer use it. -
Internal: each spring role, and the navigation, paging and picker motion tables, register themselves when their module loads, so a spring role plays at the theme's
tempowherever it plays, with no register call. A page that plays no spring ships no springs registry. A module that imports single spring rows carries only those rows, unless it also reads the wholespringstable or callsregisterSpringsTable(). Not exported from the package entry point. -
Internal:
usePanGesturecopiesonPaninto its ref at commit, not during render, before the hook's first release. A render React discards, as a suspended transition does, no longer hands the next drag to itsonPan. -
Internal: a pan gesture's begin threshold can be read from each press's first touch, so a mouse or a pen can begin a pan sooner than a finger. It is not exported from the package entry point, and nothing changes for existing callers.
-
Internal cleanups in SideNav and in how a resize handle finds its panel: a source comment that only restated the line after it goes, and one lookup function replaces two. No behavior change.
-
Internal: Spinner's rotor (the turning arc's keyframe, timing and timeline-origin pin) moves into one internal module that Lab's indeterminate CircularProgress also uses. Spinner does not change.
-
Internal: one shared reader times motion from the theme's duration and easing, as a computed transition resolves them. SegmentedControl's selection slide now reads its timing with it; its timing is unchanged,
calc()tokens included. -
Internal: DateInput and DateTimeInput keep their typed-entry state, rejection status, and commits in one shared hook instead of two copies. Typing, committing, and rejection messages are unchanged. Not exported from the package entry point. (#9842)
Thanks to everyone who contributed to this release:
- @aashish254
- @cixzhang
- @duytbp
- @harjothkhara
- @HelloOjasMutreja
- @imdreamrunner
- @korkt-kim
- @MannXo
- @nynexman4464
- @parinith-web
- @vjeux
- RichTextExtensionNode is constructible with no arguments, as a registered node class must be. A Yjs collaboration binding builds one instance of every registered node class, with no arguments, to read its default properties, so creating a binding over an editor with this package's default nodes threw
TypeError: Cannot read properties of undefined (reading 'display')before any document was loaded. Itsfactsnow default to the same empty values the class already reads for damaged stored state, so a no-argument node reports an empty inline extension instead of throwing. A node built with facts is unchanged.
Thanks to everyone who contributed to this release:
- @potatowagon
- Under the Neutral theme, a selected row, a calendar range and the other selected fills on Neutral's surfaces are visible again, and highlighted code lines are stronger.
--color-accent-mutedmatched the surface in dark mode and barely differed from the hover tint in light mode; it is now#d4d4d4in light and#474747in dark. In dark mode a selected Item, TreeListItem or checked CheckboxListItem row, a numbered Citation and a FileInput dropzone lift their secondary text so it stays readable on the fill; a dropzone's hint is slightly lighter at rest too. Neutral's code comments, operators and punctuation are slightly less muted so they stay readable on a highlighted line. The CLI's Neutral theme template carries the same values.
Thanks to everyone who contributed to this release:
- @cixzhang
Astryx v0.7.9
Astryx 0.7.9 — all @astryxdesign/* packages ship at this version.
OLD_VERSION=0.7.8 # replace with the Core version your project uses today
npm install @astryxdesign/core@0.7.9 @astryxdesign/cli@0.7.9 # plus every other @astryxdesign/* package you use, at 0.7.9
npx @astryxdesign/cli upgrade --from "$OLD_VERSION" --apply --path .
astryx discoverlists the experimental NavigationStack and NavigationScreen components from@astryxdesign/lab.
astryx discoverlists the experimental CircleCheckIndicator component from@astryxdesign/lab.astryx theme buildkeeps a theme'scomponentIconsslot functions. The built module imports each slot function from the module the theme source imports it from, writes static slot values beside it as JSON, and reads back the same staticcomponentIconsview, so component icon slots resolve the same for the built theme as for the runtime theme. This includes slots inherited from a built, package, or family base. A slot function defined in the theme file still fails the build: move it to its own module and import it. In--familyoutput, a member supplies its own slot functions through a named import of its wholecomponentIconsmap. The module keeps each slot import as the theme source spells it (--icons-specifierrewrites only the icon registry import), so with--outin another directory, a module must answer to that specifier from the output directory.- A theme chooses which shared icon a component's own icon role uses, by slot, with
defineTheme({componentIcons}). The first slot iscode-block-copy: CodeBlock's copy button passes{copied}and, with no theme entry, drawscopy, thencheckwhile copied. A slot value is a shared icon name,nullto hide the icon, a partial{icon, variant, size}request, or a pure function of the slot's state that returns one; a field the request leaves out keeps the component's own value. For the copy button,nullhides only the glyph.theme.componentIconsreads back the static values.ComponentIconSlotMapand the slot value types are exported from@astryxdesign/core/Icon.astryx theme buildcarries static slot values into built themes and fails on a slot function, andastryx componentlists a component's slots. createPanGesture(element, {axis?, onPan})andusePanGesture(ref, {axis?, onPan})report a drag on an element as values:onPangets{state, translation, velocity}as the drag begins, changes, ends at the release, or is cancelled, and a finger, a mouse and a pen all drive it. A mouse or pen drag that starts on selectable text selects it instead; give text the drag should moveuser-select: none.axislimits the drag to'x'or'y'; a drag along the other axis is not reported.translationcounts from where the drag began, so an element that follows it does not jump, andvelocityatendedis the release velocity in pixels per second. The drag claims its touch at UIKit's 10 pt and leaves presses and long presses alone.destroy()removes every listener; the hook creates one pan for the element behindrefand destroys it at unmount.- Motion: a theme's
temponow times the springs of touch motion as well as its CSS durations. Each spring keeps its shape and takestempotimes as long, and so does the animated-layout glide. Finger tracking and busy loops stay as measured. A theme with only a duration scale keeps its springs at tempo 1, even inside a theme with a tempo. The springs don't followcharacteryet. The motion doc says so.
astryx docs <namespace> <section>loads the project once instead of once for every guide below the namespace, so on a namespace with many guides, such asastryx docs authoring <section>, the read itself takes tens of milliseconds instead of hundreds. The answer is unchanged.- A run that takes over a crashed run's release-cache lock now also recognizes the npm install that run left behind by its original command line, as npm shows it before renaming its process, when Node's own file is not named
node, such asnodejsornode-linux-x64. - The lexical ranker behind
astryx build's search ranks the candidates search gathers, reused the same way. A later query in the same folder, for the sametype, gathers again only when an input changed. In a long-running caller, a build that follows a build or a search no longer reads every component, hook, doc, template and theme again. A search that follows a build no longer reads them again either. Results are unchanged. - Under the Neutral theme, a selected row, a calendar range and the other selected fills on Neutral's surfaces are visible again, and highlighted code lines are stronger.
--color-accent-mutedmatched the surface in dark mode and barely differed from the hover tint in light mode; it is now#d4d4d4in light and#474747in dark. In dark mode a selected Item, TreeListItem or checked CheckboxListItem row, a numbered Citation and a FileInput dropzone lift their secondary text so it stays readable on the fill; a dropzone's hint is slightly lighter at rest too. Neutral's code comments, operators and punctuation are slightly less muted so they stay readable on a highlighted line. The CLI's Neutral theme template carries the same values. - The stock theme's dark-mode accent (
--color-accent) is now#0171E3instead of#2694FE, so white text on an accent fill, such as a primary Button's label at rest, meets 4.5:1 (4.70:1, was 3.11:1). Hovered and pressed primary Buttons improve too (about 4.36:1 and 4.03:1, from under 3:1) but stay under 4.5:1. Light mode keeps#0064E0. Everything else painted with--color-accentin dark mode also darkens: accent fills, focus rings and accent icons. MetadataList's "Show more", Table's sort rank and DateRangeInput's applied preset now use--color-text-accent, so their text stays readable on dark surfaces. The theme template thatastryx theme templatewrites uses the same dark accent, and the generated color token docs list it. To keep the previous look, a theme can set'--color-accent': ['#0064E0', '#2694FE']. astryx swizzleno longer runsgh auth statusbefore it reports: the readygh issue createline appears whenghis on your PATH, found without running it, so a copy never waits on a process start or on GitHub. The line now also appears whenghis installed but not logged in; running it, gh asks you to log in.astryx swizzlenow copies the private attribute owner modules a component imports, and the unexported style modules they need, beside the copy in the package's layout. A swizzledItem,CheckboxList,RadioListorTreeListno longer imports aprivateAttributes/*.stylexsubpath that@astryxdesign/coredoes not export. A second swizzle reuses an identical copy and refuses a changed one without--overwrite. The receipt'sfilesandfilesCopiedinclude the modules it copied.astryx theme buildreads the theme's icon import from the source it loaded: a theme saved in place while it builds now builds with its import, or fails and builds on the next save, instead of producing a module without the import.- theme: support
CustomTextTypesmodule augmentation for custom Text variants (#5013)
astryx searchandastryx buildread Core's exports map once per run, not once per component. With the other readers, a search now reads Core's package.json 10 times instead of 183. Results are unchanged.- When no loaded integration ships templates, the integration-issue check no longer imports every Core template doc.
astryx doctorin a project whose integrations only ship icon sets now imports none of them. - With no integration loaded, the integration-issue check no longer imports every Core template doc.
astryx discover, andastryx componentwithout--showcaseor--blocks, now import none of them. - The integration manifest writer loads the code-rewrite toolkit only when it patches a manifest.
astryx component,astryx theme listandastryx doctoroutside a project no longer load it, and a component lookup loads about 70 fewer modules. astryx searchandastryx buildlook for a component's doc only in the Core folders whose listing holds its name, not in every folder. A search makes about a quarter of the file checks it made before. Results are unchanged.- Outside a project whose config names
debugorintegrations,astryx --versionno longer loads the project, integration and code-rewrite modules. In a folder with no project config, it uses about three quarters less CPU. When no integration is installed,astryx docsandastryx hook --listload the project but not the code-rewrite modules. Output and exit codes are unchanged. astryx searchandastryx buildwork out a query word's forms once per entry they score, not once per word compared. Answers are unchanged.astryx searchandastryx buildcompute a typo's edit distance only up to the most edits that can count. A fuzzy match now computes far fewer table cells, with unchanged answers.- A standalone
astryx theme buildagainst the current Core loads Core's root entry only when the theme imports it. A theme that imports only@astryxdesign/core/themeno longer loads all of Core and its components. Family builds and builds against an older Core still load the root. Some builds hand the theme its Core: family builds, builds against an older Core, and themes whose folder has no Core. There, a theme that imports a Core rootrequire()cannot load, such as one that awaits at its top level, fails withERR_THEME_LOAD. The error names that root. astryx doctor's theme checks, family builds and builds against an older Core defer an installed Core's root entry. It loads only when the theme or one of its.cjsdependencies imports it. A theme that does not import Core's root no longer loads all of Core and its components for them.
- The calendars guide says
Calendar,DateInput,DateRangeInput, andDateTimeInputshow the Gregorian calendar unless the application chooses another one. (#9842) - Hook docs for
useCalendarDays, soastryx hook useCalendarDaysandastryx searchreturn it, and auseCalendarDays — Month Gridexample block: a compact month table built from the hook'sweeksanddayNames, with the previous and next months' days dimmed and a Week starts on control (Sunday or Monday) that reorders both the header and the grid. Copy it withastryx template useCalendarDaysHookUsage <path>. - Hook docs for
useIcon, soastryx hook useIconandastryx searchreturn it, and auseIcon — Status Rowexample block: a custom status row draws the success, warning and error icons withuseIcon, and under a Theme that overrides those icons the same row draws the theme's icons. Copy it withastryx template useIconHookUsage <path>. - Hook docs for
useLinkComponent, soastryx hook useLinkComponentandastryx searchreturn it, and auseLinkComponent — Article Cardsexample block: custom Article cards render their title link with the component the hook resolves. Inside a LinkProvider wired to a client-side router the title navigates without a page load,as="a"gives a native anchor even inside the provider, and outside the provider the title falls back to the next provider up or a native anchor. Copy it withastryx template useLinkComponentHookUsage <path>. - Hook docs for
useOverlay, soastryx hook useOverlayandastryx searchreturn it, and auseOverlay — Product Quick Viewexample block: a product Card spreads the hook'scontainerRefandcontainerPropsand renders the returned scrim once over its image. Hovering the card, or moving keyboard focus into it, reveals a Quick view button, and the image link before it lets Tab reach the button. Copy it withastryx template useOverlayHookUsage <path>. - Hook docs for
useTableSortableState, soastryx hook useTableSortableStateandastryx searchreturn it, and auseTableSortableState — Multi-Sort Ordersexample block: an orders Table with no sort code of its own, where clicking Customer and then Shift-clicking Total sorts by customer and then by total. Copy it withastryx template useTableSortableStateHookUsage <path>. - Internationalization gains a short Other calendars guide: choose a calendar on
InternationalizationProviderorCalendar, values stay Gregorian ISO dates, and most calendars needTemporal(loadtemporal-polyfill/full/globalwhere it is missing). A newastryx docs calendarsnamespace has the detail: the supported calendars, loadingTemporalon the server and in the browser, why the lunisolar calendars (chinese,dangi) can differ by a day between runtimes, and ISO 8601 week numbers. The guide andCalendar'scalendarprop docs say how to get identical server and browser markup for those calendars:installImplementation()fromtemporal-polyfill/full/shimon both sides, since thefull/globalimport keeps a nativeTemporal. - InfoTip: the component page,
astryx component InfoTip --showcase, andastryx component InfoTip --blocksnow have worked examples — a showcase metric label, a row of metric definitions, and read-only settings with permission notes. InfoTip's docs list its anatomy (trigger button, info icon, help surface) and say it follows the toggletip pattern: the button reportsaria-expanded="true"while the help is pinned, and a screen reader hears the help once when it pins. - The
usePanGesturepage opens with a draggable box in two forms side by side: the hook andcreatePanGesture, which takes any element, with the same StyleX styles and Astryx tokens. The playing card follows as the advanced example. ThecreatePanGesturepage points to that example, and theusePanGestureguidance warns against holding the drag offset in a large component's state. - A new
useClickableContainer — Clickable Project Cardexample block makes a project card clickable anywhere withuseClickableContainer: a click on the card follows its title link, while the Star button, the owner link and a text selection keep their own behavior, and Ctrl/Cmd+click or middle-click opens the project in a new tab. Copy it withastryx template useClickableContainerHookUsage <path>. - A new
useImageMode — Photo Captionexample block shows a caption over a photo's sky.useImageModesamples only the strip under the caption, andMediaThemegives the caption light or dark text from the result. A Beach photo switch swaps a night photo for a bright beach photo, and the caption keeps the dark fallback while the new photo loads. Copy it withastryx template useImageModeHookUsage <path>. - A new
useInputContainer — Search Field Wrapperexample block builds a custom search field with a leading search icon, padding and a trailing Clear button.useInputContainerputs focus in the input when the icon or the padding is clicked, and a click on Clear stays with Clear. Copy it withastryx template useInputContainerHookUsage <path>. - A
useMergedRefs — AutoGrowTextAreaexample block: an AutoGrowTextArea forwards the caller's ref and keeps its own ref to measure its height, and the merged ref attaches both to the same textarea, so it resizes itself while a parent button focuses it through the forwarded ref. Copy it withastryx template useMergedRefsHookUsage <path>. - A new
useScrollLock — Full-screen Mobile Menuexample block opens a custom full-screen menu from a Menu button and locks the page behind it withuseScrollLock, so the page neither scrolls nor shifts sideways, and closing the menu returns the reader to the same scroll position. Copy it withastryx template useScrollLockHookUsage <path>. - A new
useScrollOverflow — Filter Strip Edge Fadesexample block shows a row of active filters that scrolls sideways and fades only the edge whereuseScrollOverflowreports hidden filters. Adding or removing a filter updates the fades, and a right-to-left switch moves the start edge to the right. Copy it withastryx template useScrollOverflowHookUsage <path>.
- Internal: the search gather runs in its own listing-cache operation, so a caller that gathers candidates directly lists each folder about once, as
astryx searchalready does. The command's results and output are unchanged. Not exported from the package entry point. - Internal: the CLI's in-process test helper builds only the commands its arguments need, as a run of the bin does.
- The CLI's test setup rebuilds Core when its build is older than Core's sources or translations. It does so before any test starts, so a reused checkout no longer tests the CLI against an old Core build. A test waiting on another's build reads it only once that build is finished. One checkout's build no longer holds up another's tests. A run that cannot rebuild a stale Core, while an earlier run in the checkout uses it, stops with an error naming that run.
- The PowerSearch blocks fit a phone-sized screen. Each sizes its field through a grid column,
minmax(0, <width>): the old width where there is room, and the container's width where there is not. The Search with Table block's table scrolls inside its own frame on a narrow screen instead of widening the example. - Internal: the main searcher's test checks the count contract for each query, not a byte total over eight queries.
- The Tokenizer blocks and the table-inbox and work-item-detail page templates follow Tokenizer's
'adaptive'default, so on a compact touch screen they show the bottom sheet. TheirendContentandrenderTokenwork there as in the inline field;hasClearand the overflow modes apply to the inline field, and the Clear and Overflow blocks say so. The rule: docs examples followadaptive; the stories that are fixtures for inline-field geometry (Caret placement, Logical order, RTL end lane, Long token beside the end lane) keeppresentation="popover". The table-inbox and work-item-detail back arrows mirror in right-to-left.
Thanks to everyone who contributed to this release:
- @cixzhang
- @imdreamrunner
- @jibin7jose
- @MannXo
- @nynexman4464
- @Sainath-Reddy7
- @vjeux
- Under the touch presentation, ButtonGroup tracks a finger's or a pen's press across its buttons, like a menu. Press a button and slide across the others: the highlight follows, and the button you lift on runs. Lifting outside the group runs nothing. A mouse press is unchanged: it acts as a native button's, on the button it began on. With a finger, each button's target grows toward 44 px, up to half the gap to a neighbour, without the buttons growing. A menu trigger in the group keeps its own opening, and the keyboard is unchanged. Before, each button tracked only its own press, and sliding off it ran nothing. An enabled ButtonGroup root is position: relative under a coarse pointer, so absolutely positioned children anchor to the group. With a fine primary pointer, the group is unchanged.
- DateInput, DateTimeInput: the bottom sheet shows the calendar. With a
calendarsuch as'persian','hebrew', or'japanese', the bottom sheet shows months of the calendar, with its month names and the locale's digits, in panes of one fixed height, and its closed field writes the date in the calendar. Its month wheel lists the months of the selected calendar year, including leap months such as Adar I and Adar II, and a year change keeps the month or moves to its common counterpart (Adar I becomes Adar). Year labels carry the era, such as令和8, and a year in which the era changes is labeled with the era of the shown month, as on iOS: 2019 reads平成31in January and令和元in July, and the month heading follows the same date, so January 3, 1989 reads昭和64年1月. A provider locale with a-u-ca-keyword keeps the Gregorian sheet; setcalendarto choose a calendar. Values stay Gregorian ISO dates. (#9842) - Core gains an internal parser for dates typed in a calendar system. It reads dates typed in any
CalendarSystem, in any decimal digits, with month and era names, leap months such as Adar I, and Japanese eras such as令和元年, and returns a Gregorian date. A date the calendar does not have is rejected and never adjusted. It also writes the committed text and the format hint, and reads them back as the same date in each calendar's own locale, in English and Swedish, and in locales whose weekday names are also month or era names. No component reads typed text with it yet, and no public API changes.Calendaroutput does not change. useCalendarDaysanduseCalendarNavigationtake an optionalcalendar(defaultgregory) for custom month views in other calendar systems; they never read the provider's calendar.- DateInput, DateTimeInput: never show the native date control in a calendar it cannot show. With a
calendarother thangregoryoriso8601, such as'persian'or'hebrew',adaptive-native(the default), the deprecatednativePickervalues, andpresentation="native"use the calendar popover on a mouse and the bottom sheet on a touch screen, because the native date control shows only Gregorian dates. An explicitpresentation="native"also warns in development. A provider locale with a-u-ca-keyword keeps Gregorian dates and the native control; setcalendarto choose a calendar. Values stay Gregorian ISO dates. WithoutTemporal, calendars other thangregory,iso8601,buddhist,roc, andjapaneseshow Gregorian dates and the native control as before. (#9842) - DateRangeInput: show the range in the calendar. DateRangeInput takes a
calendarprop, such as'persian'or'hebrew', defaulting to theInternationalizationProvidercalendar (a provider locale with a-u-ca-keyword keeps Gregorian dates; setcalendarto choose one), and writes its trigger text and shows its popover calendar in it. Values stay Gregorian ISO dates. WithoutTemporal, calendars other thangregory,iso8601,buddhist,roc, andjapaneseshow Gregorian dates. (#9842) - InternationalizationProvider takes a
calendarprop (CalendarSystem, exported from@astryxdesign/core/i18n), such as'persian','hebrew','islamic-umalqura', or'buddhist', and exposes the resolved calendar onInternationalizationContextascalendar. A locale never selects the calendar:fa-IRresolves to Gregorian until you setcalendar="persian", and a provider locale with a-u-ca-keyword, such asth-TH-u-ca-buddhist, keeps Gregorian dates; setcalendarto choose one. An unknown identifier resolves to Gregorian and warns once in development. The Gregorian-aligned calendars (gregory,iso8601,buddhist,roc,japanese) work everywhere; the others needTemporal. The context keeps the requested calendar; whereTemporalis missing, a date component that resolves it shows Gregorian dates and warns once in development. Astryx date components keep showing Gregorian dates, and date values stay Gregorian ISO strings. - Calendar shows dates in other calendar systems.
Calendartakes acalendarprop (CalendarSystem), such as'persian','hebrew','islamic-umalqura', or'buddhist', and otherwise shows theInternationalizationProvidercalendar. A provider locale with a-u-ca-keyword, such asen-u-ca-buddhist, keeps Gregorian dates; setcalendarto choose one. Values do not change:value,min,max,focusDate, and every callback stay Gregorian ISO dates.DateInput,DateTimeInput, andDateRangeInputshow dates in the same calendar. In the selected calendar, the grid shows that calendar's months, including leap months such as Adar I and Adar II and the five- or six-day thirteenth month of the Ethiopic and Coptic calendars. Its heading, day names, and announcements use the calendar; Previous, Next, PageUp, and PageDown move one calendar month; and Shift+PageUp and Shift+PageDown move one calendar year to the same month. Day numbers use the locale's digits, and week numbers stay ISO 8601. The Gregorian-aligned calendars (gregory,iso8601,buddhist,roc,japanese) work everywhere. The others needTemporal; where the runtime lacks it, such as Safari or Node.js 24, load a polyfill liketemporal-polyfill/full/globalon the server and in the browser, or those calendars show Gregorian dates and warn once in development. A server-rendered app loadsTemporalon the server too, since a server without it renders Gregorian markup. - Built-in date formats follow the calendar of the
InternationalizationProvider. Withcalendar="persian", Timestamp'sdate,date_long,date_weekday, anddate_timeformats, the full date in its hover card and accessible name, and PowerSearch date values are written in that calendar, the same oneCalendarshows. A provider locale with a-u-ca-keyword keeps Gregorian dates; setcalendarto choose one.date_timekeeps its time of day.time, the relative formats,system_*, andunix_secondsdo not change, and without a provider calendar every output stays the same.formatSharedDatefrom@astryxdesign/core/utilsandformatFilterValuefrom@astryxdesign/core/PowerSearch/utilstake an optional trailingcalendarargument that defaults to'gregory'. Each of these dates names the month and day thatCalendarshows for the same day, as doesplainDateFormatwith acalendaroption other than'gregory'. A calendar that needsTemporalshows Gregorian dates where the runtime lacks it, as the date components do. - DateInput, DateTimeInput: type dates in other calendar systems. Both take a
calendarprop, such as'persian','hebrew','japanese', or'buddhist', and otherwise use theInternationalizationProvidercalendar. A provider locale with a-u-ca-keyword keeps Gregorian dates; setcalendarto choose one.value,onChange,min, andmaxstay Gregorian ISO dates. People type a date the way the locale writes it in that calendar, in any decimal digits, such as۱۴۰۵/۸/۱۱,1 Adar 5784, or令和元年5月1日; the committed text, the popover calendar, and thedate,date_long, anddate_weekdayformats use the calendar, andsystem_datestays the ISO date. Years are years of the calendar, so a Buddhist field reads2026as Buddhist year 2026: setminandmaxon Buddhist and Minguo calendar fields to catch a Gregorian year typed by mistake. A date the calendar does not have, such as 30 Esfand in a common Persian year, is named and never adjusted. An empty focused field without aplaceholdershows an example date that the field accepts, and its description names the calendar when it differs from theInternationalizationProvidercalendar. Calendars other thangregory,iso8601,buddhist,roc, andjapaneseneedTemporal, native or a polyfill you load; without it the field shows Gregorian dates. (#9842) Typing changes for Gregorian fields too: - ClickableCard and SelectableCard no longer show their pressed look when a finger press starts on a control inside them, such as an info button, a button, or a link. A mouse press is unchanged.
useClickableContainerreturnsonPointerDown; on a container that paints its press withusePressFeedback, spreading it drops a touch press that starts on a nested control. A mouse:activepress paint is unchanged. A container that does not spread it works as before. - Table: the row-expansion toggle resolves its glyph through the
table-row-expandcomponent icon slot with state{expanded}. With no theme entry it drawschevronRight, as before. The toggle still turns the glyph a quarter turn while expanded and mirrors it in right-to-left layouts, whatever the slot resolves. A theme maps it withdefineTheme({componentIcons: {'table-row-expand': …}});nullhides only the glyph, and the toggle keeps its name,aria-expanded, and its toggling. - Table: a sortable column's sort indicator resolves its glyph through the
table-sort-indicatorcomponent icon slot with state{direction}('ascending' | 'descending' | 'none'). With no theme entry it drawsarrowUp,arrowDown, orarrowsUpDown, as before. A theme maps it withdefineTheme({componentIcons: {'table-sort-indicator': …}});nullhides only the glyph, and the sort control keeps its label, its sorting, and the header'saria-sort. - Timestamp: the hover card's copy button resolves its glyph through the
timestamp-copycomponent icon slot with state{copied}. With no theme entry it drawscopy, thencheckwhile copied, as before. A theme maps it withdefineTheme({componentIcons: {'timestamp-copy': …}});nullhides only the glyph, and the button keeps its name, tooltip, and copy action. - A theme chooses which shared icon a component's own icon role uses, by slot, with
defineTheme({componentIcons}). The first slot iscode-block-copy: CodeBlock's copy button passes{copied}and, with no theme entry, drawscopy, thencheckwhile copied. A slot value is a shared icon name,nullto hide the icon, a partial{icon, variant, size}request, or a pure function of the slot's state that returns one; a field the request leaves out keeps the component's own value. For the copy button,nullhides only the glyph.theme.componentIconsreads back the static values.ComponentIconSlotMapand the slot value types are exported from@astryxdesign/core/Icon.astryx theme buildcarries static slot values into built themes and fails on a slot function, andastryx componentlists a component's slots. - Under a finger (a coarse primary pointer), an interactive
Listrow's hit target grows toward 44px where its neighbors leave room: the shared touch hit area measures each row and stops at half the gap to the nearest neighboring row or control. Rows look and lay out exactly as before, and a mouse is unchanged. Under a finger a list row's::afteris that invisible layer, so decorate a row with a child instead. AnItemthat stops delegating to a nested control now drops its pressable-container marker, so a clickable card around it gets a mouse click in the row's padding again.InfoTip's button takes taps through the same layer: it still grows toward 44px where its neighbors leave room, and waits for motion around it to end before it grows; it clears when it stops taking taps. A labelledCheckboxInputand aRadioListItemoption keep their band's insets through the same hook, so the insets clear when the control stops taking taps. - Under a finger, on every device, the finger that opened a DropdownMenu or MoreMenu now picks a row only after it has moved 10 px (
spec:AST-094FR29, FR43), so a menu that appears under a resting finger never picks the row beneath it, however long the finger rests. To keep the previous behavior, where that finger's lift picked once the press had lasted 300 ms, passpresentation="popover". A mouse and the publicuseMenuPresshook are unchanged. - Holding a NumberInput stepper with a mouse or a pen repeats the step: the first repeat comes after 0.5 s, and later ones come faster. The field shows each repeated value as its draft and calls
onChangeonce, when the hold ends by release or pointer cancel. A click still steps and commits at once. createPanGesture(element, {axis?, onPan})andusePanGesture(ref, {axis?, onPan})report a drag on an element as values:onPangets{state, translation, velocity}as the drag begins, changes, ends at the release, or is cancelled, and a finger, a mouse and a pen all drive it. A mouse or pen drag that starts on selectable text selects it instead; give text the drag should moveuser-select: none.axislimits the drag to'x'or'y'; a drag along the other axis is not reported.translationcounts from where the drag began, so an element that follows it does not jump, andvelocityatendedis the release velocity in pixels per second. The drag claims its touch at UIKit's 10 pt and leaves presses and long presses alone.destroy()removes every listener; the hook creates one pan for the element behindrefand destroys it at unmount.LayoutPanel,ResizeHandleandSideNavtakeuseResizable's output directly in theirresizableprop:const resizable = useResizable({…}); <LayoutPanel resizable={resizable}>…</LayoutPanel> <ResizeHandle resizable={resizable} />. The output'spropsstill works, andSideNav'strueand config object are unchanged. Given the output,SideNav's rail follows the region: its width and collapse state come from the region, its toggle calls the region'scollapseandexpand, and the region'sautoSaveIdsaves the width. Whencollapsiblealso sets a collapsed state, the region wins andSideNavwarns in development. Any other element can use the hook'ssize; the Resizable docs show one sized with a StyleX dynamic style.- Under a finger, a labelled CheckboxInput and each RadioList option get an invisible hit area. It grows toward 44 px, never makes the target smaller than the control’s own box, and stops at half the gap to a closer neighbour, so neighbouring targets never overlap. Nothing changes size. Under a finger a RadioListItem row’s
::afteris that hit area, so decorate a row with a child. A label-hidden checkbox inside another component’s row keeps that row’s target. With a mouse or trackpad nothing changes. - Motion: a theme's
temponow times the springs of touch motion as well as its CSS durations. Each spring keeps its shape and takestempotimes as long, and so does the animated-layout glide. Finger tracking and busy loops stay as measured. A theme with only a duration scale keeps its springs at tempo 1, even inside a theme with a tempo. The springs don't followcharacteryet. The motion doc says so.
- A backdrop press that closes a menu or popover open inside a
Dialog(purpose="info"), aDrawerwith its scrim, orCommandPalettenow closes only that menu or popover. The next backdrop press closes the dialog, as a second Escape does. Fixes #9920. - BottomSheet: a finger's pull up past the top stretches the sheet on every pull, on iOS's rubber band, with no 48 px cap, unless the caller sizes or styles the sheet (an inline block size, any
xstyleor class name), which keeps it. The sheet keeps enough of its surface below the screen that the page never shows. A pull on the content stretches it too once the content has no room left to scroll: at once for content that fits, and after a long list reaches its end. A mouse or a pen keeps its current stretch. - BottomSheet, Tokenizer, PowerSearch: a swipe that asks to close a sheet whose owner keeps it open — the Tokenizer and PowerSearch sheets ask before a changed draft is discarded — no longer leaves the page behind the sheet undimmed. The scrim the drag thinned returns with the sheet to its resting detent, so the page stays dimmed while the confirmation is open and after Keep editing; Discard still closes the sheet with its exit.
- BottomSheet: a standalone sheet (not a
BottomSheetSwitcherstep) removed while it is open (rendered only while open, or removed with the rest of a view) returns focus where a close would send it, while that place is still on the page: tofinalFocusRef, else the control that opened it, or into the sheet beneath. A whole stack of standalone sheets removed at once returns focus to the first sheet'sfinalFocusRefor opener. When none of these is left on the page, for example when the opener is removed with the sheet, focus stays where the browser leaves it. Focus the person had already moved out of a sheet, to the page body, stays where it is. A sheet that opens in the same update keeps focus, and removing a covered sheet under a top sheet that stays moves no focus. - The internal calendar date parser reads back the text it writes in more locales. It reads month names that start with a number, such as Latvian
1. adarsfor Adar I and1. rabīfor Rabi' I, and still reads1. adarsas day 1 of Adar when no other number can be the day. It reads a numeric pattern that uses two separators, such as the Hindi Buddhist format hint. In the Japanese and Minguo calendars, whose era changes between 1900 and 2100, a year without its era can read as the wrong date. There, a format hint that would otherwise leave out the era shows the narrow era when it keeps the locale's field order, such as Amharic1/1/8 R. Where every era width moves the fields, a date of an earlier era adds the era, such as ColognianBROC 1. Jan. 12, which no longer reads as a date in 1923. Calendars with one era in that range, such as Buddhist, Ethiopic or Hijri, write the same text as before. No public API changes. useChatNewMessages(whichChatLayoutuses),useSpeechRecognitionanduseChatDictationread their callbacks and other latest values as last committed. A render that React discards, as a suspended transition does, no longer has its callbacks called on the next resize or recognition event. Its audio context is no longer used either. After such a render,useChatNewMessagesalso no longer flags new messages while the list follows the bottom.- A checked, enabled, editable
CheckboxListItemrow now reflectsdata-selected="selected"on its row, the element that carries itsitemandlist-itemtargets, so a theme'sselectedrules for those targets restyle checked rows and the text on their fill. - On a touch screen, a CheckboxList row now answers a finger the same way wherever it lands. Under a finger, pressing a row's label painted the whole row while pressing its box painted only the box; under a coarse primary pointer the row is now the one touch press surface, so the row and its checkbox show the pressed state together whether the finger lands on the label, the description or the box. Where the primary pointer is fine (a mouse, a trackpad, or a pen on a desktop page), the box keeps its own press and hover, as before.
- A disabled
CheckboxInputno longer runs a caller'sonKeyDown,onKeyUp,onPointerDown,onMouseDownor any otheron*prop exceptonFocusandonBlur. Before, a disabled checkbox stayed focusable and clickable, so a key or pointer press on it still reached them; onlyonClickwas withheld. Now a disabledCheckboxInputruns no caller handler other thanonFocusandonBlur, as a disabledSlider,TextInputandTextAreado (spec:AST-085FR2). An enabledCheckboxInputis unchanged. No API changes. - When a
LayoutPanelbeside aResizeHandlecollapses with focus inside it, focus moves to the handle, and the collapsed panel is inert and hidden from assistive technology until it opens, instead of keeping focus, and a Tab stop, in a panel of no width. This holds for a panel that takes auseResizableregion'sresizableprops, whether the hook's own or a hand-built object, and for a panel at the region's size beside a handle given them. A collapse started from the handle or from outside the panel keeps focus where it is. ComplexSelectorwith a status and the defaultattachedplacement now shows the status icon on its trigger in place of the chevron, asSelectorandMultiSelectordo, so an error is no longer shown by color alone on the field.- The transient copy a layout motion holds of old content (an in-place swap's old content, the SideNav rail's and AppShell content's old state during a collapse or expand) carries on the CSS animations and transitions running in it from where they were. It no longer starts each CSS animation again (a press highlight lit again, an entrance that had played playing again, whatever its fill) or shows a transition's end at once.
- DateInput and DateTimeInput keep typed text that has not reported a date, without an error, while disabled or busy, and commit it on the next Enter or blur once editable. A new value still replaces it. Typed text whose date
onChangealready reported gives way to the field's value once the field is editable again, on the next Enter or blur if the field has focus. (#9842) - DateTimeInput: a press that never becomes a click no longer leaves its document click watchers behind when the field unmounts (#10155)
- On a phone or tablet (a coarse primary pointer), a tap or a drag that lands while a
Dialogor aDraweris still entering no longer closes it, runs one of its actions, moves focus or scrolls the page: it reaches nothing until the entrance has played, and the dialog or drawer finishes entering and stays open. Around aDrawerwithout a scrim only a tap or a drag on the panel is held, and the page stays live. Keys and assistive technology stay live during the entrance, the content stays in the accessibility tree, a pinch still zooms the page, and a mouse or a trackpad still clicks: at once when it was the last pointer used, otherwise once it moves. - On a phone or a tablet, a finger's tap or drag that lands while a DropdownMenu is still opening is now ignored, as on iOS: the menu finishes opening, stays open and keeps focus, and nothing under the tap reacts. The finger that opened the menu still picks a row. Keys and assistive technology stay live. A mouse, a finger on a touchscreen laptop and
presentation="popover"are unchanged. - DropdownMenu trigger tooltips stay closed after pointer selection and still reappear for returning keyboard focus.
- FileInput: a busy or disabled dropzone now shows the no-drop cursor for a dragged file instead of the copy cursor, and the browser no longer delivers a drop the zone would ignore.
- Focusing the clear or status button of a ghost or secondary
Selectorno longer also rings the whole trigger: each tab stop shows one focus ring. - Under a finger, a Switch or InfoTip measured while an ancestor is scaled keeps the touch insets of its unscaled layout. Before, that measurement kept the scaled frame's insets until the next resize.
- InputGroup: a focused input now stays above an unfocused input beside it, so the neighbour's border no longer covers part of its focus ring.
- A selected interactive
ItemorListItemrow now shows hover and mouse-press paint over its fill, and a highlightedItemrow shows mouse-press paint. Before, the fill hid both. A highlighted row shows no extra hover, because its fill is already the hover tone. - Item: the
itemtheme target now reflects the row's state asdata-highlighted,data-selected, anddata-disabled, so a theme can restyle a highlighted, selected, or disabled row. A row that composes Item reflects the states it passes to Item: for example, ListItem reflects selected and disabled, and DropdownMenuItem reflects disabled. The docs listclassNameandstyle, giverefthe root element type, and givexstyleits standard description. - Keep TabMenu arrow-key navigation from moving focus to the outer tab strip.
- A layer's trigger (Tooltip, Popover, HoverCard, DropdownMenu, ContextMenu and every other
useLayercaller) now carries its CSSanchor-nameonly while the layer is shown or its exit animation runs. A closed trigger is no anchor, so in WebKit's Linux ports a transform animation on an element that contains a closed trigger keeps running while the main thread is busy. A layer that fades out keeps its trigger's anchor name until the fade ends. If you write a layer'sanchorIdas an anchor name yourself, write it only while the layer is shown or its exit animation runs. - An anchored layer (a Popover, a menu, a Selector's or MultiSelector's panel, a Typeahead's list) that would run under the software keyboard, in Safari on iOS, now keeps its top and shrinks to end 10 px above the keyboard, its content scrolling, and grows back when the keyboard goes down; before, the keyboard covered its lower part and a button there could not be reached. A layer clear of the keyboard, or placed above its trigger, does not change.
- A
LayoutPanelwhose width is the number 0, such as one given a collapseduseResizableregion'sresizableprops orwidth={0}, now takes no space and shows none of its children. Before, its padding kept it about 32px wide and its children showed squeezed into that strip, one letter per line. This is visual only: keyboard and assistive-technology hiding for a collapsed panel is separate. - Link: the label now takes the link's own color, at rest and on hover, so on hover a
primary,secondary, oraccentLink tints its text with its underline and external-link icon. Before, only the underline and the icon took the hover tint. A theme override on thetexttarget (for exampletext[data-color="accent"]) no longer reaches Link labels: restyle Link labels through thelinktarget. A theme that styles thetexttarget's color now also keeps any Text withcolor="inherit"on its parent's color. - A list row's touch press release keeps fading while the page runs script, once the release has started.
ListItem,ItemandTreeListrows used to freeze mid-fade until the main thread was free, and every frame of the fade repainted the row; the release now runs on the compositor, with the same color as before. Script that runs in the tap's own handlers, before the next frame, still holds the row pressed until it ends. Under a finger, while a row is pressed and through its release, the press paints on the row's::beforeand the row is its own stacking context; at rest nothing of it applies, so a row's own::beforedecoration and its children's stacking are as they were. During a press the layer is the row's::before, and any::beforeproperty a caller sets, fromxstyle(whichItemcomposes after the layer) or from a stylesheet, wins over the layer's: a decoration with its own size and background jumps to the row's corner, and the row shows no pressed fill until the release ends. Decoration that should stay through a press belongs on::afteror a child element. With a mouse or trackpad the row is unchanged: no layer and no new stacking context. ChatMessageList,TooltipandHoverCard: the chat log and the span thatTooltipandHoverCardwrap around text-only children draw the theme's focus ring instead of the browser's default. A theme's--focus-outline-*tokens now reach them. The chat log's ring sits inside its box, so the area that scrolls it no longer cuts it off.- Markdown's GFM autolink option (
autolink: 'gfm') no longer slows to seconds on long text. The bare-email scan started over at every character of a long run, so its time grew with the square of the run: a 100,000-character word took over 10 seconds. It now starts from each@, and trailing)are peeled off a link in one pass, so a long run costs about as much as a plain parse. Linked emails and URLs are unchanged. - While a finger or a pen is held in a DropdownMenu, MoreMenu or ContextMenu, the row under it now takes the pressed overlay, and no other row keeps it, so the highlight can be seen following it (
spec:AST-094FR27, FR43). A held mouse, the keyboard and a hover keep the hover overlay. - On a phone or tablet (a coarse primary pointer), a tap or a drag during MobileNav's slide-in reaches nothing; keys and assistive technology stay live, and a mouse or a trackpad still clicks: at once when it was the last pointer used, otherwise once it moves.
- MobileNav: Tab now wraps inside the open navigation drawer. Tab from the last control moves to the first, and Shift+Tab from the first moves to the last, so keyboard users no longer move from inside the drawer to the browser's toolbar. The drawer uses the same focus-region module as Dialog, BottomSheet and Lightbox.
- Dialog, AlertDialog, CommandPalette, BottomSheet, Lightbox: Tab now wraps inside an open modal Dialog, AlertDialog, CommandPalette, modal BottomSheet or Lightbox. Tab from the last control moves to the first, and Shift+Tab from the first moves to the last, so keyboard and screen-reader users no longer land in the browser's toolbar from inside these modals. They now use the same focus-region module that Drawer and Popover use. When modals nest, the innermost one handles Tab. Non-modal sheets and inline dialogs do not trap focus. Fixes #8841.
- MultiSelector: in the open list, a repeated letter now moves to the next option that starts with it, so
t,t,tover Tea, Toast and Tofu walks Toast, Tofu, Tea instead of staying on Tea. A single letter typed after a pause searches from the option after the highlighted one, not from the top of the list, as in Selector. Space still toggles the highlighted option, so a typed Space does not extend the prefix as it does in Selector.useMultiComboboxgets the same behavior. (#9921) - With a finger, a press that starts on a control inside a list row with
onClickorhref, a TreeList row withonClickor children, or a clickable or link Token withonRemoveno longer makes the row or token look pressed, since it does not run the row's or token's action. A press on the row or token itself still looks pressed. Mouse presses are unchanged. - When an
OverflowList(or auseOverflowhost) resizes, it now shows the new visible set in the same frame. Before, the resize rendered after the frame painted, so one frame showed the old set at the new width: items spilled past the edge as the box shrank, or stayed collapsed as it grew. A resize now measures again in a microtask before paint, every list that one resize reports renders together, and a resize that changes nothing renders nothing. A resize whose new set can change the list's own height (a taller indicator appearing, a multi-row list filling a different number of rows or measuring a new row height, or, withbehavior="observeParent", the indicator appearing or disappearing, which changes how the list fills its parent, or a list sized from its items, such as a flex item beside a menu that can shrink) still renders one frame later, as before, because rendering it before paint would make the browser report a ResizeObserver loop error. A resize that folds the list before paint can make Chromium report "ResizeObserver loop completed with undelivered notifications" once in that frame when another observer watches an outer element (Storybook's preview observes<body>); no notification is lost, and an error monitor should ignore this message. Tests: a resize the observer reports now applies in a microtask, still before paint. A test that calls a mockedResizeObservercallback and asserts synchronously right after it should first await the update by wrapping the call inawait act(async () => { … }). - BottomSheet and Dialog: the page no longer jumps after a sheet or a dialog closes in Safari and on iOS. Returning focus to the control that opened the overlay asks the browser not to scroll. WebKit focuses the nearest focusable ancestor of a tapped button, such as an app's
tabindex="-1"main region, and records it as the opener; the focus return then scrolled the page to that region's top edge. The focus ring still follows the last input. - Pagination: the dots variant now exposes the APG radio group it already behaves as. Before, the dots were a
groupofbuttons named "Go to page N", witharia-current="page"on the current page. Now the container is aradiogroupnamed by the page-indicators label, and each dot is aradionamed "Page N" witharia-checkedon the current page and noaria-current. Keyboard behavior is unchanged. A press on a dot now chooses its page on release, so sliding off before release takes the press back. - PowerSearch: in the sheet presentation, a placeholder longer than the trigger stays on one line and ends in an ellipsis. Before, it ran under the result count and, on a narrow field, past the field's border.
- A press's release now fades on the theme's exit motion tokens,
--duration-exitand--ease-exit, instead of 200 ms, scaled by the theme'stempo, on a curve of its own. This covers every release that fades: a finger, mouse, pen or key press on a phone or tablet, and a pen press on a desktop, on buttons, cards, switches, checkboxes, list and tree rows, tabs, menu items and action groups. With the default tokens the release is 175 ms and holds its colour a little longer before it goes. A theme that changes--duration-exitor--ease-exitnow changes the release too; a theme'stempostill does, through--duration-exit. Under Reduce Motion a finger's release still fades for no more than 200 ms. A mouse press on a desktop is unchanged. - Under a finger, a pressable (a button, card, link, list row or any other surface) outside a scroll view now highlights in the frame the finger lands, as UIKit's controls do, instead of after a 150 ms delay. Inside a scroll view it still waits that delay, so a flick paints nothing, and a scroll that takes a held press cancels it with no fade. A page that scrolls counts as a scroll view, so on a page taller than the screen a press still waits; a control in a fixed bar, or on a page that fits, highlights at once. A mouse on a desktop keeps
:active. - Under reduced motion, a pressed Button no longer shrinks, and ClickableCard, SelectableCard and Thumbnail change their hover, border and selection colors at once on every pointer, whatever the theme's duration. A finger's press highlight still fades out on release, within 200 ms.
- LayoutContent, LayoutPanel and a scrollable
divorspanStackItem name their region while it scrolls. Before, a scrolling region was a nameless element that Chromium could make a Tab stop, so a screen reader announced nothing about it. It is now a group named by the caller's label, or by a translated "Content" (accessibility target: LayoutContent AR3, LayoutPanel AR3 and Stack AR2). A caller's role stays with only the caller's name, and Tab behavior stays the browser's. - Selector: after keyboard input, the active option draws the standard focus ring inside its edge, so people moving through the list with the keyboard can see where they are. The overlay fill alone measured about 1.09:1 against the listbox in light mode and 1.15:1 in dark, under the 3:1 a state indicator needs; with the stock theme the ring measures 5.39:1 (light) and 3.10:1 (dark). The ring takes its width, style and color from the
--focus-outline-*tokens, like every other focus ring. After pointer input the active option keeps the fill alone, as a focused DropdownMenu row does. This includes a mouse that moves onto another option after keyboard input, without a click. - A disabled
SideNavItemno longer runs a caller'sonKeyDown,onKeyUp,onPointerDown,onMouseDownor any otheron*prop exceptonFocusandonBlur. Before, a key other than Enter or Space on a disabled item still reachedonKeyDown, every key reachedonKeyUp, and pointer or mouse input that bypassespointer-events: none(programmatic or assistive-technology input) reachedonPointerDownandonMouseDown. This covers the plain, link, collapsed leaf and split-row forms; the flyout trigger of a collapsed parent item is not covered. Now a disabledSideNavItemruns no caller handler other thanonFocusandonBlur, as a disabledSlider,TextInputandTextAreado (spec:AST-085FR2). An enabledSideNavItemis unchanged. No API changes. - Spinner: a turning ring now wakes the main thread once every 100 turns instead of every turn. In a React app, Chromium recalculated style at the end of each turn, about seven times a second with one ring at a 525 ms turn. The ring looks and turns the same, and reduced motion still slows it to 3 seconds a turn.
- A standalone Spinner used to mount
role="status"already holding its name, so screen readers often said nothing when it appeared. It is now an indeterminate progress indicator (role="progressbar"with noaria-valuenow, named by its visible string label, itsaria-labelor a translated "Loading") that announces once through the shared polite channel when it appears after page load, reading its rendered text in order with stacked lines kept apart, or its accessible name (AST-020 FR15; AST-086 FR2, FR7, FR11–FR12; WCAG 2.2 4.1.3). BaseTypeahead, Typeahead and Tokenizer keep their search Spinner named and never live while they announce the search themselves, and app code silences a composed Spinner witharia-hidden. No app change is needed (AST-017 FR53): a Spinner given aroleother thanprogressbarstays silent, as before. The lab EmojiPicker and Schedule now hide their loading rings and announce their own busy start once, and the Comments history dialog announces it inside the dialog (AST-086 FR6, FR8, FR14); their loading words stay readable. Every Spinner's indicator changes fromstatustoprogressbar, so a test that queries it by role changes, including the hidden rings inside Button, CheckboxInput, CheckboxList, CheckIndicator, ChatMessageList, ChatToolCalls, CommandPaletteInput, ComplexSelector, DateInput, DateRangeInput, DateTimeInput, DropdownMenu's sub-menus, FileInput, MultiSelector, Selector, Switch, TextArea, TextInput, Thumbnail and TimeInput, and lab's EmojiPicker, Comments and Schedule. - The stock theme's dark-mode accent (
--color-accent) is now#0171E3instead of#2694FE, so white text on an accent fill, such as a primary Button's label at rest, meets 4.5:1 (4.70:1, was 3.11:1). Hovered and pressed primary Buttons improve too (about 4.36:1 and 4.03:1, from under 3:1) but stay under 4.5:1. Light mode keeps#0064E0. Everything else painted with--color-accentin dark mode also darkens: accent fills, focus rings and accent icons. MetadataList's "Show more", Table's sort rank and DateRangeInput's applied preset now use--color-text-accent, so their text stays readable on dark surfaces. The theme template thatastryx theme templatewrites uses the same dark accent, and the generated color token docs list it. To keep the previous look, a theme can set'--color-accent': ['#0064E0', '#2694FE']. - A disabled
Tabno longer runs a caller'sonPointerDown,onMouseDown,onKeyDownor any otheron*prop exceptonFocusandonBlur. Before, a press or key on a disabled tab still reached them, for both the button and the link form; onlyonClickwas withheld. Now a disabledTabruns no caller handler other thanonFocusandonBlur, as a disabledSlider,TextInputandTextAreado (spec:AST-085FR2). An enabledTabis unchanged. No API changes. - A toast plays its entrance once. When a
Dialog, a modalDraweror a bottom sheet with a scrim opens or closes while toasts show, the viewport moves into it or back out, and a toast already on screen now stays where it is instead of sliding in again. - Toast: without a LayerProvider, a toast dismissed before the fallback viewport mounts no longer shows, and its
onHideruns once with'manual'. - Tokenizer: in the sheet presentation,
endContentrenders at the field's inline end andrenderTokenrenders the field's tokens, as in the inline field, so the same props work on a phone under the'adaptive'default. Their own controls act without opening the sheet: an end-content button runs its handler, and a custom token's remove control commitsonChangeat once and moves focus to the trigger. A tap on a token, the placeholder or the field's empty space still opens the sheet. Before, the sheet dropped both props with a development warning.renderItem,hasClear(the sheet has its own Clear all),hasEntriesOnFocus,menuWidth,tokenOverflowBehaviorandhasAutoFocusstill apply to the inline field only. - A disabled
TopNavItemno longer runs a caller'sonPointerDown,onMouseDown,onKeyDownor any otheron*prop exceptonFocusandonBlur. Before, a key pressed while script had focused a disabled item, or an event dispatched to it, still reached them, in both the default and the drawer form; onlyonClickwas withheld. Now a disabledTopNavItemruns no caller handler other thanonFocusandonBlur, as a disabledSlider,TextInputandTextAreado (spec:AST-085FR2). An enabledTopNavItemis unchanged. No API changes. - DateInput: under a finger, every row in view of the bottom-sheet picker's columns meets WCAG AA contrast; their edge fade is shallower.
- Typeahead, Tokenizer: picking a result now calls
onChangeQuery(''). The pick empties the input but never reported it, so an app that mirrored the query kept the text typed before the pick. A pick made while the query was already empty reports nothing, as nothing changed. (#8929) useMediaQuerynow shares one listener per query among all its callers, so a long list of components that ask the same query adds one listener, not one per component. Every caller still reads the same answer. It also listens on Safari before 14, which has only the older listener pair, where it threw before.- Gestures: a late or merged trackpad wheel that keeps the glide falling stays in the flick. When the next wheel grows instead, the new gesture keeps the late wheel's travel. A mouse wheel's notches keep their rules.
- Under a finger, the touch-inset pass of a Switch or InfoTip resolves a control's style only when a transform scales it.
- LayerProvider no longer carries the toast viewport. An app that imports
LayerProviderfrom@astryxdesign/core/Layerand never imports Toast loads about 45 KB (46,161 bytes) less JavaScript (gzip). Toast registers its viewport when it is imported, and LayerProvider then renders it after the app. Without a Toast import, LayerProvider renders no empty viewport. A toast shown before the viewport mounts waits, then shows. On the server, LayerProvider renders no viewport; it mounts after hydration. - A single-line
OverflowListno longer renders again after mount only to record an item height it never shows: it does not track the row count and item height that only its multi-row form (maxRowsabove 1) renders from. One whose items all fit now renders once on mount; one that collapses items still renders again for the new visible count. The publicuseOverflowhook is unchanged and still reports both on every path.
- A BottomSheet source comment names the pen beside the mouse for the resisted stretch past fully open. Nothing a consumer imports or renders changes.
astryx hook --listgroups the motion hooks under one category, "Motion & gestures":useAnimatedLayout,useAnimator,useEntryAnimation,usePanGestureandusePressFeedback; the docs ofcreateAnimatorandcreatePanGesturecarry the same category. The "Animation" group goes away.useLongPress,useMenuPressand the other interaction hooks stay under "Interaction".- Hook docs for
useCalendarDays, soastryx hook useCalendarDaysandastryx searchreturn it, and auseCalendarDays — Month Gridexample block: a compact month table built from the hook'sweeksanddayNames, with the previous and next months' days dimmed and a Week starts on control (Sunday or Monday) that reorders both the header and the grid. Copy it withastryx template useCalendarDaysHookUsage <path>. - Hook docs for
useIcon, soastryx hook useIconandastryx searchreturn it, and auseIcon — Status Rowexample block: a custom status row draws the success, warning and error icons withuseIcon, and under a Theme that overrides those icons the same row draws the theme's icons. Copy it withastryx template useIconHookUsage <path>. - Hook docs for
useLinkComponent, soastryx hook useLinkComponentandastryx searchreturn it, and auseLinkComponent — Article Cardsexample block: custom Article cards render their title link with the component the hook resolves. Inside a LinkProvider wired to a client-side router the title navigates without a page load,as="a"gives a native anchor even inside the provider, and outside the provider the title falls back to the next provider up or a native anchor. Copy it withastryx template useLinkComponentHookUsage <path>. - Hook docs for
useOverlay, soastryx hook useOverlayandastryx searchreturn it, and auseOverlay — Product Quick Viewexample block: a product Card spreads the hook'scontainerRefandcontainerPropsand renders the returned scrim once over its image. Hovering the card, or moving keyboard focus into it, reveals a Quick view button, and the image link before it lets Tab reach the button. Copy it withastryx template useOverlayHookUsage <path>. - Hook docs for
useTableSortableState, soastryx hook useTableSortableStateandastryx searchreturn it, and auseTableSortableState — Multi-Sort Ordersexample block: an orders Table with no sort code of its own, where clicking Customer and then Shift-clicking Total sorts by customer and then by total. Copy it withastryx template useTableSortableStateHookUsage <path>. - Internationalization gains a short Other calendars guide: choose a calendar on
InternationalizationProviderorCalendar, values stay Gregorian ISO dates, and most calendars needTemporal(loadtemporal-polyfill/full/globalwhere it is missing). A newastryx docs calendarsnamespace has the detail: the supported calendars, loadingTemporalon the server and in the browser, why the lunisolar calendars (chinese,dangi) can differ by a day between runtimes, and ISO 8601 week numbers. The guide andCalendar'scalendarprop docs say how to get identical server and browser markup for those calendars:installImplementation()fromtemporal-polyfill/full/shimon both sides, since thefull/globalimport keeps a nativeTemporal. - InfoTip: the component page,
astryx component InfoTip --showcase, andastryx component InfoTip --blocksnow have worked examples — a showcase metric label, a row of metric definitions, and read-only settings with permission notes. InfoTip's docs list its anatomy (trigger button, info icon, help surface) and say it follows the toggletip pattern: the button reportsaria-expanded="true"while the help is pinned, and a screen reader hears the help once when it pins. - InputGroup: the docs no longer say
statuspaints the group border or that the group shares one focus ring.statusshows its message below the group and reflects its type asdata-status; a focused member stays above its neighbours.isOptionalnames the "Optional" indicator it shows. - InputGroupText: the docs now say when to use a text addon and what it is not: short fixed text or a decorative icon, read as plain text and not part of the input's accessible name. They document
ref, givexstyleits standard description, and the code example no longer gives the group and its input the same label, which made a screen reader read the name twice. - Long comment sentences in some menu press sources and tests are split into shorter ones, and DropdownMenu's
presentationdescription no longer says "as before". Nothing a consumer imports or renders changes. - The
usePanGesturepage opens with a draggable box in two forms side by side: the hook andcreatePanGesture, which takes any element, with the same StyleX styles and Astryx tokens. The playing card follows as the advanced example. ThecreatePanGesturepage points to that example, and theusePanGestureguidance warns against holding the drag offset in a large component's state.
-
Internal: the undocumented
@astryxdesign/core/internal/calendar-systementry gives Lab's Schedule the calendar that Core's date components resolve, and the helpers that name its months, days, and date ranges asCalendardoes. Internal entries are outside the public API and its compatibility promise. Lab's Schedule follows that calendar. Without a provider calendar, the accessible label of a timed event that spans days now writes its dates in the Gregorian calendar; before, the dates followed the locale's default calendar, such as the Persian calendar underfa-IR. A locale never selects the calendar. -
onChangefires while typing only once the date is complete, and once for each date, so partial input such as12/0or12/01/202no longer reports wrong dates. A two-digit year, such as12/01/90, or a date without a year commits on Enter or blur, and is read in the locale's field order. A one-digit or three-digit year, such as1/5/2or12/01/202, is not a date: Enter or blur keeps it and shows it as invalid.parseDateInputstill reads, on Enter or blur, text the field cannot otherwise read and a date the locale's field order does not have, such as13/5/2026inen-US. -
A typed date outside
min,max, ordateConstraintsstays in the field with an error that names the allowed range, or says the date is not available when only adateConstraintsrule rejects it, instead of reverting on blur; the error shows even when you setstatus. Unreadable text, a date that does not exist, and an out-of-range date each announce their own message, once.Fixes #8958.
-
Internal: Core's internal touch entry also names, for Lab, the screen-edge pan recognizer, the spring solver, the motion resolver, UIKit's navigation table and four navigation springs, and the focus helpers that follow the last input. Core's English catalog gains
@astryx.navigationStack.back. Apps import nothing new. -
Internal: Core's internal motion engine factory is renamed
createMotionEngine(fromcreateMotionAnimator), with its typesMotionEngineandMotionEngineOptions, at its source and in every internal caller. Core's internal touch entry also re-exports, by name, the UIKit gesture values, the right-to-left check, and the touch-presentation hook and query that Lab components read. Lab'suseMotionEnginehook wraps the engine, and Lab's refresh control uses it. Apps import nothing new. -
Internal: Core's internal touch entry re-exports what Lab's useRefreshControl reads. That is the pan recognizer, its hook and the gesture types, and the motion animator. It is also the motion math, the map of a companion curve, the UIKit scroll table of its pull, and the motion-role parsers. None is in the public entry. Apps import nothing new.
-
Internal: one input hold for every layer (
Layer/inputHold). While a layer presents, a finger's or a pen's touch, press or click reaches nothing: anywhere around a modal layer, and only on the layer itself around a non-modal one, whose page stays live. A mouse passes once it moves, at once when the page last heard a mouse (keys and assistive technology still act, and a pinch still zooms the page). While it dismisses, every press and click reaches nothing, and the layer's content is inert. The gesture host asks the hold about each touch (holdsTouch), andisInputShieldlets a hit test see past a hold's shield. Not exported from the package entry point; Dialog's and Drawer's entrance holds and MobileNav's drawer use it. -
Internal: each spring role, and the navigation, paging and picker motion tables, register themselves when their module loads, so a spring role plays at the theme's
tempowherever it plays, with no register call. A page that plays no spring ships no springs registry. A module that imports single spring rows carries only those rows, unless it also reads the wholespringstable or callsregisterSpringsTable(). Not exported from the package entry point. -
Internal:
usePanGesturecopiesonPaninto its ref at commit, not during render, before the hook's first release. A render React discards, as a suspended transition does, no longer hands the next drag to itsonPan. -
Internal: a pan gesture's begin threshold can be read from each press's first touch, so a mouse or a pen can begin a pan sooner than a finger. It is not exported from the package entry point, and nothing changes for existing callers.
-
Internal cleanups in SideNav and in how a resize handle finds its panel: a source comment that only restated the line after it goes, and one lookup function replaces two. No behavior change.
-
Internal: Spinner's rotor (the turning arc's keyframe, timing and timeline-origin pin) moves into one internal module that Lab's indeterminate CircularProgress also uses. Spinner does not change.
-
Internal: one shared reader times motion from the theme's duration and easing, as a computed transition resolves them. SegmentedControl's selection slide now reads its timing with it; its timing is unchanged,
calc()tokens included. -
Internal: DateInput and DateTimeInput keep their typed-entry state, rejection status, and commits in one shared hook instead of two copies. Typing, committing, and rejection messages are unchanged. Not exported from the package entry point. (#9842)
Thanks to everyone who contributed to this release:
- @aashish254
- @cixzhang
- @duytbp
- @harjothkhara
- @HelloOjasMutreja
- @imdreamrunner
- @korkt-kim
- @MannXo
- @nynexman4464
- @parinith-web
- @vjeux
- RichTextExtensionNode is constructible with no arguments, as a registered node class must be. A Yjs collaboration binding builds one instance of every registered node class, with no arguments, to read its default properties, so creating a binding over an editor with this package's default nodes threw
TypeError: Cannot read properties of undefined (reading 'display')before any document was loaded. Itsfactsnow default to the same empty values the class already reads for damaged stored state, so a no-argument node reports an empty inline extension instead of throwing. A node built with facts is unchanged.
Thanks to everyone who contributed to this release:
- @potatowagon
- Under the Neutral theme, a selected row, a calendar range and the other selected fills on Neutral's surfaces are visible again, and highlighted code lines are stronger.
--color-accent-mutedmatched the surface in dark mode and barely differed from the hover tint in light mode; it is now#d4d4d4in light and#474747in dark. In dark mode a selected Item, TreeListItem or checked CheckboxListItem row, a numbered Citation and a FileInput dropzone lift their secondary text so it stays readable on the fill; a dropzone's hint is slightly lighter at rest too. Neutral's code comments, operators and punctuation are slightly less muted so they stay readable on a highlighted line. The CLI's Neutral theme template carries the same values.
Thanks to everyone who contributed to this release:
- @cixzhang
v16.5.0-canary.42
- Persist Request Insights session history: #97795
- Show route module loading and preparation in Request Insights: #97362
- Improve Request Insights request and fetch details: #96925
- Automatically compress route handler responses if not already compressed: #101220
- experimental_prepare: Don't refetch on commit due to stale inlining hints: #101234
- Bump to swc v83: #101079
- [image-optimizer] Remove optimizeImage from the public next/image-optimizer-transform API: #101133
- Add a server-router regression gate: pnpm test-routing: #100812
- [turbopack] Fix GC/eviction race by dropping task resurrection: #100370
- docs: buffer and re-serialize in the BFF proxy Route Handler example: #101199
- Turbopack: test strict receivers in minified mixed chunks: #101239
- Turbopack: retry chunk loads that failed with component chunks: #101179
- Bump to swc v84: #101194
- docs: require Cache Components compatibility for new e2e tests: #101222
- [ci] Resolve the PR to comment on from the workflow run alone: #101090
- test: capture finished OTel spans from console output in otel-middleware-trace: #100664
- Turbopack: keep IIFEs with a PURE-annotated callee in production builds: #100477
- docs: clarify directory-specific test gate guidance: #101200
- eval: verify independent dashboard streaming: #101155
- Turbopack: generate import.meta.glob as a synthetic module: #100535
- Turbopack: report unknown module types at the import: #100534
- Turbopack: use flat module context ids for dynamic requests: #100681
- Turbopack: read block-scoping support during codegen: #101157
- Make Firebase tracing fixture independent of backend connectivity: #101180
- Pin Tailwind to 4.3.3 in no-double-tailwind-execution test: #101176
Huge thanks to @jamiboym, @lukesandberg, @DavidIlie, @icyJoseph, @eps1lon, @timneutkens, @mischnic, @acdlite, @sokra, @aurorascharff, @bgw, and @wbinnssmith for helping!
electron v46.0.0-alpha.7
Note: This is an alpha release. Please file new issues for any bugs you find in it.
This release is published to npm under the alpha tag and can be installed via npm install electron@alpha, or npm install electron@46.0.0-alpha.7.
- Fixed a crash when extensions access
chrome.system.storageAPIs. #55624 (Also in 45) - Fixed proxy switches replacing
ses.setProxy()after a network service restart. #55575
- Updated Chromium to 159.0.8140.0. #55577
- Documentation changes: #55599
@astrojs/netlify@8.2.12
- Updated dependencies [
7da442b]:- @astrojs/internal-helpers@0.13.1
- @astrojs/underscore-redirects@1.0.4
@astrojs/react@7.1.4
- Updated dependencies [
7da442b]:- @astrojs/internal-helpers@0.13.1
@astrojs/node@11.1.11
- Updated dependencies [
7da442b]:- @astrojs/internal-helpers@0.13.1
@astrojs/mdx@8.1.3
- Updated dependencies [
7da442b]:- @astrojs/internal-helpers@0.13.1
- @astrojs/markdown-satteri@0.4.7
@astrojs/preact@6.1.2
- Updated dependencies [
7da442b]:- @astrojs/internal-helpers@0.13.1