@nxgt/material-vue 0.23.4 → 0.25.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 (182) hide show
  1. package/README.md +30 -28
  2. package/dist/{Button-CVIR6uLK.js → Button-C1aaI0Gw.js} +95 -72
  3. package/dist/{HoverCardTrigger-iAL5ALrL.js → HoverCardTrigger-JtHn0IFQ.js} +2 -2
  4. package/dist/{camera-Gvg5N956.js → camera-BacVyXl0.js} +6 -6
  5. package/dist/{components-AScwCcds.js → components-DRBKkOqk.js} +15764 -13833
  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-BqWxpaKu.js → i18n-provider-D0oYv0XS.js} +1 -1
  11. package/dist/i18n.js +4 -4
  12. package/dist/{icon-button-DbEME76t.js → icon-button-B2FQtIHS.js} +34 -37
  13. package/dist/lib/components/ui/actions/button/has-text-content.d.ts +10 -0
  14. package/dist/lib/components/ui/actions/copy-button/copy-value.vue.d.ts +21 -0
  15. package/dist/lib/components/ui/actions/copy-button/index.d.ts +2 -0
  16. package/dist/lib/components/ui/collections/data-table/data-table-context.d.ts +6 -0
  17. package/dist/lib/components/ui/collections/data-table/data-table-pagination.vue.d.ts +2 -0
  18. package/dist/lib/components/ui/collections/data-table/data-table.vue.d.ts +17 -27
  19. package/dist/lib/components/ui/collections/data-table/index.d.ts +1 -1
  20. package/dist/lib/components/ui/collections/data-table/partials/data-table-body.vue.d.ts +10 -1
  21. package/dist/lib/components/ui/collections/data-table/partials/data-table-empty.vue.d.ts +13 -1
  22. package/dist/lib/components/ui/collections/data-table/partials/data-table-head.vue.d.ts +22 -0
  23. package/dist/lib/components/ui/collections/data-table/partials/data-table-skeleton-rows.vue.d.ts +10 -0
  24. package/dist/lib/components/ui/collections/data-table/use-data-table.d.ts +17 -0
  25. package/dist/lib/components/ui/collections/filter/partials/filter-field-slider-range.vue.d.ts +1 -4
  26. package/dist/lib/components/ui/collections/pagination/index.d.ts +5 -0
  27. package/dist/lib/components/ui/collections/pagination/pagination-bar.vue.d.ts +21 -0
  28. package/dist/lib/components/ui/collections/pagination/pagination-items.d.ts +2 -0
  29. package/dist/lib/components/ui/collections/pagination/pagination-link.vue.d.ts +18 -0
  30. package/dist/lib/components/ui/collections/tree/use-tree.d.ts +2 -0
  31. package/dist/lib/components/ui/dates/date-field/date-field.vue.d.ts +10 -1
  32. package/dist/lib/components/ui/dates/date-field/date-form-field.vue.d.ts +3 -1
  33. package/dist/lib/components/ui/dates/date-field/day-matcher.d.ts +26 -0
  34. package/dist/lib/components/ui/dates/date-field/index.d.ts +1 -0
  35. package/dist/lib/components/ui/dates/date-field/partials/date-field-content.vue.d.ts +2 -0
  36. package/dist/lib/components/ui/dates/date-range-field/date-range-field.utils.d.ts +11 -0
  37. package/dist/lib/components/ui/dates/date-range-field/date-range-field.vue.d.ts +26 -1
  38. package/dist/lib/components/ui/dates/date-range-field/date-range-form-field.vue.d.ts +3 -1
  39. package/dist/lib/components/ui/dates/date-range-field/index.d.ts +1 -0
  40. package/dist/lib/components/ui/dates/date-range-field/partials/date-range-calendar.vue.d.ts +5 -0
  41. package/dist/lib/components/ui/dates/date-range-field/partials/date-range-field-trigger.vue.d.ts +2 -0
  42. package/dist/lib/components/ui/dates/date-range-field/partials/date-range-presets.vue.d.ts +12 -0
  43. package/dist/lib/components/ui/dates/date-range-field/partials/date-range-times.vue.d.ts +13 -0
  44. package/dist/lib/components/ui/dates/date-range-field/use-date-range-presets.d.ts +23 -0
  45. package/dist/lib/components/ui/dates/time-picker/time-picker-utils.d.ts +7 -0
  46. package/dist/lib/components/ui/display/accordion-card/accordion-card.vue.d.ts +29 -1
  47. package/dist/lib/components/ui/display/avatar/Avatar.vue.d.ts +14 -3
  48. package/dist/lib/components/ui/display/avatar/index.d.ts +1 -0
  49. package/dist/lib/components/ui/display/cards/cards.gallery.d.ts +5 -9
  50. package/dist/lib/components/ui/display/markdown/index.d.ts +18 -0
  51. package/dist/lib/components/ui/display/qrcode/qrcode.vue.d.ts +5 -3
  52. package/dist/lib/components/ui/display/status-indicator/status-tone.d.ts +3 -0
  53. package/dist/lib/components/ui/feedback/confirmation-dialog/async-form-scope.d.ts +15 -0
  54. package/dist/lib/components/ui/feedback/confirmation-dialog/confirmation-dialog.vue.d.ts +37 -24
  55. package/dist/lib/components/ui/feedback/confirmation-dialog/index.d.ts +1 -1
  56. package/dist/lib/components/ui/feedback/confirmation-dialog/partials/confirmation-dialog-form-error.vue.d.ts +3 -0
  57. package/dist/lib/components/ui/feedback/skeleton/skeleton-text.vue.d.ts +1 -1
  58. package/dist/lib/components/ui/feedback/spinner/spinner.compound-variants.d.ts +4 -0
  59. package/dist/lib/components/ui/fields/attributes/form-schema.d.ts +24 -22
  60. package/dist/lib/components/ui/fields/contacts/consts.d.ts +1 -1
  61. package/dist/lib/components/ui/fields/inline-edit/index.d.ts +1 -0
  62. package/dist/lib/components/ui/fields/inline-edit/inline-edit-form-field.vue.d.ts +7 -0
  63. package/dist/lib/components/ui/fields/number-field/number-field.vue.d.ts +2 -0
  64. package/dist/lib/components/ui/fields/slider/partials/slider-field-row.vue.d.ts +32 -0
  65. package/dist/lib/components/ui/fields/slider/slider-field.vue.d.ts +22 -1
  66. package/dist/lib/components/ui/fields/slider/slider-form-field.vue.d.ts +13 -1
  67. package/dist/lib/components/ui/forms/form-scope-schema.d.ts +43 -0
  68. package/dist/lib/components/ui/forms/form-scope.vue.d.ts +33 -0
  69. package/dist/lib/components/ui/layout/doc/doc.vue.d.ts +5 -5
  70. package/dist/lib/components/ui/layout/doc/index.d.ts +8 -1
  71. package/dist/lib/components/ui/layout/doc/partials/default-doc-link.vue.d.ts +14 -0
  72. package/dist/lib/components/ui/layout/doc/partials/doc-copy-page.vue.d.ts +6 -0
  73. package/dist/lib/components/ui/layout/doc/partials/doc-link.vue.d.ts +3 -7
  74. package/dist/lib/components/ui/layout/doc/types.d.ts +6 -1
  75. package/dist/lib/components/ui/media/file-preview/file-preview-pdf.lazy.d.ts +8 -0
  76. package/dist/lib/components/ui/media/file-preview/file-preview.vue.d.ts +9 -0
  77. package/dist/lib/components/ui/media/file-preview/index.d.ts +3 -0
  78. package/dist/lib/components/ui/media/file-preview/partials/file-preview-body.vue.d.ts +21 -0
  79. package/dist/lib/components/ui/media/file-preview/partials/file-preview-card.vue.d.ts +15 -0
  80. package/dist/lib/components/ui/media/file-preview/partials/file-preview-download.vue.d.ts +11 -0
  81. package/dist/lib/components/ui/media/file-preview/partials/file-preview-media.vue.d.ts +17 -0
  82. package/dist/lib/components/ui/media/file-preview/partials/file-preview-pdf.vue.d.ts +21 -0
  83. package/dist/lib/components/ui/media/file-preview/partials/file-preview-status.vue.d.ts +22 -0
  84. package/dist/lib/components/ui/media/file-preview/partials/file-preview-text.vue.d.ts +16 -0
  85. package/dist/lib/components/ui/media/file-preview/types.d.ts +19 -0
  86. package/dist/lib/components/ui/media/file-preview/use-file-preview.d.ts +21 -0
  87. package/dist/lib/components/ui/media/file-preview/use-text-preview.d.ts +17 -0
  88. package/dist/lib/components/ui/media/files-form-field/files-form-field.vue.d.ts +29 -0
  89. package/dist/lib/components/ui/media/files-form-field/index.d.ts +2 -0
  90. package/dist/lib/components/ui/media/image-cropper/partials/image-cropper-canvas.vue.d.ts +1 -1
  91. package/dist/lib/components/ui/media/index.d.ts +2 -0
  92. package/dist/lib/components/ui/media/pdf-viewer/index.d.ts +6 -6
  93. package/dist/lib/components/ui/media/pdf-viewer/partials/pdf-page.vue.d.ts +1 -1
  94. package/dist/lib/components/ui/media/pdf-viewer/partials/pdf-reader-body.vue.d.ts +2 -2
  95. package/dist/lib/components/ui/media/pdf-viewer/pdf-reader.vue.d.ts +2 -2
  96. package/dist/lib/components/ui/media/upload/network-upload-field/use-image-preview.d.ts +9 -1
  97. package/dist/lib/components/ui/primitives/icon/icon-context.d.ts +1 -1
  98. package/dist/lib/components/ui/primitives/tabs/Tabs.vue.d.ts +3 -0
  99. package/dist/lib/components/ui/primitives/tabs/index.d.ts +1 -0
  100. package/dist/lib/components/ui/primitives/tabs/tabs-variant.d.ts +9 -0
  101. package/dist/lib/components/ui/selection/autocomplete/autocomplete-form-field.vue.d.ts +31 -5
  102. package/dist/lib/components/ui/selection/autocomplete/autocomplete-items.d.ts +23 -0
  103. package/dist/lib/components/ui/selection/autocomplete/autocomplete.vue.d.ts +48 -13
  104. package/dist/lib/components/ui/selection/autocomplete/filter-options.d.ts +2 -1
  105. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-box.vue.d.ts +13 -1
  106. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-list.vue.d.ts +17 -3
  107. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-items.d.ts +15 -0
  108. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-open.d.ts +15 -0
  109. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-texts.d.ts +14 -0
  110. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete.d.ts +34 -0
  111. package/dist/lib/components/ui/selection/checkbox/Checkbox.vue.d.ts +21 -5
  112. package/dist/lib/components/ui/selection/checkbox/checkbox-form-field.vue.d.ts +17 -0
  113. package/dist/lib/components/ui/selection/checkbox/index.d.ts +2 -0
  114. package/dist/lib/components/ui/selection/index.d.ts +1 -0
  115. package/dist/lib/components/ui/selection/select-field/select-field.vue.d.ts +16 -1
  116. package/dist/lib/components/ui/selection/select-field/select-form-field.vue.d.ts +16 -2
  117. package/dist/lib/components/ui/selection/tree-select-field/index.d.ts +3 -0
  118. package/dist/lib/components/ui/selection/tree-select-field/partials/tree-select-panel.vue.d.ts +27 -0
  119. package/dist/lib/components/ui/selection/tree-select-field/partials/tree-select-trigger.vue.d.ts +28 -0
  120. package/dist/lib/components/ui/selection/tree-select-field/tree-select-field.vue.d.ts +8 -0
  121. package/dist/lib/components/ui/selection/tree-select-field/tree-select-form-field.vue.d.ts +8 -0
  122. package/dist/lib/components/ui/selection/tree-select-field/tree-select.utils.d.ts +29 -0
  123. package/dist/lib/components/ui/selection/tree-select-field/types.d.ts +28 -0
  124. package/dist/lib/components/ui/selection/tree-select-field/use-tree-select-field.d.ts +19 -0
  125. package/dist/lib/components/ui/widgets/charts/chart-container.vue.d.ts +1 -1
  126. package/dist/lib/components/ui/widgets/code-editor/code-editor-form-field.vue.d.ts +13 -0
  127. package/dist/lib/components/ui/widgets/code-editor/code-editor-options.d.ts +4 -1
  128. package/dist/lib/components/ui/widgets/code-editor/code-editor.vue.d.ts +7 -0
  129. package/dist/lib/components/ui/widgets/code-editor/index.d.ts +2 -0
  130. package/dist/lib/components/ui/widgets/code-editor/monaco-editor.vue.d.ts +3 -0
  131. package/dist/lib/i18n/resources/en/en.d.ts +40 -0
  132. package/dist/lib/i18n/resources/en/material.json.d.ts +43 -3
  133. package/dist/lib/i18n/resources/fr/fr.d.ts +40 -0
  134. package/dist/lib/i18n/resources/fr/material.json.d.ts +43 -3
  135. package/dist/lib/i18n/resources/resources.d.ts +80 -0
  136. package/dist/lib/i18n/translator.d.ts +1 -1
  137. package/dist/lib/nuxt/component-names.d.ts +1 -1
  138. package/dist/lib.js +2 -2
  139. package/dist/main.js +17 -17
  140. package/dist/map.js +4 -4
  141. package/dist/material-vue.css +1 -1
  142. package/dist/{pdf-outline-sidebar-oz9wdBhB.js → pdf-outline-sidebar-jJfhNjnN.js} +2 -2
  143. package/dist/{pdf-page-0rlq1Lo6.js → pdf-page-BW1hXzPu.js} +1 -1
  144. package/dist/{pdf-page-list-DfhjNHoI.js → pdf-page-list-D277RtWS.js} +2 -2
  145. package/dist/{pdf-reader-Dr8RVcs4.js → pdf-reader-BReUTsn_.js} +73 -71
  146. package/dist/{pdf-sidebar-VXPQbC_R.js → pdf-sidebar-Bk6PowY1.js} +3 -3
  147. package/dist/{pdf-thumbnail-sidebar-BpgXrVBj.js → pdf-thumbnail-sidebar-BcNDFsks.js} +3 -3
  148. package/dist/{pdf-toolbar-B6gRRcsv.js → pdf-toolbar-CHjaMhH9.js} +4 -4
  149. package/dist/pdf.js +2 -2
  150. package/dist/{sortable-provider-oEVjQiCl.js → sortable-provider-C83VBfGl.js} +2 -2
  151. package/dist/{stored-language-rjOMJkyX.js → stored-language-CC_vdcCN.js} +90 -6
  152. package/dist/{stx-player-DNADkQ31.js → stx-player-BMQ1D2Li.js} +20 -20
  153. package/dist/{translator-BCSJSO1-.js → translator-B-X_1taI.js} +1 -1
  154. package/dist/{use-heading-spy-d50_nxgR.js → use-heading-spy-C_ySuvTl.js} +1 -1
  155. package/dist/{use-material-translation-C5zPFhbk.js → use-material-translation-B6sIk0mF.js} +3 -3
  156. package/docs/components/README.md +2 -1
  157. package/docs/components/buttons-and-actions.md +19 -3
  158. package/docs/components/data-display.md +65 -11
  159. package/docs/components/data-table-filter-and-lists.md +94 -8
  160. package/docs/components/date-and-time.md +116 -9
  161. package/docs/components/feedback-and-status.md +57 -5
  162. package/docs/components/form-fields.md +21 -9
  163. package/docs/components/media-and-files.md +120 -6
  164. package/docs/components/navigation-and-layout.md +68 -7
  165. package/docs/components/primitives.md +25 -6
  166. package/docs/components/rich-widgets.md +21 -3
  167. package/docs/components/selection-and-choice.md +151 -22
  168. package/docs/guides/forms.md +16 -8
  169. package/docs/guides/i18n.md +50 -6
  170. package/docs/guides/ssr.md +2 -0
  171. package/docs/previews/buttons-actions-copybutton.webp +0 -0
  172. package/docs/previews/data-table-filter-lists-pagination.webp +0 -0
  173. package/docs/previews/date-time-daterangefield.webp +0 -0
  174. package/docs/previews/form-fields-slider.webp +0 -0
  175. package/docs/previews/media-files-filepreview.webp +0 -0
  176. package/docs/previews/navigation-layout-stepper.webp +0 -0
  177. package/docs/previews/selection-choice-checkbox.webp +0 -0
  178. package/docs/previews/selection-choice-treeselectfield.webp +0 -0
  179. package/docs/roadmap.md +47 -1
  180. package/docs/troubleshooting.md +213 -0
  181. package/lib/nuxt/component-names.ts +16 -0
  182. package/package.json +2 -2
@@ -240,14 +240,66 @@ function archive() {}
240
240
  | --- | --- | --- | --- |
241
241
  | `#trigger` slot | — | — | becomes the dialog trigger (`as-child`, no extra button): pass one element, such as a `Button` |
242
242
  | `v-model:open` | `boolean` | uncontrolled | open state; without it the dialog keeps its own |
243
- | `@confirm` | `() => void` | — | emitted on confirm; the dialog then closes |
243
+ | `@confirm` | `(values?) => void` | — | emitted on confirm, with the form's parsed values when there is one; the dialog then closes |
244
+ | `form-options` | `ConfirmationDialogFormOptions` (`{ schema, defaultValues? }`) | — | a form in the dialog, below the default slot — see below |
244
245
  | `color` | `IconButton` colour | `'warning'` | icon and both buttons |
245
246
  | `variant` | `IconButton` variant | `'tonal'` | the icon's button style |
246
247
  | `#icon` slot | — | info icon, in a decorative `aria-hidden` badge | replaces the badge: keep yours decorative (`<IconButton as="span" aria-hidden="true">`), not a button that does nothing |
247
248
  | `confirm-text` / `cancel-text` | `string` | "Confirm" / "Cancel", translated (`material.confirm-dialog.confirm` / `.cancel`) | button text |
248
249
 
249
- Vue's `ConfirmationDialog` has no `formOptions`: to collect values before
250
- confirming, put a form in its default slot or use `CustomDialog`.
250
+ `form-options` puts a form in the confirmation, as React's `formOptions`:
251
+ `schema` is a Zod 4 schema (any Standard Schema, or a vee-validate typed
252
+ schema), `defaultValues` its initial values, and the `#form` slot renders the
253
+ fields — `*-form-field` wrappers in it join the form. The confirm button stays
254
+ disabled until the form is valid, and `@confirm` receives the schema's output
255
+ (transforms applied). The component is generic, so that payload is typed from
256
+ the schema; to type the options apart from the template, import
257
+ `ConfirmationDialogFormOptions<TInput, TOutput>` from
258
+ `@nxgt/material-vue/components`. Being generic, its ref is typed with
259
+ `ComponentExposed`, not `InstanceType`
260
+ ([troubleshooting](../troubleshooting.md#does-not-satisfy-the-constraint-abstract-new-args-any--any)).
261
+
262
+ ```vue
263
+ <script setup lang="ts">
264
+ import { z } from 'zod';
265
+ import { Button, ConfirmationDialog, TextFormField } from '@nxgt/material-vue/components';
266
+
267
+ const formOptions = {
268
+ schema: z.object({ project: z.string().trim().pipe(z.literal('my-project')) }),
269
+ defaultValues: { project: '' },
270
+ };
271
+
272
+ function remove(values?: { project: 'my-project' }) {
273
+ console.log('deleting', values?.project);
274
+ }
275
+ </script>
276
+
277
+ <template>
278
+ <ConfirmationDialog
279
+ title="Delete project"
280
+ description='Type "my-project" to confirm.'
281
+ color="error"
282
+ confirm-text="Delete"
283
+ :form-options="formOptions"
284
+ @confirm="remove"
285
+ >
286
+ <template #trigger>
287
+ <Button color="error">Delete project</Button>
288
+ </template>
289
+ <template #form>
290
+ <TextFormField name="project" label="Project name" />
291
+ </template>
292
+ </ConfirmationDialog>
293
+ </template>
294
+ ```
295
+
296
+ `#form` also receives `{ values, errors, meta, setFieldValue }` for fields
297
+ that are not form wrappers. The form lives with the dialog's content, so it
298
+ starts again from `defaultValues` each time the dialog opens. The form's code
299
+ (and vee-validate) loads only when a dialog has `form-options`; confirm stays
300
+ disabled until it has loaded and first validated. If it cannot load, after one
301
+ retry, a translated message takes its place
302
+ (`material.confirm-dialog.form-failed`).
251
303
 
252
304
  ![CustomAlertDialog](../previews/feedback-status-customalertdialog.webp)
253
305
 
@@ -257,7 +309,7 @@ but built on `AlertDialog`, so it does not close on an outside click. Put
257
309
  `AlertDialogCancel` / `AlertDialogAction` in its `#footer`. Its trigger is
258
310
  `as-child`, so a `Button` goes straight in.
259
311
 
260
- **Storybook:** `Feedback & status/ConfirmDialog`, `Feedback & status/ConfirmationDialog`,
312
+ **Storybook:** `Feedback & status/ConfirmDialog`, `Feedback & status/ConfirmationDialog` (story `WithForm`),
261
313
  `Feedback & status/CustomAlertDialog` · **React:** `ConfirmDialog`, `ConfirmationDialog`, `CustomAlertDialog`
262
314
 
263
315
  ---
@@ -523,7 +575,7 @@ compiled stylesheet). `Toast` is the component each toast
523
575
  renders, and `sonnerToast` is vue-sonner's own `toast`, for anything the
524
576
  presets do not cover.
525
577
 
526
- **Storybook:** `Feedback & status/Toast` · **React:** `Toaster`, `toast`, `toasts`, `Toast`
578
+ **Storybook:** `Feedback & status/Toast` · **React:** `Toaster`, `toast`, `toasts`, `Toast` (`ToastAction` type)
527
579
 
528
580
  ---
529
581
 
@@ -267,6 +267,7 @@ const quantity = ref<number | null>(2);
267
267
  | `hide-steppers` | `boolean` | `false` | hides the +/− buttons |
268
268
  | `#suffix` | slot | — | unit after the input |
269
269
  | `aria-label` | `string` | — | names the input when it has no `label` (a part of a larger field) |
270
+ | `aria-labelledby` | `string` | — | names the input by another element's id (a part of a larger field) |
270
271
 
271
272
  **Emits:** `update:modelValue` with `number | null`.
272
273
  **Form wrapper:** `NumberFormField` — `name` inside a vee-validate form, see
@@ -529,7 +530,7 @@ const pin = ref('');
529
530
  **Emits:** `update:modelValue` with `string` (the digits typed so far).
530
531
  **Form wrapper:** `OtpFormField` — `name` inside a vee-validate form, see
531
532
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
532
- **Storybook:** `Form fields/OtpField` · **React:** `OtpField`, `OtpFormField`
533
+ **Storybook:** `Form fields/OtpField` · **React:** `OtpField`, `OtpFormField`, `InputOTP` (same parts)
533
534
 
534
535
  ---
535
536
 
@@ -600,13 +601,18 @@ const title = ref('Spring catalogue');
600
601
  | --- | --- | --- | --- |
601
602
  | `v-model` | `string` | `''` | the committed text; updated on save only |
602
603
  | `@save` | `(value: string) => void` | — | on save (Enter or the check button), not on each key |
604
+ | `label` | `string` | — | shown above the text; names the input while editing |
605
+ | `name` | `string` | — | the input's `name` while editing, and the field's `data-pw` |
603
606
  | `placeholder` | `string` | "Click to edit" | shown while empty |
604
607
  | `disabled` | `boolean` | `false` | read-only text |
605
608
 
609
+ Enter, Escape, Save and Cancel return focus to the field, while a click
610
+ elsewhere saves and leaves focus where it went.
611
+
606
612
  **Emits:** `update:modelValue` and `save`, both `string`, trimmed, on save
607
613
  only.
608
- **Form wrapper:** none — bind `v-model` to a `useField` value, see
609
- [forms](../guides/forms.md#the-pair).
614
+ **Form wrapper:** `InlineEditFormField` (`name` plus the same props; the form
615
+ value changes on save, not while typing), see [forms](../guides/forms.md#the-pair).
610
616
  **Storybook:** `Form fields/InlineEdit` · **React:** `InlineEdit`, `InlineEditFormField`
611
617
 
612
618
  ---
@@ -720,7 +726,7 @@ name: 'Satisfaction' })`.
720
726
 
721
727
  ![Slider](../previews/form-fields-slider.webp)
722
728
 
723
- Labelled single or range slider with a value readout.
729
+ Labelled single or range slider with a value readout, ticks and leading / trailing content.
724
730
 
725
731
  ```vue
726
732
  <script setup lang="ts">
@@ -731,25 +737,31 @@ const price = ref<number[]>([20, 70]);
731
737
  </script>
732
738
 
733
739
  <template>
734
- <SliderField v-model="price" label="Price" :min="0" :max="100" />
740
+ <SliderField v-model="price" label="Price" :min="0" :max="100" :ticks="5" leading="0" trailing="100" />
735
741
  </template>
736
742
  ```
737
743
 
738
744
  In Vue the labelled field is `SliderField`; `Slider` is the bare Reka UI
739
745
  slider (track, range and thumbs, `v-model` always `number[]`) it is built on.
740
- React's `ticks`, `leading` / `trailing` and editable readout inputs are not
741
- in `SliderField`: its readout is text.
746
+ React's editable readout inputs are not in `SliderField`: its readout is
747
+ text. `ticks`, `leading` and `trailing` are; for markup on either side of the
748
+ track, use the `#leading` / `#trailing` slots (`SliderFormField` passes them
749
+ through).
742
750
 
743
751
  | Prop | Type | Default | Effect |
744
752
  | --- | --- | --- | --- |
745
753
  | `v-model` | `number \| number[]` | `[min]` shown when unset | a number for one thumb, an array for one thumb per entry: `[20, 70]` |
746
754
  | `min`, `max`, `step` | `number` | `0`, `100`, `1` | range and increment |
747
755
  | `show-readout` | `boolean` | `true` | the value(s) next to the label |
756
+ | `ticks` | `boolean \| number` | — | marks under the track: `true` one per `step` (at most 21), a number that many, ends included |
757
+ | `leading`, `trailing` | `string \| number` | — | small muted text before / after the track; the `#leading` / `#trailing` slots take markup |
748
758
  | `required` | `boolean` | — | the asterisk, and `aria-required` on every thumb |
749
759
  | `aria-invalid` | `boolean \| 'true' \| 'false'` | follows `error` | on every thumb; wins over `error` |
750
760
 
751
761
  Each thumb (`role="slider"`) is named by the label; a range's thumbs add
752
762
  "Minimum" and "Maximum": `getByRole('slider', { name: 'Price Minimum' })`.
763
+ A labelled range is also a `role="group"` named by the label, so two range
764
+ sliders are told apart.
753
765
  The bare `Slider` does the same with `label-id` (the id of your label) and
754
766
  `required`, or with an `aria-labelledby` or `aria-label` you pass (they win
755
767
  over `label-id`): a vertical `Slider`, which has no field label, needs one.
@@ -762,7 +774,7 @@ wins over `error` as on React's `Slider`) goes on every thumb, not on the root
762
774
  stores the same — the initial value decides.
763
775
  **Form wrapper:** `SliderFormField` — `name` inside a vee-validate form, see
764
776
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
765
- **Storybook:** `Form fields/Slider` · **React:** `Slider`, `SliderFormField`
777
+ **Storybook:** `Form fields/Slider` (story `TicksAndEnds`) · **React:** `Slider`, `SliderField` (an alias of `Slider`), `SliderFormField`
766
778
 
767
779
  ---
768
780
 
@@ -835,7 +847,7 @@ const updates = ref(false);
835
847
  **Emits:** `update:modelValue` with `boolean`.
836
848
  **Form wrapper:** `SwitchFormField` — `name` inside a vee-validate form, see
837
849
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
838
- **Storybook:** `Form fields/Switch`, `Form fields/SwitchField` · **React:** `Switch`, `SwitchFormField`
850
+ **Storybook:** `Form fields/Switch`, `Form fields/SwitchField` · **React:** `Switch`, `SwitchField` (an alias of `Switch`), `SwitchFormField`
839
851
 
840
852
  ---
841
853
 
@@ -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>
322
+
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).
239
352
 
240
- **Storybook:** — · **React:** `FilesFormField`
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
 
@@ -677,7 +791,7 @@ and emits nothing: the capture stays inside the component. The helpers
677
791
  `isCameraSupported`, `getCameraDevices`, `requestCameraPermissions` and
678
792
  `capturePhotoFromVideo` are exported for building your own.
679
793
 
680
- **Storybook:** `Media & files/Camera` · **React:** — (a story only, not exported)
794
+ **Storybook:** `Media & files/Camera` · **React:** `Camera`, `useCamera`, `capturePhotoFromVideo` (`createCameraStore` for the store)
681
795
 
682
796
  ---
683
797
 
@@ -521,6 +521,11 @@ step has no `icon`, and there is no separate `StepperIndicator`.
521
521
  | `active` | `number` | `0` | current step; its `description` is shown as helper text below, and in a hover tooltip on a decorative info icon |
522
522
  | `@change` | `(index: number) => void` | — | a step was clicked |
523
523
  | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | vertical from `md` only; phones get the compact bar |
524
+ | `name` | `string` | — | the stepper's `data-pw` (`stepper` without it), and the prefix of each step's: `<name>-step-N` instead of `step-N` |
525
+
526
+ Each step is an `<li>` with `data-pw="step-N"` (`<name>-step-N` with `name`),
527
+ N counted from 0, for tests to select; the current step's button carries
528
+ `aria-current="step"`, and a completed or failed step is announced as such.
524
529
 
525
530
  **Storybook:** `Navigation & layout/Stepper` · **React:** `Stepper`
526
531
 
@@ -629,11 +634,67 @@ const toc: DocTocItem[] = [{ id: 'what', title: 'What it is', level: 2 }];
629
634
  ```
630
635
 
631
636
  Parts: `Doc` (the shell), `DocPage` (header, content, see also and pager);
632
- the pieces it composes — `DocTopbar`, `DocSidebar`, `DocToc`, `DocPager`,
633
- `DocSearch`, `DocSeeAlso`, `DocChildCards`, `DocProgress`, `DocSkipLink`,
634
- `DocHeader`, `DocCopyMarkdown` — and `DocEmpty` for a 404.
637
+ the pieces it composes — `DocTopbar`, `DocSidebar`, `DocNav`, `DocArticle`,
638
+ `DocToc`, `DocPager`, `DocSearch`, `DocSeeAlso`, `DocChildCards`,
639
+ `DocProgress`, `DocSkipLink`, `DocHeader`, `DocCopyPage`, `DocCopyMarkdown`,
640
+ `DocLink` — and `DocEmpty` for a 404.
635
641
  `useDoc` / `provideDoc` / `useDocContext` expose the state for a shell of
636
- your own. Search, the phone sheet and the active heading live in a Pinia
642
+ your own, built from those parts:
643
+
644
+ ```vue
645
+ <script setup lang="ts">
646
+ import { useRoute, useRouter } from 'vue-router';
647
+ import {
648
+ DocArticle,
649
+ type DocNavItem,
650
+ DocLink,
651
+ DocNav,
652
+ provideDoc,
653
+ useDoc,
654
+ } from '@nxgt/material-vue/components';
655
+
656
+ const route = useRoute();
657
+ const router = useRouter();
658
+
659
+ const items: DocNavItem[] = [
660
+ {
661
+ id: 'start',
662
+ title: 'Start',
663
+ items: [
664
+ { id: 'intro', title: 'Introduction', href: '/docs' },
665
+ { id: 'install', title: 'Install', href: '/docs/install' },
666
+ ],
667
+ },
668
+ ];
669
+
670
+ provideDoc(
671
+ useDoc({
672
+ items,
673
+ get pathname() {
674
+ return route.path;
675
+ },
676
+ onNavigate: (href) => router.push(href),
677
+ }),
678
+ );
679
+ </script>
680
+
681
+ <template>
682
+ <div class="flex">
683
+ <DocNav class="w-64" />
684
+ <DocArticle>
685
+ <slot />
686
+ <DocLink href="/docs/install">Next: Install</DocLink>
687
+ </DocArticle>
688
+ </div>
689
+ </template>
690
+ ```
691
+
692
+ `DocLink` is an anchor that navigates through `onNavigate` for an internal
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
695
+ anchor without the navigation. `DocCopyPage` (`title`) copies the title and
696
+ the page URL; `DocArticle` is the `#doc-content` landmark the skip link and
697
+ copy-as-markdown target. Search, the phone sheet and the active heading live in a Pinia
637
698
  store, so the app needs `app.use(createPinia())`.
638
699
 
639
700
  | Prop | Type | Default | Effect |
@@ -649,10 +710,10 @@ store, so the app needs `app.use(createPinia())`.
649
710
  On `DocPage`: `title`, `description`, `crumbs`, `editHref`, `updatedAt`,
650
711
  `copyPage` / `copyMarkdown` (default: shown) with `markdown` for the copy
651
712
  buttons, and a
652
- `#badge` slot. There is no `linkComponent` on `Doc` and no `DocCopyPage`
653
- export in Vue.
713
+ `#badge` slot. There is no `linkComponent` on `Doc` in Vue: wire your
714
+ router with `@navigate`.
654
715
 
655
- **Storybook:** `Navigation & layout/Doc`, `Navigation & layout/Doc/Api`, `Navigation & layout/Doc/Blocks` · **React:** `Doc`, `DocPage`, `DocTopbar`, `DocSidebar`, `DocToc`, `DocPager`, `DocSearch`, `DocChildCards`, `DocSeeAlso`, `DocEmpty`, `DocProgress`
716
+ **Storybook:** `Navigation & layout/Doc`, `Navigation & layout/Doc/Api`, `Navigation & layout/Doc/Blocks` · **React:** `Doc`, `DocPage`, `DocTopbar`, `DocSidebar`, `DocToc`, `DocPager`, `DocSearch`, `DocChildCards`, `DocSeeAlso`, `DocEmpty`, `DocProgress`, `DocNav`, `DocArticle`, `DocLink`, `DefaultDocLink`, `DocCopyPage`
656
717
 
657
718
  ---
658
719
 
@@ -557,10 +557,12 @@ const invoices = [{ id: 'INV001', amount: '$250.00' }];
557
557
  Parts: `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableRow`,
558
558
  `TableHead`, `TableCell`, `TableCaption` — each takes its HTML element's
559
559
  attributes — plus `TableEmpty`, a full-width row for "nothing here" (`colspan`
560
- defaults to `1`: set it to the column count). For sorting, paging and selection
560
+ defaults to `1`: set it to the column count). The attributes you put on
561
+ `Table` (`id`, `aria-*`, `style`, listeners) go on the `<table>`, not on its
562
+ scrolling container; `class` too. For sorting, paging and selection
561
563
  use `DataTable` on the [Data table, filter & lists](data-table-filter-and-lists.md) page.
562
564
 
563
- **Storybook:** `Primitives/Table` · **React:** `Table` (same parts; no `TableEmpty`)
565
+ **Storybook:** `Primitives/Table` · **React:** `Table` (same parts)
564
566
 
565
567
  ---
566
568
 
@@ -568,7 +570,7 @@ use `DataTable` on the [Data table, filter & lists](data-table-filter-and-lists.
568
570
 
569
571
  ![Tabs](../previews/primitives-tabs.webp)
570
572
 
571
- Tabs whose triggers can take an icon.
573
+ Tabs whose triggers can take an icon. `variant="line"` is the quieter pill style.
572
574
 
573
575
  ```vue
574
576
  <script setup lang="ts">
@@ -594,18 +596,35 @@ import {
594
596
  ```
595
597
 
596
598
  Parts: `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`. An icon goes in the
597
- trigger's slot, before the label. There is no `variant` and no `icon` prop in
598
- 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
+ ```
599
617
 
600
618
  | Prop | Type | Default | Effect |
601
619
  | --- | --- | --- | --- |
620
+ | `variant` (`Tabs`) | `'bar' \| 'line'` | `'bar'` | the look of the list and its triggers |
602
621
  | `default-value` / `v-model` (`Tabs`) | `string \| number` | — | the selected tab, uncontrolled / controlled |
603
622
  | `orientation` (`Tabs`) | `'horizontal' \| 'vertical'` | `'horizontal'` | arrow-key direction |
604
623
  | `activation-mode` (`Tabs`) | `'automatic' \| 'manual'` | `'automatic'` | select on focus, or only on Enter / click |
605
624
  | `value` (`TabsTrigger`, `TabsContent`) | `string \| number` | — (required) | pairs a trigger with its panel |
606
625
 
607
626
  **Emits:** `update:modelValue` on `Tabs` — `string | number`.
608
- **Storybook:** `Primitives/Tabs` · **React:** `Tabs` (same parts)
627
+ **Storybook:** `Primitives/Tabs` (story `Line`) · **React:** `Tabs` (same parts)
609
628
 
610
629
  ---
611
630
 
@@ -442,11 +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 and no form wrapper in Vue (React has
447
- `CodeEditorFormField`).
447
+ ```vue
448
+ <script setup lang="ts">
449
+ import { ref } from 'vue';
450
+ import { CodeEditor, type CodeEditorOptions } from '@nxgt/material-vue/components';
448
451
 
449
- **Emits:** `update:modelValue` — `string`.
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.
463
+
464
+ **Emits:** `update:modelValue` — `string`; `languageChange` — `string`.
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).
450
468
  **Storybook:** `Rich widgets/CodeEditor` · **React:** `CodeEditor`, `CodeEditorFormField`
451
469
 
452
470
  ---