plass-ui 1.4.0 → 1.6.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.
- package/CHANGELOG.md +432 -0
- package/README.md +45 -23
- package/dist/components/accordion/PlAccordion.d.ts +1 -1
- package/dist/components/alert/PlAlert.js +1 -1
- package/dist/components/anchor/PlAnchor.d.ts +14 -1
- package/dist/components/anchor/PlAnchor.js +1 -1
- package/dist/components/animate-appear/PlAnimateAppear.js +1 -1
- package/dist/components/animate-blink/PlAnimateBlink.js +1 -1
- package/dist/components/animate-counter/PlAnimateCounter.js +1 -1
- package/dist/components/animate-fade/PlAnimateFade.js +1 -1
- package/dist/components/animate-float/PlAnimateFloat.js +1 -1
- package/dist/components/animate-grow/PlAnimateGrow.js +1 -1
- package/dist/components/animate-headline/PlAnimateHeadline.js +1 -1
- package/dist/components/animate-lighting/PlAnimateLighting.js +1 -1
- package/dist/components/animate-marquee/PlAnimateMarquee.d.ts +16 -4
- package/dist/components/animate-marquee/PlAnimateMarquee.js +1 -1
- package/dist/components/animate-reveal/PlAnimateReveal.js +1 -1
- package/dist/components/animate-rotate/PlAnimateRotate.js +1 -1
- package/dist/components/animate-scramble/PlAnimateScramble.js +1 -1
- package/dist/components/animate-shake/PlAnimateShake.js +1 -1
- package/dist/components/animate-slide/PlAnimateSlide.js +1 -1
- package/dist/components/animate-split/PlAnimateSplit.d.ts +7 -4
- package/dist/components/animate-split/PlAnimateSplit.js +1 -1
- package/dist/components/animate-typing/PlAnimateTyping.d.ts +3 -2
- package/dist/components/animate-typing/PlAnimateTyping.js +1 -1
- package/dist/components/animate-zoom/PlAnimateZoom.js +1 -1
- package/dist/components/app-logo/PlAppLogo.js +1 -1
- package/dist/components/area-chart/PlAreaChart.d.ts +1 -1
- package/dist/components/area-chart/PlAreaChart.js +1 -1
- package/dist/components/aspect-ratio/PlAspectRatio.d.ts +2 -1
- package/dist/components/aspect-ratio/PlAspectRatio.js +1 -1
- package/dist/components/avatar/PlAvatar.js +1 -1
- package/dist/components/back-top/PlBackTop.d.ts +8 -1
- package/dist/components/back-top/PlBackTop.js +1 -1
- package/dist/components/badge/PlBadge.js +1 -1
- package/dist/components/bar-chart/PlBarChart.d.ts +1 -1
- package/dist/components/bar-chart/PlBarChart.js +1 -1
- package/dist/components/blockquote/PlBlockquote.js +1 -1
- package/dist/components/bottom-navigation/PlBottomNavigation.d.ts +3 -0
- package/dist/components/breadcrumb/PlBreadcrumb.js +1 -1
- package/dist/components/button/PlButton.d.ts +8 -0
- package/dist/components/button/PlButton.js +1 -1
- package/dist/components/calendar/PlCalendar.js +1 -1
- package/dist/components/card/PlCard.js +1 -1
- package/dist/components/carousel/PlCarousel.d.ts +1 -1
- package/dist/components/carousel/PlCarousel.js +1 -1
- package/dist/components/chat-bubble/PlChatBubble.d.ts +4 -1
- package/dist/components/chat-bubble/PlChatBubble.js +1 -1
- package/dist/components/checkbox/PlCheckbox.js +1 -1
- package/dist/components/chip/PlChip.d.ts +6 -2
- package/dist/components/chip/PlChip.js +1 -1
- package/dist/components/code-block/PlCodeBlock.d.ts +19 -2
- package/dist/components/code-block/PlCodeBlock.js +1 -1
- package/dist/components/color-picker/PlColorPicker.d.ts +19 -2
- package/dist/components/color-picker/PlColorPicker.js +1 -1
- package/dist/components/combobox/PlCombobox.d.ts +14 -7
- package/dist/components/combobox/PlCombobox.js +1 -1
- package/dist/components/command-palette/PlCommandPalette.d.ts +6 -2
- package/dist/components/command-palette/PlCommandPalette.js +1 -1
- package/dist/components/confirm/PlConfirmProvider.js +1 -1
- package/dist/components/container/PlContainer.js +1 -1
- package/dist/components/data-table/PlDataTable.d.ts +20 -6
- package/dist/components/data-table/PlDataTable.js +1 -1
- package/dist/components/date-picker/PlDatePicker.js +1 -1
- package/dist/components/date-range-picker/PlDateRangePicker.js +1 -1
- package/dist/components/date-time-picker/PlDateTimePicker.js +1 -1
- package/dist/components/drawer/PlDrawer.js +1 -1
- package/dist/components/empty/PlEmpty.js +1 -1
- package/dist/components/file-picker/PlFilePicker.d.ts +22 -3
- package/dist/components/file-picker/PlFilePicker.js +1 -1
- package/dist/components/floating-bottom-navigation/PlFloatingBottomNavigation.d.ts +3 -0
- package/dist/components/floating-bottom-navigation/PlFloatingBottomNavigation.js +1 -1
- package/dist/components/form/PlForm.js +1 -1
- package/dist/components/gallery/PlGallery.d.ts +40 -2
- package/dist/components/gallery/PlGallery.js +1 -1
- package/dist/components/gallery/PlGalleryViewer.js +1 -1
- package/dist/components/gauge-chart/PlGaugeChart.js +1 -1
- package/dist/components/heatmap-chart/PlHeatmapChart.js +1 -1
- package/dist/components/highlight/PlHighlight.d.ts +10 -3
- package/dist/components/highlight/PlHighlight.js +1 -1
- package/dist/components/hot-keys/PlHotKeys.js +1 -1
- package/dist/components/hover-card/PlHoverCard.d.ts +5 -3
- package/dist/components/hover-card/PlHoverCard.js +1 -1
- package/dist/components/how-to-steps/PlHowToSteps.js +1 -1
- package/dist/components/image/PlImage.d.ts +195 -8
- package/dist/components/image/PlImage.js +1 -1
- package/dist/components/image/PlImagePreview.d.ts +58 -0
- package/dist/components/image/PlImagePreview.js +1 -0
- package/dist/components/image/index.d.ts +1 -1
- package/dist/components/list/PlList.js +1 -1
- package/dist/components/menu/PlMenu.d.ts +14 -5
- package/dist/components/menu/PlMenu.js +1 -1
- package/dist/components/meter/PlMeter.js +1 -1
- package/dist/components/navigation-menu/PlNavigationMenu.d.ts +7 -1
- package/dist/components/navigation-menu/PlNavigationMenu.js +1 -1
- package/dist/components/number-field/PlNumberField.d.ts +12 -4
- package/dist/components/number-field/PlNumberField.js +1 -1
- package/dist/components/otp-field/PlOtpField.d.ts +15 -1
- package/dist/components/otp-field/PlOtpField.js +1 -1
- package/dist/components/page-layout/PlPageLayout.js +1 -1
- package/dist/components/pagination/PlPagination.d.ts +12 -9
- package/dist/components/pagination/PlPagination.js +1 -1
- package/dist/components/panes/PlPanes.d.ts +5 -0
- package/dist/components/panes/PlPanes.js +1 -1
- package/dist/components/pie-chart/PlPieChart.js +1 -1
- package/dist/components/pill/PlPill.js +1 -1
- package/dist/components/popover/PlPopover.d.ts +5 -5
- package/dist/components/popover/PlPopover.js +1 -1
- package/dist/components/progress-box/PlProgressBox.js +1 -1
- package/dist/components/progress-circular/PlProgressCircular.js +1 -1
- package/dist/components/progress-linear/PlProgressLinear.js +1 -1
- package/dist/components/radio-group/PlRadioGroup.js +1 -1
- package/dist/components/rating/PlRating.d.ts +1 -1
- package/dist/components/rating/PlRating.js +1 -1
- package/dist/components/scatter-chart/PlScatterChart.js +1 -1
- package/dist/components/scroll-area/PlScrollArea.js +1 -1
- package/dist/components/scroll-zone/PlScrollZone.d.ts +19 -6
- package/dist/components/scroll-zone/PlScrollZone.js +1 -1
- package/dist/components/segmented-button/PlSegmentedButton.js +1 -1
- package/dist/components/select/PlSelect.d.ts +8 -2
- package/dist/components/select/PlSelect.js +1 -1
- package/dist/components/show/PlShow.d.ts +16 -0
- package/dist/components/show/PlShow.js +1 -1
- package/dist/components/sidebar/PlSidebar.js +1 -1
- package/dist/components/sidebar/PlSidebarTrigger.js +1 -1
- package/dist/components/skeleton/PlSkeleton.js +1 -1
- package/dist/components/slider/PlSlider.d.ts +13 -1
- package/dist/components/slider/PlSlider.js +1 -1
- package/dist/components/sparkline/PlSparkline.js +1 -1
- package/dist/components/spoiler/PlSpoiler.d.ts +15 -8
- package/dist/components/spoiler/PlSpoiler.js +1 -1
- package/dist/components/stat/PlStat.d.ts +4 -0
- package/dist/components/stat/PlStat.js +1 -1
- package/dist/components/stepper/PlStepper.d.ts +1 -1
- package/dist/components/stepper/PlStepper.js +1 -1
- package/dist/components/switch/PlSwitch.js +1 -1
- package/dist/components/table/PlTable.d.ts +5 -5
- package/dist/components/tabs/PlTabs.d.ts +26 -1
- package/dist/components/tabs/PlTabs.js +1 -1
- package/dist/components/text-field/PlTextField.d.ts +15 -5
- package/dist/components/text-field/PlTextField.js +1 -1
- package/dist/components/text-link/PlTextLink.js +1 -1
- package/dist/components/time-picker/PlTimePicker.js +1 -1
- package/dist/components/timeline/PlTimeline.js +1 -1
- package/dist/components/timeline-chart/PlTimelineChart.js +1 -1
- package/dist/components/toast/PlToast.js +1 -1
- package/dist/components/toggle/PlToggle.js +1 -1
- package/dist/components/tooltip/PlTooltip.js +1 -1
- package/dist/components/tour/PlTour.js +1 -1
- package/dist/components/transfer/PlTransfer.d.ts +10 -1
- package/dist/components/transfer/PlTransfer.js +1 -1
- package/dist/components/tree/PlTree.js +1 -1
- package/dist/components/tree-select/PlTreeSelect.js +1 -1
- package/dist/components/typography/PlTypography.js +1 -1
- package/dist/components/window-pane/PlWindowPane.d.ts +3 -3
- package/dist/components/window-pane/PlWindowPane.js +1 -1
- package/dist/css/area-chart.css +1 -0
- package/dist/css/back-top.css +2 -0
- package/dist/css/bar-chart.css +1 -0
- package/dist/css/calendar.css +1 -0
- package/dist/css/carousel.css +2 -0
- package/dist/css/color-picker.css +1 -0
- package/dist/css/combobox.css +1 -0
- package/dist/css/command-palette.css +1 -0
- package/dist/css/confirm.css +2 -0
- package/dist/css/data-table.css +5 -0
- package/dist/css/date-picker.css +1 -0
- package/dist/css/date-range-picker.css +1 -0
- package/dist/css/date-time-picker.css +1 -0
- package/dist/css/floating-action-button.css +2 -0
- package/dist/css/gallery.css +5 -0
- package/dist/css/gauge-chart.css +1 -0
- package/dist/css/heatmap-chart.css +1 -0
- package/dist/css/icon-button.css +1 -0
- package/dist/css/image.css +2 -0
- package/dist/css/line-chart.css +1 -0
- package/dist/css/menubar.css +1 -0
- package/dist/css/pagination.css +1 -0
- package/dist/css/pie-chart.css +1 -0
- package/dist/css/popconfirm.css +2 -0
- package/dist/css/scatter-chart.css +1 -0
- package/dist/css/scroll-zone.css +2 -0
- package/dist/css/sidebar.css +3 -0
- package/dist/css/sparkline.css +1 -0
- package/dist/css/spoiler.css +1 -0
- package/dist/css/stat.css +1 -0
- package/dist/css/time-picker.css +1 -0
- package/dist/css/timeline-chart.css +1 -0
- package/dist/css/tour.css +1 -0
- package/dist/css/transfer.css +4 -0
- package/dist/css/tree-select.css +2 -0
- package/dist/hooks/usePlElementSize.d.ts +4 -1
- package/dist/hooks/usePlElementSize.js +1 -1
- package/dist/hooks/usePlHotKeys.d.ts +5 -1
- package/dist/hooks/usePlHotKeys.js +1 -1
- package/dist/hooks/usePlOnScreen.d.ts +4 -0
- package/dist/hooks/usePlOnScreen.js +1 -1
- package/dist/internal/animate.d.ts +24 -21
- package/dist/internal/animate.js +1 -1
- package/dist/internal/calendar.js +1 -1
- package/dist/internal/chart-frame.d.ts +3 -14
- package/dist/internal/chart-frame.js +1 -1
- package/dist/internal/chart-line.js +1 -1
- package/dist/internal/chart.d.ts +90 -3
- package/dist/internal/chart.js +1 -1
- package/dist/internal/color-scheme.js +1 -1
- package/dist/internal/data-table.d.ts +5 -4
- package/dist/internal/data-table.js +1 -1
- package/dist/internal/date.d.ts +10 -0
- package/dist/internal/date.js +1 -1
- package/dist/internal/defaults.d.ts +14 -1
- package/dist/internal/drag.d.ts +16 -0
- package/dist/internal/drag.js +1 -1
- package/dist/internal/focusable.d.ts +8 -0
- package/dist/internal/focusable.js +1 -0
- package/dist/internal/form.d.ts +61 -0
- package/dist/internal/form.js +1 -0
- package/dist/internal/gallery.d.ts +60 -14
- package/dist/internal/gallery.js +1 -1
- package/dist/internal/glow.d.ts +32 -0
- package/dist/internal/glow.js +1 -0
- package/dist/internal/highlight.js +1 -1
- package/dist/internal/image.d.ts +94 -0
- package/dist/internal/image.js +1 -0
- package/dist/internal/inert.d.ts +19 -0
- package/dist/internal/inert.js +1 -0
- package/dist/internal/keys.d.ts +19 -0
- package/dist/internal/keys.js +1 -1
- package/dist/internal/labels.d.ts +58 -1
- package/dist/internal/labels.js +1 -1
- package/dist/internal/link.d.ts +17 -0
- package/dist/internal/link.js +1 -1
- package/dist/internal/notch.d.ts +83 -0
- package/dist/internal/notch.js +1 -0
- package/dist/internal/page-layout.d.ts +14 -7
- package/dist/internal/page-layout.js +1 -1
- package/dist/internal/picker.d.ts +51 -8
- package/dist/internal/picker.js +1 -1
- package/dist/internal/progress.d.ts +16 -8
- package/dist/internal/progress.js +1 -1
- package/dist/internal/scramble.d.ts +4 -0
- package/dist/internal/scramble.js +1 -1
- package/dist/internal/scroll-target.d.ts +5 -0
- package/dist/internal/scroll-target.js +1 -0
- package/dist/internal/steps.js +1 -1
- package/dist/internal/styles.d.ts +87 -0
- package/dist/internal/styles.js +1 -1
- package/dist/internal/text.d.ts +39 -0
- package/dist/internal/text.js +1 -0
- package/dist/internal/watermark.d.ts +44 -0
- package/dist/internal/watermark.js +1 -0
- package/dist/internal/wheel.d.ts +52 -0
- package/dist/internal/wheel.js +1 -0
- package/dist/internal/window.js +1 -1
- package/dist/locales/de.js +1 -1
- package/dist/locales/es.js +1 -1
- package/dist/locales/fr.js +1 -1
- package/dist/locales/ja.js +1 -1
- package/dist/locales/ko.js +1 -1
- package/dist/locales/zh-Hans.js +1 -1
- package/dist/provider/PlColorSchemeScript.js +1 -1
- package/dist/provider/PlassProvider.d.ts +4 -3
- package/dist/provider/PlassProvider.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/tailwind.css +313 -15
- package/dist/tokens.css +313 -15
- package/dist/types.d.ts +71 -29
- package/package.json +15 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,438 @@
|
|
|
2
2
|
|
|
3
3
|
> This package's history. The Flutter package keeps its own at [`packages/flutter/CHANGELOG.md`](https://github.com/jooy2/plass-ui/blob/main/packages/flutter/CHANGELOG.md), because the two version independently.
|
|
4
4
|
|
|
5
|
+
## vNext (2026--)
|
|
6
|
+
|
|
7
|
+
## 1.6.0 (2026-09-19)
|
|
8
|
+
|
|
9
|
+
### Breaking changes
|
|
10
|
+
|
|
11
|
+
- **`plass-ui/styles.css` leaves the page around the components alone.** The reset inside it took the bullets off every `<ul>`, flattened every `<h1>`–`<h6>` to body text, closed up the margins of `<p>`, `<blockquote>`, `<figure>` and `<pre>`, and zeroed every element's border, which reached the rest of the page as readily as the components — a list in an app's own body text lost its markers, and `<hr>` lost its line. Those rules are gone. The components now carry `m-0 list-none p-0` where they render a list and `m-0` where they render a heading or a paragraph, and the only border the file still zeroes is the one a browser draws on a form control. An app that was using the stylesheet as its page reset gets the browser's defaults back for its own prose and has to write them itself, or import Tailwind's Preflight. In the other direction, a page's own `p { margin: 1rem }` no longer reaches inside a component, because a utility outranks a type selector where the old `:where()` rule lost to it. `plass-ui/tailwind.css` never carried the reset and is unchanged.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **A labelled field can put its label in its own top edge, with `labelPlacement="notch"`.** `top` is still the default, so nothing moves without being asked. A notch buys back a row of vertical space and ties the name to the box rather than to whatever is above it, and the cut is a real one: a `<legend>` in a `<fieldset>`, which is the only way to take a segment out of a border without knowing what is behind it — and a library that does not paint the page cannot know. It is on every field-shaped control, which is `PlTextField`, `PlSelect`, `PlCombobox`, `PlNumberField`, `PlFilePicker`, and the six that share a picker's trigger: `PlDatePicker`, `PlDateRangePicker`, `PlDateTimePicker`, `PlTimePicker`, `PlColorPicker` and `PlTreeSelect`. `PlassProvider` carries it as well, for a product whose forms all notch. Two things follow from the label sitting on the edge, and both are deliberate: focus thickens the edge rather than drawing a ring, because a ring is a rectangle and would run straight through the label; and the label is out of the flow, so it no longer widens the control and a long one wants `fullWidth`. `PlOtpField` is the one labelled field that does not take it: a notch is a segment out of one continuous edge, and a row of separate boxes with gaps between them has none, so its label stays above the row. `PlSwitch`'s own `labelPlacement` is a different axis — which side of a tick the label sits on — and is unchanged.
|
|
16
|
+
|
|
17
|
+
- **The interaction light is on the field shells as well as on the keys.** `.plass-glow` was written for every interactive surface and reached three of them — `PlButton`, `PlToggle` and `PlPill` — so a form of Plass fields answered a pointer with nothing while the button under it bloomed. It is now on `PlTextField`, `PlNumberField`, `PlSelect`, `PlCombobox`, `PlFilePicker`'s drop zone, a pressable `PlChip`, each segment of a `PlSegmentedButton`, and the six triggers that share a picker's shell: `PlDatePicker`, `PlDateRangePicker`, `PlDateTimePicker`, `PlTimePicker`, `PlColorPicker` and `PlTreeSelect`. `surfaceSlots` declares `--p-glow` and `--p-flash` for it, in the family's own tint in all three variants — a `solid` field is the well rather than a coloured fill, and white light on it would be white light on a near-white sheet. The light goes out while a control is disabled or read-only. Both layers stay at `opacity: 0` until a pointer arrives, so a page of fields nobody is touching pays for two custom properties and nothing else.
|
|
18
|
+
|
|
19
|
+
Three are deliberately without it. A `PlCheckbox`, a `PlRadio` and a `PlSwitch`'s track are the size of the text beside them, and a 6rem bloom inside a 20px box is a flat wash that slides about rather than light. A `PlOtpField`'s slot **is** an `<input>`, which renders no `::before` or `::after` at all.
|
|
20
|
+
|
|
21
|
+
- **A chart series takes `dashed`, so a forecast can be told from a measurement.** The Flutter build already had the field, documented and listed in its props table, and neither painter read it — a line marked as a forecast came out solid. It is drawn in both builds now, a 6px dash with a 4px gap, and `PlassChartSeries` on this side takes it too. Only a line has a line to dash: it does nothing on a bar, or on a stacked band, whose fill is the mark.
|
|
22
|
+
|
|
23
|
+
- **`.plass-theme` recomputes a colour family on the element that carries it.** Setting `--plass-primary-solid` on a `<div>` moved the base and nothing else, because the gradient, the tint and the hairline are mixed from it on the nearest theme root, which still held the old colour — so a scoped override gave a control the new solid and the old everything else. An element with the class derives the whole set again from the bases declared beside it. It carries no theme of its own, unlike `.dark` and `data-theme`, so the subtree stays light or dark with the page. A popup rendered through a portal sits outside the element and is not covered; `.plass-portal` is the hook to declare the same values on.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **The pointer bloom is drawn under a control's label rather than over it.** `.plass-glow::before` is positioned, and a positioned pseudo-element paints above every in-flow thing in its box, so the light was laid over the words on a key and over the value in a field. On a white label on a filled button that is invisible; on the dark ink a field is written in it is a value that got harder to read the moment a pointer arrived. The layer now takes `z-index: -1` inside a stacking context the surface owns, which paints it after the fill and before the content. The press flash stays over everything, which is the pair the Flutter build has always drawn and the pair this library's own design language describes.
|
|
28
|
+
|
|
29
|
+
- **A `PlSegmentedButton`'s segments take the light of whatever they are standing on.** All of them read the set's own two slots, so on a `solid` set an unchosen segment was lit in white — which is white light on the trough, a near-white sheet, and therefore nothing at all. A chosen segment rides the tile and keeps the white; an unchosen one takes the family's soft tint. The Flutter build makes the same distinction.
|
|
30
|
+
|
|
31
|
+
- **A `PlFilePicker` says why it turned files away.** `onReject` was the only way to hear about it, so a picker a caller had not wired reported a refusal by doing nothing at all — a reader dropped ten files, saw six appear and had no way to learn what happened to the other four. The box now says it under itself, one line per reason and counted: "2 files are not an accepted type", "1 file is too large", "3 files did not fit". Grouped by reason rather than named one file at a time, because the batch that goes wrong is usually the big one, and what the limit _is_ stays in `hint` where a picker already says what it accepts. `showRejections={false}` turns it off for an app that says the same thing somewhere of its own, and `onReject` fires either way. It is a `role="status"` rather than an alert, and it sets no `aria-invalid`: what was rejected never reached the value, so a field holding four good files is a field holding four good files. `filesRejectedType`, `filesRejectedSize` and `filesRejectedCount` join the label pack in all seven languages, which takes it to a hundred keys.
|
|
32
|
+
|
|
33
|
+
- **`PlColorPicker` takes `classNames`, which every other labelled field already did.** It was the one field a caller could not reach inside: `className` lands on the stack, and the trigger, the label, the description and the error had no way in at all. The four slots mean what they mean everywhere else, with one addition — with `inline` there is no trigger, so `control` is the panel, which is the square and the rails a reader actually acts on. `PlColorPicker` and `PlTreeSelect` are now both in the contract test that holds the vocabulary together, which is what will keep the next one from drifting.
|
|
34
|
+
|
|
35
|
+
- **A `PlFilePicker` answers a file dragged onto it.** The drag-over state was written and then lost: a plain `bg-(--p-soft-hover)` and a plain `[border-color:var(--p-ring)]` laid over the resting fill and the resting edge are one class each against one class each, and two declarations of a property at one specificity are settled by the order Tailwind emitted them in — which put the resting pair last, so the box looked exactly the same whether or not a file was over it. The state now replaces the resting classes instead of stacking on them, and it says more than it used to: the sheet takes the family's wash, the dashed edge takes the ring colour, and a soft halo spreads outside the box, all three easing over the house duration. Nothing grows and nothing lifts, because a target that moves while you are aiming at it is a target you miss. The Flutter build, which resolves the same state in Dart rather than in a stylesheet, was never affected and gains the halo.
|
|
36
|
+
|
|
37
|
+
- **A file let go over a `PlFilePicker`'s list lands in the picker.** The drag listeners sat on a wrapper the list was a sibling of, not a child of, so the bottom half of what looks like one drop target — and what the component's own source describes as one — passed the file to the browser, which navigated away from the page to open it. The list is inside the drop area now.
|
|
38
|
+
|
|
39
|
+
- **A `PlNumberField`'s and a `PlOtpField`'s label are drawn like every other field's.** Both were a weight light of the rest — `font-medium` where a `PlTextField`, a `PlSelect`, a `PlCombobox` and every picker are `font-semibold` — and the number field also dimmed its whole label with `opacity-50` when disabled instead of taking the muted foreground the others take. A form holding two of them showed two weights of label. The Flutter build has always drawn both at the heavier weight, so this is also the two packages agreeing again.
|
|
40
|
+
|
|
41
|
+
- **A `PlDatePicker` and a `PlDateRangePicker` take `density` from the provider.** The other four pickers resolve it, and these two let it ride through as an unread prop, so `<PlassProvider density="compact">` reached every field on a form except those two triggers, which kept the default track's padding. The Flutter build resolves it in all six and always did.
|
|
42
|
+
|
|
43
|
+
- **Focusing a chart reads a summary rather than every number in it.** `aria-describedby` pointed at the hidden data table, and a screen reader flattens whatever that attribute names into one string — so a chart of four hundred cells announced four hundred numbers on every focus, ahead of anything else, and the same table is a sibling in the reading order, so it was heard twice. The description is now a line: each visible series and where it ended up on a chart with axes, every slice and its share on a pie, and each row and the span its cells cover on a heatmap. The table has not moved and still carries every value, which is what a reader steps into when they want one.
|
|
44
|
+
|
|
45
|
+
- **A time axis whose two ends are the same moment opens a day around it.** `min` and `max` naming one instant left the axis with no width, and the two builds failed differently: a mark landed a screen off the plot here and on the origin in Flutter. The guard that already opened a day around a single instant in the data now covers a caller's own bounds too.
|
|
46
|
+
|
|
47
|
+
- **A `PlAnimateFloat` takes a `distance` that has to be worked out, or that is negative.** The upward drift was written by putting a minus sign in front of the value, which only negates a plain number: `calc(1rem + 2px)` became `-calc(1rem + 2px)` and `-8` became `--8px`, and neither is a length, so the drift was dropped. It is now the same `calc(-1 * …)` a `PlAnimateSlide` uses, and a negative number drifts the other way.
|
|
48
|
+
|
|
49
|
+
- **A `PlAnimateCounter` with an inline `format` builds one formatter.** The `Intl.NumberFormat` was memoised on the options object, and an object written inline — which is how the prop reads best — is a new reference on every render around the counter, so each one built another. It is now keyed by what the options say.
|
|
50
|
+
|
|
51
|
+
- **`PlAnimateLighting` starts over when it is hovered or played again.** The rewind clears `animation-name` on the element, and an inline style cannot reach a pseudo-element, so the arc carried on from wherever the last pass had left it. The root now carries `data-plass-rewind` for the length of that read, and the stylesheet answers it.
|
|
52
|
+
|
|
53
|
+
- **`PlAnimateLighting` turns on the curve it was given.** The arc is drawn on a pseudo-element, and `linear` was written into it, so `easing` reached the root and stopped there. It is the default now rather than the only answer, which is what `curve` already does in the Flutter build.
|
|
54
|
+
|
|
55
|
+
- **`PlAnimateTyping` types a new string of the same length again.** The reset watched how many characters there were rather than what they said, so "design" becoming "deploy" appeared whole instead of being typed. The Flutter build already started over.
|
|
56
|
+
|
|
57
|
+
- **A `PlAnimateMarquee` measures itself once rather than on every render around it.** `children` was a dependency of the effect that measures the strip, and it is a new reference on every render a parent does, so each one read the layout back and built a new `ResizeObserver`. The observer on the track is what catches content that changes the strip, so the measurement now happens when the size does.
|
|
58
|
+
|
|
59
|
+
- **A `PlAnimateMarquee` with a `speed` of zero or less stands still.** The travel was divided by the speed, so zero gave an infinite number of milliseconds: the strip carried an `animation-duration` of `Infinityms`, which no browser reads. A speed of zero or less is now read as not moving and holds the strip where it is, as `paused` does, and an explicit `duration` still decides on its own.
|
|
60
|
+
|
|
61
|
+
- **Copying a text effect gives its line once.** `PlAnimateSplit`, `PlAnimateScramble`, `PlAnimateCounter` and `PlAnimateTyping` each draw their line and keep a clipped copy of it beside them for a screen reader, and a selection took both: "Internationalization is long" came back as "Internationalization is longInternationalization is long". The clipped copy is now left out of the selection, and a screen reader still reads it.
|
|
62
|
+
|
|
63
|
+
- **A floating `PlBackTop` floats, and clears the safe area.** `floating` pinned the button with a `fixed` utility, which `PlButton`'s own `relative` beat at the same specificity later in the stylesheet, so the button scrolled with the page instead of staying in the corner — every demo on the site passes `floating={false}`, which is why nobody saw it. It is pinned inline now, as `PlFloatingActionButton` is, 24px off the bottom end corner with `env(safe-area-inset-bottom)` added to that, so it no longer sits under the home indicator or the navigation bar of an edge-to-edge screen. A `style` of your own still replaces the pinning.
|
|
64
|
+
|
|
65
|
+
- **A vertical `PlStepper` names its panel after the step it sits in.** Only the horizontal panel was a `role="group"` named by its step, so a screen reader landing in a vertical one was told nothing about which step it belonged to. Both are named now, which is what the stepper page has always said.
|
|
66
|
+
|
|
67
|
+
## 1.5.0 (2026-09-15)
|
|
68
|
+
|
|
69
|
+
### Breaking changes
|
|
70
|
+
|
|
71
|
+
- **A label pack now holds functions, so a Server Component cannot pass one to `PlassProvider`.** React does not send a function across the server boundary. Render the `PlassProvider` that takes a pack from a file with `'use client'` at the top.
|
|
72
|
+
|
|
73
|
+
- **Without `getRowKey`, a `PlDataTable` row is keyed by its position in `rows`, and every callback's `index` is that position.** The default key and the `index` handed to `getRowKey`, `render`, `onRowClick` and `isRowSelectable` used to be the row's place in what was drawn, while `onSelectedChange` looked the rows up by their place in `rows`. Ticking the first row on page two also showed the first row on page one as ticked and handed back `rows[0]`, and a sort or a search did the same. Both now count from the top of `rows`, so a row keeps its key and its `index` wherever a sort, a search or a page puts it. Code that used `index` as a place on the screen, such as numbering the rows as drawn, has to count them itself. A selection kept by default keys now names different rows, and `getRowKey` is still the way to key rows that survive `rows` changing.
|
|
74
|
+
|
|
75
|
+
- **Picking a day in `PlDateTimePicker` keeps the moment inside `minDate` and `maxDate`.** The day that holds a bound stays selectable, and picking it used to keep the clock as it was, or put midnight on it, without checking the bound again. With `minDate={new Date()}`, picking today committed today at 00:00. The clock is now moved into the bounds, up to the next whole minute (or second, with `showSeconds`) at or after `minDate` and down to the last one at or before `maxDate`. A clock that is already inside the bounds is kept as before.
|
|
76
|
+
|
|
77
|
+
- **A lone `width` or `height` on `PlImage` now sizes its box.** Passed alone, either one used to reach the `<img>` as an attribute and change nothing on the page. Now `height={200}` draws a box 200 pixels tall, and `width={320}` one 320 pixels wide. If you passed one of them only as a hint about the file, pass both dimensions of the file, or remove the one.
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- **`PlShow` takes `render`, so a gate inside a paragraph can be a `<span>`.** It always rendered a `<div>`, which a `<p>` cannot hold, so the HTML parser closed the paragraph in front of it and a server-rendered page failed to hydrate. `render={<span />}` renders another element, and it gates the same way.
|
|
82
|
+
|
|
83
|
+
- **`PlButton` takes `focusableWhenDisabled`.** A `disabled` button leaves the tab order, which drops the focus when a control becomes unavailable under it. With `focusableWhenDisabled` it stays a tab stop and is announced as unavailable, the way a stepper at the end of a `PlPagination` row now is.
|
|
84
|
+
|
|
85
|
+
- **`PlAnchor` takes a `target`, for headings that scroll inside an element rather than the window.** In an app shell whose `<main>` scrolls on its own the window never moves, so no row was ever lit. `target` takes an element, a ref or a function returning one, as the `target` of `PlBackTop` does, and the reading line, `offset` and the last-row rule are measured against that element. Left out, the list follows the window as before.
|
|
86
|
+
|
|
87
|
+
- **Eleven words join the label packs**, for the strings that were written into a component in English with no way to translate them but a prop: `acknowledge` (the OK on `PlConfirmProvider`'s alert), `optional` (`PlStep`), `notifications` (the region `PlToastProvider` announces toasts in), `sidebarOpen` (`PlSidebarTrigger`), `spoilerWarning` (`PlSpoiler`), `filePickerTitle` (`PlFilePicker`), and the five words a `PlChatBubble`'s mark says, `messageSending`, `messageSent`, `messageDelivered`, `messageRead` and `messageFailed`. All seven packs translate them. A pack of your own built against `PlassLabels` needs the new keys.
|
|
88
|
+
|
|
89
|
+
- **`PlImage` and `PlAspectRatio` take `fit="scale-down"`.** It is `contain` that never enlarges a file smaller than the box.
|
|
90
|
+
|
|
91
|
+
- **A lone `width` or `height` sizes a `PlImage`'s box.** A lone `height` is a box that tall across its container, and takes its width from a `ratio` when there is one. A lone `width` is a box that wide, never wider than its container. A number or a string of digits is pixels, and any other string is a CSS length. Both dimensions together keep describing the file. See Breaking changes for what this changes in existing code.
|
|
92
|
+
|
|
93
|
+
- **`PlImage` takes a `position`.** `center`, a side, a corner such as `'top left'`, or two percentages such as `'30% 20%'`. It decides which part of the picture a `cover` crop keeps, and where `contain`, `none` and `scale-down` leave their empty space. It is read on the picture as it is shown, so `position="top"` keeps the top of what the reader sees through `rotate` and `flip`, and it is physical, so it does not move on a right-to-left page. Any other `object-position` value passes through as written.
|
|
94
|
+
|
|
95
|
+
- **`PlImage` takes a `letterbox`.** It fills the part of the box that `contain`, `none` and `scale-down` leave empty. `blur` draws the picture itself behind it, covering the box and blurred, turned, mirrored, placed and tinted like the picture. The copy loads from the picture's own `src`, `srcSet`, `sizes`, `loading`, `decoding`, `crossOrigin` and `referrerPolicy`, so it costs no second request, and it is hidden from assistive technology and takes no pointer. Any other string is a CSS `background` painted on the box.
|
|
96
|
+
|
|
97
|
+
- **A `PlImage` placeholder can be a picture.** <code v-pre>placeholder={{ src, blur }}</code> stands a small copy of the picture in while the file arrives, from a URL, a data URI or a `Blob`. It is drawn under the picture with the picture's `fit`, `position`, `rotate`, `flip` and `filter`, stays until the picture has finished fading in over it, and is then removed in one step, so the page never shows through a cross-fade. `blur: true` blurs it by 20 pixels and a number by that many. A `Blob` gets an object URL that is revoked when the stand-in goes, and the skeleton is not drawn while a picture stands in. The stand-in needs a reserved box, as the skeleton does.
|
|
98
|
+
|
|
99
|
+
- **`PlImage` takes a `priority`.** For the picture a page is judged by, usually its Largest Contentful Paint image: it sets `loading="eager"` and a high fetch priority, and an attribute written out still wins. The attribute is written as `fetchPriority` under React 19 and as `fetchpriority` under React 18, so neither warns. A blurred `letterbox` copy is asked for the same way.
|
|
100
|
+
|
|
101
|
+
- **`PlImage` takes a `rotate`.** `0`, `90`, `180` or `270` degrees clockwise, and any other number goes to the nearest quarter. A picture on its side is laid out on its side: `width` and `height` still describe the file, so `width={1200} height={800} rotate={90}` reserves a box two wide by three tall, a `ratio` is kept as the shape of the layout, and without either the box takes the turned shape once the file has loaded. The turn is the CSS `rotate` property rather than `transform`, so a `transform` of the caller's own still applies on top, and `preview` opens the picture turned the same way.
|
|
102
|
+
|
|
103
|
+
- **`PlImage` takes a `flip`.** `horizontal`, `vertical` or `both`, along the axes the picture is shown on, so `flip="horizontal"` swaps left and right on the screen whether or not `rotate` has turned it. It is drawn with the CSS `scale` property, which leaves `transform` free, and `preview` opens the picture mirrored the same way.
|
|
104
|
+
|
|
105
|
+
- **`PlGallery` carries the new `PlImage` props to its pictures.** An item takes `rotate`, `flip`, `position` and a picture `placeholder`, and the gallery takes `fit` (still `cover` by default), `letterbox` and `loading` for every tile. An item's `ratio` stays the stored file's proportion: a picture on its side is dealt into a masonry lane, grown in a justified row and opened in the viewer by its turned proportion, while a `grid` tile keeps the gallery's shape. Ratios of unturned items still reach the picture as written. `hover="zoom"` is now a `transform`, so it still zooms a mirrored picture.
|
|
106
|
+
|
|
107
|
+
- **`PlImage` takes a `filter`.** Six named treatments — `grayscale`, `sepia`, `saturate`, `desaturate`, `contrast` and `dim` — and anything else you pass is used as a CSS `filter` chain exactly as written. It is set through a `--p-filter` slot and rides the house transition, which `filter` was already on, so a treatment swapped on hover travels at the same pace as the picture's own fade instead of snapping while the fade is still moving. The skeleton and the fallback are left alone.
|
|
108
|
+
|
|
109
|
+
- **`PlImage` takes a `watermark`.** A bare string sits in the bottom corner; an object says where it goes, how visible it is and at what angle. `placement: 'tile'` covers the whole picture, which is what a proof or a preview wants — a corner mark is cropped off in a second. A tiled mark is one repeating SVG background rather than forty or fifty elements, and the layer is turned as one layer so the repeat has no seam. It is drawn only once the picture has arrived, is `aria-hidden`, takes no pointer, and follows the picture into `preview`.
|
|
110
|
+
|
|
111
|
+
- **`PlImage` takes a `protect`.** Refuses the context menu, a drag out of the page, a text selection over the picture and — the one that is easy to forget — the iOS long-press callout, which on that platform _is_ the context menu. It is a deterrent and not a lock, and the documentation says so: the file is still one request away. A caller's own `onContextMenu` still runs and cannot turn the refusal off, and the refusal follows the picture into `preview`. There is no Flutter equivalent, because a Flutter app paints its pictures onto a canvas and there is no per-picture menu to refuse.
|
|
112
|
+
|
|
113
|
+
- **`PlTabs` answers the wheel.** A bar with more tabs than room is a scroll container like any other, and a mouse has one wheel that points down the page — the one direction the bar does not run in — so the reader who could see there were more tabs had nothing to reach them with but the arrow keys, which also change the selection. A vertical wheel over an overflowing bar now moves it along, exactly as it does over a `PlScrollZone`. `wheel={false}` turns it off, a bar whose tabs all fit never takes the gesture, and a bar that runs down the side is left alone.
|
|
114
|
+
|
|
115
|
+
- **`PlScrollZone` and `PlTabs` take an `overscroll`.** `'contain'`, the default, or `'auto'`, spelled after CSS's own `overscroll-behavior` and shared as `PlassOverscroll`. See below for what the default changes.
|
|
116
|
+
|
|
117
|
+
### Fixed
|
|
118
|
+
|
|
119
|
+
- **Adding a slide at the front of a `PlCarousel` keeps the slides already there.** Each slide was keyed by its position, so a new first slide remounted every slide after it and threw away their state, such as a playing video or a half-filled form. A slide is now keyed by its own `key`.
|
|
120
|
+
|
|
121
|
+
- **A vertical `PlStepper` shows only the panel of the step `active` is on.** The panel opened on any step whose `status` was `current`, so marking a step behind the reader `current` again, as for one that failed validation, opened a second panel, and giving the active step another `status` closed its own. The panel now follows `active` whatever `status` says, as the horizontal panel and the Flutter build already did.
|
|
122
|
+
|
|
123
|
+
- **A screen reader names the panel of a horizontal `PlStepper` after its step.** The panel carried `aria-labelledby` on a `div` with no role, and a name on such an element is never read, so moving into the panel said nothing about which step it belonged to. The panel is now a `role="group"` named by the step's label.
|
|
124
|
+
|
|
125
|
+
- **An `interactive` `PlCard` no longer glides up under reduced motion.** The lift under the pointer and its shadow still eased over `--plass-duration` for a reader who had asked for less motion. Both now change at once, as in the Flutter build.
|
|
126
|
+
|
|
127
|
+
- **A picker trigger measured with a `format` that writes the weekday leaves room for Friday.** The dates it measures to hold its width fell on every weekday but Friday, so the trigger could be a little narrower than a Friday needed. A Friday is now among them.
|
|
128
|
+
|
|
129
|
+
- **A held `PlScrollZone` button, and the wheel over a `PlScrollZone` or `PlTabs` strip, keep up on a strip whose CSS sets `scroll-behavior: smooth`.** Each frame of a hold and each wheel step scrolled with `behavior: 'auto'`, which follows `scroll-behavior`, so every step started a slide that the next one cut short. The strip fell behind the button and the wheel, to under half the distance in Chromium, and all but stopped in Firefox. Those steps now scroll with `'instant'`. The buttons in `item` and `page` mode still slide.
|
|
130
|
+
|
|
131
|
+
- **A `PlPill` does less work when its `details` re-renders.** A pill with `details` written inline disconnected its size observer, made a new one and measured its panel again on every render, so a pill showing a live counter did all three every second. It now keeps one observer for as long as it has `details`.
|
|
132
|
+
|
|
133
|
+
- **A `PlChatBubble` link preview fetches its picture only when the card comes near the screen.** Every preview picture was requested as soon as its bubble rendered, so a long thread with many link cards downloaded the pictures of cards scrolled far out of view. The picture now loads lazily and decodes off the main thread.
|
|
134
|
+
|
|
135
|
+
- **An `inline` `PlColorPicker` inside a disabled `<fieldset>` is disabled.** Its square and rails are not form controls, so the fieldset did not reach them: they stayed tab stops and still took keys and the pointer. They now behave as with `disabled`, the label is muted, and the picker no longer reports its value to a `PlForm`. A `PlFieldset` with `disabled` does the same, and turning the fieldset back on brings the picker back.
|
|
136
|
+
|
|
137
|
+
- **A `PlColorPicker` swatch the picker cannot read is left out.** A swatch such as `'red'` was drawn as a button that did nothing when pressed, since named colours are not read, and every swatch was painted from its string, so a bare `'ff0000'`, which the picker reads as red, was an empty circle. Such a swatch is now not drawn, and the rest are painted with the colour a press chooses.
|
|
138
|
+
|
|
139
|
+
- **The two select-all ticks of a `PlTransfer` have different names.** Both were read as "Select all", so a screen reader could not tell which list a tick belonged to. Each is now named by `selectAllLabel` and then by its list's heading, "Select all Available", read from the heading itself, so a heading given as an element is read as well.
|
|
140
|
+
|
|
141
|
+
- **A `vertical` `PlScrollArea` no longer scrolls sideways.** The viewport scrolled both ways, so a `<pre>` or a long URL wider than the box could be scrolled horizontally with no scrollbar to show it. An area now scrolls only along the axes `orientation` names, as in the Flutter build, and content wider than a `vertical` area is cut off at the edge. Let such content wrap, or use `orientation="both"`.
|
|
142
|
+
|
|
143
|
+
- **A collapsed `PlSidebar` opens from the side its `PlassProvider` `direction` says.** The drawer read only the document's direction, so a `start` sidebar in a right-to-left subtree of a left-to-right page slid in from the left. It now follows the `direction` of the nearest provider, and the document's direction when no provider names one, as the Flutter build follows `Directionality`.
|
|
144
|
+
|
|
145
|
+
- **A `PlCarousel` with its own `onPointerEnter` or `onFocus` still pauses `autoPlay` under the pointer and with the focus inside.** The caller's `onPointerEnter`, `onPointerLeave`, `onFocus` and `onBlur` replaced the carousel's own instead of running beside them. Passing one of them let the slides keep turning while the reader pointed at them or had tabbed into them, or kept a paused carousel from starting again. The carousel's handlers and the caller's now both run.
|
|
146
|
+
|
|
147
|
+
- **A pressed `PlToggle` keeps its fill in forced-colours mode under the pointer.** In a Windows contrast theme, a pressed `glass` or `ghost` toggle is filled with the system's highlight colour, but pointing at it or holding it down repainted it in the page's colour, so it looked released for as long as the pointer was on it, with its label still in the colour meant for the highlight. It now stays filled. A `solid` toggle was not affected.
|
|
148
|
+
|
|
149
|
+
- **`PlImage` in Firefox no longer reports `loaded` for a picture that has not arrived after its `src` changes.** When the previous picture was already in the browser cache, Firefox still fired that picture's `load` after the `src` had changed, and `onStatusChange` took it for the new one. A caller heard `loaded` before the new picture arrived, or after its `error`, and a picture that failed showed no fallback. That late `load` is now ignored.
|
|
150
|
+
|
|
151
|
+
- **→ on an open `PlTree` branch with nothing to step into keeps the focus where it is.** A branch with `children: []`, or whose children are all `disabled`, handed the focus to the next row, which is a sibling. The focus now moves only into a child, as the ARIA tree pattern says.
|
|
152
|
+
|
|
153
|
+
- **`PlProgressLinear`, `PlProgressCircular` and `PlProgressBox` write their value in the provider's `locale`.** Unlike `PlMeter`, they did not pass on the `locale` of the nearest `PlassProvider`, so a `format` followed the browser's language and the percentage shown without one was always written `75%`. Both now follow the provider, so `locale="de-DE"` writes `75 %` on screen and in `aria-valuetext`. Without a provider, the percentage follows the browser's language, as a `format` already did.
|
|
154
|
+
|
|
155
|
+
- **A `PlSkeleton` with a `label` says it to a screen reader.** The label was only the name of its `role="status"`, and a screen reader reads out the text inside a live region, not its name. The status was also `aria-busy`, which tells a screen reader to wait until the region is no longer busy, and a skeleton is removed instead. The label is now visually hidden text inside the status, and the status is no longer `aria-busy`. Put `aria-busy` on the region being loaded if it needs one.
|
|
156
|
+
|
|
157
|
+
- **Adding a step at the start of a `PlTimeline` keeps the steps already there.** Each step was keyed by its position, so a new first event in an activity feed remounted every step after it and threw away their state. A step is now keyed by its own `key`.
|
|
158
|
+
|
|
159
|
+
- **`PlStat` writes `change` to one decimal at most.** The number was printed as it came, so `change={0.1 + 0.2}` read `+0.30000000000000004%`. It is now rounded to one decimal, half away from zero, as the Flutter build writes it. A change that rounds to 0 is drawn as `0%` in the muted colour with no arrow.
|
|
160
|
+
|
|
161
|
+
- **Pressing the × on an uncontrolled `inline` `PlDrawer` closes it.** An inline drawer read only `open` and `defaultOpen`, so without `open` the × called `onOpenChange` and the panel stayed. It now keeps its own open state, and a controlled `open` still decides.
|
|
162
|
+
|
|
163
|
+
- **`PlTour` cuts its light round an SVG target.** Only an HTML element was measured, so a step pointed at a chart bar or any other SVG element dimmed the whole page, the target included, and the target could not be pressed. Any element is measured now.
|
|
164
|
+
|
|
165
|
+
- **`PlTour`, `PlBackTop` and `PlScrollZone` jump to where they are going when the reader has asked for less motion, whatever the page's CSS says.** `PlTour` always scrolled each target into view smoothly, so under `prefers-reduced-motion` the page still slid past on every step, and `PlBackTop` and `PlScrollZone` scrolled with `behavior: 'auto'`, which follows `scroll-behavior`, so on a page with `html { scroll-behavior: smooth }` they still slid. All three now scroll with `'instant'`.
|
|
166
|
+
|
|
167
|
+
- **The lowest bar of a `PlSparkline` bar strip above zero draws inside its box.** A value at the foot of the scale is drawn as a bar one pixel tall, and when every value is above zero the foot is the bottom edge, so that pixel sat just below the box. It now grows up from the bottom edge, and strips with values below zero draw as before.
|
|
168
|
+
|
|
169
|
+
- **A `PlTimelineChart` span whose start or end is not a time has no row in the table.** Such a span is not drawn, but the hidden table gave it a row with an empty start and end, which a screen reader read out, and a label only that span carried added a label column. It is now left out of the table, as it is left out of the marks.
|
|
170
|
+
|
|
171
|
+
- **A `PlTransfer` row that leaves `items` and comes back is no longer still ticked.** The ticks were kept for the life of the component, so a value that left and came back returned ticked, with its arrow pressable. The ticks of values that leave `items` are now dropped, as in the Flutter build.
|
|
172
|
+
|
|
173
|
+
- **`registerLanguage` also replaces a name the library reads as an alias.** A name such as `vue`, `html` or `svelte` was turned into the language it stands for before the registered grammars were checked, so after `registerLanguage('vue', vue)` a `language="vue"` block was still coloured as XML. A registered name is now looked up first, as the documentation already said, and the bar shows that name.
|
|
174
|
+
|
|
175
|
+
- **A `PlCodeBlock` whose `title` is an element names its code region after that title.** Only a string could become the region's `aria-label`, so `title={<code>src/index.ts</code>}` left a focus stop with no name. The region now takes its name from the title the bar draws, through `aria-labelledby`, and a string title keeps the name it had. With `toolbar={false}`, a string title still names the region and any other title falls back to the language, then `codeLabel`.
|
|
176
|
+
|
|
177
|
+
- **A `PlCodeBlock` `highlightLines` range stops at the last line of the block.** A range was walked from end to end whatever the block held, so a typo such as `'1-100000000'` built a hundred million line numbers and froze the tab. Only the lines the block has are walked now, so a range that runs past the end marks up to the last line.
|
|
178
|
+
|
|
179
|
+
- **A bar `PlSparkline` whose values are all below zero draws inside its box.** The bars grew from zero, which for such a strip lies above its top, so they covered the content above it. They now hang from the top of the strip, and strips with positive or mixed values draw as before.
|
|
180
|
+
|
|
181
|
+
- **A `PlTimelineChart` span that lies wholly outside a fixed `min` and `max` can no longer be reached.** It was not drawn but stayed a mark, so the arrow keys stopped on nothing and pointing beside the plot showed its tooltip outside it. It is now left out of the marks and the table, and a span that crosses an edge answers only on the part that is drawn. The tooltip and the table still give that span's real start and end.
|
|
182
|
+
|
|
183
|
+
- **A bar chart with `valueLabels="last"` labels the last value it has.** The label went on the last slot, so a series that ends in a gap, such as `[10, 20, null]`, had no label at all. It now goes on the 20, as it already did on a line chart, and `extremes` no longer walks the whole series again for every bar on each render.
|
|
184
|
+
|
|
185
|
+
- **The × on a `PlChip`, on a `PlCombobox` chip and on a picker trigger can be pressed anywhere in a 24px square around it.** It could be pressed only on the glyph, about 15px across and two pixels from the label or the trigger, which is under the minimum target size of WCAG 2.5.8 and easy to mistake for the trigger on a touch screen. The × is drawn at the same size and the chip and the trigger keep theirs; where the square reaches over the label or the trigger, a press inside the square goes to the × and the rest still goes to its neighbour. This covers `PlDatePicker`, `PlDateRangePicker`, `PlDateTimePicker`, `PlTimePicker`, `PlColorPicker` and `PlTreeSelect`.
|
|
186
|
+
|
|
187
|
+
- **A `PlHeatmapChart` thins its column names by one stride.** Each name set its own stride from its own width, so the short names either side of a long one were written over it. The stride is now taken once, from the widest name, so every name that is written clears its neighbours.
|
|
188
|
+
|
|
189
|
+
- **↑ and ↓ move between rows in a `PlHeatmapChart` grid.** Both did what → and ← do, so reaching the cell directly below in a 7×24 grid took 24 presses. On a grid they now move to the same column in the row below or above, stepping over a gap. ← and → and every key on a treemap are unchanged.
|
|
190
|
+
|
|
191
|
+
- **A tiled `PlImage` watermark draws in a token colour or `currentColor`.** The colour was written into the SVG image the tile repeats, and an image cannot read the page's custom properties or its `color`, so `var(--plass-…)` and `currentColor` came out black. The tile is now a mask over the layer's own background colour, so any CSS colour works, as it already did for a mark in a corner.
|
|
192
|
+
|
|
193
|
+
- **A tiled `PlImage` watermark covers every corner of a wide or tall picture.** The turned layer was a fixed share larger than each side, so from about 16:9 two opposite corners were left as empty triangles. It is now sized from the diagonal of the picture, which no turn can uncover.
|
|
194
|
+
|
|
195
|
+
- **A pressable `PlGallery` tile is described by the caption it draws.** The button's name is the picture's `alt` and its place in the set, and that name kept the `title` and `description` drawn inside the button from being read. The lines a tile draws now describe it through `aria-describedby`, a `hover` caption included before the pointer has shown it. The name is unchanged.
|
|
196
|
+
|
|
197
|
+
- **A `PlChatBubble` link card is a link only when `preview.url` is an address the bubble will follow.** The URL was written straight into the `href`, and a message's link preview is usually built from a URL somebody else wrote. `http`, `https`, `mailto` and relative addresses are unchanged; given anything else the same card is drawn with nothing to click, so the title, the site and the picture are still read and nothing announces a destination that is not one.
|
|
198
|
+
|
|
199
|
+
- **A chart legend asked for on the left or the right stays on that side under RTL.** `PlassSide` is a physical direction, but the legend was placed by the order of a flex row, which follows the writing direction, so `side: 'left'` put the legend on the right of an Arabic or Hebrew page. The row is now laid out physically, and the words inside the legend still read in the page's own direction.
|
|
200
|
+
|
|
201
|
+
- **Pointing at the legend entry of a series that is switched off no longer fades the whole chart.** The bar, scatter and pie marks checked only whether the hovered entry was some other series, so hovering a hidden one dimmed every series that was actually drawn, to highlight one that was not there. All of them now ask the one condition the line chart already used.
|
|
202
|
+
|
|
203
|
+
- **A `PlTooltip` wedge is the same sheet as the plate it points from.** It was drawn as two stacked triangles, a hairline one with the fill over it a pixel down, and both of those colours are translucent — so the wedge came out lighter than the plate, most visibly in the dark theme. It is now filled once with the sheet's own colour and the hairline is stroked along its two slanted sides, which is what `PlPopover` and `PlHoverCard` already drew. All three now draw the one arrow.
|
|
204
|
+
|
|
205
|
+
- **`PlCodeBlock` with `theme="auto"` follows a page that forces light with the `.light` class.** The block's own rule named only `[data-theme='light']`, so on a page whose system prefers dark and which had forced itself light with the class, the code was the one dark rectangle on a white page. Both ways of forcing a theme now count, as they already did for the tokens.
|
|
206
|
+
|
|
207
|
+
- **A macOS traffic light in `PlWindowPane` shows its mark while it holds the keyboard focus.** The mark appeared under the pointer only, so a reader who tabbed to one got a focus ring around a blank coloured dot with nothing to say which of the three it was on. The pointer still lights the whole set, and the keyboard lights the one it has reached, as the Flutter build does.
|
|
208
|
+
|
|
209
|
+
- **A `PlAnimateSplit` part wraps inside its own box.** Each part was `white-space: pre`, so a line with no space in it — a Chinese or Japanese sentence, which `by="word"` has nothing to cut at — was one part that ran out of its box instead of wrapping. A part now starts on a new line when it does not fit and wraps between its characters, as the `Text` of a part does in the Flutter build, and the line still enters as one part.
|
|
210
|
+
|
|
211
|
+
- **`PlAnimateSplit` and `PlAnimateScramble` cut text by grapheme.** Both cut by code point, so a flag or a family emoji came apart into pieces that draw as broken glyphs, a letter and its combining accent became two parts, and the scramble drew half of a surrogate pair as noise. Both now cut with the `Intl.Segmenter` `PlAnimateTyping` already used, so the two packages produce the same parts. A browser without `Intl.Segmenter`, which is Firefox below 125, still cuts by code point.
|
|
212
|
+
|
|
213
|
+
- **`PlAnimateCounter` and `PlAnimateScramble` hold their `delay` as well when `paused`.** A count held while it was still waiting started the whole `delay` again when it was let go, so a counter paused 100ms into a one-second wait took another full second to begin. What is left of the wait is now what is waited out.
|
|
214
|
+
|
|
215
|
+
- **A `trigger="hover"` effect plays once for a visit from the keyboard.** Focus events bubble, so tabbing between two links inside the wrapper started the effect again at each one and an entrance replayed itself under the reader's hands. A focus arriving from, or leaving for, something the wrapper already holds is now ignored, so the effect runs for as long as the focus is inside it, as the Flutter build does. The pointer is unchanged, and a focus arriving from outside still starts it.
|
|
216
|
+
|
|
217
|
+
- **A `trigger="visible"` effect starts on where the element will land rather than on where its first frame is holding it.** The observer watched the element after its start state had already moved it, so a `PlAnimateSlide` inside the `overflow: hidden` mask its own page recommends, and a turned `PlAnimateRotate` in a box of its own size, reported as off the screen for ever and never played. How much of the element is on screen is now measured on its resting box, with a second observer on the parent saying when the view has moved, so `threshold` is a share of the element wherever it sits, which is what it already means in the Flutter build. A slide with nothing clipping it starts a little earlier than before.
|
|
218
|
+
|
|
219
|
+
- **`PlAnimateTyping` holds the box of the whole string from the first frame, so nothing around it moves as it types.** The drawn copy held only the characters that had arrived, so a phrase that wrapped pushed the content below it down when its second line began, and the server HTML held an empty box. The characters still to come are now laid out after the caret without being drawn, as generated content that is not selected or copied, and the caret takes no room of its own while it moves. Every frame is laid out as the finished line, in the server HTML too.
|
|
220
|
+
|
|
221
|
+
- **`PlAnimateTyping` types the text inside elements among its children.** `Ship <strong>faster</strong>` was typed and read out as `Ship `, because the text of any element was dropped, while the docs said an element contributes its text. That text is now typed and read, and the markup is still left out, so the line is `Ship faster` without the bold.
|
|
222
|
+
|
|
223
|
+
- **`PlAnimateCounter` and `PlAnimateScramble` go on from where they were held when `paused` is released.** Resuming took the start time again, so a count held at 400 dropped back to `from` and a half-settled line scrambled again from its first character. A count also started over whenever a parent rendered it with an inline `easing`, which is a new function each time. Both now work the start time back from how far they had got and read `easing` from the latest render without restarting, as the Flutter build does. A hover, a new `play` or a new `value` still starts from the beginning.
|
|
224
|
+
|
|
225
|
+
- **`PlAnimateSplit` with `by="character"` wraps a line between words.** Every character was its own inline-block, and a line may break before and after each one, so in a narrow box a word wrapped partway through, such as "Internationali / zation". The characters of each word now sit together in one inline-block, which moves to the next line whole and wraps inside only when the word is wider than the line. Chinese, Japanese, Thai and the other scripts written without spaces still wrap between their characters.
|
|
226
|
+
|
|
227
|
+
- **`PlAnimateHeadline` keeps turning inside a parent that renders often.** The timer was restarted whenever the function that turns the reel changed, and that function changed with an inline `onIndexChange`, so inside a parent that rendered every second the 2600ms interval was reset before it ever fired and the line never changed. The timer now restarts only when the line, the count, `loop`, `interval`, `delay` or the running state changes.
|
|
228
|
+
|
|
229
|
+
- **`PlAnimateSplit` parts move with `effect="slide"`, and `effect="zoom"` is no longer the same as `grow`.** The parts were given no start position or scale, so they ran on the keyframe fallbacks: a slide only faded in place, and a zoom started from 0.8, the `grow` scale. Each part now starts where the component of the same name starts by default, so a slide rises from its own height below and a zoom grows from 0.4.
|
|
230
|
+
|
|
231
|
+
- **Under `prefers-reduced-motion`, the `PlAnimateMarquee` items past the edge of the box can be reached.** The strip stopped, but the box still clipped it, so with ten headlines in a 400px box only the first one or two could ever be seen. The box now draws only the first copy and scrolls along it, and it is a tab stop while there is anything to scroll, so the arrow keys move it. A vertical marquee scrolls down its own axis. Where the platform draws a permanent scrollbar, the box is taller by the bar's thickness.
|
|
232
|
+
|
|
233
|
+
- **Tab passes over the copies of a `PlAnimateMarquee` after the first.** The copies carried `aria-hidden`, which takes them off the accessibility tree but not off the tab order, so a strip of ten links was thirty Tab stops, twenty of them on a link with no name. The copies after the first are now `inert` as well, and only the first copy's links take the focus.
|
|
234
|
+
|
|
235
|
+
- **`PlRating` writes a fractional score the way the pack's language writes a number.** The packs put the raw number into the sentence, so a half star read `2.5 von 5` in German, French and Spanish, which write a decimal with a comma. Each pack now formats the score with `Intl` in its own language, `2,5 von 5`, and a whole score still reads `3`. A `ratingValue` you write yourself still receives the plain number.
|
|
236
|
+
|
|
237
|
+
- **An empty `PlTransfer` heading falls back to the label pack.** A `sourceLabel` or `targetLabel` of `''` drew the English `Available` or `Selected` whatever pack the page used. It now draws the pack's `transferAvailable` or `transferSelected`, as a heading left out already did.
|
|
238
|
+
|
|
239
|
+
- **A `PlChip` delete button is named in the word order of the label pack.** The name was the pack's `remove` followed by the chip's text, English's order in every language, so a Korean page read `삭제 Design`. It now comes from the pack's `removeItem`, `Design 삭제` in Korean, and `deleteLabel` still replaces the whole name. Words that a component of their own draws cannot be read from `children`, and still follow `remove`.
|
|
240
|
+
|
|
241
|
+
- **`PlGallery` says where a picture sits in the language of the label pack.** A tile's name and the viewer's counter were built from an English template inside the component, so a Korean page read `A harbour — 2 of 4` inside a list named `갤러리`. They now come from the new `galleryItem` label key, translated in all seven packs in each language's own word order, and `itemLabel` still wins.
|
|
242
|
+
|
|
243
|
+
- **Pressing a `PlPagination` page or stepper keeps the focus on it.** With `getPageHref`, the page that became current turned from a link into a button, a new element, and the focus fell to the top of the page; on the last page, Next became `disabled` and dropped the focus the same way, with or without `getPageHref`. The current page now stays a link marked `aria-current`, and a stepper at the end of the row stays in the tab order, announced as unavailable: an `<a>` with no address and `aria-disabled` in a link row, a button with `aria-disabled` otherwise. A `disabled` row still takes every control out of the tab order.
|
|
244
|
+
|
|
245
|
+
- **Each `PlCarousel` dot is a 24px press target.** A dot was a button the size of the dot it drew, 4 to 8 pixels across, which falls short of WCAG 2.5.8 and made jumping to a slide on a phone hard, above all with `arrows={false}`. The button is now 24px on each side with the dot drawn in its middle, and the targets sit side by side, so the dots are spaced further apart.
|
|
246
|
+
|
|
247
|
+
- **`PlCarousel` `autoPlay` keeps advancing inside a parent that renders often.** The interval was restarted whenever the function the component moves with changed, and that function changed with an inline `onValueChange`, so inside a parent that rendered every second a five-second interval was reset before it ever fired and the carousel stood still. The interval now restarts only when the slide, the count, the pause or the interval itself changes.
|
|
248
|
+
|
|
249
|
+
- **Pressing a `PlTransfer` arrow keeps the focus and says what moved.** The pressed arrow was disabled by the move, so the focus fell to the page and nothing said how many rows had gone across. The focus now goes to the first row that arrived, or to the target list when a controlled owner refused the rows, and a polite live region announces the count from the new `transferMoved` label key. The new `movedLabel` prop changes the sentence for one pair.
|
|
250
|
+
|
|
251
|
+
- **`PlOtpField` names every slot by its place in the row.** Every slot was named by the field label alone, so a screen reader read "Verification code" six times and never said which box the caret was in. Each slot is now named by the label and then its position, "Verification code Character 2 of 6", from the new `otpSlot` label key, and the new `slotLabel` prop changes the wording for one field.
|
|
252
|
+
|
|
253
|
+
- **Accessibility sentences with a value in them are read in the language of the label pack.** `PlPagination`'s page names and live line, `PlRating`'s scores, `PlCarousel`'s slide names and role descriptions, and the remove and add rows of `PlFilePicker` and `PlCombobox` were English templates inside each component, so a Korean page read `Page 5` inside a landmark called `페이지 이동`. They now come from new `PlassLabels` function keys (`paginationPage`, `paginationStatus`, `ratingValue`, `carouselSlide`, `removeItem`, `addCustom`) and the strings `ratingNone` and `slide`, translated in all seven packs in each language's own word order. A component's own prop still wins.
|
|
254
|
+
|
|
255
|
+
- **Moving the focus in `PlTree` redraws only the rows it moves between.** Every step of the focus rendered every row the tree had loaded again, and the rows inside closed branches were built on every render and thrown away, so holding an arrow key down in a tree of 10,000 nodes stuttered. Rows are now memoised: a step redraws the row the focus left, the row it reached and the branches they sit in, and a closed branch builds its rows only while it is open or closing.
|
|
256
|
+
|
|
257
|
+
- **`PlGallery` masonry tiles are read and tabbed in the order they were given, and keep their pictures when the lane count changes.** Each lane was a list of its own, so with three columns and twelve pictures Tab went 1, 4, 7, 10, 2, a screen reader read one lane after another, and the "4 of 12" in each tile's name came out of order. A tile that moved to another lane when the column count changed, at a breakpoint or right after server rendering, was mounted again and its `PlImage` lost its state. The tiles are now one list in the order they were given, placed into their lanes with CSS grid rows, so a tile that changes lane stays the same element. A picture that runs past the end of a tile in another lane is drawn taller by the gap there, and by that tile's caption with `caption="below"`.
|
|
258
|
+
|
|
259
|
+
- **Pressing Reveal or Hide on a `PlSpoiler`, or close on a `PlWindowPane`, from the keyboard no longer drops the focus to the top of the page.** The pressed button went `inert` while it still held the focus, so the browser moved the focus to the document body and the next Tab started the page over. Revealing now moves the focus to the uncovered content, and hiding moves it back to the Reveal button (or the first control of a custom `action`). Closing a window hands the focus back to the element it came in from, or, when that is gone, to the next focusable element after the window or the last one before it. The focus is left alone when it was elsewhere on the page.
|
|
260
|
+
|
|
261
|
+
- **`PlAnimateCounter`, `PlAnimateScramble` and `PlAnimateTyping` play again on every hover.** With `trigger="hover"` they ran on the first hover only, because a later hover changed nothing their loops watched, while every keyframe effect restarted each time. Each hover now starts the count, the scramble or the typing over, as the Flutter build does.
|
|
262
|
+
|
|
263
|
+
- **Restarting a `PlAnimate*` no longer replays another one nested inside it.** A restart rewound every animated element under its root, so an error message in a `PlAnimateFade` inside <code v-pre><PlAnimateShake replay={attempts}></code> faded in again on every shake. A restart now rewinds only the root and the parts that animation drew itself: its staggered children, the parts of a `PlAnimateSplit` and the tracks of a `PlAnimateMarquee`.
|
|
264
|
+
|
|
265
|
+
- **A caller's `onPointerEnter` and `onFocus` reach every `PlAnimate*` with `trigger="hover"`, and no longer stop the effect from starting.** Twelve of the components (Appear, Blink, Fade, Float, Grow, Lighting, Reveal, Rotate, Shake, Slide, Split and Zoom) laid their hover handlers over the caller's, so `onPointerEnter={prefetch}` was never called. The other five (Counter, Headline, Marquee, Scramble and Typing) did the opposite, so a caller's `onFocus` replaced the handler that starts the effect on focus, and a keyboard reader never saw it play. The two sets of handlers are now merged, and both run.
|
|
266
|
+
|
|
267
|
+
- **A `fullWidth` `PlDatePicker`, `PlTimePicker`, `PlDateTimePicker`, `PlColorPicker` or `PlTreeSelect` no longer renders its width samples.** The picker trigger held itself open at the widest value it could show by rendering every sample, hidden, even when `fullWidth` made its width its container's. A `fullWidth` trigger now renders only what it shows, as `PlSelect` and `PlDateRangePicker` do. A trigger without `fullWidth` keeps its samples.
|
|
268
|
+
|
|
269
|
+
- **A `fullWidth` `PlDateRangePicker` no longer renders every date sample in its trigger.** Each half held itself open at the widest date it could show by rendering the samples, hidden, even when `fullWidth` made the trigger's width its container's. A `fullWidth` trigger now renders only what it shows. A trigger without `fullWidth` keeps its samples, which stop it changing width as the range is filled in.
|
|
270
|
+
|
|
271
|
+
- **The `PlPill` button says whether its `details` are open.** With `details` and an `onClick`, the middle button had no `aria-expanded` and pointed at no panel, and since the other props land on the shell, a caller could not add either. The button now takes `aria-expanded` from `expanded` and points at the panel with `aria-controls`. A pill without `details` claims neither.
|
|
272
|
+
|
|
273
|
+
- **A `PlTabPanel` inside a Fragment is sorted out of the tab bar.** Children were sorted by looking only at the top level, so `items.map((item) => <><PlTab /><PlTabPanel /></>)` put every panel inside the `role="tablist"` strip, clipped under the tabs. Fragments are now opened on the way. A panel wrapped in a component of the caller's own still cannot be told apart from a tab, which the tabs page now says.
|
|
274
|
+
|
|
275
|
+
- **`PlHowToSteps` stays a list for Safari and VoiceOver.** The `<ol>` has its markers removed, which makes WebKit drop its list semantics, and the number discs are hidden from screen readers, so the order of the steps was not announced there. The list now carries `role="list"`, as `PlStepper` already did.
|
|
276
|
+
|
|
277
|
+
- **A `PlNavigationMenuItem` link can mark the page the reader is on.** There was no `active` and no way to pass `aria-current` through, so a top-level link could not say it was the current page. `active` now marks a link item `aria-current="page"` and draws its word in the accent.
|
|
278
|
+
|
|
279
|
+
- **A `PlMenuSubmenu` opens towards the end of the line under RTL, and its chevron turns with it.** The submenu opened against the physical right edge whatever the direction, while under RTL the arrow key that opens it is the left one, and the chevron kept pointing right. Left out, `side` is now the inline end, the left under RTL, and the chevron follows. A `side` given explicitly is still a physical edge.
|
|
280
|
+
|
|
281
|
+
- **An unavailable `PlMenuItem` with an `href` no longer navigates.** The link row took no `disabled`, so `<PlMenuItem href="/admin" disabled>` was drawn as available and followed its link when pressed. A disabled row with an `href` is now the plain row it has become: dimmed, announced as unavailable, and going nowhere.
|
|
282
|
+
|
|
283
|
+
- **The `PlFloatingBottomNavigation` key disappears when `value` names no destination.** The key stayed under the last destination, which no longer carried `aria-current`, so the bar drew a destination as current that nothing said was. It is now hidden, and appears in place when a destination is current again, as in the Flutter build.
|
|
284
|
+
|
|
285
|
+
- **Pressing `PlBackTop` moves the focus to the top of what it scrolled.** The button hides itself as the page scrolls up, and it kept the focus while hidden, so the next Tab went to the end of the page and scrolled it back down. When the button holds the focus, a press now hands it to the first focusable element at the top of the target, or lets go of it when there is none.
|
|
286
|
+
|
|
287
|
+
- **An unfinished mouse drag on a `PlScrollZone` ends when the button comes up outside the strip.** Until a press had moved far enough to become a drag, nothing was captured, so a button let go outside the strip left the drag listening: the strip then followed the pointer on hover alone, text could not be selected and the next click was swallowed. The drag now ends on the first move with no button held.
|
|
288
|
+
|
|
289
|
+
- **`PlCommandPalette` opens with an empty field however it was closed.** The search text was cleared only when Base UI closed the sheet, on Escape or a press outside, so running a command or setting a controlled `open` to `false` left the last search in the field for the next time it opened. It is now cleared whenever the palette goes from open to closed.
|
|
290
|
+
|
|
291
|
+
- **A `PlScrollZone` strip is a tab stop only while it overflows.** The scroller always took `tabIndex={0}`, so a strip that fits, or one whose chips are already focusable, added a stop with nothing to scroll, and without a `label` that stop had no name. It is now focusable only while there is somewhere to scroll, as `PlScrollArea` already was.
|
|
292
|
+
|
|
293
|
+
- **`PlPanes` handles can be named, and say which pane they resize.** A split with two handles read as "separator, 50" twice, with no way to tell them apart, while the Flutter build already took a `label`. `PlPanes` takes a `label` for its handles, and each handle points at the pane before it with `aria-controls`, using the pane's own `id` when it has one.
|
|
294
|
+
|
|
295
|
+
- **`PlContainer` keeps its `maxWidth` when it is given a `style`.** The caller's `style` replaced the one that carries the measure, so a `maxWidth="md"` container given <code v-pre>style={{ paddingBlock: 24 }}</code> had no width limit at all. The two are now merged, with the caller's declarations laid over the measure.
|
|
296
|
+
|
|
297
|
+
- **A `PlHeader` or `PlFooter` inside the page no longer takes over the `PlPageLayout` bar.** Every `PlHeader` and `PlFooter` under a layout registered itself as the layout's bar, and the last one won, so an article's own header inside `<main>` was measured instead of the site's and the sticky sidebars started below the wrong height. When that header went away, the layout measured nothing and the offset fell to 0. Only the bars in the `header` and `footer` slots are measured now.
|
|
298
|
+
|
|
299
|
+
- **The `PlSidebar` resize handle says the width it holds.** The handle is a focusable separator, and one of those is a value between two bounds, but it had no `aria-valuenow`, `aria-valuemin` or `aria-valuemax`, so moving it with the arrow keys told a screen reader nothing. It now carries the width in pixels between `minWidth` and `maxWidth`, and a drag or a key press updates it as the edge moves.
|
|
300
|
+
|
|
301
|
+
- **Each `PlSlider` thumb can have its own name and spoken value, and is described by `description`.** The two thumbs of a range were both named by `label`, their values were read in Base UI's own English wording such as "start range" whatever the page's language, and `description` was drawn under the track without being connected to either. `getAriaLabel` and `getAriaValueText` are passed to every thumb, and `description` now describes each one.
|
|
302
|
+
|
|
303
|
+
- **The `PlTransfer` arrows point at their own lists under RTL.** The selected list is at the inline end, which is the left under a right-to-left direction, but the arrow to it was always drawn pointing right and the arrow back pointing left. Both now turn over with the direction.
|
|
304
|
+
|
|
305
|
+
- **A `fullWidth` `PlSelect` no longer renders every option label in its trigger.** The trigger held itself open at the width of its longest label by rendering all of them, hidden, even when `fullWidth` made its width the container's. A list of 250 countries with a flag in each label requested 250 images before the list was ever opened. A `fullWidth` trigger now renders only what it shows. A trigger without `fullWidth` still renders every label, because that is what keeps it from changing width with its value.
|
|
306
|
+
|
|
307
|
+
- **The options of a disabled `PlRadioGroup` look disabled.** A group's `disabled` stopped its options answering, but each option chose its look from its own `disabled` alone, so the options of a disabled group were drawn at full strength with a pointer cursor and a hover. They now dim, take the not-allowed cursor and lose the hover, as an option disabled on its own does.
|
|
308
|
+
|
|
309
|
+
- **An `inline` `PlColorPicker` is grouped under its `label`.** The square and the rails are named "Saturation and brightness", "Hue" and "Opacity" whatever field they belong to, and the label, the description and the error were not connected to them, so two inline pickers on one page were two sets of the same sliders and an error was never announced. The panel is now a `role="group"` named by `label` and described by `description` and `error`, and an error marks the square and the rails `aria-invalid`.
|
|
310
|
+
|
|
311
|
+
- **The hue and opacity rails of `PlColorPicker` answer ↑, ↓, Home and End.** A rail took only ← and →, so a keyboard reader who pressed the other keys every slider answers to changed nothing. ↑ and ↓ now move a rail as → and ← do, and Home and End take it to its two ends.
|
|
312
|
+
|
|
313
|
+
- **A `PlFilePicker` button says which field it belongs to.** Its name was only the words inside the box, so a résumé picker and a cover letter picker on one screen were both read out as the same line. The name now starts with `label` and goes on with the box's own words.
|
|
314
|
+
|
|
315
|
+
- **The month and year buttons of a calendar say the month and the year.** Their `aria-label` was "Choose a month" and "Choose a year", which replaced the words on the buttons, so a screen reader never said which month was on screen and a voice command saying "July" found nothing. The buttons in `PlCalendar` and every picker are now named by what they show and described by what they do, and the grid under them, which had no name, is named by the header: `July 2026`.
|
|
316
|
+
|
|
317
|
+
- **The next question in a `PlConfirmProvider` queue places the focus again.** The sheet stays open between two queued questions and reused its buttons, so `autoFocus` did not run for the second one and the focus stayed on the button that had just been pressed. After a first question answered with its confirm button, a second, destructive question opened with the focus on its confirm button too, and pressing Enter twice approved both. Each question now gets new buttons, and its own `initialFocus` decides where the focus lands.
|
|
318
|
+
|
|
319
|
+
- **`PlTour` says the next step when the focus stays on Next.** Pressing Next left the focus on the button and changed the card around it, so a screen reader said nothing about the new step. The title and the content are now a polite live region that stays in place from step to step.
|
|
320
|
+
|
|
321
|
+
- **`PlTypography` clamps to the number of `lines` it is given, and cuts a `caption` or an `overline` short.** `lines` above 6 clamped to 6, because there was a class for each count up to six and nothing after. And `lines={1}` on a `caption` or an `overline` did nothing, since those levels are `<span>`s and an ellipsis needs a box. Any count now clamps to that many lines, and a one-line clamp is drawn as a block.
|
|
322
|
+
|
|
323
|
+
- **`PlImage` reports a new `src` that was already in the cache.** The status last reported was kept across a change of `src`, so a second picture that had already arrived settled as `loaded` without calling `onStatusChange`, and a caller that showed a spinner until it heard `loaded` kept showing it. A new `src` now starts over and reports its own arrival.
|
|
324
|
+
|
|
325
|
+
- **Every link that opens a new tab takes `noopener noreferrer`.** `PlTextLink` added them only for `newTab`, so a caller's own `target="_blank"` got neither them nor the "(opens in a new tab)" line a screen reader hears. `PlNavigationMenuLink` passed its `target` through with the `rel` untouched, and `PlMenuItem` took no `rel` at all. All three now merge the two tokens into the `rel` of any link whose `target` is not this tab, `PlTextLink` announces a `target="_blank"` link as it does a `newTab` one, and `PlMenuItem` takes a `rel`.
|
|
326
|
+
|
|
327
|
+
- **Each `PlChip` delete button is named after its chip.** Every delete button was named "Remove", so tabbing along a row of tags read the same word for each one and never said which tag it would remove. The name is now the label pack's word followed by the chip's text, such as "Remove design". A `deleteLabel` is still the whole name.
|
|
328
|
+
|
|
329
|
+
- **The `PlGallery` viewer keeps the focus when an arrow reaches the end of the set.** Pressing Next onto the last picture, or Previous onto the first, disabled the button that had the focus, which dropped the focus to the page, and the arrow keys stopped moving between pictures. The focus now crosses to the other arrow.
|
|
330
|
+
|
|
331
|
+
- **A `PlDataTable` sorted descending keeps its blank cells last.** The built-in order put a blank value after every other value, and the direction was then applied to that answer as well, so turning a column round put the blanks first, against what the page says. The direction now turns only the values that are there. A column's own `compare` is still turned round as a whole.
|
|
332
|
+
|
|
333
|
+
- **Opening a folded `PlBreadcrumb` keeps the keyboard focus in the trail.** The `…` button is removed when the hidden steps come back, so pressing it with the keyboard dropped the focus to the page and the next Tab started from the top. The focus now moves to the first step that came back and can take it.
|
|
334
|
+
|
|
335
|
+
- **A `PlCodeBlock` with a long run of spaces in its code no longer stalls a server render.** The trailing whitespace was cut with a regular expression that retries from every space in a run followed by more text, so 40,000 characters of a pasted log took almost three seconds in Node. It is now cut in one pass.
|
|
336
|
+
|
|
337
|
+
- **A `PlCodeBlock` handed new `code` or a new `language` stops drawing the last code.** The colours were kept without the code they were made from, so switching a block from a TypeScript file to a Python one drew the TypeScript lines under the new title until the Python grammar had loaded, while the copy button already copied the Python. The new code is now drawn plain until its own colours are ready.
|
|
338
|
+
|
|
339
|
+
- **A `PlTimelineChart` whose hours run over more than one day writes the date with each time.** An axis stepping in hours, minutes or seconds wrote only the time, so on a range of two days `09:00` could be either day, and the tooltip and the table wrote `09:00 – 17:00` with no day either. When the axis crosses midnight, every tick, the tooltip and the table now write the date in front of the time. A range inside one day is unchanged.
|
|
340
|
+
|
|
341
|
+
- **A treemap's hidden table names each tile under its own group.** `PlHeatmapChart` with `shape="treemap"` wrote its data table as a grid, with the first group's tile names as the column headings, so a screen reader heard the second group's tiles under the first group's names: "Tooling, Servers, 400". The table is now a group of rows per series, headed by the group's name, with a row per tile giving its own name and value.
|
|
342
|
+
|
|
343
|
+
- **A `PlScatterChart` whose `x` values are dates ticks its x axis like a calendar.** The axis ran on the dates' milliseconds with the same 1-2-5 steps a count gets, so it was labelled `1.7T`. It now takes the steps and the labels of a time axis, as `PlTimelineChart` does, and `xAxis.tickFormat` is handed each tick as a `Date` rather than as a number.
|
|
344
|
+
|
|
345
|
+
- **A chart stacked to `full` writes the caller's numbers in the caller's `format`.** `PlBarChart` and `PlAreaChart` keep the number each point was given for the tooltip and the table, and wrote it with `String()`, so a revenue chart with a currency `format` showed `4000` where every other chart showed `$4,000`. The numbers are now written the way the chart writes every other value.
|
|
346
|
+
|
|
347
|
+
- **`PlPieChart` and `PlGaugeChart` no longer call themselves empty on a server.** Both decided they had nothing to show from the radius they could draw, which comes from the measured width, and on a server or before the first measurement that width is zero. The server-rendered page said "Nothing here" until hydration, and the pie left its data table out, so a crawler saw neither the numbers nor the chart. The empty state now comes from the data alone, and the drawing waits for a width.
|
|
348
|
+
|
|
349
|
+
- **An axis pinned at one end stays there when every value is the same.** A flat series opens a band around itself so there is something to divide by, and the band was opened on both sides even past an end the caller had pinned, so <code v-pre>yAxis={{ min: 0 }}</code> over a row of zeros drew an axis from −1. Only the free end moves now: that chart runs from 0 to 1.
|
|
350
|
+
|
|
351
|
+
- **A chart of many categories does less work when its active column moves.** Every move of the pointer or the arrow keys to another column re-rendered `PlLineChart`, `PlBarChart` and `PlAreaChart` whole: every category label was written and measured again, the hidden data table compared a row for each category, and a group was created for each column even where it drew nothing. The labels are now written once per change to the data or to the room they have, the table is skipped when its data has not changed, and a column with nothing to draw adds nothing.
|
|
352
|
+
|
|
353
|
+
- **A finger can drag a `PlPanes` handle, a `PlSidebar` edge and a `PlWindowPane` title bar.** None of the three set `touch-action`, so on a touch screen the browser took the drag for a pan after a few pixels and ended it with `pointercancel`. They now take `touch-action: none` while they can be dragged, as `PlSlider` and the window's resize handles already did.
|
|
354
|
+
|
|
355
|
+
- **`usePlElementSize` and `usePlOnScreen` follow an element attached after the first render.** Both started watching in an effect keyed on the ref object, which never changes, so an element rendered later, as in `loading ? <Spinner /> : <div ref={box} />`, was never measured and never seen, and one swapped for another kept the old one watched. Both now read the ref after every render and start over on a different element, and `usePlOnScreen` does the same for `root`. `usePlElementSize` goes back to `null` when its element is removed.
|
|
356
|
+
|
|
357
|
+
- **`usePlColorScheme` no longer throws in a sandboxed frame.** The check for storage, `typeof localStorage`, sat outside the `try` that caught storage errors, and in a frame sandboxed without `allow-same-origin` that check is what throws, so the component using the hook took its whole tree down on first render. The check is now caught with the rest, and the choice simply does not survive a reload there, as the hook's page describes.
|
|
358
|
+
|
|
359
|
+
- **`PlColorSchemeScript` keeps its `storageKey` inside the script.** The key was written into the inline script with `JSON.stringify`, which leaves `<` as it is, so a key built from a value the page does not control, such as a tenant's slug, could close the `<script>` with `</script>` and put markup after it into a server-rendered page. `<`, `>`, `&`, U+2028 and U+2029 are now written as escapes, and the script reads the same key as before.
|
|
360
|
+
|
|
361
|
+
- **A pressable `PlPill`'s light stays on the pill and follows the pointer.** A pill in the flow was not positioned, so the two layers of its interaction light spread across the nearest positioned box around it, a card or the whole viewport, and the light sat in the middle because nothing told it where the pointer was. The pill now contains the light and moves it with the pointer, through the same handler `PlButton` and `PlToggle` use.
|
|
362
|
+
|
|
363
|
+
- **Controls keep their shape and their state in forced-colours mode.** Windows' contrast themes drop background images and box shadows and repaint backgrounds in the page's colour, so a `solid` button was left as its label with no edge, a `solid` field as a caret with no box, and every state the library draws as a fill disappeared: a checked `PlCheckbox`, `PlRadio` or `PlSwitch` looked unchecked, a pressed `PlToggle` unpressed, and the chosen `PlSegmentedButton` segment, the active `PlTabs` tab, the filled part of `PlSlider`, `PlProgressLinear`, `PlProgressBox` and `PlMeter`, the finished steps of `PlStepper`, `PlTimeline` and `PlHowToSteps`, the chosen day in `PlCalendar` and the pickers, every `PlHighlight` match, and the row the pointer or the arrow keys had reached in `PlSelect`, `PlCombobox`, `PlCommandPalette` and `PlMenu` were gone. In that mode those states are now painted in the system's own colours, `Highlight` behind `HighlightText` and `Mark` for a match, a `solid` button, field, chip, pill, badge, avatar, alert, toast, message bubble, app logo plate and key cap draws a border in a system colour, `GrayText` when disabled, and the thumbs of `PlSlider` and `PlScrollArea` are drawn in the text colour. Nothing changes outside forced-colours mode.
|
|
364
|
+
|
|
365
|
+
- **A shortcut answers the key it names, not only the character that key typed.** `usePlHotKeys` and a field's `hotKeys` compared `event.key` alone, so `Alt+K` never fired on a Mac, where Option+K types `˚`, `Mod+Shift+1` never fired because Shift turns the 1 into `!`, and `?` failed the Shift check it needs to be typed at all, while `PlHotKeys` drew those caps as if they worked. A letter or a digit is now also matched by its physical key when a modifier turned it into something else, and a symbol that takes Shift ignores Shift. On a layout that moves the letters, the printed letter still wins.
|
|
366
|
+
|
|
367
|
+
- **Shortcuts leave a key an input method is composing alone.** The Enter that commits a Korean syllable ran an `Enter` binding and, being consumed, broke the syllable, and the Escape that cancels a Japanese conversion closed a panel. Both hooks now skip a key while `isComposing` is set or the browser reports key code 229.
|
|
368
|
+
|
|
369
|
+
- **A nested `PlassProvider` changes only the words its `labels` name.** Its `labels` replaced the whole set from the provider around it, so a provider that renamed one button inside a Korean application put every other word back into English. The two sets are now merged per word, as the locales guide describes.
|
|
370
|
+
|
|
371
|
+
- **`PlConfirmProvider` and `PlTimelineChart` say the label pack's words.** The confirm dialog's Cancel and Confirm, and the start and end headings of a timeline's table, were written in English whatever `PlassProvider` said, while `PlPopconfirm` beside them already read the pack. Both now read `cancel`, `confirm`, `start` and `end`, and a component's own prop still wins.
|
|
372
|
+
|
|
373
|
+
- **The pickers, `PlCalendar` and `PlFilePicker` take part in a `PlForm`.** Base UI's form sees only the controls registered with a field, and these carried plain hidden inputs, so a `<PlDatePicker name="departure" required />` inside a `PlForm` was missing from the values `onSubmit` received, did not stop an empty submit, and never showed the form's `errors` entry for its name. They now register like any other field: an empty `required` one stops the submit and takes the focus, an `errors` entry is shown on it, and its value is in `onSubmit`. A `PlDateRangePicker`, a `multiple` `PlTreeSelect` and a `PlFilePicker` report arrays.
|
|
374
|
+
|
|
375
|
+
- **A `required` or `disabled` picker is respected by a plain `<form>`.** The hidden input under `PlDatePicker`, `PlDateRangePicker`, `PlDateTimePicker`, `PlTimePicker`, `PlColorPicker` and `PlTreeSelect` carried neither attribute, so an empty `required` picker was submitted and a `disabled` one still sent its value. An empty `required` picker now stops the submit, and a `disabled` picker, `PlCalendar` or inline `PlColorPicker` sends nothing. An empty single-choice `PlTreeSelect` now submits an empty entry under its name, as the other pickers do.
|
|
376
|
+
|
|
377
|
+
- **`PlCarousel` no longer scrolls the page when it changes slide.** It brought the slide into view with `scrollIntoView`, which moves every scrollable ancestor as well as the strip, so a carousel partly off screen pulled the page to itself on every slide, and on every tick while `autoPlay` ran. Only the strip scrolls now.
|
|
378
|
+
|
|
379
|
+
- **A responsive `PlFlex`, `PlGrid` or `PlGridItem` nested in another keeps its own values.** The per-breakpoint values are custom properties, and custom properties inherit, so a child took every breakpoint its parent named and it did not: a vertical `PlFlex` inside one that turns horizontal at `md` turned with it, and a `PlGridItem` with no `span` inside a cell of eight was eight columns wide. The slots are now registered as not inheriting. Browsers without `@property` support, Firefox before 128, keep the old behaviour.
|
|
380
|
+
|
|
381
|
+
- **A `PlFilePicker` in a form submits the files it lists.** The file input held only the last pick from the dialog, so a dropped file was never sent, a file removed from the list or turned away by `accept` or `maxSize` still was, and in `multiple` a second pick replaced the first. The input now holds the list as it is shown.
|
|
382
|
+
|
|
383
|
+
- **The time columns of `PlTimePicker` and `PlDateTimePicker` work from the keyboard.** Every row was its own tab stop and the arrow keys did nothing, so reaching Done took more than a hundred presses of Tab on a clock with seconds. Each column is now one tab stop, and <kbd>↑</kbd>, <kbd>↓</kbd>, <kbd>Home</kbd> and <kbd>End</kbd> choose a row and move the focus with it, stepping over a blocked row.
|
|
384
|
+
|
|
385
|
+
- **A labelled picker's trigger reads the value as well as the label.** `PlDatePicker`, `PlDateRangePicker`, `PlDateTimePicker`, `PlTimePicker`, `PlColorPicker` and `PlTreeSelect` named their trigger with the label alone, so a screen reader heard "Departure, button" and had to open the popup to find out which day was chosen. The trigger is now named by the label and then by the value it shows.
|
|
386
|
+
|
|
387
|
+
- **`PlTour`'s card follows the tour.** The card was given its target as a getter, which Base UI asks only when the popup opens, so from the second step on the cut-out moved and the card stayed beside the first target. A step with no target left the card invisible. The card now moves to each step's target, and a step with no target centres it on the viewport, as the documentation says.
|
|
388
|
+
|
|
389
|
+
- **`PlScatterChart` and `PlTimelineChart` no longer redraw on every pixel the pointer moves.** Both stored the pointer's offset for a tooltip mode that only a chart of columns reads, so each move re-laid the whole chart out and re-rendered every mark. They now re-render only when the nearest mark changes.
|
|
390
|
+
|
|
391
|
+
- **`plass-ui/css/<component>.css` also scans the components a component renders.** Each manifest named only the component's own folder, but Tailwind scans files, so a project that registered `icon-button.css` alone got none of the utilities spelled in `PlButton`, and the date pickers and charts lost the ones in `PlButton` and `PlBox`. A manifest now lists every component folder its modules reach, directly or through the library's internals.
|
|
392
|
+
|
|
393
|
+
- **Hidden content is inert under React 18 as well.** A covered `PlSpoiler`, a collapsed `PlPill`'s details, a closing or minimized `PlWindowPane` and a `disabled` `PlCalendar` set `inert`, which React 18 does not know and dropped with a warning. The content stayed in the tab order and on the accessibility tree, and a disabled calendar still took a pick. The attribute is now written in the form each supported React version keeps.
|
|
394
|
+
|
|
395
|
+
- **A `PlScrollZone` or `PlTabs` with `overscroll="auto"` gives the wheel back from the first moment.** The latch that keeps a gesture on a strip it was just scrolling counted from the page's own start rather than from a real move, so for the first quarter-second after a page loaded, a strip at its end held the wheel it should have handed on. The Flutter build already counted from nothing.
|
|
396
|
+
|
|
397
|
+
- **A `PlImage` with `preview` reserves its box again.** The preview's trigger is a `<button>`, and a button sizes itself to its content even when it is displayed as a block. The content is a picture sized off the box, so before the file arrived the box had no width and its `ratio` reserved nothing, and a file smaller than the container shrank the box to the file. The trigger now takes the full width, as a picture without `preview` always did.
|
|
398
|
+
|
|
399
|
+
- **`PlChip` keeps the tails of its letters.** The label is truncated, and truncation clips at the line box, which the chip's one-em leading made shorter than the glyphs inside it — so every g, j, p, q and y lost its descender, at every size. The label's line box is now the font's own height. The chip is the same height and the words sit where they did.
|
|
400
|
+
|
|
401
|
+
- **`PlSpoiler` no longer changes height when it is uncovered.** The cover is a line of explanation and a button, so it is routinely taller than the text it covers — and it was taken out of the layout on reveal, which collapsed the sheet to its content and pushed the whole page below it up. Covering it again pushed everything back down. The cover now keeps its place and is held hidden and `inert`, exactly as the `reversible` hide row already was, so the sheet measures the same in both states. A `maxHeight` clamp is still released on reveal, which is the one thing that may resize it.
|
|
402
|
+
|
|
403
|
+
- **A justified `PlGallery` no longer blows up its last row.** Every tile is grown in proportion to its own width, and the last row has fewer of them sharing the same width — so one leftover photograph stretched to fill it and stood two or three times as tall as the gallery above it. The list now carries a `::after` that eats the slack, which keeps the last row at the height it was aiming for. It is a pseudo-element rather than a filler item on purpose: an extra `<li>` would be an extra entry in a `role="list"`, counted by every screen reader that announces how many there are. The Flutter build already left its last row unstretched.
|
|
404
|
+
|
|
405
|
+
- **`PlImage` no longer hides a picture that had already arrived.** The component moved out of its loading state on the `<img>`'s `load` event alone, and an event is only heard by something already listening: a file served from the cache — or one a server rendered, so the browser began fetching it while parsing the HTML — can finish decoding before React attaches the handler. The picture then sat at `opacity: 0` behind its own placeholder for good. It now asks the element where it got to on mount and on every `src` change, so a picture that is already `complete` is shown rather than waited for.
|
|
406
|
+
|
|
407
|
+
### Changed
|
|
408
|
+
|
|
409
|
+
- **`PlCombobox` and `PlCommandPalette` say the label pack's `empty` when nothing matched.** Their `emptyMessage` defaults, "No matches" and "No commands found", were written in English, so a translated application still said them in English. Both now fall back to `empty`, as `PlTreeSelect` and `PlTransfer` do, which makes the English default "Nothing here" for both. Pass `emptyMessage` to keep the old words.
|
|
410
|
+
|
|
411
|
+
- **Focus rings are drawn in the family's `accent`, opaque.** The ring was the `solid` colour at 55% opacity, which came to about 2.2:1 against a white surface, and 1.4:1 for `warning`, under the 3:1 a focus indicator needs. `--plass-{c}-ring` now resolves to `--plass-{c}-accent`, which clears 3:1 against the surface and the page in both themes. Every focused control looks different: darker in the light theme and lighter in the dark one. A project that overrides an `accent` now moves the ring with it.
|
|
412
|
+
|
|
413
|
+
- **A `PlScrollZone` no longer hands the wheel back to the page at its ends.** The pointer being on the shelf is the reader saying which of the two things under it they meant to move, and reaching the last card is not them saying something else — so the page used to start moving at a pixel nobody chose, in the middle of a flick. The new `overscroll` default is `'contain'`, and `overscroll="auto"` is the old behaviour. Even `auto` now keeps a gesture that was scrolling the strip a moment ago and hands the page the wheel only once the reader has paused. Two things keep this from being a trap: a strip everything fits in is not a scroller and holds nothing back either way, and only the axis the strip runs on is contained, so a finger sliding down a horizontal shelf still scrolls the page.
|
|
414
|
+
|
|
415
|
+
- **`PlImage`'s preview overlay is now a separate chunk.** `preview` is off by default and the overlay is several times the weight of the picture component that opens it, so it is reached through `React.lazy` — as `PlGallery`'s viewer already was. That takes 21 kB gzipped off the first paint of any page importing `PlImage` — it measured 26.8 kB and the overlay was 25.4 kB of it — and the same 21 kB off `PlGallery`, which draws its tiles with a `PlImage` and whose own lazy viewer had been undone by this one import. Nothing to configure, and no API change; on a cold cache the overlay now appears a moment after the first press.
|
|
416
|
+
|
|
417
|
+
- **The size budget covers `PlImage` and `PlGallery`**, the two components whose heaviest feature is off by default, so a static import added back to either is caught rather than reviewed past.
|
|
418
|
+
|
|
419
|
+
- The two runtime dependencies move their floor up: `@base-ui/react` to `^1.8.0` and `highlight.js` to `^11.12.0`. Neither release changes an API this package uses.
|
|
420
|
+
|
|
421
|
+
### Documentation
|
|
422
|
+
|
|
423
|
+
- **The `PlShow` page says that a layer which portals out is not hidden with its half.** It said both halves are hidden with `display: none`, but a `PlModal`, `PlDrawer` or popover inside the closed half renders at the end of `<body>` and still shows, and two halves reading the same `open` open two windows. The page and the JSDoc now say so and point to `usePlBreakpointValue` for layers.
|
|
424
|
+
|
|
425
|
+
- **The line chart page lists the fields of `PlassChartAxis`, `PlassChartLegend` and `PlassChartTooltip`.** The props tables named the three types and nothing more, so `min`, `tickCount`, `tickFormat`, `showValue`, `crosshair` and `mode` could only be found in the type declarations. The bar, area and scatter chart pages link to them.
|
|
426
|
+
|
|
427
|
+
- **The documentation was audited and rewritten for accuracy and plainness.** Three claims were wrong and are corrected: `PlImage` said there is no gallery component, the component index said every component has a Flutter half, and the component counts had gone stale (130 previews on the index, 130 components registered by `plass-ui/tailwind.css`, 127 shared between the two frameworks).
|
|
428
|
+
|
|
429
|
+
- **The section headings name the section.** 135 of them were essay titles (`What it answers before it knows`, `Which floating surface`, `Why it is not a PlCard`) and now name what the section holds. Every anchor link moved with them, and three links that were already broken are fixed.
|
|
430
|
+
|
|
431
|
+
- **The prose reads as reference rather than as an essay.** Most of the em dashes in both locales were replaced with plain punctuation or a sentence of their own. Around thirty of the longest paragraphs are split into sentences with the rationale taken out and the facts kept, the `is not X, it is Y` construction is gone where the negated half added nothing, and a 661-character explanation that had been copied into six pages is one clause per framework and a link.
|
|
432
|
+
|
|
433
|
+
- **The Korean pages read as Korean.** 해요체 no longer drifts into 합쇼체 pages, 구어 축약 is written out, `말합니다`/`주장합니다`/`손을 뻗다` as literal renderings of say, assert and reach for are replaced by the verb the sentence actually needs, `가지고 있다` becomes `~이 있다`, and 31 ledes that were noun-phrase fragments are complete sentences.
|
|
434
|
+
|
|
435
|
+
- **The home page and the READMEs read as an introduction.** The headline says what the library is, the feature cards are one or two plain sentences each, and the internals that belonged on a reference page are gone from them.
|
|
436
|
+
|
|
5
437
|
## 1.4.0 (2026-09-05)
|
|
6
438
|
|
|
7
439
|
### Added
|