@nxgt/material 2.10.1 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/{blockDiagram-BEXU5L5S-CJLOORWK.js → blockDiagram-BEXU5L5S-CnFXw-7E.js} +1 -1
  3. package/dist/{chunk-3FUC2YCW-ClO5Q7A1.js → chunk-3FUC2YCW-3vMiTgTl.js} +1 -1
  4. package/dist/{chunk-7M6MHVWA-DsMPzivr.js → chunk-7M6MHVWA-NPaJJ6Gy.js} +1 -1
  5. package/dist/{chunk-GNY47TPC-CFItyjGy.js → chunk-GNY47TPC-DCFqYc_z.js} +4 -4
  6. package/dist/{chunk-NETBCI7D-Bll4gy0a.js → chunk-NETBCI7D-BB4rjLSQ.js} +1 -1
  7. package/dist/{chunk-Z7XXMR3K-CwcuZpJa.js → chunk-Z7XXMR3K-vmLFt9Cr.js} +1 -1
  8. package/dist/{classDiagram-v2-NBCMYWYE-BAvo2puZ.js → classDiagram-v2-NBCMYWYE-eUVWeh_6.js} +2 -2
  9. package/dist/components/ui/primitives/label/label.css +14 -21
  10. package/dist/{components-N_PpCQjB.js → components-BYxsGbyO.js} +23364 -23345
  11. package/dist/components.js +2 -2
  12. package/dist/{dagre-6A5THRUB-C70bzAEw.js → dagre-6A5THRUB-LmEtEC93.js} +3 -3
  13. package/dist/{diagram-22UHCM2B-BtBii6xG.js → diagram-22UHCM2B-BwARaM-x.js} +2 -2
  14. package/dist/{diagram-3UASUU5V-DMDUtIe2.js → diagram-3UASUU5V-BkeqK_e8.js} +1 -1
  15. package/dist/{elk-276RUBZZ-D2kPrlDF.js → elk-276RUBZZ-DL4pxCSP.js} +3 -3
  16. package/dist/{erDiagram-OPXOYQCR-CItUoDJi.js → erDiagram-OPXOYQCR-Ajlj2lcb.js} +2 -2
  17. package/dist/{field-blur-DbgP3nv0.js → field-blur-DgfIJceK.js} +909 -890
  18. package/dist/{flowDiagram-KWPJA3E3-BF73zGqG.js → flowDiagram-KWPJA3E3-BOEc9KSn.js} +3 -3
  19. package/dist/{ganttDiagram-FUAMR5RP-DWnIg8g9.js → ganttDiagram-FUAMR5RP-DdxLcLNx.js} +1 -1
  20. package/dist/{journeyDiagram-ZHPQQLJL-C1G4q1sS.js → journeyDiagram-ZHPQQLJL-BIzB9BVn.js} +1 -1
  21. package/dist/lib/components/ui/collections/data-table/partials/data-table-empty-row.d.ts +3 -1
  22. package/dist/lib/components/ui/collections/data-table/partials/data-table-toolbar.d.ts +10 -6
  23. package/dist/lib/components/ui/collections/data-table/select-checkbox.d.ts +5 -2
  24. package/dist/lib/components/ui/dates/calendar/calendar-classes.d.ts +45 -0
  25. package/dist/lib/components/ui/dates/calendar/calendar-weekday.d.ts +4 -0
  26. package/dist/lib/components/ui/feedback/banner/banner.d.ts +9 -4
  27. package/dist/lib/components/ui/feedback/toast/toast-classes.d.ts +13 -0
  28. package/dist/lib/components/ui/layout/app-shell/use-navigation-sheet.d.ts +3 -2
  29. package/dist/lib/components/ui/selection/select-chip-field/select-chip-field.d.ts +8 -3
  30. package/dist/main.js +2 -2
  31. package/dist/map.js +1 -1
  32. package/dist/material.css +1 -1
  33. package/dist/{mermaid.core-C74WUE6C.js → mermaid.core-6yWyNdZa.js} +21 -21
  34. package/dist/{mindmap-definition-NLK3R4M7-CFOfIsM7.js → mindmap-definition-NLK3R4M7-aHycauO9.js} +2 -2
  35. package/dist/{pieDiagram-5QR66LMP-B26Y_url.js → pieDiagram-5QR66LMP-DEiooyQu.js} +1 -1
  36. package/dist/{quadrantDiagram-O4NWA36T-vIoMpSWW.js → quadrantDiagram-O4NWA36T-CLlaqEdw.js} +1 -1
  37. package/dist/{requirementDiagram-PLB6GJNP-CL3H5GL7.js → requirementDiagram-PLB6GJNP-DD74Pa6_.js} +2 -2
  38. package/dist/{sankeyDiagram-IPEJSGJF-Wksiclao.js → sankeyDiagram-IPEJSGJF-DAR9G0yC.js} +1 -1
  39. package/dist/{stateDiagram-v2-GCMORJYK-oLPhH1yQ.js → stateDiagram-v2-GCMORJYK-pti7a_r-.js} +2 -2
  40. package/dist/{swimlanes-2SLR337P-C6I2boWI.js → swimlanes-2SLR337P-D10pSc8m.js} +3 -3
  41. package/dist/{swimlanesDiagram-TC7HE7FX-zAPI0RjB.js → swimlanesDiagram-TC7HE7FX-B8HgZod5.js} +3 -3
  42. package/dist/{timeline-definition-EJHVYXUP-Dpsxn1Fj.js → timeline-definition-EJHVYXUP-D8CWcNj3.js} +1 -1
  43. package/dist/{usecaseDiagram-POWQR4AR-DLZ1ULxe.js → usecaseDiagram-POWQR4AR-DIdH4Oia.js} +2 -2
  44. package/dist/{xychartDiagram-PMCCYNJV-HJtnH7o5.js → xychartDiagram-PMCCYNJV-aecZdRhd.js} +1 -1
  45. package/docs/components/data-table-filter-and-lists.md +16 -8
  46. package/docs/components/date-and-time.md +8 -1
  47. package/docs/components/feedback-and-status.md +17 -1
  48. package/docs/components/primitives.md +7 -6
  49. package/docs/components/selection-and-choice.md +2 -0
  50. package/docs/guides/app-shell.md +2 -1
  51. package/docs/previews/date-time-calendar.webp +0 -0
  52. package/docs/previews/date-time-datefield.webp +0 -0
  53. package/docs/previews/date-time-daterangefield.webp +0 -0
  54. package/docs/previews/feedback-status-toast.webp +0 -0
  55. package/docs/roadmap.md +90 -47
  56. package/docs/troubleshooting.md +129 -6
  57. package/lib/components/ui/primitives/label/label.css +14 -21
  58. package/package.json +2 -2
package/docs/roadmap.md CHANGED
@@ -1,15 +1,65 @@
1
1
  # Roadmap
2
2
 
3
3
  What is being worked on, what is likely to follow, and what has been decided
4
- against. This is a direction, not a commitment: the only number on this page is
5
- the version something shipped in.
4
+ against. This is a direction, not a commitment: shipped entries carry the
5
+ version they shipped in, and planned entries may name the major they target.
6
6
 
7
7
  ## Now
8
8
 
9
- Nothing in progress right now; see Next.
9
+ - **Empty and error states that look designed** — `EmptyState` and a new
10
+ `ErrorState` in three sizes (`default`, `compact`, `inline`), with in-house
11
+ inline SVG illustrations or a slot for your own. `ErrorState` is a
12
+ `role="alert"` with a Retry action and collapsible technical details. Every
13
+ built-in empty or error state uses them — the `DataTable` default (your
14
+ `empty` prop still replaces it), `PdfReader`, `CommandEmpty`, the gallery, the
15
+ kanban board and chat. Ships in `@nxgt/material` 3.0, with the rest of the
16
+ redesign.
10
17
 
11
18
  ## Next
12
19
 
20
+ - **One media player, video and audio** — `MediaPlayer`, `MediaPlayerMini`,
21
+ `useMediaPlayer` and `AudioPlayer`, built on the native `<video>` / `<audio>`.
22
+ `StxPlayer` and `VideoPlayer` are removed in `@nxgt/material` 3.0, with a
23
+ migration guide. It plays streams — native HLS, plus `hls.js` with a quality
24
+ menu when you install it (an optional peer, loaded only when a stream needs
25
+ it) — shows chapters and a transcript that follows the playback, a waveform
26
+ for audio, thumbnails on hover (VTT sprites), resumes where the viewer left
27
+ off, and adds gestures, shuffle, a shortcut-help panel and a screen that stays
28
+ awake. It runs embedded at an aspect ratio, fullscreen, floating (a corner
29
+ mini player, and an audio bar that persists across pages) or in a modal
30
+ lightbox. Built for WCAG 2.2 AA and selected AAA criteria: controls at 7:1,
31
+ captions plus a transcript, a full keyboard, screen-reader announcements,
32
+ `aria-valuetext` on sliders, reduced motion and forced colors.
33
+ - **PDFs generated without React** — a framework-free engine on pdfkit replaces
34
+ `@react-pdf/renderer`, so a document renders the same in React, Vue, Node, Bun
35
+ and the browser. One data API, `definePdfDocument`, everywhere; the `Print*`
36
+ JSX components are removed in `@nxgt/material` 3.0. Output is tagged for
37
+ accessibility (PDF/UA), archival (PDF/A-3) and electronic invoicing (Factur-X,
38
+ EN 16931). It also brings the items below.
39
+ - **Your brand on the page** — a print theme whose defaults follow the screen
40
+ theme, and your own font files registered in one call (the library bundles
41
+ no fonts).
42
+ - **QR codes, barcodes and charts in a PDF** — pass the value or the chart
43
+ options and get vector output, no data URL to prepare.
44
+ - **Rich text and Markdown in a PDF** — the rich-text editor's content and
45
+ Markdown rendered as PDF headings, paragraphs, lists, tables and links.
46
+ - **A live preview inside `PdfReader`** — `PdfReader` takes a document and
47
+ renders it itself, refreshing a moment after its data stops changing.
48
+ - **Test helpers for generated PDFs** — read a generated PDF's text, page count
49
+ and metadata in a unit test, without a browser.
50
+ - **Archive-ready PDFs** — PDF/A-3 output through one option, for invoices and
51
+ records that must be kept.
52
+ - **Electronic invoices (Factur-X)** — an invoice PDF that carries its
53
+ machine-readable data (EN 16931), built on archive-ready PDFs and the
54
+ invoice template, for e-invoicing mandates such as France's.
55
+ - **A PDF reader that annotates and fills forms** — pdf.js used directly in
56
+ both libraries (React drops `react-pdf`), with one `PdfReader` whose `mode`
57
+ is `embedded`, `dialog` or `page` and whose toolbar adapts to the width of
58
+ its container. Viewers add their own annotations, held as JSON in
59
+ `value` / `onChange` and exportable into the PDF; fill AcroForms; read two
60
+ pages side by side; scroll continuously or page by page; hear the text read
61
+ aloud; or switch to a reflowed text mode. Annotation export loads `pdf-lib`
62
+ only when used.
13
63
  - **Dark-mode previews** — each component preview also shown in the dark theme,
14
64
  so you can check contrast without running it.
15
65
  - **Previews that cannot go stale** — a CI check that fails when a story changes
@@ -18,6 +68,11 @@ Nothing in progress right now; see Next.
18
68
 
19
69
  ## Later
20
70
 
71
+ - **Next major: an error `Banner` interrupts** — `tone="error"` becomes
72
+ `role="alert"` `aria-live="assertive"`, so a screen reader reads it out at
73
+ once. Every tone is `role="status"` `aria-live="polite"` today; pass
74
+ `role="alert"` for an urgent banner until then. Breaking for a test that
75
+ finds an error `Banner` with `getByRole('status')`.
21
76
  - **Next major: `ListTile`'s `data-slot` becomes `list-tile`** — it is
22
77
  `item-detail` today, so the name matches the component like every other
23
78
  slot. Breaking for a selector or a test that looks for `item-detail`.
@@ -44,23 +99,6 @@ Nothing in progress right now; see Next.
44
99
  - **Ready-made documents** — invoice, quote, receipt, report, certificate and
45
100
  label-sheet templates that take your data and return a document, their labels
46
101
  translated.
47
- - **Your brand on the page** — a print theme whose defaults follow the screen
48
- theme, and your own font files registered in one call (the library bundles
49
- no fonts).
50
- - **QR codes, barcodes and charts in a PDF** — pass the value or the chart
51
- options and get vector output, no data URL to prepare.
52
- - **Rich text and Markdown in a PDF** — the rich-text editor's content and
53
- Markdown rendered as PDF headings, paragraphs, lists, tables and links.
54
- - **A live preview inside `PdfReader`** — `PdfReader` takes a document and
55
- renders it itself, refreshing a moment after its data stops changing. Today
56
- `usePdfFile` and a `PdfReader` given its `url` make the preview by hand.
57
- - **Test helpers for generated PDFs** — read a generated PDF's text, page count
58
- and metadata in a unit test, without a browser.
59
- - **Archive-ready PDFs** — PDF/A output through one option, for invoices and
60
- records that must be kept.
61
- - **Electronic invoices (Factur-X)** — an invoice PDF that carries its
62
- machine-readable data, built on archive-ready PDFs and the invoice template,
63
- for e-invoicing mandates such as France's.
64
102
 
65
103
  ## Not planned
66
104
 
@@ -73,13 +111,17 @@ Nothing in progress right now; see Next.
73
111
  could not upgrade. Browser-only and node-only dependencies are contained in
74
112
  this package, behind their own entry and optional peers or behind
75
113
  `clientOnly`.
76
- - **Print components back on the main entry** — `@react-pdf/renderer` reaches a
77
- dependency that crashes any SSR server that bundles it, on every request.
78
- Print stays on `@nxgt/material/print`, with its dependencies as optional
79
- peers.
80
- - **Right-to-left and complex-script PDFs** — `@react-pdf/renderer` does not
81
- shape Arabic or Hebrew text reliably. Render such a document as HTML and print
82
- it to PDF with a browser on a server, such as Playwright or Gotenberg.
114
+ - **Print components back on the main entry** — the PDF engine reaches
115
+ pdfkit, which needs Node-style dependencies a server bundle should not pull
116
+ in on every request. The PDF engine and `definePdfDocument` stay on
117
+ `@nxgt/material/print`, not the main entry, with their dependencies as
118
+ optional peers. The `Print*` JSX components go away in 3.0.
119
+ - **Right-to-left and complex-script PDFs** — pdfkit does not lay out
120
+ right-to-left text or shape Arabic and Hebrew reliably, so the new engine
121
+ does not promise it. Render such a document as HTML and print it to PDF with
122
+ a browser on a server, such as Playwright or Gotenberg.
123
+ - **Two PDF engines** — pdfkit replaces `@react-pdf/renderer`; they do not
124
+ coexist. One engine and one document model serve both libraries.
83
125
  - **Importing `@nxgt/material/map` without a DOM** — leaflet reads `window` as
84
126
  it is evaluated. An SSR app renders the map client-side, behind `clientOnly`.
85
127
  - **An app shell that builds its own menu** — `items` stays a prop and labels
@@ -159,8 +201,8 @@ Nothing in progress right now; see Next.
159
201
  - **PDFs from a Next.js route handler in one line of config** —
160
202
  `withNxgtPrint(nextConfig)` from `@nxgt/material/next` adds what `/print`
161
203
  needs to `next.config.ts` — the library in `serverExternalPackages`, and
162
- pdfkit's fonts in the `output: 'standalone'` trace wherever your package manager
163
- installed them — merged with your own config. 2.4.0.
204
+ pdfkit's fonts in the `output: 'standalone'` trace wherever your package
205
+ manager installed them — merged with your own config. 2.4.0.
164
206
  ([printing](guides/printing.md#nextjs-serverexternalpackages-and-output-standalone))
165
207
  - **One copy of the framework-free code, shared with `@nxgt/material-vue`** —
166
208
  the PDF document model and renderer, the print formatters, the PDF file
@@ -178,14 +220,15 @@ Nothing in progress right now; see Next.
178
220
  2.2.0. ([forms](guides/forms.md#touched-on-blur))
179
221
  - **Accessible names, ids and focus across the catalogue** — every field is
180
222
  named by its label and says it is required on the element a screen reader
181
- lands on (the input, the picker's `combobox`, the slider thumbs, the
182
- group), with the asterisk drawn by CSS, so in no name or label text; groups are named by their question
183
- and describe their error instead of carrying `aria-invalid` where ARIA does
184
- not allow it; ids are generated rather than taken from a field's `name` or
185
- an option's `value`, so two forms on one page no longer collide; icon
186
- buttons, crumbs, carousel arrows, codes and the camera have names; and a
187
- dialog, sheet or drawer takes focus when it opens and gives it back when it
188
- closes. 2.2.0. ([upgrading](getting-started.md#220-ids-names-and-aria-invalid-moved))
223
+ lands on (the input, the picker's `combobox`, the slider thumbs, the group),
224
+ with the asterisk drawn by CSS, so in no name or label text; groups are named
225
+ by their question and describe their error instead of carrying `aria-invalid`
226
+ where ARIA does not allow it; ids are generated rather than taken from a
227
+ field's `name` or an option's `value`, so two forms on one page no longer
228
+ collide; icon buttons, crumbs, carousel arrows, codes and the camera have
229
+ names; and a dialog, sheet or drawer takes focus when it opens and gives it
230
+ back when it closes. 2.2.0.
231
+ ([upgrading](getting-started.md#220-ids-names-and-aria-invalid-moved))
189
232
  - **PdfReader, turned pages and a steady zoom** — a page the PDF turns
190
233
  (`/Rotate`) shows the right way up, the fit zoom settles once when the
191
234
  document opens instead of moving while you read, `onPageChange` also reports
@@ -197,8 +240,8 @@ Nothing in progress right now; see Next.
197
240
  documents written as plain data (`pdfDocument` and its builders,
198
241
  `definePdfDocument` templates, `dataTable` from column definitions) alongside
199
242
  the JSX components, and `@nxgt/material-vue/pdf` (0.22.0) renders the same
200
- model, so a template renders the same pages in both libraries (experimental: the model
201
- may still change in a minor release). 2.2.0.
243
+ model, so a template renders the same pages in both libraries (experimental:
244
+ the model may still change in a minor release). 2.2.0.
202
245
  ([printing](guides/printing.md#documents-as-data))
203
246
  - **One call to a PDF file** — `downloadPdf` and `openPdf` in the browser (a
204
247
  blocked tab falls back to a download), `usePdfFile` for a preview that keeps
@@ -231,10 +274,10 @@ Nothing in progress right now; see Next.
231
274
  its zoom menu works from the keyboard and in fullscreen. 2.2.0.
232
275
  ([PdfReader](components/media-and-files.md#pdfreader))
233
276
  - **Amounts and dates in your locale** — `formatAmount` and `formatDate` take a
234
- locale and time zone, use each currency's own decimals (`{ fractionDigits: 2 }`
235
- keeps two) and keep a date-only value on its day; `useFormatters()` binds them
236
- to the `I18nProvider` language and `createFormatters()` to a locale you pass,
237
- so a document rendered on a server reads like the screen. 2.2.0.
277
+ locale and time zone, use each currency's own decimals (`{ fractionDigits: 2
278
+ }` keeps two) and keep a date-only value on its day; `useFormatters()` binds
279
+ them to the `I18nProvider` language and `createFormatters()` to a locale you
280
+ pass, so a document rendered on a server reads like the screen. 2.2.0.
238
281
  ([printing](guides/printing.md#formatamount-and-formatdate))
239
282
  - **More built-in labels follow the language** — calendar navigation,
240
283
  the data table's selection checkboxes, filter presets and
@@ -269,10 +312,10 @@ Nothing in progress right now; see Next.
269
312
  server's guess while hydrating. 2.2.0.
270
313
  - **Declaration files that always resolve** — a packed or linked build types
271
314
  `react` like the npm one, instead of as `any`. 2.2.0.
272
- - **Component documentation with previews** — every component has an entry
273
- with a picture of how it renders, an example that compiles and the options
274
- that matter, under the same twelve categories in the README and `docs/`. A forms guide covers React Hook Form, Zod, composite values and
275
- server errors. 2.2.0.
315
+ - **Component documentation with previews** — every component has an entry with
316
+ a picture of how it renders, an example that compiles and the options that
317
+ matter, under the same twelve categories in the README and `docs/`. A forms
318
+ guide covers React Hook Form, Zod, composite values and server errors. 2.2.0.
276
319
  - **`@nxgt/material/map`** — the leaflet map, markers, location field and
277
320
  `useGeolocation` as an entry of this package, with leaflet as optional peers.
278
321
  2.1.0.
@@ -24,6 +24,7 @@ words in your error.
24
24
  - [An accessibility audit reports low contrast on a tonal or outlined button, a selected list tile or a hovered badge link](#an-accessibility-audit-reports-low-contrast-on-a-tonal-or-outlined-button-a-selected-list-tile-or-a-hovered-badge-link)
25
25
  - [An accessibility audit reports low contrast on error text in the dark theme](#an-accessibility-audit-reports-low-contrast-on-error-text-in-the-dark-theme)
26
26
  - [Dialogs, toasts or buttons no longer animate](#dialogs-toasts-or-buttons-no-longer-animate)
27
+ - [A Content-Security-Policy reports a `data:` image on every form with a required field](#a-content-security-policy-reports-a-data-image-on-every-form-with-a-required-field)
27
28
  - [A Button's label overflows a card on a phone](#a-buttons-label-overflows-a-card-on-a-phone)
28
29
 
29
30
  **Feedback**
@@ -33,6 +34,8 @@ words in your error.
33
34
  - [A toast never goes away after a tap](#a-toast-never-goes-away-after-a-tap)
34
35
  - [A toast's action cannot be clicked while a dialog is open](#a-toasts-action-cannot-be-clicked-while-a-dialog-is-open)
35
36
  - [`toasts.success(message, { action })` shows no action button](#toastssuccessmessage--action--shows-no-action-button)
37
+ - [An accessibility audit reports low contrast on a toast's action](#an-accessibility-audit-reports-low-contrast-on-a-toasts-action)
38
+ - [A `Banner` is not announced while a dialog is open](#a-banner-is-not-announced-while-a-dialog-is-open)
36
39
  - [The focus leaves a dialog that a toast's action opened](#the-focus-leaves-a-dialog-that-a-toasts-action-opened)
37
40
  - [A `ConfirmationDialog` reopened with new content blocks clicks](#a-confirmationdialog-reopened-with-new-content-blocks-clicks)
38
41
  - [The first Escape after closing a nested dialog does nothing](#the-first-escape-after-closing-a-nested-dialog-does-nothing)
@@ -40,6 +43,7 @@ words in your error.
40
43
  **Navigation**
41
44
 
42
45
  - [A `ListTile` link reloads the whole page](#a-listtile-link-reloads-the-whole-page)
46
+ - [After a link in the phone menu, the focus goes to `<main>`, not to the page's heading](#after-a-link-in-the-phone-menu-the-focus-goes-to-main-not-to-the-pages-heading)
43
47
  - [The phone menu needs two Escapes to close, or a tooltip shows over it](#the-phone-menu-needs-two-escapes-to-close-or-a-tooltip-shows-over-it)
44
48
  - [`Tabs` look different after upgrading](#tabs-look-different-after-upgrading)
45
49
  - [A screen reader announces an icon-only tab as "tab", with no name](#a-screen-reader-announces-an-icon-only-tab-as-tab-with-no-name)
@@ -99,6 +103,7 @@ words in your error.
99
103
  - [Zod messages ignore the language, or your `material.zod.*` strings](#zod-messages-ignore-the-language-or-your-materialzod-strings)
100
104
  - [A test asserting the old Zod string length text fails](#a-test-asserting-the-old-zod-string-length-text-fails)
101
105
  - [A calendar's weekday row is in English on a French page](#a-calendars-weekday-row-is-in-english-on-a-french-page)
106
+ - [An accessibility audit reports low contrast on a calendar's weekday row or its days](#an-accessibility-audit-reports-low-contrast-on-a-calendars-weekday-row-or-its-days)
102
107
 
103
108
  **Runtime**
104
109
 
@@ -115,6 +120,7 @@ words in your error.
115
120
  - [A `TimeFormField` shows its error on a desktop but not on a phone](#a-timeformfield-shows-its-error-on-a-desktop-but-not-on-a-phone)
116
121
  - [`setError(…, { shouldFocus: true })` or a failed submit does not focus the field](#seterror--shouldfocus-true--or-a-failed-submit-does-not-focus-the-field)
117
122
  - [A test that finds a chip by `aria-selected` or `getByRole('option')` no longer finds it](#a-test-that-finds-a-chip-by-aria-selected-or-getbyroleoption-no-longer-finds-it)
123
+ - [The `ref` of a `SelectChipField` focuses nothing](#the-ref-of-a-selectchipfield-focuses-nothing)
118
124
  - [`touchedFields` stays empty after leaving a field, or `mode: 'onBlur'` never validates](#touchedfields-stays-empty-after-leaving-a-field-or-mode-onblur-never-validates)
119
125
  - [`TypeError: values.map is not a function` (a `SliderFormField`)](#typeerror-valuesmap-is-not-a-function)
120
126
  - [An end-to-end test no longer finds `aria-expanded` on a `ResponsiveButton`](#an-end-to-end-test-no-longer-finds-aria-expanded-on-a-responsivebutton)
@@ -164,6 +170,8 @@ words in your error.
164
170
  - [A `DataTable` shows its header row on a phone](#a-datatable-shows-its-header-row-on-a-phone)
165
171
  - [A test's `getByRole('region')` finds more than one region](#a-tests-getbyroleregion-finds-more-than-one-region)
166
172
  - [A `DataTable` toolbar is gone](#a-datatable-toolbar-is-gone)
173
+ - [axe reports `target-size` on a `DataTable`'s "Select all"](#axe-reports-target-size-on-a-datatables-select-all)
174
+ - [A `DataTable`'s empty state is cut off at its right edge](#a-datatables-empty-state-is-cut-off-at-its-right-edge)
167
175
 
168
176
  **PDF reader**
169
177
 
@@ -453,8 +461,8 @@ is open and leaves it focusable; a control the tab order reaches inside an
453
461
  reports it predates the inert background.
454
462
  **Fix:** upgrade: the page behind a modal layer of this library is `inert`
455
463
  as well — out of the tab order and of the accessibility tree — and the rule
456
- passes. Live regions (the `Toaster`) stay reachable. Do not exclude
457
- `[aria-hidden]` or `[data-aria-hidden]` from the audit: it hides real
464
+ passes. Live regions (the `Toaster`, every `Banner`) stay reachable. Do not
465
+ exclude `[aria-hidden]` or `[data-aria-hidden]` from the audit: it hides real
458
466
  failures. A layer of yours built on Radix directly (not on these
459
467
  components) still marks the page `aria-hidden` only; make the rest of the
460
468
  page inert with core's `inertOthers`, which keeps live regions and counts
@@ -667,6 +675,42 @@ toasts.success({ title: 'Staff member restored', action: { label: 'Send an invit
667
675
  `data` takes no `icon` (a preset draws its own) and no `cancel` (the X button
668
676
  beside the action stands for it).
669
677
 
678
+ ### An accessibility audit reports low contrast on a toast's action
679
+
680
+ **When:** axe (or Lighthouse) reports `color-contrast` on a toast's action
681
+ button (`[data-slot="toast-action"]`), the `{ label }` one: an
682
+ "Undo", a "Send an invitation", or the "Close" a preset shows in its place.
683
+ **Why:** the action is a `tonal` `Button`, whose tones are made for the page,
684
+ and the toast is the inverse of the page (`bg-foreground`: dark in the light
685
+ theme, light in the dark one). Its `primary` label on a 15 % primary tint over
686
+ the toast read 2.5:1 in the light theme and 1.5:1 in the dark one.
687
+ **Fix:** upgrade: the action takes the other theme's tone (the lightened
688
+ primary on the dark toast, `primary` on the light one) on an opaque mix of
689
+ primary toward the toast's surface, 9.4:1 and 5.5:1 at rest, and more hovered,
690
+ focused or pressed. Drop an exclusion of `[data-slot="toast-action"]` from
691
+ your contrast scan. A function action (`(dismiss) => <Button …>`) renders
692
+ your own buttons, which keep the page's tones: give them colours measured on
693
+ the toast, or use the `{ label, onClick }` form.
694
+
695
+ ### A `Banner` is not announced while a dialog is open
696
+
697
+ **When:** a `Banner` (an update notice, a session notice) changes or shows
698
+ while a `Dialog`, `Sheet` or another modal layer is open, and a screen reader
699
+ says nothing; or an `aria-live` you set on it is dropped.
700
+ **Why:** a modal layer makes the page behind it `inert` and keeps only live
701
+ regions (`[aria-live]`) reachable; a `Banner` was a `role="status"` without
702
+ `aria-live`, so it went inert with the page, and it did not forward its props.
703
+ **Fix:** upgrade: every `Banner`, `error` included, is a polite live region
704
+ (`role="status"` `aria-live="polite"` `aria-atomic="true"`), so the layer
705
+ keeps it reachable, and the props you pass (`id`, `data-*`, `aria-*`, `role`)
706
+ go to its root and win: pass `role="alert"` for an urgent banner.
707
+ Drop a `<div aria-live="polite">` wrapper kept for this. The page is made
708
+ inert when the layer opens: a `Banner` mounted later inside a part of the page
709
+ made inert stays inert until the layer closes, so render it before (and change
710
+ its text), or keep it in a live container that is already there. A kept
711
+ `Banner` stays interactive behind the layer: a screen reader's browse mode
712
+ reaches its action and dismiss button (Tab stays in the layer).
713
+
670
714
  ### The focus leaves a dialog that a toast's action opened
671
715
 
672
716
  **When:** a toast's action opens a `Dialog` (or an `AlertDialog`, a `Sheet`,
@@ -791,6 +835,33 @@ const RouterLink: AppShellLinkComponent = ({ href, ...props }) => (
791
835
  <ListTile title="Notifications" href="/settings/notifications" linkComponent={RouterLink} />
792
836
  ```
793
837
 
838
+ ### After a link in the phone menu, the focus goes to `<main>`, not to the page's heading
839
+
840
+ **When:** on a phone, a link followed in the menu sheet of `ActivityLayout` or
841
+ `AppHeader` (or a navigation while it is open) opens a screen that moves the
842
+ focus to its own element as it mounts (an error page's `h1`, a step's title),
843
+ and the focus ends on `<main>` instead.
844
+ **Why:** as the sheet closed after a navigation, it always focused `<main>`,
845
+ over what the new screen had focused.
846
+ **Fix:** update `@nxgt/material`. The sheet now leaves the focus where the screen put it
847
+ inside `<main>`, and focuses `<main>` only when the focus is elsewhere (on the
848
+ body, or still in the closing sheet). A controlled modal layer (the menu
849
+ sheet is one) releases the page from `inert` in the same commit that closes
850
+ it, so the focus your screen moves as it mounts is not refused. Code that gave the focus back
851
+ to the screen's element after the sheet closed can go.
852
+
853
+ ### A Content-Security-Policy reports a `data:` image on every form with a required field
854
+
855
+ **When:** a page with a required field (`Label` with the asterisk, or a field
856
+ of this library with `required`) reports a CSP violation for a
857
+ `data:image/svg+xml` image under a policy whose `img-src` refuses `data:`
858
+ (`img-src 'self'`), and the red asterisk is missing.
859
+ **Why:** the mark was a CSS mask whose image was a `data:` URI.
860
+ **Fix:** update `@nxgt/material`. The mark is now an `aria-hidden` SVG in the label's
861
+ markup, which loads no image. An override of `--label-required-mark` (or a copy
862
+ of the mark served by your app) can go; restyle the mark with
863
+ `[data-slot="label-required-mark"]` (see [Label](components/primitives.md#label)).
864
+
794
865
  ### The phone menu needs two Escapes to close, or a tooltip shows over it
795
866
 
796
867
  **When:** on a phone, the menu sheet of `ActivityLayout` or `AppHeader` opens
@@ -1688,6 +1759,31 @@ z.config({ customError: zodLocaleError({ storageKey: 'lang', translate }) });
1688
1759
  z.config({ customError: zodLocaleError({ language: () => requestLanguage() }) });
1689
1760
  ```
1690
1761
 
1762
+ ### An accessibility audit reports low contrast on a calendar's weekday row or its days
1763
+
1764
+ **When:** axe (or Lighthouse) reports `color-contrast` on the weekday row
1765
+ (`th.rdp-weekday`) of a `Calendar`, `DateField`, `DateRangeField` or
1766
+ `EventCalendar`'s date picker, in the light theme, on the page or in its
1767
+ popover; or on the days of the neighbouring months (`.rdp-outside`, such as
1768
+ 31 August on September's grid), the middle days of a range, or the chosen
1769
+ day.
1770
+ **Why:** the base stylesheet tints every table header (`th`,
1771
+ `bg-primary/15`) and every other row (`bg-primary/5`), and a calendar is a
1772
+ table. Before this fix, the weekday row drew `--muted-foreground` on that
1773
+ header tint (4.4:1), a range's middle days drew it on their `bg-primary/15`,
1774
+ and every day's label was drawn at 70% opacity: 2.9:1 for a neighbouring
1775
+ month's day (they can be picked, so they need 4.5:1), 2.6:1 for a range's
1776
+ middle day and 4.4:1 for the chosen day's near-white label.
1777
+ **Fix:** update. The weekday row and a range's middle days are muted toward
1778
+ the foreground (5.6:1 light, 6.2:1 dark on the popover), the neighbouring
1779
+ months' days keep `--muted-foreground` without the fade (5.1:1 light, 5.6:1
1780
+ dark at least) and the other days are drawn at full strength. Drop an
1781
+ exclusion your accessibility test kept for `.rdp-weekday` or `.rdp-outside`.
1782
+ If the audit still fails, your stylesheet overrides `--muted-foreground`,
1783
+ `--primary` or the table rules, or a `classNames` of your own sets the
1784
+ weekday or outside colour back to `text-muted-foreground`; check it at 4.5:1
1785
+ on `bg-primary/15` over `--popover`.
1786
+
1691
1787
  ### A calendar's weekday row is in English on a French page
1692
1788
 
1693
1789
  **When:** under `<I18nProvider>` set to French, a `DateField`,
@@ -2057,6 +2153,22 @@ const design = within(topics).getByRole('button', { name: 'Design' });
2057
2153
  expect(design).toHaveAttribute('aria-pressed', 'true');
2058
2154
  ```
2059
2155
 
2156
+ ### The `ref` of a `SelectChipField` focuses nothing
2157
+
2158
+ **When:** `ref.current?.focus()` on a `SelectChipField`, or React Hook Form's
2159
+ `setFocus(name)` on a `SelectChipFormField`, leaves focus where it was, or
2160
+ lands on the first chip when one is chosen. Before the release that lists
2161
+ this fix only.
2162
+ **Why:** the `ref` is the `role="group"` element, which a browser cannot
2163
+ focus; the chips are the focusable controls.
2164
+ **Fix:** upgrade: the `ref` now reaches the chosen chip, else the first, as a
2165
+ radio group does (type `Ref<HTMLButtonElement>`). Temporary, until
2166
+ material #502: on an older release, focus the chip yourself.
2167
+
2168
+ ```tsx
2169
+ group.current?.querySelector<HTMLElement>('[aria-pressed="true"], button')?.focus();
2170
+ ```
2171
+
2060
2172
  ### `touchedFields` stays empty after leaving a field, or `mode: 'onBlur'` never validates
2061
2173
 
2062
2174
  **When:** a `*FormField` keeps `formState.touchedFields` (and
@@ -2238,10 +2350,9 @@ await expect(page.getByRole('combobox', { name: 'Delivery date' })).toHaveAttrib
2238
2350
  ```
2239
2351
 
2240
2352
  Custom CSS that targeted the old `<span class="text-error">` should target
2241
- `[data-slot="label-text"][data-required]::after` instead; the recipe is in
2242
- [Label](components/primitives.md#label). The mark is drawn by the package stylesheet, so a
2243
- snapshot of the DOM no longer contains it, and it stays visible in
2244
- forced-colors mode.
2353
+ `[data-slot="label-required-mark"]` instead; the recipe is in
2354
+ [Label](components/primitives.md#label). The mark is an `aria-hidden` SVG with
2355
+ no text, and it stays visible in forced-colors mode.
2245
2356
 
2246
2357
  ### An end-to-end test no longer finds `aria-expanded` on a `ResponsiveButton`
2247
2358
 
@@ -3172,6 +3283,18 @@ page.getByRole('region', { name: 'Invoices' });
3172
3283
  **Why:** the toolbar is rendered only when it has content (yours, or `showViewOptions`): an empty `<fieldset>` is a nameless group to a screen reader.
3173
3284
  **Fix:** look for what the toolbar holds (a filter by its label, the view options by "View"), not for the `fieldset` itself. A component of yours that renders nothing still counts as content: render it conditionally instead.
3174
3285
 
3286
+ ### axe reports `target-size` on a `DataTable`'s "Select all"
3287
+
3288
+ **When:** axe (WCAG 2.5.8) flags the header row's "Select all" checkbox (`thead [data-slot="checkbox-field"]`) when the column after the `select` column is a sortable `DataTableColumnHeader`, mostly on a phone, where the columns are narrowest. The rows' checkboxes pass.
3289
+ **Why:** axe measures the 16px box itself, not its 24px `::before`, so it asks for a 24px circle around the box clear of every other control. The sort button reaches 12px back into its cell's padding (`-ml-3`), and the `select` cell had no padding after its checkbox: the circle met the button.
3290
+ **Fix:** upgrade: the `select` column's checkboxes now keep a 12px margin after them. Drop any axe `exclude` of `thead [data-slot="checkbox-field"]` you kept for it. A `select` column of your own (not `predefinedColumns().select`) needs the same: `me-3` on its checkboxes.
3291
+
3292
+ ### A `DataTable`'s empty state is cut off at its right edge
3293
+
3294
+ **When:** a custom `empty` as wide as its cell (a paragraph, a `w-full` box) on a phone: its last 4px sit under the edge of the table's scroll box, and look cut off.
3295
+ **Why:** the empty state was sized to the box's visible width (`--table-visible-width`) but drawn inside the table container's 4px padding, so it stuck out by that padding.
3296
+ **Fix:** upgrade: the empty state now takes the padding off its width and sticks 4px in from the box's edge. Nothing to change in your `empty`.
3297
+
3175
3298
  ---
3176
3299
 
3177
3300
  ---
@@ -1,33 +1,26 @@
1
- /* The required mark of `Label withAsterisk`. Drawn, not typed: a "*" in the
2
- * label's text joined its name ("Email *") for `getByLabel(…, { exact })`,
3
- * `getByLabelText` and screen readers, and generated text (`content: "*"`)
4
- * joins the accessible name too. An empty `content` painted by a mask adds
5
- * nothing to the name or the text. The field's own `required` /
6
- * `aria-required` is what is announced. Restyle it from your own CSS:
7
- * `background-color` for the colour, `width` / `height` for the size. */
8
- [data-slot="label-text"][data-required]::after {
9
- content: "";
1
+ /* The required mark of `Label withAsterisk`: an `aria-hidden` inline SVG in
2
+ * the label's markup, not a "*" in its text. A typed "*" joined the label's
3
+ * name ("Email *") for `getByLabel(…, { exact })`, `getByLabelText` and
4
+ * screen readers, and so does generated text (`content: "*"`). The field's
5
+ * own `required` / `aria-required` is what is announced. Drawn by the markup,
6
+ * it loads no image: a Content-Security-Policy whose `img-src` refuses
7
+ * `data:` draws it too. Restyle it from your own CSS: `color` for the colour,
8
+ * `width` / `height` for the size. */
9
+ [data-slot="label-required-mark"] {
10
10
  display: inline-block;
11
11
  flex: none;
12
12
  align-self: flex-start;
13
13
  width: 0.5em;
14
14
  height: 0.5em;
15
15
  margin-block-start: 0.05em;
16
- background-color: var(--color-error);
17
- --label-required-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M5 1v8M1.5 3l7 4M8.5 3l-7 4' stroke='black' stroke-width='1.75' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
18
- mask-image: var(--label-required-mark);
19
- mask-size: contain;
20
- mask-repeat: no-repeat;
21
- mask-position: center;
22
- /* A browser's print stylesheet drops backgrounds; this one is the mark. */
23
- print-color-adjust: exact;
24
- -webkit-print-color-adjust: exact;
16
+ color: var(--color-error);
17
+ overflow: visible;
25
18
  }
26
19
 
27
- /* Forced colours replace background-color with the system palette's own;
20
+ /* Forced colours keep an SVG's `currentColor` stroke as authored;
28
21
  * CanvasText keeps the mark visible on any theme. */
29
22
  @media (forced-colors: active) {
30
- [data-slot="label-text"][data-required]::after {
31
- background-color: CanvasText;
23
+ [data-slot="label-required-mark"] {
24
+ color: CanvasText;
32
25
  }
33
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "2.10.1",
3
+ "version": "2.12.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"
@@ -114,7 +114,7 @@
114
114
  "docs:previews": "vitest run --project previews"
115
115
  },
116
116
  "dependencies": {
117
- "@nxgt/material-core": "^0.7.0",
117
+ "@nxgt/material-core": "^0.7.1",
118
118
  "@base-ui/react": "^1.6.0",
119
119
  "@dnd-kit/core": "^6.3.1",
120
120
  "@dnd-kit/sortable": "^10.0.0",