1 hours ago
Leaflet

v2.0.0-beta.2

The second beta of Leaflet 2.0 adds a built-in fullscreen control, a way to translate the built-in text, and canvas layers that no longer block clicks on the layers below them, along with over a dozen bug fixes.

Plugin authors: if your plugin works with Leaflet 2.0, set compatible-v2: true in its entry in docs/_plugins to list it under "Ready for Leaflet 2" on the plugins page. See the migration guide for what changed for plugins, and Leaflet.markercluster and Leaflet.heat for examples of plugins ported to support both 1.9 and 2.0.

❇️ New features

  • Add FullscreenControl, an opt-in control that makes the map fullscreen, replacing the leaflet.fullscreen plugins. It adds 0.5 kB gzipped only when imported, and falls back to filling the window on iPhone (#10531)
  • Add a translate map option to translate the built-in text, such as control titles and accessible labels: translate: text => i18next.t(text). Plugins can call map.translate(text) so their text gets translated the same way (#10534, #9092)
  • Add originalEvent to movestart, move, moveend, zoomstart, zoom and zoomend when the user changes the view, so listeners can tell user moves from programmatic ones (#10525)
  • Add a title option to LayersControl

✨ Improvements

  • Make canvas renderers transparent to pointer events, so SVG paths, markers, overlays and other canvas renderers below them get clicks and hovers (#10523, #3888, #4135, #5739, #5886, #6205, #7049)
  • Stop throttling hover events on canvas paths, so they respond as quickly as SVG paths (#9514)
  • Draw the zoom control's + and − as SVG, so they're crisp and centered with any font

♿️ Accessibility

  • Raise markers with riseOnHover on keyboard focus too (#8078)
  • Fix Enter and Space on a focused marker doing nothing after a drag (#10529 by @Enfiladeguide)
  • Fix Escape being ignored by the map after closing a popup
  • Set aria-describedby on a layer whenever its tooltip opens, however it was opened

🐞 Bug fixes

  • Fix paths jumping when they're changed or added during a zoom animation or pinch (#9373)
  • Fix setView and other view changes being ignored during a zoom animation, they now apply once it ends (#10407 by @yellowman, #1289)
  • Fix minNativeZoom and maxNativeZoom requesting the wrong zoom level with detectRetina on retina screens (#10111, #8850)
  • Fix closestLayerPoint on polygons ignoring the segment that closes the ring (#9098, #9114)
  • Fix a second click on an image, video or SVG overlay closing its popup instead of moving it, as on paths (#7849)
  • Fix zIndex and setZIndex() being ignored on video and SVG overlays, and zIndex: 0 and opacity: 0 being ignored on all image overlays (#10533 by @b84330808)
  • Fix setContent('') leaving the old content in a popup or tooltip (#10532 by @Don-Yin)
  • Fix markers losing their altitude after a drag (#9694, #9689)
  • Fix a double tap firing dblclick three times on iOS with a canvas renderer (#10524)
  • Fix a click firing after a long-press contextmenu on iOS
  • Fix gaps in the API docs (#7213, #7936)

⚠️ Breaking changes

  • The <canvas> element of a canvas renderer no longer receives pointer events, so DOM listeners added to it don't fire; listen on the layers or the map instead. The private Canvas methods _onClick, _onPointerMove, _handlePointerHover, _handlePointerOut and _fireEvent are gone; the map calls _layerAt() to find the path under the pointer (#10523)
  • DomEvent.on(el, 'dblclick', ...) no longer simulates double taps on iOS for elements outside a map (#10524)
  • With detectRetina, minNativeZoom and maxNativeZoom are now adjusted like maxZoom. If you added 1 to them to work around #10111, remove it
1 hours ago
zustand

v5.1.0

The persist and devtools middleware are improved thanks to @Tnalxmsk and @dgesteves!

Release note

  • The persist middleware now supports PromiseLike results from storage reads, migrations, and migration writes. persist.rehydrate() now returns a native Promise<void>, which you can await to ensure hydration is complete.
  • The devtools middleware now supports dispatching store actions directly from Redux DevTools using the new actionCreators option. It also supports nested actions and arguments.

What's Changed

Full Changelog: https://github.com/pmndrs/zustand/compare/v5.0.16...v5.1.0

3 hours ago
shaka-player

v5.3.4

5.3.4 (2026-11-06)

Bug Fixes

  • Ads: Detect mid-rolls when no video frames are presented (#10925) (4b53b29)
  • Ads: Exclude embedded slots from the interstitial break count (#10926) (a34df11)
  • Avoid a TypeError when a stream fails during unload (#10927) (13c95f3)
  • Avoid an unhandled rejection when a preload fails (#10913) (da207d3)
  • DASH: Apply PTO timeline adjustment only once (#10881) (012f2d9), closes #10604
  • DASH: Avoid continuous manifest updates when minimumUpdatePeriod is 0 (#10919) (6621d9f)
  • DASH: Keep the text timestamp offset when video is in another Period (#10895) (4c6950a)
  • Don't reuse failed segment prefetches on retry (#10902) (031c17f)
  • Fix a MediaSource leak when unload() is interrupted (#10886) (680afb5)
  • Fix leaks and hangs when destroy() interrupts load() or attach() (#10928) (44ad8ae)
  • Fix TypeError when the segment index is released during a switch (#10885) (3ca0b6c)
  • Make codec rejection in the MediaSource polyfill work (#10868) (1e8d709)
  • MKV: preserve raw MP3 continuity across clusters (#10857) (8f1ac8f)
  • MSF: Stop waiting early for a fill fetch stream that never shows up (#10898) (0c27a46), closes #10892
  • Preload: Choose the initial variant from the player's bandwidth estimate (#10827) (c33a7e8), closes #10825
  • Text: Align wrapped lines correctly on older browsers (#10851) (bc6b3e6)
  • UI: Fix mouse wheel seeking on the seek bar (#10880) (f6c4c00), closes #10879
  • UI: Keep the keyboard focus indicator visible over the video (#10906) (f9a59bc), closes #3146
  • UI: Keep the seek bar preview in sync with the live seek range (#10924) (cd026a3)
  • UI: Make tooltips dismissible and hoverable (#10904) (d1175c5), closes #3146

Performance Improvements

3 hours ago
shaka-player

v5.2.17

5.2.17 (2026-11-06)

Bug Fixes

  • Avoid a TypeError when a stream fails during unload (#10927) (50eee10)
  • Avoid an unhandled rejection when a preload fails (#10913) (1ec0c22)
  • DASH: Apply PTO timeline adjustment only once (#10881) (3b935f4), closes #10604
  • DASH: Avoid continuous manifest updates when minimumUpdatePeriod is 0 (#10919) (194f63f)
  • Don't reuse failed segment prefetches on retry (#10902) (8843db8)
  • Fix a MediaSource leak when unload() is interrupted (#10886) (0e256fd)
  • Fix leaks and hangs when destroy() interrupts load() or attach() (#10928) (3c5e038)
  • Fix TypeError when the segment index is released during a switch (#10885) (02fb39f)
  • Preload: Choose the initial variant from the player's bandwidth estimate (#10827) (42023af), closes #10825
  • Text: Align wrapped lines correctly on older browsers (#10851) (a022633)
  • UI: Keep the seek bar preview in sync with the live seek range (#10924) (dfb27fe)

Performance Improvements

3 hours ago
shaka-player

v4.16.56

4.16.56 (2026-11-06)

Bug Fixes

  • Avoid a TypeError when a stream fails during unload (#10927) (99e8065)
  • DASH: Apply PTO timeline adjustment only once (#10881) (4c2828d), closes #10604
  • DASH: Avoid continuous manifest updates when minimumUpdatePeriod is 0 (#10919) (2c0a937)
  • Don't reuse failed segment prefetches on retry (#10902) (ec936f4)
  • Fix a MediaSource leak when unload() is interrupted (#10886) (2871369)
  • Fix leaks and hangs when destroy() interrupts load() or attach() (#10928) (fb44afc)
  • Fix TypeError when the segment index is released during a switch (#10885) (ceef8ad)
  • Text: Align wrapped lines correctly on older browsers (#10851) (5e3879b)

Performance Improvements

4 hours ago
shaka-player

v4.15.67

4.15.67 (2026-11-06)

Bug Fixes

  • Avoid a TypeError when a stream fails during unload (#10927) (a39513b)
  • DASH: Apply PTO timeline adjustment only once (#10881) (0a48a9a), closes #10604
  • DASH: Avoid continuous manifest updates when minimumUpdatePeriod is 0 (#10919) (c3ca123)
  • Don't reuse failed segment prefetches on retry (#10902) (a8edc93)
  • Fix a MediaSource leak when unload() is interrupted (#10886) (03a9086)
  • Fix leaks and hangs when destroy() interrupts load() or attach() (#10928) (b62b022)
  • Fix TypeError when the segment index is released during a switch (#10885) (00a5d85)
  • Text: Align wrapped lines correctly on older browsers (#10851) (a5293af)

Performance Improvements

4 hours ago
mobx

mobx-react-plugin@1.0.0

Major Changes

  • 920f93f2aecc843523f7b598e5f808021b5e2933 #4735 Thanks @kubk! - Add mobx-react-plugin, which wraps React components in observer, adds the "use no memo" directive so React Compiler leaves observed components to MobX, and turns async methods of observable classes and objects into MobX flows. Each feature can be switched off.
4 hours ago
mobx

mobx@7.1.5

Patch Changes

6 hours ago
element-plus

2.14.8

2.14.8

2026-11-06

Features

  • Components [image/image-viewer] add viewer-placeholder slot (#24910 by @ixyzorg)

  • Locale add Galician and complete ca, es, eu, pt, pt-br (#24957 by @jlancharro)

  • Components [scrollbar] add configurable scroll masks (#24952 by @YunYouJun)

  • Components [drawer] extend resizable (#23496 by @Joansitoh)

  • Docs add theme editor (#13530 by @YunYouJun)

Bug fixes

  • Locale [ro] translate missing strings (#24951 by @vicuts)

  • Docs generate locales before builds and fix type errors (#24968 by @YunYouJun)

  • Theme-chalk [menu] prevent jitter with custom margins (#24972 by @wjkhhh)

  • Components [date-picker] keep shortcuts reactive in panels (#24994 by @APX403)

  • Locale [tr] translate missing strings (#25002 by @aozmekik)

  • Hooks [useFocusController] clear isFocused when the target gets disabled while focused (#24945 by @ump45nose)

  • Components [input-number] keep aria attributes in sync with props (#25024 by @MindOfqc)

  • Components [select] disable select options with parent (#25041 by @Xapollon430)

  • Components [dialog, drawer] only reference an existing title label (#25040 by @MindOfqc)

  • Components [table-column] update header slot on column replace (#25035 by @dhrupo)

Refactors

  • Utils extract and rename CSS size helpers from splitter (#24997 by @rzzf)
11 hours ago
astryx

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 .

@astryxdesign/cli

Experimental APIs

  • astryx discover lists the experimental NavigationStack and NavigationScreen components from @astryxdesign/lab.

New Features

  • astryx discover lists the experimental CircleCheckIndicator component from @astryxdesign/lab.
  • astryx theme build keeps a theme's componentIcons slot 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 static componentIcons view, 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 --family output, a member supplies its own slot functions through a named import of its whole componentIcons map. The module keeps each slot import as the theme source spells it (--icons-specifier rewrites only the icon registry import), so with --out in 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 is code-block-copy: CodeBlock's copy button passes {copied} and, with no theme entry, draws copy, then check while copied. A slot value is a shared icon name, null to 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, null hides only the glyph. theme.componentIcons reads back the static values. ComponentIconSlotMap and the slot value types are exported from @astryxdesign/core/Icon. astryx theme build carries static slot values into built themes and fails on a slot function, and astryx component lists a component's slots.
  • createPanGesture(element, {axis?, onPan}) and usePanGesture(ref, {axis?, onPan}) report a drag on an element as values: onPan gets {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 move user-select: none. axis limits the drag to 'x' or 'y'; a drag along the other axis is not reported. translation counts from where the drag began, so an element that follows it does not jump, and velocity at ended is 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 behind ref and destroys it at unmount.
  • Motion: a theme's tempo now times the springs of touch motion as well as its CSS durations. Each spring keeps its shape and takes tempo times 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 follow character yet. The motion doc says so.

Fixes

  • 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 as astryx 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 as nodejs or node-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 same type, 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-muted matched the surface in dark mode and barely differed from the hover tint in light mode; it is now #d4d4d4 in light and #474747 in 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 #0171E3 instead 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-accent in 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 that astryx theme template writes 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 swizzle no longer runs gh auth status before it reports: the ready gh issue create line appears when gh is on your PATH, found without running it, so a copy never waits on a process start or on GitHub. The line now also appears when gh is installed but not logged in; running it, gh asks you to log in.
  • astryx swizzle now 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 swizzled Item, CheckboxList, RadioList or TreeList no longer imports a privateAttributes/*.stylex subpath that @astryxdesign/core does not export. A second swizzle reuses an identical copy and refuses a changed one without --overwrite. The receipt's files and filesCopied include the modules it copied.
  • astryx theme build reads 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 CustomTextTypes module augmentation for custom Text variants (#5013)

Performance

  • astryx search and astryx build read 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 doctor in 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, and astryx component without --showcase or --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 list and astryx doctor outside a project no longer load it, and a component lookup loads about 70 fewer modules.
  • astryx search and astryx build look 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 debug or integrations, astryx --version no 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 docs and astryx hook --list load the project but not the code-rewrite modules. Output and exit codes are unchanged.
  • astryx search and astryx build work out a query word's forms once per entry they score, not once per word compared. Answers are unchanged.
  • astryx search and astryx build compute 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 build against the current Core loads Core's root entry only when the theme imports it. A theme that imports only @astryxdesign/core/theme no 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 root require() cannot load, such as one that awaits at its top level, fails with ERR_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 .cjs dependencies imports it. A theme that does not import Core's root no longer loads all of Core and its components for them.

Documentation

  • The calendars guide says Calendar, DateInput, DateRangeInput, and DateTimeInput show the Gregorian calendar unless the application chooses another one. (#9842)
  • Hook docs for useCalendarDays, so astryx hook useCalendarDays and astryx search return it, and a useCalendarDays — Month Grid example block: a compact month table built from the hook's weeks and dayNames, 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 with astryx template useCalendarDaysHookUsage <path>.
  • Hook docs for useIcon, so astryx hook useIcon and astryx search return it, and a useIcon — Status Row example block: a custom status row draws the success, warning and error icons with useIcon, and under a Theme that overrides those icons the same row draws the theme's icons. Copy it with astryx template useIconHookUsage <path>.
  • Hook docs for useLinkComponent, so astryx hook useLinkComponent and astryx search return it, and a useLinkComponent — Article Cards example 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 with astryx template useLinkComponentHookUsage <path>.
  • Hook docs for useOverlay, so astryx hook useOverlay and astryx search return it, and a useOverlay — Product Quick View example block: a product Card spreads the hook's containerRef and containerProps and 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 with astryx template useOverlayHookUsage <path>.
  • Hook docs for useTableSortableState, so astryx hook useTableSortableState and astryx search return it, and a useTableSortableState — Multi-Sort Orders example 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 with astryx template useTableSortableStateHookUsage <path>.
  • Internationalization gains a short Other calendars guide: choose a calendar on InternationalizationProvider or Calendar, values stay Gregorian ISO dates, and most calendars need Temporal (load temporal-polyfill/full/global where it is missing). A new astryx docs calendars namespace has the detail: the supported calendars, loading Temporal on 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 and Calendar's calendar prop docs say how to get identical server and browser markup for those calendars: installImplementation() from temporal-polyfill/full/shim on both sides, since the full/global import keeps a native Temporal.
  • InfoTip: the component page, astryx component InfoTip --showcase, and astryx component InfoTip --blocks now 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 reports aria-expanded="true" while the help is pinned, and a screen reader hears the help once when it pins.
  • The usePanGesture page opens with a draggable box in two forms side by side: the hook and createPanGesture, which takes any element, with the same StyleX styles and Astryx tokens. The playing card follows as the advanced example. The createPanGesture page points to that example, and the usePanGesture guidance warns against holding the drag offset in a large component's state.
  • A new useClickableContainer — Clickable Project Card example block makes a project card clickable anywhere with useClickableContainer: 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 with astryx template useClickableContainerHookUsage <path>.
  • A new useImageMode — Photo Caption example block shows a caption over a photo's sky. useImageMode samples only the strip under the caption, and MediaTheme gives 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 with astryx template useImageModeHookUsage <path>.
  • A new useInputContainer — Search Field Wrapper example block builds a custom search field with a leading search icon, padding and a trailing Clear button. useInputContainer puts focus in the input when the icon or the padding is clicked, and a click on Clear stays with Clear. Copy it with astryx template useInputContainerHookUsage <path>.
  • A useMergedRefs — AutoGrowTextArea example 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 with astryx template useMergedRefsHookUsage <path>.
  • A new useScrollLock — Full-screen Mobile Menu example block opens a custom full-screen menu from a Menu button and locks the page behind it with useScrollLock, so the page neither scrolls nor shifts sideways, and closing the menu returns the reader to the same scroll position. Copy it with astryx template useScrollLockHookUsage <path>.
  • A new useScrollOverflow — Filter Strip Edge Fades example block shows a row of active filters that scrolls sideways and fades only the edge where useScrollOverflow reports 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 with astryx template useScrollOverflowHookUsage <path>.

Other Changes

  • 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 search already 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. Their endContent and renderToken work there as in the inline field; hasClear and the overflow modes apply to the inline field, and the Clear and Overflow blocks say so. The rule: docs examples follow adaptive; the stories that are fixtures for inline-field geometry (Caret placement, Logical order, RTL end lane, Long token beside the end lane) keep presentation="popover". The table-inbox and work-item-detail back arrows mirror in right-to-left.

Contributors

Thanks to everyone who contributed to this release:

  • @cixzhang
  • @imdreamrunner
  • @jibin7jose
  • @MannXo
  • @nynexman4464
  • @Sainath-Reddy7
  • @vjeux

@astryxdesign/core

New Features

  • 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 calendar such 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 平成31 in 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; set calendar to 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. Calendar output does not change.
  • useCalendarDays and useCalendarNavigation take an optional calendar (default gregory) 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 calendar other than gregory or iso8601, such as 'persian' or 'hebrew', adaptive-native (the default), the deprecated nativePicker values, and presentation="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 explicit presentation="native" also warns in development. A provider locale with a -u-ca- keyword keeps Gregorian dates and the native control; set calendar to choose a calendar. Values stay Gregorian ISO dates. Without Temporal, calendars other than gregory, iso8601, buddhist, roc, and japanese show Gregorian dates and the native control as before. (#9842)
  • DateRangeInput: show the range in the calendar. DateRangeInput takes a calendar prop, such as 'persian' or 'hebrew', defaulting to the InternationalizationProvider calendar (a provider locale with a -u-ca- keyword keeps Gregorian dates; set calendar to choose one), and writes its trigger text and shows its popover calendar in it. Values stay Gregorian ISO dates. Without Temporal, calendars other than gregory, iso8601, buddhist, roc, and japanese show Gregorian dates. (#9842)
  • InternationalizationProvider takes a calendar prop (CalendarSystem, exported from @astryxdesign/core/i18n), such as 'persian', 'hebrew', 'islamic-umalqura', or 'buddhist', and exposes the resolved calendar on InternationalizationContext as calendar. A locale never selects the calendar: fa-IR resolves to Gregorian until you set calendar="persian", and a provider locale with a -u-ca- keyword, such as th-TH-u-ca-buddhist, keeps Gregorian dates; set calendar to 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 need Temporal. The context keeps the requested calendar; where Temporal is 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. Calendar takes a calendar prop (CalendarSystem), such as 'persian', 'hebrew', 'islamic-umalqura', or 'buddhist', and otherwise shows the InternationalizationProvider calendar. A provider locale with a -u-ca- keyword, such as en-u-ca-buddhist, keeps Gregorian dates; set calendar to choose one. Values do not change: value, min, max, focusDate, and every callback stay Gregorian ISO dates. DateInput, DateTimeInput, and DateRangeInput show 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 need Temporal; where the runtime lacks it, such as Safari or Node.js 24, load a polyfill like temporal-polyfill/full/global on the server and in the browser, or those calendars show Gregorian dates and warn once in development. A server-rendered app loads Temporal on the server too, since a server without it renders Gregorian markup.
  • Built-in date formats follow the calendar of the InternationalizationProvider. With calendar="persian", Timestamp's date, date_long, date_weekday, and date_time formats, the full date in its hover card and accessible name, and PowerSearch date values are written in that calendar, the same one Calendar shows. A provider locale with a -u-ca- keyword keeps Gregorian dates; set calendar to choose one. date_time keeps its time of day. time, the relative formats, system_*, and unix_seconds do not change, and without a provider calendar every output stays the same. formatSharedDate from @astryxdesign/core/utils and formatFilterValue from @astryxdesign/core/PowerSearch/utils take an optional trailing calendar argument that defaults to 'gregory'. Each of these dates names the month and day that Calendar shows for the same day, as does plainDateFormat with a calendar option other than 'gregory'. A calendar that needs Temporal shows Gregorian dates where the runtime lacks it, as the date components do.
  • DateInput, DateTimeInput: type dates in other calendar systems. Both take a calendar prop, such as 'persian', 'hebrew', 'japanese', or 'buddhist', and otherwise use the InternationalizationProvider calendar. A provider locale with a -u-ca- keyword keeps Gregorian dates; set calendar to choose one. value, onChange, min, and max stay 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 the date, date_long, and date_weekday formats use the calendar, and system_date stays the ISO date. Years are years of the calendar, so a Buddhist field reads 2026 as Buddhist year 2026: set min and max on 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 a placeholder shows an example date that the field accepts, and its description names the calendar when it differs from the InternationalizationProvider calendar. Calendars other than gregory, iso8601, buddhist, roc, and japanese need Temporal, 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. useClickableContainer returns onPointerDown; on a container that paints its press with usePressFeedback, spreading it drops a touch press that starts on a nested control. A mouse :active press 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-expand component icon slot with state {expanded}. With no theme entry it draws chevronRight, 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 with defineTheme({componentIcons: {'table-row-expand': …}}); null hides 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-indicator component icon slot with state {direction} ('ascending' | 'descending' | 'none'). With no theme entry it draws arrowUp, arrowDown, or arrowsUpDown, as before. A theme maps it with defineTheme({componentIcons: {'table-sort-indicator': …}}); null hides only the glyph, and the sort control keeps its label, its sorting, and the header's aria-sort.
  • Timestamp: the hover card's copy button resolves its glyph through the timestamp-copy component icon slot with state {copied}. With no theme entry it draws copy, then check while copied, as before. A theme maps it with defineTheme({componentIcons: {'timestamp-copy': …}}); null hides 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 is code-block-copy: CodeBlock's copy button passes {copied} and, with no theme entry, draws copy, then check while copied. A slot value is a shared icon name, null to 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, null hides only the glyph. theme.componentIcons reads back the static values. ComponentIconSlotMap and the slot value types are exported from @astryxdesign/core/Icon. astryx theme build carries static slot values into built themes and fails on a slot function, and astryx component lists a component's slots.
  • Under a finger (a coarse primary pointer), an interactive List row'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 ::after is that invisible layer, so decorate a row with a child instead. An Item that 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 labelled CheckboxInput and a RadioListItem option 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-094 FR29, 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, pass presentation="popover". A mouse and the public useMenuPress hook 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 onChange once, when the hold ends by release or pointer cancel. A click still steps and commits at once.
  • createPanGesture(element, {axis?, onPan}) and usePanGesture(ref, {axis?, onPan}) report a drag on an element as values: onPan gets {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 move user-select: none. axis limits the drag to 'x' or 'y'; a drag along the other axis is not reported. translation counts from where the drag began, so an element that follows it does not jump, and velocity at ended is 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 behind ref and destroys it at unmount.
  • LayoutPanel, ResizeHandle and SideNav take useResizable's output directly in their resizable prop: const resizable = useResizable({…}); <LayoutPanel resizable={resizable}>…</LayoutPanel> <ResizeHandle resizable={resizable} />. The output's props still works, and SideNav's true and 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's collapse and expand, and the region's autoSaveId saves the width. When collapsible also sets a collapsed state, the region wins and SideNav warns in development. Any other element can use the hook's size; 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 ::after is 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 tempo now times the springs of touch motion as well as its CSS durations. Each spring keeps its shape and takes tempo times 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 follow character yet. The motion doc says so.

Fixes

  • A backdrop press that closes a menu or popover open inside a Dialog (purpose="info"), a Drawer with its scrim, or CommandPalette now 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 xstyle or 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 BottomSheetSwitcher step) 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: to finalFocusRef, 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's finalFocusRef or 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. adars for Adar I and 1. rabī for Rabi' I, and still reads 1. adars as 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 Amharic 1/1/8 R. Where every era width moves the fields, a date of an earlier era adds the era, such as Colognian BROC 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 (which ChatLayout uses), useSpeechRecognition and useChatDictation read 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, useChatNewMessages also no longer flags new messages while the list follows the bottom.
  • A checked, enabled, editable CheckboxListItem row now reflects data-selected="selected" on its row, the element that carries its item and list-item targets, so a theme's selected rules 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 CheckboxInput no longer runs a caller's onKeyDown, onKeyUp, onPointerDown, onMouseDown or any other on* prop except onFocus and onBlur. Before, a disabled checkbox stayed focusable and clickable, so a key or pointer press on it still reached them; only onClick was withheld. Now a disabled CheckboxInput runs no caller handler other than onFocus and onBlur, as a disabled Slider, TextInput and TextArea do (spec:AST-085 FR2). An enabled CheckboxInput is unchanged. No API changes.
  • When a LayoutPanel beside a ResizeHandle collapses 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 a useResizable region's resizable props, 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.
  • ComplexSelector with a status and the default attached placement now shows the status icon on its trigger in place of the chevron, as Selector and MultiSelector do, 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 onChange already 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 Dialog or a Drawer is 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 a Drawer without 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 Selector no 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 Item or ListItem row now shows hover and mouse-press paint over its fill, and a highlighted Item row 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 item theme target now reflects the row's state as data-highlighted, data-selected, and data-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 list className and style, give ref the root element type, and give xstyle its 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 useLayer caller) now carries its CSS anchor-name only 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's anchorId as 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 LayoutPanel whose width is the number 0, such as one given a collapsed useResizable region's resizable props or width={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, or accent Link 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 the text target (for example text[data-color="accent"]) no longer reaches Link labels: restyle Link labels through the link target. A theme that styles the text target's color now also keeps any Text with color="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, Item and TreeList rows 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 ::before and the row is its own stacking context; at rest nothing of it applies, so a row's own ::before decoration and its children's stacking are as they were. During a press the layer is the row's ::before, and any ::before property a caller sets, from xstyle (which Item composes 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 ::after or a child element. With a mouse or trackpad the row is unchanged: no layer and no new stacking context.
  • ChatMessageList, Tooltip and HoverCard: the chat log and the span that Tooltip and HoverCard wrap 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-094 FR27, 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, t over 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. useMultiCombobox gets the same behavior. (#9921)
  • With a finger, a press that starts on a control inside a list row with onClick or href, a TreeList row with onClick or children, or a clickable or link Token with onRemove no 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 a useOverflow host) 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, with behavior="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 mocked ResizeObserver callback and asserts synchronously right after it should first await the update by wrapping the call in await 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 group of buttons named "Go to page N", with aria-current="page" on the current page. Now the container is a radiogroup named by the page-indicators label, and each dot is a radio named "Page N" with aria-checked on the current page and no aria-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-exit and --ease-exit, instead of 200 ms, scaled by the theme's tempo, 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-exit or --ease-exit now changes the release too; a theme's tempo still 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 div or span StackItem 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 SideNavItem no longer runs a caller's onKeyDown, onKeyUp, onPointerDown, onMouseDown or any other on* prop except onFocus and onBlur. Before, a key other than Enter or Space on a disabled item still reached onKeyDown, every key reached onKeyUp, and pointer or mouse input that bypasses pointer-events: none (programmatic or assistive-technology input) reached onPointerDown and onMouseDown. This covers the plain, link, collapsed leaf and split-row forms; the flyout trigger of a collapsed parent item is not covered. Now a disabled SideNavItem runs no caller handler other than onFocus and onBlur, as a disabled Slider, TextInput and TextArea do (spec:AST-085 FR2). An enabled SideNavItem is 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 no aria-valuenow, named by its visible string label, its aria-label or 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 with aria-hidden. No app change is needed (AST-017 FR53): a Spinner given a role other than progressbar stays 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 from status to progressbar, 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 #0171E3 instead 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-accent in 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 that astryx theme template writes 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 Tab no longer runs a caller's onPointerDown, onMouseDown, onKeyDown or any other on* prop except onFocus and onBlur. Before, a press or key on a disabled tab still reached them, for both the button and the link form; only onClick was withheld. Now a disabled Tab runs no caller handler other than onFocus and onBlur, as a disabled Slider, TextInput and TextArea do (spec:AST-085 FR2). An enabled Tab is unchanged. No API changes.
  • A toast plays its entrance once. When a Dialog, a modal Drawer or 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 onHide runs once with 'manual'.
  • Tokenizer: in the sheet presentation, endContent renders at the field's inline end and renderToken renders 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 commits onChange at 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, tokenOverflowBehavior and hasAutoFocus still apply to the inline field only.
  • A disabled TopNavItem no longer runs a caller's onPointerDown, onMouseDown, onKeyDown or any other on* prop except onFocus and onBlur. 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; only onClick was withheld. Now a disabled TopNavItem runs no caller handler other than onFocus and onBlur, as a disabled Slider, TextInput and TextArea do (spec:AST-085 FR2). An enabled TopNavItem is 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)
  • useMediaQuery now 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.

Performance

  • 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 LayerProvider from @astryxdesign/core/Layer and 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 OverflowList no 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 (maxRows above 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 public useOverflow hook is unchanged and still reports both on every path.

Documentation

  • 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 --list groups the motion hooks under one category, "Motion & gestures": useAnimatedLayout, useAnimator, useEntryAnimation, usePanGesture and usePressFeedback; the docs of createAnimator and createPanGesture carry the same category. The "Animation" group goes away. useLongPress, useMenuPress and the other interaction hooks stay under "Interaction".
  • Hook docs for useCalendarDays, so astryx hook useCalendarDays and astryx search return it, and a useCalendarDays — Month Grid example block: a compact month table built from the hook's weeks and dayNames, 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 with astryx template useCalendarDaysHookUsage <path>.
  • Hook docs for useIcon, so astryx hook useIcon and astryx search return it, and a useIcon — Status Row example block: a custom status row draws the success, warning and error icons with useIcon, and under a Theme that overrides those icons the same row draws the theme's icons. Copy it with astryx template useIconHookUsage <path>.
  • Hook docs for useLinkComponent, so astryx hook useLinkComponent and astryx search return it, and a useLinkComponent — Article Cards example 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 with astryx template useLinkComponentHookUsage <path>.
  • Hook docs for useOverlay, so astryx hook useOverlay and astryx search return it, and a useOverlay — Product Quick View example block: a product Card spreads the hook's containerRef and containerProps and 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 with astryx template useOverlayHookUsage <path>.
  • Hook docs for useTableSortableState, so astryx hook useTableSortableState and astryx search return it, and a useTableSortableState — Multi-Sort Orders example 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 with astryx template useTableSortableStateHookUsage <path>.
  • Internationalization gains a short Other calendars guide: choose a calendar on InternationalizationProvider or Calendar, values stay Gregorian ISO dates, and most calendars need Temporal (load temporal-polyfill/full/global where it is missing). A new astryx docs calendars namespace has the detail: the supported calendars, loading Temporal on 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 and Calendar's calendar prop docs say how to get identical server and browser markup for those calendars: installImplementation() from temporal-polyfill/full/shim on both sides, since the full/global import keeps a native Temporal.
  • InfoTip: the component page, astryx component InfoTip --showcase, and astryx component InfoTip --blocks now 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 reports aria-expanded="true" while the help is pinned, and a screen reader hears the help once when it pins.
  • InputGroup: the docs no longer say status paints the group border or that the group shares one focus ring. status shows its message below the group and reflects its type as data-status; a focused member stays above its neighbours. isOptional names 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, give xstyle its 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 presentation description no longer says "as before". Nothing a consumer imports or renders changes.
  • The usePanGesture page opens with a draggable box in two forms side by side: the hook and createPanGesture, which takes any element, with the same StyleX styles and Astryx tokens. The playing card follows as the advanced example. The createPanGesture page points to that example, and the usePanGesture guidance warns against holding the drag offset in a large component's state.

Other Changes

  • Internal: the undocumented @astryxdesign/core/internal/calendar-system entry gives Lab's Schedule the calendar that Core's date components resolve, and the helpers that name its months, days, and date ranges as Calendar does. 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 under fa-IR. A locale never selects the calendar.

  • onChange fires while typing only once the date is complete, and once for each date, so partial input such as 12/0 or 12/01/202 no longer reports wrong dates. A two-digit year, such as 12/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 as 1/5/2 or 12/01/202, is not a date: Enter or blur keeps it and shows it as invalid. parseDateInput still reads, on Enter or blur, text the field cannot otherwise read and a date the locale's field order does not have, such as 13/5/2026 in en-US.

  • A typed date outside min, max, or dateConstraints stays in the field with an error that names the allowed range, or says the date is not available when only a dateConstraints rule rejects it, instead of reverting on blur; the error shows even when you set status. 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 (from createMotionAnimator), with its types MotionEngine and MotionEngineOptions, 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's useMotionEngine hook 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), and isInputShield lets 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 tempo wherever 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 whole springs table or calls registerSpringsTable(). Not exported from the package entry point.

  • Internal: usePanGesture copies onPan into 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 its onPan.

  • 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)

Contributors

Thanks to everyone who contributed to this release:

  • @aashish254
  • @cixzhang
  • @duytbp
  • @harjothkhara
  • @HelloOjasMutreja
  • @imdreamrunner
  • @korkt-kim
  • @MannXo
  • @nynexman4464
  • @parinith-web
  • @vjeux

@astryxdesign/richtext

Fixes

  • 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. Its facts now 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.

Contributors

Thanks to everyone who contributed to this release:

  • @potatowagon

@astryxdesign/theme-neutral

Fixes

  • 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-muted matched the surface in dark mode and barely differed from the hover tint in light mode; it is now #d4d4d4 in light and #474747 in 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.

Contributors

Thanks to everyone who contributed to this release:

  • @cixzhang