@nxgt/material-vue 0.23.3 → 0.24.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 (97) hide show
  1. package/README.md +22 -18
  2. package/dist/{Button-CVIR6uLK.js → Button-DOcT4Pej.js} +95 -72
  3. package/dist/{HoverCardTrigger-iAL5ALrL.js → HoverCardTrigger-WQe85PCH.js} +2 -2
  4. package/dist/{camera-CrMabX3d.js → camera-BNbLUPb1.js} +13 -13
  5. package/dist/{components-aga1BOH2.js → components-YlYuHLEI.js} +10961 -10481
  6. package/dist/components.js +8 -8
  7. package/dist/composables.js +2 -2
  8. package/dist/dnd.js +1 -1
  9. package/dist/{i18n-provider-BqWxpaKu.js → i18n-provider-DTuxugHO.js} +1 -1
  10. package/dist/i18n.js +4 -4
  11. package/dist/{icon-button-DbEME76t.js → icon-button-DKLuZ2c-.js} +34 -37
  12. package/dist/lib/components/ui/actions/button/has-text-content.d.ts +10 -0
  13. package/dist/lib/components/ui/actions/copy-button/copy-value.vue.d.ts +21 -0
  14. package/dist/lib/components/ui/actions/copy-button/index.d.ts +2 -0
  15. package/dist/lib/components/ui/collections/data-table/data-table-context.d.ts +6 -0
  16. package/dist/lib/components/ui/collections/data-table/data-table-pagination.vue.d.ts +2 -0
  17. package/dist/lib/components/ui/collections/data-table/data-table.vue.d.ts +17 -27
  18. package/dist/lib/components/ui/collections/data-table/index.d.ts +1 -1
  19. package/dist/lib/components/ui/collections/data-table/partials/data-table-body.vue.d.ts +10 -1
  20. package/dist/lib/components/ui/collections/data-table/partials/data-table-empty.vue.d.ts +13 -1
  21. package/dist/lib/components/ui/collections/data-table/partials/data-table-head.vue.d.ts +22 -0
  22. package/dist/lib/components/ui/collections/data-table/partials/data-table-skeleton-rows.vue.d.ts +10 -0
  23. package/dist/lib/components/ui/collections/data-table/use-data-table.d.ts +17 -0
  24. package/dist/lib/components/ui/collections/filter/partials/filter-field-slider-range.vue.d.ts +1 -4
  25. package/dist/lib/components/ui/collections/pagination/index.d.ts +5 -0
  26. package/dist/lib/components/ui/collections/pagination/pagination-bar.vue.d.ts +21 -0
  27. package/dist/lib/components/ui/collections/pagination/pagination-items.d.ts +2 -0
  28. package/dist/lib/components/ui/collections/pagination/pagination-link.vue.d.ts +18 -0
  29. package/dist/lib/components/ui/display/markdown/index.d.ts +18 -0
  30. package/dist/lib/components/ui/feedback/skeleton/skeleton-text.vue.d.ts +1 -1
  31. package/dist/lib/components/ui/feedback/spinner/spinner.compound-variants.d.ts +4 -0
  32. package/dist/lib/components/ui/fields/contacts/consts.d.ts +1 -1
  33. package/dist/lib/components/ui/fields/inline-edit/index.d.ts +1 -0
  34. package/dist/lib/components/ui/fields/inline-edit/inline-edit-form-field.vue.d.ts +7 -0
  35. package/dist/lib/components/ui/layout/doc/doc.vue.d.ts +5 -5
  36. package/dist/lib/components/ui/layout/doc/index.d.ts +8 -1
  37. package/dist/lib/components/ui/layout/doc/partials/default-doc-link.vue.d.ts +14 -0
  38. package/dist/lib/components/ui/layout/doc/partials/doc-copy-page.vue.d.ts +6 -0
  39. package/dist/lib/components/ui/layout/doc/partials/doc-link.vue.d.ts +3 -7
  40. package/dist/lib/components/ui/layout/doc/types.d.ts +6 -1
  41. package/dist/lib/components/ui/media/pdf-viewer/index.d.ts +6 -6
  42. package/dist/lib/components/ui/media/pdf-viewer/pdf-reader.vue.d.ts +2 -2
  43. package/dist/lib/components/ui/primitives/icon/icon-context.d.ts +1 -1
  44. package/dist/lib/components/ui/selection/checkbox/Checkbox.vue.d.ts +21 -5
  45. package/dist/lib/components/ui/selection/checkbox/checkbox-form-field.vue.d.ts +17 -0
  46. package/dist/lib/components/ui/selection/checkbox/index.d.ts +2 -0
  47. package/dist/lib/components/ui/widgets/charts/chart-container.vue.d.ts +1 -1
  48. package/dist/lib/components/ui/widgets/code-editor/code-editor-form-field.vue.d.ts +13 -0
  49. package/dist/lib/components/ui/widgets/code-editor/index.d.ts +1 -0
  50. package/dist/lib/i18n/resources/en/en.d.ts +7 -0
  51. package/dist/lib/i18n/resources/en/material.json.d.ts +8 -1
  52. package/dist/lib/i18n/resources/fr/fr.d.ts +7 -0
  53. package/dist/lib/i18n/resources/fr/material.json.d.ts +8 -1
  54. package/dist/lib/i18n/resources/resources.d.ts +14 -0
  55. package/dist/lib/i18n/translator.d.ts +1 -1
  56. package/dist/lib/nuxt/component-names.d.ts +1 -1
  57. package/dist/lib.js +2 -2
  58. package/dist/main.js +17 -17
  59. package/dist/map.js +4 -4
  60. package/dist/material-vue.css +1 -1
  61. package/dist/{pdf-outline-sidebar-D3NgGXpn.js → pdf-outline-sidebar-0I-LGrgY.js} +2 -2
  62. package/dist/{pdf-page-DniEW6ho.js → pdf-page-C74-6lyZ.js} +1 -1
  63. package/dist/{pdf-page-list-ByRU33Lx.js → pdf-page-list-DPsquYPY.js} +2 -2
  64. package/dist/{pdf-reader-DzLQa6Uq.js → pdf-reader-Bkpp7UZ7.js} +8 -8
  65. package/dist/{pdf-sidebar-DoGWupRN.js → pdf-sidebar-BH-1jvy-.js} +3 -3
  66. package/dist/{pdf-thumbnail-sidebar-B4Srzga5.js → pdf-thumbnail-sidebar-ByqNaYfz.js} +3 -3
  67. package/dist/{pdf-toolbar-B6gRRcsv.js → pdf-toolbar-BRnmzddi.js} +4 -4
  68. package/dist/pdf.js +2 -2
  69. package/dist/{sortable-provider-oEVjQiCl.js → sortable-provider-CC9eO1nm.js} +2 -2
  70. package/dist/{stored-language-rjOMJkyX.js → stored-language-N_UoPJsY.js} +20 -2
  71. package/dist/{stx-player-CrirKEQm.js → stx-player-BGKIDqNX.js} +22 -22
  72. package/dist/{translator-BCSJSO1-.js → translator-CU0jKvzB.js} +1 -1
  73. package/dist/{use-heading-spy-d50_nxgR.js → use-heading-spy-Bw1LIAkC.js} +1 -1
  74. package/dist/{use-material-translation-C5zPFhbk.js → use-material-translation-8hUB2M-S.js} +3 -3
  75. package/docs/components/buttons-and-actions.md +19 -3
  76. package/docs/components/data-display.md +14 -1
  77. package/docs/components/data-table-filter-and-lists.md +90 -8
  78. package/docs/components/feedback-and-status.md +1 -1
  79. package/docs/components/form-fields.md +7 -5
  80. package/docs/components/media-and-files.md +1 -1
  81. package/docs/components/navigation-and-layout.md +67 -7
  82. package/docs/components/primitives.md +4 -2
  83. package/docs/components/rich-widgets.md +4 -3
  84. package/docs/components/selection-and-choice.md +16 -13
  85. package/docs/guides/forms.md +14 -5
  86. package/docs/guides/i18n.md +6 -2
  87. package/docs/guides/ssr.md +2 -0
  88. package/docs/previews/buttons-actions-actioncard.webp +0 -0
  89. package/docs/previews/buttons-actions-copybutton.webp +0 -0
  90. package/docs/previews/data-table-filter-lists-pagination.webp +0 -0
  91. package/docs/previews/form-fields-slider.webp +0 -0
  92. package/docs/previews/navigation-layout-stepper.webp +0 -0
  93. package/docs/previews/selection-choice-checkbox.webp +0 -0
  94. package/docs/roadmap.md +41 -1
  95. package/docs/troubleshooting.md +104 -0
  96. package/lib/nuxt/component-names.ts +12 -0
  97. package/package.json +1 -1
@@ -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,66 @@ 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); `DefaultDocLink` is the same
694
+ anchor without the navigation. `DocCopyPage` (`title`) copies the title and
695
+ the page URL; `DocArticle` is the `#doc-content` landmark the skip link and
696
+ copy-as-markdown target. Search, the phone sheet and the active heading live in a Pinia
637
697
  store, so the app needs `app.use(createPinia())`.
638
698
 
639
699
  | Prop | Type | Default | Effect |
@@ -649,10 +709,10 @@ store, so the app needs `app.use(createPinia())`.
649
709
  On `DocPage`: `title`, `description`, `crumbs`, `editHref`, `updatedAt`,
650
710
  `copyPage` / `copyMarkdown` (default: shown) with `markdown` for the copy
651
711
  buttons, and a
652
- `#badge` slot. There is no `linkComponent` on `Doc` and no `DocCopyPage`
653
- export in Vue.
712
+ `#badge` slot. There is no `linkComponent` on `Doc` in Vue: wire your
713
+ router with `@navigate`.
654
714
 
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`
715
+ **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
716
 
657
717
  ---
658
718
 
@@ -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
 
@@ -443,10 +443,11 @@ Monaco mounts on the client only.
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
445
 
446
- There is no `options` pass-through and no form wrapper in Vue (React has
447
- `CodeEditorFormField`).
446
+ There is no `options` pass-through in Vue yet.
448
447
 
449
- **Emits:** `update:modelValue` — `string`.
448
+ **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).
450
451
  **Storybook:** `Rich widgets/CodeEditor` · **React:** `CodeEditor`, `CodeEditorFormField`
451
452
 
452
453
  ---
@@ -105,7 +105,7 @@ const food = ref('banana');
105
105
 
106
106
  **Emits:** `update:modelValue` with the item value (`string` here). For a
107
107
  form, use `SelectField` / `SelectFormField`.
108
- **Storybook:** `Selection & choice/Select` · **React:** `Select` (same parts)
108
+ **Storybook:** `Selection & choice/Select` · **React:** `Select` (same parts), `NativeSelect` (same parts)
109
109
 
110
110
  ---
111
111
 
@@ -263,35 +263,38 @@ field.
263
263
 
264
264
  ![Checkbox](../previews/selection-choice-checkbox.webp)
265
265
 
266
- Reka UI checkbox with an indeterminate state; pair it with a `Label`.
266
+ Checkbox with label, helper text and an indeterminate state.
267
267
 
268
268
  ```vue
269
269
  <script setup lang="ts">
270
270
  import { ref } from 'vue';
271
- import { Checkbox, Label } from '@nxgt/material-vue/components';
271
+ import { Checkbox } from '@nxgt/material-vue/components';
272
272
 
273
273
  const receipts = ref(true);
274
274
  </script>
275
275
 
276
276
  <template>
277
- <div class="flex items-center gap-2">
278
- <Checkbox id="receipts" v-model="receipts" />
279
- <Label for="receipts">Email me receipts</Label>
280
- </div>
277
+ <Checkbox v-model="receipts" label="Email me receipts" helper-text="Sent after every payment." />
281
278
  </template>
282
279
  ```
283
280
 
284
281
  | Prop | Type | Default | Effect |
285
282
  | --- | --- | --- | --- |
286
283
  | `v-model` / `default-value` | `boolean \| 'indeterminate'` | — | Reka UI checkbox state |
287
- | `disabled`, `required`, `name`, `value` | | — | as on a native checkbox |
284
+ | `label` / `#label` slot | `string` | — | renders the box in a row with this label, which names and toggles it |
285
+ | `helper-text` / `#helper-text` slot | `string` | — | a line under the label |
286
+ | `class` | | — | the row when labelled, else the box |
287
+ | `disabled`, `required`, `name`, `value`, `id` | | — | as on a native checkbox; a labelled box gets an id when it has none |
288
288
 
289
- Unlike React, `Checkbox` has no `label` / `helper-text` of its own, and there
290
- is no `CheckboxFormField`: bind `v-model` to a `useField` value, or use
291
- `CheckboxGroup` / `SwitchFormField`.
289
+ Without `label` or `helper-text` it is the bare Reka UI box, as before: pair
290
+ it with your own `Label`. The default slot replaces the check mark (its
291
+ props give `state`, to draw a dash for `'indeterminate'`). Attributes such as
292
+ `aria-*` always reach the box, not the row.
292
293
 
293
294
  **Emits:** `update:modelValue` with `boolean | 'indeterminate'`.
294
- **Form wrapper:** none — `useField` + `v-model`, see [forms](../guides/forms.md#the-pair).
295
+ **Form wrapper:** `CheckboxFormField` (`name` plus the same props): an unset
296
+ value shows as indeterminate, and an indeterminate value is written as `null`;
297
+ the error message replaces `helper-text`. See [forms](../guides/forms.md#the-pair).
295
298
  **Storybook:** `Selection & choice/Checkbox` · **React:** `Checkbox`, `CheckboxFormField`
296
299
 
297
300
  ---
@@ -399,7 +402,7 @@ the layout wrapper): your own `aria-label` / `aria-labelledby` wins, and your
399
402
  **Emits:** `update:modelValue` with `string`.
400
403
  **Form wrapper:** `RadioGroupFormField` — `name` inside a vee-validate form, see
401
404
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
402
- **Storybook:** `Selection & choice/RadioGroup` · **React:** `RadioGroup`, `RadioGroupItem`, `RadioGroupFormField`
405
+ **Storybook:** `Selection & choice/RadioGroup` · **React:** `RadioGroupField` (an alias of `RadioGroup`), `RadioGroup`, `RadioGroupItem`, `RadioGroupFormField`
403
406
 
404
407
  ---
405
408
 
@@ -110,7 +110,7 @@ useForm({
110
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 |
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
- | `RichTextEditorFormField` | focus leaves the editor and its toolbar |
113
+ | `RichTextEditorFormField`, `CodeEditorFormField` | focus leaves the editor and its toolbar |
114
114
  | `FilterFormField` | the filter is applied (or on every change, with `live-update`) — not on blur |
115
115
 
116
116
  "Opened" means named by one of the field's controls as the popup it controls
@@ -344,7 +344,7 @@ or fails validation.
344
344
  | `ColorField` · `ColorFormField` | `string` (hex, `'#1e293b'`), written when a valid colour is committed | `''` |
345
345
  | `OtpField` · `OtpFormField` | `string` of up to `length` (default 6) characters | `''` |
346
346
  | `InputGroupField` · `InputGroupFormField` | `string` | `''` |
347
- | `InlineEdit` | `string`, written on save only. No form wrapper: bind it with `useField` | `''` |
347
+ | `InlineEdit` · `InlineEditFormField` | `string`, written on save only | `''` |
348
348
  | `CountryField` · `CountryFormField` | `mode="single"` (default): ISO code `string \| null` (`'FR'`). `mode="multiple"`: `string[]` | `null` / `[]` |
349
349
  | `TimezoneField` · `TimezoneFormField` | IANA `string \| null` (`'Europe/Paris'`) | `null` |
350
350
  | `RatingField` · `RatingFormField` | `number`, `0` for no rating | `0` |
@@ -372,7 +372,7 @@ or fails validation.
372
372
  | `SelectField` · `SelectFormField` | `string` (an option `value`) | `''` |
373
373
  | `ComboboxField` · `ComboboxFormField` | `string \| null`; picking the selected option again clears it | `null` |
374
374
  | `Autocomplete` · `AutocompleteFormField` | `mode="single"` (default): `string \| null`. `mode="multiple"`: `string[]` | `null` / `[]` |
375
- | `Checkbox` | `boolean` (`'indeterminate'` / `null` for the third state). No form wrapper: use `SwitchFormField`, or bind it with `useField` | `false` |
375
+ | `Checkbox` · `CheckboxFormField` | `boolean`; an indeterminate value is written as `null` | `false` (see [Traps](#traps)) |
376
376
  | `CheckboxGroup` · `CheckboxGroupFormField` | `string[]` | `[]` |
377
377
  | `RadioGroupField` · `RadioGroupFormField` | `string` | `''` |
378
378
  | `CheckCardField` · `CheckCardFormField` | `boolean`: `true` for the first card, `false` for the second | `false` |
@@ -391,6 +391,7 @@ or fails validation.
391
391
  | `FileListField`, `FileListS3Field` · `FileListFormField`, `FileListS3FormField` | `string[]` | `[]` |
392
392
  | `UploadField` · `UploadFormField` | a `File`, or `File[]` with `multiple`. Nothing is uploaded | `undefined` / `[]` |
393
393
  | `RichTextEditor` · `RichTextEditorFormField` | `string`: the editor's HTML | `''` |
394
+ | `CodeEditor` · `CodeEditorFormField` | `string`: the source | `''` |
394
395
  | `LocationField` · `LocationFormField` (from `@nxgt/material-vue/map`) | `[lat, lng]`, rounded to 7 decimals | `null` |
395
396
 
396
397
  An upload field emits a **string** because it uploads as soon as a file is
@@ -400,8 +401,7 @@ handler sends it (with `convertToFormData` from `@nxgt/material-vue/lib`,
400
401
  for example).
401
402
 
402
403
  Not in `@nxgt/material-vue`: `FilesFormField` (use `NetworkUploadFormField`
403
- or `FileListFormField`) and `CheckboxFormField` / `InlineEditFormField` (see
404
- the rows above).
404
+ or `FileListFormField`).
405
405
 
406
406
  ## Composite values
407
407
 
@@ -947,6 +947,15 @@ Type the schema as an array:
947
947
  z.object({ plan: z.array(z.string()).length(1, { error: 'Pick a plan' }) });
948
948
  ```
949
949
 
950
+ **A checkbox with no initial value is indeterminate.** `CheckboxFormField`
951
+ renders `undefined` as the indeterminate state (and writes `null` for it), as
952
+ in `@nxgt/material`. A `validationSchema` alone gives it no initial value.
953
+ Give it a boolean:
954
+
955
+ ```ts
956
+ useForm({ initialValues: { terms: false } });
957
+ ```
958
+
950
959
  **A field re-mounted on every render loses its state.** A `:key` that changes
951
960
  on each render (`:key="Math.random()"`) mounts a new field every time: focus,
952
961
  the `PhoneField` draft and the `NumberField` text are lost as you type, and
@@ -280,7 +280,8 @@ checkboxes, the filter's presets and `Min`/`Max` fields, the `variant="4"` spinn
280
280
  `You are here` and zoom buttons, and the close button of dialogs, sheets and
281
281
  `DialogFooter`. The other icon-only buttons are named from it too: the
282
282
  `Sidebar` toggle (`material.sidebar.toggle`), the data table's row expander
283
- (`material.table.labels.toggle-row`), a `Chip`'s remove button, which the
283
+ (`material.table.labels.toggle-row`), its loading status, "Loading data…" /
284
+ "Chargement des données…" (`material.table.loading`), a `Chip`'s remove button, which the
284
285
  `Autocomplete` chips use ("Remove {label}", `material.chip.dismiss-label`),
285
286
  the `LocationPreview` trigger (`material.map.preview` / `preview-named`),
286
287
  `DurationField`'s inputs, "Hours" and "Minutes"
@@ -298,7 +299,10 @@ names and text, which reka-ui left in English (`material.pagination.first`,
298
299
  `previous`, `next`, `last`, `page`, and `more` for the ellipsis), and the
299
300
  `aria-label` of `Pagination` and `Breadcrumb` and the breadcrumb ellipsis's
300
301
  "More" (`material.pagination.label`, `material.breadcrumb.*`), and
301
- `DocHeader`'s breadcrumb (`material.doc.breadcrumb`); `CommandDialog`'s
302
+ `DocHeader`'s breadcrumb (`material.doc.breadcrumb`); the status a `Stepper`
303
+ step announces after its title, "Completed" or "Error"
304
+ (`material.stepper.completed` / `error`, "Terminée" / "Erreur" in French);
305
+ `CommandDialog`'s
302
306
  default title and description (`material.command.*`); the carousel's
303
307
  `aria-roledescription` (`material.carousel.roledescription` / `slide`); the
304
308
  filter's empty state and `+N more` chip (`material.filter.empty` / `more`) and
@@ -42,12 +42,14 @@ at import time would crash the server before it answers anything.
42
42
  | --- | --- |
43
43
  | `PdfReader`, `PdfSidebar`, `PdfPageList`, `PdfThumbnailSidebar` | `pdfjs-dist` runs `new DOMMatrix()` when it is imported |
44
44
  | `CodeEditor`, `CodeDiff` | Monaco |
45
+ | `CodeEditorFormField` | it renders `CodeEditor`, whose Monaco is client-only |
45
46
  | `Camera` | `getUserMedia` |
46
47
  | `StxPlayer` | media element APIs |
47
48
  | the charts (`BarChart`, `AreaChart`, `HeatmapChart`, …) | ECharts draws on a canvas it sizes from the DOM |
48
49
  | `ImageCropper` (its canvas) | canvas cropping |
49
50
  | `RichTextEditor` (its editor) | Tiptap / ProseMirror |
50
51
  | mermaid diagrams inside `Markdown` | mermaid renders in the browser; the rest of the Markdown renders on the server |
52
+ | `MermaidDiagram` | the same mermaid chart on its own, rendered in the browser |
51
53
 
52
54
  - **The map entry is importable, but draws in the browser only.**
53
55
  `@nxgt/material-vue/map` is its own entry because `leaflet` and
package/docs/roadmap.md CHANGED
@@ -6,7 +6,31 @@ the version something shipped in.
6
6
 
7
7
  ## Now
8
8
 
9
- Nothing in progress right now; see Next.
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.
10
34
 
11
35
  ## Next
12
36
 
@@ -14,6 +38,20 @@ Nothing in progress right now; see Next.
14
38
  their table header on every printed page, keep rows and totals whole, print
15
39
  their colours, and a `PrintPage` sets size and margins for `window.print()`.
16
40
  Vue only: `@nxgt/material`'s print blocks already produce a PDF.
41
+ - **`StepperIndicator`, as in `@nxgt/material`** — the step markers on their
42
+ 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.
17
55
 
18
56
  ## Later
19
57
 
@@ -53,6 +91,8 @@ Nothing in progress right now; see Next.
53
91
 
54
92
  ## Not planned
55
93
 
94
+ - **The `Combobox` primitive family** — `ComboboxFormField` covers forms; the
95
+ low-level `Combobox` parts stay in `@nxgt/material` only.
56
96
  - **The React library's state and form stacks** — widgets keep their state in
57
97
  per-instance Pinia stores (`defineLocalStore`), forms use vee-validate, the
58
98
  primitives Reka UI, the charts ECharts through `vue-echarts`, and routing
@@ -23,6 +23,7 @@ words in your error.
23
23
  - [A `Toc`, `Pager`, `SeeAlso` or `ScrollProgress` inside `Doc` selected by `data-slot` stops matching](#a-toc-pager-seealso-or-scrollprogress-inside-doc-selected-by-data-slot-stops-matching)
24
24
  - [A `ResponsiveButton` selected by `[aria-expanded]` stops matching](#a-responsivebutton-selected-by-aria-expanded-stops-matching)
25
25
  - [A menu item selected by `[data-variant=destructive]` stops matching](#a-menu-item-selected-by-data-variantdestructive-stops-matching)
26
+ - [A `Stepper` step selected by `#step-N` stops matching](#a-stepper-step-selected-by-step-n-stops-matching)
26
27
  - [A test no longer finds a date or time picker by the `button` role, or a slider thumb by the exact name `Minimum`](#a-test-no-longer-finds-a-date-or-time-picker-by-the-button-role-or-a-slider-thumb-by-the-exact-name-minimum)
27
28
  - [An end-to-end test no longer finds a required field by a name ending in `*`](#an-end-to-end-test-no-longer-finds-a-required-field-by-a-name-ending-in-)
28
29
  - [A test no longer finds `aria-invalid` on an upload field's group or drop zone](#a-test-no-longer-finds-aria-invalid-on-an-upload-fields-group-or-drop-zone)
@@ -30,6 +31,7 @@ words in your error.
30
31
  - [A test no longer finds the breadcrumb's home button](#a-test-no-longer-finds-the-breadcrumbs-home-button)
31
32
  - [A `QRCode`'s `aria-label` is no longer on its wrapper](#a-qrcodes-aria-label-is-no-longer-on-its-wrapper)
32
33
  - [A `CheckboxGroup` or `RadioGroupField` option selected by `#<id>-<value>` stops matching](#a-checkboxgroup-or-radiogroupfield-option-selected-by-id-value-stops-matching)
34
+ - [A `Table`'s `id`, `aria-*` or `style` is no longer on its scrolling container](#a-tables-id-aria--or-style-is-no-longer-on-its-scrolling-container)
33
35
 
34
36
  **SSR & build**
35
37
 
@@ -100,6 +102,7 @@ words in your error.
100
102
  - [A test no longer finds a pagination, breadcrumb or other built-in label by its English name](#a-test-no-longer-finds-a-pagination-breadcrumb-or-other-built-in-label-by-its-english-name)
101
103
  - [A test no longer finds a chip's remove button by the exact name `Remove`, or a `LocationPreview` by its place name](#a-test-no-longer-finds-a-chips-remove-button-by-the-exact-name-remove-or-a-locationpreview-by-its-place-name)
102
104
  - [A test no longer finds an icon badge by role `button`, or an `Icon` named "Icon {name}" inside a button](#a-test-no-longer-finds-an-icon-badge-by-role-button-or-an-icon-named-icon-name-inside-a-button)
105
+ - [A button named "Icon plus Add" is now named "Add"](#a-button-named-icon-plus-add-is-now-named-add)
103
106
  - [`An invalid form control with name='…' is not focusable` (an `UploadField`)](#an-invalid-form-control-with-name-is-not-focusable)
104
107
  - [An `id`, `aria-describedby` or `data-testid` on a `DateField`, `DateRangeField` or `TimeField` is nowhere in the page](#an-id-aria-describedby-or-data-testid-on-a-datefield-daterangefield-or-timefield-is-nowhere-in-the-page)
105
108
  - [A test no longer finds the label or helper text inside a date or time picker's trigger](#a-test-no-longer-finds-the-label-or-helper-text-inside-a-date-or-time-pickers-trigger)
@@ -401,6 +404,30 @@ menubar colour had no token, so Tailwind generated nothing).
401
404
  [data-slot='dropdown-menu-item'][data-variant='error'] { font-weight: 600; }
402
405
  ```
403
406
 
407
+ ### A `Stepper` step selected by `#step-N` stops matching
408
+
409
+ **When:** after upgrading to 0.24.0, a test or a stylesheet that targets a
410
+ step by its id — `#step-0`, `page.locator('#step-1')`,
411
+ `document.getElementById('step-2')` — finds nothing.
412
+ **Why:** every `Stepper` gave its steps the same ids, `step-0`, `step-1`, …,
413
+ so two steppers on one page produced duplicate ids, which is invalid HTML and
414
+ breaks any `aria-*` reference to them. Each id is now prefixed with one
415
+ unique to its stepper (Vue's `useId`). The steps are also an `<ol>` of `<li>`
416
+ now, and the current one carries `aria-current="step"`. A selector
417
+ written against the old markup, such as `div[data-slot="stepper-step"]`,
418
+ stops matching too, since a step is now an `li`: drop the tag
419
+ (`[data-slot="stepper-step"]`).
420
+ **Fix:** select by `data-pw`: `step-N`, or `<name>-step-N` when you passed
421
+ `name` — the same values as in `@nxgt/material`, now on each step here too:
422
+
423
+ ```ts
424
+ await page.locator('[data-pw="step-1"]').click();
425
+ // with <Stepper name="checkout" … />
426
+ await page.locator('[data-pw="checkout-step-1"]').click();
427
+ // or the current step
428
+ await expect(page.locator('[aria-current="step"]')).toContainText('Shipping');
429
+ ```
430
+
404
431
  ### A test no longer finds a date or time picker by the `button` role, or a slider thumb by the exact name `Minimum`
405
432
 
406
433
  **When:** after upgrading to 0.22.0, a spec such as
@@ -535,6 +562,36 @@ which a `group` may not carry: assert its description instead,
535
562
  hidden hint sits inside the helper element, so match its text with
536
563
  `getByText('Pick any.', { exact: false })` or the description with a RegExp.
537
564
 
565
+ ### A `Table`'s `id`, `aria-*` or `style` is no longer on its scrolling container
566
+
567
+ **When:** after upgrading, either:
568
+
569
+ - a selector or listener aimed at the attributes you put on `<Table>` finds
570
+ nothing on `div[data-slot="table-container"]` (`#orders` matches the
571
+ `<table>` instead, `[data-testid]` / `[aria-label]` queries return the
572
+ table, a `@scroll` listener on `<Table>` never fires); or
573
+ - a `style` such as `max-height` on `<Table>` no longer limits the height,
574
+ and the table stops scrolling.
575
+
576
+ **Why:** `Table` passed every attribute but `class` to its scrolling
577
+ container. It now puts them on the `<table>`, as `@nxgt/material` does, so
578
+ `aria-busy`, `aria-label` or `id` describe the table itself. A
579
+ `max-height` on a `<table>` has no effect: the table grows instead of
580
+ scrolling.
581
+ **Fix:** select the container by its slot, or the table by role, and put a
582
+ height limit on a wrapper of your own:
583
+
584
+ ```vue
585
+ <div class="max-h-96 overflow-auto">
586
+ <Table aria-label="Orders">…</Table>
587
+ </div>
588
+ ```
589
+
590
+ ```ts
591
+ page.getByRole('table', { name: 'Orders' });
592
+ page.locator('[data-slot="table-container"]'); // the scrolling container
593
+ ```
594
+
538
595
  ### An end-to-end test no longer finds a required field by a name ending in `*`
539
596
 
540
597
  **When:** after upgrading to 0.22.0, `getByRole('textbox', { name: 'Email*' })`
@@ -1486,6 +1543,26 @@ const filter = useFilterContext(); // fine: this component is below FilterProvid
1486
1543
  </template>
1487
1544
  ```
1488
1545
 
1546
+ For a documentation shell of your own (`DocNav`, `DocArticle`, `DocLink`
1547
+ without `<Doc>`), provide the state yourself in the component that renders
1548
+ the parts:
1549
+
1550
+ ```vue
1551
+ <script setup lang="ts">
1552
+ import { type DocNavItem, provideDoc, useDoc } from '@nxgt/material-vue/components';
1553
+
1554
+ const props = defineProps<{ items: DocNavItem[]; pathname: string }>();
1555
+ provideDoc(
1556
+ useDoc({
1557
+ items: props.items,
1558
+ get pathname() {
1559
+ return props.pathname;
1560
+ },
1561
+ }),
1562
+ );
1563
+ </script>
1564
+ ```
1565
+
1489
1566
  ### `Clipboard is not available`
1490
1567
 
1491
1568
  **When:** calling `copyToClipboard(value)` on a page served over plain `http`
@@ -1728,6 +1805,33 @@ await expect(page.getByRole('img', { name: 'Phone' })).toBeVisible();
1728
1805
  await page.getByRole('button', { name: 'Delete' }).click();
1729
1806
  ```
1730
1807
 
1808
+ ### A button named "Icon plus Add" is now named "Add"
1809
+
1810
+ **When:** after upgrading to 0.24.0, `getByRole('button', { name: 'Icon plus Add' })`
1811
+ (or any name that starts with "Icon {name}") finds nothing, or a
1812
+ `getByRole('img', { name: 'Icon plus' })` inside a `Button` that also has text.
1813
+ **Why:** an `Icon` is named "Icon {name}" by default, and a `Button` takes its
1814
+ name from its content, so a `Button` holding an `Icon` and the text "Add" was
1815
+ announced "Icon plus Add". A `Button` with text, an `aria-label` or an
1816
+ `aria-labelledby` now hides an `Icon` inside it (`aria-hidden="true"`), as a
1817
+ named `IconButton` already did. A `Button` holding only an `Icon` still reads
1818
+ it, since that is the only name it has; give it an `aria-label` instead. Text
1819
+ a component produces from its props (a translation component) is not seen:
1820
+ pass the button an `aria-label` then. Text inside an `aria-hidden` element
1821
+ (a decorative count, `<span aria-hidden="true">3</span>`) does not name the
1822
+ button either, so beside it the `Icon` stays named.
1823
+ **Fix:** select the button by its text:
1824
+
1825
+ ```ts
1826
+ await page.getByRole('button', { name: 'Add' }).click();
1827
+ ```
1828
+
1829
+ and name an icon-only button yourself:
1830
+
1831
+ ```vue
1832
+ <Button aria-label="Add"><Icon name="plus" /></Button>
1833
+ ```
1834
+
1731
1835
  ### `An invalid form control with name='…' is not focusable`
1732
1836
 
1733
1837
  **When:** submitting a form with an empty `required` `UploadField`, with
@@ -99,6 +99,7 @@ export const COMPONENT_NAMES = [
99
99
  'CheckCardField',
100
100
  'CheckCardFormField',
101
101
  'Checkbox',
102
+ 'CheckboxFormField',
102
103
  'CheckboxGroup',
103
104
  'CheckboxGroupFormField',
104
105
  'ChecklistCard',
@@ -107,6 +108,7 @@ export const COMPONENT_NAMES = [
107
108
  'CodeBlock',
108
109
  'CodeDiff',
109
110
  'CodeEditor',
111
+ 'CodeEditorFormField',
110
112
  'CodeGroup',
111
113
  'Collapsible',
112
114
  'CollapsibleContent',
@@ -146,6 +148,7 @@ export const COMPONENT_NAMES = [
146
148
  'ContextMenuSubTrigger',
147
149
  'ContextMenuTrigger',
148
150
  'CopyButton',
151
+ 'CopyValue',
149
152
  'CountBadge',
150
153
  'CountryField',
151
154
  'CountryFormField',
@@ -162,6 +165,7 @@ export const COMPONENT_NAMES = [
162
165
  'DateFormField',
163
166
  'DateRangeField',
164
167
  'DateRangeFormField',
168
+ 'DefaultDocLink',
165
169
  'DeltaListCard',
166
170
  'Description',
167
171
  'Dialog',
@@ -177,10 +181,14 @@ export const COMPONENT_NAMES = [
177
181
  'DndOverlay',
178
182
  'DndProvider',
179
183
  'Doc',
184
+ 'DocArticle',
180
185
  'DocChildCards',
181
186
  'DocCopyMarkdown',
187
+ 'DocCopyPage',
182
188
  'DocEmpty',
183
189
  'DocHeader',
190
+ 'DocLink',
191
+ 'DocNav',
184
192
  'DocPage',
185
193
  'DocPager',
186
194
  'DocProgress',
@@ -280,6 +288,7 @@ export const COMPONENT_NAMES = [
280
288
  'ImageS3Field',
281
289
  'ImageS3FormField',
282
290
  'InlineEdit',
291
+ 'InlineEditFormField',
283
292
  'Input',
284
293
  'InputGroup',
285
294
  'InputGroupAddon',
@@ -328,6 +337,7 @@ export const COMPONENT_NAMES = [
328
337
  'MenubarSubContent',
329
338
  'MenubarSubTrigger',
330
339
  'MenubarTrigger',
340
+ 'MermaidDiagram',
331
341
  'MessageCard',
332
342
  'MiniGallery',
333
343
  'NativeSelect',
@@ -351,11 +361,13 @@ export const COMPONENT_NAMES = [
351
361
  'PaceCard',
352
362
  'Pager',
353
363
  'Pagination',
364
+ 'PaginationBar',
354
365
  'PaginationContent',
355
366
  'PaginationEllipsis',
356
367
  'PaginationFirst',
357
368
  'PaginationItem',
358
369
  'PaginationLast',
370
+ 'PaginationLink',
359
371
  'PaginationNext',
360
372
  'PaginationPrevious',
361
373
  'PaneLayout',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material-vue",
3
- "version": "0.23.3",
3
+ "version": "0.24.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"