gridsmith-ui 0.29.1 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +5 -1
  3. package/dist/DropdownMenu-DqT7ZQ0D.d.ts +39 -0
  4. package/dist/charts.js +24 -9
  5. package/dist/charts.js.map +1 -1
  6. package/dist/chat.js +7 -7
  7. package/dist/{chunk-MFAWJSMJ.js → chunk-6C2IUMY7.js} +4 -23
  8. package/dist/chunk-6C2IUMY7.js.map +1 -0
  9. package/dist/{chunk-OYNUU346.js → chunk-7QLXYRPD.js} +3 -12
  10. package/dist/chunk-7QLXYRPD.js.map +1 -0
  11. package/dist/{chunk-FD4YED7O.js → chunk-DBHVJ7Z3.js} +5 -49
  12. package/dist/chunk-DBHVJ7Z3.js.map +1 -0
  13. package/dist/chunk-IELIF4PJ.js +110 -0
  14. package/dist/chunk-IELIF4PJ.js.map +1 -0
  15. package/dist/chunk-ISONVOA7.js +316 -0
  16. package/dist/chunk-ISONVOA7.js.map +1 -0
  17. package/dist/{chunk-4TUJXDRU.js → chunk-KMKBOTFR.js} +93 -32
  18. package/dist/chunk-KMKBOTFR.js.map +1 -0
  19. package/dist/{chunk-MKFWHWBK.js → chunk-L6HWACK7.js} +7 -7
  20. package/dist/chunk-L6HWACK7.js.map +1 -0
  21. package/dist/chunk-LJYSVZMI.js +25 -0
  22. package/dist/chunk-LJYSVZMI.js.map +1 -0
  23. package/dist/{chunk-6A5RHW2W.js → chunk-NEUWZUK6.js} +4 -5
  24. package/dist/chunk-NEUWZUK6.js.map +1 -0
  25. package/dist/chunk-NKUCS67T.js +13 -0
  26. package/dist/chunk-NKUCS67T.js.map +1 -0
  27. package/dist/{chunk-RECCCA7Z.js → chunk-OVHBQT3Z.js} +6 -5
  28. package/dist/chunk-OVHBQT3Z.js.map +1 -0
  29. package/dist/{chunk-I3MTQ3CS.js → chunk-OX3NNMWG.js} +6 -6
  30. package/dist/{chunk-I3MTQ3CS.js.map → chunk-OX3NNMWG.js.map} +1 -1
  31. package/dist/{chunk-ZI3OQDET.js → chunk-R5QXMNW7.js} +297 -15
  32. package/dist/chunk-R5QXMNW7.js.map +1 -0
  33. package/dist/chunk-SJZWEBHL.js +58 -0
  34. package/dist/chunk-SJZWEBHL.js.map +1 -0
  35. package/dist/{chunk-FUKSLQ77.js → chunk-SLRTSBCJ.js} +91 -51
  36. package/dist/chunk-SLRTSBCJ.js.map +1 -0
  37. package/dist/chunk-T52LAFKG.js +1350 -0
  38. package/dist/chunk-T52LAFKG.js.map +1 -0
  39. package/dist/chunk-TYNDXMBV.js +105 -0
  40. package/dist/chunk-TYNDXMBV.js.map +1 -0
  41. package/dist/{chunk-37WOC7F7.js → chunk-UDJOIVKQ.js} +31 -113
  42. package/dist/chunk-UDJOIVKQ.js.map +1 -0
  43. package/dist/{chunk-RJEUQ7RL.js → chunk-ULCACQSA.js} +47 -90
  44. package/dist/chunk-ULCACQSA.js.map +1 -0
  45. package/dist/{chunk-HP7WUZPK.js → chunk-WIUGPZOH.js} +10 -3
  46. package/dist/chunk-WIUGPZOH.js.map +1 -0
  47. package/dist/{chunk-JTU7OBYJ.js → chunk-YL5KD5RC.js} +4 -3
  48. package/dist/chunk-YL5KD5RC.js.map +1 -0
  49. package/dist/data-grid.d.ts +25 -6
  50. package/dist/data-grid.js +180 -58
  51. package/dist/data-grid.js.map +1 -1
  52. package/dist/editor-mentions.js +4 -5
  53. package/dist/editor-mentions.js.map +1 -1
  54. package/dist/editor.js +11 -8
  55. package/dist/editor.js.map +1 -1
  56. package/dist/{feather-FNBQ74RR.js → feather-XFFEFLTV.js} +5 -3
  57. package/dist/feather-XFFEFLTV.js.map +1 -0
  58. package/dist/{heroicons-DODCSYWD.js → heroicons-L6DOHBEA.js} +5 -3
  59. package/dist/heroicons-L6DOHBEA.js.map +1 -0
  60. package/dist/icons.d.ts +1 -1
  61. package/dist/icons.js +1 -1
  62. package/dist/index.d.ts +138 -39
  63. package/dist/index.js +846 -1667
  64. package/dist/index.js.map +1 -1
  65. package/dist/kanban.d.ts +26 -8
  66. package/dist/kanban.js +204 -79
  67. package/dist/kanban.js.map +1 -1
  68. package/dist/{lucide-ZWE3EPGP.js → lucide-JOORDTO6.js} +5 -3
  69. package/dist/lucide-JOORDTO6.js.map +1 -0
  70. package/dist/{phosphor-DIHPA4AJ.js → phosphor-TKGDYNGR.js} +5 -3
  71. package/dist/phosphor-TKGDYNGR.js.map +1 -0
  72. package/dist/resizable.js +47 -16
  73. package/dist/resizable.js.map +1 -1
  74. package/dist/roots.css +12 -8
  75. package/dist/{table-utils-CxnpFHRF.d.ts → table-utils-BXkP-2Hx.d.ts} +3 -1
  76. package/dist/{tabler-D3VYTSGI.js → tabler-YGETLYRU.js} +5 -3
  77. package/dist/tabler-YGETLYRU.js.map +1 -0
  78. package/dist/tokens.css +14 -0
  79. package/dist/utils.js.map +1 -1
  80. package/dist/virtualized.d.ts +6 -1
  81. package/dist/virtualized.js +17 -13
  82. package/dist/virtualized.js.map +1 -1
  83. package/dist-static/tokens.css +14 -0
  84. package/package.json +1 -1
  85. package/dist/chunk-37WOC7F7.js.map +0 -1
  86. package/dist/chunk-4TUJXDRU.js.map +0 -1
  87. package/dist/chunk-6A5RHW2W.js.map +0 -1
  88. package/dist/chunk-FD4YED7O.js.map +0 -1
  89. package/dist/chunk-FUKSLQ77.js.map +0 -1
  90. package/dist/chunk-HP7WUZPK.js.map +0 -1
  91. package/dist/chunk-JTU7OBYJ.js.map +0 -1
  92. package/dist/chunk-MFAWJSMJ.js.map +0 -1
  93. package/dist/chunk-MKFWHWBK.js.map +0 -1
  94. package/dist/chunk-OYNUU346.js.map +0 -1
  95. package/dist/chunk-QM4SJKWV.js +0 -51
  96. package/dist/chunk-QM4SJKWV.js.map +0 -1
  97. package/dist/chunk-QNHVPE2B.js +0 -117
  98. package/dist/chunk-QNHVPE2B.js.map +0 -1
  99. package/dist/chunk-RECCCA7Z.js.map +0 -1
  100. package/dist/chunk-RJEUQ7RL.js.map +0 -1
  101. package/dist/chunk-WH2FUFAC.js +0 -15
  102. package/dist/chunk-WH2FUFAC.js.map +0 -1
  103. package/dist/chunk-ZI3OQDET.js.map +0 -1
  104. package/dist/feather-FNBQ74RR.js.map +0 -1
  105. package/dist/heroicons-DODCSYWD.js.map +0 -1
  106. package/dist/lucide-ZWE3EPGP.js.map +0 -1
  107. package/dist/phosphor-DIHPA4AJ.js.map +0 -1
  108. package/dist/tabler-D3VYTSGI.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,99 @@
1
1
  # gridsmith-ui changelog
2
2
 
3
+ ## Unreleased (0.30.1)
4
+
5
+ **Added.**
6
+ - **VirtualizedList's name.** `aria-label` names the list for the Tab stop it becomes while it scrolls with nothing in it to Tab to ("Activity log"; default "List").
7
+
8
+ **Fixed.**
9
+ - **The Sidebar's Collapse button keeps focus.** Pressing it by keyboard drew a new button each time, so focus fell to the page; it now stays on the button, which says what it will do next ("Expand sidebar").
10
+ - **A button that turns itself off, or goes, hands focus on.** Pagination's Previous and Next at the first or last page, TransferList's move buttons once nothing is left to move, BulkActionBar's Clear selection and ChatMessageList's suggestions each left focus on the page. Focus now goes on: to the page now current, to the button that moves the other way, to the select-all box of the Table under the bar, to the message field; else to the nearest control still there.
11
+ - **Focus never falls to the page when a whole list goes.** A row's menu action that empties its list, on a page that swaps the table for its empty state, left focus on the page, at once or after a server round trip. It now goes to what took the list's place, its first control, else its heading, else to the nearest container's.
12
+ - **An overlay opened from one that closed as it opened gives focus back.** A preview dialog's "Edit details" that swaps the preview for an edit drawer left the drawer nothing to give focus back to. When the drawer closes, focus now goes to the preview's own opener, the card, once the overlay its opener sat in has closed.
13
+ - **A StackedList or Table item the page takes away while its button has focus hands focus on.** An alert's Acknowledge that filters the alert out of the list left focus on the page. Focus now goes to the same button in the item before, else in the item after; with the list gone, to what took its place, else the section's heading. Focus the person had moved elsewhere stays there.
14
+ - **A panel open from the start gives focus back as it closes.** A CornerPanel or AsidePanel open as the page loaded had no opener, so closing it by Escape left focus on the page. Focus now goes to the control that names it, its Launcher, else to the nearest control where the panel sat.
15
+ - **An infinite list holds while its Load more has keyboard focus.** The list loaded more as the button came into view, which keyboard focus also does, so each load pushed the focused button off the screen again. While the button has keyboard focus the list waits for Enter, and after that load the button is brought back into view.
16
+ - **A card moved from its menu keeps focus on its ⋯.** After a Move, focus went to the card itself. It now goes to the card's ⋯ in its new place, the button that opened the menu, as every menu gives focus back to its button; a refused move leaves it there too.
17
+ - **The shell's phone menu is modal with the navbar in it, and is named.** Under the navbar it was marked modal but let Tab out to the page, and it had no name. While it's open the page behind is now inert, Tab cycles through the navbar and the menu, Escape closes it, and it's named "Menu". The stacked shell's menu, with no navbar above it, traps as before.
18
+ - **A scroll area with nothing in it to Tab to is a Tab stop.** A Drawer's or Dialog's body of text, VirtualizedList, the shell's `<main>` on a page with no control, the Calendar's day headers on a phone, the chat's message list and the Table's phone list scrolled out of a keyboard's reach, since Safari makes no such area focusable by itself. Each is now a named Tab stop while it scrolls and holds nothing to Tab to, drawn with the inset ring SplitView's panes have. `<main>` keeps its landmark and stays the skip link's target, which draws no ring.
19
+ - **The Dropdown's open list is named and in the keyboard's reach.** The list had no name, and no option was a Tab stop, so a list long enough to scroll was out of the keyboard's reach. It's now named by the field's label, its chosen option (else its first) is its one Tab stop, and a list that takes many choices says so. The arrows, Home and End move as before.
20
+ - **A collapsed or mini sidebar's links are named, and the current page says so.** A collapsed link had no name, its label undrawn and its tooltip only a description; it's now named by its label, with the same one tooltip. SidebarNav's current item and BottomNav's current tab are marked `aria-current="page"`, not by their colour alone.
21
+ - **TreeView's three-state checkbox is named** by its node's label.
22
+ - **A chart's data rows no longer give its shapes their roles.** Charts drew each bar, slice, stage, track and point with its data row's own fields, so a row with a `role`, `aria-label` or `id` field (a chart of people by role) gave the shape that role. Each now draws from its own props.
23
+ - **Escape asks before throwing away a half-typed tag.** TagInput makes typed text a tag on Enter, a comma or leaving the field, so the other ways out of an overlay asked; Escape closed a Drawer or Dialog with the text lost when the page's `dirty` counted only committed tags. Text a control holds but hasn't committed now always makes the overlay ask, whatever the page's `dirty` says.
24
+ - **SplitView's phone Back works without `onDetailClose`.** A page that passed `detailOpen` but no `onDetailClose` drew a Back on phones that did nothing, leaving the person stranded away from the list. Without the handler the view now shows the list itself, and a press in the list brings the detail back; a development warning names the missing handler. On a phone, the detail giving way to the list leaves focus on the list's chosen item.
25
+ - **A control at the foot of a page shows its whole outline.** The shell's `<main>` keeps the outline's reach at its foot as scroll padding, so a control Tab reaches across the bottom edge comes into view with its outline, the page's last control included. A Checkbox's input now covers its box, so Tab brings the box into view, outline and all; its 1 px hidden input had let the outline be cut by up to 16 px.
26
+ - **A minimal sidebar under a floating navbar keeps room for its first link's outline,** which dropping its top inset had cut by 4 px.
27
+ - **Icon buttons that had no tooltip show the library's one tooltip, their name:** the Drawer's and Dialog's close, Pagination's Previous and Next, a Dropdown's chosen-option remove, the date, date-range and time pickers' buttons, Card's More actions, the Table's row toggle, Tag's remove and the Carousel's arrows. RichTextEditor's toolbar buttons are named by their words and show the same tooltip in place of the browser's title, and its toggles say whether they're on (`aria-pressed`).
28
+ - **A timeline's "Previous period" is unavailable at its start, and "Next period" at its end,** drawn as Zoom's buttons are at their limits. Each had done nothing there.
29
+ - **A Table column of elements sorts only where it can.** Sorted by the Table, a column marked `sortable` whose cells are elements with no `sortValue` drew a sort button that sorted nothing; it now draws none, and a development warning says why. Sorted by the page (`sortKey` with `onSortChange`), the header shows the page's sort whatever its cells are; it had never marked a column of elements as sorted.
30
+
31
+ ### Upgrade note for agents (0.30.0 → 0.30.1)
32
+
33
+ `^0.30.0` covers 0.30.1, so `npm update gridsmith-ui` brings it in. Nothing in an app's code changes, and the theme's tokens need no regenerating. What to check:
34
+ - **The Tab order.** A scroll area with nothing in it to Tab to is now a Tab stop while it scrolls: a Drawer's or Dialog's body of text, VirtualizedList, the shell's `<main>` on a page with no control, the Calendar's day headers on a phone, the chat's message list and the Table's phone list. The Dropdown's open list has one Tab stop, its chosen option. A test that counts Tab presses through such a page counts one more.
35
+ - **Where focus goes.** After a Kanban card's Move it's on the card's ⋯, not the card; after BulkActionBar's Clear selection, on the Table's select-all box. A test that expected focus on the page after a list emptied, or after an overlay with no opener closed, now finds it on a heading or a control.
36
+ - **A test that finds an editor toolbar button by its title.** RichTextEditor's toolbar buttons carry no `title`, their tooltip showing the words: find them by role and name (`getByRole('button', { name: 'Bold (Ctrl+B)' })`).
37
+ - **The shell's phone menu** is no longer `aria-modal`. While it's open, the page behind it is `inert`, so a test that reaches the page's controls with the menu open finds them inert.
38
+ - **A Table column marked `sortable` whose cells are elements** draws no sort button when the Table sorts the rows: give it a `sortValue`, or sort the rows in the page with `sortKey` and `onSortChange`, and then the header shows that sort.
39
+ - **A Drawer or Dialog holding a TagInput** asks before Escape closes it while text is typed that isn't yet a tag.
40
+ - **SplitView with `detailOpen` and no `onDetailClose`** shows the list on a phone's Back, and says so in development. A page that closes its detail itself passes `onDetailClose`, as before.
41
+ - **VirtualizedList** takes `aria-label`, its name as a Tab stop.
42
+
43
+ ## 0.30.0 — 15 October 2026
44
+
45
+ **Added.**
46
+ - **Days a picker can't choose, beyond `min` and `max`.** DatePicker and DateRangePicker take `disabledDates`: a function of the day, or weekdays as a shorthand (`[0, 6]` for weekends; the `DisabledDates` type). An excluded day can't be chosen by a click, the keyboard or typing, a range's ends are never excluded days though the range may span them, and DatePicker's Today is unavailable when today can't be chosen.
47
+ - **DateRangePicker's range can be typed.** Its field holds the start's and the end's day, month and year, typed as DatePicker's are; the chevron opens the calendar. A range is taken once both ends are whole, valid and in order. At phone width its panel lines up with its field, the field's width, one month filling it with days a comfortable tap, its quick ranges one row of chips that scrolls sideways, and its footer with Clear always on the screen.
48
+ - **A consent notice on Alert.** `consent` gives an Alert two choices of equal weight (accept and reject analytics cookies, say), which stay two buttons at phone width; the notice is a region named by its title, never an alert, and is never pinned over the page. `confirmation` replaces the choices with what the person chose, announced and focused. Equal weight is the default and the better practice; `primary` on one choice draws it as the primary button for a page that insists (the `AlertConsentChoice` type).
49
+ - **A card moves without dragging.** Each KanbanBoard card has a ⋯ button in its top right, outside the card's own button, whose menu lists the columns it can go to ("Move to Done") and its place in its own ("Move up", "Move down"); it shows without hover on a touch screen. A move from the menu goes through `onCardMove` as a drop does, so a page that refuses it puts the card back.
50
+ - **A card's own actions.** `cardActions` gives a card's ⋯ menu the page's actions (open, edit, copy, delete), with the board's Move among them as a submenu where the page puts it, or at their end; on a read-only board the menu holds the actions alone.
51
+ - **Submenus in DropdownMenu.** An entry with `items` (the `DropdownMenuSubmenu` type) opens a submenu, one level deep: beside the menu on a desktop, where → or Enter opens it, its first item focused, and ← or Escape closes it, focus back on its entry, and a hover opens it too; on a phone its items take the menu's place under a Back item. Home and End now go to the menu's first and last items.
52
+ - **A column's work-in-progress limit.** `KanbanColumn.limit` draws the column's count against it ("4/3"), and a column past it is marked by a warning icon and by its count's words for a screen reader ("4 cards, over the limit of 3"), never by colour alone. A limit informs and never refuses a drop.
53
+ - **Tabs that activate by Enter.** `activation="manual"`: the arrows move focus among the tabs, and Enter or Space selects one, for tabs whose panels load or navigate. The default still selects as focus moves.
54
+ - **The Gantt shades days.** `shadeDays` (a non-working day, a holiday) passes to its TimeViewport, at the zooms that draw days.
55
+ - **A selected Tag.** `selected` draws a Tag chosen, as a filter chip, and reports it (`aria-pressed`).
56
+ - **A range of rows in one go.** In a selectable Table, Shift with a click on a row's checkbox, or with Space on it, selects every row between the last one chosen and this one, in the order the rows are drawn; a plain click still picks one. Checkbox's `onChange` passes on whether Shift was held (`onChange(checked, { shiftKey })`).
57
+ - **An editable DataGrid shows which cells edit, and each kind edits with its own control.** A pencil shows on an editable cell while it is hovered or focused; a calculated or locked cell (`meta.editable: false`) shows none and is marked read-only. A number edits in a NumberInput, a date in a DatePicker, a choice in a Dropdown of the column's `meta.options` (`meta.kind: "choice"`; each option a `DataGridOption`, from `gridsmith-ui/data-grid`), a yes or no in a Checkbox (the `boolean` kind, found from the values), and text in a text Input; each saves once (Enter, a pick, or leaving the editor), and Escape leaves the cell as it was. A picker's popup is part of its editor, so opening it doesn't end the edit, and while a cell is edited its keys are its editor's. The editor sits inside its cell, so no column moves and the row keeps its height while it is open: an editable column of numbers, dates or choices is at least as wide as its editor.
58
+ - **The carousel's stop and play button.** An auto-playing Carousel has a quiet button to stop and start it in its top-left corner, first in its Tab order: a 24 px target, its icon on a faint see-through backdrop that reads on a photo too. Keyboard focus coming to any part of it stops auto-play, which stays stopped until the person presses play; the pointer over it, a drag or focus in a slide pause it; and it never starts by itself for a person who has asked for reduced motion. It takes an `aria-label`, each slide is named by its place ("3 of 10"), only the slide in view can be reached, and a move the person makes is announced. Auto-play goes on from the last slide to the first.
59
+ - **Icons `play` and `pause`,** in every pack.
60
+
61
+ **Fixed.**
62
+ - **A person's reduced motion is kept in generated apps.** The library zeroes its motion tokens when the person asks for reduced motion, but a theme's stylesheet declared them again after it, so the theme's speeds won. The generated stylesheet now ends with its own reduced-motion block, and the library's block outranks a stylesheet from an earlier version and a token written on the page's root.
63
+ - **Typing a date or a time.** A part (day, month, year; hour, minute) keeps focus until the person moves on, with Tab, an arrow or a separator (`/`, `-`, `.` or a space for a date; `:`, `.` or a space for a time), and a digit the part can't take starts the next. A part come back to by Tab, an arrow or a click is typed afresh. Typing 07, Tab, 12 had given the day 07 and a blank month, and 09, Tab, 20 a blank minute: the Tab after a full part skipped the next one. Typing the digits alone still works.
64
+ - **NotificationCenter groups its rows by their date,** not the words in their time: "2 days ago" sat under Today. A row's time is a RelativeTime; a row whose `timestamp` is text alone can't be dated, so it goes under Earlier.
65
+ - **NumberInput is a spin button with its value and limits,** and Home and End go to its limits where it has them (they stay the caret's keys where it hasn't).
66
+ - **A pinned page header no longer covers the control in focus.** A collapsed PageHeader pinned over the page took no room from the scroller, so a control reached with Tab, or brought into view by a link to it, could stop under the bar. The scroller's top scroll padding now follows the bar, inside the shell and on the window outside one, and the shell's outer box can no longer be scrolled by focus.
67
+ - **Alert's urgency matches its message.** An error or a warning announces at once (`role="alert"`), a success or information politely (`role="status"`). Its Dismiss is the library's icon Button, with its tooltip and a 24 px target.
68
+ - **A control that removes itself hands focus on.** After an Alert's Dismiss or a Tag's remove, focus moves to the same control in the item before, else to the item before, else after, else to the group, never to the page.
69
+ - **A clickable, removable Tag has two controls side by side.** Its own button and its remove button were one inside the other; now they are siblings, the whole chip still answers a press, and the chip draws the focus outline.
70
+ - **A label-less control keeps the name the page gives it.** An `aria-label` now reaches the control of a Checkbox, RadioGroup, Rating, PinInput, NumberInput, DatePicker, TimePicker, DateRangePicker (its name, then the range), ColorPicker (its group), FileUpload (its input) and TreeView (with `aria-labelledby` too); it had dropped on the way, leaving the control unnamed.
71
+ - **TimePicker's columns say what they are.** Each is a listbox (Hours, Minutes, AM or PM) whose options report which is chosen; the choice had been marked by its look alone.
72
+ - **A picker in a sheet or a dialog gives focus back.** A press outside a popup that leaves focus on nothing, inside a modal (the phone's date sheet, a Drawer), now gives focus back to the field the popup opened from.
73
+ - **Table's first load draws skeleton rows,** at phone and desktop width, as many as a page shows, where it drew a spinner; a refresh keeps its rows, marks the table busy and draws a thin moving line along its top edge (still, under reduced motion).
74
+ - **Every sortable column shows that it sorts,** in Table and DataGrid alike: a double chevron in the muted text colour on each sortable header, and on the sorted one a single chevron in the accent pointing its way, its label in full colour. In a right-aligned column the indicator sits before the label, so the label ends where the column's values end. Table showed nothing until a column was sorted, and DataGrid's chevrons were too faint to read (1.6:1); they now meet the 3:1 an icon marking a control needs.
75
+ - **Cut text has a keyboard and a tap route to its full text.** Text cut to fit (TextOverflow, a table's cut cell, a long email address) is a Tab stop that draws the focus outline, its full text shown on keyboard focus and at once on a tap as well as on hover; inside a control (a button, a link, a row that opens on a click, a grid's cell) it adds no stop. A table's cut email address had only a native title, which no keyboard or tap reaches.
76
+ - **CodeBlock's copy button shows the copy icon,** where it showed a folder.
77
+ - **A flow open in two tabs no longer has them undo each other.** `useFlowSteps` keeps the step on screen, so "Save and continue later" and a return resume there, but it also wrote it whenever the stored progress changed, another tab's change included. Two tabs on different steps each wrote their own step back over the other's for as long as both stayed open, and "Save and continue later" in one could resume at the other's step. A tab now keeps its step only when it shows one: on arriving at a step and on each move.
78
+
79
+ ### Upgrade note for agents (0.29.1 → 0.30.0)
80
+
81
+ Upgrade `gridsmith-ui` to `^0.30.0` (an app pinned to `^0.29.0` never reaches it), then report the new version as `ui_version`: the rules and guidance that need 0.30.0 are served from then on. An app's code needs no change and its theme's stylesheet no regenerating; what to check:
82
+ - **A test that finds a success or information Alert with `getByRole('alert')`.** Those are `role="status"` now; only an error or a warning is an alert. Find them with `getByRole('status')`, or by their title's text.
83
+ - **A test that clicks a time in TimePicker's panel as a button.** Its times are options in listboxes: `getByRole('option', { name: '09' })`.
84
+ - **A test that finds a clickable Tag's chip as the button.** The button is the Tag's label, a `<button>` inside the chip: `getByRole('button', { name: 'Design' })`.
85
+ - **A test that types into a NumberInput as a textbox.** It is a spin button: `getByRole('spinbutton', { name })`.
86
+ - **A test that waits for Table's spinner.** A first load draws skeleton rows and says "Loading table data" to a screen reader; a refresh marks the table `aria-busy`.
87
+ - **A test that finds a Carousel's slide by "Slide 3 of 10".** A slide is a group named "3 of 10", and only the slide in view is reachable.
88
+ - **A test that finds a table's long email address by its title.** It has none: its full address is the cell's tooltip, reached by hover, Tab or a tap.
89
+ - **An editable DataGrid.** A column of numbers, dates, or yeses and noes now edits with its kind's control; a column with a fixed set of values edits with a Dropdown once its `meta` gives `kind: "choice"` and the `options`, and edits as text until then (a development build says so). A test that typed into a text Input in every cell finds the kind's control instead.
90
+ - **NotificationCenter rows with text timestamps.** A row whose `timestamp` is text ("2 days ago") can't be dated, so it goes under Earlier: give it the time itself (an ISO string, a Date or milliseconds), and the row shows it as a RelativeTime under its day's group.
91
+ - **An auto-playing Carousel.** Give it an `aria-label` naming the set. It now goes on from its last slide to its first, looping or not.
92
+ - **Code that reads a menu's entries.** `DropdownMenuEntry` now includes `DropdownMenuSubmenu`, an entry with `items` and no `onClick`: code that calls `onClick` on every entry that isn't a divider narrows with `"items" in entry` first. Code that only builds menus needs nothing.
93
+ - **A test that looks for a sort indicator only on the sorted column.** Every sortable header now has one; the sorted one is marked `data-sort="asc"` or `"desc"`, the others `"none"`, and the header's `aria-sort` still says the order.
94
+ - **An editable DataGrid's column widths.** An editable column of numbers, dates or choices is at least as wide as its editor (9 to 11.5 rem), so a grid laid out to the pixel may need a little more room or a `meta.width`.
95
+ - **Types that grew.** A `Notification`'s `timestamp` is a `TimeValue` (a string, a Date or milliseconds), `ColumnKind` adds `"choice"` and `"boolean"`, and a Checkbox's `onChange` passes a second argument, `{ shiftKey }`. Code that reads a timestamp as text, switches over every kind, or calls a Checkbox's `onChange` itself is the code to check.
96
+
3
97
  ## 0.29.1 — 9 October 2026
4
98
 
5
99
  **Fixed.**
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # gridsmith-ui
2
2
 
3
+ <!-- Last checked against the code on 13 October 2026 (gridsmith-ui 0.29.1). -->
4
+
3
5
  React components and charts with a CSS custom property theming engine, and 5 icon packs.
4
6
 
5
7
  ## Installation
@@ -86,6 +88,8 @@ import { preloadPack } from "gridsmith-ui/icons";
86
88
  preloadPack("lucide");
87
89
  ```
88
90
 
91
+ **Server-rendered apps (Next.js, Remix).** Wrap the app's top in `LocaleProvider`, from `gridsmith-ui`, with the page's language and the request's `Accept-Language` header: `<LocaleProvider lang={LANG} acceptLanguage={acceptLanguage}>`, the same `LANG` on `<html lang>`. The server then writes dates and numbers in the visitor's form, as the browser does, so the page hydrates without a mismatch. In Next.js, read the header in the root layout (`(await headers()).get("accept-language")`). A Vite app doesn't need it.
92
+
89
93
  ## Entry Points
90
94
 
91
95
  Every entry but `gridsmith-ui/utils` is client code, each file opening with `"use client"`. In Next.js (15 or later), a server component (a page with no `'use client'` of its own) renders the components with plain data: text, numbers, dates, elements. A function (`onClick`, `onChange`, a column's `render`) needs a client component of yours, and so does calling the library's hooks; a server page hands the library's constants and contexts on as they are, without reading them, and renders a compound part by its own name (`TabsList` for `Tabs.List`, `CollapsibleTrigger` for `Collapsible.Trigger`). On the server (a page, a route handler, a server action), call the helpers from `gridsmith-ui/utils`, passing them `requestLocale(LANG, acceptLanguage)` so their words read as the components' do; a Date is read there in the server's time zone, so hand `RelativeTime` and the Calendar the data for what the visitor reads in their own. Vite and Remix ignore the marker.
@@ -102,7 +106,7 @@ Every entry but `gridsmith-ui/utils` is client code, each file opening with `"us
102
106
  | `gridsmith-ui/virtualized` | `VirtualizedList` (requires `@tanstack/react-virtual`) |
103
107
  | `gridsmith-ui/resizable` | `ResizablePanelGroup`, `ResizablePanel`, `ResizableHandle`, `SplitView` (requires `react-resizable-panels`) |
104
108
  | `gridsmith-ui/icons` | `Icon`, `IconProvider`, `preloadPack`, `registerIconPack` — 5 icon packs with two-phase loading |
105
- | `gridsmith-ui/utils` | `cn()` — className merge utility (clsx + tailwind-merge) — and the helpers server code calls: the repeat rules, calendar days and date ranges, time zones, a chart's label kind, and `requestLocale()` for a request's locale. Nothing in it needs React or a browser |
109
+ | `gridsmith-ui/utils` | `cn()` — className merge utility (clsx + tailwind-merge) — the shared focus styles (`FOCUS_RING` and its kin) for an element of your own that takes focus, and the helpers server code calls: the repeat rules, calendar days and date ranges, time zones, a chart's label kind, and `requestLocale()` for a request's locale. Nothing in it needs React or a browser |
106
110
  | `gridsmith-ui/roots.css` | CSS custom property engine |
107
111
  | `gridsmith-ui/tokens.css` | Default preset token values |
108
112
  | `gridsmith-ui/tailwind.css` | Tailwind v4 `@theme` bindings for `--ds-*` tokens |
@@ -0,0 +1,39 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { IconName } from './icons.js';
4
+
5
+ type Side = "top" | "right" | "bottom" | "left";
6
+ type Align = "start" | "center" | "end";
7
+
8
+ interface DropdownMenuItem {
9
+ label: string;
10
+ icon?: IconName;
11
+ onClick: () => void;
12
+ variant?: "default" | "destructive";
13
+ disabled?: boolean;
14
+ }
15
+ interface DropdownMenuDivider {
16
+ type: "divider";
17
+ }
18
+ /**
19
+ * An entry that opens a submenu of its own (Sprint 25.8.7's look check: a card's Move among its other actions), one
20
+ * level deep. On a desktop it opens beside the menu: → or Enter opens it, its first item focused; ← or Escape closes it,
21
+ * focus back on this entry; a hover opens it too. On a phone its items take the menu's place, under a Back item.
22
+ */
23
+ interface DropdownMenuSubmenu {
24
+ label: string;
25
+ icon?: IconName;
26
+ items: (DropdownMenuItem | DropdownMenuDivider)[];
27
+ disabled?: boolean;
28
+ }
29
+ type DropdownMenuEntry = DropdownMenuItem | DropdownMenuDivider | DropdownMenuSubmenu;
30
+ interface DropdownMenuProps {
31
+ trigger: ReactNode;
32
+ items: DropdownMenuEntry[];
33
+ align?: Align;
34
+ className?: string;
35
+ container?: Element | DocumentFragment;
36
+ }
37
+ declare function DropdownMenu({ trigger, items, align, className, container }: DropdownMenuProps): react_jsx_runtime.JSX.Element;
38
+
39
+ export { type Align as A, type DropdownMenuSubmenu as D, type Side as S, type DropdownMenuEntry as a, DropdownMenu as b, type DropdownMenuDivider as c, type DropdownMenuItem as d };
package/dist/charts.js CHANGED
@@ -1,10 +1,12 @@
1
1
  "use client";
2
- import { useFormatLocale, useHydrated, TITLE_TEXT, Skeleton } from './chunk-MFAWJSMJ.js';
3
- import { formatLocale, elementType } from './chunk-OYNUU346.js';
2
+ import { TITLE_TEXT, Skeleton } from './chunk-6C2IUMY7.js';
3
+ import { useFormatLocale, useHydrated } from './chunk-LJYSVZMI.js';
4
+ import { formatLocale } from './chunk-7QLXYRPD.js';
5
+ import { elementType } from './chunk-NKUCS67T.js';
4
6
  import { mergeRefs } from './chunk-7Q46JXSK.js';
5
- import { cn, GLASS_SURFACE } from './chunk-JTU7OBYJ.js';
7
+ import { cn, GLASS_SURFACE } from './chunk-YL5KD5RC.js';
6
8
  import { createContext, forwardRef, useContext, useRef, useState, useMemo, useLayoutEffect, useEffect, useId, useSyncExternalStore, Children, isValidElement } from 'react';
7
- import { ResponsiveContainer, BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Bar, LineChart as LineChart$1, Line, AreaChart as AreaChart$1, Area, PieChart as PieChart$1, Pie, Cell, ComposedChart, ScatterChart as ScatterChart$1, ZAxis, Scatter, RadarChart as RadarChart$1, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, RadialBarChart as RadialBarChart$1, RadialBar, FunnelChart as FunnelChart$1, Funnel, LabelList, Treemap, Sankey, Text, Legend, getNiceTickValues, Rectangle } from 'recharts';
9
+ import { ResponsiveContainer, BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Bar, LineChart as LineChart$1, Line, AreaChart as AreaChart$1, Area, PieChart as PieChart$1, Pie, Cell, ComposedChart, ScatterChart as ScatterChart$1, ZAxis, Scatter, Symbols, RadarChart as RadarChart$1, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, RadialBarChart as RadialBarChart$1, RadialBar, FunnelChart as FunnelChart$1, Funnel, LabelList, Treemap, Sankey, Text, Legend, getNiceTickValues, Rectangle } from 'recharts';
8
10
  import { createPortal } from 'react-dom';
9
11
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
10
12
 
@@ -602,6 +604,14 @@ function seriesValues(data, keys, stacked = false) {
602
604
  var hasLabel = (label) => label != null && label !== "" && typeof label !== "number";
603
605
  var entryName = (entry, label) => !hasLabel(label) && typeof entry.payload?.name === "string" && entry.payload.name || entry.name;
604
606
  var entryValue = (entry, format, locale) => typeof entry.value === "number" ? format ? format(entry.value, entry.dataKey) : entry.value.toLocaleString(locale) : entry.value;
607
+ var DRAWING = /* @__PURE__ */ new Set(["x", "y", "cx", "cy", "width", "height", "r", "radius", "innerRadius", "outerRadius", "startAngle", "endAngle", "cornerRadius", "upperWidth", "lowerWidth", "size", "fill", "stroke", "strokeWidth", "opacity", "fillOpacity", "strokeOpacity", "className"]);
608
+ function withoutRowFields(props) {
609
+ const row = props.payload;
610
+ if (!row || typeof row !== "object") return props;
611
+ const own = {};
612
+ for (const [key, value] of Object.entries(props)) if (DRAWING.has(key) || !(key in row)) own[key] = value;
613
+ return own;
614
+ }
605
615
  function tooltipAnnouncement(label, payload, format, locale = formatLocale()) {
606
616
  if (!payload?.length) return "";
607
617
  const values = payload.map((entry) => `${entryName(entry, label)} ${entryValue(entry, format, locale)}`).join(", ");
@@ -865,7 +875,7 @@ function barShape(bars, index, stacked, radius, horizontal = false) {
865
875
  const value = Number(row[b.dataKey]);
866
876
  return !(down ? value < 0 : value > 0);
867
877
  });
868
- return /* @__PURE__ */ jsx(Rectangle, { ...props, radius: onTop ? round : square });
878
+ return /* @__PURE__ */ jsx(Rectangle, { ...withoutRowFields(props), radius: onTop ? round : square });
869
879
  };
870
880
  return Shape;
871
881
  }
@@ -1207,12 +1217,13 @@ var PieChart = markChart(forwardRef(function PieChart2({
1207
1217
  const colors = useChartColors(data.length, false);
1208
1218
  const isAnimated = useChartAnimation(animated);
1209
1219
  const renderLabel = showLabels ? ({ percent }) => `${((percent ?? 0) * 100).toFixed(0)}%` : false;
1220
+ const chartData = data.map((d) => ({ name: d.name, value: d.value }));
1210
1221
  if (!data?.length) return null;
1211
1222
  return /* @__PURE__ */ jsx(ChartWrapper, { ref, height, size: { model: "square" }, chart: "pie", className, "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(PieChart$1, { accessibilityLayer: true, children: [
1212
1223
  /* @__PURE__ */ jsx(
1213
1224
  Pie,
1214
1225
  {
1215
- data,
1226
+ data: chartData,
1216
1227
  dataKey: "value",
1217
1228
  nameKey: "name",
1218
1229
  cx: "50%",
@@ -1496,7 +1507,8 @@ var ScatterChart = markChart(forwardRef(function ScatterChart2({
1496
1507
  name: series.name,
1497
1508
  data: series.points,
1498
1509
  fill: colors[i],
1499
- isAnimationActive: isAnimated
1510
+ isAnimationActive: isAnimated,
1511
+ shape: (p) => /* @__PURE__ */ jsx(Symbols, { ...withoutRowFields(p), type: "circle" })
1500
1512
  },
1501
1513
  series.name
1502
1514
  ))
@@ -1578,7 +1590,9 @@ var RadialBarChart = markChart(forwardRef(function RadialBarChart2({
1578
1590
  const colors = useChartColors(data.length, false);
1579
1591
  const isAnimated = useChartAnimation(animated);
1580
1592
  const chartData = data.map((d, i) => ({
1581
- ...d,
1593
+ name: d.name,
1594
+ value: d.value,
1595
+ ...d.max !== void 0 ? { max: d.max } : {},
1582
1596
  fill: colors[i % colors.length]
1583
1597
  }));
1584
1598
  if (!data?.length) return null;
@@ -1622,7 +1636,8 @@ var FunnelChart = markChart(forwardRef(function FunnelChart2({
1622
1636
  const colors = useChartColors(data.length, false);
1623
1637
  const isAnimated = useChartAnimation(animated);
1624
1638
  const chartData = data.map((d, i) => ({
1625
- ...d,
1639
+ name: d.name,
1640
+ value: d.value,
1626
1641
  fill: colors[i % colors.length]
1627
1642
  }));
1628
1643
  if (!data?.length) return null;