plass-ui 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/README.md +2 -2
  3. package/dist/components/accordion/PlAccordion.d.ts +9 -4
  4. package/dist/components/accordion/PlAccordion.js +1 -1
  5. package/dist/components/alert/PlAlert.js +1 -1
  6. package/dist/components/anchor/PlAnchor.js +1 -1
  7. package/dist/components/animate-appear/PlAnimateAppear.js +1 -1
  8. package/dist/components/animate-counter/PlAnimateCounter.d.ts +5 -2
  9. package/dist/components/animate-counter/PlAnimateCounter.js +1 -1
  10. package/dist/components/animate-float/PlAnimateFloat.js +1 -1
  11. package/dist/components/animate-headline/PlAnimateHeadline.js +1 -1
  12. package/dist/components/animate-lighting/PlAnimateLighting.js +1 -1
  13. package/dist/components/animate-marquee/PlAnimateMarquee.js +1 -1
  14. package/dist/components/animate-scramble/PlAnimateScramble.js +1 -1
  15. package/dist/components/animate-shake/PlAnimateShake.js +1 -1
  16. package/dist/components/animate-split/PlAnimateSplit.js +1 -1
  17. package/dist/components/animate-typing/PlAnimateTyping.js +1 -1
  18. package/dist/components/app-logo/PlAppLogo.d.ts +51 -15
  19. package/dist/components/area-chart/PlAreaChart.js +1 -1
  20. package/dist/components/avatar/PlAvatar.js +1 -1
  21. package/dist/components/bar-chart/PlBarChart.js +1 -1
  22. package/dist/components/bottom-navigation/PlBottomNavigation.d.ts +15 -5
  23. package/dist/components/bottom-navigation/PlBottomNavigation.js +1 -1
  24. package/dist/components/breadcrumb/PlBreadcrumb.d.ts +16 -3
  25. package/dist/components/breadcrumb/PlBreadcrumb.js +1 -1
  26. package/dist/components/button/PlButton.js +1 -1
  27. package/dist/components/calendar/PlCalendar.js +1 -1
  28. package/dist/components/card/PlCard.d.ts +2 -1
  29. package/dist/components/card/PlCard.js +1 -1
  30. package/dist/components/carousel/PlCarousel.js +1 -1
  31. package/dist/components/checkbox/PlCheckbox.js +1 -1
  32. package/dist/components/chip/PlChip.js +1 -1
  33. package/dist/components/code-block/PlCodeBlock.js +1 -1
  34. package/dist/components/collapsible/PlCollapsible.js +1 -1
  35. package/dist/components/color-picker/PlColorPicker.js +1 -1
  36. package/dist/components/combobox/PlCombobox.d.ts +1 -1
  37. package/dist/components/combobox/PlCombobox.js +1 -1
  38. package/dist/components/command-palette/PlCommandPalette.js +1 -1
  39. package/dist/components/confirm/PlConfirmProvider.js +1 -1
  40. package/dist/components/data-table/PlDataTable.js +1 -1
  41. package/dist/components/date-picker/PlDatePicker.js +1 -1
  42. package/dist/components/date-range-picker/PlDateRangePicker.js +1 -1
  43. package/dist/components/date-time-picker/PlDateTimePicker.js +1 -1
  44. package/dist/components/drawer/PlDrawer.d.ts +27 -2
  45. package/dist/components/drawer/PlDrawer.js +1 -1
  46. package/dist/components/empty/PlEmpty.d.ts +4 -1
  47. package/dist/components/empty/PlEmpty.js +1 -1
  48. package/dist/components/file-picker/PlFilePicker.d.ts +29 -0
  49. package/dist/components/file-picker/PlFilePicker.js +1 -1
  50. package/dist/components/file-picker/index.d.ts +1 -1
  51. package/dist/components/floating-bottom-navigation/PlFloatingBottomNavigation.d.ts +10 -0
  52. package/dist/components/floating-bottom-navigation/PlFloatingBottomNavigation.js +1 -1
  53. package/dist/components/footer/PlFooter.js +1 -1
  54. package/dist/components/gallery/PlGallery.js +1 -1
  55. package/dist/components/gauge-chart/PlGaugeChart.d.ts +11 -2
  56. package/dist/components/gauge-chart/PlGaugeChart.js +1 -1
  57. package/dist/components/header/PlHeader.js +1 -1
  58. package/dist/components/heatmap-chart/PlHeatmapChart.d.ts +1 -1
  59. package/dist/components/heatmap-chart/PlHeatmapChart.js +1 -1
  60. package/dist/components/image/PlImage.js +1 -1
  61. package/dist/components/image/PlImagePreview.d.ts +3 -2
  62. package/dist/components/list/PlList.d.ts +16 -5
  63. package/dist/components/list/PlList.js +1 -1
  64. package/dist/components/menu/PlMenu.d.ts +19 -7
  65. package/dist/components/menu/PlMenu.js +1 -1
  66. package/dist/components/menubar/PlMenubar.js +1 -1
  67. package/dist/components/meter/PlMeter.js +1 -1
  68. package/dist/components/mockup/PlMockup.d.ts +4 -3
  69. package/dist/components/mockup/PlMockup.js +1 -1
  70. package/dist/components/modal/PlModal.js +1 -1
  71. package/dist/components/navigation-menu/PlNavigationMenu.d.ts +52 -8
  72. package/dist/components/navigation-menu/PlNavigationMenu.js +1 -1
  73. package/dist/components/number-field/PlNumberField.d.ts +1 -1
  74. package/dist/components/number-field/PlNumberField.js +1 -1
  75. package/dist/components/otp-field/PlOtpField.js +1 -1
  76. package/dist/components/overlay/PlOverlay.js +1 -1
  77. package/dist/components/page-layout/PlPageLayout.js +1 -1
  78. package/dist/components/panes/PlPanes.d.ts +4 -1
  79. package/dist/components/panes/PlPanes.js +1 -1
  80. package/dist/components/pie-chart/PlPieChart.d.ts +1 -1
  81. package/dist/components/pie-chart/PlPieChart.js +1 -1
  82. package/dist/components/pill/PlPill.js +1 -1
  83. package/dist/components/popover/PlPopover.js +1 -1
  84. package/dist/components/progress-box/PlProgressBox.js +1 -1
  85. package/dist/components/progress-circular/PlProgressCircular.d.ts +11 -1
  86. package/dist/components/progress-circular/PlProgressCircular.js +1 -1
  87. package/dist/components/progress-linear/PlProgressLinear.js +1 -1
  88. package/dist/components/radio-group/PlRadioGroup.js +1 -1
  89. package/dist/components/scatter-chart/PlScatterChart.js +1 -1
  90. package/dist/components/scroll-zone/PlScrollZone.js +1 -1
  91. package/dist/components/segmented-button/PlSegmentedButton.js +1 -1
  92. package/dist/components/select/PlSelect.js +1 -1
  93. package/dist/components/sidebar/PlSidebar.d.ts +12 -0
  94. package/dist/components/sidebar/PlSidebar.js +1 -1
  95. package/dist/components/slider/PlSlider.d.ts +2 -1
  96. package/dist/components/slider/PlSlider.js +1 -1
  97. package/dist/components/sparkline/PlSparkline.d.ts +15 -3
  98. package/dist/components/sparkline/PlSparkline.js +1 -1
  99. package/dist/components/stepper/PlStepper.d.ts +11 -0
  100. package/dist/components/stepper/PlStepper.js +1 -1
  101. package/dist/components/switch/PlSwitch.js +1 -1
  102. package/dist/components/tabs/PlTabs.js +1 -1
  103. package/dist/components/text-field/PlTextField.js +1 -1
  104. package/dist/components/text-link/PlTextLink.js +1 -1
  105. package/dist/components/time-picker/PlTimePicker.js +1 -1
  106. package/dist/components/timeline-chart/PlTimelineChart.js +1 -1
  107. package/dist/components/toast/PlToast.js +1 -1
  108. package/dist/components/toggle/PlToggle.js +1 -1
  109. package/dist/components/tour/PlTour.js +1 -1
  110. package/dist/components/transfer/PlTransfer.js +1 -1
  111. package/dist/components/tree/PlTree.d.ts +12 -0
  112. package/dist/components/tree/PlTree.js +1 -1
  113. package/dist/components/tree-select/PlTreeSelect.js +1 -1
  114. package/dist/components/typography/PlTypography.d.ts +20 -7
  115. package/dist/components/typography/PlTypography.js +1 -1
  116. package/dist/components/visually-hidden/PlVisuallyHidden.d.ts +12 -3
  117. package/dist/components/visually-hidden/PlVisuallyHidden.js +1 -1
  118. package/dist/components/window-pane/PlWindowPane.d.ts +3 -0
  119. package/dist/components/window-pane/PlWindowPane.js +1 -1
  120. package/dist/css/accordion.css +4 -0
  121. package/dist/css/alert.css +5 -0
  122. package/dist/css/anchor.css +6 -0
  123. package/dist/css/animate-appear.css +3 -0
  124. package/dist/css/animate-blink.css +3 -0
  125. package/dist/css/animate-counter.css +4 -0
  126. package/dist/css/animate-fade.css +3 -0
  127. package/dist/css/animate-float.css +3 -0
  128. package/dist/css/animate-grow.css +3 -0
  129. package/dist/css/animate-headline.css +3 -0
  130. package/dist/css/animate-lighting.css +4 -0
  131. package/dist/css/animate-marquee.css +4 -0
  132. package/dist/css/animate-reveal.css +3 -0
  133. package/dist/css/animate-rotate.css +3 -0
  134. package/dist/css/animate-scramble.css +5 -0
  135. package/dist/css/animate-shake.css +3 -0
  136. package/dist/css/animate-slide.css +3 -0
  137. package/dist/css/animate-split.css +4 -0
  138. package/dist/css/animate-typing.css +4 -0
  139. package/dist/css/animate-zoom.css +3 -0
  140. package/dist/css/app-logo.css +2 -0
  141. package/dist/css/area-chart.css +12 -0
  142. package/dist/css/aspect-ratio.css +2 -0
  143. package/dist/css/avatar.css +2 -0
  144. package/dist/css/back-top.css +11 -0
  145. package/dist/css/badge.css +2 -0
  146. package/dist/css/bar-chart.css +10 -0
  147. package/dist/css/base.css +2 -3
  148. package/dist/css/blockquote.css +2 -0
  149. package/dist/css/bottom-navigation.css +4 -0
  150. package/dist/css/box.css +2 -0
  151. package/dist/css/breadcrumb.css +6 -0
  152. package/dist/css/button-group.css +2 -0
  153. package/dist/css/button.css +7 -0
  154. package/dist/css/calendar.css +17 -0
  155. package/dist/css/card.css +2 -0
  156. package/dist/css/carousel.css +10 -0
  157. package/dist/css/chat-bubble.css +6 -0
  158. package/dist/css/checkbox.css +3 -0
  159. package/dist/css/chip.css +8 -0
  160. package/dist/css/code-block.css +6 -0
  161. package/dist/css/collapsible.css +3 -0
  162. package/dist/css/color-picker.css +17 -0
  163. package/dist/css/combobox.css +10 -0
  164. package/dist/css/command-palette.css +7 -0
  165. package/dist/css/confirm.css +9 -0
  166. package/dist/css/container.css +5 -0
  167. package/dist/css/data-list.css +2 -0
  168. package/dist/css/data-table.css +15 -0
  169. package/dist/css/date-picker.css +16 -0
  170. package/dist/css/date-range-picker.css +16 -0
  171. package/dist/css/date-time-picker.css +16 -0
  172. package/dist/css/divider.css +2 -0
  173. package/dist/css/drawer.css +6 -0
  174. package/dist/css/empty.css +2 -0
  175. package/dist/css/fieldset.css +2 -0
  176. package/dist/css/file-picker.css +8 -0
  177. package/dist/css/flex.css +5 -0
  178. package/dist/css/floating-action-button.css +7 -0
  179. package/dist/css/floating-bottom-navigation.css +5 -0
  180. package/dist/css/footer.css +6 -0
  181. package/dist/css/form.css +2 -0
  182. package/dist/css/gallery.css +15 -0
  183. package/dist/css/gauge-chart.css +11 -0
  184. package/dist/css/grid.css +5 -0
  185. package/dist/css/header.css +6 -0
  186. package/dist/css/heatmap-chart.css +10 -0
  187. package/dist/css/highlight.css +2 -0
  188. package/dist/css/hot-keys.css +3 -0
  189. package/dist/css/hover-card.css +9 -0
  190. package/dist/css/how-to-steps.css +3 -0
  191. package/dist/css/icon-button.css +7 -0
  192. package/dist/css/icon.css +1 -0
  193. package/dist/css/image.css +7 -0
  194. package/dist/css/line-chart.css +12 -0
  195. package/dist/css/list.css +2 -0
  196. package/dist/css/menu.css +5 -0
  197. package/dist/css/menubar.css +5 -0
  198. package/dist/css/meter.css +6 -0
  199. package/dist/css/mockup.css +4 -0
  200. package/dist/css/modal.css +5 -0
  201. package/dist/css/navigation-menu.css +4 -0
  202. package/dist/css/number-field.css +10 -0
  203. package/dist/css/otp-field.css +6 -0
  204. package/dist/css/overlay.css +4 -0
  205. package/dist/css/page-layout.css +7 -0
  206. package/dist/css/pagination.css +8 -0
  207. package/dist/css/panes.css +7 -0
  208. package/dist/css/pie-chart.css +10 -0
  209. package/dist/css/pill.css +4 -0
  210. package/dist/css/popconfirm.css +12 -0
  211. package/dist/css/popover.css +11 -0
  212. package/dist/css/portal.css +1 -0
  213. package/dist/css/progress-box.css +5 -0
  214. package/dist/css/progress-circular.css +5 -0
  215. package/dist/css/progress-linear.css +5 -0
  216. package/dist/css/radio-group.css +3 -0
  217. package/dist/css/rating.css +6 -0
  218. package/dist/css/scatter-chart.css +10 -0
  219. package/dist/css/scroll-area.css +2 -0
  220. package/dist/css/scroll-zone.css +15 -0
  221. package/dist/css/segmented-button.css +5 -0
  222. package/dist/css/select.css +9 -0
  223. package/dist/css/show.css +1 -0
  224. package/dist/css/sidebar.css +16 -0
  225. package/dist/css/skeleton.css +2 -0
  226. package/dist/css/slider.css +3 -0
  227. package/dist/css/sparkline.css +10 -0
  228. package/dist/css/spoiler.css +10 -0
  229. package/dist/css/stack.css +5 -0
  230. package/dist/css/stat.css +2 -0
  231. package/dist/css/stepper.css +8 -0
  232. package/dist/css/switch.css +3 -0
  233. package/dist/css/table.css +3 -0
  234. package/dist/css/tabs.css +7 -0
  235. package/dist/css/text-field.css +8 -0
  236. package/dist/css/text-link.css +6 -0
  237. package/dist/css/time-picker.css +16 -0
  238. package/dist/css/timeline-chart.css +10 -0
  239. package/dist/css/timeline.css +6 -0
  240. package/dist/css/toast.css +5 -0
  241. package/dist/css/toggle.css +5 -0
  242. package/dist/css/toolbar.css +2 -0
  243. package/dist/css/tooltip.css +9 -0
  244. package/dist/css/tour.css +10 -0
  245. package/dist/css/transfer.css +13 -0
  246. package/dist/css/tree-select.css +14 -0
  247. package/dist/css/tree.css +3 -0
  248. package/dist/css/visually-hidden.css +2 -0
  249. package/dist/css/window-pane.css +9 -0
  250. package/dist/internal/animate.d.ts +59 -4
  251. package/dist/internal/animate.js +1 -1
  252. package/dist/internal/bottom-bar.js +1 -1
  253. package/dist/internal/calendar.js +1 -1
  254. package/dist/internal/chart-frame.d.ts +130 -11
  255. package/dist/internal/chart-frame.js +1 -1
  256. package/dist/internal/commit-change.d.ts +38 -0
  257. package/dist/internal/commit-change.js +1 -0
  258. package/dist/internal/data-table.d.ts +2 -2
  259. package/dist/internal/data-table.js +1 -1
  260. package/dist/internal/date.d.ts +9 -0
  261. package/dist/internal/date.js +1 -1
  262. package/dist/internal/defaults.d.ts +34 -1
  263. package/dist/internal/defaults.js +1 -1
  264. package/dist/internal/direction.d.ts +8 -2
  265. package/dist/internal/direction.js +1 -1
  266. package/dist/internal/form.d.ts +43 -0
  267. package/dist/internal/form.js +1 -1
  268. package/dist/internal/format.d.ts +4 -1
  269. package/dist/internal/format.js +1 -1
  270. package/dist/internal/gallery.d.ts +28 -0
  271. package/dist/internal/gallery.js +1 -1
  272. package/dist/internal/glow.d.ts +7 -2
  273. package/dist/internal/glow.js +1 -1
  274. package/dist/internal/heading.d.ts +11 -0
  275. package/dist/internal/heading.js +1 -0
  276. package/dist/internal/highlight.d.ts +4 -2
  277. package/dist/internal/highlight.js +1 -1
  278. package/dist/internal/labels.d.ts +5 -0
  279. package/dist/internal/labels.js +1 -1
  280. package/dist/internal/layout-box.d.ts +35 -0
  281. package/dist/internal/layout-box.js +1 -0
  282. package/dist/internal/lazy.d.ts +37 -0
  283. package/dist/internal/lazy.js +1 -0
  284. package/dist/internal/media.d.ts +6 -0
  285. package/dist/internal/media.js +1 -1
  286. package/dist/internal/page-layout.d.ts +24 -13
  287. package/dist/internal/page-layout.js +1 -1
  288. package/dist/internal/picker.d.ts +1 -1
  289. package/dist/internal/picker.js +1 -1
  290. package/dist/internal/progress.d.ts +24 -0
  291. package/dist/internal/progress.js +1 -1
  292. package/dist/internal/search.d.ts +12 -0
  293. package/dist/internal/search.js +1 -1
  294. package/dist/internal/sizer.d.ts +15 -8
  295. package/dist/internal/sizer.js +1 -1
  296. package/dist/internal/styles.d.ts +22 -6
  297. package/dist/internal/styles.js +1 -1
  298. package/dist/internal/wheel.d.ts +13 -1
  299. package/dist/internal/wheel.js +1 -1
  300. package/dist/internal/window.js +1 -1
  301. package/dist/locales/de.js +1 -1
  302. package/dist/locales/es.js +1 -1
  303. package/dist/locales/fr.js +1 -1
  304. package/dist/locales/ja.js +1 -1
  305. package/dist/locales/ko.js +1 -1
  306. package/dist/locales/zh-Hans.js +1 -1
  307. package/dist/styles.css +1 -1
  308. package/dist/tailwind.css +758 -40
  309. package/dist/tokens.css +758 -40
  310. package/dist/types.d.ts +10 -1
  311. package/package.json +18 -17
package/CHANGELOG.md CHANGED
@@ -4,8 +4,226 @@
4
4
 
5
5
  ## vNext (2026--)
6
6
 
7
+ ## 1.8.0 (2026-10-07)
8
+
9
+ ### Breaking changes
10
+
11
+ - **An `aria-label` given beside a visible `label` names the control in the label's place.** On a `PlTextField`, a `PlCheckbox`, a `PlSwitch`, a `PlRadio`, a `PlRadioGroup` and an `inline` `PlColorPicker`, Base UI pointed the control's `aria-labelledby` at the visible label, which outranks an `aria-label`, so the `aria-label` was ignored, where a native control, ARIA and the Flutter build's `semanticLabel` all let it win. It now names the control, and the label's words are not read after it; a `PlRadioGroup` or a `PlSegmentedButton` inside a `PlFieldset` takes it in the legend's place the same way. A caller's `aria-labelledby` still outranks both, and a control with no `aria-label` is named as before. Remove an `aria-label` that was only there beside a `label`, or make it contain the label's words, so a reader who says the visible label to a voice-control tool still reaches the control.
12
+ - **An `aria-label` given beside a visible `label` names a meter or a progress indicator in the label's place.** On a `PlMeter`, a `PlProgressLinear`, a `PlProgressCircular` and a `PlProgressBox`, Base UI pointed the root's `aria-labelledby` at the visible label, which outranks an `aria-label`, so the `aria-label` was ignored, where a native `<meter>` or `<progress>`, ARIA and every field control in this package let it win. It now names the meter or the indicator, and the label's words are not read after it. A caller's `aria-labelledby` still outranks both, and an indicator with no `aria-label` is named as before. Remove an `aria-label` that was only there beside a `label`, or make it contain the label's words, so a reader who sees the label and hears the name can tell both belong to the same indicator.
13
+ - **`PlNavigationMenuItemProps` is a union of a link item and an item that opens a panel.** That is what lets a link item take `render` while an item with children refuses it. A type that extended it with `interface … extends` becomes `type … = PlNavigationMenuItemProps & {…}`, and an `Omit<…>` of it now applies to each half; a call to the component is unchanged.
14
+
15
+ - **A `PlAppLogo` with `src` and no `name` needs `alt`.** Such a logo drew its picture with an empty `alt`, so rendered as the link home it was a link with no name. `PlAppLogoProps` is now a union, and TypeScript reports a `src` with neither: write what the picture says, or `alt=""` for a decorative picture. A call with a `name` is unchanged, artwork passed as `children` is not asked for `alt`, and a JavaScript caller still gets `alt=""`. A type that extended `PlAppLogoProps` with `interface … extends` becomes `type … = PlAppLogoProps & {…}`.
16
+
17
+ ### Added
18
+
19
+ - **`PlVisuallyHidden` takes a `headingLevel`, which renders `<h1>`–`<h6>` in place of the `<span>`.** A heading that structures the page for a screen reader without appearing in the design took `render={<h2 />}`, and `eslint-plugin-jsx-a11y`'s `heading-has-content` reports that empty element on every use. `headingLevel={2}` now renders the `<h2>` itself, clipped like any other content and still revealed by `focusable`. A value outside 1–6 is ignored, and `render` still wins when both are given.
20
+ - **`PlProgressCircular` takes `diameter`, a number of pixels the ring is drawn at instead of `size`'s rung.** The ring's diameters stopped at 32px, so a ring for a loading screen had to override the `<svg>`'s `width` and `height` with CSS, outside the API and broken the moment the markup changed. `diameter={96}` now draws the ring 96 pixels across with the stroke thickened in the ladder's own proportion, while `size` still sets the gap and the text beside it. A value that is not a finite number above zero is ignored, and a ring without it is drawn as before.
21
+ - **`PlFilePicker` takes `onAdd`, which hands over what one drop or dialog pick kept and what it turned away together.** `onReject` and `onFilesChange` were called one after the other for the same drop and neither knew about the other, so an error shown only when a drop produced nothing usable took a flag set in one, cleared in the other and read in a microtask, relying on an order the docs never stated. `onAdd` is called once per batch, after the other two, with a `PlFileBatch` of `kept`, `rejected` and `source` (`'drop'` or `'dialog'`). A drag that carried no file, and a removal, call nothing.
22
+ - **`PlTypography` takes a `headingLevel`, which renders `<h1>`–`<h6>` without changing the type scale `level` picks.** Keeping a heading's size while changing its level took `render={<h2 />}`, and `eslint-plugin-jsx-a11y`'s `heading-has-content` reports that empty element on every use. `headingLevel={2}` now renders the `<h2>` itself. It works on any `level`, so `level="body"` with `headingLevel={2}` is an `<h2>` at body size. A value outside 1–6 is ignored, and `render` still wins when both are given.
23
+ - **`PlFilePicker` takes `preventDocumentDrop`, so a file let go beside the box does not replace the page.** A browser opens a file dropped where nothing takes it, in place of the page, or downloads it, so missing the box by a few pixels threw away whatever the reader had filled in. With `preventDocumentDrop` on, while the picker is mounted and neither disabled nor read-only, a file dragged over the rest of the page shows the no-drop pointer and is not dropped. A drop target that takes the file itself, another picker or a native `<input type="file">` among them, and a drag that carries no file are left alone. It is off by default.
24
+ - **`PlassHeadingLevel` names the levels `1` to `6` a heading can take, and every `headingLevel` prop is typed with it.** `PlAccordion` had its own `PlAccordionHeadingLevel`, so a second component that drew a heading would have needed a second name for the same six numbers. `PlAccordion` and `PlDrawer` now both take `PlassHeadingLevel`, and `PlAccordionHeadingLevel` is still exported as a deprecated alias of it.
25
+
26
+ - **Glass is opaque for a reader who has asked for less transparency.** The sheets, the `glass` controls and the popups drew a translucent fill over a `backdrop-filter` blur, which is the most expensive thing on a screen and exactly what a reader who turned on reduced transparency asked not to see through. With `prefers-reduced-transparency: reduce` the blur is gone and each step of the glass is laid over the theme's own `--plass-surface`, in the light and the dark theme, with the hairline, the shadows and the focus ring as they were. A `glass` `PlOverlay` lays its dim over the surface, and a `PlWindowPane` is opaque whatever its `transparency`. The scrim behind a `PlModal`, a `PlDrawer`, a `PlCommandPalette`, a `PlTour` and a `scrim` `PlOverlay` keeps its dim and loses its blur, which is now the `--plass-scrim-blur` token. Browsers that do not report the preference draw the glass as before.
27
+
28
+ - **`PlBreadcrumbItem`, `PlListItem`, `PlMenuItem`, `PlBottomNavigationItem`, `PlFloatingBottomNavigationItem`, `PlNavigationMenuLink` and a `PlNavigationMenuItem` that is a link take `render`, so a router's `Link` can be the item's link.** These items wrote a fixed `<a href>`, so a site that navigates with Next.js's or React Router's `Link` could only switch to `onClick`, which turned the item into a `<button>` and took the link a crawler follows off the page. `render={<Link href="/docs" />}` now draws the item's link on that element, keeping the item's classes, its `aria-current`, a menu row's role and highlight, and the arrow keys. A `render` alone makes an item a link, as an `href` does, and an `href` on the element wins over the item's own, as on `PlTextLink`; a breadcrumb's `structuredData` reads a step's address off it too. A disabled item and the current step of a trail go nowhere, so they leave it out, and a `PlNavigationMenuItem` that opens a panel is a trigger, so its type refuses `render` beside children.
29
+
30
+ - **`PlDrawer` takes `headingLevel`, so its title sits at the level the page's outline needs.** The title was always an `<h2>`, so a drawer inside a section headed by an `<h2>` put its title beside that heading rather than under it. It takes `1` to `6` and defaults to `2`, in both modes, and an `overlay` drawer's title still names the dialog. Only the element changes, never the type scale, and a level outside the six is read as `2`.
31
+
32
+ - **Every chart takes `initialWidth`, the width it is drawn at until its box has been measured.** A chart is laid out from the width its box is measured at, so the HTML a server sent held an empty box of the right height, and the plot, the axes and a `PlGaugeChart`'s reading arrived only once the script ran. Given `initialWidth`, the server and the render that hydrates its HTML draw the whole chart at that width, a drawing wider than its box is cut at the box's edge, and the measured width takes over as soon as the page has hydrated. A chart without it renders as before.
33
+
34
+ - **`PlStepper` and `PlTree` take `keepMounted`, which keeps content that is not on screen in the document.** A stepper rendered only the panel of the step `active` was on, and a tree built no row of a shut branch, so neither was in the HTML a server sends or a search engine reads. With `keepMounted` a stepper renders every step's panel and a tree builds every branch, and what is not on screen stays hidden, out of the focus order and off the accessibility tree. A kept panel keeps what it holds when the reader steps away, and the tree's arrow keys still walk only the visible rows. It is off by default.
35
+
36
+ - **`PlDrawer` and `PlSidebar` take `keepMounted`, which keeps a closed drawer's content in the document.** A closed `overlay` drawer took its content out of the page, so below `collapseBelow` a sidebar's links were missing at the width a search engine indexes a page at. With `keepMounted` the content stays, hidden, out of the focus order and off the accessibility tree. It is off by default.
37
+
38
+ ### Changed
39
+
40
+ - **A `PlCard` `headerAction` is centred on the first line of the title.** The header row lined the action up with the top of the title, so a `PlIconButton` beside a one-line title sat with its middle about 10px below the title's at `md`, and further below at the larger sizes. The action's middle now lands on the middle of the title's first line, or of the subtitle's when there is no title, and stays there while the title wraps. The header grows to hold whichever of the two is taller, so a taller action moves the title down rather than hanging below it. A header with only an action keeps it at the top.
41
+ - **A `PlFilePicker`'s whole field is its drop area, and the element its native attributes land on hears the drag.** The four drag listeners sat on a `<div>` inside the field, so a drag event dispatched on the element a `data-testid` finds, which is the field, never reached them, and a file let go over the label or the messages was handled by the browser instead. They are now on the field itself, so the label, the box, the file list and the messages are one drop area. `onDragEnter`, `onDragOver`, `onDragLeave` and `onDrop` passed to the picker still run, after the picker has handled the event.
42
+ - **A new `value` makes `PlAnimateCounter` count on from the figure on screen.** It counted from `from` every time, so a figure that moved from 4,812 to 4,830 dropped to 0 and counted all the way up again, although the page said it went on from where the last count landed. It now counts from the figure the last count landed on, or from the frame a count still running had got to, over the same `duration` with the same `easing`. The first count and a replay, such as a second hover or a new `play`, still start at `from`, and reduced motion, `paused` and `trigger` work as before. Until it lands, the box keeps the width of the figure it started from, so a count down to a smaller number narrows once, when it lands, rather than at every digit it loses.
43
+
44
+ - **A disabled `PlNavigationMenuItem` is no longer a Tab stop.** Base UI keeps a disabled trigger in the Tab order, so Tab stopped on an item that opens nothing, where a disabled `PlBottomNavigationItem`, the Flutter menu and most disabled controls are passed over. A disabled trigger or link is now passed over by Tab as well as by the arrow keys, and is still announced as unavailable and drawn dimmed.
45
+
46
+ - **`PlConfirmProvider` and `PlSidebar` fetch their dialog when it is needed rather than with the page.** An app shell of `PlassProvider`, `PlToastProvider`, `PlConfirmProvider`, `PlPageLayout`, `PlSidebar` and `PlHeader` carried the whole dialog stack, 37.2 kB gzip. The confirm dialog is now fetched once the page is idle and mounted by the first question, which still fades in and takes the focus; the sidebar's drawer is fetched once the window is narrower than `collapseBelow`. The same shell is 25.1 kB, and a server render is unchanged.
47
+
48
+ - **`plass-ui/css/<component>.css` registers only the library modules its component reaches.** `plass-ui/css/base.css` scanned every one of the library's internal modules, so a Tailwind project that registered one component still generated the utilities of all of them: `PlButton` on its own came to 14.3 kB gzipped. Each manifest now lists the modules its component reaches, and `base.css` is the tokens alone, which brings `PlButton` to 11.4 kB, `PlButton` with `PlTextField` to 12.1 kB and `PlTypography` to 8.9 kB, against 22.5 kB for `plass-ui/tailwind.css`. Nothing changes in a project's imports, and nothing a component uses is left out.
49
+
7
50
  ### Fixed
8
51
 
52
+ - **The column of span names in a `PlTimelineChart`'s table is headed in the language of the label pack.** It was headed by the English word "label", which no pack could translate, so a Korean page's table was headed "label", "시작", "종료". It now comes from the new `name` label key, "Name", translated in all seven packs. The key is React's alone, as the Flutter chart draws no table.
53
+ - **A `reference` on a `PlTimelineChart` is read as a time.** The chart's description wrote the line's value as a number, so a reference at a date was read as a count of milliseconds, "Freeze 1.8T". It is now written as the chart writes a span's start and end, "Freeze Jan 4, 2026". The Flutter build makes the same change.
54
+ - **A `PlTimelineChart` is described by its rows.** Its description read the one series the chart hands the frame in its rows' place, so a screen reader heard "1 1" after the chart's name. It now names each row and the stretch of time its spans on the plot cover, from the first start to the last end, "Design Jan 1, 2026 – Jan 18, 2026, Build Jan 8, 2026 – Jan 26, 2026", and a row with nothing on the plot by its name alone; a `reference` is still read after them, and the table under the chart still lists every span.
55
+ - **An `aria-label`, `aria-labelledby` or `aria-describedby` on a chart reaches its picture.** On a `PlLineChart`, `PlAreaChart`, `PlBarChart`, `PlScatterChart`, `PlTimelineChart`, `PlPieChart`, `PlHeatmapChart`, `PlGaugeChart` or `PlSparkline` all three landed on the box around the picture, a `<div>` with no role, so they named and described nothing: the picture went on being called by `label` or by "Chart", a gauge or a sparkline with no `label` was no picture at all, and the table under a chart was captioned by `label` alone. They now name the picture, a caller's `aria-labelledby` first, then an `aria-label` in `label`'s place, then `label`, then "Chart", and the table under it is named by the same three in the same order: an `aria-labelledby` points it at the same element, and an `aria-label` or `label` is its caption. A `PlPieChart`'s value column is headed by the same words as that caption, and by nothing when the pie is named by reference alone or not at all, where it was headed by `label` or else by "1". Either of the two makes a gauge a named image and a sparkline a named `role="img"` with its values written out beside it, as `label` does, and a gauge still reads its value after the name. An `aria-describedby` describes the picture, read ahead of the chart's own description, and a sparkline with no name, which is off the accessibility tree, takes none. A chart given none of them is named and described as before.
56
+ - **An endless `PlAnimate*` effect given a finite `repeat` just as its run ended under the setting for less movement is not taken for one that landed.** `animationend` arrives a frame or more after the keyframe has finished, and a count given in between was read as the count of the run that ended, so the effect was marked as landed and stood on its last frame once the setting was turned off, where it should play on from its clock. The run is now measured by when it ended, and one that ended while the effect was still endless is left unmarked.
57
+ - **`PlVisuallyHidden`'s ref is typed as the `HTMLElement` it is handed.** It was typed as a `<span>`, although `headingLevel` and `render` hand it a heading or any other element, so an editor showed `ref.current` as a span that was not one.
58
+ - **An `aria-label` or `aria-labelledby` on a `PlTransfer` or a `PlCalendar` names it.** Both landed on the root, a `<div>` with no role, so they named nothing, and two transfers on one page read their lists as the same "Available" and "Selected" twice. The root now takes `role="group"` when it is given either, so the name lands on the pair or the calendar as a whole; a calendar's grid is still named by its month heading, and with neither the root has no role, as before.
59
+ - **The French, Japanese and Simplified Chinese label packs write their punctuation one way throughout.** Two of the French messages for files a `PlFilePicker` turned away used the typewriter apostrophe where the rest of the pack uses `’`, and the words a `PlTextLink` that opens a new tab says to a screen reader were bracketed in ASCII in the Japanese and Simplified Chinese packs, which use the full-width pair. They now match the rest of each pack.
60
+ - **Muted text clears 4.5:1 on a floating sheet in the dark theme.** `--plass-muted-fg` measured 4.4:1 on the densest step of the glass over the dark page and 4.2:1 over an opaque sheet, which is what a modal or a select's list over a card is and what a reader who has asked for less transparency sees everywhere, so the labels, descriptions and hints there fell below what body text needs. It is a shade lighter in the dark theme, `#a0a9bf` for `#99a2ba`, and clears 4.5:1 on the page and on every step of the glass in both themes. The chart baseline, mixed from it, follows.
61
+ - **An `aria-label` or `aria-labelledby` on a `PlSelect`, `PlCombobox`, `PlNumberField`, `PlOtpField`, `PlSlider`, `PlColorPicker`, `PlDatePicker`, `PlTimePicker`, `PlDateTimePicker`, `PlDateRangePicker`, `PlTreeSelect` or `PlFilePicker` names the control.** Both landed on the field's wrapper, a `<div>` with no role, so a field with no room for a visible `label`, such as a select in a toolbar, had no way to name its trigger, and a slider's `aria-label` named its group but not the thumb a screen reader lands on. They now go to the trigger, the input, every slot of a code as the start of its name, every thumb of a slider, or a file picker's browse button as the start of its name, and an `inline` `PlColorPicker` keeps them on its group. An `aria-label` names the control in a visible `label`'s place, a caller's `aria-labelledby` outranks both, and a picker's trigger still reads its value after the name.
62
+ - **A `PlFilePicker`'s glyph follows `size`, as the Flutter build's does.** It was sized against the font size the page around the picker set, since the box sets none of its own, so it was the same at every `size`, about 23px in a 13px page, and changed with the page; a larger glyph needed a stylesheet rule that outranked the picker's. It is now drawn against the title under it, so it grows and shrinks with `size` and keeps to it whatever the page sets.
63
+ - **A `PlFilePicker` stops showing a file over it when the drag ends without leaving the box.** A drag cancelled with Escape while it was over the box sent the box no `dragleave` in any browser, so the box kept its drag-over edge, wash and halo until the next drag left it or a file was dropped on it. It now goes back to resting as soon as the drag is over: on a drop or a `dragend` anywhere in the page, when the window loses focus, or when the pointer moves again.
64
+ - **A `PlAnimateHeadline` let go of its `index` holds the line that is up for a whole `interval`.** It went on with the wait it had before it was controlled, so a reel given `index` 600ms into an `interval` of 1000 turned 400ms after `index` was taken away, however long it had been controlled. The wait now starts when `index` is taken away, as it does in the Flutter build, and a controlled reel still runs no timer of its own.
65
+ - **A `PlAnimateHeadline` whose lines were all taken away and given back holds the line that comes up for a whole `interval`.** It went on with the wait it had before the lines were taken away, so when the same line came back it turned early. A line that comes back to an empty reel now arrives anew, as it does in the Flutter build.
66
+ - **A `PlAnimateHeadline` that has stopped on its last line, or holds only one, brings the next line up once the line that is up has had its `interval` when a line is added or `loop` is turned on.** It waited a whole `interval` from the change, so a reel stopped on its last line 400ms into an `interval` of 1000 held it for another whole second once a line was added. The wait now goes on counting while there is no line to turn to, and a line already up for its `interval` gives way at once. A change of lines or `loop` while the reel is turning leaves the wait as it was, as before.
67
+ - **A `PlAnimateHeadline` let go by `paused` or back on screen holds its line only for what was left of the wait.** The timer started the wait over each time, so a reel paused 600ms into an `interval` of 1000 held its line for another whole second once let go, the first line waited its whole `delay` again as well, and a reel paused and let go more often than its `interval` never turned. A new `interval` or `delay` is now measured from when the wait began too, and turns the reel at once when the wait is already past it. A reel stopped by its trigger, such as a hover that leaves, still waits a whole `interval` once it starts again.
68
+ - **A `PlAnimateMarquee` held by `paused` keeps the one scrolling copy reduced motion showed when the setting is turned off.** The copies came back and the box clipped them while the strip was still paused, so the reader could no longer scroll to what was past its edge, and the box stopped being a tab stop. It now stays one copy in a box that scrolls, where the reader left it and still a tab stop, until `paused` is let go, and then runs as the moving strip, as before.
69
+ - **A new `delay` given to a `PlAnimateTyping` that is still waiting for its first character is measured from when the wait began.** The typing went on with what was left of the old delay, so a `delay` of 1000 changed to 1600 600ms in typed at 1000, and one changed to 300 waited another 400ms. It now types once the new delay is over, at once if the wait has already gone past it, as the other effects do. A pause still holds the wait, and a new delay once the line has started typing changes nothing.
70
+ - **A `PlAnimateTyping` that types its line once and landed under reduced motion keeps the whole line when the setting is turned off.** Under the setting the whole line is shown, but the typing stood where it was, so giving movement back emptied the line and typed it again. A typing that types once now lands once its `delay` is over, as the other effects do, and keeps the line until it runs again, such as a new `play`, a second hover or a new `text`. Given movement back while it is still waiting, it is empty for the rest of the wait and then types, one never let go still waits for its trigger, and a typing that repeats types again from its first character, as before.
71
+ - **A `PlAnimateLighting` held by `paused` keeps the even glow reduced motion showed when the setting is turned off.** The arc came back at the angle its clock stood at, `0deg` for an endless light, while it was still paused. It now stays an even glow until `paused` is let go, and then turns from where its clock stands, as before: from its start for an endless light.
72
+ - **A finite `PlAnimate*` effect held by `paused` before its `delay` is over keeps the frame reduced motion showed when the setting is turned off.** A paused keyframe stands where its clock stopped, which for a run held before its delay was over is ahead of its start, so giving movement back turned a fade held by `paused`, from the mount or during its wait, from fully drawn to nothing, and a turn from its `to` angle back to its `from`. It now stays on that frame until `paused` is let go, as an endless one does, and then waits out the rest of its delay and runs, as before.
73
+ - **A `PlAnimateMarquee` running across inside one running down keeps its own way.** The vertical strip's rule reached every strip inside it, so a horizontal marquee nested in a vertical one stacked its items in a column and moved down rather than across. It now reaches only the vertical strip's own tracks.
74
+ - **A `PlAnimateCounter` played again with a `delay` shows `from` while it waits.** A replay, such as a second hover, stood on the figure the last count landed on through its wait and then jumped to `from`, where a keyframe shows its first frame while it waits out its delay. It now shows `from` from the moment the replay starts. A new `value` still counts on from the figure on screen, and waits on that figure.
75
+ - **The two copies of an endless vertical `PlAnimateMarquee` stay in step when reduced motion is turned off.** Under the setting a vertical strip was not switched off as a horizontal one is: its first copy ran a pass in no time, which sent `animationend`, and once movement was given back it went on from its own clock while the second copy started afresh, about 100ms behind it, so a gap opened in the strip. A vertical strip is now switched off under the setting as a horizontal one is, and both copies start together when the setting is turned off.
76
+ - **An effect nested inside another `PlAnimate*` effect no longer takes the outer one's settings.** An effect wrote only the slots it had a value for, and the rest came from the effect around it, so a fade inside an endless `PlAnimateRotate` with `easing="linear"` eased linearly, an `alternate` fade with a `repeat` of 2 inside it landed visible under reduced motion rather than faded out, and a fade inside a `timeline="view"` effect followed the scroll. Every effect now reads only its own, a `PlAnimateLighting`, `PlAnimateMarquee` and `PlAnimateHeadline` included.
77
+ - **A finite `PlAnimateLighting` or `PlAnimateMarquee` that landed under reduced motion stays where it landed when the setting is turned off.** Under the setting both were switched off, so nothing landed, and giving movement back played the whole run again from the start. A light or a strip that ends now runs in no time under the setting, as the other effects do, so `animationend` arrives, and once it has landed it stands still when the setting is turned off, the strip where it started and the light where a turn ends, until it runs again. What either draws under the setting is unchanged, and an endless one is still switched off and goes on from its start.
78
+ - **A new `delay` no longer stops a `PlAnimateCounter` or `PlAnimateScramble` that is under way.** The run measured itself against the new delay, so a count with a `delay` of 100 given 1000 400ms in stopped on its frame and, once the new delay had gone by, counted again almost from `from`, and a scramble went back to noise. A run under way now keeps the delay it started with, and a new one applies from the next run. While the run is still waiting, a new delay is measured from when the wait began, as before.
79
+ - **A `PlAnimateCounter` or `PlAnimateScramble` that landed under reduced motion keeps its answer when the setting is turned off.** Under the setting a counter shows its figure and a scramble its line, but the run's clock stood where it was, so giving movement back counted from `from` again or turned the line back into noise: a linear 1000ms count to 1,000 stood at 300 after 300ms. A run now lands once its `delay` is over, as a keyframe does, and stays on its answer until it runs again, such as a new `play`, a `visible` counter seen again or a new `value`. Given movement back while it is still waiting, a run shows `from`, or noise, for the rest of the wait and then runs, and one never let go still waits for its trigger.
80
+ - **An endless `PlAnimate*` effect held by `paused` keeps the frame reduced motion showed when the setting is turned off.** A paused keyframe stands where its clock stopped, which for a run that landed under the setting is the start of a pass and for one paused before it moved is its start, so giving movement back turned an endless fade held by `paused` from fully drawn to an opacity of 0.001 and an endless turn from its `to` angle back to its `from`. It now stays on that frame until `paused` is let go, including one paused with movement before the setting arrived, and then goes on from where its clock stands, as before. A run waiting for its trigger still waits on its first frame.
81
+ - **A new `delay` no longer sends a `PlAnimate*` effect that is under way back to waiting.** The new delay applied to the running keyframe, so a fade with a `delay` of 300 given 1000 at 400ms vanished and started again from nothing once the new delay had passed. A run under way now keeps the delay it started with, and a new one applies from the next run, such as a second hover, a new `play` or a `visible` effect seen again. While the effect is still waiting, a new delay is measured from when the wait began, as before. Each child of a `stagger` and each part of a `PlAnimateSplit` decides for itself, and `PlAnimateMarquee` and `PlAnimateLighting` keep their delay the same way.
82
+ - **A finite `PlAnimate*` effect that landed under reduced motion stays on its last frame when the setting is turned off.** Under reduced motion an effect runs in no time and shows its last frame, but its keyframe went on counting from when the run began, so turning the setting off before the run would have ended put it that far back into the run and played the rest: a 1000ms fade given movement back 250ms after it began dropped to a quarter of its opacity and faded in again. A run that has landed, including one that was running or paused when the setting arrived, now stays where it landed until it runs again. An endless run still goes on from where its passes would have got to, a run still waiting out its `delay` plays when the wait is over, a `timeline="view"` effect follows the scroll again, and one never let go still waits for its trigger.
83
+ - **Under `plass-ui/styles.css`, the label or the glyph of a button of fixed height sits in its middle in WebKit.** These buttons set their height and no padding top and bottom, and WebKit pads a button 2px above and 3px below, so the label of a `PlButton` and of every component drawn with one, a `PlToggle`, a `PlTab`, the trigger of a `PlMenubarMenu`, a `PlNavigationMenuItem` and a `PlSelect`, the days, months, years and times of `PlCalendar`, `PlDatePicker`, `PlDateRangePicker`, `PlDateTimePicker` and `PlTimePicker`, a pressable `PlChip` and the action of a toast sat half a pixel high, as did the glyph of a `PlNumberField` stepper, a `PlFloatingBottomNavigationItem`, the controls of a `PlWindowPane`, and the × of a `PlChip`, a `PlCombobox` chip, a picker's clear button, `PlAlert`, `PlModal`, `PlDrawer`, `PlPopover`, `PlTour`, a toast and a file in `PlFilePicker`. The × of a file in `PlFilePicker` was also squeezed narrower than it is tall in every browser. Chromium and Firefox pad a button evenly top and bottom, so nothing else moves there; a `p-*` passed in `className` still outranks the new padding, and a project that runs Tailwind's Preflight sees no change.
84
+
85
+ - **Under `plass-ui/styles.css`, the fields inside a component no longer keep the padding a browser gives an `<input>` or a `<textarea>`.** The stylesheet's reset leaves padding alone, and these fields set none of their own, because the shell's padding is the field's. The text of a `PlTextField`, a `PlNumberField` and a `PlCombobox`, and of the search field in `PlTransfer`, `PlDataTable` and `PlTreeSelect`, started 2px further in than the value of a `PlDatePicker` beside it in Chromium and Firefox and 1px in WebKit, and so further in than a `float` label resting over it; a `multiline` `PlTextField` stood 4px taller than its `rows` put it, so a one-row field was taller than a single-line one, and its text started 2px lower and 2px further in; and the query of a `PlCommandPalette` started further in than its row puts it. A project that runs Tailwind's Preflight saw none of this and sees no change.
86
+
87
+ - **Under `plass-ui/styles.css`, the buttons inside a component no longer keep the padding a browser gives a `<button>`.** The stylesheet's reset leaves padding alone, and these buttons set none on one axis or on either. A `PlBreadcrumbItem` with an `onClick`, and a fold that opens, stood 2px taller than a link or a text step in Chromium and Firefox and 5px in WebKit; a `PlPill` with an `onClick` stood taller than one without; the value in the trigger of `PlDatePicker`, `PlDateRangePicker`, `PlDateTimePicker`, `PlTimePicker`, `PlColorPicker` and `PlTreeSelect` started 4 to 6px further in than the field's padding put it, and a field that is not `fullWidth` was wider by twice that; the chevron and the × of `PlCombobox` were wider than their glyphs; the current `PlCarousel` dot's target was wider than the rest, so the row moved as the slide changed; and in WebKit the chosen `PlColorPicker` swatch stood taller than it was wide. A project that runs Tailwind's Preflight saw none of this and sees no change.
88
+
89
+ - **The interaction light stays under the pointer over what a lit surface holds.** A pointer over the input of a `PlTextField`, `PlNumberField` or `PlCombobox` placed the light by its distance on the screen from the surface's outer edge, so inside a `transform`, a field in a scaled `PlMockup` for example, the light fell short of the pointer by as much as the surface was scaled, and on any page it sat a border's width off the place a pointer over the surface itself gives it. It is now placed in the surface's own pixels from inside its border, as it already was over the surface itself.
90
+
91
+ - **A paused `PlAnimateTyping` holds its line when its `text` changes.** Given a new `text` while `paused` was on, it drew as many characters of the new string as it had typed of the old one, so "Hel" became "Wor", the start of a line it had never typed. The line on screen now stays as it was until `paused` is let go, and the new text is then typed from its first character, as a new run that arrives while it is paused already is. A screen reader is given the new text whole at once, as before.
92
+
93
+ - **A fixed `PlBottomNavigation` or `PlFloatingBottomNavigation` inside a scaled ancestor publishes the room it takes there.** Inside a `transform`, a bar in a scaled `PlMockup` for example, a `fixed` bar is fixed to that box and covers what is laid out in it, but `--plass-bottom-navigation-height` was the height the bar was drawn at, so a page inside that box reserved half the room at half the scale. It is now the height the bar is laid out at, the same number it publishes with nothing scaling it.
94
+
95
+ - **A `PlAnchor` with a `target` inside a scaled ancestor lights the heading the reader has reached.** Inside a `transform`, a shell in a scaled `PlMockup` for example, the list measured how far each heading was below the top of `target` at the size it was drawn at, and took `offset` and the panel's top border off that in the panel's own pixels, so the reading line sat at another depth and a heading was lit before it had reached it. The distance is now measured in the panel's own pixels, which is what `offset` is counted in.
96
+
97
+ - **Charts inside a scaled ancestor read the column or the mark under the pointer.** Inside a `transform`, a chart in a scaled `PlMockup` for example, `PlLineChart`, `PlAreaChart`, `PlBarChart`, `PlScatterChart` and `PlTimelineChart` measured the pointer at the size the chart was drawn at and compared it with a plot laid out in the chart's own pixels, so the panel read another column or another mark than the one under the pointer, the crosshair was drawn through that other column, and <code v-pre>tooltip={{ mode: 'item' }}</code> named another series. The pointer is now read in the chart's own pixels, across and down, in both orientations.
98
+
99
+ - **A `PlAnimateSlide` or `PlAnimateRotate` with `trigger="visible"` and `once={false}` plays to its end inside a mask.** Once such an effect had started, whether it was on screen was judged by where it was drawn rather than where it sits, so a slide a tenth of the way into a box with `overflow: hidden` read as leaving and stopped there until the page scrolled, a turn whose corners swung outside the box started again from its first frame, and a `mode="out"` slide that had left the box went out again each time a scroll moved it. It is now judged by where the element sits, as it already was while the effect waited, so each plays through once, and one that leaves the screen and comes back still plays again.
100
+
101
+ - **A disabled `PlBreadcrumbItem` or `PlListItem` that has only an `onClick` is announced as an unavailable button.** It was drawn as a `<span>` or a `<div>` carrying `aria-disabled` and no role, so a screen reader read it as plain text, where a disabled `PlChip` stays a button. It is now the `<button>` it is when enabled, with `disabled`, so Tab passes over it and its `onClick` is not called, and its look is unchanged. A disabled row says nothing about being `selected`, as a disabled link row does. A step or row with neither `onClick` nor an address stays plain text.
102
+
103
+ - **The time columns of `PlTimePicker` and `PlDateTimePicker` show the chosen row on a scaled page.** When a `transform` reached the popup, on a page scaled as a whole for example, a column measured how far down its chosen row was at the size it was drawn at and scrolled that far in its own pixels, so it opened with the chosen hour or minute out of view, and an arrow, Home or End key chose a row it left out of view. The row is now scrolled into view.
104
+
105
+ - **A `PlScrollZone` inside a scaled ancestor goes to the next item and follows a drag.** Inside a `transform`, a zone in a scaled `PlMockup` for example, the strip measured the distance to the next or the previous item at the size it was drawn at and scrolled that far in its own pixels, so a button stopped part of the way there, and a mouse drag slid the strip at a different speed from the pointer, across, down and under RTL. A button now lands on the item and a drag keeps the strip under the pointer.
106
+
107
+ - **A `PlWindowPane` inside a scaled ancestor moves and resizes in its own pixels.** Inside a `transform`, a window in a scaled `PlMockup` for example, a corner, an edge or the resize key started from the size the window was drawn at, so the window jumped to another size as soon as it was taken hold of; a drag moved the window at a different speed from the pointer; an arrow key on the title bar stopped short of the edge of the view; and a window with no `height` rolled up from, and came back down to, the height it was drawn at. All of them now start from the window's own size, a key moves it 16 of its own pixels, and a drag keeps the title bar or the corner under the pointer.
108
+
109
+ - **A `visible` effect that is not `once` no longer starts again while it stays on screen.** Its observers report every time its share on screen crosses one of their steps, and each report started the effect again, so a half-visible `PlAnimateCounter` counted from `from` again as the page scrolled, and a fade or a slide went back to its first frame. It now starts when it comes on screen, and plays again only after it has left and come back.
110
+
111
+ - **A `PlCarousel` inside a scaled ancestor shows the slide it is sent to.** Inside a `transform`, a carousel in a scaled `PlMockup` for example, the strip measured the distance to a slide at the size it was drawn at and scrolled that far in its own pixels, so it opened on, and went to, a slide short of the one its dots marked, in either direction and under RTL. It now lands on the slide.
112
+
113
+ - **A `PlSidebar` inside a scaled ancestor resizes from the width it is laid out at.** Inside a `transform`, a sidebar in a scaled `PlMockup` for example, a drag or an arrow key started from the width the sidebar was drawn at, so the width jumped as soon as it began and a drag moved the edge at a different speed from the pointer. Both now start from the sidebar's own width, a key moves it 16 of its own pixels, and a drag keeps the edge under the pointer.
114
+
115
+ - **A disabled `PlBreadcrumbItem` or `PlListItem` that has an `href` or a `render` is announced as an unavailable link.** It was drawn as a `<span>` or a `<div>` carrying `aria-disabled` and no role, so a screen reader read it as plain text and never said it was unavailable. It is now an `<a>` with no address, `role="link"` and `aria-disabled="true"`, as a disabled `PlBottomNavigationItem` link is, so it still goes nowhere and Tab still passes over it.
116
+
117
+ - **A new `value` no longer starts a `PlAnimateCounter` its trigger has not started, nor a new line a `PlAnimateScramble`.** A `manual` counter with `play` off, or a `visible` one not yet seen, started counting as soon as its `value` changed, before it was asked to or where nobody could see it. A new `value` before then only changes what the count arrives at: the counter goes on showing `from` and counts to the new value when its trigger comes, and a scramble's new line waits the same way. A counter that has started still counts on from the figure on screen, and `PlAnimateShake` still plays on every new `replay`.
118
+
119
+ - **`PlAnimateCounter` in compact notation writes the figures on its way with no more decimals than the answer.** Compact notation keeps two significant digits where that is the finer rounding, so a count to "5K" showed "1.6" and "1.7K" on the way, and a count to "4.8K" could show "0.048". A frame with more fraction digits than the answer is now rounded to the answer's, so a count to "5K" goes through whole numbers and then "1K", "2K", and a frame that already fits, such as "123" or "1.2K", is written as compact notation writes it. A `roundingPriority` written in `format` is left as it is.
120
+
121
+ - **A `PlAnimateTyping` let go during its `delay` or between two characters waits only what was left of that wait.** Paused during the `delay`, or in the wait before the next pass types its first character, it waited the whole `delay` again once it was let go, and paused between two characters it waited a whole character's time again. Every wait now goes on with the time that was left of it, as the hold already did.
122
+
123
+ - **A disabled `PlBottomNavigationItem` or `PlFloatingBottomNavigationItem` that is a link is announced as an unavailable link.** It is drawn on an `<a>` with no `href`, which has no role, so a screen reader read it as plain text and never said it was unavailable. It now carries `role="link"` with its `aria-disabled`, as a disabled `PlNavigationMenuItem` link does, and Tab still passes over it.
124
+
125
+ - **A `PlPanes` inside a scaled ancestor sizes its panes in its own pixels.** Inside a `transform`, a split in a scaled `PlMockup` for example, the split measured the size it was drawn at rather than the size it is laid out at, so a pane with a pixel `defaultSize` moved as the page hydrated, a pixel `minSize` held it at the wrong width, an arrow key moved the line the wrong distance, and a drag ran slightly ahead of the pointer. Pixel lengths, minimums and an arrow key's 16 pixels are now measured in the split's own pixels, and a drag keeps the handle under the pointer.
126
+
127
+ - **A `PlAnimateTyping` paused during a hold holds for the rest of it when it is let go.** Paused during the hold before `erase` deletes its line, it started deleting one character after it was let go; paused during the hold between two passes without `erase`, it held for the whole hold again. Both now hold for the time that was left of the hold.
128
+
129
+ - **`PlAnimateCounter` given significant digits in `format` writes the figures on its way with no more decimals than the answer.** With `maximumSignificantDigits` alone, a frame was rounded to the significant digits and nothing else, so a count to 4,812 with three of them showed "12.3" on the way to "4,810". A frame now keeps to both the significant digits and the answer's number of fraction digits, whichever is coarser.
130
+
131
+ - **Moving to another step of a `PlStepper` from inside its panel puts the focus in the new step's panel, or on the step itself when it has none.** A Next button in a panel went with the panel, or was hidden with it under `keepMounted`, so the focus fell to the page's body and the next Tab started from the top of the page. The panel that arrives now takes the focus, is announced by its step's name, and the next Tab goes on to what is in it, in both orientations; a step with no panel takes the focus on its own button. The panel is not a Tab stop, and a focus anywhere else, on a step for example, stays where it is.
132
+
133
+ - **A `PlHeader` or `PlFooter` that changes only its `position` inside a `PlPageLayout` has its room reserved again.** The layout measured a bar when it arrived or changed size, so a bar switched from `static` to `fixed` left the page reserving nothing for it and covering the top of the content, or its end for a footer, until something resized it. The layout now measures it on that commit. A collapsed `PlSidebar` also reads the page's direction once rather than on every render, and its drawer turns round with a page that turns over while it is open.
134
+
135
+ - **A `PlSegmentedButton`'s tile and a `PlFloatingBottomNavigation`'s key follow an inline `style` that moves the chosen segment or the current destination.** They measured again only on a commit that changed the value, a prop, the children, a class or a `dir`, so a `style` that moved a segment without resizing the set left the tile where it was until the next change. They now measure on that commit too, and a parent render that hands over the same styles still measures nothing.
136
+
137
+ - **A padded `PlPanes` or `PlMockup` no longer moves as the page hydrates.** A split measured its border box where its panes' `flex-basis` percentages resolve against its content box, so with padding or a border a pane given a pixel `defaultSize` shrank as the split measured itself, and a handle fell behind the pointer and the arrow keys. A mockup measured its content box where the stylesheet scales the device to the box inside its border, so a padded mockup drawn on the server shrank at hydration. Both now measure the box their CSS reads, and a padded mockup's device fills the box inside its border, padding included, as its first paint already did.
138
+
139
+ - **A calendar or date picker no longer works out which day its week starts on at every render.** With no `weekStartsOn` given, `PlCalendar`, `PlDatePicker`, `PlDateRangePicker` and `PlDateTimePicker` built an `Intl.Locale` on every render, and with no `locale` named a formatter as well, about 30 µs each time. The answer is now kept for each locale.
140
+
141
+ - **A calendar keeps its month and weekday names on the right day when the browser's time zone changes while the page is open.** The pickers kept the formatters they write names with, and a formatter keeps the zone it was made in, so after the zone changed a calendar could head September as August or start its week on Saturday until the page was reloaded. The formatters are now made again when the zone changes.
142
+
143
+ - **A `PlSlider` inside a `PlassProvider` writes its value in the provider's `locale`.** It read only its own `locale`, so on a page that named its locale once on the provider a slider wrote `1,234.5` while the number fields and meters beside it wrote `1.234,5`. It now reads the provider's, as `PlNumberField` and `PlMeter` do, and its own `locale` still wins.
144
+
145
+ - **A long `PlDataTable` whose `columns` are written inline no longer draws every row again whenever the component holding it draws.** A row was skipped only while `columns` kept its identity, and an array written inline is new on every render. A row now compares its columns by `key`, `align` and `render`, so a new array that draws alike passes over every row. A `render` written inline is still a new function each time and still draws every row, so a long table keeps its `render` functions, or the whole `columns`, outside the component or in `useMemo`.
146
+
147
+ - **A `PlAnimateMarquee` strip and an indeterminate `PlProgressLinear` segment travel the way the nearest `dir` says, however deeply one region is nested in another.** The stylesheet answered one region inside another, so a right-to-left region inside a left-to-right one inside a right-to-left page ran the English way. A browser with `:dir()` now reads the direction the strip or the segment is in; Chrome 111 to 119, which lack it, still read the `dir` attribute one region deep.
148
+
149
+ - **A disabled `PlNavigationMenuItem` that is a link no longer goes anywhere.** `disabled` reached only an item that opens a panel, so a link item marked disabled kept its `href`, or the router's `Link` it was given through `render`, and a press or Enter still followed it. A disabled link item is now drawn dimmed, as a disabled trigger is, on an `<a>` with no address, announced as an unavailable link. Neither the arrow keys nor Tab stop on it.
150
+
151
+ - **A chart given `height` as a CSS length is drawn at that height.** The prop took any CSS length, but a `PlLineChart`, `PlAreaChart`, `PlBarChart`, `PlScatterChart` or `PlTimelineChart` laid a string out at no height and drew nothing, and a `PlPieChart`, `PlGaugeChart` or `PlHeatmapChart` ignored it and drew at the height its `size` gives. The box now takes the length and the chart is drawn at the height the box is measured at, a scatter's largest bubble included. A server sends the box at that height with nothing drawn in it, with `initialWidth` too, and the chart is drawn once the page has hydrated. A height in pixels and the default are drawn as before.
152
+
153
+ - **Moving to another step of a horizontal `PlStepper` gives that step a panel of its own.** The stepper drew every step's panel in one element, so what had been typed into an uncontrolled field stayed there when the next step put a field of the same kind in the same place. Each step's panel is now built anew when the reader reaches it. A vertical stepper already worked this way, and a `keepMounted` panel still keeps what it holds.
154
+
155
+ - **A `PlSelect` holds itself at the width of its widest option with one hidden element rather than one per option.** To keep the trigger from changing width with its value, the select laid every option out in a hidden element of its own, so ten selects of 250 countries put about 2,500 extra elements in the page, each with a generated box to style and lay out. All the options are now laid out in one hidden element, a line each, and the trigger is exactly as wide as before, including for a label with extra spaces or a line break in it. `PlDatePicker`, `PlTimePicker`, `PlDateTimePicker`, `PlColorPicker` and `PlDateRangePicker` hold their width the same way.
156
+
157
+ - **A preview, a viewer, a confirm dialog or a sidebar drawer whose chunk fails to load no longer breaks the page around it.** `PlImage`'s preview, `PlGallery`'s viewer, `PlConfirmProvider`'s dialog and a collapsed `PlSidebar`'s drawer are fetched on their own, and when one could not be fetched, because the connection dropped or a deploy replaced the chunk under an open page, `React.lazy` threw to the nearest error boundary and took down everything under it. The import is now tried once more, and if that fails too, the preview, viewer or drawer does not open and a question is answered as `Esc` answers it: `confirm` resolves `false` and `alert` resolves. The next press or question asks for the chunk again.
158
+
159
+ - **Answering a `PlConfirmProvider` question puts the focus back on the button that asked it.** The button that should hold the focus took it with `autoFocus` before the dialog noted where the focus had been, so confirming, cancelling or pressing `Esc` left the reader on the page's body, and the next Tab started again from the top of the page. The dialog now moves the focus onto that button itself, and gives it back to whatever held it before the question was asked. A question that follows another in the open dialog, queued or asked as soon as the last one was answered, still takes the focus on its own button.
160
+
161
+ - **A server-rendered `PlPageLayout` keeps the padding and the edges of a `fixed` or full-width `sticky` `PlFooter` clear in the first paint.** The footer's height was known only once the layout measured it after hydration, so the HTML a server sent drew each sidebar a whole footer too tall and let a fixed footer cover the end of the content by its whole height. The stylesheet now reserves the footer's padding for its `size` and `density` and its edges until the layout has measured it. The height of what is inside a footer cannot be known before then, so at hydration the sidebars still get that much shorter, and the end of the content still moves up from under a fixed footer by that much.
162
+
163
+ - **The sidebars under a full-width `sticky` `PlHeader` in a server-rendered `PlPageLayout` start below the header in the first paint, and hydrating no longer resizes them.** The header's height was known only once the layout measured it after hydration, so the HTML a server sent drew each sidebar as tall as the whole window: a short page's footer sat a header's height further down and moved up when the page hydrated, and on a page scrolled before hydration the sidebar was stuck under the header. The stylesheet now reserves the header's floor for its `size` and its edges for a sticky header that spans the full width, as it already did for a `fixed` one, and the measured height takes over when the page hydrates. A header whose content is taller than its `size` allows still moves the sidebars once, when it is measured.
164
+
165
+ - **A `PlAnimateTyping` with a finite `repeat` plays only the passes it was asked for when it is paused and let go.** Each time `paused` let it go, it counted its passes from the first again, so a typing paused during the last of its passes played all the earlier ones again. It now goes on with the pass it was on, and one let go during the hold between two passes holds and then goes on to the next.
166
+
167
+ - **A `PlAnimateTyping` line is in the page's text once at every moment.** The server's HTML ended the line in "||", the caret and the room kept for it, and once the line was typed out it was text twice, in the clipped copy for a screen reader and in the drawn copy, so a search engine that runs the page's script indexed it twice. The characters typed so far and a caret given as a string are now generated content; the clipped copy is the only text and is what a copy gives. How it looks and what a screen reader hears are unchanged, and a caret given as an element is still drawn as it is.
168
+
169
+ - **`PlAnimateCounter` writes the figures on its way with no more decimals than the answer.** Every frame was written with `format`, and the default one writes up to three decimals, so a count to 4,812 showed figures like "1,105.535" on the way, wider than the answer, and the box reserved for the answer widened on every frame but the first and the last. A frame is now rounded to the answer's own number of fraction digits: a count to a whole number shows whole numbers, and a count to 12.5 shows one decimal. The rest of `format` still decides how a frame is written: a currency keeps its two decimals in every frame, and an explicit `maximumFractionDigits` is the most a frame can show, not a number of decimals to fill. A frame that rounds to zero from below is drawn as 0 rather than -0.
170
+
171
+ - **`Esc` closes a `PlCommandPalette`, and every close puts the focus back where it was.** The palette's search list took `Esc` for a popup of its own that it never had and stopped it there, so the key did nothing although the page said it closed the sheet. The field also took the focus before the dialog noted where it had been, so closing the palette by running a command or from the parent left the focus on the page's body. `Esc` now closes the sheet on the first press, whatever is typed in the field, and the focus goes back to whatever held it before the palette opened.
172
+
173
+ - **A parent's render no longer makes a `PlTabs` bar measure its indicator again.** Base UI's indicator measures the list and the chosen tab while it renders, and the bar made a new indicator on every render, so every render of whatever held the bar read the list's box, its scroll width and the tab's computed style for a tab that had not moved. The indicator is now made again only when its look or the bar's direction changes, and it still moves on the same frame as before when the value changes, when a tab is renamed or resized, when the bar is resized, and when the bar or the document turns round.
174
+
175
+ - **A `PlCodeBlock` asks for the highlighter again after the first request for it failed.** If the highlight.js core did not arrive once, for example because the connection dropped while it was in flight, the failure was kept, and every code block on the page stayed plain for as long as the page was open. A failed request is now forgotten: the next block that needs the highlighter asks for it again, and blocks still show their code plain until the colours arrive.
176
+
177
+ - **A `PlCalendar` rendered on a server hydrates in a browser whose clock is on another day.** Today was read in each runtime's own zone, so at 08:00 in Seoul a UTC server marked yesterday and, on the 1st of a month, opened on the month before, and React threw the server's tree away. A server render and its hydration now read the clock in UTC, and the calendar moves its month, its today mark and its tab stop to the reader's day as soon as hydration is done. A calendar rendered only in the browser reads the browser's clock from the first paint, as before.
178
+
179
+ - **A server-rendered `PlMockup` sized by a CSS length is drawn before it hydrates.** At `width='100%'` (the default) or any CSS length, the device was `visibility: hidden` until the browser measured its box, so nothing on its screen was painted until the script ran. The stylesheet now works the scale out from the box with container query units until the measurement replaces it, and only a box measured with no room is hidden.
180
+
181
+ - **A server-rendered `PlPanes` is drawn at its `defaultSize`s before it hydrates.** Every pane took an even share until an effect measured the split, so a split whose first pane asked for a quarter was drawn half and half, then moved. Where the sizes settle the split on their own (percentages, pixel lengths naming every pane, pixel lengths beside a pane that takes the rest), the panes are now sized during render. `rem` and `em` lengths still wait for the measurement.
182
+
183
+ - **A `PlSegmentedButton` or `PlFloatingBottomNavigation` measures its tile only when something it is placed by has changed.** The effect that places the tile listed `children`, a new reference on every render a parent does, so each one read the chosen segment's box back. The first placement also forced a second layout with `void offsetWidth`, and several on one screen recalculated the layout between each other's writes. The tile is now measured on a commit that changed the choice, a prop the segments are sized by, the segments themselves or the direction, and its transition comes back on in the next frame. What is drawn and how it moves are unchanged.
184
+
185
+ - **A `PlSidebar` that cannot be resized no longer reads the document's computed style while it renders.** It turned its default `16rem` width into pixels on every render for a handle it does not draw.
186
+
187
+ - **A server-rendered `PlPageLayout` draws its content below a `fixed` `PlHeader` in the first paint, and hydrating no longer moves it.** The header's height was measured in an effect after hydration, so the HTML a server sent drew the content and the sidebars under the header and pushed them down when the page hydrated, a layout shift of 0.06 to 0.23 on the docs demos. The stylesheet now reserves the header's floor for its `size` and its edges until the layout has measured it, and the layout measures before the browser paints, reading both bars before writing either, so a resize no longer recalculates the page's style once per bar. A header whose content is taller than its `size` allows still moves the content once, when it is measured.
188
+
189
+ - **A list or a paragraph passed to `PlEmpty` as its `description` stays where it was put.** The description was drawn in a `<p>`, which cannot hold a block, so a server-rendered page split it around the list, and React reported a hydration mismatch and rendered the tree again. It is now a `<div>` like the title, and it looks and reads as before.
190
+
191
+ - **A marquee and an indeterminate `PlProgressLinear` inside a left-to-right region of a right-to-left page travel the English way.** The stylesheet turned them round whenever any ancestor said `dir="rtl"`, so an English quote, a code sample or a form set to `dir="ltr"` inside an Arabic page ran its marquee backwards and swept its progress bar from the wrong end. The nearest `dir` now decides, in either order of nesting.
192
+
193
+ - **A `PlSkeleton` highlight runs left to right on a right-to-left page too, as the design language and the Flutter build have it.** The React highlight turned round under RTL because it was placed from the inline start, so the same placeholder swept one way in English and the other in Arabic. A sweep is a light crossing a surface rather than something read along a line, and it now crosses from left to right in every writing direction. The indeterminate `PlProgressLinear` segment still runs toward the reader's end.
194
+
195
+ - **An endless animation rests while it is off screen.** `PlAnimateLighting` redraws on the main thread every frame, and it, the marquee, float and blink, every `repeat="infinite"` keyframe, a looping `PlAnimateHeadline`, an endless `PlAnimateTyping` and the typing caret ran for the whole visit wherever they were on the page. Each now pauses once it leaves the screen and goes on from the frame, line or character it stopped on. Finite effects play as before.
196
+
197
+ - **`PlAnimateCounter` and `PlAnimateScramble` hold the width of their finished line from the first paint.** The box grew with the figure as it counted and with the noise as it settled, so the text beside it moved on every new digit. The finished line is now laid out under the drawn one and not drawn, in the server's HTML too, so a frame narrower than it leaves the text around it where it is. A scramble inside running text is one box now, and moves to the next line whole rather than breaking across two.
198
+
199
+ - **A text effect's line is in the page's text once.** `PlAnimateCounter`, `PlAnimateScramble` and `PlAnimateSplit` put the line they draw beside a clipped copy for a screen reader, and both were text, so the server's HTML and what a search engine indexed held it twice: a counter on 12,345 read "12,3450", and a split or scrambled line said its sentence twice. `PlAnimateHeadline` ran its lines together. The drawn copy is now generated content, the clipped copy is the only text and is what a copy gives, and a headline's lines are separated by a space that is not laid out. Selecting the line no longer highlights the drawn copy, but copying still gives the line once.
200
+
201
+ - **Typing in the search field of a long `PlTreeSelect` or `PlTransfer` no longer folds every label again for each character.** `PlTransfer` also did it again on every tick while a search was active. The matches are unchanged.
202
+
203
+ - **A page with many `PlCodeBlock`s is coloured one short task per block, and each grammar is fetched together with the highlighter.** Blocks waiting on one grammar were coloured back to back in one long task, and the grammar was only requested once the core had arrived. Each block still shows plain text until its colours are ready.
204
+
205
+ - **Ticking a row of a long `PlDataTable` draws that row again, not every row.** With `paging: 'scroll'` every tick redrew every row and its `PlCheckbox`, each row searched the whole list of ticked keys, and every search keystroke folded every cell again. A row now redraws only when something about it changed, the selection is looked up in a set, and each row's search text is folded once until `rows` or `columns` change.
206
+
207
+ - **A masonry `PlGallery` rendered on a server has the right number of lanes before hydration.** Its lane count was worked out in JavaScript from the window, so the server's markup always held two lanes and a desktop page laid the gallery out again once React ran. It is now dealt for every column count `columns` names and the stylesheet draws the one for the window's width, so nothing moves at hydration and a resize no longer re-renders it. A `columns` that is not a whole number, which threw, now rounds as the grid's column count does.
208
+
209
+ - **`preview` on `PlImage` and `PlGallery` no longer downloads the overlay before anyone reaches for it.** The closed preview sat in a `Suspense` boundary on every page that offered one, so its chunk was fetched on load and a server render left a boundary that hydration had to switch to client rendering. The overlay is now rendered from the first open, and its chunk is requested when the pointer, the focus or a touch arrives on the picture or tile.
210
+
211
+ - **A `PlImage` rendered on a server is drawn as soon as its file arrives.** The server's markup held the picture at `opacity: 0` until React had hydrated, with `priority` too, so a hero's Largest Contentful Paint waited for the page's JavaScript and a page without JavaScript never showed it. A picture in a server's markup is now drawn over its placeholder as it decodes, unless it has a `watermark` or `protect`, which keeps it hidden until React has seen it load so the original never shows without its mark or guard; a picture mounted in the browser, and a `src` changed later, still fade in. `PlGallery`'s tiles change with it.
212
+
213
+ - **A horizontal `PlTabs` measures its bar only when something about the bar changed.** It read the list's scroll width after every render, including a parent's render that changed nothing in the bar, and read the computed style on every scroll event. It now measures when the bar is resized, when a tab is added, removed or relabelled, and when the direction turns over; a scroll reads only the scroll position, and the fade still updates on the frame the tabs changed in.
214
+
215
+ - **Scrolling the page over a horizontal `PlTabs` or `PlScrollZone` whose contents fit no longer waits for the main thread.** Both put a wheel listener that can cancel the wheel on the strip as soon as they mounted, so the browser ran it before scrolling the page on every notch, even over a bar that had nothing to scroll. It is now on only while the strip overflows, and an overflowing strip turns a vertical wheel into a sideways scroll as before.
216
+
217
+ - **`hiddenUntilFound` on `PlAccordion` and `PlCollapsible` no longer makes Base UI warn.** Both passed `keepMounted={false}` to Base UI whenever the caller left `keepMounted` out, and Base UI warns in development when an explicit `false` meets `hiddenUntilFound`, which ignores it. `keepMounted` is now passed only when it was given, and closed panels behave as before.
218
+
219
+ - **A server-rendered `PlAvatar` starts loading its picture with the page.** The `<img>` was mounted only once Base UI had preloaded the picture after hydration, so the request waited for the JavaScript. It is now in the server's markup from the start, kept out of sight, out of the layout and away from screen readers until it has loaded, so what is drawn, the fallback's `delay` and what is announced are unchanged. A `loading`, `fetchPriority` or `decoding` passed in `imageProps` now applies to the request that loads the picture, so `loading: 'lazy'` defers an avatar below the fold where before it was fetched at once.
220
+
221
+ - **A heading passed to `PlEmpty` as its `title` stays where it was put.** The title was drawn in a `<p>`, which cannot hold a heading, so a server-rendered page put the heading beside an empty title, and React reported a hydration mismatch and rendered the tree again. The title is now a `<div>`, and it looks and reads as before.
222
+
223
+ - **A `PlSkeleton` and an indeterminate `PlProgressLinear` no longer add to a page's layout shift while they run.** The highlight and the segment travelled on `inset-inline-start`, which laid them out again on every frame, and the browser recorded every one of those frames as a layout shift: a 600×340 skeleton added 0.39 to the page's CLS in five seconds. Both now rest at the start of their box and move on `translate`, over the same path, at the same pace and in the same direction, so nothing on the page is laid out while they run.
224
+
225
+ - **A page rendered on a server hydrates in whatever language the reader's browser uses.** With no `locale` given, every component that writes a number or a date wrote it in the runtime's own locale: `PlAnimateCounter`, the progress components, `PlMeter`, `PlNumberField`, `PlSlider`, the pickers, `PlCalendar` and every chart, and so did a `PlDataTable` sorting text and a `PlAvatar` writing initials. A server's runtime is not the reader's, so an `en-US` server wrote `12,345` where a `de-DE` browser hydrated `12.345`, and React threw the server's tree away and rendered it again. A server render and its hydration now use the locale the component or the nearest `PlassProvider` names, or `en-US` when neither does, and switch to the browser's locale as soon as hydration is done. A page rendered only in the browser writes in the browser's locale from the first paint, as before, and a server-rendered page that gives the provider a `locale` shows the reader's format from the start.
226
+
9
227
  - **A quick click on the words of a resting `float` label opens the `PlTreeSelect` under it.** The press gave the trigger the focus, which ended the rest and put the label back in reach of the pointer while it was still lying over the words. A release that came before the label had moved off them landed on the label, the browser gave the click to the frame round both rather than to the trigger, and the picker stayed shut. The label now lets the pointer through until a press that went down on the control is over, and a click on the label once it has risen still reaches the control.
10
228
 
11
229
  ## 1.7.0 (2026-09-28)
package/README.md CHANGED
@@ -55,12 +55,12 @@ If your project already runs Tailwind v4, import the token sheet instead:
55
55
 
56
56
  ```css
57
57
  @import 'tailwindcss';
58
- @import 'plass-ui/css/base.css'; /* tokens + what every component shares */
58
+ @import 'plass-ui/css/base.css'; /* the tokens, once */
59
59
  @import 'plass-ui/css/button.css';
60
60
  @import 'plass-ui/css/text-field.css';
61
61
  ```
62
62
 
63
- Still one Tailwind pass, so the utilities keep Tailwind's own order, and about 5 kB gzipped smaller for a small set of components. There is one manifest per component, named after its folder in `dist/components`.
63
+ Still one Tailwind pass, so the utilities keep Tailwind's own order, and about 10 kB gzipped smaller for a small set of components. There is one manifest per component, named after its folder in `dist/components`.
64
64
 
65
65
  ### The page under the components
66
66
 
@@ -1,7 +1,12 @@
1
1
  import * as React from 'react';
2
- import type { PlassElevation, PlassStyleProps } from '../../types.js';
3
- /** The six levels an HTML heading has. */
4
- export type PlAccordionHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
2
+ import type { PlassElevation, PlassHeadingLevel, PlassStyleProps } from '../../types.js';
3
+ /**
4
+ * The six levels an HTML heading has.
5
+ *
6
+ * @deprecated Use `PlassHeadingLevel`, the same type in the shared vocabulary,
7
+ * which `PlDrawer` takes too.
8
+ */
9
+ export type PlAccordionHeadingLevel = PlassHeadingLevel;
5
10
  export interface PlAccordionProps extends PlassStyleProps, Omit<React.ComponentPropsWithoutRef<'div'>, 'color' | 'defaultValue' | 'onChange'> {
6
11
  /**
7
12
  * Drop shadow depth. `0` is the default — an accordion is part of the page it
@@ -44,7 +49,7 @@ export interface PlAccordionProps extends PlassStyleProps, Omit<React.ComponentP
44
49
  * Only the element changes: the type scale is the accordion's either way.
45
50
  * @default 3
46
51
  */
47
- headingLevel?: PlAccordionHeadingLevel;
52
+ headingLevel?: PlassHeadingLevel;
48
53
  /**
49
54
  * Keeps closed panels in the DOM so the browser's own page search can find
50
55
  * and open them. Overrides `keepMounted`.
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import*as l from"react";import{useDefaults as s}from"../../internal/defaults.js";import{Accordion as a}from"@base-ui/react/accordion";import{ChevronIcon as n}from"../../internal/icons.js";import{focusRingClasses as i,focusRingInsetClasses as r,gapClasses as d,hasContent as o,iconClasses as c,metaTextClasses as m,radiusClasses as p,sheetBodyClasses as u,sheetHeaderGapClasses as f,sheetPaddingXClasses as h,sheetPaddingYClasses as x,sheetRestClasses as v,sheetTitleClasses as b,surfaceSlots as g,transitionClasses as N}from"../../internal/styles.js";const j=/* @__PURE__ */l.createContext({size:"md",density:"default",dividers:!0,headingLevel:3}),y={xs:p.xs,sm:p.xs,md:p.sm,lg:p.sm,xl:p.md},w={default:{xs:"pt-1.5",sm:"pt-2",md:"pt-3",lg:"pt-3.5",xl:"pt-4"},compact:{xs:"pt-1",sm:"pt-1.5",md:"pt-2",lg:"pt-2.5",xl:"pt-3"}},$={default:{xs:"pb-2.5",sm:"pb-3",md:"pb-5",lg:"pb-6",xl:"pb-7"},compact:{xs:"pb-2",sm:"pb-2.5",md:"pb-3.5",lg:"pb-4",xl:"pb-5"}};export const PlAccordion=/* @__PURE__ */l.forwardRef(function PlAccordion({variant:t="glass",size:n,color:i,density:r,elevation:d=0,multiple:o=!1,value:c,defaultValue:m,onValueChange:u,dividers:f=!0,disabled:h=!1,headingLevel:x=3,hiddenUntilFound:b=!1,keepMounted:y=!1,className:w,style:$,children:P,...k},z){const A=s(),I=n??A.size??"md",C=i??A.color??"primary",L=r??A.density??"default",V=Number.isInteger(x)&&x>=1&&x<=6?x:3,_=l.useMemo(()=>({size:I,density:L,dividers:f,headingLevel:V}),[I,L,f,V]),B=["flex flex-col",p[I],v[t],N,f?"overflow-hidden [&>div+div]:border-t [&>div+div]:[border-color:var(--plass-divider)]":"p-1",w??""].filter(Boolean).join(" ");return e(j.Provider,{value:_,children:e(a.Root,{ref:z,multiple:o,value:c,defaultValue:m,onValueChange:e=>u?.(e),disabled:h,hiddenUntilFound:b,keepMounted:y,className:B,style:{...g(C,d),...$},...k,children:P})})});export const PlAccordionItem=/* @__PURE__ */l.forwardRef(function PlAccordionItem({value:s,title:p,subtitle:v,startIcon:g,action:P,truncate:k=!1,disabled:z=!1,className:A,children:I,...C},L){const{size:V,density:_,dividers:B,headingLevel:M}=l.useContext(j),R=`h${M}`,F=h[_][V],U=x[_][V],H=k?"truncate":"";return t(a.Item,{ref:L,value:s,disabled:z,className:["flex flex-col",A??""].filter(Boolean).join(" "),...C,children:[t(a.Header,{render:e(R,{}),className:"m-0 flex w-full items-center [font:inherit]",children:[t(a.Trigger,{className:["flex min-w-0 flex-1 cursor-pointer items-center text-start",F,U,d[V],N,c,B?r:`${i} ${y[V]}`,"hover:bg-(--p-soft)","data-[panel-open]:bg-(--p-soft) data-[panel-open]:text-(--p-accent)","disabled:cursor-not-allowed disabled:bg-transparent disabled:opacity-50"].filter(Boolean).join(" "),children:[o(g)?e("span",{className:"flex h-[1lh] shrink-0 items-center text-(--plass-muted-fg)",children:g}):null,t("span",{className:`flex min-w-0 flex-1 flex-col ${f[V]}`,children:[o(p)?e("span",{className:`plass-title font-semibold ${H} ${b[V]}`.trim(),children:p}):null,o(v)?e("span",{className:`text-(--plass-muted-fg) ${H} ${m[V]}`.trim(),children:v}):null]}),e("span",{className:["flex h-[1lh] shrink-0 items-center text-(--plass-muted-fg)","[transition:rotate_var(--plass-duration)_var(--plass-ease)]","motion-reduce:[transition-duration:0ms]","data-[panel-open]:rotate-180"].join(" "),children:e(n,{})})]}),o(P)?e("span",{className:`flex shrink-0 items-center ${F}`,children:P}):null]}),e(a.Panel,{className:["h-(--accordion-panel-height) overflow-hidden","[transition:height_var(--plass-duration-slow)_var(--plass-ease)]","motion-reduce:[transition-duration:0ms]","data-[starting-style]:h-0 data-[ending-style]:h-0"].join(" "),children:e("div",{className:["text-(--plass-muted-fg)",u[V],F,w[_][V],$[_][V]].join(" "),children:I})})]})});
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import*as l from"react";import{useDefaults as s}from"../../internal/defaults.js";import{headingLevelOf as a}from"../../internal/heading.js";import{Accordion as n}from"@base-ui/react/accordion";import{ChevronIcon as i}from"../../internal/icons.js";import{focusRingClasses as r,focusRingInsetClasses as o,gapClasses as d,hasContent as c,iconClasses as m,metaTextClasses as p,radiusClasses as u,sheetBodyClasses as f,sheetHeaderGapClasses as h,sheetPaddingXClasses as x,sheetPaddingYClasses as v,sheetRestClasses as g,sheetTitleClasses as b,surfaceSlots as N,transitionClasses as j}from"../../internal/styles.js";const y=/* @__PURE__ */l.createContext({size:"md",density:"default",dividers:!0,headingLevel:3}),w={xs:u.xs,sm:u.xs,md:u.sm,lg:u.sm,xl:u.md},$={default:{xs:"pt-1.5",sm:"pt-2",md:"pt-3",lg:"pt-3.5",xl:"pt-4"},compact:{xs:"pt-1",sm:"pt-1.5",md:"pt-2",lg:"pt-2.5",xl:"pt-3"}},P={default:{xs:"pb-2.5",sm:"pb-3",md:"pb-5",lg:"pb-6",xl:"pb-7"},compact:{xs:"pb-2",sm:"pb-2.5",md:"pb-3.5",lg:"pb-4",xl:"pb-5"}};export const PlAccordion=/* @__PURE__ */l.forwardRef(function PlAccordion({variant:t="glass",size:i,color:r,density:o,elevation:d=0,multiple:c=!1,value:m,defaultValue:p,onValueChange:f,dividers:h=!0,disabled:x=!1,headingLevel:v=3,hiddenUntilFound:b=!1,keepMounted:w,className:$,style:P,children:k,...z},A){const C=s(),I=i??C.size??"md",L=r??C.color??"primary",V=o??C.density??"default",_=a(v)??3,B=l.useMemo(()=>({size:I,density:V,dividers:h,headingLevel:_}),[I,V,h,_]),M=["flex flex-col",u[I],g[t],j,h?"overflow-hidden [&>div+div]:border-t [&>div+div]:[border-color:var(--plass-divider)]":"p-1",$??""].filter(Boolean).join(" ");return e(y.Provider,{value:B,children:e(n.Root,{ref:A,multiple:c,value:m,defaultValue:p,onValueChange:e=>f?.(e),disabled:x,hiddenUntilFound:b,keepMounted:w,className:M,style:{...N(L,d),...P},...z,children:k})})});export const PlAccordionItem=/* @__PURE__ */l.forwardRef(function PlAccordionItem({value:s,title:a,subtitle:u,startIcon:g,action:N,truncate:k=!1,disabled:z=!1,className:A,children:C,...I},L){const{size:V,density:_,dividers:B,headingLevel:M}=l.useContext(y),R=`h${M}`,F=x[_][V],U=v[_][V],H=k?"truncate":"";return t(n.Item,{ref:L,value:s,disabled:z,className:["flex flex-col",A??""].filter(Boolean).join(" "),...I,children:[t(n.Header,{render:e(R,{}),className:"m-0 flex w-full items-center [font:inherit]",children:[t(n.Trigger,{className:["flex min-w-0 flex-1 cursor-pointer items-center text-start",F,U,d[V],j,m,B?o:`${r} ${w[V]}`,"hover:bg-(--p-soft)","data-[panel-open]:bg-(--p-soft) data-[panel-open]:text-(--p-accent)","disabled:cursor-not-allowed disabled:bg-transparent disabled:opacity-50"].filter(Boolean).join(" "),children:[c(g)?e("span",{className:"flex h-[1lh] shrink-0 items-center text-(--plass-muted-fg)",children:g}):null,t("span",{className:`flex min-w-0 flex-1 flex-col ${h[V]}`,children:[c(a)?e("span",{className:`plass-title font-semibold ${H} ${b[V]}`.trim(),children:a}):null,c(u)?e("span",{className:`text-(--plass-muted-fg) ${H} ${p[V]}`.trim(),children:u}):null]}),e("span",{className:["flex h-[1lh] shrink-0 items-center text-(--plass-muted-fg)","[transition:rotate_var(--plass-duration)_var(--plass-ease)]","motion-reduce:[transition-duration:0ms]","data-[panel-open]:rotate-180"].join(" "),children:e(i,{})})]}),c(N)?e("span",{className:`flex shrink-0 items-center ${F}`,children:N}):null]}),e(n.Panel,{className:["h-(--accordion-panel-height) overflow-hidden","[transition:height_var(--plass-duration-slow)_var(--plass-ease)]","motion-reduce:[transition-duration:0ms]","data-[starting-style]:h-0 data-[ending-style]:h-0"].join(" "),children:e("div",{className:["text-(--plass-muted-fg)",f[V],F,$[_][V],P[_][V]].join(" "),children:C})})]})});
@@ -1 +1 @@
1
- "use client";import{jsx as s,jsxs as e}from"react/jsx-runtime";import*as l from"react";import{useDefaults as t}from"../../internal/defaults.js";import{useLabels as a}from"../../internal/labels.js";import{CloseIcon as i,severityIcon as r}from"../../internal/icons.js";import{controlSlots as o,focusRingClasses as n,forcedFieldEdgeClasses as c,glassClasses as d,hasContent as m,iconClasses as f,radiusClasses as p,sheetBodyClasses as u,sheetHeaderGapClasses as h,sheetPaddingXClasses as v,sheetPaddingYClasses as x,sheetSectionGapClasses as g,sheetTitleClasses as b,targetClasses as y,transitionClasses as j}from"../../internal/styles.js";const N={solid:/* @__PURE__ */["text-(--p-on-solid) [background-image:var(--p-fill)]","[box-shadow:var(--p-elev),var(--p-lift)]",c].join(" "),glass:/* @__PURE__ */[d,"border text-(--plass-fg) bg-(--plass-glass)","[border-color:var(--p-line)]","[box-shadow:var(--p-elev),var(--plass-gloss-glass)]"].join(" "),ghost:"text-(--plass-fg) bg-(--p-soft)"},w={solid:"",glass:"text-(--p-accent)",ghost:"text-(--p-accent)"},k={solid:"",glass:"text-(--plass-muted-fg)",ghost:"text-(--plass-muted-fg)"},$={primary:"status",secondary:"status",info:"status",success:"status",warning:"alert",danger:"alert"};export const PlAlert=/* @__PURE__ */l.forwardRef(function PlAlert({variant:l="glass",size:c,color:d,density:z,elevation:A=0,title:C,icon:P,action:_,onClose:B,closeLabel:L,className:R,style:q,children:D,...E},F){const G=t(),H=a(),I=L??H.dismiss,J=c??G.size??"md",K=d??G.color??"info",M=z??G.density??"default",O=void 0===P?r(K):P,Q=w[l],S=m(C);return e("div",{ref:F,role:$[K],className:["flex w-full items-start",v[M][J],x[M][J],p[J],g[J],u[J],j,N[l],f,R??""].filter(Boolean).join(" "),style:{...o(K,A,l),...q},...E,children:[m(O)?s("span",{className:`flex h-[1lh] shrink-0 items-center ${Q}`,children:O}):null,e("div",{className:`flex min-w-0 flex-1 flex-col ${h[J]}`,children:[S?s("div",{className:`plass-title font-semibold ${b[J]} ${Q}`,children:C}):null,m(D)?s("div",{className:S?k[l]:void 0,children:D}):null]}),m(_)?s("div",{className:"flex h-[1lh] shrink-0 items-center",children:_}):null,B?s("span",{className:"flex h-[1lh] shrink-0 items-center",children:s("button",{type:"button","aria-label":I,onClick:B,className:[y,"inline-flex size-[1.15em] cursor-pointer items-center justify-center rounded-full","opacity-70 [transition:opacity_var(--plass-duration)_var(--plass-ease)]","motion-reduce:[transition-duration:0ms]","hover:opacity-100 focus-visible:opacity-100",n].join(" "),children:s(i,{})})}):null]})});
1
+ "use client";import{jsx as s,jsxs as e}from"react/jsx-runtime";import*as l from"react";import{useDefaults as t}from"../../internal/defaults.js";import{useLabels as a}from"../../internal/labels.js";import{CloseIcon as i,severityIcon as r}from"../../internal/icons.js";import{controlSlots as o,focusRingClasses as n,forcedFieldEdgeClasses as c,glassClasses as d,hasContent as m,iconClasses as f,radiusClasses as p,sheetBodyClasses as u,sheetHeaderGapClasses as h,sheetPaddingXClasses as v,sheetPaddingYClasses as x,sheetSectionGapClasses as g,sheetTitleClasses as b,targetClasses as y,transitionClasses as j}from"../../internal/styles.js";const N={solid:/* @__PURE__ */["text-(--p-on-solid) [background-image:var(--p-fill)]","[box-shadow:var(--p-elev),var(--p-lift)]",c].join(" "),glass:/* @__PURE__ */[d,"border text-(--plass-fg) bg-(--plass-glass)","[border-color:var(--p-line)]","[box-shadow:var(--p-elev),var(--plass-gloss-glass)]"].join(" "),ghost:"text-(--plass-fg) bg-(--p-soft)"},w={solid:"",glass:"text-(--p-accent)",ghost:"text-(--p-accent)"},k={solid:"",glass:"text-(--plass-muted-fg)",ghost:"text-(--plass-muted-fg)"},$={primary:"status",secondary:"status",info:"status",success:"status",warning:"alert",danger:"alert"};export const PlAlert=/* @__PURE__ */l.forwardRef(function PlAlert({variant:l="glass",size:c,color:d,density:z,elevation:A=0,title:C,icon:P,action:_,onClose:B,closeLabel:L,className:R,style:q,children:D,...E},F){const G=t(),H=a(),I=L??H.dismiss,J=c??G.size??"md",K=d??G.color??"info",M=z??G.density??"default",O=void 0===P?r(K):P,Q=w[l],S=m(C);return e("div",{ref:F,role:$[K],className:["flex w-full items-start",v[M][J],x[M][J],p[J],g[J],u[J],j,N[l],f,R??""].filter(Boolean).join(" "),style:{...o(K,A,l),...q},...E,children:[m(O)?s("span",{className:`flex h-[1lh] shrink-0 items-center ${Q}`,children:O}):null,e("div",{className:`flex min-w-0 flex-1 flex-col ${h[J]}`,children:[S?s("div",{className:`plass-title font-semibold ${b[J]} ${Q}`,children:C}):null,m(D)?s("div",{className:S?k[l]:void 0,children:D}):null]}),m(_)?s("div",{className:"flex h-[1lh] shrink-0 items-center",children:_}):null,B?s("span",{className:"flex h-[1lh] shrink-0 items-center",children:s("button",{type:"button","aria-label":I,onClick:B,className:[y,"inline-flex size-[1.15em] cursor-pointer items-center justify-center rounded-full p-0","opacity-70 [transition:opacity_var(--plass-duration)_var(--plass-ease)]","motion-reduce:[transition-duration:0ms]","hover:opacity-100 focus-visible:opacity-100",n].join(" "),children:s(i,{})})}):null]})});
@@ -1 +1 @@
1
- "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import*as r from"react";import{useDefaults as n}from"../../internal/defaults.js";import{resolveScrollTarget as s}from"../../internal/scroll-target.js";import{useLabels as o}from"../../internal/labels.js";import{controlTextLeadingClasses as l,cx as a,focusRingClasses as i,surfaceSlots as c,transitionClasses as m}from"../../internal/styles.js";export const PlAnchor=/* @__PURE__ */r.forwardRef(function PlAnchor({items:f,active:d,offset:p=0,target:u,onSelect:h,label:g,navLabel:v,size:x,color:b,className:w,style:j,...y},E){const N=n(),A=o(),L=v??A.onThisPage,z=x??N.size??"sm",H=b??N.color??"primary",[B,C]=r.useState(void 0),P=void 0!==d;r.useEffect(()=>{if(P)return;const e=s(u);if(!e)return;let t=0;const measure=()=>{t=0,C(function readingAt(e,t,r){if(0===e.length)return;const n=r instanceof Window,s=n?document.documentElement.scrollHeight:r.scrollHeight,o=n?window.innerHeight:r.clientHeight,l=n?window.scrollY:r.scrollTop;if(s>o+1&&o+l>=s-2)return e[e.length-1].href;const a=n?0:r.getBoundingClientRect().top+r.clientTop;let i;for(const r of e){const e=document.getElementById(r.href.replace(/^#/,""));e&&e.getBoundingClientRect().top-a-t<=1&&(i=r.href)}return i}(f,p,e))},onScroll=()=>{t||=requestAnimationFrame(measure)};return measure(),e.addEventListener("scroll",onScroll,{passive:!0}),window.addEventListener("resize",onScroll,{passive:!0}),()=>{t&&cancelAnimationFrame(t),e.removeEventListener("scroll",onScroll),window.removeEventListener("resize",onScroll)}},[P,f,p,u]);const R=P?d:B;return t("nav",{ref:E,"aria-label":L,className:a("flex flex-col gap-2",l[z],w),style:{...c(H,0),...j},...y,children:[g?e("span",{className:"font-semibold text-(--plass-fg)",children:g}):null,e("ul",{className:"m-0 flex list-none flex-col gap-0.5 p-0",children:f.map(t=>{const r=t.href===R;return e("li",{className:"min-w-0",children:e("a",{href:t.href,"aria-current":r?"location":void 0,onClick:e=>h?.(t,e),className:a("block truncate rounded-(--plass-radius-xs) px-2 py-1","border-s-2",m,i,r?"[border-color:var(--p-accent)] bg-(--p-soft) font-medium text-(--p-accent)":"border-transparent text-(--plass-muted-fg) hover:bg-(--plass-glass-hover) hover:text-(--plass-fg)"),style:{paddingInlineStart:`calc(0.5rem + ${t.depth??0} * 0.75rem)`},children:t.label})},t.href)})})]})});
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import*as r from"react";import{useDefaults as n}from"../../internal/defaults.js";import{layoutBox as o}from"../../internal/layout-box.js";import{resolveScrollTarget as s}from"../../internal/scroll-target.js";import{useLabels as l}from"../../internal/labels.js";import{controlTextLeadingClasses as i,cx as a,focusRingClasses as c,surfaceSlots as m,transitionClasses as f}from"../../internal/styles.js";export const PlAnchor=/* @__PURE__ */r.forwardRef(function PlAnchor({items:d,active:p,offset:u=0,target:h,onSelect:g,label:v,navLabel:x,size:b,color:w,className:j,style:y,...E},N){const A=n(),L=l(),z=x??L.onThisPage,H=b??A.size??"sm",P=w??A.color??"primary",[B,C]=r.useState(void 0),R=void 0!==p;r.useEffect(()=>{if(R)return;const e=s(h);if(!e)return;let t=0;const measure=()=>{t=0,C(function readingAt(e,t,r){if(0===e.length)return;const n=r instanceof Window,s=n?document.documentElement.scrollHeight:r.scrollHeight,l=n?window.innerHeight:r.clientHeight,i=n?window.scrollY:r.scrollTop;if(s>l+1&&l+i>=s-2)return e[e.length-1].href;const a=n?0:r.getBoundingClientRect().top,c=n?0:r.clientTop,m=n?1:o(r,!1).perPixel;let f;for(const r of e){const e=document.getElementById(r.href.replace(/^#/,""));e&&(e.getBoundingClientRect().top-a)*m-c-t<=1&&(f=r.href)}return f}(d,u,e))},onScroll=()=>{t||=requestAnimationFrame(measure)};return measure(),e.addEventListener("scroll",onScroll,{passive:!0}),window.addEventListener("resize",onScroll,{passive:!0}),()=>{t&&cancelAnimationFrame(t),e.removeEventListener("scroll",onScroll),window.removeEventListener("resize",onScroll)}},[R,d,u,h]);const S=R?p:B;return t("nav",{ref:N,"aria-label":z,className:a("flex flex-col gap-2",i[H],j),style:{...m(P,0),...y},...E,children:[v?e("span",{className:"font-semibold text-(--plass-fg)",children:v}):null,e("ul",{className:"m-0 flex list-none flex-col gap-0.5 p-0",children:d.map(t=>{const r=t.href===S;return e("li",{className:"min-w-0",children:e("a",{href:t.href,"aria-current":r?"location":void 0,onClick:e=>g?.(t,e),className:a("block truncate rounded-(--plass-radius-xs) px-2 py-1","border-s-2",f,c,r?"[border-color:var(--p-accent)] bg-(--p-soft) font-medium text-(--p-accent)":"border-transparent text-(--plass-muted-fg) hover:bg-(--plass-glass-hover) hover:text-(--plass-fg)"),style:{paddingInlineStart:`calc(0.5rem + ${t.depth??0} * 0.75rem)`},children:t.label})},t.href)})})]})});
@@ -1 +1 @@
1
- "use client";import*as e from"react";import{mergeProps as a}from"@base-ui/react/merge-props";import{useRender as r}from"@base-ui/react/use-render";import{animBaseClass as t,animateChildren as n,animationClasses as s,animationSlots as i,isInfinite as o,slideOffsets as p,staggerSlots as d,useAnimationRun as l}from"../../internal/animate.js";export const PlAnimateAppear=/* @__PURE__ */e.forwardRef(function PlAnimateAppear({duration:e=380,delay:m=0,easing:c,repeat:u=1,alternate:f,paused:g,trigger:y="mount",play:h,once:x=!0,threshold:A=.2,stagger:b=70,durationStep:v=0,from:N="bottom",distance:P="0.75rem",fade:S=!0,reverse:$=!1,render:j,className:w,style:R,children:k,...q},z){const B=l({trigger:y,play:h,once:x,threshold:A,paused:g,infinite:o(u)}),{x:C,y:D}=p(N,P),E=n(k,`${t} ${s.slide}`,(a,r)=>i(d({duration:e,delay:m,easing:c,repeat:u,alternate:f,x:C,y:D,opacity:S?0:1},{index:a,count:r,stagger:b,durationStep:v,reverse:$})));return r({render:j,ref:[z,B.ref],props:{...a(q,B.handlers),className:w,style:{"--p-anim-state":B.state,...R},"data-plass-animation":"appear","data-state":B.state,children:E}})});
1
+ "use client";import*as e from"react";import{mergeProps as a}from"@base-ui/react/merge-props";import{useRender as r}from"@base-ui/react/use-render";import{animBaseClass as t,animateChildren as n,animationClasses as s,animationSlots as i,isInfinite as o,slideOffsets as p,staggerSlots as d,useAnimationRun as l}from"../../internal/animate.js";export const PlAnimateAppear=/* @__PURE__ */e.forwardRef(function PlAnimateAppear({duration:e=380,delay:m=0,easing:c,repeat:u=1,alternate:f,paused:g,trigger:y="mount",play:h,once:x=!0,threshold:A=.2,stagger:b=70,durationStep:v=0,from:N="bottom",distance:P="0.75rem",fade:S=!0,reverse:$=!1,render:j,className:w,style:R,children:k,...q},z){const B=l({trigger:y,play:h,once:x,threshold:A,paused:g,infinite:o(u),endless:o(u)}),{x:C,y:D}=p(N,P),E=n(k,`${t} ${s.slide}`,(a,r)=>i(d({duration:e,delay:m,easing:c,repeat:u,alternate:f,x:C,y:D,opacity:S?0:1},{index:a,count:r,stagger:b,durationStep:v,reverse:$})));return r({render:j,ref:[z,B.ref],props:{...a(q,B.handlers),className:w,style:{"--p-anim-state":B.state,...R},"data-plass-animation":"appear","data-state":B.state,children:E}})});
@@ -5,7 +5,9 @@ export interface PlAnimateCounterProps extends Omit<React.ComponentPropsWithoutR
5
5
  /** The number it arrives at, and the one a screen reader is told. */
6
6
  value: number;
7
7
  /**
8
- * The number it starts from.
8
+ * The number the first count starts from, and a replay of it: a second
9
+ * hover, a new `play`, a `visible` trigger seen again. A new `value` counts
10
+ * on from the figure on screen instead.
9
11
  * @default 0
10
12
  */
11
13
  from?: number;
@@ -18,7 +20,8 @@ export interface PlAnimateCounterProps extends Omit<React.ComponentPropsWithoutR
18
20
  delay?: number;
19
21
  /**
20
22
  * How the number is written — `Intl.NumberFormat` options, so a currency, a
21
- * percentage or a compact `1.2M` all work.
23
+ * percentage or a compact `1.2M` all work. The figures on the way are written
24
+ * with them too, and never with more decimals than the answer has.
22
25
  *
23
26
  * This is the reason the count is JavaScript rather than a `@property` and a
24
27
  * CSS counter, which is otherwise the neater way to animate a number: CSS can
@@ -1 +1 @@
1
- "use client";import{jsx as e,Fragment as r,jsxs as t}from"react/jsx-runtime";import*as n from"react";import{mergeProps as a}from"@base-ui/react/merge-props";import{useRender as s}from"@base-ui/react/use-render";import{useAnimationRun as i}from"../../internal/animate.js";import{useDefaults as o}from"../../internal/defaults.js";import{usePrefersReducedMotion as u}from"../../internal/media.js";import{cx as m,srOnlyCopyClasses as c}from"../../internal/styles.js";function easeOut(e){return 1-(1-e)**3}export const PlAnimateCounter=/* @__PURE__ */n.forwardRef(function PlAnimateCounter({value:l,from:f=0,duration:d=1200,delay:p=0,format:h,easing:g=easeOut,trigger:j="visible",play:y,once:A=!0,threshold:F=.2,paused:b,render:x,className:N,...v},R){const q=o(),E=u(),O=i({trigger:j,play:y,once:A,threshold:F,paused:b,infinite:!1,nonce:l}),k=`${q.locale??""}\0${JSON.stringify(h??null)}`,w=n.useRef(null);null!==w.current&&w.current.key===k||(w.current={key:k,formatter:new Intl.NumberFormat(q.locale,h)});const C=w.current.formatter,[M,P]=n.useState(()=>E?l:f),S=n.useRef(0),$=n.useRef(g);n.useEffect(()=>{$.current=g}),n.useEffect(()=>{S.current=0},[O.runs,l,f]),n.useEffect(()=>{if(E)return void P(l);if(!O.started)return S.current=0,void P(f);if(b)return;const e=Math.max(1,d);let r,t=0;const step=n=>{r??=n-S.current,S.current=n-r;const a=Math.min(1,(S.current-p)/e);a<0?t=requestAnimationFrame(step):(P(f+(l-f)*$.current(a)),a<1&&(t=requestAnimationFrame(step)))};return t=requestAnimationFrame(step),()=>cancelAnimationFrame(t)},[O.started,O.runs,E,b,l,f,d,p]);const I=C.format(l);return s({render:x??e("span",{}),ref:[R,O.ref],props:{className:m("tabular-nums",N),"data-plass-animation":"counter","data-state":O.state,children:t(r,{children:[e("span",{className:c,children:I}),e("span",{"aria-hidden":"true",children:C.format(M)})]}),...a(v,O.handlers)}})});
1
+ "use client";import{jsx as r,Fragment as t,jsxs as e}from"react/jsx-runtime";import*as n from"react";import{mergeProps as a}from"@base-ui/react/merge-props";import{useRender as u}from"@base-ui/react/use-render";import{useAnimationRun as i}from"../../internal/animate.js";import{useLocale as o}from"../../internal/defaults.js";import{usePrefersReducedMotion as s}from"../../internal/media.js";import{cx as c,drawnCopyClasses as m,srOnlyClasses as l}from"../../internal/styles.js";function easeOut(r){return 1-(1-r)**3}function fractionDigitsIn(r){return r.reduce((r,t)=>"fraction"===t.type?r+[...t.value].length:r,0)}export const PlAnimateCounter=/* @__PURE__ */n.forwardRef(function PlAnimateCounter({value:f,from:d=0,duration:g=1200,delay:p=0,format:h,easing:F=easeOut,trigger:v="visible",play:y,once:x=!0,threshold:A=.2,paused:b,render:j,className:D,...N},P){const R=o(),w=s(),q=i({trigger:v,play:y,once:x,threshold:A,paused:b,infinite:!1,target:f}),I=`${R??""}\0${JSON.stringify(h??null)}`,k=n.useRef(null);null!==k.current&&k.current.key===I||(k.current={key:I,formatter:new Intl.NumberFormat(R,h)});const M=k.current.formatter,O=M.format(f),S=`${I}\0${f}`,$=n.useRef(null);if(null===$.current||$.current.key!==S){const r=function frameFormatter(r,t,e,n){const a=fractionDigitsIn(r.formatToParts(t)),u=r.resolvedOptions(),i=u.roundingPriority??"auto",o=u.maximumFractionDigits??a,s=void 0!==u.maximumSignificantDigits&&"auto"===i,c="auto"!==i&&void 0===n?.roundingPriority;if(o<=a&&!s&&!c)return r;let m;try{m=new Intl.NumberFormat(e,{...n,...s?{roundingPriority:"lessPrecision"}:{},minimumFractionDigits:Math.min(u.minimumFractionDigits??0,a),maximumFractionDigits:a})}catch{return r}return c?{format(t){const e=r.formatToParts(t);return fractionDigitsIn(e)>a?m.format(t):e.map(r=>r.value).join("")}}:m}(M,f,R,h),t=r.format(-0),e=r.format(0);$.current={key:S,write:n=>{const a=r.format(n);return a===t?e:a}}}const E=$.current,[C,T]=n.useState(()=>w?f:d),[J,z]=n.useState({value:f,from:d,runs:q.runs,origin:d,pending:!1,count:0});J.value!==f||J.from!==d?z({value:f,from:d,runs:q.runs,origin:J.from!==d?d:C,pending:J.pending||J.value!==f,count:J.count+1}):J.runs!==q.runs&&z(J.pending?{...J,runs:q.runs,pending:!1}:{...J,runs:q.runs,origin:d,count:J.count+1});const B=J.origin,G=n.useRef(0),H=n.useRef(null),K=n.useRef(F),L=n.useRef(p);n.useEffect(()=>{K.current=F,L.current=p}),n.useEffect(()=>{G.current=0,H.current=null},[J.count]),n.useEffect(()=>{const r=Math.max(1,g);let t,e=0;if(w){if(T(f),!q.started)return;const land=()=>{H.current??=L.current,G.current=Math.max(G.current,H.current+r)};if(null!==H.current||G.current>=L.current)return void land();if(b)return;const wait=r=>{t??=r-G.current,G.current=r-t,G.current<L.current?e=requestAnimationFrame(wait):land()};return e=requestAnimationFrame(wait),()=>cancelAnimationFrame(e)}if(!q.started)return G.current=0,H.current=null,void T(d);if(b)return;null===H.current&&G.current<L.current&&T(B);const step=n=>{t??=n-G.current,G.current=n-t;const a=Math.min(1,(G.current-(H.current??L.current))/r);a<0?e=requestAnimationFrame(step):(H.current??=L.current,T(B+(f-B)*K.current(a)),a<1&&(e=requestAnimationFrame(step)))};return e=requestAnimationFrame(step),()=>cancelAnimationFrame(e)},[q.started,J.count,w,b,f,d,B,g]);const Q=C===f?O:E.write(C),U=C===f?null:E.write(B);return u({render:j??r("span",{}),ref:[P,q.ref],props:{className:c("tabular-nums",D),"data-plass-animation":"counter","data-state":q.state,children:e(t,{children:[r("span",{className:l,children:O}),r("span",{"aria-hidden":"true","data-text":Q,"data-sample":O,className:m,children:null===U?null:r("span",{"data-text":U,className:"invisible [grid-area:1/1] before:content-[attr(data-text)]"})})]}),...a(N,q.handlers)}})});
@@ -1 +1 @@
1
- "use client";import*as e from"react";import{mergeProps as a}from"@base-ui/react/merge-props";import{useRender as r}from"@base-ui/react/use-render";import{animBaseClass as t,lengthValue as n,useAnimateElement as i}from"../../internal/animate.js";import{cx as l}from"../../internal/styles.js";export const PlAnimateFloat=/* @__PURE__ */e.forwardRef(function PlAnimateFloat({duration:e=3e3,delay:s=0,easing:o="ease-in-out",repeat:c="infinite",alternate:p,paused:m,trigger:d="mount",play:f,once:u=!0,threshold:y=.2,distance:h=8,orientation:g="vertical",render:b,className:x,style:N,children:j,...v},A){const F=n(h),P="vertical"===g,w=i({effect:null,duration:e,delay:s,easing:o,repeat:c,alternate:p,x:P?"0":F,y:P?"number"==typeof h?-h+"px":`calc(-1 * ${F})`:"0",trigger:d,play:f,once:u,threshold:y,paused:m,infinite:!0,children:j});return r({render:b,ref:[A,w.ref],props:{...a(v,w.props),className:l(t,"plass-anim-float",w.className,x),style:{...w.style,...N},"data-plass-animation":"float",children:w.children}})});
1
+ "use client";import*as e from"react";import{mergeProps as a}from"@base-ui/react/merge-props";import{useRender as r}from"@base-ui/react/use-render";import{animBaseClass as t,lengthValue as n,useAnimateElement as i}from"../../internal/animate.js";import{cx as s}from"../../internal/styles.js";export const PlAnimateFloat=/* @__PURE__ */e.forwardRef(function PlAnimateFloat({duration:e=3e3,delay:l=0,easing:o="ease-in-out",repeat:c="infinite",alternate:m,paused:p,trigger:d="mount",play:f,once:u=!0,threshold:y=.2,distance:h=8,orientation:g="vertical",render:b,className:v,style:x,children:N,...j},A){const F=n(h),P="vertical"===g,w=i({effect:null,duration:e,delay:l,easing:o,repeat:c,alternate:m,x:P?"0":F,y:P?"number"==typeof h?-h+"px":`calc(-1 * ${F})`:"0",trigger:d,play:f,once:u,threshold:y,paused:p,infinite:!0,moves:!0,children:N});return r({render:b,ref:[A,w.ref],props:{...a(j,w.props),className:s(t,"plass-anim-float",w.className,v),style:{...w.style,...x},"data-plass-animation":"float",children:w.children}})});
@@ -1 +1 @@
1
- "use client";import{jsx as e}from"react/jsx-runtime";import*as t from"react";import{mergeProps as n}from"@base-ui/react/merge-props";import{isInfinite as a,lengthValue as r,useAnimationRun as s}from"../../internal/animate.js";import{usePrefersReducedMotion as i}from"../../internal/media.js";import{cx as l}from"../../internal/styles.js";export const PlAnimateHeadline=/* @__PURE__ */t.forwardRef(function PlAnimateHeadline({duration:o=460,delay:u=0,easing:c,repeat:m="infinite",paused:d,trigger:f="mount",play:p,once:h=!0,threshold:g=.2,interval:v=2600,index:y,defaultIndex:x=0,onIndexChange:E,loop:j=!0,rise:N="100%",className:R,style:T,children:A,...C},b){const k=s({trigger:f,play:p,once:h,threshold:g,paused:d,infinite:a(m)}),H=i(),I=t.Children.toArray(A),M=I.length,[P,S]=t.useState(x),w=Math.min(y??P,Math.max(M-1,0)),[V,$]=t.useState(null),q=t.useRef(w);t.useEffect(()=>{q.current!==w&&($(q.current),q.current=w)},[w]),t.useEffect(()=>{if(null===V)return;const e=setTimeout(()=>$(null),o);return()=>clearTimeout(e)},[V,o]);const z=t.useCallback(()=>{const e=w+1;if(e>=M){if(!j)return;return void 0===y&&S(0),void E?.(0)}void 0===y&&S(e),E?.(e)},[w,M,j,y,E]),B=t.useRef(z);t.useEffect(()=>{B.current=z});const D=t.useRef(!1);return t.useEffect(()=>{if(void 0!==y||M<2||"running"!==k.state)return;if(!j&&w===M-1)return;const e=setTimeout(()=>{D.current=!0,B.current()},v+(D.current?0:u));return()=>clearTimeout(e)},[y,M,k.state,v,u,j,w]),e("div",{ref:e=>{k.ref(e),"function"==typeof b?b(e):b&&(b.current=e)},className:l("plass-headline",R),style:{"--p-anim-duration":`${o}ms`,"--p-anim-rise":r(N),...c?{"--p-anim-ease":c}:{},"--p-anim-state":k.state,...T},"data-plass-animation":"headline","data-state":k.state,...n(C,k.handlers),children:I.map((n,a)=>{const r=a===w?"active":a!==V||H?void 0:"leaving";if(!t.isValidElement(n))return e("span",{className:"plass-headline-item","data-state":r,children:n},a);const s=n.props;return t.cloneElement(n,{key:a,className:l("plass-headline-item",s.className),"data-state":r})})})});
1
+ "use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import*as n from"react";import{mergeProps as r}from"@base-ui/react/merge-props";import{isInfinite as a,lengthValue as i,useAnimationRun as s}from"../../internal/animate.js";import{usePrefersReducedMotion as l}from"../../internal/media.js";import{cx as o}from"../../internal/styles.js";export const PlAnimateHeadline=/* @__PURE__ */n.forwardRef(function PlAnimateHeadline({duration:u=460,delay:c=0,easing:m,repeat:d="infinite",paused:f,trigger:p="mount",play:h,once:g=!0,threshold:v=.2,interval:x=2600,index:y,defaultIndex:j=0,onIndexChange:E,loop:N=!0,rise:R="100%",className:T,style:w,children:A,...C},M){const b=s({trigger:p,play:h,once:g,threshold:v,paused:f,infinite:a(d),endless:N&&void 0===y}),H=l(),I=n.Children.toArray(A),P=I.length,[S,k]=n.useState(j),F=Math.min(y??S,Math.max(P-1,0)),[V,$]=n.useState(null),q=n.useRef(F);n.useEffect(()=>{q.current!==F&&($(q.current),q.current=F)},[F]),n.useEffect(()=>{if(null===V)return;const e=setTimeout(()=>$(null),u);return()=>clearTimeout(e)},[V,u]);const z=n.useCallback(()=>{const e=F+1;if(e>=P){if(!N)return;return void 0===y&&k(0),void E?.(0)}void 0===y&&k(e),E?.(e)},[F,P,N,y,E]),B=n.useRef(z);n.useEffect(()=>{B.current=z});const D=n.useRef(!1),G=n.useRef({line:F,gone:0});return n.useEffect(()=>{if(!b.started)return void(G.current={line:F,gone:0});if(P<1)return void(G.current={line:F,gone:0});if(void 0!==y)return void(G.current={line:F,gone:0});if("running"!==b.state)return;G.current.line!==F&&(G.current={line:F,gone:0});const e=G.current,t=performance.now(),n=P<2||!N&&F===P-1?void 0:setTimeout(()=>{D.current=!0,B.current()},Math.max(0,x+(D.current?0:c)-e.gone));return()=>{clearTimeout(n),e.gone+=performance.now()-t}},[y,P,b.started,b.state,x,c,N,F]),e("div",{ref:e=>{b.ref(e),"function"==typeof M?M(e):M&&(M.current=e)},className:o("plass-headline",T),style:{"--p-anim-duration":`${u}ms`,"--p-anim-rise":i(R),...m?{"--p-anim-ease":m}:{},"--p-anim-state":b.state,...w},"data-plass-animation":"headline","data-state":b.state,...r(C,b.handlers),children:I.map((r,a)=>{const i=a===F?"active":a!==V||H?void 0:"leaving",s=n.isValidElement(r)?r.props:null,l=s?n.cloneElement(r,{className:o("plass-headline-item",s.className),"data-state":i}):e("span",{className:"plass-headline-item","data-state":i,children:r});return t(n.Fragment,{children:[a>0?" ":null,l]},a)})})});
@@ -1 +1 @@
1
- "use client";import*as e from"react";import{mergeProps as r}from"@base-ui/react/merge-props";import{useDefaults as a}from"../../internal/defaults.js";import{useRender as i}from"@base-ui/react/use-render";import{isInfinite as t,useAnimateElement as n}from"../../internal/animate.js";import{cx as o,radiusClasses as l}from"../../internal/styles.js";export const PlAnimateLighting=/* @__PURE__ */e.forwardRef(function PlAnimateLighting({duration:e=3e3,delay:s=0,easing:p,repeat:m="infinite",alternate:d,paused:g,trigger:c="mount",play:f,once:u=!0,threshold:h=.2,color:y,glow:w,size:$,spread:b=3,arc:j=50,blur:v=5,reverse:x=!1,render:z,className:A,style:L,children:N,...P},R){const k=a(),q=y??k.color??"primary",B=$??k.size??"md",C=n({effect:null,duration:e,delay:s,easing:p,repeat:m,alternate:d,mode:x?"out":"in",trigger:c,play:f,once:u,threshold:h,paused:g,infinite:t(m)});return i({render:z,ref:[R,C.ref],props:{...r(P,C.props),className:o("plass-anim-lighting",l[B],A),style:{...C.style,"--p-anim-glow-from":w??`var(--plass-${q}-solid)`,"--p-anim-glow-to":w??`var(--plass-${q}-solid-to)`,"--p-anim-glow-width":`${b}px`,"--p-anim-glow-arc":`${j}deg`,"--p-anim-glow-blur":`${v}px`,...L},"data-plass-animation":"lighting",children:N}})});
1
+ "use client";import*as e from"react";import{mergeProps as r}from"@base-ui/react/merge-props";import{useDefaults as a}from"../../internal/defaults.js";import{useRender as i}from"@base-ui/react/use-render";import{isInfinite as t,useAnimateElement as n}from"../../internal/animate.js";import{cx as o,radiusClasses as s}from"../../internal/styles.js";export const PlAnimateLighting=/* @__PURE__ */e.forwardRef(function PlAnimateLighting({duration:e=3e3,delay:l=0,easing:p,repeat:m="infinite",alternate:d,paused:g,trigger:c="mount",play:f,once:u=!0,threshold:h=.2,color:y,glow:w,size:$,spread:b=3,arc:v=50,blur:j=5,reverse:x=!1,render:z,className:A,style:L,children:N,...P},R){const k=a(),q=y??k.color??"primary",B=$??k.size??"md",C=n({effect:null,duration:e,delay:l,easing:p,repeat:m,alternate:d,mode:x?"out":"in",trigger:c,play:f,once:u,threshold:h,paused:g,infinite:t(m)});return i({render:z,ref:[R,C.ref],props:{...r(P,C.props),className:o("plass-anim-lighting",s[B],A),style:{...C.style,"--p-anim-glow-from":w??`var(--plass-${q}-solid)`,"--p-anim-glow-to":w??`var(--plass-${q}-solid-to)`,"--p-anim-glow-width":`${b}px`,"--p-anim-glow-arc":`${v}deg`,"--p-anim-glow-blur":`${j}px`,...L},"data-plass-animation":"lighting","data-plass-finite":t(m)?void 0:"",children:N}})});
@@ -1 +1 @@
1
- "use client";import{jsx as e}from"react/jsx-runtime";import*as t from"react";import{mergeProps as r}from"@base-ui/react/merge-props";import{isInfinite as a,lengthValue as i,repeatValue as n,directionValue as s,useAnimationRun as o}from"../../internal/animate.js";import{inertProps as l}from"../../internal/inert.js";import{usePrefersReducedMotion as m}from"../../internal/media.js";import{cx as p}from"../../internal/styles.js";export const PlAnimateMarquee=/* @__PURE__ */t.forwardRef(function PlAnimateMarquee({duration:u,delay:c=0,easing:d,repeat:f="infinite",alternate:h,paused:v,trigger:g="mount",play:y,once:b=!0,threshold:j=.2,orientation:q="horizontal",reverse:x=!1,speed:R=60,gap:H="2rem",copies:M=2,pauseOnHover:w=!0,label:z,className:A,style:N,children:O,...S},W){const G=void 0===u&&R<=0,P=o({trigger:g,play:y,once:b,threshold:j,paused:v||G,infinite:a(f)}),$=t.useRef(null),k=t.useRef(null),[C,E]=t.useState(0),[F,I]=t.useState(!1),B="vertical"===q,D=m();t.useEffect(()=>{const e=$.current,t=k.current;if(!e||!t)return;const measure=()=>{const r=getComputedStyle(e),a=parseFloat(B?r.rowGap:r.columnGap)||0,i=B?t.offsetHeight:t.offsetWidth;E(i+a),I(B?e.scrollHeight>e.clientHeight:e.scrollWidth>e.clientWidth)};if(measure(),"undefined"==typeof ResizeObserver)return;const r=new ResizeObserver(measure);return r.observe(t),r.observe(e),()=>r.disconnect()},[B,D]);const J=u??(C>0&&!G?C/R*1e3:12e3);return e("div",{ref:e=>{$.current=e,P.ref(e),"function"==typeof W?W(e):W&&(W.current=e)},className:p("plass-marquee",B&&"plass-marquee-vertical",A),style:{"--p-anim-gap":i(H),...d?{"--p-anim-ease":d}:{},"--p-anim-duration":`${Math.round(J)}ms`,"--p-anim-delay":`${c}ms`,"--p-anim-repeat":n(f),"--p-anim-direction":s(x?"out":"in",h),"--p-anim-state":P.state,...N},"data-plass-animation":"marquee","data-state":P.state,"data-pause-on-hover":w?"":void 0,tabIndex:D&&F?0:void 0,role:z?"group":void 0,"aria-label":z,...r(S,P.handlers),children:Array.from({length:Math.max(1,M)},(t,r)=>(t=>e("div",{ref:0===t?k:void 0,className:"plass-marquee-track","aria-hidden":0===t?void 0:"true",...l(0!==t),children:O},t))(r))})});
1
+ "use client";import{jsx as e}from"react/jsx-runtime";import*as t from"react";import{mergeProps as r}from"@base-ui/react/merge-props";import{isInfinite as a,lengthValue as n,repeatValue as i,directionValue as s,useAnimationRun as o}from"../../internal/animate.js";import{inertProps as l}from"../../internal/inert.js";import{usePrefersReducedMotion as u}from"../../internal/media.js";import{cx as m}from"../../internal/styles.js";export const PlAnimateMarquee=/* @__PURE__ */t.forwardRef(function PlAnimateMarquee({duration:d,delay:p=0,easing:c,repeat:f="infinite",alternate:h,paused:v,trigger:g="mount",play:y,once:b=!0,threshold:j=.2,orientation:q="horizontal",reverse:x=!1,speed:R=60,gap:H="2rem",copies:M=2,pauseOnHover:S=!0,label:w,className:z,style:A,children:N,...O},W){const G=void 0===d&&R<=0,P=o({trigger:g,play:y,once:b,threshold:j,paused:v||G,infinite:a(f),endless:a(f)}),$=t.useRef(null),k=t.useRef(null),[B,C]=t.useState(0),[E,F]=t.useState(!1),I="vertical"===q,D=u(),J=P.started&&Boolean(v||G),[K,L]=t.useState(null),Q=J&&(D||K===P.runs),T=Q?P.runs:null;T!==K&&L(T);const U=D||Q;t.useEffect(()=>{const e=$.current,t=k.current;if(!e||!t)return;const measure=()=>{const r=getComputedStyle(e),a=parseFloat(I?r.rowGap:r.columnGap)||0,n=I?t.offsetHeight:t.offsetWidth;C(n+a),F(I?e.scrollHeight>e.clientHeight:e.scrollWidth>e.clientWidth)};if(measure(),"undefined"==typeof ResizeObserver)return;const r=new ResizeObserver(measure);return r.observe(t),r.observe(e),()=>r.disconnect()},[I,U]);const V=d??(B>0&&!G?B/R*1e3:12e3);return e("div",{ref:e=>{$.current=e,P.ref(e),"function"==typeof W?W(e):W&&(W.current=e)},className:m("plass-marquee",I&&"plass-marquee-vertical",z),style:{"--p-anim-gap":n(H),...c?{"--p-anim-ease":c}:{},"--p-anim-duration":`${Math.round(V)}ms`,"--p-anim-delay":`${p}ms`,"--p-anim-repeat":i(f),"--p-anim-direction":s(x?"out":"in",h),"--p-anim-state":P.state,...A},"data-plass-animation":"marquee","data-plass-finite":a(f)?void 0:"","data-state":P.state,"data-pause-on-hover":S?"":void 0,tabIndex:U&&E?0:void 0,role:w?"group":void 0,"aria-label":w,...r(O,P.handlers),children:Array.from({length:Math.max(1,M)},(t,r)=>(t=>e("div",{ref:0===t?k:void 0,className:"plass-marquee-track","aria-hidden":0===t?void 0:"true",...l(0!==t),children:N},t))(r))})});
@@ -1 +1 @@
1
- "use client";import{jsx as e,Fragment as r,jsxs as t}from"react/jsx-runtime";import*as a from"react";import{mergeProps as n}from"@base-ui/react/merge-props";import{useRender as s}from"@base-ui/react/use-render";import{useAnimationRun as i}from"../../internal/animate.js";import{usePrefersReducedMotion as m}from"../../internal/media.js";import{poolOf as o,scrambleAt as c}from"../../internal/scramble.js";import{cx as l,srOnlyCopyClasses as u}from"../../internal/styles.js";export const PlAnimateScramble=/* @__PURE__ */a.forwardRef(function PlAnimateScramble({children:d,characters:f,duration:p=1200,delay:h=0,tick:x=45,trigger:b="visible",play:j,once:M=!0,threshold:g=.2,paused:A,render:y,className:F,...v},N){const S=m(),q=i({trigger:b,play:j,once:M,threshold:g,paused:A,infinite:!1,nonce:d}),E=a.useMemo(()=>f??o(d),[f,d]),[P,R]=a.useState(()=>d),k=a.useRef(0);return a.useEffect(()=>{k.current=0},[q.runs,d]),a.useEffect(()=>{if(S)return void R(d);if(!q.started)return k.current=0,void R(c(d,E,0,0));if(A)return;const e=Math.max(1,p);let r,t=0,a=0,n=-1;const step=s=>{r??=s-k.current,k.current=s-r;const i=k.current-h,m=Math.min(1,i/e),o=Math.floor(Math.max(0,i)/Math.max(1,x));o!==n&&(n=o,a+=1,R(c(d,E,Math.max(0,m),a))),m<1?t=requestAnimationFrame(step):R(d)};return t=requestAnimationFrame(step),()=>cancelAnimationFrame(t)},[q.started,q.runs,S,A,d,E,p,h,x]),s({render:y??e("span",{}),ref:[N,q.ref],props:{className:l(F),"data-plass-animation":"scramble","data-state":q.state,children:t(r,{children:[e("span",{className:u,children:d}),e("span",{"aria-hidden":"true",children:P})]}),...n(v,q.handlers)}})});
1
+ "use client";import{jsx as r,Fragment as e,jsxs as t}from"react/jsx-runtime";import*as n from"react";import{mergeProps as a}from"@base-ui/react/merge-props";import{useRender as s}from"@base-ui/react/use-render";import{useAnimationRun as c}from"../../internal/animate.js";import{usePrefersReducedMotion as u}from"../../internal/media.js";import{poolOf as i,scrambleAt as m}from"../../internal/scramble.js";import{cx as o,drawnCopyClasses as l,srOnlyClasses as f}from"../../internal/styles.js";export const PlAnimateScramble=/* @__PURE__ */n.forwardRef(function PlAnimateScramble({children:d,characters:p,duration:h=1200,delay:x=0,tick:A=45,trigger:M="visible",play:b,once:g=!0,threshold:j=.2,paused:F,render:q,className:y,...N},R){const v=u(),E=c({trigger:M,play:b,once:g,threshold:j,paused:F,infinite:!1,target:d}),S=n.useMemo(()=>p??i(d),[p,d]),[P,k]=n.useState(()=>d),w=n.useRef(0),z=n.useRef(null),B=n.useRef(x);return n.useEffect(()=>{B.current=x}),n.useEffect(()=>{w.current=0,z.current=null},[E.runs,d]),n.useEffect(()=>{const r=Math.max(1,h);let e,t=0;if(v){if(k(d),!E.started)return;const land=()=>{z.current??=B.current,w.current=Math.max(w.current,z.current+r)};if(null!==z.current||w.current>=B.current)return void land();if(F)return;const wait=r=>{e??=r-w.current,w.current=r-e,w.current<B.current?t=requestAnimationFrame(wait):land()};return t=requestAnimationFrame(wait),()=>cancelAnimationFrame(t)}if(!E.started)return w.current=0,z.current=null,void k(m(d,S,0,0));if(F)return;let n=0,a=-1;const step=s=>{e??=s-w.current,w.current=s-e;const c=w.current-(z.current??B.current),u=Math.min(1,c/r),i=Math.floor(Math.max(0,c)/Math.max(1,A));c>=0&&(z.current??=B.current),i!==a&&(a=i,n+=1,k(m(d,S,Math.max(0,u),n))),u<1?t=requestAnimationFrame(step):k(d)};return t=requestAnimationFrame(step),()=>cancelAnimationFrame(t)},[E.started,E.runs,v,F,d,S,h,A]),s({render:q??r("span",{}),ref:[R,E.ref],props:{className:o(y),"data-plass-animation":"scramble","data-state":E.state,children:t(e,{children:[r("span",{className:f,children:d}),r("span",{"aria-hidden":"true","data-text":P,"data-sample":d,className:l})]}),...a(N,E.handlers)}})});
@@ -1 +1 @@
1
- "use client";import*as e from"react";import{mergeProps as a}from"@base-ui/react/merge-props";import{useRender as r}from"@base-ui/react/use-render";import{animBaseClass as t,lengthValue as n,useAnimateElement as s}from"../../internal/animate.js";import{cx as i}from"../../internal/styles.js";export const PlAnimateShake=/* @__PURE__ */e.forwardRef(function PlAnimateShake({duration:e=400,delay:l=0,easing:o,repeat:p=1,alternate:m,paused:c,trigger:d="manual",play:f=!1,once:h=!0,threshold:u=.2,replay:y,distance:g=6,render:k,className:N,style:b,children:j,...x},A){const P=s({effect:null,duration:e,delay:l,easing:o,repeat:p,alternate:m,x:n(g),trigger:d,play:f,once:h,threshold:u,paused:c,infinite:!1,nonce:y,children:j});return r({render:k,ref:[A,P.ref],props:{...a(x,P.props),className:i(t,"plass-anim-shake",P.className,N),style:{...P.style,...b},"data-plass-animation":"shake",children:P.children}})});
1
+ "use client";import*as e from"react";import{mergeProps as a}from"@base-ui/react/merge-props";import{useRender as r}from"@base-ui/react/use-render";import{animBaseClass as t,lengthValue as n,useAnimateElement as s}from"../../internal/animate.js";import{cx as i}from"../../internal/styles.js";export const PlAnimateShake=/* @__PURE__ */e.forwardRef(function PlAnimateShake({duration:e=400,delay:l=0,easing:o,repeat:m=1,alternate:p,paused:c,trigger:d="manual",play:f=!1,once:h=!0,threshold:u=.2,replay:y,distance:g=6,render:k,className:N,style:b,children:j,...x},A){const P=s({effect:null,duration:e,delay:l,easing:o,repeat:m,alternate:p,x:n(g),trigger:d,play:f,once:h,threshold:u,paused:c,infinite:!1,moves:!0,nonce:y,children:j});return r({render:k,ref:[A,P.ref],props:{...a(x,P.props),className:i(t,"plass-anim-shake",P.className,N),style:{...P.style,...b},"data-plass-animation":"shake",children:P.children}})});