@nxgt/material 3.71.0 → 3.73.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 (86) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +1 -1
  3. package/dist/{alert-dialog-y2pbowVh.js → alert-dialog-A8K8zai1.js} +1 -1
  4. package/dist/{async-slice-CGfWBOaV.js → async-slice-B8933IHY.js} +1 -1
  5. package/dist/{button-D54Hrjo0.js → button-C8moFnIT.js} +1 -1
  6. package/dist/charts.js +3 -3
  7. package/dist/{chip-B5MhXBfm.js → chip-B6AY1E91.js} +2 -2
  8. package/dist/{code-editor-mULs4SL8.js → code-editor-DQZIKL54.js} +5 -5
  9. package/dist/code-editor.js +5 -5
  10. package/dist/{code-language-items-BWDnqUqn.js → code-language-items-8VL0aMKG.js} +2 -2
  11. package/dist/components/ui/fields/attributes/attribute-values-form/attribute-values-form.css +5 -0
  12. package/dist/{components-B_taYVcd.js → components-Cvp2F9uk.js} +16928 -16733
  13. package/dist/components.js +28 -28
  14. package/dist/{dialog-AjCYZ2Vz.js → dialog-WuLTwxkr.js} +5 -5
  15. package/dist/dnd.js +1 -1
  16. package/dist/{dropdown-menu-LN60hu_3.js → dropdown-menu-tadySeGH.js} +1 -1
  17. package/dist/editor.js +10 -10
  18. package/dist/{empty-state-Bo8G8W8G.js → empty-state-ZYiFSSBQ.js} +2 -2
  19. package/dist/{field-blur-Cnd6FfCH.js → field-blur-BYR0yE5L.js} +1 -1
  20. package/dist/{field-blur-context-bUFZj8iN.js → field-blur-context-bEhByoK-.js} +1 -1
  21. package/dist/{hooks-Dt4PkQiZ.js → hooks-GpwD8iDu.js} +2 -2
  22. package/dist/hooks.js +2 -2
  23. package/dist/{hover-card-QtGnvsnc.js → hover-card-BowaI_ra.js} +1 -1
  24. package/dist/i18n.js +3 -3
  25. package/dist/{icon-badge-DLbpCQIm.js → icon-badge-B1B0Lg02.js} +3 -3
  26. package/dist/{icon-button-C59DqL7L.js → icon-button-BbGU17cb.js} +1 -1
  27. package/dist/lib/components/ui/collections/filter/use-filter-field-config.d.ts +3 -0
  28. package/dist/lib/components/ui/fields/attributes/attribute-values-form-field-set/attribute-values-form-field-set.d.ts +5 -1
  29. package/dist/lib/components/ui/fields/choice-list/choice-list-focus.d.ts +42 -0
  30. package/dist/lib/components/ui/fields/contacts/contact-dialog.d.ts +8 -2
  31. package/dist/lib/components/ui/fields/contacts/contacts-dialog-form-field.d.ts +4 -1
  32. package/dist/lib/components/ui/fields/contacts/contacts-editor.d.ts +15 -2
  33. package/dist/lib/components/ui/fields/contacts/contacts-form-field.d.ts +6 -1
  34. package/dist/lib/components/ui/fields/contacts/logic/offered-types.d.ts +3 -0
  35. package/dist/lib/components/ui/fields/contacts/models.d.ts +2 -0
  36. package/dist/lib/components/ui/fields/contacts/partials/contact-fields.d.ts +4 -2
  37. package/dist/lib/components/ui/fields/contacts/partials/contact-groups.d.ts +4 -2
  38. package/dist/lib/components/ui/fields/contacts/partials/contact-row.d.ts +4 -2
  39. package/dist/lib/components/ui/fields/contacts/partials/phone-sheet.d.ts +4 -2
  40. package/dist/lib/components/ui/fields/contacts/partials/quick-add.d.ts +4 -2
  41. package/dist/lib/components/ui/fields/contacts/partials/row-actions.d.ts +3 -1
  42. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  43. package/dist/lib/components/ui/media/upload/network-upload-field/network-upload-field.d.ts +5 -0
  44. package/dist/lib/components/ui/media/upload/network-upload-field/partials/network-upload-field-items.d.ts +2 -1
  45. package/dist/lib/components/ui/media/upload/network-upload-field/upload-constraints.d.ts +10 -2
  46. package/dist/lib/components/ui/overlays/dialog/dialog-body-overflow.d.ts +7 -0
  47. package/dist/lib/components/ui/overlays/dialog/dialog-body.d.ts +10 -0
  48. package/dist/lib/components/ui/overlays/dialog/dialog-variants.d.ts +4 -0
  49. package/dist/lib/components/ui/overlays/dialog/dialog.d.ts +1 -0
  50. package/dist/lib/components/ui/selection/autocomplete/autocomplete-enter.d.ts +7 -3
  51. package/dist/lib/i18n/resources/en/en.d.ts +7 -0
  52. package/dist/lib/i18n/resources/en/material.json.d.ts +7 -0
  53. package/dist/lib/i18n/resources/fr/fr.d.ts +7 -0
  54. package/dist/lib/i18n/resources/fr/material.json.d.ts +7 -0
  55. package/dist/lib/i18n/resources/resources.d.ts +14 -0
  56. package/dist/lib/i18n/translator.d.ts +1 -1
  57. package/dist/lib.js +1 -1
  58. package/dist/main.js +42 -42
  59. package/dist/map.js +8 -8
  60. package/dist/material.css +1 -1
  61. package/dist/pdf-reader.js +1 -1
  62. package/dist/print.js +1 -1
  63. package/dist/{public-BSaSCwy7.js → public-CFj2qn7l.js} +9 -9
  64. package/dist/{rich-code-block-BFVj1GWz.js → rich-code-block-B7Teg7-e.js} +5 -5
  65. package/dist/{sortable-provider-REwyjiOZ.js → sortable-provider-DU2zp_jn.js} +19 -13
  66. package/dist/{spinner-BFZxqdWC.js → spinner-C5Kb6MWu.js} +1 -1
  67. package/dist/theme-picker.js +2 -2
  68. package/dist/{theme-toggle-BHi6miz-.js → theme-toggle-B1JZg7mU.js} +1 -1
  69. package/dist/{toolbar-items-BTtoKVlx.js → toolbar-items-m_1FTFPQ.js} +3 -3
  70. package/dist/{translate-material-R-uLthBy.js → translate-material-D1ndrd7a.js} +1 -1
  71. package/dist/{translator-ClbXOGQO.js → translator-CtEC_AQa.js} +14 -0
  72. package/dist/{typography-v-PJ4Zco.js → typography-prQHb3gI.js} +1 -1
  73. package/dist/{use-chat-composer-Rhan4Xv0.js → use-chat-composer-BAoUsyKN.js} +1924 -1911
  74. package/dist/{use-heading-spy-CJnBkCY0.js → use-heading-spy-Bzj5mIUZ.js} +4 -4
  75. package/dist/{use-material-translation-CCWUeiEn.js → use-material-translation-C6H0B4kE.js} +1 -1
  76. package/dist/{use-return-focus-Duk40DZI.js → use-return-focus-CkUX1HFl.js} +1 -1
  77. package/docs/components/form-fields.md +72 -3
  78. package/docs/components/media-and-files.md +4 -1
  79. package/docs/components/overlays-and-menus.md +20 -1
  80. package/docs/components/primitives.md +2 -1
  81. package/docs/components/selection-and-choice.md +4 -2
  82. package/docs/guides/forms.md +8 -3
  83. package/docs/guides/i18n.md +1 -1
  84. package/docs/troubleshooting.md +114 -1
  85. package/lib/components/ui/fields/attributes/attribute-values-form/attribute-values-form.css +5 -0
  86. package/package.json +3 -2
@@ -1,8 +1,8 @@
1
1
  import { t as e } from "./utils-BLfntITJ.js";
2
- import { a as t, c as n, d as r, i, n as a, r as o, t as s, u as c } from "./alert-dialog-y2pbowVh.js";
3
- import { a as l } from "./button-D54Hrjo0.js";
4
- import { i as u } from "./translate-material-R-uLthBy.js";
5
- import { t as d } from "./use-material-translation-CCWUeiEn.js";
2
+ import { a as t, c as n, d as r, i, n as a, r as o, t as s, u as c } from "./alert-dialog-A8K8zai1.js";
3
+ import { a as l } from "./button-C8moFnIT.js";
4
+ import { i as u } from "./translate-material-D1ndrd7a.js";
5
+ import { t as d } from "./use-material-translation-C6H0B4kE.js";
6
6
  import { _ as f, g as p, h as m, n as h, v as g, y as _ } from "./number.utils-BG_eEt5g.js";
7
7
  import { useCallback as v, useEffect as y, useMemo as b, useRef as x, useState as S } from "react";
8
8
  import { Fragment as C, jsx as w, jsxs as T } from "react/jsx-runtime";
@@ -1,4 +1,4 @@
1
- import { i as e, t } from "./translate-material-R-uLthBy.js";
1
+ import { i as e, t } from "./translate-material-D1ndrd7a.js";
2
2
  import { useCallback as n } from "react";
3
3
  //#region lib/i18n/use-material-translation.ts
4
4
  function r() {
@@ -1,6 +1,6 @@
1
1
  import { o as e, r as t } from "./dist-DVRQkaTK.js";
2
2
  import { a as n, c as r, i, l as a, o, r as s, s as c } from "./dist-lmHmz_bu.js";
3
- import { C as l, S as u, w as d } from "./spinner-BFZxqdWC.js";
3
+ import { C as l, S as u, w as d } from "./spinner-C5Kb6MWu.js";
4
4
  import { c as f, i as p, l as m, o as h, r as g, s as ee } from "./in-dialog-Vwkyp-M5.js";
5
5
  import { r as _ } from "./escape-owner-DpaP0lm1.js";
6
6
  import * as v from "react";
@@ -375,6 +375,15 @@ up / down buttons, `min` and `max` rows (Remove is disabled at `min`, Add is
375
375
  announcements. Without a form library, `ChoiceListField` takes `value` and
376
376
  `onChange`.
377
377
 
378
+ **Focus.** `ChoiceListFormField` gives React Hook Form's `field.ref` a handle
379
+ that focuses the first refused row's text input: the row of the first
380
+ `choices.<i>.text` error (a resolver's), or the first blank row when the
381
+ built-in blank rule refused the list. An error on the list itself (too few
382
+ rows, no row marked) focuses its first control. So `shouldFocusError`,
383
+ `setFocus('choices')` and `setError('choices', …, { shouldFocus: true })` land
384
+ on the refused row when there is an error, and on the first control otherwise;
385
+ the form needs no `[aria-invalid="true"]` lookup.
386
+
378
387
  | Prop | Type | Default | What it does |
379
388
  | --- | --- | --- | --- |
380
389
  | `control`, `name` | `Control<T>`, `Path<T>` | — | the form and its array (`ChoiceListFormField`) |
@@ -1700,6 +1709,37 @@ import { AttributesFormField } from '@nxgt/material/components';
1700
1709
  <AttributesFormField control={control} name="attributes" label="Attributes" />
1701
1710
  ```
1702
1711
 
1712
+ **`AttributeValuesFormFieldSet`** renders one input per attribute and writes
1713
+ `{ [attribute.name]: value }` to the form at `name`:
1714
+
1715
+ ```tsx
1716
+ import { AttributeValuesFormFieldSet, FormSection } from '@nxgt/material/components';
1717
+
1718
+ const form = useForm({ defaultValues: { attributes: { color: '#2563eb', size: 'M' } } });
1719
+
1720
+ <FormSection title="Attributes">
1721
+ <AttributeValuesFormFieldSet
1722
+ control={form.control}
1723
+ name="attributes"
1724
+ label="Attributes"
1725
+ hideLabel
1726
+ attributes={[
1727
+ { name: 'color', label: 'Colour', type: 'COLOR' },
1728
+ { name: 'size', label: 'Size', type: 'SELECT', options: ['S', 'M', 'L'] },
1729
+ ]}
1730
+ />
1731
+ </FormSection>
1732
+ ```
1733
+
1734
+ | Prop | |
1735
+ | --- | --- |
1736
+ | `attributes` | the attributes to fill, one input each |
1737
+ | `control`, `name` | the form and the path of the object it writes |
1738
+ | `label` | the fieldset's accessible name (required) |
1739
+ | `hideLabel` | default `false`. `true` keeps `label` as the name but draws nothing, for a `FormSection` that already has the title |
1740
+ | `defaultValues` | optional fallback, used only while the form value at `name` is empty: `undefined`, `null` or an object with no keys (`{}`). Any other value in the form wins |
1741
+ | `className` | on the `<fieldset>` |
1742
+
1703
1743
  **The inline editor** is one row per attribute: a grip, the type's icon, the
1704
1744
  label with `name · Type` under it, the unit as a badge and the default value
1705
1745
  (a Yes or No, a colour swatch, a number with its unit). A row opens to its
@@ -1811,7 +1851,10 @@ const schema = z.object({ attributes: attributesListSchema() });
1811
1851
 
1812
1852
  **Emits:** `Attribute[]`; the inline editor writes a date as `yyyy-MM-dd`, a
1813
1853
  date-time as an ISO instant and a time as `HH:mm`. `AttributeValuesFormFieldSet`
1814
- writes an object keyed by attribute `name`; `AttributeValuesForm` reads a bare
1854
+ writes an object keyed by attribute `name`, reads the form's value at `name`
1855
+ (`defaultValues` is only the fallback while it holds nothing), fills the row
1856
+ width, and takes `hideLabel` to keep `label` as the fieldset's accessible name
1857
+ without drawing the heading; `AttributeValuesForm` reads a bare
1815
1858
  `yyyy-MM-dd` default as that local day.
1816
1859
  **Form wrapper:** `AttributesFormField` — `control` and `name`, see
1817
1860
  [forms](../guides/forms.md#the-pair), [what each field emits](../guides/forms.md#what-each-field-emits)
@@ -1863,6 +1906,32 @@ The editor checks the shape of a value, not that the line exists. Below 640px
1863
1906
  each contact is a summary row; tapping it opens a bottom sheet with the same
1864
1907
  fields and actions, and closing it with an empty value drops the contact.
1865
1908
 
1909
+ **Limit the types, hide the star.** A backend that stores only some types, or
1910
+ no primary flag, says so on `ContactsFormField` and `ContactsEditor` (`primary` is inline only):
1911
+
1912
+ ```tsx
1913
+ <ContactsFormField
1914
+ control={control}
1915
+ name="contacts"
1916
+ label="Contacts"
1917
+ types={['EMAIL', 'PHONE', 'FAX', 'WEBSITE']}
1918
+ primary={false}
1919
+ />
1920
+ ```
1921
+
1922
+ `types` (an array of `ContactTypeName`, i.e. `'EMAIL' | 'PHONE' | …` or the
1923
+ `ContactType` values; every type by default) limits the "Add" chips and the
1924
+ type select; list at least one type, since an empty `types={[]}` is not
1925
+ supported. The chips keep the library's order (phone, mobile, email,
1926
+ website, fax); the select follows the order given. With `editing="dialog"`,
1927
+ `types` limits the dialog's type select (`ContactDialog` takes it too, and a
1928
+ new contact starts with the first type); `primary` has no effect there, since
1929
+ the dialog has no star. A contact
1930
+ already in the value with another type is kept and shown, and its own type
1931
+ stays in its select, so nothing is changed silently. `primary={false}` (`true`
1932
+ by default) hides the star and the primary marks; a `primary` flag already in
1933
+ the value is left untouched.
1934
+
1866
1935
  `variant="cards"` is the Expressive variant: each contact is its own tinted
1867
1936
  card with a round tile for the primary one. The fields keep the library's
1868
1937
  field radius in both. `variant` is on `ContactsFormField`, `ContactsEditor`
@@ -1895,9 +1964,9 @@ only, and a value that fails its check is shown without a link.
1895
1964
  | `empty` | `ReactNode` | "No contacts" | shown when there is nothing to list; `null` renders nothing |
1896
1965
 
1897
1966
  `ContactsEditor` takes `value`, `onValueChange`, `label`, `headingLevel` (`3`
1898
- by default; the type headings sit one below it), `variant` and `disabled`. A
1967
+ by default; the type headings sit one below it), `variant`, `types`, `primary` and `disabled`. A
1899
1968
  wrong format is said once the field was left, a duplicate at once. `ContactsFormField` takes `editing` (`'inline' \| 'dialog'`,
1900
- default `'inline'`), `variant` (inline only) and `cancelVariant` (dialog only,
1969
+ default `'inline'`), `variant` (inline only), `types`, `primary` (inline only) and `cancelVariant` (dialog only,
1901
1970
  the Cancel button's `Button` variant, default `'tonal'`; `'outlined'` restores
1902
1971
  the earlier look). `Contacts` takes `data`, `onAdd`, `onEdit` and `disabled`.
1903
1972
 
@@ -78,12 +78,15 @@ function Attachments() {
78
78
  | `mode` | `'single' \| 'multiple'` | — (required) | `value` is a `string` or a `string[]` |
79
79
  | `onUpload` | `(file, onProgress?, signal?) => Promise<string>` | — | `NetworkUploadField` only; the resolved string becomes the value. Removing an uploading row or unmounting the field aborts `signal`: pass it to your request, or the upload still completes (its value is dropped). An abort after the request completed cannot unsend it: the value is dropped and `onRemove` is not called. The S3 fields abort their request themselves and reject with an `UploadError` of kind `aborted` (named `AbortError`, no longer a `DOMException`) |
80
80
  | `getPresignedUrl` | `(file) => Promise<PresignedUploadTarget>` | — | `S3UploadField` only; `{ uploadUrl, publicUrl, headers? }` PUTs the file and stores `publicUrl`; `{ method: 'POST', url, fields, value }` posts a form and stores `value` (see [presigned POST](#presigned-post)) |
81
- | `accept` | `string` | — | file input `accept`, also shown under the drop zone |
81
+ | `accept` | `string` | — | file input `accept`; the drop zone's hint shows it as short names (`image/jpeg,image/png` reads `JPEG, PNG`, `.pdf` reads `PDF`, `image/*` reads `Images`; unknown entries stay as given) |
82
+ | `acceptLabel` | `string` | — | replaces those names in the hint (`"Photos only"`); `""` hides the type part. |
82
83
  | `maxSize` | `number` (bytes) | — | larger files are rejected before upload |
83
84
  | `maxFiles` | `number` | — | `mode="multiple"` only; the drop zone is disabled once reached |
84
85
  | `onRemove` | `(value) => void \| Promise<void>` | — | called when a committed file is removed (delete it server-side here) |
85
86
  | `resolveUrl` | `(value) => string \| null \| undefined` | — | turns a stored key into an `<img src>` so saved images preview |
86
87
 
88
+ The drop zone's hint wraps in a narrow dialog instead of being clipped.
89
+
87
90
  The field is a `group` named by its `label`. The drop zone is a `button`, which
88
91
  may not carry `aria-invalid`: with `error` it is described by `helperText`, or
89
92
  without one by a visually hidden "This value is not valid."
@@ -61,10 +61,27 @@ Your own `onEscapeKeyDown` on the content receives the Escape that only closes a
61
61
  >
62
62
  ```
63
63
 
64
+ **A long dialog scrolls its body, not the whole box.** Wrap the fields in `DialogBody` (a direct child of `DialogContent`, between `DialogHeader` and `DialogFooter`): the content becomes a clipped column, the header and the footer stay in view, and only the body scrolls. A hairline shows on the edge where content is hidden beneath the header or the footer. It works the same as a bottom sheet or full screen on a phone, with the safe-area padding kept under the footer. A dialog without `DialogBody` is unchanged and scrolls as a whole. Give the body an `aria-label` (or `aria-labelledby`) to make it a named region; `className` sets its inner layout (`grid gap-3`). It sets `data-overflow-start` and `data-overflow-end` while content is hidden above or below. `DialogScrollContent`, `Sheet`, `Drawer` and `AlertDialog` do not take it yet.
65
+
66
+ ```tsx
67
+ <DialogContent>
68
+ <DialogHeader>
69
+ <DialogTitle>New item</DialogTitle>
70
+ </DialogHeader>
71
+ <DialogBody aria-label="Item fields" className="grid gap-3">
72
+ {/* twelve fields */}
73
+ </DialogBody>
74
+ <DialogFooter>
75
+ <Button>Save</Button>
76
+ </DialogFooter>
77
+ </DialogContent>
78
+ ```
79
+
64
80
  ```tsx
65
81
  import {
66
82
  Button,
67
83
  Dialog,
84
+ DialogBody,
68
85
  DialogClose,
69
86
  DialogContent,
70
87
  DialogDescription,
@@ -94,7 +111,8 @@ import {
94
111
  ```
95
112
 
96
113
  Parts: `Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`,
97
- `DialogTitle`, `DialogDescription`, `DialogFooter`, `DialogClose`, plus
114
+ `DialogTitle`, `DialogDescription`, `DialogFooter`, `DialogBody` (the part
115
+ that scrolls alone), `DialogClose`, plus
98
116
  `DialogOverlay` and `DialogPortal`, and `DialogScrollContent`: a content for
99
117
  a dialog taller than the screen, which sits in a scrolling overlay with a
100
118
  margin instead of being clipped, and renders its own close button. It takes
@@ -132,6 +150,7 @@ export function Terms() {
132
150
  | `variant`, `color` (`DialogClose`) | as `IconButton` | `'ghost'` | a top-right X button, named "Close" (`material.buttons.close`, translated) while it shows only the icon; text children or an `aria-label` name it instead, and icon-only children leave it unnamed; children replace the X |
133
151
  | `size` (`DialogContent`) | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | its width from 640 px up: 384, 512, 672, 896 px, or the screen less 2rem a side |
134
152
  | `mobilePresentation` (`DialogContent`) | `'dialog' \| 'bottom-sheet' \| 'top' \| 'fullscreen'` | `'dialog'` | below 640 px: a centred dialog, a sheet that springs up from the bottom edge (rounded top corners), a dialog pinned under the top edge, or the whole screen |
153
+ | `className`, `aria-label` / `aria-labelledby`, `onScroll`, `ref` (`DialogBody`) | `ComponentProps<'div'>` | — | the scroll region: `className` lays out its content (`grid gap-3`); an `aria-label` or `aria-labelledby` makes it a `region` with that name; `onScroll` and `ref` are called after its own overflow flags update. It sets `data-overflow-start` / `data-overflow-end` while content is hidden above / below, and bleeds by `--dialog-px` (1.5rem) so a `DialogContent` with its own padding sets both: `p-4 [--dialog-px:1rem]` |
135
154
  | `scrim` (`DialogContent`, `SheetContent`, `DrawerContent`) | `'dim' \| 'blur'` | `'dim'` | the scrim behind; `blur` also blurs the page a little |
136
155
 
137
156
  The look lives in the stylesheet (`nxgt-dialog`, `nxgt-scrim`), under the
@@ -163,7 +163,8 @@ import {
163
163
  Parts: `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`,
164
164
  `CardContent`, `CardFooter` — each a styled `<div>` taking its element's props,
165
165
  except `CardTitle`, a heading: an `h3` by default, another level or a `div`
166
- with `as` (`<CardTitle as="h2">`), so `getByRole('heading', { name })` finds it.
166
+ with `as` (`<CardTitle as="h2">`), so `getByRole('heading', { name })` finds it. The title takes its text size's
167
+ line height; pass a `leading-*` class to override.
167
168
  `CardAction` sits top-right of the header.
168
169
 
169
170
  **Storybook:** `Primitives/Card` · **Vue:** `Card` (same parts)
@@ -436,8 +436,10 @@ disabled with the field; Backspace in the empty input removes the last one.
436
436
  Enter in a form: it acts on the list and does not submit the form while it has
437
437
  something to do (WAI-ARIA combobox practice). It picks the highlighted option;
438
438
  with `freeSolo` and no option highlighted it adds the typed text; with the list
439
- open it is only default-prevented. On a closed list with nothing typed it
440
- submits, as a plain input does.
439
+ open it is only default-prevented. In `mode="multiple"` (a tags box, `freeSolo`
440
+ or not) Enter never submits the form, closed list and empty input included, so
441
+ the Enter after a pick does not save the form. In `single` mode, on a closed
442
+ list with nothing typed it submits, as a plain input does.
441
443
 
442
444
  An avatar in the rows and the chips:
443
445
  `getOptionLeading={(person) => <Avatar src={person.photo} alt="" />}`; see
@@ -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
 
@@ -324,7 +324,7 @@ breadcrumb (`material.doc.breadcrumb`); `MessageCard`'s "Unread"
324
324
  failed", its type, size and count errors (`material.upload.replace-named`,
325
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}%"
@@ -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)
@@ -68,6 +69,8 @@ words in your error.
68
69
  - [An input now has `placeholder=" "` in the DOM](#an-input-now-has-placeholder--in-the-dom)
69
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)
70
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)
71
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)
72
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)
73
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)
@@ -310,6 +313,7 @@ words in your error.
310
313
  - [A click on the next field right after an `Autocomplete` pick is lost](#a-click-on-the-next-field-right-after-an-autocomplete-pick-is-lost)
311
314
  - [An older search's results replace the newer ones in an `Autocomplete`](#an-older-searchs-results-replace-the-newer-ones-in-an-autocomplete)
312
315
  - [An `Autocomplete` option does nothing on click or Enter](#an-autocomplete-option-does-nothing-on-click-or-enter)
316
+ - [Enter in a multiple `Autocomplete` no longer submits the form](#enter-in-a-multiple-autocomplete-no-longer-submits-the-form)
313
317
  - ["The search failed. Try again." no longer shows in an `Autocomplete`](#the-search-failed-try-again-no-longer-shows-in-an-autocomplete)
314
318
  - [A test no longer finds `aria-invalid` on an `OtpField` slot](#a-test-no-longer-finds-aria-invalid-on-an-otpfield-slot)
315
319
  - [A test no longer finds `aria-invalid` on a `CodeEditor`](#a-test-no-longer-finds-aria-invalid-on-a-codeeditor)
@@ -381,6 +385,7 @@ words in your error.
381
385
  - [`` [@nxgt/material] DataTable: `tableRef` stays null in the `cards` layout; use `focusRef`. ``](#nxgtmaterial-datatable-tableref-stays-null-in-the-cards-layout-use-focusref)
382
386
  - [`defineCodeTheme: "keyword" must be a hex colour`](#definecodetheme-keyword-must-be-a-hex-colour-rgb-or-rrggbb)
383
387
  - [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)
388
+ - [`primary={false}` has no effect on a `ContactsFormField` with `editing="dialog"`](#primaryfalse-has-no-effect-on-a-contactsformfield-with-editingdialog)
384
389
 
385
390
  **PDF reader**
386
391
 
@@ -536,7 +541,28 @@ bun add @react-pdf/renderer@^4.9
536
541
  `--legacy-peer-deps` silences the error but keeps a renderer the print
537
542
  entry is not tested against.
538
543
 
539
- ### `Cannot find package 'leaflet'`
544
+ ### `AttributeValuesFormFieldSet` shows empty fields, or ignores my `defaultValues`
545
+
546
+ **When:** the set comes up empty although you pass `defaultValues`, or shows
547
+ other values than the ones you passed.
548
+
549
+ **Why:** the set reads the form value at `name` first. `defaultValues`
550
+ is only a fallback while that value is empty: `undefined`, `null` or an object
551
+ with no keys. A form that already holds `{ color: 'red' }` at `name` wins over
552
+ `defaultValues`; a form that holds `{ color: undefined }` has a key, so it is
553
+ not empty either.
554
+
555
+ **Fix:** put the values in the form, where every other field reads them:
556
+
557
+ ```tsx
558
+ const form = useForm({ defaultValues: { attributes: { size: 'M' } } });
559
+ // or, for a record loaded later
560
+ form.reset({ attributes: record.attributes });
561
+ ```
562
+
563
+ Keep `defaultValues` on the set only for a fallback when the form holds nothing.
564
+
565
+ ## `Cannot find package 'leaflet'`
540
566
 
541
567
  **When:** at build or at the first import of `@nxgt/material/map`. The missing
542
568
  name may also be `react-leaflet`, `@react-leaflet/core`,
@@ -614,6 +640,14 @@ never receives the press.
614
640
  forwards `ref` and `...props` to its root. Or skip `trigger` and open it from
615
641
  your own state with `open` / `onOpenChange`.
616
642
 
643
+ ### A test that read the upload hint now sees "JPEG, PNG, WebP" or "Images"
644
+
645
+ **When:** after upgrading, a test or snapshot that matched the drop zone's hint
646
+ (`image/jpeg,image/png · Max 2 MB`, `image/*`) fails.
647
+ **Why:** the hint shows short names derived from `accept` instead of the raw
648
+ list: `JPEG, PNG · Max 2 MB`, `Images`.
649
+ **Fix:** match the names, or pass `acceptLabel="..."` to fix the text.
650
+
617
651
  ### `This browser cannot play HLS streams.`
618
652
 
619
653
  **When:** an HLS track on a browser without Media Source Extensions where
@@ -1461,6 +1495,38 @@ with `className="max-h-none overflow-visible"`; to change the cap, pass your own
1461
1495
  For a dialog that should grow taller than the screen and scroll inside the
1462
1496
  overlay instead of capping, use `DialogScrollContent`.
1463
1497
 
1498
+ With a `DialogBody` as a direct child, the content is `overflow-hidden` (a
1499
+ clipped flex column) and only the body scrolls, so a child that sticks out of
1500
+ the content is clipped there too; keep popovers in a portal.
1501
+
1502
+ ### A `DialogBody`'s header and footer do not stay pinned
1503
+
1504
+ **When:** a long dialog with a `DialogBody` still scrolls as a whole, or the
1505
+ footer sits at the end of the fields.
1506
+ **Why:** the content only turns into a column when the `DialogBody` is a
1507
+ *direct* child of `DialogContent` (`has-[>[data-slot=dialog-body]]`). Inside a
1508
+ `<form>` or another wrapper it is not. Another cause: a `max-h-none` or
1509
+ `overflow-visible` in the content's `className`.
1510
+ **Fix:** make the body a direct child, and put the `<form>` around the whole
1511
+ content, or keep the body inside and submit with `form="id"` on the footer button:
1512
+
1513
+ ```tsx
1514
+ <DialogContent>
1515
+ <DialogHeader>…</DialogHeader>
1516
+ <DialogBody>…</DialogBody>
1517
+ <DialogFooter>…</DialogFooter>
1518
+ </DialogContent>
1519
+ ```
1520
+
1521
+ ### A form's `form-actions` bar sticks inside a `DialogBody`
1522
+
1523
+ **When:** a `.form-grid-layout` form with `form-actions` in a `DialogBody` shows
1524
+ its Save bar at the bottom of the body, not of the dialog.
1525
+ **Why:** the bar is sticky at the bottom of whatever scrolls, and the body now
1526
+ scrolls.
1527
+ **Fix:** use the `DialogFooter` for the buttons, with `form="my-form"` on the
1528
+ submit button and `id="my-form"` on the form, and leave `form-actions` out.
1529
+
1464
1530
  ### A form's fields no longer scroll in their own box after upgrading
1465
1531
 
1466
1532
  **When:** after upgrading, a `.form-grid-layout` form grows with the page
@@ -4815,6 +4881,19 @@ on a desktop and on a phone alike: closing the picker with OK hands focus back
4815
4881
  to the trigger, and the next Tab or click elsewhere touches it. See the next
4816
4882
  entry.
4817
4883
 
4884
+ ### A failed submit focuses a `ChoiceListFormField` but not the refused row
4885
+
4886
+ **When:** a form with a `ChoiceListFormField` fails validation (a row's text
4887
+ is blank, `choices.2.text` in a resolver) and `document.activeElement` is the
4888
+ list's first control, or nothing, instead of the refused row. In versions
4889
+ before this fix.
4890
+ **Why:** the field gave React Hook Form a handle that focused the first
4891
+ control of the list, whichever row was refused.
4892
+ **Fix:** upgrade to the release that lists this fix in its changelog:
4893
+ `shouldFocusError` and `setFocus(name)` focus the first refused row's text
4894
+ input. Remove a workaround that queries `[aria-invalid="true"]` after submit.
4895
+ For the other fields, see the next entry.
4896
+
4818
4897
  ### `setError(…, { shouldFocus: true })` or a failed submit does not focus the field
4819
4898
 
4820
4899
  **When:** a form wired with `*FormField`s: `handleSubmit` fails validation, or
@@ -5647,6 +5726,27 @@ may be chosen:
5647
5726
  />
5648
5727
  ```
5649
5728
 
5729
+ ### Enter in a multiple `Autocomplete` no longer submits the form
5730
+
5731
+ **When:** after upgrading, Enter in an `Autocomplete` (or `AutocompleteFormField`,
5732
+ a tags field) with `mode="multiple"` no longer submits the surrounding form
5733
+ when the list is closed and the input is empty.
5734
+ **Why:** the field is a tags box: the Enter that follows a pick used to save the
5735
+ whole form by surprise (#2199). Enter in `mode="multiple"` now only picks or
5736
+ adds. `single` mode is unchanged.
5737
+ **Fix:** submit with the form's button, or with a key handler of your own on the
5738
+ form if a keyboard shortcut is wanted (Ctrl/Cmd + Enter); drop any
5739
+ `onKeyDown` that prevented Enter on the combobox.
5740
+
5741
+ ```tsx
5742
+ <form
5743
+ onSubmit={save}
5744
+ onKeyDown={(e) => {
5745
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) e.currentTarget.requestSubmit();
5746
+ }}
5747
+ >
5748
+ ```
5749
+
5650
5750
  ### "The search failed. Try again." no longer shows in an `Autocomplete`
5651
5751
 
5652
5752
  **When:** after upgrading, an `Autocomplete` whose `onSearch` rejects shows
@@ -6950,6 +7050,19 @@ structural theme back when it mounts, but not while the other editor stays.
6950
7050
  **Fix:** keep that editor on a page of its own, or stop it calling `setTheme`
6951
7051
  (`CodeEditor`'s `ref` handle gives Monaco's editor for the options you need).
6952
7052
 
7053
+ ### `primary={false}` has no effect on a `ContactsFormField` with `editing="dialog"`
7054
+
7055
+ **When:** `<ContactsFormField editing="dialog" primary={false} />` looks the
7056
+ same as without the prop.
7057
+
7058
+ **Why:** `primary` hides the star and the primary marks of the inline editor.
7059
+ The dialog edits `type`, `value` and `label` only; it has no star to hide, and
7060
+ the `primary` flag already in the value is left untouched. `types` does work
7061
+ in both modes.
7062
+
7063
+ **Fix:** use the default `editing="inline"` when the star must go, or drop the
7064
+ `primary` flag from the value before saving.
7065
+
6953
7066
  ## PDF reader
6954
7067
 
6955
7068
  ### PDF text is missing or in the wrong font in `PdfReader` (cMaps and standard fonts)
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "3.71.0",
3
+ "version": "3.73.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"
@@ -372,7 +372,7 @@
372
372
  "docs:previews": "rm -rf node_modules/.cache/nxgt-preview-free/missing && NXGT_PREVIEW_FREE=1 vitest run --project previews && bun .storybook/preview-free-report.ts"
373
373
  },
374
374
  "dependencies": {
375
- "@nxgt/material-core": "^0.46.0",
375
+ "@nxgt/material-core": "^0.47.1",
376
376
  "@base-ui/react": "^1.6.0",
377
377
  "@dnd-kit/core": "^6.3.1",
378
378
  "@dnd-kit/sortable": "^10.0.0",
@@ -487,6 +487,7 @@
487
487
  "axe-core": "4.12.1",
488
488
  "bwip-js": "^4.11.4",
489
489
  "globals": "^17.7.0",
490
+ "happy-dom": "^21.0.4",
490
491
  "hls.js": "^1.7.3",
491
492
  "leaflet": "^1.9.4",
492
493
  "lightningcss": "^1.33.0",