@nxgt/material 2.4.1 → 2.5.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 (170) hide show
  1. package/README.md +28 -19
  2. package/dist/{_Uint8Array-DEXn42kj.js → _Uint8Array-DuTT6911.js} +1 -1
  3. package/dist/_overRest-93z6TD9V.js +81 -0
  4. package/dist/{abnfDiagram-O67JEVCF-C7NxOWqd.js → abnfDiagram-O67JEVCF-Cl4ukf6a.js} +4 -4
  5. package/dist/{architectureDiagram-NJMV4G6O-BIvwyzEh.js → architectureDiagram-NJMV4G6O-WmFlBQF5.js} +7 -7
  6. package/dist/{client-only.utils-DhcBUmau.js → async-slice-B8Xv2BLH.js} +164 -93
  7. package/dist/{blockDiagram-BEXU5L5S-CD5PW_mQ.js → blockDiagram-BEXU5L5S-C8UXdLfB.js} +11 -11
  8. package/dist/{c4Diagram-YGBWAQC7-WgqlRIL5.js → c4Diagram-YGBWAQC7-D9xSt2QO.js} +8 -8
  9. package/dist/{channel-Ba8pEAWx.js → channel-Dg92Hoym.js} +1 -1
  10. package/dist/{chunk-3FUC2YCW-CNBUvTSg.js → chunk-3FUC2YCW-4gCRt6fO.js} +8 -8
  11. package/dist/{chunk-5DYCD2WN-Bqi3iL5i.js → chunk-5DYCD2WN-Cja1INgd.js} +3 -3
  12. package/dist/{chunk-6AEJRKK7-DDXoGWDs.js → chunk-6AEJRKK7-3YWRP4aV.js} +2 -2
  13. package/dist/{chunk-742MDFTN-nG_ROTnT.js → chunk-742MDFTN-Cs1qGiTK.js} +2 -2
  14. package/dist/{chunk-7INBJB4K-Dg64CbhT.js → chunk-7INBJB4K-BY5n77ka.js} +6 -6
  15. package/dist/{chunk-7M6MHVWA-DEh1Crrc.js → chunk-7M6MHVWA-DnPXbiTM.js} +9 -9
  16. package/dist/{chunk-7PRAP22T-DIiYRAce.js → chunk-7PRAP22T-_Av9vcXz.js} +1 -1
  17. package/dist/{chunk-GNY47TPC-vOPvRE_8.js → chunk-GNY47TPC-DLqgU2SJ.js} +11 -11
  18. package/dist/{chunk-GTNCS2PH-CzDYTYNy.js → chunk-GTNCS2PH-BZ9WMD1v.js} +3 -3
  19. package/dist/{chunk-GWA4HPMP-CmsxMX3A.js → chunk-GWA4HPMP-Co-A6dtV.js} +3 -3
  20. package/dist/{chunk-MBY4JIJT-IA2XBafa.js → chunk-MBY4JIJT-C3RQP_Nk.js} +4 -4
  21. package/dist/{chunk-NETBCI7D-CQV8lo68.js → chunk-NETBCI7D-CBowDvwz.js} +1 -1
  22. package/dist/{chunk-O7XYJQB3-C87zK8E5.js → chunk-O7XYJQB3-A66QSOPA.js} +1 -1
  23. package/dist/{chunk-UA2S7LBM-B0LGFGu3.js → chunk-UA2S7LBM-BNeHeMx9.js} +6 -6
  24. package/dist/{chunk-WEXAMYUT-C-QP0DFq.js → chunk-WEXAMYUT-CucJfehv.js} +2 -2
  25. package/dist/{chunk-XXDRQBXY-Bq4sFnO2.js → chunk-XXDRQBXY-FgXU14Bh.js} +1 -1
  26. package/dist/{chunk-Z7XXMR3K-DcqSgI9Q.js → chunk-Z7XXMR3K-dRJc1Z30.js} +8 -8
  27. package/dist/{chunk-ZIGJFQKS-HDJ6e6OJ.js → chunk-ZIGJFQKS-C3xUAXJq.js} +3 -3
  28. package/dist/{classDiagram-v2-NBCMYWYE-tU42Be-2.js → classDiagram-v2-NBCMYWYE-CQmePeL4.js} +14 -14
  29. package/dist/{components-NLbPtyeB.js → components-CUkJhBwK.js} +26367 -25835
  30. package/dist/components.js +6 -6
  31. package/dist/{cose-bilkent-JH36ORCC-11-T1rfk.js → cose-bilkent-JH36ORCC-DRUT3qBb.js} +2 -2
  32. package/dist/{cynefinDiagram-VND7K2PF-1kHi2rGa.js → cynefinDiagram-VND7K2PF-B9HrdQW8.js} +4 -4
  33. package/dist/{dagre-6A5THRUB-CXwkkHFr.js → dagre-6A5THRUB-BhyJENrD.js} +574 -615
  34. package/dist/{diagram-22UHCM2B-BswK7SlD.js → diagram-22UHCM2B-CJc0JoTV.js} +15 -15
  35. package/dist/{diagram-3UASUU5V-5p5ra9Yi.js → diagram-3UASUU5V-0P3Cnq5H.js} +7 -7
  36. package/dist/{diagram-ATOU4E4O-Clju_0RY.js → diagram-ATOU4E4O-CNY-8kl0.js} +3 -3
  37. package/dist/{diagram-CDSNMT55-w7kkJ6MU.js → diagram-CDSNMT55-C8Mk1smi.js} +5 -5
  38. package/dist/{diagram-MLGK6HIB-mUOtRVmm.js → diagram-MLGK6HIB-CpfeILwW.js} +4 -4
  39. package/dist/{diagram-MPIPVDR6-DEUw58dq.js → diagram-MPIPVDR6-Do7bUnTd.js} +4 -4
  40. package/dist/{dist-gYpAi-su.js → dist-C3YK_ean.js} +1 -1
  41. package/dist/{dist-CeHNxLJG.js → dist-CRbPtqwD.js} +1 -1
  42. package/dist/dnd.js +1 -1
  43. package/dist/{ebnfDiagram-ZINNZB2B-BYfatie5.js → ebnfDiagram-ZINNZB2B-1GewX9Zq.js} +4 -4
  44. package/dist/{elk-276RUBZZ-CuGpDp18.js → elk-276RUBZZ-CvgDuHoY.js} +13 -13
  45. package/dist/{erDiagram-OPXOYQCR-vpNBbFgR.js → erDiagram-OPXOYQCR-BFgRy8E9.js} +14 -14
  46. package/dist/{field-blur-EUOqpHbF.js → field-blur-D1eQk-49.js} +1109 -1083
  47. package/dist/flowDiagram-KWPJA3E3-DGaa9iT2.js +17 -0
  48. package/dist/{ganttDiagram-FUAMR5RP-DC1sfzgs.js → ganttDiagram-FUAMR5RP-CDGrtGHY.js} +6 -6
  49. package/dist/{gitGraphDiagram-X574FWY7-DcGaPSwy.js → gitGraphDiagram-X574FWY7-80yOq8dE.js} +3 -3
  50. package/dist/{graphlib-DvOFXJbI.js → graphlib-Dt-isUVV.js} +103 -139
  51. package/dist/hooks.js +2 -2
  52. package/dist/{i18n-provider-XTTU9ArK.js → i18n-provider-Drsc2YXX.js} +1 -1
  53. package/dist/i18n.js +6 -6
  54. package/dist/{icon-button-Dl9PDYCE.js → icon-button-BxySFfrr.js} +581 -581
  55. package/dist/{infoDiagram-VRGFBTTK-Cvj167dl.js → infoDiagram-VRGFBTTK-Cboh-B9n.js} +3 -3
  56. package/dist/isArrayLikeObject-BhWbq65y.js +28 -0
  57. package/dist/isEmpty-DdlzV8Ue.js +53 -0
  58. package/dist/{ishikawaDiagram-OU5B5YK6-ChBwjCfr.js → ishikawaDiagram-OU5B5YK6-DwTd1YxL.js} +4 -4
  59. package/dist/{journeyDiagram-ZHPQQLJL-C6djNSgq.js → journeyDiagram-ZHPQQLJL-BuLKqWS3.js} +4 -4
  60. package/dist/{kanban-definition-PNTS6WVX-BRoa-Yp5.js → kanban-definition-PNTS6WVX-S8plI4wn.js} +10 -10
  61. package/dist/lib/components/ui/actions/button/button.compound-variants.d.ts +114 -0
  62. package/dist/lib/components/ui/actions/button/button.d.ts +4 -7
  63. package/dist/lib/components/ui/actions/button/button.variants.d.ts +7 -0
  64. package/dist/lib/components/ui/actions/button/has-text-content.d.ts +10 -0
  65. package/dist/lib/components/ui/actions/icon-button/icon-button.variants.d.ts +1 -1
  66. package/dist/lib/components/ui/collections/data-table/data-table-context.d.ts +4 -0
  67. package/dist/lib/components/ui/collections/data-table/data-table-pagination.d.ts +3 -1
  68. package/dist/lib/components/ui/collections/data-table/data-table.d.ts +12 -2
  69. package/dist/lib/components/ui/collections/data-table/hooks.d.ts +13 -13
  70. package/dist/lib/components/ui/collections/data-table/partials/data-table-body.d.ts +9 -5
  71. package/dist/lib/components/ui/collections/data-table/partials/data-table-empty-row.d.ts +14 -0
  72. package/dist/lib/components/ui/collections/data-table/partials/data-table-skeleton-rows.d.ts +7 -0
  73. package/dist/lib/components/ui/collections/filter/filter-field-number-range.d.ts +15 -0
  74. package/dist/lib/components/ui/collections/pagination/index.d.ts +1 -1
  75. package/dist/lib/components/ui/collections/pagination/pagination.d.ts +9 -1
  76. package/dist/lib/components/ui/display/chip/chip.d.ts +1 -1
  77. package/dist/lib/components/ui/feedback/toast/toast.d.ts +9 -4
  78. package/dist/lib/components/ui/fields/otp-field/index.d.ts +1 -0
  79. package/dist/lib/components/ui/fields/otp-field/input-otp.d.ts +1 -1
  80. package/dist/lib/components/ui/fields/slider/slider-header.d.ts +15 -0
  81. package/dist/lib/components/ui/fields/slider/slider.d.ts +7 -0
  82. package/dist/lib/components/ui/fields/switch/switch.d.ts +7 -0
  83. package/dist/lib/components/ui/layout/stepper/stepper-step.d.ts +17 -0
  84. package/dist/lib/components/ui/media/camera/camera-content.d.ts +1 -2
  85. package/dist/lib/components/ui/media/camera/camera-context.d.ts +4 -0
  86. package/dist/lib/components/ui/media/camera/camera-provider.d.ts +2 -0
  87. package/dist/lib/components/ui/media/camera/camera.d.ts +6 -3
  88. package/dist/lib/components/ui/media/camera/capture-photo.d.ts +13 -0
  89. package/dist/lib/components/ui/media/camera/index.d.ts +9 -1
  90. package/dist/lib/components/ui/media/camera/state/camera.reducers.d.ts +1 -1
  91. package/dist/lib/components/ui/media/camera/state/camera.slice.d.ts +4 -9
  92. package/dist/lib/components/ui/media/camera/state/camera.store.d.ts +11 -5
  93. package/dist/lib/components/ui/media/camera/state/camera.thunks.d.ts +4 -4
  94. package/dist/lib/components/ui/media/camera/types.d.ts +19 -9
  95. package/dist/lib/components/ui/media/camera/use-camera.d.ts +5 -11
  96. package/dist/lib/components/ui/media/camera/utils/camera.utils.d.ts +0 -1
  97. package/dist/lib/components/ui/media/index.d.ts +1 -0
  98. package/dist/lib/components/ui/overlays/dialog/dialog-outside.d.ts +5 -0
  99. package/dist/lib/components/ui/overlays/dialog/dialog-scroll-content.d.ts +9 -0
  100. package/dist/lib/components/ui/overlays/dialog/index.d.ts +1 -0
  101. package/dist/lib/components/ui/primitives/icon/icon-context.d.ts +1 -1
  102. package/dist/lib/components/ui/primitives/table/table.d.ts +6 -1
  103. package/dist/lib/components/ui/selection/index.d.ts +1 -0
  104. package/dist/lib/components/ui/selection/native-select/index.d.ts +1 -0
  105. package/dist/lib/components/ui/selection/native-select/native-select.d.ts +11 -0
  106. package/dist/lib/components/ui/selection/radio-group/radio-group.d.ts +8 -1
  107. package/dist/lib/i18n/resources/en/en.d.ts +7 -0
  108. package/dist/lib/i18n/resources/en/material.json.d.ts +8 -1
  109. package/dist/lib/i18n/resources/fr/fr.d.ts +7 -0
  110. package/dist/lib/i18n/resources/fr/material.json.d.ts +8 -1
  111. package/dist/lib/i18n/resources/resources.d.ts +14 -0
  112. package/dist/lib/i18n/translator.d.ts +1 -1
  113. package/dist/lib/lib/redux/status-slice.d.ts +1 -1
  114. package/dist/lib.js +295 -4
  115. package/dist/main.js +16 -16
  116. package/dist/map.js +5 -5
  117. package/dist/material.css +1 -1
  118. package/dist/merge-a7J0YkZX.js +156 -0
  119. package/dist/{mermaid.core-CEDjsxgL.js → mermaid.core-CE34byhz.js} +49 -49
  120. package/dist/{mindmap-definition-NLK3R4M7-BYtcw4Io.js → mindmap-definition-NLK3R4M7-emoij-nW.js} +13 -13
  121. package/dist/{pegDiagram-GJSIUBJH-Dcm3LCQX.js → pegDiagram-GJSIUBJH-CAO6aYvj.js} +4 -4
  122. package/dist/{pieDiagram-5QR66LMP-8ETWucra.js → pieDiagram-5QR66LMP-BdG1tWLn.js} +5 -5
  123. package/dist/print.js +2 -2
  124. package/dist/{quadrantDiagram-O4NWA36T-X05JG-jZ.js → quadrantDiagram-O4NWA36T-BCd-NwsO.js} +3 -3
  125. package/dist/{railroadDiagram-XR7U4H2S-CzOJlTUA.js → railroadDiagram-XR7U4H2S-DmKDXg8V.js} +4 -4
  126. package/dist/{requirementDiagram-PLB6GJNP-BwWSE7Fn.js → requirementDiagram-PLB6GJNP-CCcH5uSr.js} +13 -13
  127. package/dist/{sankeyDiagram-IPEJSGJF-B6YxyZMn.js → sankeyDiagram-IPEJSGJF-78cK-bfe.js} +3 -3
  128. package/dist/{sequenceDiagram-PO4LG4MO-DMLeIQ03.js → sequenceDiagram-PO4LG4MO-B2qb55A5.js} +5 -5
  129. package/dist/{sortable-provider-DuRzFvwe.js → sortable-provider-VCxPasNp.js} +17 -17
  130. package/dist/{src-CJ8DNrI1.js → src-C9kQSo6K.js} +1 -1
  131. package/dist/{stateDiagram-v2-GCMORJYK-CAqcgA-x.js → stateDiagram-v2-GCMORJYK-B4ghJrhD.js} +14 -14
  132. package/dist/{stored-language-BP90Ydtg.js → stored-language-CeD7kHui.js} +20 -2
  133. package/dist/{swimlanes-2SLR337P-DRQ2gho_.js → swimlanes-2SLR337P-DmHm2yJS.js} +12 -12
  134. package/dist/swimlanesDiagram-TC7HE7FX-Dq3gGsPj.js +28 -0
  135. package/dist/{timeline-definition-EJHVYXUP-BtvpCePa.js → timeline-definition-EJHVYXUP-EqJV_QKn.js} +5 -5
  136. package/dist/{translate-material-Ctzt0Y2N.js → translate-material-DgRdhz5d.js} +2 -2
  137. package/dist/{translator-Cfug7hC5.js → translator-B4zUVm7j.js} +1 -1
  138. package/dist/{use-heading-spy-DtVo2HBk.js → use-heading-spy-1BHMXjIn.js} +2 -2
  139. package/dist/{use-material-translation-CJswHOmK.js → use-material-translation-UbARR81K.js} +2 -2
  140. package/dist/{usecaseDiagram-POWQR4AR-DsYqBvQ-.js → usecaseDiagram-POWQR4AR-CvCl0p5N.js} +13 -13
  141. package/dist/{vennDiagram-UO4OBE2U-Aql8mhcy.js → vennDiagram-UO4OBE2U-CO0yFCI8.js} +4 -4
  142. package/dist/{wardleyDiagram-VNRHLVJA-tknlhG_t.js → wardleyDiagram-VNRHLVJA-QkwghAYo.js} +4 -4
  143. package/dist/{xychartDiagram-PMCCYNJV-CEf5bvG1.js → xychartDiagram-PMCCYNJV-BphsHFKA.js} +7 -7
  144. package/docs/components/buttons-and-actions.md +8 -3
  145. package/docs/components/data-display.md +1 -1
  146. package/docs/components/data-table-filter-and-lists.md +95 -6
  147. package/docs/components/feedback-and-status.md +3 -2
  148. package/docs/components/form-fields.md +48 -4
  149. package/docs/components/media-and-files.md +46 -7
  150. package/docs/components/navigation-and-layout.md +6 -1
  151. package/docs/components/overlays-and-menus.md +27 -2
  152. package/docs/components/primitives.md +33 -2
  153. package/docs/components/rich-widgets.md +1 -1
  154. package/docs/components/selection-and-choice.md +32 -6
  155. package/docs/guides/i18n.md +14 -8
  156. package/docs/guides/ssr.md +3 -1
  157. package/docs/previews/buttons-actions-actioncard.webp +0 -0
  158. package/docs/previews/media-files-camera.webp +0 -0
  159. package/docs/previews/navigation-layout-stepper.webp +0 -0
  160. package/docs/roadmap.md +37 -3
  161. package/docs/troubleshooting.md +109 -0
  162. package/package.json +1 -1
  163. package/dist/flowDiagram-KWPJA3E3-PC80XOp8.js +0 -17
  164. package/dist/isArrayLikeObject-CCJDO2RL.js +0 -86
  165. package/dist/lib/components/ui/media/camera/camera.provider.d.ts +0 -6
  166. package/dist/lib/components/ui/media/camera/state/index.d.ts +0 -2
  167. package/dist/lib/components/ui/media/camera/utils/index.d.ts +0 -1
  168. package/dist/lib-DIj5dvbb.js +0 -367
  169. package/dist/merge-Dm-kHFxo.js +0 -172
  170. package/dist/swimlanesDiagram-TC7HE7FX-DaXG362E.js +0 -28
@@ -412,10 +412,41 @@ export function Code() {
412
412
  | `error` | `boolean` | — | error colour on the slots |
413
413
  | `id` | `string` | from `useId()` | the input's id, which the label points to; the generated one is the same on the server and the client |
414
414
 
415
+ `InputOTP`, `InputOTPGroup`, `InputOTPSlot` and `InputOTPSeparator` are the
416
+ parts `OtpField` is built from (`input-otp`), for a layout of your own — no
417
+ label, no helper text:
418
+
419
+ ```tsx
420
+ import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from '@nxgt/material/components';
421
+ import { useState } from 'react';
422
+
423
+ export function Pin() {
424
+ const [pin, setPin] = useState('');
425
+ return (
426
+ <InputOTP aria-label="PIN" maxLength={4} value={pin} onChange={setPin}>
427
+ <InputOTPGroup>
428
+ <InputOTPSlot index={0} />
429
+ <InputOTPSlot index={1} />
430
+ </InputOTPGroup>
431
+ <InputOTPSeparator />
432
+ <InputOTPGroup>
433
+ <InputOTPSlot index={2} />
434
+ <InputOTPSlot index={3} />
435
+ </InputOTPGroup>
436
+ </InputOTP>
437
+ );
438
+ }
439
+ ```
440
+
441
+ `InputOTP` takes `input-otp`'s `OTPInput` props (`maxLength`, `value`,
442
+ `onChange`, `pattern`, …) plus `containerClassName`; `InputOTPSlot` takes the
443
+ `index` it shows; `InputOTPSeparator` is a `separator` with a dash, or your
444
+ children.
445
+
415
446
  **Emits:** `string` (the digits typed so far).
416
447
  **Form wrapper:** `OtpFormField` — `control` and `name`, see
417
448
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
418
- **Storybook:** `Form fields/OtpField` · **Vue:** `OtpField`, `OtpFormField`, `InputOTP`
449
+ **Storybook:** `Form fields/OtpField` · **Vue:** `OtpField`, `OtpFormField`, `InputOTP` (same parts)
419
450
 
420
451
  ---
421
452
 
@@ -478,7 +509,7 @@ import { InlineEdit } from '@nxgt/material/components';
478
509
  **Emits:** `string`, trimmed, on save only.
479
510
  **Form wrapper:** `InlineEditFormField` — `control` and `name`, see
480
511
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
481
- **Storybook:** `Form fields/InlineEdit` · **Vue:** `InlineEdit`
512
+ **Storybook:** `Form fields/InlineEdit` · **Vue:** `InlineEdit`, `InlineEditFormField`
482
513
 
483
514
  ---
484
515
 
@@ -605,8 +636,16 @@ import { Slider } from '@nxgt/material/components';
605
636
  Each thumb (`role="slider"`) is named by an `aria-labelledby` or `aria-label`
606
637
  you pass, else by the label (a vertical slider does not show it, and it still
607
638
  names the thumbs); a range's thumbs add "Minimum" and "Maximum":
608
- `getByRole('slider', { name: 'Price Minimum' })`. `aria-invalid` (or `error`)
609
- goes on every thumb too, not on the root `span`.
639
+ `getByRole('slider', { name: 'Price Minimum' })`. A labelled range is also a
640
+ `role="group"` named by the label, and its readout inputs are named
641
+ "Minimum" and "Maximum" (`material.slider.minimum` / `maximum`), so two range
642
+ sliders are told apart. `aria-invalid` (or `error`) goes on every thumb too,
643
+ not on the root `span`.
644
+
645
+ `SliderField` is the same component under `@nxgt/material-vue`'s name (an
646
+ alias, `SliderFieldProps` for its props), so code reads the same in both
647
+ libraries; neither name is deprecated, and the full naming alignment is
648
+ planned for 3.0.
610
649
 
611
650
  **Emits:** `number[]`. `SliderFormField` stores a plain `number` when the
612
651
  form's value is a number, and `number[]` when it is an array — the default
@@ -656,6 +695,11 @@ while the switch is on, from `checked` or, uncontrolled, from `defaultChecked` a
656
695
  `[data-checked="true"]` (a bare `[data-checked]` also matches rows that are
657
696
  off). `data-filed` says the same and is deprecated: it goes in 3.0.
658
697
 
698
+ `SwitchField` is the same component under `@nxgt/material-vue`'s name (an
699
+ alias, `SwitchFieldProps` for its props), so code reads the same in both
700
+ libraries; neither name is deprecated, and the full naming alignment is
701
+ planned for 3.0.
702
+
659
703
  **Emits:** `boolean`.
660
704
  **Form wrapper:** `SwitchFormField` — `control` and `name`, see
661
705
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
@@ -19,6 +19,7 @@ them into a form.
19
19
  | <img src="../previews/media-files-pdfreader.webp" width="96" alt=""> | [PdfReader](#pdfreader) | Browser-only PDF reader with zoom, search, thumbnails and print |
20
20
  | <img src="../previews/media-files-stxplayer.webp" width="96" alt=""> | [StxPlayer](#stxplayer) | Video / audio player with a queue, captions and mini layout |
21
21
  | | [VideoPlayer](#videoplayer) | Deprecated alias of `StxPlayer` |
22
+ | <img src="../previews/media-files-camera.webp" width="96" alt=""> | [Camera](#camera) | Webcam preview with a start / stop toggle and a still capture |
22
23
  | | [Print components](#print-components) | `PrintDocument` page shell and `@react-pdf/renderer` building blocks for PDF documents, and `downloadPdf`, `openPdf`, `usePdfFile` and `pdfResponse` to get the file |
23
24
  | <img src="../previews/media-files-map.webp" width="96" alt=""> | [Map](#map) | Leaflet map, markers and a location field (`@nxgt/material/map`) |
24
25
 
@@ -633,6 +634,51 @@ No preview: it is [`StxPlayer`](#stxplayer) under another name
633
634
 
634
635
  ---
635
636
 
637
+ ## Camera
638
+
639
+ ![Camera](../previews/media-files-camera.webp)
640
+
641
+ Webcam preview with a start / stop toggle and a still capture.
642
+
643
+ ```tsx
644
+ import { Camera } from '@nxgt/material/components';
645
+
646
+ export function Webcam() {
647
+ return (
648
+ <div className="w-[640px]">
649
+ <Camera />
650
+ </div>
651
+ );
652
+ }
653
+ ```
654
+
655
+ It asks for camera permission when it mounts, lists the devices, streams on
656
+ the toggle and shows the captured photo beside the preview; the stream is
657
+ stopped on unmount. **It renders in the browser only, after mount**
658
+ (`clientOnly`): on the server, and on the first client render, it renders
659
+ nothing, so it is safe in an SSR page. `Camera` takes the props of a `div`
660
+ (`className`, …) and has no callback: the capture stays inside the
661
+ component. Each `Camera` keeps its own state.
662
+
663
+ `Camera` renders its own parts and takes no children. The helpers it runs
664
+ are exported, and the first three are what a webcam UI of your own needs
665
+ (`<video>` + `getUserMedia`):
666
+
667
+ | Export | What it is |
668
+ | --- | --- |
669
+ | `capturePhotoFromVideo(video, state)` | draws a `<video>` frame on a canvas (`captureSettings`: resolution, quality, format, mirror) and returns `{ capturedImage }` as a data URL, `{ alert }` on failure, `null` when not streaming or on the server |
670
+ | `isCameraSupported()`, `requestCameraPermissions()`, `getCameraDevices()` | the browser checks the component runs |
671
+ | `useCamera()`, `useCameraContext()` | the context `Camera`'s own parts read (`{ videoRef, canvasRef, streamRef, state, actions }`); they throw outside `Camera`, and `Camera` exposes no slot, so they serve its internals and tests, as in `@nxgt/material-vue` |
672
+ | `createCameraStore(preloaded?)` | the Redux store each `Camera` creates for itself, for tests |
673
+
674
+ Types: `CameraProps`, `CameraState`, `CameraActions`, `CameraContextValue`,
675
+ `CameraAlert`, `CameraDeviceInfo`, `CaptureSettings`, `ImageFormat`,
676
+ `CapturePhotoResult`, `CameraStore`.
677
+
678
+ **Storybook:** `Media & files/Camera` · **Vue:** `Camera`, `useCamera`, `capturePhotoFromVideo` (`useCameraStore` for the store)
679
+
680
+ ---
681
+
636
682
  ## Print components
637
683
 
638
684
  No preview: these render PDF with `@react-pdf/renderer`, not DOM, so there is
@@ -892,10 +938,3 @@ Parts: `Map` (also exported as `MapComponent`), `Marker`, `LocationMarker`,
892
938
 
893
939
  **Form wrapper:** `LocationFormField` — see [forms](../guides/forms.md).
894
940
  **Storybook:** `Media & files/Map` · **Vue:** —
895
-
896
- ---
897
-
898
- ## Not documented here
899
-
900
- `Media & files/Camera` exists in Storybook only: the `Camera` component is not
901
- exported from the package, so it has no entry on this page.
@@ -437,6 +437,11 @@ Parts: `Stepper`, `StepperIndicator` (the progress bar alone: `current`,
437
437
  | `onChange` | `(index: number) => void` | — | makes steps clickable |
438
438
  | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | vertical from `md` only; phones get the compact bar |
439
439
  | `renderStepLabel` | `(step, index) => ReactNode` | — | replaces the "Step n" caption |
440
+ | `name` | `string` | — | the stepper's `data-pw` (`stepper` without it), and the prefix of each step's: `<name>-step-N` instead of `step-N` |
441
+
442
+ Each step is an `<li>` with `data-pw="step-N"` (`<name>-step-N` with `name`),
443
+ N counted from 0, for tests to select; the current step's button carries
444
+ `aria-current="step"`, and a completed or failed step is announced as such.
440
445
 
441
446
  A server renders the desktop layout (every step), as does a hydrating client;
442
447
  a phone switches to the compact bar right after hydrating. A page rendered
@@ -550,7 +555,7 @@ On `DocPage`: `title`, `description`, `badge`, `crumbs`, `editHref`,
550
555
  `updatedAt`, and `copyPage` / `copyMarkdown` with `markdown` for the copy
551
556
  buttons.
552
557
 
553
- **Storybook:** `Navigation & layout/Doc`, `Navigation & layout/Doc/Api`, `Navigation & layout/Doc/Blocks` · **Vue:** `Doc`, `DocPage`, `DocTopbar`, `DocSidebar`, `DocToc`, `DocPager`, `DocSearch`, `DocChildCards`, `DocSeeAlso`, `DocEmpty`, `DocProgress`
558
+ **Storybook:** `Navigation & layout/Doc`, `Navigation & layout/Doc/Api`, `Navigation & layout/Doc/Blocks` · **Vue:** `Doc`, `DocPage`, `DocTopbar`, `DocSidebar`, `DocNav`, `DocArticle`, `DocToc`, `DocPager`, `DocSearch`, `DocChildCards`, `DocSeeAlso`, `DocEmpty`, `DocProgress`, `DocLink`, `DefaultDocLink`, `DocCopyPage` (no `linkComponent`: `@navigate`)
554
559
 
555
560
  ---
556
561
 
@@ -61,7 +61,32 @@ import {
61
61
 
62
62
  Parts: `Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`,
63
63
  `DialogTitle`, `DialogDescription`, `DialogFooter`, `DialogClose`, plus
64
- `DialogOverlay` and `DialogPortal`.
64
+ `DialogOverlay` and `DialogPortal`, and `DialogScrollContent`: a content for
65
+ a dialog taller than the screen, which sits in a scrolling overlay with a
66
+ margin instead of being clipped, and renders its own close button. It takes
67
+ `DialogContent`'s props and moves and returns focus the same way.
68
+
69
+ ```tsx
70
+ import { Button, Dialog, DialogScrollContent, DialogTitle, DialogTrigger } from '@nxgt/material/components';
71
+
72
+ const clauses = Array.from({ length: 30 }, (_, i) => `Clause ${i + 1}. Either party may end the service with thirty days' notice.`);
73
+
74
+ export function Terms() {
75
+ return (
76
+ <Dialog>
77
+ <DialogTrigger>
78
+ <Button>Read the terms</Button>
79
+ </DialogTrigger>
80
+ <DialogScrollContent>
81
+ <DialogTitle>Terms of service</DialogTitle>
82
+ {clauses.map((clause) => (
83
+ <p key={clause}>{clause}</p>
84
+ ))}
85
+ </DialogScrollContent>
86
+ </Dialog>
87
+ );
88
+ }
89
+ ```
65
90
 
66
91
  | Prop | Type | Default | Effect |
67
92
  | --- | --- | --- | --- |
@@ -105,7 +130,7 @@ export function RenameButton() {
105
130
  }
106
131
  ```
107
132
 
108
- **Storybook:** `Overlays & menus/Dialog` · **Vue:** `Dialog` (same parts), `DialogScrollContent`
133
+ **Storybook:** `Overlays & menus/Dialog` · **Vue:** `Dialog` (same parts)
109
134
 
110
135
  ---
111
136
 
@@ -516,11 +516,42 @@ import {
516
516
  ```
517
517
 
518
518
  Parts: `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableRow`,
519
- `TableHead`, `TableCell`, `TableCaption` — each takes its HTML element's props.
519
+ `TableHead`, `TableCell`, `TableCaption` — each takes its HTML element's props
520
+ — plus `TableEmpty`, a full-width row for "nothing here" (`colSpan`, default
521
+ `1`; the content is centred):
522
+
523
+ ```tsx
524
+ import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow } from '@nxgt/material/components';
525
+
526
+ type Invoice = { id: string; amount: string };
527
+
528
+ export function Invoices({ invoices }: { invoices: Invoice[] }) {
529
+ return (
530
+ <Table>
531
+ <TableHeader>
532
+ <TableRow>
533
+ <TableHead>Invoice</TableHead>
534
+ <TableHead className="text-right">Amount</TableHead>
535
+ </TableRow>
536
+ </TableHeader>
537
+ <TableBody>
538
+ {invoices.length === 0 && <TableEmpty colSpan={2}>No invoices yet.</TableEmpty>}
539
+ {invoices.map((invoice) => (
540
+ <TableRow key={invoice.id}>
541
+ <TableCell>{invoice.id}</TableCell>
542
+ <TableCell className="text-right">{invoice.amount}</TableCell>
543
+ </TableRow>
544
+ ))}
545
+ </TableBody>
546
+ </Table>
547
+ );
548
+ }
549
+ ```
550
+
520
551
  For sorting, paging and selection use `DataTable` on the
521
552
  [Data table, filter & lists](data-table-filter-and-lists.md) page.
522
553
 
523
- **Storybook:** `Primitives/Table` · **Vue:** `Table` (same parts), `TableEmpty`
554
+ **Storybook:** `Primitives/Table` · **Vue:** `Table` (same parts)
524
555
 
525
556
  ---
526
557
 
@@ -372,7 +372,7 @@ function Config() {
372
372
 
373
373
  **Form wrapper:** `CodeEditorFormField` — `control` and `name`, see
374
374
  [forms](../guides/forms.md).
375
- **Storybook:** `Rich widgets/CodeEditor` · **Vue:** `CodeEditor`
375
+ **Storybook:** `Rich widgets/CodeEditor` · **Vue:** `CodeEditor`, `CodeEditorFormField`
376
376
 
377
377
  ---
378
378
 
@@ -10,7 +10,7 @@ value type each one writes. Everything is imported from
10
10
 
11
11
  | | Component | What it does |
12
12
  | --- | --- | --- |
13
- | <img src="../previews/selection-choice-select.webp" width="96" alt=""> | [Select](#select) | Select primitives with groups and labels |
13
+ | <img src="../previews/selection-choice-select.webp" width="96" alt=""> | [Select](#select) | Select primitives with groups and labels, and a styled native `<select>` |
14
14
  | <img src="../previews/selection-choice-selectfield.webp" width="96" alt=""> | [SelectField](#selectfield) | Labelled select built from `options` |
15
15
  | <img src="../previews/selection-choice-combobox.webp" width="96" alt=""> | [Combobox](#combobox) | Base UI combobox primitives |
16
16
  | <img src="../previews/selection-choice-comboboxfield.webp" width="96" alt=""> | [ComboboxField](#comboboxfield) | Labelled, searchable single select |
@@ -73,9 +73,32 @@ Parts: `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`,
73
73
  | `SelectContent` `position` | `'popper' \| 'item-aligned'` | `'popper'` | below the trigger, as wide as it; `item-aligned` overlays it macOS-style |
74
74
  | `SelectTrigger` `size` | `'sm' \| 'default'` | `'default'` | trigger height |
75
75
 
76
- **Emits:** `string` through `onValueChange`. For a form, use `SelectField` /
77
- `SelectFormField`.
78
- **Storybook:** `Selection & choice/Select` · **Vue:** `Select` (same parts), `NativeSelect`
76
+ `NativeSelect` is the browser's own `<select>` with the library's styling and
77
+ chevron, for when the platform picker is what you want (long lists on a
78
+ phone, no portal). Parts: `NativeSelect`, `NativeSelectOption`,
79
+ `NativeSelectOptGroup`; `value` / `defaultValue` / `onChange`, `className`
80
+ and every other attribute go to the `<select>`.
81
+
82
+ ```tsx
83
+ import { NativeSelect, NativeSelectOptGroup, NativeSelectOption } from '@nxgt/material/components';
84
+ import { useState } from 'react';
85
+
86
+ export function Food() {
87
+ const [food, setFood] = useState('banana');
88
+ return (
89
+ <NativeSelect aria-label="Food" value={food} onChange={(event) => setFood(event.target.value)}>
90
+ <NativeSelectOptGroup label="Fruits">
91
+ <NativeSelectOption value="apple">Apple</NativeSelectOption>
92
+ <NativeSelectOption value="banana">Banana</NativeSelectOption>
93
+ </NativeSelectOptGroup>
94
+ </NativeSelect>
95
+ );
96
+ }
97
+ ```
98
+
99
+ **Emits:** `string` through `onValueChange` (`Select`), a change event
100
+ (`NativeSelect`). For a form, use `SelectField` / `SelectFormField`.
101
+ **Storybook:** `Selection & choice/Select` · **Vue:** `Select` (same parts), `NativeSelect` (same parts)
79
102
 
80
103
  ---
81
104
 
@@ -297,7 +320,7 @@ the field's `name`, which two forms on one page shared).
297
320
  and `null` for indeterminate.
298
321
  **Form wrapper:** `CheckboxFormField` — `control` and `name`, see
299
322
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
300
- **Storybook:** `Selection & choice/Checkbox` · **Vue:** `Checkbox`
323
+ **Storybook:** `Selection & choice/Checkbox` · **Vue:** `Checkbox`, `CheckboxFormField`
301
324
 
302
325
  ---
303
326
 
@@ -372,7 +395,10 @@ export function Plan() {
372
395
  ```
373
396
 
374
397
  Parts: `RadioGroup` (labelled, from `options`) and `RadioGroupItem` (one
375
- radio, for a hand-built layout).
398
+ radio, for a hand-built layout). `RadioGroupField` is the same component under
399
+ `@nxgt/material-vue`'s name (an alias, `RadioGroupFieldProps` for its props),
400
+ so code reads the same in both libraries; neither name is deprecated, and the
401
+ full naming alignment is planned for 3.0.
376
402
 
377
403
  | Prop | Type | Default | Effect |
378
404
  | --- | --- | --- | --- |
@@ -195,16 +195,19 @@ The copy components render on their own comes from this namespace: empty
195
195
  states, dialogs, pickers, the rich-text editor's toolbar, menus, node dialogs,
196
196
  slash menu, font names and table-caption alignment
197
197
  (`material['rich-text-editor'].*`), the carousel arrows' screen-reader text,
198
- the camera, the autocomplete's `Add "…"` and empty state, the QR code and
198
+ the camera's button, toggle and image (`material.camera.capture`, `captured`,
199
+ `start` and `stop`), the autocomplete's `Add "…"` and empty state, the QR code and
199
200
  barcode accessible names, the calendar's navigation tooltips
200
201
  (`material.calendar.*`), the data table's selection checkboxes, the filter's
201
202
  presets and `Min`/`Max` fields, the `variant="4"` spinner's label,
202
203
  `ScrollToTop`, the network upload field's `Retry`/`Remove`, the map's
203
204
  `You are here` and zoom buttons, and the icon-only close button of
204
205
  `DialogClose`, `DrawerClose`, `SheetClose` and `SheetContent`'s
205
- `withCloseButton`. The other icon-only buttons are named from it too: the
206
+ `withCloseButton`. A range `Slider`'s readout inputs read
207
+ `material.slider.minimum` / `maximum` ("Minimum", "Maximum"). The other icon-only buttons are named from it too: the
206
208
  `Sidebar` toggle (`material.sidebar.toggle`), the data table's row expander
207
- (`material.table.labels.toggle-row`), a chip's remove button — `Chip`,
209
+ (`material.table.labels.toggle-row`), its loading status, "Loading data…" /
210
+ "Chargement des données…" (`material.table.loading`), a chip's remove button — `Chip`,
208
211
  `ComboboxChip` and the `Autocomplete` chips, "Remove {label}"
209
212
  (`material.chip.dismiss-label`) — the `LocationPreview` trigger
210
213
  (`material.map.preview` / `preview-named`), and the delete button of the
@@ -246,11 +249,14 @@ sheet title on a phone (`material['pane-layout']['sheet-title']`);
246
249
  `JsonViewer`'s "Empty JSON" (`material['json-viewer'].empty`);
247
250
  `TimePicker`'s AM/PM (`material['time-picker'].am` / `pm`, "a.m." / "p.m." in
248
251
  French); and the names of `DurationField`'s inputs, "Hours" and "Minutes"
249
- (`material['duration-field']['hours-label']` / `minutes-label`). `material.pagination.first` / `last`,
250
- `material.carousel.roledescription` / `slide` and
251
- `material.attributes.buttons.add-option` are there for `@nxgt/material-vue`'s
252
- `PaginationFirst`, `PaginationLast`, carousel and attribute options, so the
253
- bundles hold the same keys. Your `placeholder`, `sheetTitle` and `aria-label` still
252
+ (`material['duration-field']['hours-label']` / `minutes-label`); the status a
253
+ `Stepper` step announces after its title, "Completed" or "Error"
254
+ (`material.stepper.completed` / `error`, "Terminée" / "Erreur" in French); and
255
+ `PaginationFirst` / `PaginationLast`'s "First" / "Last"
256
+ (`material.pagination.first` / `last`). `material.carousel.roledescription` /
257
+ `slide` and `material.attributes.buttons.add-option` are there for
258
+ `@nxgt/material-vue`'s carousel and attribute options, so the bundles hold
259
+ the same keys. Your `placeholder`, `sheetTitle` and `aria-label` still
254
260
  win.
255
261
 
256
262
  To format filter values yourself in the provider's language, pass
@@ -34,7 +34,9 @@ at import time would crash the server before it answers anything.
34
34
  - **Browser-only widgets render nothing on the server.** `PdfReader`,
35
35
  `PdfSidebar`, `PdfPageList`, `PdfThumbnailSidebar` and the mermaid diagrams
36
36
  inside `Markdown` load their dependency with `import()` and are wrapped in
37
- `clientOnly`. They appear after hydration, with no mismatch.
37
+ `clientOnly`. They appear after hydration, with no mismatch. `Camera` is
38
+ `clientOnly` too: it has no lazy dependency, but `getUserMedia` has no
39
+ server side.
38
40
  - **Two entries are the exceptions, on purpose.** They are not in any barrel,
39
41
  and the build fails if another entry reaches their dependencies:
40
42
  - `@nxgt/material/print` reaches `@react-pdf/renderer` and pdfkit, whose
package/docs/roadmap.md CHANGED
@@ -6,7 +6,27 @@ 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
+ - **Stepper: unique ids, announced steps and truncated titles** — two steppers
17
+ on one page no longer share ids, a screen reader hears which step is current
18
+ and whether each is done or in error, and a long step title truncates.
19
+ - **Layouts that hold long content** — `ListTile`, `StatCard` and `Banner`
20
+ keep long titles, values and actions inside their box instead of overflowing.
21
+ - **The same exports as `@nxgt/material-vue`** — `Camera`, `NativeSelect`, the
22
+ `InputOTP` parts, `PaginationFirst` and `PaginationLast`, `TableEmpty`,
23
+ `DialogScrollContent` and `ToastAction`, `Button`'s sizes, and the
24
+ `RadioGroupField`, `SliderField` and `SwitchField` names, so code moves
25
+ between the two libraries without renaming.
26
+ - **DataTable loading and empty states** — skeleton rows while the data loads
27
+ (the rows already shown stay in place, dimmed, during a refresh), the table
28
+ announced as busy, and your own content when there are no rows, told
29
+ whether a filter emptied it.
10
30
 
11
31
  ## Next
12
32
 
@@ -15,6 +35,12 @@ Nothing in progress right now; see Next.
15
35
  - **Previews that cannot go stale** — a CI check that fails when a story changes
16
36
  but its preview image does not, so the picture in the docs is the component
17
37
  you install.
38
+ - **DateRangeField presets and time** — one-click ranges such as Today, Last 7
39
+ days and This month, and a time of day on each end of the range.
40
+ - **TreeSelectField** — pick one or several items from a tree inside a form
41
+ field.
42
+ - **FilePreview** — preview an image, PDF, video or text file, chosen by its
43
+ type, in one component.
18
44
 
19
45
  ## Later
20
46
 
@@ -30,11 +56,14 @@ Nothing in progress right now; see Next.
30
56
  The `Switch` row's misspelt `data-filed` attribute goes too: select
31
57
  `[data-checked="true"]` instead. `DateField` / `DateRangeField` stop
32
58
  accepting a day rule as `disabled`: pass it as `disabledDays`.
59
+ - **3.0: the same component names as `@nxgt/material-vue`** — the labelled
60
+ fields take the names both libraries share (`RadioGroupField`,
61
+ `SliderField`, `SwitchField`, today aliases of `RadioGroup`, `Slider` and
62
+ `Switch`), so code reads the same in React and Vue. Both names work until
63
+ then, and neither is deprecated yet.
33
64
  - **Multi-month calendar with custom caption** — the month, caption and grid
34
65
  slots together with `numberOfMonths` greater than one; today that combination
35
66
  is not supported.
36
- - **Range filters in generated filter forms** — slider ranges and number ranges
37
- rendered by the schema-driven filter fields, not only by hand-written ones.
38
67
  - **Ready-made documents** — invoice, quote, receipt, report, certificate and
39
68
  label-sheet templates that take your data and return a document, their labels
40
69
  translated.
@@ -219,5 +248,10 @@ Nothing in progress right now; see Next.
219
248
  chat and markdown. 1.16.1.
220
249
  - **`MentionList`** — mention suggestions with local search, for chat and the
221
250
  rich text editor. 1.16.0.
251
+ - **Range filters in generated filter forms** — slider ranges and number ranges
252
+ rendered by the schema-driven filter fields: a `Filter` schema field with
253
+ `type: 'slider-range'` or `'number-range'` renders a two-thumb slider or a
254
+ `Min`/`Max` pair. 1.0.0.
255
+ ([filters](components/data-table-filter-and-lists.md))
222
256
 
223
257
  Older releases: the [version list on npm](https://www.npmjs.com/package/@nxgt/material?activeTab=versions).
@@ -77,6 +77,7 @@ words in your error.
77
77
  **Runtime**
78
78
 
79
79
  - [`must be used within` / `must be used inside` (context providers)](#must-be-used-within--must-be-used-inside)
80
+ - [`Camera components must be used inside <Camera>`](#camera-components-must-be-used-inside-camera)
80
81
  - [`Clipboard is not available`](#clipboard-is-not-available)
81
82
  - [`Upload failed with status 403`](#upload-failed-with-status-403)
82
83
  - [`Network error during upload`](#network-error-during-upload)
@@ -94,6 +95,7 @@ words in your error.
94
95
  - [A test no longer finds a chip's remove button by the exact name `Remove`](#a-test-no-longer-finds-a-chips-remove-button-by-the-exact-name-remove)
95
96
  - [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)
96
97
  - [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)
98
+ - [A button named "Icon plus Add" is now named "Add"](#a-button-named-icon-plus-add-is-now-named-add)
97
99
  - [`An invalid form control with name='…' is not focusable` (an `UploadField`)](#an-invalid-form-control-with-name-is-not-focusable)
98
100
  - [A `DateField` or `DateRangeField` with `disabled` still opens](#a-datefield-or-daterangefield-with-disabled-still-opens)
99
101
  - [An `id`, `aria-describedby` or `data-testid` on a `DateField`, `DateRangeField` or `TimeField` is not on the trigger](#an-id-aria-describedby-or-data-testid-on-a-datefield-daterangefield-or-timefield-is-not-on-the-trigger)
@@ -109,6 +111,7 @@ words in your error.
109
111
  - [`ConfirmDialog` buttons stay in English](#confirmdialog-buttons-stay-in-english)
110
112
  - [A `ConfirmationDialog` opened with `open` no longer closes](#a-confirmationdialog-opened-with-open-no-longer-closes)
111
113
  - [A menu item selected by `[data-variant=destructive]` stops matching](#a-menu-item-selected-by-data-variantdestructive-stops-matching)
114
+ - [A `Stepper` step selected by `#step-N` stops matching](#a-stepper-step-selected-by-step-n-stops-matching)
112
115
  - [Keys typed right after opening a dialog, sheet or drawer land inside it](#keys-typed-right-after-opening-a-dialog-sheet-or-drawer-land-inside-it)
113
116
 
114
117
  **PDF reader**
@@ -1213,6 +1216,61 @@ function ActiveCount() {
1213
1216
  </FilterProvider>;
1214
1217
  ```
1215
1218
 
1219
+ ### `Camera components must be used inside <Camera>`
1220
+
1221
+ **When:** on render, from `useCamera()` or `useCameraContext()` called in a
1222
+ component of yours.
1223
+ **Why:** both read the context `Camera` provides to its own parts (the video,
1224
+ the capture button, the toggle). `Camera` renders those parts itself and takes
1225
+ no children, so no component of yours is ever below its provider: the hooks
1226
+ serve its internals, and throw anywhere else. A second copy of
1227
+ `@nxgt/material` gives the same message (see
1228
+ [Invalid hook call](#invalid-hook-call-hooks-can-only-be-called-inside-of-the-body-of-a-function-component)).
1229
+ **Fix:** render `<Camera />` as it is, or build your own webcam UI on the
1230
+ helpers, which need no provider:
1231
+
1232
+ ```tsx
1233
+ import { capturePhotoFromVideo, isCameraSupported } from '@nxgt/material/components';
1234
+ import { useEffect, useRef, useState } from 'react';
1235
+
1236
+ const SETTINGS = { resolution: { width: 1280, height: 720 }, quality: 0.9, format: 'jpeg' as const, mirror: false };
1237
+
1238
+ export function Snapshot() {
1239
+ const video = useRef<HTMLVideoElement>(null);
1240
+ const [photo, setPhoto] = useState<string | null>(null);
1241
+
1242
+ useEffect(() => {
1243
+ if (!isCameraSupported()) return;
1244
+ let stream: MediaStream | undefined;
1245
+ let cancelled = false;
1246
+ const stop = (s: MediaStream) => s.getTracks().forEach((track) => track.stop());
1247
+ navigator.mediaDevices.getUserMedia({ video: true }).then((s) => {
1248
+ // Unmounted before the permission prompt resolved: release the camera.
1249
+ if (cancelled) return stop(s);
1250
+ stream = s;
1251
+ if (video.current) video.current.srcObject = s;
1252
+ });
1253
+ return () => {
1254
+ cancelled = true;
1255
+ if (stream) stop(stream);
1256
+ };
1257
+ }, []);
1258
+
1259
+ const capture = () => {
1260
+ const result = capturePhotoFromVideo(video.current, { isStreaming: true, captureSettings: SETTINGS });
1261
+ if (result?.capturedImage) setPhoto(result.capturedImage);
1262
+ };
1263
+
1264
+ return (
1265
+ <>
1266
+ <video ref={video} autoPlay playsInline muted />
1267
+ <button type="button" onClick={capture}>Capture</button>
1268
+ {photo && <img src={photo} alt="Captured" />}
1269
+ </>
1270
+ );
1271
+ }
1272
+ ```
1273
+
1216
1274
  ### `Clipboard is not available`
1217
1275
 
1218
1276
  **When:** calling `copyToClipboard(value)` on a page served over plain `http`
@@ -1629,6 +1687,33 @@ If you passed an `IconButton` as `ConfirmationDialog`'s `icon`, pass the glyph
1629
1687
  alone and style the badge with `iconProps={{ variant, color }}`: the dialog
1630
1688
  already wraps it in a badge, and yours is now a button inside a `<span>`.
1631
1689
 
1690
+ ### A button named "Icon plus Add" is now named "Add"
1691
+
1692
+ **When:** after upgrading to 2.5.0, `getByRole('button', { name: 'Icon plus Add' })`
1693
+ (or any name that starts with "Icon {name}") finds nothing, or a
1694
+ `getByRole('img', { name: 'Icon plus' })` inside a `Button` that also has text.
1695
+ **Why:** an `Icon` is named "Icon {name}" by default, and a `Button` takes its
1696
+ name from its content, so a `Button` holding an `Icon` and the text "Add" was
1697
+ announced "Icon plus Add". A `Button` with text, an `aria-label` or an
1698
+ `aria-labelledby` now hides an `Icon` inside it (`aria-hidden="true"`), as a
1699
+ named `IconButton` already did. A `Button` holding only an `Icon` still reads
1700
+ it, since that is the only name it has; give it an `aria-label` instead. Text
1701
+ a component produces from its props (a translation component) is not seen:
1702
+ pass the button an `aria-label` then. Text inside an `aria-hidden` element
1703
+ (a decorative count, `<span aria-hidden="true">3</span>`) does not name the
1704
+ button either, so beside it the `Icon` stays named.
1705
+ **Fix:** select the button by its text:
1706
+
1707
+ ```ts
1708
+ await page.getByRole('button', { name: 'Add' }).click();
1709
+ ```
1710
+
1711
+ and name an icon-only button yourself:
1712
+
1713
+ ```tsx
1714
+ <Button aria-label="Add"><Icon name="plus" /></Button>
1715
+ ```
1716
+
1632
1717
  ### `An invalid form control with name='…' is not focusable`
1633
1718
 
1634
1719
  **When:** submitting a form with an empty `required` `UploadField`, before
@@ -1930,6 +2015,30 @@ render `data-variant="error"`, and both use the `error` colour.
1930
2015
  [data-slot='menubar-item'][data-variant='error'] { font-weight: 600; }
1931
2016
  ```
1932
2017
 
2018
+ ### A `Stepper` step selected by `#step-N` stops matching
2019
+
2020
+ **When:** after upgrading to 2.5.0, a test or a stylesheet that targets a
2021
+ step by its id — `#step-0`, `page.locator('#step-1')`,
2022
+ `document.getElementById('step-2')` — finds nothing.
2023
+ **Why:** every `Stepper` gave its steps the same ids, `step-0`, `step-1`, …,
2024
+ so two steppers on one page produced duplicate ids, which is invalid HTML and
2025
+ breaks any `aria-*` reference to them. Each id is now prefixed with one
2026
+ unique to its stepper (React's `useId`). The steps are also an `<ol>` of
2027
+ `<li>` now, and the current one carries `aria-current="step"`. A selector
2028
+ written against the old markup, such as `div[data-slot="stepper-step"]`,
2029
+ stops matching too, since a step is now an `li`: drop the tag
2030
+ (`[data-slot="stepper-step"]`).
2031
+ **Fix:** select by `data-pw`, which did not change — `step-N`, or
2032
+ `<name>-step-N` when you passed `name`:
2033
+
2034
+ ```ts
2035
+ await page.locator('[data-pw="step-1"]').click();
2036
+ // with <Stepper name="checkout" … />
2037
+ await page.locator('[data-pw="checkout-step-1"]').click();
2038
+ // or the current step
2039
+ await expect(page.locator('[aria-current="step"]')).toContainText('Shipping');
2040
+ ```
2041
+
1933
2042
  ### Keys typed right after opening a dialog, sheet or drawer land inside it
1934
2043
 
1935
2044
  **When:** after upgrading, a test (or code of yours) that opens a `Dialog`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "2.4.1",
3
+ "version": "2.5.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"
@@ -1,17 +0,0 @@
1
- import "./src-CJ8DNrI1.js";
2
- import "./chunk-ZIGJFQKS-HDJ6e6OJ.js";
3
- import "./chunk-O7XYJQB3-C87zK8E5.js";
4
- import "./chunk-742MDFTN-nG_ROTnT.js";
5
- import "./chunk-MBY4JIJT-IA2XBafa.js";
6
- import "./chunk-7PRAP22T-DIiYRAce.js";
7
- import "./chunk-7INBJB4K-Dg64CbhT.js";
8
- import "./chunk-5DYCD2WN-Bqi3iL5i.js";
9
- import "./chunk-Z7XXMR3K-DcqSgI9Q.js";
10
- import "./chunk-GWA4HPMP-CmsxMX3A.js";
11
- import "./chunk-XXDRQBXY-Bq4sFnO2.js";
12
- import "./chunk-WEXAMYUT-C-QP0DFq.js";
13
- import "./chunk-UA2S7LBM-B0LGFGu3.js";
14
- import "./chunk-GNY47TPC-vOPvRE_8.js";
15
- import "./chunk-LNGE3PJU-DJjGTS7O.js";
16
- import { n as e } from "./chunk-7M6MHVWA-DEh1Crrc.js";
17
- export { e as diagram };