@nxgt/material 2.5.0 → 2.6.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 (115) hide show
  1. package/README.md +5 -7
  2. package/dist/{_Uint8Array-DuTT6911.js → _Uint8Array-CDlDeQmw.js} +1 -1
  3. package/dist/{_overRest-93z6TD9V.js → _overRest-CWIWQwDJ.js} +1 -1
  4. package/dist/{async-slice-B8Xv2BLH.js → async-slice-Qoen0Mqm.js} +2 -2
  5. package/dist/{blockDiagram-BEXU5L5S-C8UXdLfB.js → blockDiagram-BEXU5L5S-_6EVgQ2-.js} +2 -2
  6. package/dist/{chunk-3FUC2YCW-4gCRt6fO.js → chunk-3FUC2YCW-CRQHl-Jx.js} +2 -2
  7. package/dist/{chunk-7M6MHVWA-DnPXbiTM.js → chunk-7M6MHVWA-DqXpf6CM.js} +1 -1
  8. package/dist/{chunk-GNY47TPC-DLqgU2SJ.js → chunk-GNY47TPC-BgMsLbCS.js} +4 -4
  9. package/dist/{chunk-NETBCI7D-CBowDvwz.js → chunk-NETBCI7D-CsyH43bj.js} +1 -1
  10. package/dist/{chunk-Z7XXMR3K-dRJc1Z30.js → chunk-Z7XXMR3K-CoCjTVVx.js} +1 -1
  11. package/dist/{classDiagram-v2-NBCMYWYE-CQmePeL4.js → classDiagram-v2-NBCMYWYE-j4vk04t-.js} +2 -2
  12. package/dist/{components-CUkJhBwK.js → components-C3wIUrWw.js} +25959 -25227
  13. package/dist/components.js +5 -5
  14. package/dist/{dagre-6A5THRUB-BhyJENrD.js → dagre-6A5THRUB-DXq_YBNL.js} +54 -54
  15. package/dist/{diagram-22UHCM2B-CJc0JoTV.js → diagram-22UHCM2B-He8NDldw.js} +2 -2
  16. package/dist/{diagram-3UASUU5V-0P3Cnq5H.js → diagram-3UASUU5V-CeILva4U.js} +1 -1
  17. package/dist/dnd.js +1 -1
  18. package/dist/{elk-276RUBZZ-CvgDuHoY.js → elk-276RUBZZ-DFn4kTey.js} +3 -3
  19. package/dist/{erDiagram-OPXOYQCR-BFgRy8E9.js → erDiagram-OPXOYQCR-BzVUcUwh.js} +2 -2
  20. package/dist/{field-blur-D1eQk-49.js → field-blur-BlrE9fOD.js} +1 -1
  21. package/dist/{flowDiagram-KWPJA3E3-DGaa9iT2.js → flowDiagram-KWPJA3E3-B4pJCj2Q.js} +3 -3
  22. package/dist/{ganttDiagram-FUAMR5RP-CDGrtGHY.js → ganttDiagram-FUAMR5RP-B4A4UFFi.js} +1 -1
  23. package/dist/{graphlib-Dt-isUVV.js → graphlib-CQ3hLS1N.js} +6 -6
  24. package/dist/hooks.js +2 -2
  25. package/dist/{i18n-provider-Drsc2YXX.js → i18n-provider-CbYD2KxP.js} +1 -1
  26. package/dist/i18n.js +6 -6
  27. package/dist/{isArrayLikeObject-BhWbq65y.js → isArrayLikeObject-5rnYwEK7.js} +3 -3
  28. package/dist/{isEmpty-DdlzV8Ue.js → isEmpty-DChvniYe.js} +3 -3
  29. package/dist/{journeyDiagram-ZHPQQLJL-BuLKqWS3.js → journeyDiagram-ZHPQQLJL-C91SPPkY.js} +1 -1
  30. package/dist/lib/components/ui/collections/tree/use-tree.d.ts +2 -0
  31. package/dist/lib/components/ui/dates/date-range-field/date-range-field-trigger.d.ts +1 -1
  32. package/dist/lib/components/ui/dates/date-range-field/date-range-field.d.ts +10 -1
  33. package/dist/lib/components/ui/dates/date-range-field/date-range-field.utils.d.ts +13 -0
  34. package/dist/lib/components/ui/dates/date-range-field/index.d.ts +1 -0
  35. package/dist/lib/components/ui/dates/date-range-field/partials/date-range-field-content.d.ts +25 -0
  36. package/dist/lib/components/ui/dates/date-range-field/partials/date-range-presets.d.ts +11 -0
  37. package/dist/lib/components/ui/dates/date-range-field/partials/date-range-times.d.ts +11 -0
  38. package/dist/lib/components/ui/dates/date-range-field/use-date-range-presets.d.ts +22 -0
  39. package/dist/lib/components/ui/dates/time-picker/time-picker-utils.d.ts +7 -0
  40. package/dist/lib/components/ui/fields/number-field/number-field.d.ts +3 -1
  41. package/dist/lib/components/ui/fields/slider/slider-header.d.ts +5 -1
  42. package/dist/lib/components/ui/fields/slider/slider-readout.d.ts +5 -1
  43. package/dist/lib/components/ui/media/file-preview/file-preview-pdf.lazy.d.ts +9 -0
  44. package/dist/lib/components/ui/media/file-preview/file-preview.d.ts +12 -0
  45. package/dist/lib/components/ui/media/file-preview/index.d.ts +3 -0
  46. package/dist/lib/components/ui/media/file-preview/partials/file-preview-body.d.ts +9 -0
  47. package/dist/lib/components/ui/media/file-preview/partials/file-preview-card.d.ts +20 -0
  48. package/dist/lib/components/ui/media/file-preview/partials/file-preview-media.d.ts +11 -0
  49. package/dist/lib/components/ui/media/file-preview/partials/file-preview-pdf.d.ts +10 -0
  50. package/dist/lib/components/ui/media/file-preview/partials/file-preview-status.d.ts +17 -0
  51. package/dist/lib/components/ui/media/file-preview/partials/file-preview-text.d.ts +13 -0
  52. package/dist/lib/components/ui/media/file-preview/types.d.ts +23 -0
  53. package/dist/lib/components/ui/media/file-preview/use-file-preview.d.ts +22 -0
  54. package/dist/lib/components/ui/media/file-preview/use-text-preview.d.ts +15 -0
  55. package/dist/lib/components/ui/media/index.d.ts +1 -0
  56. package/dist/lib/components/ui/media/upload/network-upload-field/use-image-preview.d.ts +6 -0
  57. package/dist/lib/components/ui/selection/index.d.ts +1 -0
  58. package/dist/lib/components/ui/selection/tree-select-field/index.d.ts +2 -0
  59. package/dist/lib/components/ui/selection/tree-select-field/partials/tree-select-content.d.ts +13 -0
  60. package/dist/lib/components/ui/selection/tree-select-field/partials/tree-select-panel.d.ts +19 -0
  61. package/dist/lib/components/ui/selection/tree-select-field/partials/tree-select-trigger.d.ts +24 -0
  62. package/dist/lib/components/ui/selection/tree-select-field/tree-select-field.d.ts +9 -0
  63. package/dist/lib/components/ui/selection/tree-select-field/tree-select-form-field.d.ts +8 -0
  64. package/dist/lib/components/ui/selection/tree-select-field/tree-select.utils.d.ts +35 -0
  65. package/dist/lib/components/ui/selection/tree-select-field/types.d.ts +37 -0
  66. package/dist/lib/components/ui/selection/tree-select-field/use-tree-select-field.d.ts +20 -0
  67. package/dist/lib/i18n/resources/en/en.d.ts +33 -0
  68. package/dist/lib/i18n/resources/en/material.json.d.ts +36 -3
  69. package/dist/lib/i18n/resources/fr/fr.d.ts +33 -0
  70. package/dist/lib/i18n/resources/fr/material.json.d.ts +36 -3
  71. package/dist/lib/i18n/resources/resources.d.ts +66 -0
  72. package/dist/lib/i18n/translator.d.ts +1 -1
  73. package/dist/lib.js +3 -3
  74. package/dist/main.js +15 -15
  75. package/dist/map.js +2 -2
  76. package/dist/material.css +1 -1
  77. package/dist/{merge-a7J0YkZX.js → merge-_Awk4ywA.js} +4 -4
  78. package/dist/{mermaid.core-CE34byhz.js → mermaid.core-DciYB1MV.js} +21 -21
  79. package/dist/{mindmap-definition-NLK3R4M7-emoij-nW.js → mindmap-definition-NLK3R4M7-Brf_2jIE.js} +2 -2
  80. package/dist/{pieDiagram-5QR66LMP-BdG1tWLn.js → pieDiagram-5QR66LMP-MPh-EWf-.js} +1 -1
  81. package/dist/print.js +2 -2
  82. package/dist/{quadrantDiagram-O4NWA36T-BCd-NwsO.js → quadrantDiagram-O4NWA36T-CwDeE9KB.js} +1 -1
  83. package/dist/{requirementDiagram-PLB6GJNP-CCcH5uSr.js → requirementDiagram-PLB6GJNP-CCP3YtvB.js} +2 -2
  84. package/dist/{sankeyDiagram-IPEJSGJF-78cK-bfe.js → sankeyDiagram-IPEJSGJF-vgVchd2a.js} +1 -1
  85. package/dist/{sortable-provider-VCxPasNp.js → sortable-provider-CzSnl6It.js} +1 -1
  86. package/dist/{stateDiagram-v2-GCMORJYK-B4ghJrhD.js → stateDiagram-v2-GCMORJYK-T-oIXh-8.js} +2 -2
  87. package/dist/{stored-language-CeD7kHui.js → stored-language-DmUUhRb5.js} +72 -6
  88. package/dist/{swimlanes-2SLR337P-DmHm2yJS.js → swimlanes-2SLR337P-tW3_wBWO.js} +3 -3
  89. package/dist/{swimlanesDiagram-TC7HE7FX-Dq3gGsPj.js → swimlanesDiagram-TC7HE7FX-B1lWzHp1.js} +3 -3
  90. package/dist/{timeline-definition-EJHVYXUP-EqJV_QKn.js → timeline-definition-EJHVYXUP-SdA6wt2-.js} +1 -1
  91. package/dist/{translate-material-DgRdhz5d.js → translate-material-DuY12xca.js} +2 -2
  92. package/dist/{translator-B4zUVm7j.js → translator-CaIYjyFP.js} +1 -1
  93. package/dist/{use-heading-spy-1BHMXjIn.js → use-heading-spy-Crdz4OH5.js} +2 -2
  94. package/dist/{use-material-translation-UbARR81K.js → use-material-translation-Od0QsFPA.js} +2 -2
  95. package/dist/{usecaseDiagram-POWQR4AR-CvCl0p5N.js → usecaseDiagram-POWQR4AR-B3rBcR4E.js} +2 -2
  96. package/dist/{xychartDiagram-PMCCYNJV-BphsHFKA.js → xychartDiagram-PMCCYNJV-stQWLgCx.js} +1 -1
  97. package/docs/components/README.md +2 -1
  98. package/docs/components/data-display.md +3 -3
  99. package/docs/components/data-table-filter-and-lists.md +5 -0
  100. package/docs/components/date-and-time.md +106 -6
  101. package/docs/components/feedback-and-status.md +1 -1
  102. package/docs/components/form-fields.md +8 -2
  103. package/docs/components/media-and-files.md +71 -1
  104. package/docs/components/primitives.md +1 -1
  105. package/docs/components/rich-widgets.md +1 -1
  106. package/docs/components/selection-and-choice.md +79 -2
  107. package/docs/guides/forms.md +2 -1
  108. package/docs/guides/i18n.md +47 -4
  109. package/docs/previews/date-time-daterangefield.webp +0 -0
  110. package/docs/previews/media-files-filepreview.webp +0 -0
  111. package/docs/previews/selection-choice-treeselectfield.webp +0 -0
  112. package/docs/roadmap.md +32 -27
  113. package/docs/troubleshooting.md +58 -0
  114. package/package.json +2 -2
  115. package/dist/lib/components/ui/fields/slider/slider-ticks.d.ts +0 -1
@@ -105,7 +105,7 @@ calendar is labelled by the trigger, so named by the label.
105
105
  **Emits:** `Date | undefined`.
106
106
  **Form wrapper:** `DateFormField` — `control` and `name`, see
107
107
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
108
- **Storybook:** `Date & time/DateField` · **Vue:** `DateField`, `DateFormField`
108
+ **Storybook:** `Date & time/DateField` · **Vue:** `DateField`, `DateFormField` (`disabled-days`, same rules)
109
109
 
110
110
  ---
111
111
 
@@ -113,7 +113,7 @@ calendar is labelled by the trigger, so named by the label.
113
113
 
114
114
  ![DateRangeField](../previews/date-time-daterangefield.webp)
115
115
 
116
- From / to range picked in a popover calendar.
116
+ From / to range picked in a popover calendar, with presets and times.
117
117
 
118
118
  ```tsx
119
119
  import { type DateRange, DateRangeField } from '@nxgt/material/components';
@@ -128,10 +128,13 @@ export function Stay() {
128
128
  | Prop | Type | Default | Effect |
129
129
  | --- | --- | --- | --- |
130
130
  | `value` | `DateRange` | — | `{ from: Date \| undefined; to?: Date }` |
131
- | `onValueChange` | `(value?: DateRange) => void` | — | called on each click: `to` is unset after the first |
131
+ | `onValueChange` | `(value?: DateRange) => void` | — | called on each click: after the first, `to` is the same day as `from` |
132
132
  | `placeholder`, `label`, `helperText`, `error`, `required` | | — | `placeholder` is the trigger's text; `label` and `helperText` sit beside it; `error` and `required` its state |
133
133
  | `disabled` | `boolean` | — | disables the field: the trigger cannot be focused or opened |
134
134
  | `disabledDays` | react-day-picker's `Matcher \| Matcher[]` | — | days that cannot be picked (the calendar's `disabled`) |
135
+ | `presets` | `readonly DateRangePresetOption[]` | — (no list) | one-click ranges beside the calendar: built-in ids (`DATE_RANGE_PRESET_IDS`) and `{ label, range }` of your own |
136
+ | `withTime` | `boolean` | `false` | a time of day on each end, under the calendar, and on the trigger; the popover stays open until OK |
137
+ | `minDate`, `maxDate` | `Date` | — | the first and last day that can be picked (the calendar's, as are `disablePast` / `disableFuture`) |
135
138
 
136
139
  Every other `Calendar` prop is passed to the calendar; it opens on the
137
140
  month of `value.from`. `id`, `aria-describedby` and `data-*` attributes go on
@@ -148,11 +151,103 @@ trigger in the field's root `div` (`data-slot="date-range-field"`, which takes
148
151
  rendered alone, shows only the value: use the field for the label and the
149
152
  helper text. The open calendar is labelled by the trigger.
150
153
 
151
- **Emits:** `DateRange | undefined` — `{ from, to }`, where `to` stays
152
- `undefined` until the second day is picked.
154
+ ### Presets
155
+
156
+ `presets` lists one-click ranges beside the calendar (above it on a narrow
157
+ screen), in the order you give them. A built-in id is translated
158
+ (`material.date-range-field.preset.<id>`); a preset of your own is
159
+ `{ label, range }`, where `range` is `{ from, to }` or `(now) => ({ from, to })`.
160
+
161
+ ```tsx
162
+ import {
163
+ type DateRange,
164
+ DateRangeField,
165
+ type DateRangePreset,
166
+ } from '@nxgt/material/components';
167
+ import { useState } from 'react';
168
+
169
+ const lastQuarter: DateRangePreset = {
170
+ label: 'Last quarter',
171
+ range: (now) => {
172
+ const start = new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3 - 3, 1);
173
+ return { from: start, to: new Date(start.getFullYear(), start.getMonth() + 3, 0) };
174
+ },
175
+ };
176
+
177
+ export function Report() {
178
+ const [period, setPeriod] = useState<DateRange | undefined>();
179
+ return (
180
+ <DateRangeField
181
+ label="Period"
182
+ value={period}
183
+ onValueChange={setPeriod}
184
+ maxDate={new Date()}
185
+ presets={['today', 'last-7-days', 'this-month', 'last-month', lastQuarter]}
186
+ />
187
+ );
188
+ }
189
+ ```
190
+
191
+ | Built-in id | Range |
192
+ | --- | --- |
193
+ | `today`, `yesterday` | that day |
194
+ | `last-7-days`, `last-30-days` | ends today and counts it |
195
+ | `this-month`, `this-year` | from the first day to today |
196
+ | `last-month` | the whole previous month |
197
+
198
+ A built-in range starts at 00:00 (and with `withTime` ends at 23:59); your
199
+ own ranges keep the times you gave them. Picking a preset sets the value and
200
+ closes the popover (with `withTime` it stays open); open again, the calendar
201
+ is on that range's month. The list is a group named "Presets"
202
+ (`material.date-range-field.presets`) of buttons in the tab order, and the
203
+ one whose days are the value's has `aria-pressed="true"`. A preset is
204
+ disabled when its range starts before `minDate`, ends after `maxDate`, or
205
+ either end is one of `disabledDays`; the days between are not checked, as the
206
+ calendar lets a range span a disabled day.
207
+ The built-in presets use the current date when the popover opens; there is
208
+ no `now` prop, and none is planned. A preset of your own whose end comes
209
+ before its start is disabled.
210
+
211
+ ### Times
212
+
213
+ `withTime` adds two `TimePicker`s under the calendar, in groups named
214
+ "Start time" and "End time" (`material.date-range-field.start-time`,
215
+ `.end-time`), each disabled until its end has a day, and an OK button
216
+ (`material.buttons.ok`) that closes the popover. The trigger shows each end
217
+ with its time (date-fns `Pp`).
218
+
219
+ ```tsx
220
+ import { type DateRange, DateRangeField } from '@nxgt/material/components';
221
+ import { useState } from 'react';
222
+
223
+ export function Booking() {
224
+ const [slot, setSlot] = useState<DateRange | undefined>();
225
+ return (
226
+ <DateRangeField
227
+ label="Booking"
228
+ value={slot}
229
+ onValueChange={setSlot}
230
+ withTime
231
+ presets={['today']}
232
+ />
233
+ );
234
+ }
235
+ ```
236
+
237
+ A day picked on the calendar keeps the time that end had; a new start is
238
+ 00:00 and a new end 23:59, as a built-in preset ends, so a filter on
239
+ `<= to` keeps the last day. On the same day, an end time set before the
240
+ start time becomes the start time.
241
+
242
+ Without `withTime`, a picked day is at 00:00. (`@nxgt/material-vue` gives
243
+ it the current time of day.)
244
+
245
+ **Emits:** `DateRange | undefined` — `{ from, to }`, where the first day
246
+ picked is both `from` and `to` (react-day-picker's `addToRange`) until the
247
+ second day is picked.
153
248
  **Form wrapper:** `DateRangeFormField` — `control` and `name`, see
154
249
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
155
- **Storybook:** `Date & time/DateRangeField` · **Vue:** `DateRangeField`, `DateRangeFormField`
250
+ **Storybook:** `Date & time/DateRangeField` (stories `DisabledDays`, `MinMaxDates`, `Presets`, `DisabledPresets`, `WithTime`) · **Vue:** `DateRangeField`, `DateRangeFormField` (`disabled-days`, `presets`, `with-time`, `min-date` / `max-date`; without time, a picked day carries the current time of day there, 00:00 here)
156
251
 
157
252
  ---
158
253
 
@@ -235,6 +330,11 @@ export function Clock() {
235
330
  | `label` | `ReactNode` | — | header text |
236
331
  | `footer` | `ReactNode` | — | under the inputs (the OK button in `TimeField`) |
237
332
 
333
+ The hours and minutes inputs are named "Hours" and "Minutes"
334
+ (`material.time-picker.hours`, `.minutes`). AM / PM shows the half of the day
335
+ `date` is in, so a date changed from outside shows its own; switching it moves
336
+ the time 12 hours, and a click on the pressed half changes nothing.
337
+
238
338
  **Emits:** `Date | undefined`. No form wrapper — use `TimeFormField`.
239
339
  **Storybook:** `Date & time/TimePicker` · **Vue:** `TimePicker`
240
340
 
@@ -227,7 +227,7 @@ built on `AlertDialog`, so it does not close on an outside click. Put
227
227
  `AlertDialogCancel` / `AlertDialogAction` in its `footer`.
228
228
 
229
229
  **Storybook:** `Feedback & status/ConfirmDialog`, `Feedback & status/ConfirmationDialog`,
230
- `Feedback & status/CustomAlertDialog` · **Vue:** `ConfirmDialog`, `ConfirmationDialog`, `CustomAlertDialog`
230
+ `Feedback & status/CustomAlertDialog` · **Vue:** `ConfirmDialog`, `ConfirmationDialog` (`form-options` + `#form` slot for `renderForm`), `CustomAlertDialog`
231
231
 
232
232
  ---
233
233
 
@@ -218,6 +218,7 @@ export function Quantity() {
218
218
  | `hideSteppers` | `boolean` | `false` | hides the +/− buttons |
219
219
  | `suffix` | `ReactNode` | — | unit after the input |
220
220
  | `aria-label` | `string` | — | names the input when it has no `label` (a part of a larger field) |
221
+ | `aria-labelledby` | `string` | — | names the input by another element's id (a part of a larger field) |
221
222
 
222
223
  **Emits:** `number | null`.
223
224
  **Form wrapper:** `NumberFormField` — `control` and `name`, see
@@ -506,6 +507,9 @@ import { InlineEdit } from '@nxgt/material/components';
506
507
  | `placeholder` | `string` | "Click to edit" | shown while empty |
507
508
  | `disabled` | `boolean` | `false` | read-only text |
508
509
 
510
+ Enter, Escape, Save and Cancel return focus to the field, while a click
511
+ elsewhere saves and leaves focus where it went.
512
+
509
513
  **Emits:** `string`, trimmed, on save only.
510
514
  **Form wrapper:** `InlineEditFormField` — `control` and `name`, see
511
515
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
@@ -639,7 +643,9 @@ names the thumbs); a range's thumbs add "Minimum" and "Maximum":
639
643
  `getByRole('slider', { name: 'Price Minimum' })`. A labelled range is also a
640
644
  `role="group"` named by the label, and its readout inputs are named
641
645
  "Minimum" and "Maximum" (`material.slider.minimum` / `maximum`), so two range
642
- sliders are told apart. `aria-invalid` (or `error`) goes on every thumb too,
646
+ sliders are told apart. A single value's readout input is named as its thumb
647
+ is, and "Value" (`material.slider.value`) when nothing names the slider.
648
+ `aria-invalid` (or `error`) goes on every thumb too,
643
649
  not on the root `span`.
644
650
 
645
651
  `SliderField` is the same component under `@nxgt/material-vue`'s name (an
@@ -652,7 +658,7 @@ form's value is a number, and `number[]` when it is an array — the default
652
658
  value decides.
653
659
  **Form wrapper:** `SliderFormField` — `control` and `name`, see
654
660
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
655
- **Storybook:** `Form fields/Slider` · **Vue:** `Slider`, `SliderField`, `SliderFormField`
661
+ **Storybook:** `Form fields/Slider` · **Vue:** `Slider`, `SliderField` (`ticks`, `leading` / `trailing` or their slots), `SliderFormField`
656
662
 
657
663
  ---
658
664
 
@@ -11,6 +11,7 @@ them into a form.
11
11
  | <img src="../previews/media-files-s3uploadfield.webp" width="96" alt=""> | [S3UploadField, NetworkUploadField](#s3uploadfield-networkuploadfield) | Drag-and-drop uploader with progress, retry, previews and size / count limits |
12
12
  | <img src="../previews/media-files-uploadfield.webp" width="96" alt=""> | [UploadField](#uploadfield) | Labelled file input showing the chosen file's name |
13
13
  | <img src="../previews/media-files-filelist.webp" width="96" alt=""> | [FileList](#filelist) | File list with type icons, size, download and remove |
14
+ | <img src="../previews/media-files-filepreview.webp" width="96" alt=""> | [FilePreview](#filepreview) | One file previewed by its type: image, PDF, video, audio, text, or a download card |
14
15
  | | [FilesFormField](#filesformfield) | Free-solo autocomplete of file URLs, with image thumbnails |
15
16
  | <img src="../previews/media-files-imagefield.webp" width="96" alt=""> | [ImageField](#imagefield) | Single image upload with preview and progress |
16
17
  | <img src="../previews/media-files-imagecropper.webp" width="96" alt=""> | [ImageCropper](#imagecropper) | Crop with zoom, rotation, aspect presets and a round shape |
@@ -221,6 +222,75 @@ dropzone as well read the error twice, and the group still says it once
221
222
 
222
223
  ---
223
224
 
225
+ ## FilePreview
226
+
227
+ ![FilePreview](../previews/media-files-filepreview.webp)
228
+
229
+ Previews one file, picked by its MIME type (else its extension): an image, a PDF, a video or audio file, the text of a text, JSON, CSV or Markdown file, and a download card for anything else.
230
+
231
+ ```tsx
232
+ import { useState } from 'react';
233
+ import { FilePreview } from '@nxgt/material/components';
234
+
235
+ export function Attachment() {
236
+ const [file, setFile] = useState<File | null>(null);
237
+ return (
238
+ <>
239
+ <input type="file" onChange={(event) => setFile(event.target.files?.[0] ?? null)} />
240
+ {/* A File or Blob: shown through an object URL, revoked on change and unmount. */}
241
+ <FilePreview file={file} onError={(error) => console.warn(error)} />
242
+ {/* A URL: the kind comes from `type`, else the extension; `name` is the alt text. */}
243
+ <FilePreview file="/api/files/42" type="application/pdf" name="contract.pdf" />
244
+ </>
245
+ );
246
+ }
247
+ ```
248
+
249
+ | Prop | Type | Default | Effect |
250
+ | --- | --- | --- | --- |
251
+ | `file` | `File \| Blob \| string \| null` | — | the file, or its URL; nothing renders without one. A URL must be `http(s):`, relative, `blob:`, or a `data:` URL of an image, video, audio, plain text, CSV, Markdown, JSON or PDF; any other (`javascript:`, `data:text/html`, and also a `data:` URL of XML, TSV or YAML) shows the error state, with no link and nothing loaded |
252
+ | `type` | `string` | the `Blob`'s, or a `data:` URL's | MIME type; picks the kind before the extension does |
253
+ | `name` | `string` | the `File`'s, else the URL's last segment | shown on the card, the image's alt text and the download's file name; "Untitled file" when there is none |
254
+ | `maxTextBytes` | `number` | `262144` (`TEXT_PREVIEW_MAX_BYTES`, 256 KB) | how much of a text kind is read; past it, the preview shows the beginning and says so |
255
+ | `onError` | `(error: unknown) => void` | — | the file could not be read (an HTTP error: `FilePreviewHttpError`, with its `status`; a refused URL: `FilePreviewUnsafeUrlError`) or shown (a broken image, video or audio file) |
256
+ | `className`, other `<figure>` props | | | on the root `<figure>` |
257
+
258
+ | Kind | Picked by | Renders |
259
+ | --- | --- | --- |
260
+ | `image` | `image/*`; `.png` `.jpg` `.jpeg` `.gif` `.webp` `.avif` `.svg` `.bmp` `.ico` | `<img>`, its alt text the file name |
261
+ | `pdf` | `application/pdf`; `.pdf` | [`PdfReader`](#pdfreader), imported the first time a PDF is previewed; the card when the viewer cannot be loaded. A PDF the reader fails to open leaves the preview `ready`: `PdfReader` shows its own error, and `onError` is not called |
262
+ | `video` | `video/*`; `.mp4` `.m4v` `.webm` `.ogv` `.mov` | `<video controls>` |
263
+ | `audio` | `audio/*`; `.mp3` `.wav` `.ogg` `.oga` `.m4a` `.aac` `.flac` `.opus` | `<audio controls>` |
264
+ | `json` | `application/json`, `text/json`, `*+json`; `.json` | [`JsonViewer`](data-display.md#codeblock) |
265
+ | `markdown` | `text/markdown`, `text/x-markdown`; `.md` `.markdown` | [`Markdown`](data-display.md#markdown), rendered; its links are sanitized like any `Markdown`, but a remote image in it still loads, so an untrusted `.md` can make the browser request a URL of its choosing |
266
+ | `csv` | `text/csv`, `application/csv`, `text/tab-separated-values`; `.csv` `.tsv` | the text, in a `<pre>` |
267
+ | `text` | any other `text/*`, `application/xml`, `application/yaml`, `application/javascript`…; `.txt` `.log` `.xml` `.yaml` `.yml` `.ini` `.toml` | the text, in a `<pre>` |
268
+ | `other` | anything else, `application/octet-stream` without a known extension | a [`FileCard`](data-display.md#card-presets): icon, name, size and type, and a Download link |
269
+
270
+ A text kind is fetched (a URL) or read (a `Blob`) up to `maxTextBytes` and
271
+ decoded with the `charset` of the response's `Content-Type` (or of the
272
+ `Blob`'s `type`) when the browser's `TextDecoder` supports it, else as UTF-8;
273
+ a JSON or Markdown file cut by the cap is shown as plain text, with "Showing
274
+ the first 256.0 KB of this file." below it. The `<pre>` and the Markdown box
275
+ are regions named "Contents of {name}"; they scroll and take focus, so a
276
+ keyboard can scroll them.
277
+
278
+ The root is a `<figure>` named "Preview of {name}", with `data-kind` and
279
+ `data-state`: `pending` on the server and in the first client render — an
280
+ empty placeholder, with no object URL made and nothing fetched, so it hydrates
281
+ without a mismatch — then `loading` ("Loading preview…", put into a `status` live region that
282
+ stays mounted, so it is announced), `ready`
283
+ or `error` ("This file could not be previewed.", an `alert`, with a Download
284
+ link when there is something to download). The kind comes from
285
+ `fileKind({ type, name, url })`, exported too, from
286
+ [`@nxgt/material-core/file-preview`](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/README.md#a-files-preview-file-preview).
287
+ Every string is translated (`material.file-preview.*`, see the
288
+ [i18n guide](../guides/i18n.md)).
289
+
290
+ **Storybook:** `Media & files/FilePreview` · **Vue:** `FilePreview`
291
+
292
+ ---
293
+
224
294
  ## FilesFormField
225
295
 
226
296
  No preview: it has no story of its own. It is an [`Autocomplete`](selection-and-choice.md)
@@ -257,7 +327,7 @@ function Gallery() {
257
327
  | `freeSolo` | `boolean` | `true` | accept values that are not in `options` |
258
328
 
259
329
  **Form wrapper:** it is one — `control` and `name`, see [forms](../guides/forms.md).
260
- **Storybook:** — · **Vue:** —
330
+ **Storybook:** — · **Vue:** `FilesFormField`
261
331
 
262
332
  ---
263
333
 
@@ -593,7 +593,7 @@ Parts: `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`.
593
593
  | `onValueChange` (`Tabs`) | `(value: string) => void` | — | called when a trigger is selected |
594
594
  | `icon` (`TabsTrigger`) | `ReactNode` | — | shown before the label (above it on the phone in `bar`) |
595
595
 
596
- **Storybook:** `Primitives/Tabs` · **Vue:** `Tabs` (same parts)
596
+ **Storybook:** `Primitives/Tabs` · **Vue:** `Tabs` (same parts, `variant="line"`)
597
597
 
598
598
  ---
599
599
 
@@ -372,7 +372,7 @@ function Config() {
372
372
 
373
373
  **Form wrapper:** `CodeEditorFormField` — `control` and `name`, see
374
374
  [forms](../guides/forms.md).
375
- **Storybook:** `Rich widgets/CodeEditor` · **Vue:** `CodeEditor`, `CodeEditorFormField`
375
+ **Storybook:** `Rich widgets/CodeEditor` · **Vue:** `CodeEditor` (`options`), `CodeEditorFormField`
376
376
 
377
377
  ---
378
378
 
@@ -23,6 +23,7 @@ value type each one writes. Everything is imported from
23
23
  | <img src="../previews/selection-choice-selectchipfield.webp" width="96" alt=""> | [SelectChipField](#selectchipfield) | Choice shown as chips |
24
24
  | <img src="../previews/selection-choice-transferlist.webp" width="96" alt=""> | [TransferList](#transferlist) | Two-pane picker |
25
25
  | <img src="../previews/selection-choice-sortablelist.webp" width="96" alt=""> | [SortableList](#sortablelist) | Reorderable list of strings |
26
+ | <img src="../previews/selection-choice-treeselectfield.webp" width="96" alt=""> | [TreeSelectField](#treeselectfield) | Pick from a tree, single or multiple |
26
27
  | <img src="../previews/selection-choice-switcher.webp" width="96" alt=""> | [Switcher](#switcher) | Workspace or account switcher |
27
28
 
28
29
  ## Select
@@ -137,7 +138,7 @@ export function Framework() {
137
138
  **Emits:** `string`.
138
139
  **Form wrapper:** `SelectFormField` — `control` and `name`, see
139
140
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
140
- **Storybook:** `Selection & choice/SelectField` · **Vue:** `SelectField`, `SelectFormField`
141
+ **Storybook:** `Selection & choice/SelectField` · **Vue:** `SelectField`, `SelectFormField` (`#option` slot for `renderOption`)
141
142
 
142
143
  ---
143
144
 
@@ -291,7 +292,7 @@ Parts: `Autocomplete`, `AutocompleteChips` (the chips row it uses in
291
292
  **Emits:** `string | null` in `single` mode, `string[]` in `multiple`.
292
293
  **Form wrapper:** `AutocompleteFormField` — `control` and `name`, see
293
294
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
294
- **Storybook:** `Selection & choice/Autocomplete` · **Vue:** `Autocomplete`, `AutocompleteFormField`
295
+ **Storybook:** `Selection & choice/Autocomplete` · **Vue:** `Autocomplete`, `AutocompleteFormField` (generic; `#option`, `#selected-leading`, `#actions` slots)
295
296
 
296
297
  ---
297
298
 
@@ -627,6 +628,82 @@ export function Ranking() {
627
628
 
628
629
  ---
629
630
 
631
+ ## TreeSelectField
632
+
633
+ ![TreeSelectField](../previews/selection-choice-treeselectfield.webp)
634
+
635
+ A field that opens a `Tree` in a popover: pick one node, or several as chips,
636
+ with an optional filter.
637
+
638
+ The field is controlled only: it holds no value of its own. Pass `value` and update it in `onValueChange`;
639
+ without it, a pick changes nothing on screen.
640
+
641
+ ```tsx
642
+ import { TreeSelectField, type TreeNode } from '@nxgt/material/components';
643
+ import { useState } from 'react';
644
+
645
+ const regions: TreeNode[] = [
646
+ {
647
+ id: 'europe',
648
+ label: 'Europe',
649
+ children: [
650
+ { id: 'france', label: 'France' },
651
+ { id: 'spain', label: 'Spain' },
652
+ ],
653
+ },
654
+ { id: 'africa', label: 'Africa', children: [{ id: 'cameroon', label: 'Cameroon' }] },
655
+ ];
656
+
657
+ export function Regions() {
658
+ const [value, setValue] = useState<string[]>(['france']);
659
+ return (
660
+ <TreeSelectField
661
+ label="Regions"
662
+ items={regions}
663
+ multiple
664
+ searchable
665
+ value={value}
666
+ onValueChange={setValue}
667
+ />
668
+ );
669
+ }
670
+ ```
671
+
672
+ Single mode (no `multiple`): a pick closes the popover and the trigger shows
673
+ the node's label. Multiple: the tree has checkboxes, and each pick is a
674
+ dismissible `Chip` beside the trigger, its remove button named "Remove
675
+ {label}" (`material.chip.dismiss-label`). The trigger is a `combobox`, named by
676
+ the label, with `aria-expanded` and, while open, `aria-controls`. Opening moves
677
+ focus into the filter, else into the tree on the pick (its branches unfolded);
678
+ the tree keeps its own keys (arrows, Home / End, Enter / Space), ↓ from the
679
+ filter enters it, and Escape closes the popover and hands focus back to the
680
+ trigger.
681
+
682
+ | Prop | Type | Default | Effect |
683
+ | --- | --- | --- | --- |
684
+ | `items` | `TreeNode[]` | — | required; `Tree`'s nodes: `{ id, label, icon?, trailing?, disabled?, children? }` |
685
+ | `multiple` | `boolean` | `false` | checkboxes and chips; decides the `value` type |
686
+ | `value` | `string \| null` / `string[]` | — | the picked id, or ids with `multiple` |
687
+ | `onValueChange` | `(value: string \| null) => void` / `(value: string[]) => void` | — | per `multiple` |
688
+ | `searchable` | `boolean` | `false` | a filter above the tree; each match stays under its ancestors, which open |
689
+ | `placeholder` | `string` | "Select…" | shown while nothing is picked (`material.tree-select-field.placeholder`) |
690
+ | `searchPlaceholder` | `string` | "Search" | the filter's placeholder and name (`material.tree-select-field.search`) |
691
+ | `required` | `boolean` | — | the asterisk, and `aria-required` on the `combobox` |
692
+ | `disabled` | `boolean` | — | disables the trigger and each chip's remove button |
693
+ | `error` | `boolean` | — | error border, error helper text, `aria-invalid` on the `combobox` |
694
+ | `id`, `aria-describedby`, `data-*` | | — | on the trigger: the label is `for` the `id`, the description follows the helper text's |
695
+
696
+ Its copy is `material.tree-select-field.*`: `placeholder`, `search`, `empty` ("No
697
+ match found.") and `selected` ("{count} selected", the trigger's text in
698
+ multiple mode).
699
+
700
+ **Emits:** `string | null` (single), `string[]` (multiple).
701
+ **Form wrapper:** `TreeSelectFormField` — `control`, `name` and `multiple`, see
702
+ [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
703
+ **Storybook:** `Selection & choice/TreeSelectField` · **Vue:** `TreeSelectField`, `TreeSelectFormField`
704
+
705
+ ---
706
+
630
707
  ## Switcher
631
708
 
632
709
  ![Switcher](../previews/selection-choice-switcher.webp)
@@ -116,7 +116,7 @@ export function PlanForm() {
116
116
  | --- | --- |
117
117
  | one input (`TextFormField`, `EmailFormField`, `TextareaFormField`, `SwitchFormField`, `CheckboxFormField`, …) | its input loses focus, as a native `blur` |
118
118
  | several controls (`PasswordFormField` and its eye button, `NumberFormField` and its steppers, `PhoneFormField`, `OtpFormField`, `SliderFormField` with two thumbs, `DurationFormField`, `RatingFormField`, `RadioGroupFormField`, `CheckboxGroupFormField`, `SelectChipFormField`, `SelectCardFormField`, `SortableListFormField`, `TransferListFormField`, `InlineEditFormField`, `AttributeValuesFormFieldSet`, …) | focus leaves all of them; moving between them is not leaving |
119
- | a picker (`SelectFormField`, `ComboboxFormField`, `AutocompleteFormField`, `CountryFormField`, `TimezoneFormField`, `ColorFormField`, `DateFormField`, `DateRangeFormField`, `TimeFormField`, …) | focus leaves the trigger **and** the popover it opened. Opening the popover, or picking in it, is not leaving; closing it hands focus back to the trigger, and the next Tab or click elsewhere is |
119
+ | a picker (`SelectFormField`, `ComboboxFormField`, `AutocompleteFormField`, `TreeSelectFormField`, `CountryFormField`, `TimezoneFormField`, `ColorFormField`, `DateFormField`, `DateRangeFormField`, `TimeFormField`, …) | focus leaves the trigger **and** the popover it opened. Opening the popover, or picking in it, is not leaving; closing it hands focus back to the trigger, and the next Tab or click elsewhere is |
120
120
  | a dialog editor (`PostalAddressFormField`, `ContactsFormField`, `OpeningHoursFormField`, `AttributesFormField`, and `LocationFormField` from `@nxgt/material/map`) | focus leaves its buttons and the dialog they open. Closing the dialog hands focus back to the button that opened it |
121
121
  | an upload (`UploadFormField`, `NetworkUploadFormField`, `S3UploadFormField`, `ImageFormField`, `ImageS3FormField`, `FileListFormField`, `FileListS3FormField`) | focus leaves the drop zone and its buttons. Choosing a file in the browser's own dialog is not leaving |
122
122
  | `RichTextEditorFormField`, `CodeEditorFormField` | focus leaves the editor and its toolbar |
@@ -343,6 +343,7 @@ or fails validation.
343
343
  | `SelectChipField` · `SelectChipFormField` | same as `SelectCardField` | `''` / `[]` |
344
344
  | `TransferList` · `TransferListFormField` | `string[]`: the selected `value`s, in order | `[]` |
345
345
  | `SortableList` · `SortableListFormField` | `string[]`: the `value`s, in the user's order | `[]` |
346
+ | `TreeSelectField` · `TreeSelectFormField` | `string \| null` (a node `id`). With `multiple`: `string[]`, in the order picked | `null` / `[]` |
346
347
 
347
348
  ### Media & files
348
349
 
@@ -204,7 +204,9 @@ presets and `Min`/`Max` fields, the `variant="4"` spinner's label,
204
204
  `You are here` and zoom buttons, and the icon-only close button of
205
205
  `DialogClose`, `DrawerClose`, `SheetClose` and `SheetContent`'s
206
206
  `withCloseButton`. A range `Slider`'s readout inputs read
207
- `material.slider.minimum` / `maximum` ("Minimum", "Maximum"). The other icon-only buttons are named from it too: the
207
+ `material.slider.minimum` / `maximum` ("Minimum", "Maximum"), and a
208
+ single value's input, when nothing names the slider, `material.slider.value`
209
+ ("Value", "Valeur"). The other icon-only buttons are named from it too: the
208
210
  `Sidebar` toggle (`material.sidebar.toggle`), the data table's row expander
209
211
  (`material.table.labels.toggle-row`), its loading status, "Loading data…" /
210
212
  "Chargement des données…" (`material.table.loading`), a chip's remove button — `Chip`,
@@ -220,7 +222,10 @@ reads `material.print.table.total` ("Total", "Total") for its totals row and
220
222
  unless you pass `totalLabel` or `emptyText`; a PDF does not see your
221
223
  app's provider, so pass `PrintDocument` its `language` (see
222
224
  [printing](printing.md#translating-built-in-strings-in-a-pdf)). The keys and the French
223
- are the same in `@nxgt/material-vue`, so one set of overrides fits both. Text
225
+ are the same in `@nxgt/material-vue`, so one set of overrides fits both. Some
226
+ keys are read there only, such as `material.confirm-dialog.form-failed` ("The form could
227
+ not be loaded."), which `@nxgt/material-vue`'s `ConfirmationDialog` shows when
228
+ its on-demand form fails to load. Text
224
229
  you pass yourself always wins over the translation: a `label`,
225
230
  `Autocomplete`'s `freeSoloLabel` and `empty`, the editor's `options.labels`,
226
231
  `QRCode`'s `title`, `Barecode`'s `aria-label`, `CarouselPrevious`/`CarouselNext`
@@ -252,8 +257,46 @@ French); and the names of `DurationField`'s inputs, "Hours" and "Minutes"
252
257
  (`material['duration-field']['hours-label']` / `minutes-label`); the status a
253
258
  `Stepper` step announces after its title, "Completed" or "Error"
254
259
  (`material.stepper.completed` / `error`, "Terminée" / "Erreur" in French); and
255
- `PaginationFirst` / `PaginationLast`'s "First" / "Last"
256
- (`material.pagination.first` / `last`). `material.carousel.roledescription` /
260
+ the pagination buttons' names and text: `PaginationPrevious` /
261
+ `PaginationNext`'s "Previous" / "Next" (`material.pagination.previous` /
262
+ `next`), `PaginationFirst` / `PaginationLast`'s "First" / "Last"
263
+ (`material.pagination.first` / `last`), `PaginationBar`'s "Page {page}" on
264
+ each page button (`material.pagination.page`) and `PaginationEllipsis`'s
265
+ "More pages" (`material.pagination.more`). `TreeSelectField`'s placeholder,
266
+ search box, empty message and "{count} selected" follow the language too
267
+ (`material['tree-select-field'].placeholder` / `search` / `empty` /
268
+ `selected`; in French `selected` is an ICU plural, `{count, plural, …}`); the
269
+ `placeholder` and `searchPlaceholder` props take precedence over them. The names of
270
+ `TimePicker`'s inputs read `material.time-picker.hours` and
271
+ `material.time-picker.minutes` ("Hours" / "Minutes", "Heures" / "Minutes").
272
+ `DateRangeField`'s preset list is a group named by
273
+ `material.date-range-field.presets` ("Presets", "Plages prédéfinies"); its
274
+ built-in presets read `material.date-range-field.preset.today`,
275
+ `material.date-range-field.preset.yesterday`,
276
+ `material.date-range-field.preset.last-7-days`,
277
+ `material.date-range-field.preset.last-30-days`,
278
+ `material.date-range-field.preset.this-month`,
279
+ `material.date-range-field.preset.last-month` and
280
+ `material.date-range-field.preset.this-year` ("Today", "Yesterday", "Last 7
281
+ days", "Last 30 days", "This month", "Last month", "This year"; "Aujourd’hui",
282
+ "Hier", "7 derniers jours", "30 derniers jours", "Ce mois-ci", "Le mois
283
+ dernier", "Cette année"), and with times its two groups
284
+ `material.date-range-field.start-time` and `material.date-range-field.end-time`
285
+ ("Start time" / "End time", "Heure de début" / "Heure de fin"); its OK button
286
+ reads `material.buttons.ok`. The label of a preset of your own is yours.
287
+ `FilePreview`'s figure is named by `material.file-preview.label` ("Preview
288
+ of {name}", "Aperçu de {name}"), with `material.file-preview.untitled`
289
+ ("Untitled file", "Fichier sans nom") as the name of a file that has none; it
290
+ reads `material.file-preview.loading` ("Loading preview…", "Chargement de
291
+ l’aperçu…") while it reads the file, `material.file-preview.error` ("This
292
+ file could not be previewed.", "Impossible d’afficher l’aperçu de ce
293
+ fichier.") when it cannot show it, `material.file-preview.download`
294
+ ("Download", "Télécharger") on its download link, and
295
+ `material.file-preview.truncated` ("Showing the first {size} of this file.",
296
+ "Seuls les premiers {size} de ce fichier sont affichés.") under a text cut
297
+ by the size cap; the text or Markdown it shows scrolls in a region named by
298
+ `material.file-preview.content` ("Contents of {name}", "Contenu de {name}").
299
+ `material.carousel.roledescription` /
257
300
  `slide` and `material.attributes.buttons.add-option` are there for
258
301
  `@nxgt/material-vue`'s carousel and attribute options, so the bundles hold
259
302
  the same keys. Your `placeholder`, `sheetTitle` and `aria-label` still
package/docs/roadmap.md CHANGED
@@ -6,27 +6,16 @@ the version something shipped in.
6
6
 
7
7
  ## Now
8
8
 
9
- - **Spinner `light` colour renders** — a `Spinner` with `color="light"` shows
10
- its light ring, for loading states on dark or coloured surfaces.
11
- - **Range filters announce their field name** — the slider thumbs and the
12
- `Min`/`Max` inputs of a `slider-range` or `number-range` filter are named
13
- after the field, so a screen reader says which range is changing.
14
- - **Alert keeps long content inside** — a long title, description or unbroken
15
- string wraps inside the `Alert` instead of running past its edge.
16
- - **Stepper: unique ids, announced steps and truncated titles** — two steppers
17
- on one page no longer share ids, a screen reader hears which step is current
18
- and whether each is done or in error, and a long step title truncates.
19
- - **Layouts that hold long content** — `ListTile`, `StatCard` and `Banner`
20
- keep long titles, values and actions inside their box instead of overflowing.
21
- - **The same exports as `@nxgt/material-vue`** — `Camera`, `NativeSelect`, the
22
- `InputOTP` parts, `PaginationFirst` and `PaginationLast`, `TableEmpty`,
23
- `DialogScrollContent` and `ToastAction`, `Button`'s sizes, and the
24
- `RadioGroupField`, `SliderField` and `SwitchField` names, so code moves
25
- between the two libraries without renaming.
26
- - **DataTable loading and empty states** — skeleton rows while the data loads
27
- (the rows already shown stay in place, dimmed, during a refresh), the table
28
- announced as busy, and your own content when there are no rows, told
29
- whether a filter emptied it.
9
+ - **TreeSelectField** — pick one or several items from a tree inside a form
10
+ field, as chips when several, with an optional filter that keeps each match
11
+ under its ancestors (`TreeSelectField`, `TreeSelectFormField`).
12
+ - **DateRangeField presets and time** — one-click ranges such as Today, Last 7
13
+ days and This month, or your own, beside the calendar (`presets`), and a time
14
+ of day on each end of the range (`withTime`).
15
+ - **FilePreview** — preview a file picked by its type, in one component: an
16
+ image, a PDF in `PdfReader`, a video or audio file, the text of a text, JSON,
17
+ CSV or Markdown file, or a card with a download link for anything else
18
+ (`FilePreview`, `onError`). Safe on a server.
30
19
 
31
20
  ## Next
32
21
 
@@ -35,12 +24,6 @@ the version something shipped in.
35
24
  - **Previews that cannot go stale** — a CI check that fails when a story changes
36
25
  but its preview image does not, so the picture in the docs is the component
37
26
  you install.
38
- - **DateRangeField presets and time** — one-click ranges such as Today, Last 7
39
- days and This month, and a time of day on each end of the range.
40
- - **TreeSelectField** — pick one or several items from a tree inside a form
41
- field.
42
- - **FilePreview** — preview an image, PDF, video or text file, chosen by its
43
- type, in one component.
44
27
 
45
28
  ## Later
46
29
 
@@ -111,6 +94,28 @@ the version something shipped in.
111
94
 
112
95
  ## Shipped
113
96
 
97
+ - **Spinner `light` colour renders** — a `Spinner` with `color="light"` shows
98
+ its light ring, for loading states on dark or coloured surfaces. 2.5.0.
99
+ - **Range filters announce their field name** — the slider thumbs and the
100
+ `Min`/`Max` inputs of a `slider-range` or `number-range` filter are named
101
+ after the field, so a screen reader says which range is changing. 2.5.0.
102
+ - **Alert keeps long content inside** — a long title, description or unbroken
103
+ string wraps inside the `Alert` instead of running past its edge. 2.5.0.
104
+ - **Stepper: unique ids, announced steps and truncated titles** — two steppers
105
+ on one page no longer share ids, a screen reader hears which step is current
106
+ and whether each is done or in error, and a long step title truncates. 2.5.0.
107
+ - **Layouts that hold long content** — `ListTile`, `StatCard` and `Banner`
108
+ keep long titles, values and actions inside their box instead of overflowing.
109
+ 2.5.0.
110
+ - **The same exports as `@nxgt/material-vue`** — `Camera`, `NativeSelect`, the
111
+ `InputOTP` parts, `PaginationFirst` and `PaginationLast`, `TableEmpty`,
112
+ `DialogScrollContent` and `ToastAction`, `Button`'s sizes, and the
113
+ `RadioGroupField`, `SliderField` and `SwitchField` names, so code moves
114
+ between the two libraries without renaming. 2.5.0.
115
+ - **DataTable loading and empty states** — skeleton rows while the data loads
116
+ (the rows already shown stay in place, dimmed, during a refresh), the table
117
+ announced as busy, and your own content when there are no rows, told
118
+ whether a filter emptied it. 2.5.0.
114
119
  - **PDFs from a Next.js route handler in one line of config** —
115
120
  `withNxgtPrint(nextConfig)` from `@nxgt/material/next` adds what `/print`
116
121
  needs to `next.config.ts` — the library in `serverExternalPackages`, and