@nxgt/material-vue 0.24.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +17 -19
  2. package/dist/{Button-DOcT4Pej.js → Button-C1aaI0Gw.js} +1 -1
  3. package/dist/{HoverCardTrigger-WQe85PCH.js → HoverCardTrigger-JtHn0IFQ.js} +2 -2
  4. package/dist/{camera-BNbLUPb1.js → camera-BacVyXl0.js} +13 -13
  5. package/dist/{components-YlYuHLEI.js → components-DRBKkOqk.js} +15333 -13879
  6. package/dist/components.js +8 -8
  7. package/dist/composables.js +2 -2
  8. package/dist/dnd.js +1 -1
  9. package/dist/form-scope-VxlJuvFe.js +43 -0
  10. package/dist/{i18n-provider-DTuxugHO.js → i18n-provider-D0oYv0XS.js} +1 -1
  11. package/dist/i18n.js +4 -4
  12. package/dist/{icon-button-DKLuZ2c-.js → icon-button-B2FQtIHS.js} +1 -1
  13. package/dist/lib/components/ui/collections/tree/use-tree.d.ts +2 -0
  14. package/dist/lib/components/ui/dates/date-field/date-field.vue.d.ts +10 -1
  15. package/dist/lib/components/ui/dates/date-field/date-form-field.vue.d.ts +3 -1
  16. package/dist/lib/components/ui/dates/date-field/day-matcher.d.ts +26 -0
  17. package/dist/lib/components/ui/dates/date-field/index.d.ts +1 -0
  18. package/dist/lib/components/ui/dates/date-field/partials/date-field-content.vue.d.ts +2 -0
  19. package/dist/lib/components/ui/dates/date-range-field/date-range-field.utils.d.ts +11 -0
  20. package/dist/lib/components/ui/dates/date-range-field/date-range-field.vue.d.ts +26 -1
  21. package/dist/lib/components/ui/dates/date-range-field/date-range-form-field.vue.d.ts +3 -1
  22. package/dist/lib/components/ui/dates/date-range-field/index.d.ts +1 -0
  23. package/dist/lib/components/ui/dates/date-range-field/partials/date-range-calendar.vue.d.ts +5 -0
  24. package/dist/lib/components/ui/dates/date-range-field/partials/date-range-field-trigger.vue.d.ts +2 -0
  25. package/dist/lib/components/ui/dates/date-range-field/partials/date-range-presets.vue.d.ts +12 -0
  26. package/dist/lib/components/ui/dates/date-range-field/partials/date-range-times.vue.d.ts +13 -0
  27. package/dist/lib/components/ui/dates/date-range-field/use-date-range-presets.d.ts +23 -0
  28. package/dist/lib/components/ui/dates/time-picker/time-picker-utils.d.ts +7 -0
  29. package/dist/lib/components/ui/display/accordion-card/accordion-card.vue.d.ts +29 -1
  30. package/dist/lib/components/ui/display/avatar/Avatar.vue.d.ts +14 -3
  31. package/dist/lib/components/ui/display/avatar/index.d.ts +1 -0
  32. package/dist/lib/components/ui/display/cards/cards.gallery.d.ts +5 -9
  33. package/dist/lib/components/ui/display/qrcode/qrcode.vue.d.ts +5 -3
  34. package/dist/lib/components/ui/display/status-indicator/status-tone.d.ts +3 -0
  35. package/dist/lib/components/ui/feedback/confirmation-dialog/async-form-scope.d.ts +15 -0
  36. package/dist/lib/components/ui/feedback/confirmation-dialog/confirmation-dialog.vue.d.ts +37 -24
  37. package/dist/lib/components/ui/feedback/confirmation-dialog/index.d.ts +1 -1
  38. package/dist/lib/components/ui/feedback/confirmation-dialog/partials/confirmation-dialog-form-error.vue.d.ts +3 -0
  39. package/dist/lib/components/ui/fields/attributes/form-schema.d.ts +24 -22
  40. package/dist/lib/components/ui/fields/contacts/consts.d.ts +1 -1
  41. package/dist/lib/components/ui/fields/number-field/number-field.vue.d.ts +2 -0
  42. package/dist/lib/components/ui/fields/slider/partials/slider-field-row.vue.d.ts +32 -0
  43. package/dist/lib/components/ui/fields/slider/slider-field.vue.d.ts +22 -1
  44. package/dist/lib/components/ui/fields/slider/slider-form-field.vue.d.ts +13 -1
  45. package/dist/lib/components/ui/forms/form-scope-schema.d.ts +43 -0
  46. package/dist/lib/components/ui/forms/form-scope.vue.d.ts +33 -0
  47. package/dist/lib/components/ui/media/file-preview/file-preview-pdf.lazy.d.ts +8 -0
  48. package/dist/lib/components/ui/media/file-preview/file-preview.vue.d.ts +9 -0
  49. package/dist/lib/components/ui/media/file-preview/index.d.ts +3 -0
  50. package/dist/lib/components/ui/media/file-preview/partials/file-preview-body.vue.d.ts +21 -0
  51. package/dist/lib/components/ui/media/file-preview/partials/file-preview-card.vue.d.ts +15 -0
  52. package/dist/lib/components/ui/media/file-preview/partials/file-preview-download.vue.d.ts +11 -0
  53. package/dist/lib/components/ui/media/file-preview/partials/file-preview-media.vue.d.ts +17 -0
  54. package/dist/lib/components/ui/media/file-preview/partials/file-preview-pdf.vue.d.ts +21 -0
  55. package/dist/lib/components/ui/media/file-preview/partials/file-preview-status.vue.d.ts +22 -0
  56. package/dist/lib/components/ui/media/file-preview/partials/file-preview-text.vue.d.ts +16 -0
  57. package/dist/lib/components/ui/media/file-preview/types.d.ts +19 -0
  58. package/dist/lib/components/ui/media/file-preview/use-file-preview.d.ts +21 -0
  59. package/dist/lib/components/ui/media/file-preview/use-text-preview.d.ts +17 -0
  60. package/dist/lib/components/ui/media/files-form-field/files-form-field.vue.d.ts +29 -0
  61. package/dist/lib/components/ui/media/files-form-field/index.d.ts +2 -0
  62. package/dist/lib/components/ui/media/image-cropper/partials/image-cropper-canvas.vue.d.ts +1 -1
  63. package/dist/lib/components/ui/media/index.d.ts +2 -0
  64. package/dist/lib/components/ui/media/pdf-viewer/partials/pdf-page.vue.d.ts +1 -1
  65. package/dist/lib/components/ui/media/pdf-viewer/partials/pdf-reader-body.vue.d.ts +2 -2
  66. package/dist/lib/components/ui/media/upload/network-upload-field/use-image-preview.d.ts +9 -1
  67. package/dist/lib/components/ui/primitives/tabs/Tabs.vue.d.ts +3 -0
  68. package/dist/lib/components/ui/primitives/tabs/index.d.ts +1 -0
  69. package/dist/lib/components/ui/primitives/tabs/tabs-variant.d.ts +9 -0
  70. package/dist/lib/components/ui/selection/autocomplete/autocomplete-form-field.vue.d.ts +31 -5
  71. package/dist/lib/components/ui/selection/autocomplete/autocomplete-items.d.ts +23 -0
  72. package/dist/lib/components/ui/selection/autocomplete/autocomplete.vue.d.ts +48 -13
  73. package/dist/lib/components/ui/selection/autocomplete/filter-options.d.ts +2 -1
  74. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-box.vue.d.ts +13 -1
  75. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-list.vue.d.ts +17 -3
  76. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-items.d.ts +15 -0
  77. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-open.d.ts +15 -0
  78. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-texts.d.ts +14 -0
  79. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete.d.ts +34 -0
  80. package/dist/lib/components/ui/selection/index.d.ts +1 -0
  81. package/dist/lib/components/ui/selection/select-field/select-field.vue.d.ts +16 -1
  82. package/dist/lib/components/ui/selection/select-field/select-form-field.vue.d.ts +16 -2
  83. package/dist/lib/components/ui/selection/tree-select-field/index.d.ts +3 -0
  84. package/dist/lib/components/ui/selection/tree-select-field/partials/tree-select-panel.vue.d.ts +27 -0
  85. package/dist/lib/components/ui/selection/tree-select-field/partials/tree-select-trigger.vue.d.ts +28 -0
  86. package/dist/lib/components/ui/selection/tree-select-field/tree-select-field.vue.d.ts +8 -0
  87. package/dist/lib/components/ui/selection/tree-select-field/tree-select-form-field.vue.d.ts +8 -0
  88. package/dist/lib/components/ui/selection/tree-select-field/tree-select.utils.d.ts +29 -0
  89. package/dist/lib/components/ui/selection/tree-select-field/types.d.ts +28 -0
  90. package/dist/lib/components/ui/selection/tree-select-field/use-tree-select-field.d.ts +19 -0
  91. package/dist/lib/components/ui/widgets/code-editor/code-editor-options.d.ts +4 -1
  92. package/dist/lib/components/ui/widgets/code-editor/code-editor.vue.d.ts +7 -0
  93. package/dist/lib/components/ui/widgets/code-editor/index.d.ts +1 -0
  94. package/dist/lib/components/ui/widgets/code-editor/monaco-editor.vue.d.ts +3 -0
  95. package/dist/lib/i18n/resources/en/en.d.ts +33 -0
  96. package/dist/lib/i18n/resources/en/material.json.d.ts +36 -3
  97. package/dist/lib/i18n/resources/fr/fr.d.ts +33 -0
  98. package/dist/lib/i18n/resources/fr/material.json.d.ts +36 -3
  99. package/dist/lib/i18n/resources/resources.d.ts +66 -0
  100. package/dist/lib/i18n/translator.d.ts +1 -1
  101. package/dist/lib/nuxt/component-names.d.ts +1 -1
  102. package/dist/lib.js +2 -2
  103. package/dist/main.js +17 -17
  104. package/dist/map.js +4 -4
  105. package/dist/material-vue.css +1 -1
  106. package/dist/{pdf-outline-sidebar-0I-LGrgY.js → pdf-outline-sidebar-jJfhNjnN.js} +2 -2
  107. package/dist/{pdf-page-C74-6lyZ.js → pdf-page-BW1hXzPu.js} +1 -1
  108. package/dist/{pdf-page-list-DPsquYPY.js → pdf-page-list-D277RtWS.js} +2 -2
  109. package/dist/{pdf-reader-Bkpp7UZ7.js → pdf-reader-BReUTsn_.js} +73 -71
  110. package/dist/{pdf-sidebar-BH-1jvy-.js → pdf-sidebar-Bk6PowY1.js} +3 -3
  111. package/dist/{pdf-thumbnail-sidebar-ByqNaYfz.js → pdf-thumbnail-sidebar-BcNDFsks.js} +3 -3
  112. package/dist/{pdf-toolbar-BRnmzddi.js → pdf-toolbar-CHjaMhH9.js} +4 -4
  113. package/dist/pdf.js +2 -2
  114. package/dist/{sortable-provider-CC9eO1nm.js → sortable-provider-C83VBfGl.js} +2 -2
  115. package/dist/{stored-language-N_UoPJsY.js → stored-language-CC_vdcCN.js} +72 -6
  116. package/dist/{stx-player-BGKIDqNX.js → stx-player-BMQ1D2Li.js} +20 -20
  117. package/dist/{translator-CU0jKvzB.js → translator-B-X_1taI.js} +1 -1
  118. package/dist/{use-heading-spy-Bw1LIAkC.js → use-heading-spy-C_ySuvTl.js} +1 -1
  119. package/dist/{use-material-translation-8hUB2M-S.js → use-material-translation-B6sIk0mF.js} +3 -3
  120. package/docs/components/README.md +2 -1
  121. package/docs/components/data-display.md +51 -10
  122. package/docs/components/data-table-filter-and-lists.md +4 -0
  123. package/docs/components/date-and-time.md +116 -9
  124. package/docs/components/feedback-and-status.md +56 -4
  125. package/docs/components/form-fields.md +15 -5
  126. package/docs/components/media-and-files.md +119 -5
  127. package/docs/components/navigation-and-layout.md +2 -1
  128. package/docs/components/primitives.md +21 -4
  129. package/docs/components/rich-widgets.md +20 -3
  130. package/docs/components/selection-and-choice.md +135 -9
  131. package/docs/guides/forms.md +3 -4
  132. package/docs/guides/i18n.md +44 -4
  133. package/docs/previews/date-time-daterangefield.webp +0 -0
  134. package/docs/previews/media-files-filepreview.webp +0 -0
  135. package/docs/previews/selection-choice-treeselectfield.webp +0 -0
  136. package/docs/roadmap.md +45 -37
  137. package/docs/troubleshooting.md +140 -1
  138. package/lib/nuxt/component-names.ts +4 -0
  139. package/package.json +2 -2
@@ -10,7 +10,8 @@ printable documents — plus Leaflet maps, which live outside the barrel in
10
10
  | <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 |
11
11
  | <img src="../previews/media-files-uploadfield.webp" width="96" alt=""> | [UploadField](#uploadfield) | Labelled file input holding the chosen `File` |
12
12
  | <img src="../previews/media-files-filelist.webp" width="96" alt=""> | [FileList](#filelist) | File list with type icons, size, download and remove |
13
- | | [FilesFormField](#filesformfield) | Not in `@nxgt/material-vue` |
13
+ | <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
+ | | [FilesFormField](#filesformfield) | Free-solo autocomplete of file URLs, with image thumbnails |
14
15
  | <img src="../previews/media-files-imagefield.webp" width="96" alt=""> | [ImageField](#imagefield) | Single image upload with preview and progress |
15
16
  | <img src="../previews/media-files-imagecropper.webp" width="96" alt=""> | [ImageCropper](#imagecropper) | Crop with zoom, rotation, aspect presets and a round shape |
16
17
  | <img src="../previews/media-files-gallery.webp" width="96" alt=""> | [Gallery, LightboxGallery, MiniGallery](#gallery-lightboxgallery-minigallery) | Image / video gallery with a lightbox and selection |
@@ -231,13 +232,126 @@ dropzone as well read the error twice, and the group still says it once
231
232
 
232
233
  ---
233
234
 
235
+ ## FilePreview
236
+
237
+ ![FilePreview](../previews/media-files-filepreview.webp)
238
+
239
+ 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.
240
+
241
+ ```vue
242
+ <script setup lang="ts">
243
+ import { ref } from 'vue';
244
+ import { FilePreview } from '@nxgt/material-vue/components';
245
+
246
+ const file = ref<File | null>(null);
247
+
248
+ function pick(event: Event) {
249
+ file.value = (event.target as HTMLInputElement).files?.[0] ?? null;
250
+ }
251
+ </script>
252
+
253
+ <template>
254
+ <input type="file" @change="pick" />
255
+ <!-- A File or Blob: shown through an object URL, revoked on change and unmount. -->
256
+ <FilePreview :file="file" @error="(error) => console.warn(error)" />
257
+ <!-- A URL: the kind comes from `type`, else the extension; `name` is the alt text. -->
258
+ <FilePreview file="/api/files/42" type="application/pdf" name="contract.pdf" />
259
+ </template>
260
+ ```
261
+
262
+ | Prop | Type | Default | Effect |
263
+ | --- | --- | --- | --- |
264
+ | `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 |
265
+ | `type` | `string` | the `Blob`'s, or a `data:` URL's | MIME type; picks the kind before the extension does |
266
+ | `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 |
267
+ | `max-text-bytes` | `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 |
268
+ | `class`, other attributes | | | on the root `<figure>` |
269
+
270
+ | Kind | Picked by | Renders |
271
+ | --- | --- | --- |
272
+ | `image` | `image/*`; `.png` `.jpg` `.jpeg` `.gif` `.webp` `.avif` `.svg` `.bmp` `.ico` | `<img>`, its alt text the file name |
273
+ | `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 the `error` event is not emitted |
274
+ | `video` | `video/*`; `.mp4` `.m4v` `.webm` `.ogv` `.mov` | `<video controls>` |
275
+ | `audio` | `audio/*`; `.mp3` `.wav` `.ogg` `.oga` `.m4a` `.aac` `.flac` `.opus` | `<audio controls>` |
276
+ | `json` | `application/json`, `text/json`, `*+json`; `.json` | [`JsonViewer`](data-display.md#codeblock) |
277
+ | `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 |
278
+ | `csv` | `text/csv`, `application/csv`, `text/tab-separated-values`; `.csv` `.tsv` | the text, in a `<pre>` |
279
+ | `text` | any other `text/*`, `application/xml`, `application/yaml`, `application/javascript`…; `.txt` `.log` `.xml` `.yaml` `.yml` `.ini` `.toml` | the text, in a `<pre>` |
280
+ | `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 |
281
+
282
+ A text kind is fetched (a URL) or read (a `Blob`) up to `max-text-bytes` and
283
+ decoded with the `charset` of the response's `Content-Type` (or of the
284
+ `Blob`'s `type`) when the browser's `TextDecoder` supports it, else as UTF-8;
285
+ a JSON or Markdown file cut by the cap is shown as plain text, with "Showing
286
+ the first 256.0 KB of this file." below it. The `<pre>` and the Markdown box
287
+ are regions named "Contents of {name}"; they scroll and take focus, so a
288
+ keyboard can scroll them.
289
+
290
+ The root is a `<figure>` named "Preview of {name}", with `data-kind` and
291
+ `data-state`: `pending` on the server and before mount — an empty
292
+ placeholder, with no object URL made and nothing fetched, so it hydrates
293
+ without a mismatch — then `loading` ("Loading preview…", put into a `status` live region that
294
+ stays mounted, so it is announced), `ready`
295
+ or `error` ("This file could not be previewed.", an `alert`, with a Download
296
+ link when there is something to download). The kind comes from
297
+ `fileKind({ type, name, url })`, exported too, from
298
+ [`@nxgt/material-core/file-preview`](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/README.md#a-files-preview-file-preview).
299
+ Every string is translated (`material.file-preview.*`, see the
300
+ [i18n guide](../guides/i18n.md)).
301
+
302
+ **Emits:** `error` — `unknown`: 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).
303
+ **Storybook:** `Media & files/FilePreview` · **React:** `FilePreview`
304
+
305
+ ---
306
+
234
307
  ## FilesFormField
235
308
 
236
- Not in `@nxgt/material-vue`. For a list of file URLs use
237
- [`FileListFormField`](#filelist); for typed URLs with suggestions, an
238
- `AutocompleteFormField` with `free-solo` (see [Selection & choice](selection-and-choice.md#autocomplete)).
309
+ No preview: it has no story title of its own. It is an
310
+ [`AutocompleteFormField`](selection-and-choice.md#autocomplete) bound to
311
+ vee-validate, free-solo by default.
312
+
313
+ Free-solo autocomplete of file URLs, with image thumbnails.
314
+
315
+ ```vue
316
+ <script setup lang="ts">
317
+ import { useForm } from 'vee-validate';
318
+ import { FilesFormField } from '@nxgt/material-vue/components';
319
+
320
+ useForm({ initialValues: { photos: [] as string[] } });
321
+ </script>
239
322
 
240
- **Storybook:** — · **React:** `FilesFormField`
323
+ <template>
324
+ <FilesFormField
325
+ name="photos"
326
+ mode="multiple"
327
+ label="Photos"
328
+ placeholder="Paste an image URL"
329
+ image
330
+ :image-link-builder="(key) => `https://cdn.example.com/${key}`"
331
+ />
332
+ </template>
333
+ ```
334
+
335
+ | Prop | Type | Default | Effect |
336
+ | --- | --- | --- | --- |
337
+ | `name` | `string` | — | required; the vee-validate field |
338
+ | `mode` | `'single' \| 'multiple'` | `'single'` | one URL or a list |
339
+ | `image` | `boolean` | — | selected values show a thumbnail instead of a file icon |
340
+ | `image-link-builder` | `(value) => string` | — | maps a stored key to the thumbnail `src` |
341
+ | `options` | `T[]`, `T extends { value: string; label: string }` | `[]` | suggestions, typed by your own option type; any typed value is accepted too |
342
+ | `get-option-value` / `get-option-label` | `(option: T) => string` | `option.value` / `option.label` | read another field of your option as its value or label, as on `Autocomplete` |
343
+ | `free-solo` | `boolean` | `true` | accept values that are not in `options` |
344
+
345
+ Like `Autocomplete`, it is generic over `T`, inferred from `options`; it has
346
+ no slots of its own (the chip's leading thumbnail or file icon is its
347
+ `#selected-leading`). Its ref is typed with
348
+ `ComponentExposed`, not `InstanceType`
349
+ ([troubleshooting](../troubleshooting.md#does-not-satisfy-the-constraint-abstract-new-args-any--any)).
350
+
351
+ For uploads rather than typed URLs, use [`FileListFormField`](#filelist).
352
+
353
+ **Form wrapper:** it is one — `name` inside a vee-validate form, see [forms](../guides/forms.md#the-pair).
354
+ **Storybook:** `Selection & choice/Autocomplete` (story `FilesFormField`) · **React:** `FilesFormField`
241
355
 
242
356
  ---
243
357
 
@@ -690,7 +690,8 @@ provideDoc(
690
690
  ```
691
691
 
692
692
  `DocLink` is an anchor that navigates through `onNavigate` for an internal
693
- `href` (an external one opens in a new tab); `DefaultDocLink` is the same
693
+ `href` (an external one opens in a new tab); without `@navigate`, it and the
694
+ sidebar are plain anchors that load the page. `DefaultDocLink` is the same
694
695
  anchor without the navigation. `DocCopyPage` (`title`) copies the title and
695
696
  the page URL; `DocArticle` is the `#doc-content` landmark the skip link and
696
697
  copy-as-markdown target. Search, the phone sheet and the active heading live in a Pinia
@@ -570,7 +570,7 @@ use `DataTable` on the [Data table, filter & lists](data-table-filter-and-lists.
570
570
 
571
571
  ![Tabs](../previews/primitives-tabs.webp)
572
572
 
573
- Tabs whose triggers can take an icon.
573
+ Tabs whose triggers can take an icon. `variant="line"` is the quieter pill style.
574
574
 
575
575
  ```vue
576
576
  <script setup lang="ts">
@@ -596,18 +596,35 @@ import {
596
596
  ```
597
597
 
598
598
  Parts: `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`. An icon goes in the
599
- trigger's slot, before the label. There is no `variant` and no `icon` prop in
600
- Vue: the list is one muted pill with the active trigger raised.
599
+ trigger's slot, before the label; there is no `icon` prop in Vue. By default
600
+ (`variant="bar"`) the list is one muted pill with the active trigger raised.
601
+ `variant="line"` is React's quieter style: a full-width, scrolling strip of
602
+ light tabs, the active one on the background — `TabsList` and `TabsTrigger`
603
+ read it from `Tabs`.
604
+
605
+ ```vue
606
+ <template>
607
+ <Tabs default-value="ts" variant="line">
608
+ <TabsList>
609
+ <TabsTrigger value="ts">TypeScript</TabsTrigger>
610
+ <TabsTrigger value="bash">Bash</TabsTrigger>
611
+ </TabsList>
612
+ <TabsContent value="ts">const n = 1;</TabsContent>
613
+ <TabsContent value="bash">bun install</TabsContent>
614
+ </Tabs>
615
+ </template>
616
+ ```
601
617
 
602
618
  | Prop | Type | Default | Effect |
603
619
  | --- | --- | --- | --- |
620
+ | `variant` (`Tabs`) | `'bar' \| 'line'` | `'bar'` | the look of the list and its triggers |
604
621
  | `default-value` / `v-model` (`Tabs`) | `string \| number` | — | the selected tab, uncontrolled / controlled |
605
622
  | `orientation` (`Tabs`) | `'horizontal' \| 'vertical'` | `'horizontal'` | arrow-key direction |
606
623
  | `activation-mode` (`Tabs`) | `'automatic' \| 'manual'` | `'automatic'` | select on focus, or only on Enter / click |
607
624
  | `value` (`TabsTrigger`, `TabsContent`) | `string \| number` | — (required) | pairs a trigger with its panel |
608
625
 
609
626
  **Emits:** `update:modelValue` on `Tabs` — `string | number`.
610
- **Storybook:** `Primitives/Tabs` · **React:** `Tabs` (same parts)
627
+ **Storybook:** `Primitives/Tabs` (story `Line`) · **React:** `Tabs` (same parts)
611
628
 
612
629
  ---
613
630
 
@@ -442,12 +442,29 @@ Monaco mounts on the client only.
442
442
  | `filename` | `string` | — | shown in the header |
443
443
  | `required` | `boolean` | — | the asterisk, and `aria-required` on Monaco's textarea (its `ariaRequired` option) |
444
444
  | `label` | `string` | — | shown above the editor, and the name of Monaco's textarea (its `ariaLabel` option) |
445
+ | `options` | `CodeEditorOptions` | — | Monaco's own construction options (`IStandaloneEditorConstructionOptions`), passed as they are; they win over the editor's defaults, and a change reaches the mounted editor |
445
446
 
446
- There is no `options` pass-through in Vue yet.
447
+ ```vue
448
+ <script setup lang="ts">
449
+ import { ref } from 'vue';
450
+ import { CodeEditor, type CodeEditorOptions } from '@nxgt/material-vue/components';
451
+
452
+ const source = ref('const greeting = "hello";\n');
453
+ const options: CodeEditorOptions = { lineNumbers: 'off', fontSize: 16, minimap: { enabled: false } };
454
+ </script>
455
+
456
+ <template>
457
+ <CodeEditor v-model="source" label="Snippet" :options="options" />
458
+ </template>
459
+ ```
460
+
461
+ `CodeEditorOptions` is a type-only import: Monaco still loads on the client
462
+ only.
447
463
 
448
464
  **Emits:** `update:modelValue` — `string`; `languageChange` — `string`.
449
- **Form wrapper:** `CodeEditorFormField` (`name` plus the same props; the error
450
- message replaces `helper-text`), see [forms](../guides/forms.md#the-pair).
465
+ **Form wrapper:** `CodeEditorFormField` (`name` plus the same props, `options`
466
+ included; the error message replaces `helper-text`), see
467
+ [forms](../guides/forms.md#the-pair).
451
468
  **Storybook:** `Rich widgets/CodeEditor` · **React:** `CodeEditor`, `CodeEditorFormField`
452
469
 
453
470
  ---
@@ -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
@@ -113,7 +114,7 @@ form, use `SelectField` / `SelectFormField`.
113
114
 
114
115
  ![SelectField](../previews/selection-choice-selectfield.webp)
115
116
 
116
- Labelled select built from `options`.
117
+ Labelled select built from `options`, with an `#option` slot.
117
118
 
118
119
  ```vue
119
120
  <script setup lang="ts">
@@ -142,7 +143,23 @@ const framework = ref('nuxt');
142
143
  | `required` | `boolean` | — | an asterisk after the label, and the select's `required` |
143
144
  | `label`, `helper-text`, `error`, `disabled` | | — | the field's text and state |
144
145
 
145
- React's `renderOption` is not in Vue: an option shows its `label`.
146
+ `#option` renders each option's content in the list (React's
147
+ `renderOption`); it receives `{ option, index, selected }`. The trigger shows
148
+ the picked option's text.
149
+
150
+ ```vue
151
+ <template>
152
+ <SelectField v-model="framework" label="Framework" :options="frameworks">
153
+ <template #option="{ option, selected }">
154
+ {{ option.label }} <small v-if="selected">current</small>
155
+ </template>
156
+ </SelectField>
157
+ </template>
158
+ ```
159
+
160
+ (with the script of the example above).
161
+
162
+ `SelectFormField` passes the slot through.
146
163
 
147
164
  **Emits:** `update:modelValue` with `string`.
148
165
  **Form wrapper:** `SelectFormField` — `name` inside a vee-validate form, see
@@ -208,7 +225,7 @@ options by label. Picking the selected option again clears it.
208
225
 
209
226
  ![Autocomplete](../previews/selection-choice-autocomplete.webp)
210
227
 
211
- Single or multiple (chips) autocomplete with free-solo values and a query event.
228
+ Single or multiple (chips) autocomplete typed by its options, with free-solo values, a query event and option slots.
212
229
 
213
230
  ```vue
214
231
  <script setup lang="ts">
@@ -239,23 +256,64 @@ const picked = ref<string[]>(['nuxt']);
239
256
  | --- | --- | --- | --- |
240
257
  | `mode` | `'single' \| 'multiple'` | `'single'` | decides the `v-model` type |
241
258
  | `v-model` | `string \| string[] \| null` | `null` | per `mode` |
242
- | `options` | `{ value: string; label: string }[]` | — | required; the items |
259
+ | `options` | `T[]`, `T extends { value: string; label: string }` | — | required; the items, typed by your own option type |
260
+ | `get-option-value` | `(option: T) => string` | `option.value` | the value an option stands for |
261
+ | `get-option-label` | `(option: T) => string` | `option.label` | the text it shows and is searched by |
243
262
  | `free-solo` | `boolean` | `false` | accept a typed value that is not an option ("Add …") |
263
+ | `free-solo-label` | `(query: string) => string` | `Add "{query}"`, translated | the free-solo button's text |
244
264
  | `@query-change` | `(query: string) => void` | — | the typed text, debounced 300 ms — fetch options here |
245
265
  | `empty` | `string` | "No option found." | text when nothing matches |
246
266
  | `required` | `boolean` | — | the asterisk, and `aria-required` on the input, which the label names |
247
267
  | `error` | `boolean` | — | red border on the box, error helper text, `aria-invalid` on the input |
248
268
 
249
- React's `getOptionValue` / `getOptionLabel` and `actions` are not in Vue:
250
- options are `{ value, label }`. In `multiple` mode each chip is a dismissible
251
- `Chip`: its remove button is named "Remove {label}", and is disabled with the
252
- field.
269
+ It is a generic component: `T` is inferred from `options`, and the slots —
270
+ React's render props — receive it whole:
271
+
272
+ | Slot | Props | Replaces React's |
273
+ | --- | --- | --- |
274
+ | `#option` | `{ option: T, index, selected }` | `renderOption` |
275
+ | `#selected-leading` | `{ option: T }` | `renderSelectedLeading` (before a chip's label) |
276
+ | `#actions` | `{ query }` | `actions` (above the list) |
277
+
278
+ ```vue
279
+ <script setup lang="ts">
280
+ import { ref } from 'vue';
281
+ import { Autocomplete } from '@nxgt/material-vue/components';
282
+
283
+ type Country = { value: string; label: string; flag: string; capital: string };
284
+ const countries: Country[] = [
285
+ { value: 'fr', label: 'France', flag: '🇫🇷', capital: 'Paris' },
286
+ { value: 'ch', label: 'Switzerland', flag: '🇨🇭', capital: 'Bern' },
287
+ ];
288
+ const picked = ref<string[]>([]);
289
+ </script>
290
+
291
+ <template>
292
+ <Autocomplete
293
+ v-model="picked"
294
+ mode="multiple"
295
+ label="Countries"
296
+ :options="countries"
297
+ :get-option-label="(country) => `${country.label} (${country.capital})`"
298
+ >
299
+ <template #option="{ option }">{{ option.flag }} {{ option.label }}</template>
300
+ <template #selected-leading="{ option }">{{ option.flag }}</template>
301
+ </Autocomplete>
302
+ </template>
303
+ ```
304
+
305
+ The value stays the options' values (`string` or `string[]`), as in React. In
306
+ `multiple` mode each chip is a dismissible `Chip`: its remove button is named
307
+ "Remove {label}", and is disabled with the field. `AutocompleteFormField` is
308
+ generic too, and passes the slots through. A generic component's ref is typed
309
+ with `ComponentExposed`, not `InstanceType`
310
+ ([troubleshooting](../troubleshooting.md#does-not-satisfy-the-constraint-abstract-new-args-any--any)).
253
311
 
254
312
  **Emits:** `update:modelValue` with `string | null` in `single` mode,
255
313
  `string[]` in `multiple`.
256
314
  **Form wrapper:** `AutocompleteFormField` — `name` inside a vee-validate form, see
257
315
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
258
- **Storybook:** `Selection & choice/Autocomplete` · **React:** `Autocomplete`, `AutocompleteFormField`
316
+ **Storybook:** `Selection & choice/Autocomplete` (stories `TypedOptions`, `FilesFormField`) · **React:** `Autocomplete`, `AutocompleteFormField`
259
317
 
260
318
  ---
261
319
 
@@ -615,6 +673,74 @@ The `#item` slot replaces a row's content.
615
673
 
616
674
  ---
617
675
 
676
+ ## TreeSelectField
677
+
678
+ ![TreeSelectField](../previews/selection-choice-treeselectfield.webp)
679
+
680
+ A field that opens a `Tree` in a popover: pick one node, or several as chips,
681
+ with an optional filter.
682
+
683
+ The field is controlled only: it holds no value of its own. Bind it with `v-model`;
684
+ without it, a pick changes nothing on screen.
685
+
686
+ ```vue
687
+ <script setup lang="ts">
688
+ import { ref } from 'vue';
689
+ import { TreeSelectField, type TreeNode } from '@nxgt/material-vue/components';
690
+
691
+ const regions: TreeNode[] = [
692
+ {
693
+ id: 'europe',
694
+ label: 'Europe',
695
+ children: [
696
+ { id: 'france', label: 'France' },
697
+ { id: 'spain', label: 'Spain' },
698
+ ],
699
+ },
700
+ { id: 'africa', label: 'Africa', children: [{ id: 'cameroon', label: 'Cameroon' }] },
701
+ ];
702
+
703
+ const picked = ref<string[]>(['france']);
704
+ </script>
705
+
706
+ <template>
707
+ <TreeSelectField v-model="picked" label="Regions" :items="regions" multiple searchable />
708
+ </template>
709
+ ```
710
+
711
+ Single mode (no `multiple`): a pick closes the popover and the trigger shows
712
+ the node's label. Multiple: the tree has checkboxes, and each pick is a
713
+ dismissible `Chip` beside the trigger, its remove button named "Remove
714
+ {label}" (`material.chip.dismiss-label`). The trigger is a `combobox`, named by
715
+ the label, with `aria-expanded` and `aria-controls`. Opening moves focus into
716
+ the filter, else into the tree on the pick (its branches unfolded); the tree
717
+ keeps its own keys (arrows, Home / End, Enter / Space), ↓ from the filter
718
+ enters it, and Escape closes the popover and hands focus back to the trigger.
719
+
720
+ | Prop | Type | Default | Effect |
721
+ | --- | --- | --- | --- |
722
+ | `items` | `TreeNode[]` | — | required; `Tree`'s nodes: `{ id, label, icon?, trailing?, disabled?, children? }` |
723
+ | `v-model` | `string \| null` / `string[]` | — | the picked id, or ids with `multiple` |
724
+ | `multiple` | `boolean` | `false` | checkboxes and chips; decides the value type |
725
+ | `searchable` | `boolean` | `false` | a filter above the tree; each match stays under its ancestors, which open |
726
+ | `placeholder` | `string` | "Select…" | shown while nothing is picked (`material.tree-select-field.placeholder`) |
727
+ | `search-placeholder` | `string` | "Search" | the filter's placeholder and name (`material.tree-select-field.search`) |
728
+ | `required` | `boolean` | — | the asterisk, and `aria-required` on the `combobox` |
729
+ | `disabled` | `boolean` | — | disables the trigger and each chip's remove button |
730
+ | `error` | `boolean` | — | error border, error helper text, `aria-invalid` on the `combobox` |
731
+ | `id`, `aria-describedby`, `data-*` | | — | on the trigger: the label is `for` the `id`, the description follows the helper text's |
732
+
733
+ Its copy is `material.tree-select-field.*`: `placeholder`, `search`, `empty` ("No
734
+ match found.") and `selected` ("{count} selected", the trigger's text in
735
+ multiple mode).
736
+
737
+ **Emits:** `update:modelValue` with `string | null` (single) or `string[]` (multiple).
738
+ **Form wrapper:** `TreeSelectFormField` — `name` (and `multiple`) inside a vee-validate form, see
739
+ [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
740
+ **Storybook:** `Selection & choice/TreeSelectField` · **React:** `TreeSelectField`, `TreeSelectFormField`
741
+
742
+ ---
743
+
618
744
  ## Switcher
619
745
 
620
746
  ![Switcher](../previews/selection-choice-switcher.webp)
@@ -107,7 +107,7 @@ useForm({
107
107
  | --- | --- |
108
108
  | one input (`TextFormField`, `NumberFormField`, `SwitchFormField`, …) | its input loses focus, as a native `blur` |
109
109
  | several controls (`DateRangeFormField`, `SliderFormField` with two thumbs, `OtpFormField`, `PhoneFormField`, `TransferListFormField`, …) | focus leaves all of them; moving between them is not leaving |
110
- | a picker (`SelectFormField`, `DateFormField`, `TimeFormField`, `ComboboxFormField`, `CountryFormField`, …) | 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 |
110
+ | a picker (`SelectFormField`, `DateFormField`, `TimeFormField`, `ComboboxFormField`, `TreeSelectFormField`, `CountryFormField`, …) | 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 |
111
111
  | a dialog editor (`PostalAddressFormField`, `ContactsFormField`, `OpeningHoursFormField`, `AttributesFormField`, and `LocationFormField` from `@nxgt/material-vue/map`) | focus leaves its buttons and the dialog they open |
112
112
  | an upload (`UploadFormField`, `NetworkUploadFormField`, `ImageFormField`, `FileListFormField` and their S3 variants) | focus leaves the drop zone and its buttons. Choosing a file in the browser's own dialog is not leaving |
113
113
  | `RichTextEditorFormField`, `CodeEditorFormField` | focus leaves the editor and its toolbar |
@@ -380,6 +380,7 @@ or fails validation.
380
380
  | `SelectChipField` · `SelectChipFormField` | same as `SelectCardField` | `[]` |
381
381
  | `TransferList` · `TransferListFormField` | `string[]`: the selected `value`s, in order | `[]` |
382
382
  | `SortableList` · `SortableListFormField` | `string[]`: the `value`s, in the user's order | `[]` |
383
+ | `TreeSelectField` · `TreeSelectFormField` | `string \| null` (a node `id`). With `multiple`: `string[]`, in the order picked | `null` / `[]` |
383
384
 
384
385
  ### Media & files
385
386
 
@@ -389,6 +390,7 @@ or fails validation.
389
390
  | `S3UploadField` · `S3UploadFormField` | as above; the value is `publicUrl` from `get-presigned-url` | `undefined` / `[]` |
390
391
  | `ImageField`, `ImageS3Field` · `ImageFormField`, `ImageS3FormField` | `string \| undefined` (the uploaded URL) | `undefined` |
391
392
  | `FileListField`, `FileListS3Field` · `FileListFormField`, `FileListS3FormField` | `string[]` | `[]` |
393
+ | `FilesFormField` | `mode="single"`: `string \| null`. `mode="multiple"`: `string[]` (typed or picked URLs) | `null` / `[]` |
392
394
  | `UploadField` · `UploadFormField` | a `File`, or `File[]` with `multiple`. Nothing is uploaded | `undefined` / `[]` |
393
395
  | `RichTextEditor` · `RichTextEditorFormField` | `string`: the editor's HTML | `''` |
394
396
  | `CodeEditor` · `CodeEditorFormField` | `string`: the source | `''` |
@@ -400,9 +402,6 @@ dropped: the form holds where the file went, never the file.
400
402
  handler sends it (with `convertToFormData` from `@nxgt/material-vue/lib`,
401
403
  for example).
402
404
 
403
- Not in `@nxgt/material-vue`: `FilesFormField` (use `NetworkUploadFormField`
404
- or `FileListFormField`).
405
-
406
405
  ## Composite values
407
406
 
408
407
  A composite field edits a structured value through its own dialog. The form
@@ -288,7 +288,10 @@ the `LocationPreview` trigger (`material.map.preview` / `preview-named`),
288
288
  (`material['duration-field']['hours-label']` / `minutes-label`), and
289
289
  the delete button of the deprecated `ContactItem` / `OpeningHoursItem`
290
290
  (`material.contacts.buttons.remove`,
291
- `material['opening-hours'].buttons.remove`). The keys and the French are the
291
+ `material['opening-hours'].buttons.remove`). When the form of a
292
+ `ConfirmationDialog` with `form-options` cannot be loaded, it says so in the
293
+ form's place: "The form could not be loaded." / "Le formulaire n’a pas pu être
294
+ chargé." (`material.confirm-dialog.form-failed`). The keys and the French are the
292
295
  same in `@nxgt/material`, so one set of overrides fits both. Text you pass yourself always wins over the
293
296
  translation: a `label`, an `aria-label` on `Spinner`, `ScrollToTop`,
294
297
  `CalendarPrevButton` or `CalendarNextButton`, and `LocationMarker`'s
@@ -323,10 +326,47 @@ buttons (`material['rich-text-editor'].placeholder` / `insert-emoji`);
323
326
  (`material['circular-progress'].label`); `StxPlayer`'s "Media player" for a
324
327
  track without a title (`material['stx-player'].label`); `PaneLayoutPane`'s
325
328
  sheet title on a phone (`material['pane-layout']['sheet-title']`);
326
- `JsonViewer`'s "Empty JSON" (`material['json-viewer'].empty`); and
329
+ `JsonViewer`'s "Empty JSON" (`material['json-viewer'].empty`);
327
330
  `TimePicker`'s AM/PM (`material['time-picker'].am` / `pm`, "a.m." / "p.m." in
328
- French). Your `placeholder`, `sheetTitle`, `title`, `description` and
329
- `aria-label` still win.
331
+ French). `TreeSelectField`'s placeholder, search box, empty message and
332
+ "{count} selected" follow the language too (`material['tree-select-field'].placeholder` /
333
+ `search` / `empty` / `selected`; in French `selected` is an ICU plural,
334
+ `{count, plural, …}`); the `placeholder` and `search-placeholder` props take
335
+ precedence over them. Your `placeholder`, `sheetTitle`, `title`, `description`
336
+ and `aria-label` still win. The names of
337
+ `TimePicker`'s inputs read `material.time-picker.hours` and
338
+ `material.time-picker.minutes` ("Hours" / "Minutes", "Heures" / "Minutes").
339
+ `DateRangeField`'s preset list is a group named by
340
+ `material.date-range-field.presets` ("Presets", "Plages prédéfinies"); its
341
+ built-in presets read `material.date-range-field.preset.today`,
342
+ `material.date-range-field.preset.yesterday`,
343
+ `material.date-range-field.preset.last-7-days`,
344
+ `material.date-range-field.preset.last-30-days`,
345
+ `material.date-range-field.preset.this-month`,
346
+ `material.date-range-field.preset.last-month` and
347
+ `material.date-range-field.preset.this-year` ("Today", "Yesterday", "Last 7
348
+ days", "Last 30 days", "This month", "Last month", "This year"; "Aujourd’hui",
349
+ "Hier", "7 derniers jours", "30 derniers jours", "Ce mois-ci", "Le mois
350
+ dernier", "Cette année"), and with times its two groups
351
+ `material.date-range-field.start-time` and `material.date-range-field.end-time`
352
+ ("Start time" / "End time", "Heure de début" / "Heure de fin"); its OK button
353
+ reads `material.buttons.ok`. The label of a preset of your own is yours.
354
+ `FilePreview`'s figure is named by `material.file-preview.label` ("Preview
355
+ of {name}", "Aperçu de {name}"), with `material.file-preview.untitled`
356
+ ("Untitled file", "Fichier sans nom") as the name of a file that has none; it
357
+ reads `material.file-preview.loading` ("Loading preview…", "Chargement de
358
+ l’aperçu…") while it reads the file, `material.file-preview.error` ("This
359
+ file could not be previewed.", "Impossible d’afficher l’aperçu de ce
360
+ fichier.") when it cannot show it, `material.file-preview.download`
361
+ ("Download", "Télécharger") on its download link, and
362
+ `material.file-preview.truncated` ("Showing the first {size} of this file.",
363
+ "Seuls les premiers {size} de ce fichier sont affichés.") under a text cut
364
+ by the size cap; the text or Markdown it shows scrolls in a region named by
365
+ `material.file-preview.content` ("Contents of {name}", "Contenu de {name}").
366
+ `material.slider.value` / `minimum` / `maximum`
367
+ ("Value", "Minimum", "Maximum") are there for `@nxgt/material`'s editable
368
+ `Slider` readout inputs (this package's readout is text), so the bundles hold
369
+ the same keys.
330
370
 
331
371
  To format filter values yourself in the provider's language, pass
332
372
  `formatFilterValue` the same words `FilterChips` uses (`FilterValueWords`; the
package/docs/roadmap.md CHANGED
@@ -6,31 +6,7 @@ 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
- - **Slider thumb follows the theme in dark mode** — the `Slider` thumb takes
17
- the dark theme's colours instead of staying light. Vue only.
18
- - **Stepper: unique ids, announced steps and truncated titles** — two steppers
19
- on one page no longer share ids, a screen reader hears which step is current
20
- and whether each is done or in error, and a long step title truncates.
21
- - **Layouts that hold long content** — `ListTile`, `StatCard` and `Banner`
22
- keep long titles, values and actions inside their box instead of overflowing.
23
- - **The same exports as `@nxgt/material`** — a `Checkbox` with its own
24
- `label` and `helper-text` (the bare box is unchanged) and
25
- `CheckboxFormField`, `InlineEditFormField`, `CodeEditorFormField`,
26
- `PaginationBar` with `paginationItems` and `PaginationLink`, `CopyValue`,
27
- `MermaidDiagram`, and the `Doc` parts `DocArticle`, `DocNav`, `DocLink`,
28
- `DefaultDocLink` and `DocCopyPage`, under the names they have in React.
29
- ([forms](guides/forms.md#what-each-field-emits))
30
- - **DataTable loading and empty states** — skeleton rows while the data loads
31
- (the rows already shown stay in place, dimmed, during a refresh), the table
32
- announced as busy, and your own content when there are no rows, told
33
- whether a filter emptied it.
9
+ Nothing in progress right now; see Next.
34
10
 
35
11
  ## Next
36
12
 
@@ -40,18 +16,6 @@ the version something shipped in.
40
16
  Vue only: `@nxgt/material`'s print blocks already produce a PDF.
41
17
  - **`StepperIndicator`, as in `@nxgt/material`** — the step markers on their
42
18
  own, for a stepper laid out your way.
43
- - **The same options as `@nxgt/material`** — a generic `Autocomplete` and
44
- `FilesFormField` typed by your items, a `ConfirmationDialog` that holds a
45
- form, date fields that disable chosen days, `SliderField` ticks and
46
- leading/trailing content, a presence status on `Avatar`, a logo in the QR
47
- code, slots on `AccordionCard`, an option slot on `SelectField`, editor
48
- options on `CodeEditor` and a line variant for `Tabs`.
49
- - **DateRangeField presets and time** — one-click ranges such as Today, Last 7
50
- days and This month, and a time of day on each end of the range.
51
- - **TreeSelectField** — pick one or several items from a tree inside a form
52
- field.
53
- - **FilePreview** — preview an image, PDF, video or text file, chosen by its
54
- type, in one component.
55
19
 
56
20
  ## Later
57
21
 
@@ -128,6 +92,50 @@ the version something shipped in.
128
92
 
129
93
  ## Shipped
130
94
 
95
+ - **TreeSelectField** — pick one or several items from a tree inside a form
96
+ field, as chips when several, with an optional filter that keeps each match
97
+ under its ancestors (`TreeSelectField`, `TreeSelectFormField`). 0.25.0.
98
+ - **The same options as `@nxgt/material`** — a generic `Autocomplete` and
99
+ `FilesFormField` typed by your items, a `ConfirmationDialog` that holds a
100
+ form, date fields that disable chosen days, `SliderField` ticks and
101
+ leading/trailing content, a presence status on `Avatar`, a logo slot on the
102
+ QR code, slots on `AccordionCard`, an option slot on `SelectField`, Monaco's
103
+ own options on `CodeEditor` and a line variant for `Tabs`. 0.25.0.
104
+ - **DateRangeField presets and time** — one-click ranges such as Today, Last 7
105
+ days and This month, or your own, beside the calendar (`presets`), and a time
106
+ of day on each end of the range (`with-time`), and `min-date` / `max-date`.
107
+ 0.25.0.
108
+ - **FilePreview** — preview a file picked by its type, in one component: an
109
+ image, a PDF in `PdfReader`, a video or audio file, the text of a text, JSON,
110
+ CSV or Markdown file, or a card with a download link for anything else
111
+ (`FilePreview`, `@error`). Safe on a server. 0.25.0.
112
+ - **Spinner `light` colour renders** — a `Spinner` with `color="light"` shows
113
+ its light ring, for loading states on dark or coloured surfaces. 0.24.0.
114
+ - **Range filters announce their field name** — the slider thumbs and the
115
+ `Min`/`Max` inputs of a `slider-range` or `number-range` filter are named
116
+ after the field, so a screen reader says which range is changing. 0.24.0.
117
+ - **Alert keeps long content inside** — a long title, description or unbroken
118
+ string wraps inside the `Alert` instead of running past its edge. 0.24.0.
119
+ - **Slider thumb follows the theme in dark mode** — the `Slider` thumb takes
120
+ the dark theme's colours instead of staying light. Vue only. 0.24.0.
121
+ - **Stepper: unique ids, announced steps and truncated titles** — two steppers
122
+ on one page no longer share ids, a screen reader hears which step is current
123
+ and whether each is done or in error, and a long step title truncates. 0.24.0.
124
+ - **Layouts that hold long content** — `ListTile`, `StatCard` and `Banner`
125
+ keep long titles, values and actions inside their box instead of overflowing.
126
+ 0.24.0.
127
+ - **The same exports as `@nxgt/material`** — a `Checkbox` with its own
128
+ `label` and `helper-text` (the bare box is unchanged) and
129
+ `CheckboxFormField`, `InlineEditFormField`, `CodeEditorFormField`,
130
+ `PaginationBar` with `paginationItems` and `PaginationLink`, `CopyValue`,
131
+ `MermaidDiagram`, and the `Doc` parts `DocArticle`, `DocNav`, `DocLink`,
132
+ `DefaultDocLink` and `DocCopyPage`, under the names they have in React.
133
+ 0.24.0.
134
+ ([forms](guides/forms.md#what-each-field-emits))
135
+ - **DataTable loading and empty states** — skeleton rows while the data loads
136
+ (the rows already shown stay in place, dimmed, during a refresh), the table
137
+ announced as busy, and your own content when there are no rows, told
138
+ whether a filter emptied it. 0.24.0.
131
139
  - **One copy of the framework-free code, shared with `@nxgt/material`** —
132
140
  the PDF document model and renderer, the print formatters, the PDF file
133
141
  helpers, `PdfReader`'s page geometry, scroll spy and search, the