Repository navigation
Material Plus 1.9.0
material-plus-ui/a2ui is new: A2UI's basic catalog drawn as Material Design 3, and five components of this library's own for the tables and charts an agent reporting numbers has to draw — all behind optional peer dependencies, so a project that renders no agent interfaces pays nothing for them. The rest of the release is the charts: an axis that turns a long label instead of cutting it, a line that can bridge a gap or read it as zero, a range a reader can drag out of a long axis, percent stacks, target lines and bands, and a hover panel that sorts its rows and adds up a stack.
Added
-
A chart axis turns a long label instead of cutting it. Whichever axis runs along the bottom now reads
tickLabels, and its defaultautokeeps the labels level while the widest of them fits its slot and turns them once it does not — 45° first, upright where even a turned label's line box will not fit between two ticks.truncateis the old behaviour, androtateandverticalturn every label whether or not it needed it.MPHeatmapChart's column names read the same option.It replaces the two things the axis used to do with a long name, both of which threw the name away: cutting it to "Onbo…", and showing every nth until the survivors fit. A turned label collides across its line box rather than across its own width, and a line box does not grow with the label — so twelve page names fit an axis that was showing four of them. Short labels are left alone however crowded the axis is, because an axis of "Jan", "Feb", "Mar" is crowded by how many of them there are and not by how long any one is — and so are ticks as opposed to names, so
autoturns nothing along the bottom of a chart turned on its side or of a scatter, where that axis is a ruler and every nth loses nothing.The band a turned axis takes is capped at half the chart's height, and 120px whichever is smaller; past that the names are cut again, and a chart that genuinely needs more wants a taller
heightor athicknessof its own. Each label is measured against its own tick rather than against the first one, so only the leftmost is ever shortened to stay inside the chart. -
MPLineChartandMPAreaCharttakegaps, for the two readings a broken line gets wrong.connectjoins the two sides of anulland draws the joining run dashed, so the line carries on and the picture still says which part of it was measured; on an area the fill closes across the gap as well.zerodraws the missing reading at zero and widens the value axis to hold it, which is right where a gap means "none of it happened" and wrong where it means "nobody was counting".break, the default, is what the two charts already did. Only the drawing changes in all three: the hover panel and the table behind the chart still report a gap as a gap. -
zoomlets a reader drag a range out of a long category axis. For the chart with ninety points on it, where the shape of one week is inside a picture of a quarter: a drag across the plot redraws it to what was picked, and the scale re-fits, so the window is a chart of that range rather than a magnified picture of the whole one. The value scale, the hover panel, the spoken readout and the table behind the picture all follow it — the table most of all, since it is what a reader who cannot see the plot is given instead of it.The way back is a Show all control over the corner of the plot, named in all nineteen locales and a sibling of the picture rather than a child:
role="img"is a leaf role, so a button inside it is one no keyboard can reach.Escapeclears the reading first and the window second, which is the order the reader made them in, and a drag narrower than two categories is ignored — a window of one has no distance left in it for a line to have travelled, and a plain click on the plot is exactly that.Pass an object for
rangeandonRangeChange,defaultRange, or aminwider than two. A range is clamped to the data rather than trusted, because one that outlives the series it was picked from is an empty chart with no way back to the full one. Only a chart whose categories are a row of slots takes it:MPScatterCharthas two value axes and no row to cut, and crops withxAxisandyAxisinstead. -
The hover panel takes
sort, and adds up a stacked column on its own.tooltip={{ sort: 'value' }}puts the largest row first, which is what a chart of eight series wants: the row a reader is looking for is usually the big one. The default stays the order the series were passed, so a reader who has learned the legend can find a row without reading it, and ties keep their series order either way.A stack also gets a row for what the column adds up to, ruled off from the parts and carrying no swatch — the height of that column is the whole point of the shape, and a panel that listed the parts without it left the reader adding them up. An unstacked chart does not get one, because its series are not parts of anything; nor does a percent stack, whose answer is 100% every time.
tooltip={{ total }}overrides both. The row is read out with the rest of the panel and is named in every locale the library ships. -
MPBarChartandMPAreaCharttakestacked="percent". Every bar is full and every band reaches the top, so the totals leave the picture and what is compared is the composition — the shape a share-of-traffic chart wants. It answers "what is this made of" and refuses to answer "how much of it is there", which is why it is a third setting rather than a flag on the second: a quarter of 1,200 and a quarter of 12 are drawn identically, so a total a reader needs belongs somewhere else on the page.The axis runs 0% to 100%, and the hover panel says both the value and its share, because the share is what the picture shows and the value is what the reader came for. A negative has no share of a whole and is left out of one, the way
MPPieChartleaves it out; the table behind the chart still has the number. An area's value labels write each band's own share rather than the running total. -
A chart axis takes
references: the target line, the threshold and the band a reader brought with them.yAxis={{ references: [{ value: 300, label: 'SLO', color: 'error' }] }}draws a dashed line across the plot at 300 with its name at the end of it; a reference with atoas well is a band, both edges drawn and the room between them filled. Avaluemay be aDateon an axis of dates.It is deliberately not data: a reference wears the chrome's ink rather than a palette slot, it is dashed so it cannot be mistaken for a gridline, and nothing is inferred. The scale opens to hold it, because a target the plot is cropped above is the one thing a target must never be — pin
minormaxto stop that. A band is drawn under the marks and a line over them: a fill over a bar is a bar the reader has to look through, and a target behind the series that crossed it is a target nobody can read.A reference belongs to the axis whose scale its
valueis on and is drawn perpendicular to it, so the same one is a horizontal line upright and a vertical one on a chart turned on its side. Only an axis with a scale reads them — the value axis always, and the category axis where it runs on numbers, asMPScatterChart's does. A named reference is read out beside the table, which has no column for it. -
MPSparklinetakesgapstoo. The same three readings the line and the area chart take:connectjoins the two sides of a missing point with a dashed run and closes an area across it, andzeroreads the point as a zero and opens the scale to hold it — which matters more on a mark with no axis, since nothing else would say the floor had moved. Abarhas no line to join, so it takeszeroand readsconnectasbreak. The sentence the mark is read out as still counts only the readings it has. -
Charts take
labelColor, which writes the text naming a series in that series' colour. It covers the legend's entries and the values written onto the marks.inkstays the default, and the reason is measurable rather than stylistic: the eight palette slots are fitted to 3:1 against the surface, which is the bar a mark has to clear, and small text is held to 4.5:1. Labels drawn on a fill — a pie's shares, a heatmap's cells — keep the ink that reads against what they sit on, andMPHeatmapChartandMPTimelineChartdo not take the prop at all. -
MPPieCharttakesholeandgap.holeis how much of the radius adonutor asemileaves empty, from0to0.9against the previous fixed0.62;gapis how much page shows between two slices, in pixels,0for slices that touch. The gap is still drawn as a stroke rather than by narrowing each sweep, so every slice keeps the angle its share earned however wide it is set. -
material-plus-ui/a2ui: A2UI's basic catalog, drawn as Material Design 3. A2UI is a protocol for an agent to describe an interface instead of writing one: the agent sends JSON naming components from a catalog, and a renderer on the client draws them. The new subpath implements the protocol's own basic catalog — all eighteen ofText,Image,Icon,Video,AudioPlayer,Row,Column,List,Card,Tabs,Modal,Divider,Button,TextField,CheckBox,ChoicePicker,SliderandDateTimeInput— out of this library's components, so an agent's surface arrives as MD3 rather than as somebody's default styling.mpA2uiCatalogis registered under the basic catalog's own id, which makes it a drop-in: acatalogIdis a promise about schemas and says nothing about how they are drawn, and these implementations are built from the…Apiobjects the protocol publishes rather than from copies of them. So an agent already targeting the basic catalog needs no change, and no schema has to be hosted anywhere for a client to announce it.createMPA2uiCatalogmints a catalog under an id of your own, with your own components beside these or in place of one of them.The two SDK packages it needs —
@a2ui/reactand@a2ui/web_core— are optional peer dependencies, because they bring Lit, signals, Zod, a date library and a Markdown parser with them and this package promises two runtime dependencies. Nothing in the library outsidesrc/a2uiimports either one and the subpath is not in the barrel, so every bundle figure in this file still holds for a project that does not render agent interfaces. The catalog adds no CSS of its own either: it draws with components that are already styled, somaterial-plus-ui/styles.cssand the split per-component sheets are unchanged, and the few declarations it does write are inline and read the--mp-sys-*tokens — an agent's surface follows--mp-source-coloranddata-mp-shapelike everything else.Where the mapping had to choose, it chose MD3's answer: the protocol's
primary,defaultandborderlessbuttons are the filled, outlined and text buttons, a filterableChoicePickerisMPComboboxrather than a text box above a list, andDateTimeInput's two switches pick between the date, time and date-time pickers — converting the payload's ISO 8601 strings to local dates by hand, so a day is never moved by a time zone on the way in or out.VideoandAudioPlayerare the browser's own players with Material's corner on them, since neither is a component this library has. Markdown in aTextis rendered only when the application has configured a renderer through@a2ui/react's Markdown context, whose contract requires it to sanitize; with none configured an agent's string is drawn as the characters it contains. The guide has a page on all of it. -
material-plus-ui/a2uialso carries a catalog of this library's own:DataTable,BarChart,LineChart,PieChartandStatistic. The basic catalog cannot describe a table or a chart, which is most of what an agent reporting numbers has to draw, sompA2uiExtendedCatalogis the eighteen plus these five underMP_A2UI_CATALOG_ID— an id of ours, because five components the basic vocabulary cannot express are a different contract and must not claim to be that one. Register both and the agent chooses per surface.Their schemas are this library's rather than the protocol's, and are written for a caller that can only send JSON: a table's
rowsand a chart'sseriestake a literal array or a path into the data model, a series states which accent role it reads rather than a colour of its own, and the props that are functions in a page — cell renderers, column accessors,Intloptions — are replaced by the narrower questions an agent can answer. There is deliberately no row action: an A2UI action resolves its payload from data paths when it fires and cannot say which row was pressed, so selection carries that instead, throughselectedKeysbound to a path.A value bound to a path is resolved rather than validated — the schema checked the binding, and the data model may hold something else by the time it is read — so what arrives is read rather than trusted. A number that is not a number is a gap, a series that is not a list is an empty chart, and a cell holding an object is written out as JSON instead of throwing where React refuses it.
-
The catalog's schema is published at the id it names, material-plus.cdget.com/a2ui/v0_9/catalog.json, generated in
npm run buildfrom the same Zod schemas the renderer enforces — so the description an agent reads cannot drift from the contract a payload is checked against. The conversion is the SDK's, with its deduplication turned off: it points repeated subschemas at#/properties/…pointers that the component envelope leaves naming nothing, which is survivable on the wire and is a dangling reference in a file published at a URL. A test compares the committed file against the running catalog, and the build fails if the SDK's own output stops agreeing about which components exist and what they require. -
npm run buildmeasures what the A2UI subpath costs, in the two halves a reader has to decide between: 38.9 kB gzipped for the basic catalog and 60.6 kB with the data display, against 77.3 kB and 98.9 kB with the protocol SDK bundled in. The figures are in the guide. Making them separable meant annotating two more calls as pure —createComponentImplementationandcreateMPA2uiCatalog— without which one module held both catalogs and an application taking the basic eighteen carried the data table and the three charts along with them.annotate-pure.mjsnow also skips matches inside comments, because the first thing it annotated was an example in a doc comment, which closed the comment and left the rest of the file as syntax.
Changed
-
A chart whose category names did not fit now turns them rather than cutting them. That is
tickLabelsdefaulting toauto, described above, and it is a visible change to any chart that was showing an ellipsis or dropping every other label — the plot loses height to the band the turned names take. A chart whose labels already fitted is drawn exactly as it was.xAxis={{ tickLabels: 'truncate' }}restores the old drawing. -
A switched-off legend entry is drawn as a disabled control rather than struck through. The swatch and the name fade together to Material's disabled opacity, which is the treatment a reader already knows for "this is off". A series merely standing back while another is hovered is now one step quieter than usual instead of sharing that fade, so the two states cannot be mistaken for each other. Neither depends on hue, so both survive greyscale and forced colours, and
aria-pressedis unchanged. -
Clicking a legend entry no longer leaves the plot faded behind the pointer. The entry took focus on the click and focus dimmed every other series, so a chart sat in its hovered state until something else was clicked. Focus dims the rest only when it arrived from the keyboard now, which is what the dimming was for.
-
The documentation's component groups are ordered Charts, Transitions, Hooks after the alphabetical run, and the Motion group is now Transitions. Pages under
/components/motion/answer at/components/transitions/, and the old addresses redirect.
Fixed
- Two documentation sentences were being evaluated as Vue instead of read as text. A page on the site is a Vue template, and
{{ … }}is a mustache wherever it appears in one — inside an inline code span included. So the line writing out the bar chart's escape hatch rendered asyAxis=with nothing after it, in both languages, and had done since the page was written. Both are rephrased, and the documentation build now fails on prose Vue would evaluate rather than shipping a page with a phrase missing from it.
Every release of this package is in the changelog on the docs site and in CHANGELOG.md at this tag.