@nxgt/material 3.70.0 → 3.72.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 (151) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +9 -6
  3. package/dist/{alert-dialog-CBkWG32g.js → alert-dialog-A8K8zai1.js} +1 -1
  4. package/dist/{async-slice-CNZ3N55P.js → async-slice-B8933IHY.js} +1 -1
  5. package/dist/{button-D3qzqDY_.js → button-C8moFnIT.js} +1 -1
  6. package/dist/charts.js +3 -3
  7. package/dist/{chip-CMTe4sQd.js → chip-B6AY1E91.js} +2 -2
  8. package/dist/{code-editor-Cs7mz1J-.js → code-editor-DQZIKL54.js} +5 -5
  9. package/dist/code-editor.js +5 -5
  10. package/dist/{code-language-items-D_QMgjVy.js → code-language-items-8VL0aMKG.js} +2 -2
  11. package/dist/components/ui/collections/server-table/server-table-phone-row.css +6 -0
  12. package/dist/components/ui/display/code/code-moonlight.css +37 -0
  13. package/dist/components/ui/fields/attributes/attribute-values-form/attribute-values-form.css +5 -0
  14. package/dist/{components-BbWcozcj.js → components-EHdmrvV4.js} +20862 -19924
  15. package/dist/components.js +28 -28
  16. package/dist/{dialog-B3BSdRci.js → dialog-WuLTwxkr.js} +5 -5
  17. package/dist/dnd.js +1 -1
  18. package/dist/{dropdown-menu-BaupOWgS.js → dropdown-menu-tadySeGH.js} +1 -1
  19. package/dist/editor.js +35 -35
  20. package/dist/{empty-state-B2Um5UpI.js → empty-state-ZYiFSSBQ.js} +2 -2
  21. package/dist/{field-blur-1XLyxRPr.js → field-blur-BYR0yE5L.js} +1 -1
  22. package/dist/{field-blur-context-XNvCDnMT.js → field-blur-context-bEhByoK-.js} +1 -1
  23. package/dist/{hooks-BU21Jpaa.js → hooks-GpwD8iDu.js} +2 -2
  24. package/dist/hooks.js +3 -3
  25. package/dist/{hover-card-Bvm3svX9.js → hover-card-BowaI_ra.js} +1 -1
  26. package/dist/i18n.js +3 -3
  27. package/dist/{icon-badge-wip9bfa2.js → icon-badge-B1B0Lg02.js} +3 -3
  28. package/dist/{icon-button-QwIeBTdw.js → icon-button-BbGU17cb.js} +1 -1
  29. package/dist/lib/components/ui/actions/fab/fab-dock-inset.d.ts +29 -0
  30. package/dist/lib/components/ui/actions/fab/fab-dock.d.ts +6 -1
  31. package/dist/lib/components/ui/collections/data-table/select-checkbox.d.ts +3 -2
  32. package/dist/lib/components/ui/collections/filter/use-filter-field-config.d.ts +3 -0
  33. package/dist/lib/components/ui/collections/index.d.ts +1 -0
  34. package/dist/lib/components/ui/collections/record-actions-menu/index.d.ts +2 -0
  35. package/dist/lib/components/ui/collections/record-actions-menu/record-actions-dialogs.d.ts +15 -0
  36. package/dist/lib/components/ui/collections/record-actions-menu/record-actions-menu.d.ts +72 -0
  37. package/dist/lib/components/ui/collections/record-actions-menu/record-actions-rules.d.ts +68 -0
  38. package/dist/lib/components/ui/collections/record-actions-menu/record-actions-words.d.ts +52 -0
  39. package/dist/lib/components/ui/collections/record-actions-menu/use-record-actions.d.ts +51 -0
  40. package/dist/lib/components/ui/collections/record-actions-menu/use-record-flight.d.ts +16 -0
  41. package/dist/lib/components/ui/collections/row-menu/row-menu-column.d.ts +4 -0
  42. package/dist/lib/components/ui/collections/row-menu/row-menu.d.ts +1 -1
  43. package/dist/lib/components/ui/collections/server-table/partials/server-table-band-classes.d.ts +7 -5
  44. package/dist/lib/components/ui/collections/server-table/partials/server-table-column-sorting.d.ts +7 -0
  45. package/dist/lib/components/ui/collections/server-table/types.d.ts +3 -1
  46. package/dist/lib/components/ui/display/markdown/markdown.d.ts +12 -1
  47. package/dist/lib/components/ui/display/markdown/markdown.utils.d.ts +2 -0
  48. package/dist/lib/components/ui/display/markdown/mermaid-csp-scope.d.ts +8 -0
  49. package/dist/lib/components/ui/display/markdown/mermaid-markup.d.ts +20 -0
  50. package/dist/lib/components/ui/display/markdown/partials/markdown-code.d.ts +5 -1
  51. package/dist/lib/components/ui/feedback/confirm-dialog/action-refusal.d.ts +24 -0
  52. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-alert.d.ts +17 -0
  53. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-description.d.ts +10 -0
  54. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog.d.ts +24 -2
  55. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-refusal.d.ts +7 -0
  56. package/dist/lib/components/ui/feedback/confirm-dialog/index.d.ts +1 -0
  57. package/dist/lib/components/ui/feedback/confirm-dialog/use-confirm-action.d.ts +6 -0
  58. package/dist/lib/components/ui/feedback/index.d.ts +1 -0
  59. package/dist/lib/components/ui/feedback/stale-notice/index.d.ts +1 -0
  60. package/dist/lib/components/ui/feedback/stale-notice/stale-notice.d.ts +15 -0
  61. package/dist/lib/components/ui/fields/attributes/attribute-values-form-field-set/attribute-values-form-field-set.d.ts +5 -1
  62. package/dist/lib/components/ui/fields/contacts/contact-dialog.d.ts +8 -2
  63. package/dist/lib/components/ui/fields/contacts/contacts-dialog-form-field.d.ts +4 -1
  64. package/dist/lib/components/ui/fields/contacts/contacts-editor.d.ts +15 -2
  65. package/dist/lib/components/ui/fields/contacts/contacts-form-field.d.ts +6 -1
  66. package/dist/lib/components/ui/fields/contacts/logic/offered-types.d.ts +3 -0
  67. package/dist/lib/components/ui/fields/contacts/models.d.ts +2 -0
  68. package/dist/lib/components/ui/fields/contacts/partials/contact-fields.d.ts +4 -2
  69. package/dist/lib/components/ui/fields/contacts/partials/contact-groups.d.ts +4 -2
  70. package/dist/lib/components/ui/fields/contacts/partials/contact-row.d.ts +4 -2
  71. package/dist/lib/components/ui/fields/contacts/partials/phone-sheet.d.ts +4 -2
  72. package/dist/lib/components/ui/fields/contacts/partials/quick-add.d.ts +4 -2
  73. package/dist/lib/components/ui/fields/contacts/partials/row-actions.d.ts +3 -1
  74. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  75. package/dist/lib/components/ui/forms/index.d.ts +1 -0
  76. package/dist/lib/components/ui/forms/use-server-field-errors.d.ts +21 -0
  77. package/dist/lib/components/ui/layout/index.d.ts +1 -0
  78. package/dist/lib/components/ui/layout/record-page/index.d.ts +2 -0
  79. package/dist/lib/components/ui/layout/record-page/record-details.d.ts +40 -0
  80. package/dist/lib/components/ui/layout/record-page/record-page.d.ts +27 -0
  81. package/dist/lib/components/ui/media/upload/network-upload-field/network-upload-field.d.ts +5 -0
  82. package/dist/lib/components/ui/media/upload/network-upload-field/partials/network-upload-field-items.d.ts +2 -1
  83. package/dist/lib/components/ui/media/upload/network-upload-field/upload-constraints.d.ts +10 -2
  84. package/dist/lib/components/ui/overlays/dialog/dialog-body-overflow.d.ts +7 -0
  85. package/dist/lib/components/ui/overlays/dialog/dialog-body.d.ts +10 -0
  86. package/dist/lib/components/ui/overlays/dialog/dialog-variants.d.ts +4 -0
  87. package/dist/lib/components/ui/overlays/dialog/dialog.d.ts +1 -0
  88. package/dist/lib/components/ui/widgets/chat/chat-conversation-item.d.ts +8 -2
  89. package/dist/lib/components/ui/widgets/chat/chat-markdown.d.ts +4 -0
  90. package/dist/lib/components/ui/widgets/chat/chat-message-list.d.ts +8 -1
  91. package/dist/lib/components/ui/widgets/chat/index.d.ts +2 -0
  92. package/dist/lib/components/ui/widgets/chat/partials/chat-conversation-item-text.d.ts +2 -1
  93. package/dist/lib/components/ui/widgets/chat/partials/chat-empty-thread.d.ts +2 -0
  94. package/dist/lib/components/ui/widgets/chat/proposal/chat-action-proposal-parts.d.ts +24 -0
  95. package/dist/lib/components/ui/widgets/chat/proposal/chat-action-proposal.d.ts +39 -0
  96. package/dist/lib/components/ui/widgets/chat/proposal/chat-action-proposal.rules.d.ts +17 -0
  97. package/dist/lib/components/ui/widgets/chat/proposal/chat-tool-activity.d.ts +16 -0
  98. package/dist/lib/i18n/resources/en/en.d.ts +47 -0
  99. package/dist/lib/i18n/resources/en/material.json.d.ts +47 -0
  100. package/dist/lib/i18n/resources/fr/fr.d.ts +47 -0
  101. package/dist/lib/i18n/resources/fr/material.json.d.ts +47 -0
  102. package/dist/lib/i18n/resources/resources.d.ts +94 -0
  103. package/dist/lib/i18n/translator.d.ts +1 -1
  104. package/dist/lib/print/output/to-pdf-element.d.ts +6 -0
  105. package/dist/lib.js +1 -1
  106. package/dist/main.js +42 -42
  107. package/dist/map.js +8 -8
  108. package/dist/material.css +1 -1
  109. package/dist/pdf-reader.js +1 -1
  110. package/dist/print.js +135 -131
  111. package/dist/{public-BuGgOtAl.js → public-CFj2qn7l.js} +9 -9
  112. package/dist/{rich-code-block-DdHqzzek.js → rich-code-block-B7Teg7-e.js} +5 -5
  113. package/dist/{sortable-provider-DMyaSfZZ.js → sortable-provider-y5sFhRDj.js} +2 -2
  114. package/dist/{spinner-ByYdcukU.js → spinner-C5Kb6MWu.js} +1 -1
  115. package/dist/theme-picker.js +2 -2
  116. package/dist/{theme-toggle-DB3aZZnO.js → theme-toggle-B1JZg7mU.js} +1 -1
  117. package/dist/{toolbar-items-DbzIKjo7.js → toolbar-items-m_1FTFPQ.js} +3 -3
  118. package/dist/{translate-material-Dh7MYkHT.js → translate-material-D1ndrd7a.js} +1 -1
  119. package/dist/{translator-Byhohi1S.js → translator-CtEC_AQa.js} +94 -0
  120. package/dist/{typography-rUf0mE5a.js → typography-prQHb3gI.js} +1 -1
  121. package/dist/{use-chat-composer-uUUTRB3Z.js → use-chat-composer-DBiyNXvL.js} +107 -107
  122. package/dist/{use-heading-spy-CVzW2NM3.js → use-heading-spy-Bzj5mIUZ.js} +179 -87
  123. package/dist/{use-material-translation-BAYyZOcH.js → use-material-translation-C6H0B4kE.js} +1 -1
  124. package/dist/{use-return-focus-CQAuk9kh.js → use-return-focus-CkUX1HFl.js} +1 -1
  125. package/docs/components/buttons-and-actions.md +24 -0
  126. package/docs/components/data-display.md +33 -0
  127. package/docs/components/data-table-filter-and-lists.md +117 -5
  128. package/docs/components/feedback-and-status.md +58 -2
  129. package/docs/components/form-fields.md +63 -3
  130. package/docs/components/media-and-files.md +4 -1
  131. package/docs/components/navigation-and-layout.md +58 -0
  132. package/docs/components/overlays-and-menus.md +20 -1
  133. package/docs/components/rich-widgets.md +35 -2
  134. package/docs/guides/cards-and-tiles.md +4 -2
  135. package/docs/guides/csp.md +44 -6
  136. package/docs/guides/forms.md +59 -7
  137. package/docs/guides/i18n.md +7 -6
  138. package/docs/guides/printing.md +17 -7
  139. package/docs/previews/data-table-filter-lists-recordactionsmenu.webp +0 -0
  140. package/docs/previews/feedback-status-stalenotice.webp +0 -0
  141. package/docs/previews/media-files-networkuploadfield.webp +0 -0
  142. package/docs/previews/media-files-s3uploadfield.webp +0 -0
  143. package/docs/previews/media-files-uploadfield.webp +0 -0
  144. package/docs/previews/navigation-layout-recordpage.webp +0 -0
  145. package/docs/previews/rich-widgets-chat-action-proposal.webp +0 -0
  146. package/docs/roadmap.md +11 -1
  147. package/docs/troubleshooting.md +126 -8
  148. package/lib/components/ui/collections/server-table/server-table-phone-row.css +6 -0
  149. package/lib/components/ui/display/code/code-moonlight.css +37 -0
  150. package/lib/components/ui/fields/attributes/attribute-values-form/attribute-values-form.css +5 -0
  151. package/package.json +3 -2
@@ -28,7 +28,7 @@ Content-Security-Policy:
28
28
  | `style-src-elem 'self' 'nonce-X'` | Your stylesheet and the `<style>` elements that carry the nonce. No component needs `'unsafe-inline'` here, apart from the [known gaps](#known-gaps): see [the `<style>` elements the components inject](#style-elements-the-components-inject) |
29
29
  | `style-src-attr 'unsafe-inline'` | The `style="…"` attributes in the server-rendered HTML and those set through `setAttribute('style', …)`. CSP blocks only these two; `el.style.x = …` and `style.setProperty` are not blocked. An attribute cannot carry a nonce, so this is the only way to allow it. It does not allow `<style>` elements or scripts |
30
30
  | `font-src 'self'` | The library ships no web font (host Inter yourself: [Inter / the sans font](#inter--the-sans-font)), and KaTeX's fonts are files, never `data:` URIs: see [KaTeX fonts](#katex-fonts) for the one Vite setting the Tailwind route needs |
31
- | `img-src 'self' data: blob:` | `data:` for the cropper's output, a captured `Camera` photo, a `Markdown` mermaid diagram and the PDF reader's annotation icons (SVG `data:` images; the icons the components draw are inline `<svg>` elements and need none); `blob:` for previews of a file the user picked. Drop what you do not use |
31
+ | `img-src 'self' data: blob:` | `data:` for the cropper's output, a captured `Camera` photo, and the PDF reader's annotation icons (SVG `data:` images; the icons the components draw are inline `<svg>` elements and need none); `blob:` for previews of a file the user picked and for a `Markdown` mermaid diagram (an SVG image from a `blob:` URL). Drop what you do not use |
32
32
  | `worker-src 'self'` | The pdf.js worker of `PdfReader`: the build ships it as a file, `dist/pdf.worker.min.mjs`, referenced relative to the module that loads it, so `'self'` is enough with no setting and no `data:`. `configurePdfReader({ workerSrc })` overrides it ([PDF reader guide](pdf-reader.md#content-security-policy)). Monaco: see below |
33
33
  | `connect-src 'self'` | `PdfReader` fetches the PDF from its `url` and its pdf.js assets from `assetsUrl`: add those origins when they are not yours |
34
34
 
@@ -38,7 +38,7 @@ Components outside this table need nothing more. Measured per component:
38
38
  | --- | --- | --- |
39
39
  | `PdfReader` | `worker-src 'self'` (the worker is a file of the package), `connect-src` for the PDF's and the assets' origins, and `script-src 'wasm-unsafe-eval'` for the JPEG 2000 and ICC WebAssembly decoders (without it: a JavaScript fallback for JPEG 2000, device colours for ICC profiles) | worker checked in the same Playwright run (Vite production build under `worker-src 'self'`), the rest read from pdf.js's code: [PDF reader guide](pdf-reader.md#content-security-policy) |
40
40
  | `CodeEditor`, `CodeDiff` (Monaco) | `@monaco-editor/react` loads Monaco from `https://cdn.jsdelivr.net/npm/monaco-editor@…/min/vs` by default: that host in `style-src-elem` (Monaco's CSS) and `font-src` (its icon font), plus `worker-src blob:` | not yet verified in a browser. Safe default: allow `https://cdn.jsdelivr.net` in `script-src`, `style-src-elem` and `font-src`, and `worker-src blob:`; or stay on `'self'` by [serving Monaco yourself](#monaco-on-self) |
41
- | `Markdown` with a `mermaid` block | mermaid is imported lazily and runs with `securityLevel: 'strict'` | whether mermaid needs `'unsafe-eval'` is not yet verified. Safe default: keep `'unsafe-eval'` out and report any violation you see; its SVG carries a `<style>` element, see [gaps](#known-gaps) |
41
+ | `Markdown` (maths, mermaid, code) | nothing beyond the policy above, in a client render | checked in Chromium under that policy with `style-src-attr 'none'`, no `img-src data:` and no `'unsafe-eval'`: [Markdown under a strict CSP](#markdown-katex-mermaid-and-code-under-a-strict-csp) |
42
42
  | `RichTextViewer` (`@nxgt/material/editor/viewer`) | none, but it emits `style` attributes: a text colour, a highlight, a text alignment or a table cell's background in the saved document becomes an inline `style` (only allow-listed properties with plain values survive), so a page that shows such a document needs `style-src-attr 'unsafe-inline'`. A document with none sends no style | measured by reading the renderers; a strict `style-src-attr 'none'` page shows the document without those colours |
43
43
  | `Camera` | none: `getUserMedia` is not governed by CSP; the captured photo is a `data:` image | `media-src` for the live preview is not yet verified. Safe default: `media-src 'self' blob:` (a `srcObject` stream usually needs nothing) |
44
44
  | `MediaPlayer` with `hlsStreaming()` | `connect-src` and `media-src blob:` for the stream; `worker-src blob:` only with `config: { enableWorker: true }` (the adapter defaults to no worker) | see the [streaming guide](media-streaming.md#content-security-policy) |
@@ -201,8 +201,8 @@ instead.
201
201
  `style-src-elem 'self' 'nonce-X'` is enough for every component below: your
202
202
  stylesheet carries the CSS the dependencies used to inject, and under a
203
203
  `NonceProvider` the few `<style>` elements left carry the nonce. No route
204
- needs `'unsafe-inline'` in `style-src-elem` for them; mermaid and pdf.js's
205
- font fallback are the [known gaps](#known-gaps).
204
+ needs `'unsafe-inline'` in `style-src-elem` for them; the mindmap diagrams of
205
+ mermaid and pdf.js's font fallback are the [known gaps](#known-gaps).
206
206
 
207
207
  | Component | What it needs | How |
208
208
  | --- | --- | --- |
@@ -261,12 +261,50 @@ when the page carries it nowhere. `setCspNonce` does nothing on a server (no
261
261
  `document`), so a request's nonce never lands in a module global there: see
262
262
  [SSR](#ssr).
263
263
 
264
+ ### Markdown (KaTeX, mermaid and code) under a strict CSP
265
+
266
+ The full `Markdown` (and `ChatMarkdown`) needs no relaxed directive: no
267
+ `'unsafe-inline'`, no `'unsafe-eval'`, no `style-src-attr`, no `img-src data:`.
268
+ Only `img-src blob:` (already in the policy above) for the diagrams. The
269
+ `Data display/Markdown > StrictCsp` story plays maths, two flowcharts (one with
270
+ an `<img>` in a label, removed before the render, since mermaid would load it in the page) and a code block in a document served
271
+ `script-src 'self' 'nonce-X'` (no `'unsafe-eval'`),
272
+ `style-src-elem 'self' 'nonce-X'`, `style-src-attr 'none'`,
273
+ `img-src 'self' blob:` and `font-src 'self'`, and expects not one violation.
274
+ What the app does: wrap the app in [`NonceProvider`](#nonceprovider) (or let
275
+ the page carry the nonce in its meta tag).
276
+
277
+ - **Code blocks**: the scheme's colours are classes of the stylesheet
278
+ (`data-scheme`), and `maxHeight` is a custom property written through the
279
+ CSSOM in a layout effect (a server-rendered block is full height until
280
+ hydration).
281
+ - **KaTeX**: on a client render React writes its styles through the CSSOM.
282
+ Its fonts are files ([KaTeX fonts](#katex-fonts)).
283
+ - **Mermaid**: the diagram stays an image, now from a `blob:` URL (revoked when
284
+ the diagram goes), so the SVG's own `<style>` and `style` attributes live in
285
+ the image and the page needs neither `data:` nor `style-src-attr` for them.
286
+ While a diagram renders, mermaid builds it in a temporary element of the
287
+ page and sanitises it with DOMPurify, which a strict policy reports. For
288
+ that render only, and only for the elements of that diagram, a short-lived
289
+ scope gives mermaid's `<style>` the nonce, writes its `style` attributes
290
+ through the CSSOM and parks the `style` attributes of the string DOMPurify
291
+ parses (patches of `Document.prototype.createElement`,
292
+ `Element.prototype.setAttribute` and `DOMParser.prototype.parseFromString`;
293
+ each is a pass-through once the last render ends and is restored only if
294
+ no other script patched it since). Unrelated calls, such as an editor
295
+ parsing HTML, are left alone.
296
+ - **Left**: a page whose HTML the server renders with math carries KaTeX's
297
+ `style="…"` attributes in the markup, which `style-src-attr 'none'` blocks
298
+ (the formula loses its spacing until `style-src-attr 'unsafe-inline'`, or a
299
+ client-only render of the `Markdown`). Mermaid's mindmap and architecture
300
+ diagrams add a `<style>` to `<head>` ([known gaps](#known-gaps)).
301
+
264
302
  ### Known gaps
265
303
 
266
304
  Not fixable by configuration yet, and still reported by a strict policy:
267
305
 
268
- - the `<style>` element inside mermaid's SVG, and the one cytoscape adds to
269
- `<head>` for mermaid's mindmap and architecture diagrams;
306
+ - the `<style>` element cytoscape adds to `<head>` for mermaid's mindmap and
307
+ architecture diagrams (flowcharts, sequences and the like add none);
270
308
  - the font fallback of pdf.js, when a PDF font is not embedded;
271
309
  - this repository's Storybook, or an app that imports react-use's barrel
272
310
  itself on a dev server: react-use served as one module, whose `useCss`
@@ -279,10 +279,11 @@ the form, on a translucent, blurred background, so Cancel and Save stay in
279
279
  reach while the fields scroll under them; a form shorter than the screen
280
280
  shows them right under its fields.
281
281
 
282
- - In a `DialogContent` or a `SheetContent`, which are capped to the screen
282
+ - In a `DialogContent` without a `DialogBody`, or a `SheetContent`, which are capped to the screen
283
283
  and scroll themselves, the actions stick to the dialog's or the sheet's
284
284
  bottom. A `DrawerContent` does not scroll: a form that is its direct child
285
285
  is the scroll box there.
286
+ - A long form in a `Dialog` can pin its own header and `DialogFooter` instead: put the fields in a `DialogBody` and only the body scrolls (see the Dialog section of the overlays page). Do not combine it with `form-actions`: they would stick inside the body, so use a `DialogFooter` for the buttons and `form="id"` on the submit button.
286
287
  - In an `AlertDialogContent` (the rich text editor's edit dialogs use one),
287
288
  the actions stick to its bottom the same way, over its padding.
288
289
  - `ConfirmationDialog` holds a form when you pass it `formOptions` (a schema, its `defaultValues` and `renderForm`): it renders the
@@ -608,7 +609,7 @@ export function SupplierForm({ onSave }: { onSave: (values: Values) => void }) {
608
609
 
609
610
  | Wrapper | Value | Notes |
610
611
  | --- | --- | --- |
611
- | `ContactsFormField` | `Contact[]` | Edited in place by default (one `primary` per type); `editing="dialog"` goes through `ContactDialog`. `ContactType` is `EMAIL`, `FAX`, `MOBILE`, `PHONE`, `WEBSITE` |
612
+ | `ContactsFormField` | `Contact[]` | Edited in place by default (one `primary` per type); `editing="dialog"` goes through `ContactDialog`. `ContactType` is `EMAIL`, `FAX`, `MOBILE`, `PHONE`, `WEBSITE`; `types={['EMAIL', 'PHONE']}` limits the offered types and `primary={false}` hides the star (inline only) |
612
613
  | `OpeningHoursFormField` | `OpeningHour[]` | Edits the week inline by default (`weekStartsOn` and `locale` apply there); `editing="dialog"` keeps the list and dialog, where `showAddAllDays` adds Monday–Friday 09:00–17:00 and a closed weekend in one click |
613
614
  | `PostalAddressFormField` | `PostalAddressFieldValue` | Deleting the address writes `{}`, not `null` |
614
615
  | `AttributesFormField` | `Attribute[]` | A typed attribute editor, in place by default (rows that open, drag and keyboard reorder that sets `priority`, unique names); `editing="dialog"` keeps the list and dialog. Its `label` prop is required |
@@ -628,7 +629,11 @@ export function SupplierForm({ onSave }: { onSave: (values: Values) => void }) {
628
629
  `form.setValue()` replace the list it shows.
629
630
  - `AttributeValuesFormFieldSet` is the other half: given `attributes`, it
630
631
  renders one input per attribute and writes `{ [attribute.name]: value }` to
631
- its `name`.
632
+ its `name`. It reads the value the form already holds at `name` (and shows
633
+ a `reset()` or `setValue()`), so `defaultValues` is only the fallback while
634
+ the form value is empty (`undefined`, `null` or `{}`). Its fields fill the row, and `hideLabel` keeps
635
+ `label` as the group's accessible name without drawing it, under a
636
+ `FormSection` that already has the title.
632
637
 
633
638
  ### Rows a user adds and removes
634
639
 
@@ -786,8 +791,9 @@ form.setFocus('email'); // or without an error
786
791
 
787
792
  ## A form that is quick to fill
788
793
 
789
- `useFastForm` and `useFocusOnRefusal` (`@nxgt/material/components`) are the
790
- two habits of a record form, in one place. `useFastForm(props, first, active?)`
794
+ `useFastForm`, `useFocusOnRefusal` and `useServerFieldErrors`
795
+ (`@nxgt/material/components`) are the three habits of a record form, in one
796
+ place; the first two are below, the third under [Server errors](#server-errors). `useFastForm(props, first, active?)`
791
797
  is `useForm` with `mode: 'onTouched'` (an error shows once the user leaves the
792
798
  field, then follows each change; a `mode` in `props` wins) that also focuses
793
799
  the `first` field when the form opens, so typing starts at once. It waits a
@@ -821,8 +827,24 @@ Storybook: `Guides/Forms/useFastForm`. If the focus does not move, see
821
827
 
822
828
  ## Server errors
823
829
 
824
- Map an API's field errors onto the form with `setError`, and put anything that
825
- belongs to no field under `root`, shown in an `Alert`:
830
+ `useServerFieldErrors(form, fieldErrors)` (`@nxgt/material/components`) puts an
831
+ action's `{ path: message }` field errors on the form, each under its field
832
+ with `type: 'server'`, and focuses the first. It runs when the `fieldErrors`
833
+ object changes, so pass the answer's own object (one per answer) and
834
+ `undefined` while a request runs or when the answer is not a refusal; entries
835
+ with an empty message are skipped, and the keys must be names of the form's
836
+ fields.
837
+
838
+ ```tsx
839
+ import { useFastForm, useServerFieldErrors } from '@nxgt/material/components';
840
+
841
+ const form = useFastForm<Values>({ defaultValues }, 'email');
842
+ useServerFieldErrors(form, actionData?.fieldErrors); // { email: 'Already used' }
843
+ ```
844
+
845
+ It pairs with `useFastForm` and `useFocusOnRefusal` above. To write the same
846
+ mapping by hand, or to put anything that belongs to no field under `root`,
847
+ shown in an `Alert`, use `setError`:
826
848
 
827
849
  ```tsx
828
850
  import { useForm } from 'react-hook-form';
@@ -870,6 +892,36 @@ export function SignInForm() {
870
892
  - `Alert` `variant` is one of `primary`, `secondary`, `error`, `success`,
871
893
  `info`, `warning`, `foreground`.
872
894
 
895
+ ### A save refused because another write came first
896
+
897
+ When the API refuses a versioned save (`stale-version`), keep the form as it is
898
+ and show a `StaleNotice` above it. It takes the focus and scrolls into view on
899
+ mount, and its one action reads the record again. There is no overwrite.
900
+
901
+ ```tsx
902
+ import type { FormEventHandler } from 'react';
903
+ import type { UseFormReturn } from 'react-hook-form';
904
+ import { Button, StaleNotice, TextFormField } from '@nxgt/material/components';
905
+
906
+ type Props = {
907
+ form: UseFormReturn<{ title: string }>;
908
+ stale: boolean; // the save was refused with `stale-version`
909
+ reload: () => void; // read the record again
910
+ reloading: boolean;
911
+ onSubmit: FormEventHandler<HTMLFormElement>;
912
+ };
913
+
914
+ function EditNote({ form, stale, reload, reloading, onSubmit }: Props) {
915
+ return (
916
+ <form onSubmit={onSubmit} className="grid gap-4">
917
+ {stale && <StaleNotice onReload={reload} busy={reloading} />}
918
+ <TextFormField control={form.control} name="title" label="Title" />
919
+ <Button type="submit">Save</Button>
920
+ </form>
921
+ );
922
+ }
923
+ ```
924
+
873
925
  ## Dialogs and steps
874
926
 
875
927
  ### A form in a dialog
@@ -236,7 +236,7 @@ the camera's button, toggle and image (`material.camera.capture`, `captured`,
236
236
  barcode accessible names, the calendar's navigation tooltips
237
237
  (`material.calendar.*`), the data table's selection checkboxes, the filter's
238
238
  presets and `Min`/`Max` fields, the `variant="4"` spinner's label,
239
- `ScrollToTop`, the network upload field's `Retry`/`Remove`, the map's
239
+ `ScrollToTop`, the network upload field's `Retry` and `Remove {name}`, the map's
240
240
  `You are here` and zoom buttons, and the icon-only close button of
241
241
  `DialogClose`, `DrawerClose`, `SheetClose` and `SheetContent`'s
242
242
  `withCloseButton`. A range `Slider`'s readout inputs read
@@ -320,11 +320,11 @@ those words as an optional third argument); the `aria-label` of `Breadcrumb`
320
320
  and `Pagination` and the breadcrumb ellipsis's "More"
321
321
  (`material.breadcrumb.*`, `material.pagination.label`), and `DocHeader`'s
322
322
  breadcrumb (`material.doc.breadcrumb`); `MessageCard`'s "Unread"
323
- (`material.cards.unread`); the network upload field's "Replace {name}", "Upload
323
+ (`material.cards.unread`); the network upload field's "Replace {name}", "Remove {name}", "Upload
324
324
  failed", its type, size and count errors (`material.upload.replace-named`,
325
- `failed`, `invalid-type`, `too-large`, `max-files`; an error keeps the language
325
+ `remove-named`, `failed`, `invalid-type`, `too-large`, `max-files`; an error keeps the language
326
326
  it was raised in) and the drop zone's "Max {size}" and "Up to {count} files"
327
- (`material.upload.dropzone.max-size` / `max-files`); the editor's
327
+ (`material.upload.dropzone.max-size` / `max-files`) and the names `accept` wildcards read as (`material.upload.accept.images`, `audio`, `video`, `text`, `application`); the editor's
328
328
  placeholder, which follows a language change without rebuilding the editor,
329
329
  and its emoji buttons (`material['rich-text-editor'].placeholder` /
330
330
  `insert-emoji`); `CircularProgress`'s "Progress: {value}%"
@@ -485,8 +485,9 @@ and description and against the English ones, ignoring accents: in French,
485
485
 
486
486
  Some strings are still English in every language, among them the filter's
487
487
  validation messages, the camera's error messages, the editor's callout type
488
- names and default block content. File sizes (`2 KB`) and the filter chips' dates are not formatted for
489
- the provider's language.
488
+ names and default block content. The filter chips' dates, and file sizes outside the upload field (`2 KB`),
489
+ are not formatted for the provider's language; the upload field writes its
490
+ size in it (`10,4 ko`).
490
491
 
491
492
  The emoji picker's category tabs are icon-only; their name, read by screen
492
493
  readers and shown as a tooltip, comes from `material.emoji.groups.<group>`.
@@ -1013,7 +1013,7 @@ it:
1013
1013
  | `heading` | `heading(text, level?)` | A title, level `1`, `2` or `3` |
1014
1014
  | `text` | `text(content, { variant?, style? })` | A paragraph: a string, or runs `{ text, bold?, italic?, color?, href? }`; `variant` is `'body'`, `'muted'`, `'caption'` or `'label'` |
1015
1015
  | `stack` / `row` | `stack(children, { gap?, style?, keepTogether? })`, `row(children, { gap?, align?, justify?, style? })` | Children one under the other, or side by side |
1016
- | `section` | `section(title, children, { keepTogether? })` | A titled block, as `PrintSection`; `keepTogether` as on a `stack` |
1016
+ | `section` | `section(title, children, { keepTogether? })` | A titled block, as `PrintSection`; `keepTogether` as on a `stack`; a block taller than a page breaks normally, see [Keeping a block on one page](#keeping-a-block-on-one-page) |
1017
1017
  | `table` | `table({ columns, rows, footer?, labelTotals?, striped?, repeatHeader?, keepRowsWhole?, emptyText?, hideWhenEmpty? })`, or `dataTable(columns, rows, options?)` from [column definitions](#tables-from-column-definitions-datatable-and-keyvalueof) | A table of `PrintValue`s, as `PrintDataTable`; a row or a cell may carry a `style`, see [Row and cell styles](#row-and-cell-styles-in-a-table); with `labelTotals`, an empty first cell of a `footer` row reads "Total" in the document's language |
1018
1018
  | `keyValue` | `keyValue(items, { columns?, keepEmpty? })`, or `keyValueOf(fields, data, options?)` from column definitions | Label and value pairs, as `PrintKeyValue` |
1019
1019
  | `totals` | `totals(rows)` | Label and value rows; `emphasis` makes one bold |
@@ -1140,12 +1140,22 @@ section(group.title, rows, { keepTogether: true });
1140
1140
  stack([heading('Result'), table(result)], { keepTogether: true });
1141
1141
  ```
1142
1142
 
1143
- Use it on a block you know is shorter than a page. react-pdf cannot split a
1144
- block that opted out of wrapping: a block taller than the page area is not
1145
- broken, it overflows the page and is cut off at the edge (react-pdf logs
1146
- "can't wrap between pages and it's bigger than available page height").
1147
- Leave `keepTogether` off for a block that may grow past a page, and use
1148
- `keepRowsWhole` and `repeatHeader` on a long table instead.
1143
+ A block taller than a page breaks normally. react-pdf cannot split a block
1144
+ that opted out of wrapping (it overflows and is cut off at the edge), and
1145
+ cannot measure ahead, so for a document as data `renderPdfBuffer`,
1146
+ `renderPdfBlob`, `renderPdfStream`, `downloadPdf`, `openPdf` and `pdfResponse`
1147
+ first render each `keepTogether` block alone, on a page like the one it sits
1148
+ on, and drop the option of those that need more than one page. A block that
1149
+ fits keeps it, so its output does not change; a document with no
1150
+ `keepTogether` pays for no extra pass. The measure is made at the page's
1151
+ width: a block inside a `row` is measured with that row, but a parent's padding or narrower style is not applied.
1152
+
1153
+ This holds for a document as data only. `<PrintSection keepTogether>` in a
1154
+ `PrintDocument` element, and a document you render yourself with
1155
+ `renderPdfElement`, are not measured: use `keepTogether` there on a block
1156
+ shorter than a page, or call `resolveKeepTogether(doc)` from
1157
+ `@nxgt/material-core/pdf/render` first and render its result. For a long
1158
+ table, `keepRowsWhole` and `repeatHeader` keep the rows apart from each other.
1149
1159
 
1150
1160
  ### Row and cell styles in a table
1151
1161
 
package/docs/roadmap.md CHANGED
@@ -6,6 +6,8 @@ version they shipped in, and planned entries may name the major they target.
6
6
 
7
7
  ## Now
8
8
 
9
+ - **Calculator** — `@nxgt/material/calculator` adds `Calculator`, in six variants (Standard, Scientific, point of sale, Financial, Unit conversion, Programmer) chosen by `variant` or by its own switcher, and `CalculatorField` / `CalculatorFormField`, a number field whose button opens it in a popover, or a bottom sheet on a phone. Every variant shows the whole expression above the result, honours operator precedence and computes in exact decimals (0.1 + 0.2 = 0.3) without `eval`, so a strict CSP holds. A history tape (persisted, with an opt-out), memory keys, the physical keyboard, copy and paste, locale formatting and screen-reader announcements come with each; haptic and sound feedback are opt-in. Point of sale charges through `onCharge(total)`; currency rates come from the app. React only for now; the Vue port is tracked in [#2100](https://github.com/softistx/nxgt-material/issues/2100). Tracked in [#2101](https://github.com/softistx/nxgt-material/issues/2101).
10
+
9
11
  - **Barcodes and label sheets** — `@nxgt/material/barcode` draws Code 128
10
12
  and Data Matrix as accessible SVG (themed, dark-aware, black on white in
11
13
  print) and lays labels out on a sheet to print: label size, columns, rows,
@@ -15,6 +17,10 @@ version they shipped in, and planned entries may name the major they target.
15
17
  - **Toast texts follow app translations** — the toast presets read the
16
18
  app's `I18nProvider` `resources`, so an override under `material.*`
17
19
  changes their wording ([#1855](https://github.com/softistx/nxgt-material/issues/1855)).
20
+ - **A record page layout** — `RecordPage` and `RecordDetails` lay out one
21
+ record: a header card, the content, and its facts as a side panel from `xl`
22
+ that stacks after or before the content, with a created / updated line
23
+ ([#1689](https://github.com/softistx/nxgt-material/issues/1689)).
18
24
  - **A decision bar for phone pages** — `StickyActionBar` holds a long page's
19
25
  main decision at the bottom of a phone, above the keyboard and clear of the
20
26
  safe areas, and inline from `md`
@@ -157,6 +163,8 @@ version they shipped in, and planned entries may name the major they target.
157
163
 
158
164
  ## Next
159
165
 
166
+ - **Layouts: adaptive navigation, headers and page layouts** — `ActivityLayout` takes `navigation` (a bottom bar, a rail with a FAB, standard and modal drawers, a collapsible sidebar, a top navigation with dropdowns, two-level navigation or a floating toolbar, chosen for phone, tablet and desktop) and `header` (small, center-aligned, medium and large collapsing on scroll, a search bar with full-screen search on a phone, sticky tabs, hide on scroll); each piece is also exported. Page layouts grow with it: an inspector pane and URL state for `ListDetailsLayout`, phone modes for `PaneLayout`, spans and masonry for `ResponsiveGrid`, tabs for `Kanban` on a phone, and new settings, dashboard (rearranging tiles), editor and landing layouts, each with a screen recipe. Everything is opt-in; the shell's default is unchanged. Tracked in [#2103](https://github.com/softistx/nxgt-material/issues/2103).
167
+
160
168
  - **FilterBar "Add a filter" stays on its row** — the button no longer wraps
161
169
  alone onto a second row on a desktop screen ([#1752](https://github.com/softistx/nxgt-material/issues/1752)).
162
170
 
@@ -241,7 +249,6 @@ version they shipped in, and planned entries may name the major they target.
241
249
  slot; the bar's `views` slot is planned with it. They are meant to replace
242
250
  the legacy `FilterPresets`. Tracked in [#1876](https://github.com/softistx/nxgt-material/issues/1876).
243
251
 
244
- - **RecordActionsMenu** — one menu for a record's lifecycle: archive with an Undo toast (or confirm first with `archivePolicy="confirm"`), restore, and delete that always confirms and shows the backend's refusal, with `ConfirmDialog` gaining `details` and `refusal` slots.
245
252
  - **Cursor trail cap** — `ServerTable` and `PageFooter` keep cursor URLs short: the trail is encoded once and capped by `maxTrail` (20 by default) with an exact page count, and `totalCount` becomes optional for APIs that do not count.
246
253
 
247
254
  ## Later
@@ -364,6 +371,9 @@ version they shipped in, and planned entries may name the major they target.
364
371
 
365
372
  ## Shipped
366
373
 
374
+ - **RecordActionsMenu (next minor)** — one menu for a record's lifecycle: archive with an Undo toast (or confirm first with `archivePolicy="confirm"`), restore, and delete that always confirms and shows the backend's refusal, with `ConfirmDialog` gaining `details` and `refusal` slots.
375
+ - **Assistant approval card (next minor)** — `ChatActionProposal` shows an action an assistant proposes, with its status, deadline and Confirm / Reject, and `ChatToolActivity` is a compact row for a tool step in a chat thread ([#2134](https://github.com/softistx/nxgt-material/issues/2134)).
376
+
367
377
  - **Link navigation (3.66.0)** — `TabsNav` and `SegmentedNav` render route tabs and segmented navigation as a `nav` of links with `aria-current="page"`, looking like Tabs and the segmented ToggleGroup, plus `useTabParam`, `skipTabChange` and `tabIds` to keep the current tab in the URL. Tracked in [#2029](https://github.com/softistx/nxgt-material/issues/2029).
368
378
 
369
379
  - **Smaller, easier-to-read components (3.58.0)** — the library's own source
@@ -11,6 +11,7 @@ words in your error.
11
11
  - [`Cannot find package '@react-pdf/renderer'`](#cannot-find-package-react-pdfrenderer)
12
12
  - [`Cannot find package 'react-router'` after upgrading to 3.0](#cannot-find-package-react-router-after-upgrading-to-30) (and `ERESOLVE` on `react` or `react-router`; `Element type is invalid` for `ViewTransition` or `browser is not a function` from a stale Storybook or Vitest pre-bundle)
13
13
  - [`Conflicting peer dependency: @react-pdf/renderer` (`ERESOLVE`)](#conflicting-peer-dependency-react-pdfrenderer)
14
+ - [`AttributeValuesFormFieldSet` shows empty fields, or ignores my `defaultValues`](#attributevaluesformfieldset-shows-empty-fields-or-ignores-my-defaultvalues)
14
15
  - [`Cannot find package 'leaflet'`](#cannot-find-package-leaflet)
15
16
  - [`hls.js could not be loaded. Install it (`npm i hls.js`) to play HLS streams in this browser.`](#hlsjs-could-not-be-loaded-install-it-npm-i-hlsjs-to-play-hls-streams-in-this-browser)
16
17
  - [`This browser cannot play HLS streams.`](#this-browser-cannot-play-hls-streams)
@@ -66,7 +67,10 @@ words in your error.
66
67
  - [A Button's label overflows a card on a phone](#a-buttons-label-overflows-a-card-on-a-phone)
67
68
  - [A ResponsiveButton no longer shows its tooltip next to its visible label](#a-responsivebutton-no-longer-shows-its-tooltip-next-to-its-visible-label)
68
69
  - [An input now has `placeholder=" "` in the DOM](#an-input-now-has-placeholder--in-the-dom)
70
+ - ["Select all" runs under the sort, "Filters" or the actions in a cards or tiles list on a phone](#select-all-runs-under-the-sort-filters-or-the-actions-in-a-cards-or-tiles-list-on-a-phone)
69
71
  - [A dialog or sheet scrolls, or clips a child, after upgrading](#a-dialog-or-sheet-scrolls-or-clips-a-child-after-upgrading)
72
+ - [A `DialogBody`'s header and footer do not stay pinned](#a-dialogbodys-header-and-footer-do-not-stay-pinned)
73
+ - [A form's `form-actions` bar sticks inside a `DialogBody`](#a-forms-form-actions-bar-sticks-inside-a-dialogbody)
70
74
  - [A form's fields no longer scroll in their own box after upgrading](#a-forms-fields-no-longer-scroll-in-their-own-box-after-upgrading)
71
75
  - [My activity content lost its side padding, or the rail's entries moved up](#my-activity-content-lost-its-side-padding-or-the-rails-entries-moved-up)
72
76
  - [axe reports `target-size` on a control under a form's sticky actions](#axe-reports-target-size-on-a-control-under-a-forms-sticky-actions)
@@ -380,6 +384,7 @@ words in your error.
380
384
  - [`` [@nxgt/material] DataTable: `tableRef` stays null in the `cards` layout; use `focusRef`. ``](#nxgtmaterial-datatable-tableref-stays-null-in-the-cards-layout-use-focusref)
381
385
  - [`defineCodeTheme: "keyword" must be a hex colour`](#definecodetheme-keyword-must-be-a-hex-colour-rgb-or-rrggbb)
382
386
  - [A `CodeEditor` shows its code in one colour after another Monaco editor mounts](#a-codeeditor-shows-its-code-in-one-colour-after-another-monaco-editor-mounts)
387
+ - [`primary={false}` has no effect on a `ContactsFormField` with `editing="dialog"`](#primaryfalse-has-no-effect-on-a-contactsformfield-with-editingdialog)
383
388
 
384
389
  **PDF reader**
385
390
 
@@ -535,7 +540,28 @@ bun add @react-pdf/renderer@^4.9
535
540
  `--legacy-peer-deps` silences the error but keeps a renderer the print
536
541
  entry is not tested against.
537
542
 
538
- ### `Cannot find package 'leaflet'`
543
+ ### `AttributeValuesFormFieldSet` shows empty fields, or ignores my `defaultValues`
544
+
545
+ **When:** the set comes up empty although you pass `defaultValues`, or shows
546
+ other values than the ones you passed.
547
+
548
+ **Why:** the set reads the form value at `name` first. `defaultValues`
549
+ is only a fallback while that value is empty: `undefined`, `null` or an object
550
+ with no keys. A form that already holds `{ color: 'red' }` at `name` wins over
551
+ `defaultValues`; a form that holds `{ color: undefined }` has a key, so it is
552
+ not empty either.
553
+
554
+ **Fix:** put the values in the form, where every other field reads them:
555
+
556
+ ```tsx
557
+ const form = useForm({ defaultValues: { attributes: { size: 'M' } } });
558
+ // or, for a record loaded later
559
+ form.reset({ attributes: record.attributes });
560
+ ```
561
+
562
+ Keep `defaultValues` on the set only for a fallback when the form holds nothing.
563
+
564
+ ## `Cannot find package 'leaflet'`
539
565
 
540
566
  **When:** at build or at the first import of `@nxgt/material/map`. The missing
541
567
  name may also be `react-leaflet`, `@react-leaflet/core`,
@@ -613,6 +639,14 @@ never receives the press.
613
639
  forwards `ref` and `...props` to its root. Or skip `trigger` and open it from
614
640
  your own state with `open` / `onOpenChange`.
615
641
 
642
+ ### A test that read the upload hint now sees "JPEG, PNG, WebP" or "Images"
643
+
644
+ **When:** after upgrading, a test or snapshot that matched the drop zone's hint
645
+ (`image/jpeg,image/png · Max 2 MB`, `image/*`) fails.
646
+ **Why:** the hint shows short names derived from `accept` instead of the raw
647
+ list: `JPEG, PNG · Max 2 MB`, `Images`.
648
+ **Fix:** match the names, or pass `acceptLabel="..."` to fix the text.
649
+
616
650
  ### `This browser cannot play HLS streams.`
617
651
 
618
652
  **When:** an HLS track on a browser without Media Source Extensions where
@@ -1427,6 +1461,18 @@ your own through `className`. With `asChild`, put the label in a `<span>` (on a
1427
1461
  </Button>
1428
1462
  ```
1429
1463
 
1464
+ ### "Select all" runs under the sort, "Filters" or the actions in a cards or tiles list on a phone
1465
+
1466
+ **When:** below 768px, a `ServerTable` in `cards` or `tiles` with `filter` and
1467
+ `actions` draws "Select all" over the sort menu and "Filters (N)" when an action
1468
+ has a long label ("Add a measurement unit").
1469
+ **Why:** up to 3.70.0, the label of row 2 neither shrank nor truncated, and the
1470
+ end group (sort, "Filters (N)", actions) never gave up its width.
1471
+ **Fix:** update. The "Select all" text now ends in "…" (the box keeps its name,
1472
+ "Select all on this page"), and an end group too wide for the row wraps to its
1473
+ own line under it. Until then, use a `Fab` for the create action on phones, or
1474
+ a short or icon-only label.
1475
+
1430
1476
  ### A dialog or sheet scrolls, or clips a child, after upgrading
1431
1477
 
1432
1478
  **When:** after upgrading, a tall `DialogContent`, `AlertDialogContent` or
@@ -1448,6 +1494,38 @@ with `className="max-h-none overflow-visible"`; to change the cap, pass your own
1448
1494
  For a dialog that should grow taller than the screen and scroll inside the
1449
1495
  overlay instead of capping, use `DialogScrollContent`.
1450
1496
 
1497
+ With a `DialogBody` as a direct child, the content is `overflow-hidden` (a
1498
+ clipped flex column) and only the body scrolls, so a child that sticks out of
1499
+ the content is clipped there too; keep popovers in a portal.
1500
+
1501
+ ### A `DialogBody`'s header and footer do not stay pinned
1502
+
1503
+ **When:** a long dialog with a `DialogBody` still scrolls as a whole, or the
1504
+ footer sits at the end of the fields.
1505
+ **Why:** the content only turns into a column when the `DialogBody` is a
1506
+ *direct* child of `DialogContent` (`has-[>[data-slot=dialog-body]]`). Inside a
1507
+ `<form>` or another wrapper it is not. Another cause: a `max-h-none` or
1508
+ `overflow-visible` in the content's `className`.
1509
+ **Fix:** make the body a direct child, and put the `<form>` around the whole
1510
+ content, or keep the body inside and submit with `form="id"` on the footer button:
1511
+
1512
+ ```tsx
1513
+ <DialogContent>
1514
+ <DialogHeader>…</DialogHeader>
1515
+ <DialogBody>…</DialogBody>
1516
+ <DialogFooter>…</DialogFooter>
1517
+ </DialogContent>
1518
+ ```
1519
+
1520
+ ### A form's `form-actions` bar sticks inside a `DialogBody`
1521
+
1522
+ **When:** a `.form-grid-layout` form with `form-actions` in a `DialogBody` shows
1523
+ its Save bar at the bottom of the body, not of the dialog.
1524
+ **Why:** the bar is sticky at the bottom of whatever scrolls, and the body now
1525
+ scrolls.
1526
+ **Fix:** use the `DialogFooter` for the buttons, with `form="my-form"` on the
1527
+ submit button and `id="my-form"` on the form, and leave `form-actions` out.
1528
+
1451
1529
  ### A form's fields no longer scroll in their own box after upgrading
1452
1530
 
1453
1531
  **When:** after upgrading, a `.form-grid-layout` form grows with the page
@@ -2128,6 +2206,12 @@ settles, with `focusRef` on the table. It matches the row by id, not by label.
2128
2206
  See "Handing the focus back to a row" in
2129
2207
  [RowMenu](components/data-table-filter-and-lists.md#handing-the-focus-back-to-a-row).
2130
2208
 
2209
+ ### A `RecordActionsMenu` or `ConfirmDialog` refusal shows no action, or its link reloads the page
2210
+
2211
+ **When:** `send` rejects (or a `ConfirmDialog`'s `onConfirm` throws) with an error that should offer a link, but the dialog or toast shows only the message; or the link of a toast action reloads the whole app.
2212
+ **Why:** only an `ActionRefusal` carries an action (a plain `Error`, or a rethrown copy of its message, does not). A toast's `href` is a plain navigation (`window.location.assign`), since a toast is outside your router.
2213
+ **Fix:** reject with `new ActionRefusal(message, { action: { label, href } })` and keep it as thrown (do not wrap it in another `Error`). For a client-side navigation use `action: { label, onAction: () => navigate(...) }`. A `ConfirmDialog` renders it by default; a `refusal` prop of your own replaces that rendering, so it must show the details and the action itself. See [ConfirmDialog](components/feedback-and-status.md#confirmdialog) and [RecordActionsMenu](components/data-table-filter-and-lists.md#recordactionsmenu).
2214
+
2131
2215
  ### A `ConfirmationDialog` reopened with new content blocks clicks
2132
2216
 
2133
2217
  **When:** a `ConfirmationDialog` or a `ConfirmDialog` (or a `Dialog`,
@@ -3649,6 +3733,26 @@ fill is now left empty, and the title stays centred.
3649
3733
  <PrintHeader title="Invoice" logoText="LOGO" />
3650
3734
  ```
3651
3735
 
3736
+ ### A `keepTogether` block is cut off at the bottom of the page
3737
+
3738
+ **When:** the console shows `Node of type VIEW can't wrap between pages and
3739
+ it's bigger than available page height` and the rows of a `PrintSection` or
3740
+ a `section` / `stack` with `keepTogether` run past the page edge.
3741
+ **Why:** react-pdf keeps a block whole with `wrap={false}` and cannot split
3742
+ one taller than a page, nor measure it ahead.
3743
+ **Fix:** for a document as data, render it with `renderPdfBuffer`,
3744
+ `renderPdfBlob`, `renderPdfStream`, `downloadPdf` or `pdfResponse`: they
3745
+ measure each `keepTogether` block and let a too-tall one break normally. For a
3746
+ `<PrintSection keepTogether>` in a `PrintDocument`, or your own call to
3747
+ `renderPdfElement`, remove `keepTogether` from blocks that can outgrow a page,
3748
+ or resolve the document first:
3749
+
3750
+ ```ts
3751
+ import { resolveKeepTogether } from '@nxgt/material-core/pdf/render';
3752
+
3753
+ const element = renderPdfElement(await resolveKeepTogether(doc));
3754
+ ```
3755
+
3652
3756
  ### French amounts print with a slash, as in `1/234,50`
3653
3757
 
3654
3758
  **When:** before 2.2.0, `formatAmount(1234.5, 'EUR', { locale: 'fr-FR' })`
@@ -5142,7 +5246,7 @@ such as `getByRole('navigation', { name: 'pagination' })`,
5142
5246
  | `No filters available`, `+2 more` | `Aucun filtre disponible`, `+2 de plus` | `filter.empty`, `filter.more` |
5143
5247
  | `Yes` / `No`, `From 5`, `Until …`, `Up to 9` (filter chips) | `Oui` / `Non`, `À partir de 5`, `Jusqu'au …`, `Jusqu'à 9` | `filter.value.*` |
5144
5248
  | `Unread` (`MessageCard`) | `Non lu` | `cards.unread` |
5145
- | `Replace {name}`, `Upload failed` | `Remplacer {name}`, `Échec de l'envoi` | `upload.replace-named`, `upload.failed` |
5249
+ | `Replace {name}`, `Remove {name}`, `Upload failed` | `Remplacer {name}`, `Supprimer {name}`, `Échec de l'envoi` | `upload.replace-named`, `upload.remove-named`, `upload.failed` |
5146
5250
  | `File type not accepted`, `File exceeds maximum size of …`, `Maximum of 3 files reached` | `Type de fichier non accepté`, `Le fichier dépasse la taille maximale de …`, `Maximum de 3 fichiers atteint` | `upload.invalid-type`, `upload.too-large`, `upload.max-files` |
5147
5251
  | `Write something …`, `Insert {name} emoji` (editor) | `Écrire quelque chose…`, `Insérer l'emoji {name}` | `rich-text-editor.placeholder`, `rich-text-editor.insert-emoji` |
5148
5252
  | `Progress: 40%` | `Progression : 40 %` | `circular-progress.label` |
@@ -6911,6 +7015,19 @@ structural theme back when it mounts, but not while the other editor stays.
6911
7015
  **Fix:** keep that editor on a page of its own, or stop it calling `setTheme`
6912
7016
  (`CodeEditor`'s `ref` handle gives Monaco's editor for the options you need).
6913
7017
 
7018
+ ### `primary={false}` has no effect on a `ContactsFormField` with `editing="dialog"`
7019
+
7020
+ **When:** `<ContactsFormField editing="dialog" primary={false} />` looks the
7021
+ same as without the prop.
7022
+
7023
+ **Why:** `primary` hides the star and the primary marks of the inline editor.
7024
+ The dialog edits `type`, `value` and `label` only; it has no star to hide, and
7025
+ the `primary` flag already in the value is left untouched. `types` does work
7026
+ in both modes.
7027
+
7028
+ **Fix:** use the default `editing="inline"` when the star must go, or drop the
7029
+ `primary` flag from the value before saving.
7030
+
6914
7031
  ## PDF reader
6915
7032
 
6916
7033
  ### PDF text is missing or in the wrong font in `PdfReader` (cMaps and standard fonts)
@@ -7748,8 +7865,13 @@ one. The usual sources are the components listed in
7748
7865
  [the guide](guides/csp.md#style-elements-the-components-inject).
7749
7866
  `ScrollArea`, `Select`, `RichTextEditor` and the scroll lock of dialogs and
7750
7867
  sheets take the nonce from `NonceProvider`, and report this when the app has
7751
- none (or gives it another nonce than the header's). A `<style>` in mermaid's
7752
- SVG is a [known gap](guides/csp.md#known-gaps). `Toaster`, `Drawer`,
7868
+ none (or gives it another nonce than the header's). Mermaid and KaTeX
7869
+ need none of this on a client render
7870
+ ([Markdown under a strict CSP](guides/csp.md#markdown-katex-mermaid-and-code-under-a-strict-csp));
7871
+ the two left are a server-rendered page with math (KaTeX's `style` attributes
7872
+ need `style-src-attr 'unsafe-inline'`) and the `<style>` cytoscape adds to
7873
+ `<head>` for mermaid's mindmap and architecture diagrams.
7874
+ `Toaster`, `Drawer`,
7753
7875
  `InputOTP`, `ImageCropper`, `Spinner` and `PdfReader` create none: their CSS is in the stylesheet, so if one of them looks unstyled, the
7754
7876
  app imports neither `styles.css` nor `dist/material.css`.
7755
7877
  **Fix:** for `style-src-attr`, allow attributes: they cannot be nonced.
@@ -7770,10 +7892,6 @@ import { NonceProvider } from '@nxgt/material';
7770
7892
  </NonceProvider>;
7771
7893
  ```
7772
7894
 
7773
- For a mermaid diagram, serve that route with `'unsafe-inline'` in
7774
- `style-src-elem` instead of the nonce (a nonce in the same directive makes
7775
- browsers ignore `'unsafe-inline'`).
7776
-
7777
7895
  Once a page has a header-delivered CSP, browsers blank the `nonce` attribute, so React's development build may warn of an attribute mismatch when it hydrates a server-rendered `<style nonce>`. The warning is harmless.
7778
7896
 
7779
7897
  ### `Refused to load the font 'data:font/woff2…' because it violates the following Content Security Policy directive: "font-src 'self'"`
@@ -187,3 +187,9 @@
187
187
  gap: 0.75rem;
188
188
  }
189
189
  }
190
+ /* A wide action (#2168): the actions may shrink within the row, so a long
191
+ * label ends in "..." (the button's own ellipsis) instead of overflowing. */
192
+ [data-slot="server-table-phone-row-end"] > [data-slot="server-table-actions"] {
193
+ flex-shrink: 1;
194
+ min-width: 0;
195
+ }
@@ -1,3 +1,40 @@
1
+ /* The scheme's colours as custom properties on the block: classes of the
2
+ stylesheet, never an inline `style` (strict CSP). Same values as
3
+ moonlight-tokens.ts; code-block-scheme.test.ts keeps them equal. */
4
+ [data-slot="code-block"][data-scheme="dark"] {
5
+ --code-bg: #222436;
6
+ --code-fg: #c8d3f5;
7
+ --code-comment: #7a88cf;
8
+ --code-blue: #82aaff;
9
+ --code-cyan: #86e1fc;
10
+ --code-green: #c3e88d;
11
+ --code-orange: #ff966c;
12
+ --code-yellow: #ffc777;
13
+ --code-red: #ff757f;
14
+ --code-magenta: #c099ff;
15
+ --code-pink: #fca7ea;
16
+ --code-selection: #2f334d;
17
+ }
18
+
19
+ [data-slot="code-block"][data-scheme="light"] {
20
+ --code-bg: #f5f7ff;
21
+ --code-fg: #383a51;
22
+ --code-comment: #7a88cf;
23
+ --code-blue: #3e68d7;
24
+ --code-cyan: #0b8ec2;
25
+ --code-green: #4db380;
26
+ --code-orange: #c44c22;
27
+ --code-yellow: #ac8300;
28
+ --code-red: #c53b40;
29
+ --code-magenta: #7a5af5;
30
+ --code-pink: #b04d9a;
31
+ --code-selection: #d4daf0;
32
+ }
33
+
34
+ [data-slot="code-block"] [data-code-scroll] {
35
+ max-height: var(--code-max-height, none);
36
+ }
37
+
1
38
  [data-slot="code-block"] {
2
39
  background: var(--code-bg);
3
40
  color: var(--code-fg);
@@ -3,6 +3,11 @@
3
3
  form {
4
4
  @apply grid @xl:grid-cols-2 @2xl:grid-cols-3 @3xl:grid-cols-4 gap-2;
5
5
 
6
+ /* Every field takes its cell's full width, whatever its own slot. */
7
+ > * {
8
+ @apply w-full min-w-0;
9
+ }
10
+
6
11
  [data-slot="separator"] {
7
12
  @apply col-start-full;
8
13
  }