@nxgt/material 3.80.0 → 3.82.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 (163) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +4 -3
  3. package/dist/{alert-dialog-DgHtXZuB.js → alert-dialog-DXLJ4AZP.js} +75 -74
  4. package/dist/{async-slice-YBQsGtQT.js → async-slice-BfVokA46.js} +1 -1
  5. package/dist/{button-_vm-xSBU.js → button-CSKUtMI3.js} +2 -2
  6. package/dist/{calculator-CTd_S23A.js → calculator-Cv00UQgR.js} +10 -10
  7. package/dist/calculator-field.js +6 -6
  8. package/dist/{calculator-segment-OI1BT3bA.js → calculator-segment-CwnC2cV_.js} +1 -1
  9. package/dist/calculator.js +1 -1
  10. package/dist/charts.js +3 -3
  11. package/dist/{chip-DumcVzUO.js → chip-BGn0V6Pt.js} +3 -3
  12. package/dist/{code-editor-CiwgfSOK.js → code-editor-B25-mWhb.js} +4 -4
  13. package/dist/code-editor.js +4 -4
  14. package/dist/{code-language-items-Dip0xnmc.js → code-language-items-oRjtO3qV.js} +2 -2
  15. package/dist/components/base-content.css +11 -0
  16. package/dist/components/components.css +2 -0
  17. package/dist/components/ui/collections/data-table/data-table-items-tiles.css +27 -1
  18. package/dist/components/ui/collections/data-table/data-table-items.css +15 -0
  19. package/dist/components/ui/display/tube-cap/tube-cap.css +87 -0
  20. package/dist/{components-CJFHhkZv.js → components-BmZk-git.js} +19258 -18882
  21. package/dist/components.js +37 -37
  22. package/dist/{conversion-variant-CqrPwPjC.js → conversion-variant-wZd-G6xR.js} +3 -3
  23. package/dist/{currency-field-CMFvctye.js → currency-field-DizBTAat.js} +4 -4
  24. package/dist/{date.utils-DUvqcjan.js → date.utils-JTEIo3mM.js} +98 -98
  25. package/dist/{dialog-B0iU3fmh.js → dialog-B-3s-oXt.js} +3 -3
  26. package/dist/dnd.js +1 -1
  27. package/dist/{dropdown-menu-CPOgcajv.js → dropdown-menu-C6rcYGB9.js} +1 -1
  28. package/dist/editor.js +987 -980
  29. package/dist/{empty-state-D3Jggupu.js → empty-state-CSkghnD9.js} +1 -1
  30. package/dist/{financial-variant-BdyfYhnp.js → financial-variant-igpGRbGw.js} +5 -5
  31. package/dist/{hooks-Z7KKbv4G.js → hooks-k8_f3B0k.js} +2 -2
  32. package/dist/hooks.js +9 -8
  33. package/dist/{hover-card-CFGIX6kg.js → hover-card-DuaVVKMb.js} +1 -1
  34. package/dist/i18n.js +3 -3
  35. package/dist/{icon-badge-kxGWUvcO.js → icon-badge-3ThP6EOl.js} +1 -1
  36. package/dist/{icon-button-BamxMwZs.js → icon-button-4PKlaI1g.js} +2 -2
  37. package/dist/lib/components/ui/actions/button/button-tones.d.ts +17 -16
  38. package/dist/lib/components/ui/collections/day-grouped-list/day-grouped-list.d.ts +6 -1
  39. package/dist/lib/components/ui/collections/filter/use-filter-field-config.d.ts +2 -0
  40. package/dist/lib/components/ui/collections/server-table/types-states.d.ts +5 -1
  41. package/dist/lib/components/ui/display/index.d.ts +1 -0
  42. package/dist/lib/components/ui/display/tube-cap/index.d.ts +2 -0
  43. package/dist/lib/components/ui/display/tube-cap/tube-cap.d.ts +27 -0
  44. package/dist/lib/components/ui/display/tube-cap/tube-cap.kinds.d.ts +7 -0
  45. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  46. package/dist/lib/components/ui/fields/inline-edit/inline-edit-display.d.ts +5 -1
  47. package/dist/lib/components/ui/fields/inline-edit/inline-edit-editor.d.ts +6 -1
  48. package/dist/lib/components/ui/fields/inline-edit/inline-edit.d.ts +14 -1
  49. package/dist/lib/components/ui/fields/inline-edit/use-inline-edit.d.ts +4 -1
  50. package/dist/lib/components/ui/fields/repeatable-group/partials/repeatable-group-row.d.ts +3 -1
  51. package/dist/lib/components/ui/fields/repeatable-group/partials/repeatable-group-rows.d.ts +2 -1
  52. package/dist/lib/components/ui/fields/repeatable-group/repeatable-group.d.ts +1 -1
  53. package/dist/lib/components/ui/fields/repeatable-group/types.d.ts +7 -0
  54. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.types.d.ts +6 -1
  55. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-tile.d.ts +1 -1
  56. package/dist/lib/components/ui/media/galleries/gallery.d.ts +1 -1
  57. package/dist/lib/components/ui/media/galleries/gallery.utils.d.ts +14 -0
  58. package/dist/lib/components/ui/media/galleries/lightbox-gallery.d.ts +1 -1
  59. package/dist/lib/components/ui/media/galleries/mini-gallery.d.ts +1 -1
  60. package/dist/lib/components/ui/media/galleries/partials/gallery-tile-button.d.ts +12 -0
  61. package/dist/lib/components/ui/media/galleries/partials/gallery-tile.d.ts +3 -1
  62. package/dist/lib/components/ui/overlays/dialog/dialog-parts.d.ts +1 -1
  63. package/dist/lib/components/ui/overlays/sheet/sheet-parts.d.ts +5 -0
  64. package/dist/lib/components/ui/selection/choice-card-group/choice-card-balance.d.ts +4 -0
  65. package/dist/lib/components/ui/selection/choice-card-group/choice-card-group.d.ts +3 -1
  66. package/dist/lib/components/ui/selection/choice-card-group/choice-card-group.types.d.ts +7 -0
  67. package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-body.d.ts +5 -1
  68. package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-row-parts.d.ts +2 -1
  69. package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-row-props.d.ts +8 -5
  70. package/dist/lib/components/ui/selection/sortable-list/partials/use-owed-focus.d.ts +10 -0
  71. package/dist/lib/components/ui/selection/sortable-list/sortable-list-context.d.ts +2 -0
  72. package/dist/lib/components/ui/selection/sortable-list/sortable-list.keyboard.d.ts +1 -1
  73. package/dist/lib/components/ui/widgets/event-calendar/event-calendar-context.d.ts +3 -0
  74. package/dist/lib/components/ui/widgets/event-calendar/event-calendar-shell.d.ts +15 -0
  75. package/dist/lib/components/ui/widgets/event-calendar/event-calendar-template.d.ts +9 -0
  76. package/dist/lib/components/ui/widgets/event-calendar/event-calendar.d.ts +7 -3
  77. package/dist/lib/components/ui/widgets/event-calendar/event-calendar.template.d.ts +41 -0
  78. package/dist/lib/components/ui/widgets/event-calendar/index.d.ts +1 -0
  79. package/dist/lib/components/ui/widgets/event-calendar/partials/calendar-date-nav.d.ts +2 -0
  80. package/dist/lib/components/ui/widgets/event-calendar/partials/calendar-lane-toggle.d.ts +5 -0
  81. package/dist/lib/components/ui/widgets/event-calendar/partials/time-grid-header-template.d.ts +7 -0
  82. package/dist/lib/components/ui/widgets/event-calendar/template.types.d.ts +73 -0
  83. package/dist/lib/components/ui/widgets/event-calendar/types.d.ts +2 -0
  84. package/dist/lib/components/ui/widgets/event-calendar/use-chip-event.d.ts +1 -0
  85. package/dist/lib/components/ui/widgets/event-calendar/use-drawn-events.d.ts +4 -3
  86. package/dist/lib/components/ui/widgets/event-calendar/use-template-calendar.d.ts +18 -0
  87. package/dist/lib/components/ui/widgets/event-calendar/use-template-lane.d.ts +17 -0
  88. package/dist/lib/hooks/index.d.ts +1 -0
  89. package/dist/lib/i18n/resources/en/en.d.ts +2 -0
  90. package/dist/lib/i18n/resources/en/material.json.d.ts +2 -0
  91. package/dist/lib/i18n/resources/fr/fr.d.ts +2 -0
  92. package/dist/lib/i18n/resources/fr/material.json.d.ts +2 -0
  93. package/dist/lib/i18n/resources/resources.d.ts +4 -0
  94. package/dist/lib/i18n/translator.d.ts +1 -1
  95. package/dist/lib/illustrations-game/index.d.ts +4 -3
  96. package/dist/lib.js +2 -2
  97. package/dist/main.js +53 -52
  98. package/dist/map.js +7 -7
  99. package/dist/material.css +1 -1
  100. package/dist/pdf-reader.js +1 -1
  101. package/dist/{percent-field-C2LNr8NA.js → percent-field-QwU1l2Q4.js} +1 -1
  102. package/dist/{picker-surface-BUCFMoAZ.js → picker-surface-CiTycWgV.js} +4 -4
  103. package/dist/{popover-DFRwQF12.js → popover-YmbzbKQG.js} +1 -1
  104. package/dist/{pos-types-CXnEdjdy.js → pos-types-BFrapNmx.js} +1 -1
  105. package/dist/{pos-variant-DMwn_7is.js → pos-variant-CctfqieO.js} +4 -4
  106. package/dist/print.js +1 -1
  107. package/dist/{programmer-variant-Bo4eiDFi.js → programmer-variant-Dd0YOixn.js} +2 -2
  108. package/dist/{public-CyQPkkSR.js → public-CshR_aW7.js} +10 -10
  109. package/dist/{rich-code-block-iK7UcZdc.js → rich-code-block-BuJLDoBu.js} +5 -5
  110. package/dist/{scientific-variant-B9ScTgfx.js → scientific-variant-CjErQxuZ.js} +2 -2
  111. package/dist/{sortable-provider-BZwVjSVU.js → sortable-provider-DU2nvpFc.js} +2 -2
  112. package/dist/{spinner-RITOxvpH.js → spinner-DH1mm8hH.js} +1 -1
  113. package/dist/{tabs-DSo4eCUn.js → tabs-Um0SFyf1.js} +1 -1
  114. package/dist/theme-picker.js +2 -2
  115. package/dist/{theme-toggle-3gbzQX0x.js → theme-toggle-C-Ih4uQU.js} +1 -1
  116. package/dist/{toolbar-items-Bt5RK-Nx.js → toolbar-items-BoEY5bkt.js} +4 -4
  117. package/dist/{translate-material-CZTGqgen.js → translate-material-D4rn5UaQ.js} +1 -1
  118. package/dist/{translator-DTlOlIoq.js → translator-CcEgQ-H7.js} +4 -0
  119. package/dist/{use-chat-composer-DBY01IpX.js → use-chat-composer-BfFcmDua.js} +1439 -1391
  120. package/dist/{use-click-lock-BZYtTmVt.js → use-click-lock-Bwg_caHO.js} +14 -14
  121. package/dist/{use-heading-spy-Dt48bBts.js → use-heading-spy-gDNtK1sU.js} +19 -19
  122. package/dist/{use-material-translation-BJucC_xe.js → use-material-translation-DYrfV_JJ.js} +1 -1
  123. package/dist/{use-return-focus-BS1JLcnq.js → use-return-focus-C_Qz_yTk.js} +1 -1
  124. package/docs/README.md +1 -1
  125. package/docs/components/data-display.md +36 -8
  126. package/docs/components/data-table-filter-and-lists.md +7 -3
  127. package/docs/components/form-fields.md +6 -0
  128. package/docs/components/media-and-files.md +1 -0
  129. package/docs/components/navigation-and-layout.md +3 -1
  130. package/docs/components/overlays-and-menus.md +11 -5
  131. package/docs/components/rich-widgets.md +85 -1
  132. package/docs/components/selection-and-choice.md +49 -10
  133. package/docs/guides/forms.md +7 -0
  134. package/docs/guides/helpers.md +23 -0
  135. package/docs/guides/icon-motifs.md +1 -1
  136. package/docs/guides/illustrations.md +5 -5
  137. package/docs/guides/layouts.md +2 -2
  138. package/docs/guides/theming.md +7 -7
  139. package/docs/previews/data-display-tubecap.webp +0 -0
  140. package/docs/previews/feedback-status-illustration-game.webp +0 -0
  141. package/docs/previews/selection-choice-sortablelist.webp +0 -0
  142. package/docs/roadmap.md +3 -1
  143. package/docs/troubleshooting.md +50 -2
  144. package/lib/components/base-content.css +11 -0
  145. package/lib/components/components.css +2 -0
  146. package/lib/components/ui/collections/data-table/data-table-items-tiles.css +27 -1
  147. package/lib/components/ui/collections/data-table/data-table-items.css +15 -0
  148. package/lib/components/ui/display/tube-cap/tube-cap.css +87 -0
  149. package/lib/themes/ambre.css +1 -1
  150. package/lib/themes/ardoise.css +1 -1
  151. package/lib/themes/bleumono.css +1 -1
  152. package/lib/themes/bluemoon.css +1 -1
  153. package/lib/themes/corail.css +2 -2
  154. package/lib/themes/foret.css +1 -1
  155. package/lib/themes/lavande.css +1 -1
  156. package/lib/themes/menthe.css +2 -2
  157. package/lib/themes/ocean.css +1 -1
  158. package/lib/themes/pink.css +2 -2
  159. package/lib/themes/prune.css +1 -1
  160. package/lib/themes/sable.css +1 -1
  161. package/lib/themes/slate.css +1 -1
  162. package/lib/themes/teal.css +2 -2
  163. package/package.json +2 -2
@@ -16,35 +16,35 @@ function o(e, t, n) {
16
16
  //#region lib/components/ui/actions/button/button-tones.ts
17
17
  var s = {
18
18
  primary: "text-primary-text",
19
- secondary: "text-secondary",
20
- success: "text-success dark:text-[color-mix(in_oklab,var(--color-success)_85%,white)]",
21
- info: "text-info",
19
+ secondary: "text-[color-mix(in_oklab,var(--color-secondary)_90%,black)] dark:text-[color-mix(in_oklab,var(--color-secondary)_80%,white)]",
20
+ success: "text-[color-mix(in_oklab,var(--color-success)_90%,black)] dark:text-[color-mix(in_oklab,var(--color-success)_70%,white)]",
21
+ info: "text-[color-mix(in_oklab,var(--color-info)_90%,black)] dark:text-[color-mix(in_oklab,var(--color-info)_85%,white)]",
22
22
  warning: "text-[color-mix(in_oklab,var(--color-warning)_85%,black)] dark:text-[color-mix(in_oklab,var(--color-warning)_85%,white)]",
23
- error: "text-[color-mix(in_oklab,var(--color-error)_85%,black)] dark:text-[color-mix(in_oklab,var(--color-error)_70%,white)]",
23
+ error: "text-[color-mix(in_oklab,var(--color-error)_80%,black)] dark:text-[color-mix(in_oklab,var(--color-error)_70%,white)]",
24
24
  default: "text-foreground"
25
25
  }, c = {
26
26
  primary: "[&_svg]:data-[slot=icon]:text-primary-text",
27
- secondary: "[&_svg]:data-[slot=icon]:text-secondary",
28
- success: "[&_svg]:data-[slot=icon]:text-success dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-success)_85%,white)]",
29
- info: "[&_svg]:data-[slot=icon]:text-info",
27
+ secondary: "[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-secondary)_90%,black)] dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-secondary)_80%,white)]",
28
+ success: "[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-success)_90%,black)] dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-success)_70%,white)]",
29
+ info: "[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-info)_90%,black)] dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-info)_85%,white)]",
30
30
  warning: "[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-warning)_85%,black)] dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-warning)_85%,white)]",
31
- error: "[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-error)_85%,black)] dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-error)_70%,white)]",
31
+ error: "[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-error)_80%,black)] dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-error)_70%,white)]",
32
32
  default: "[&_svg]:data-[slot=icon]:text-foreground"
33
33
  }, l = {
34
34
  primary: "[&_svg]:stroke-primary-text",
35
- secondary: "[&_svg]:stroke-secondary",
36
- success: "[&_svg]:stroke-success dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-success)_85%,white)]",
37
- info: "[&_svg]:stroke-info",
35
+ secondary: "[&_svg]:stroke-[color-mix(in_oklab,var(--color-secondary)_90%,black)] dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-secondary)_80%,white)]",
36
+ success: "[&_svg]:stroke-[color-mix(in_oklab,var(--color-success)_90%,black)] dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-success)_70%,white)]",
37
+ info: "[&_svg]:stroke-[color-mix(in_oklab,var(--color-info)_90%,black)] dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-info)_85%,white)]",
38
38
  warning: "[&_svg]:stroke-[color-mix(in_oklab,var(--color-warning)_85%,black)] dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-warning)_85%,white)]",
39
- error: "[&_svg]:stroke-[color-mix(in_oklab,var(--color-error)_85%,black)] dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-error)_70%,white)]",
39
+ error: "[&_svg]:stroke-[color-mix(in_oklab,var(--color-error)_80%,black)] dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-error)_70%,white)]",
40
40
  default: "[&_svg]:stroke-foreground"
41
41
  }, u = {
42
42
  primary: "[--morph-on:var(--color-primary)] [--morph-on-fg:var(--color-primary-foreground)] [--btn-focus:var(--color-primary-text)] [--btn-line:var(--color-primary)]",
43
43
  secondary: "[--morph-on:var(--color-secondary)] dark:[--morph-on:color-mix(in_oklab,var(--color-secondary)_70%,var(--color-background))] [--morph-on-fg:var(--color-secondary-foreground)] [--btn-focus:var(--color-secondary)] [--btn-line:var(--color-secondary)]",
44
- success: "[--morph-on:var(--color-success)] dark:[--morph-on:color-mix(in_oklab,var(--color-success)_60%,var(--color-background))] [--morph-on-fg:var(--color-success-foreground)] [--btn-focus:var(--color-success)] dark:[--btn-focus:color-mix(in_oklab,var(--color-success)_85%,white)] [--btn-line:var(--color-success)]",
44
+ success: "[--morph-on:var(--color-success)] dark:[--morph-on:color-mix(in_oklab,var(--color-success)_60%,var(--color-background))] [--morph-on-fg:var(--color-success-foreground)] [--btn-focus:var(--color-success)] dark:[--btn-focus:color-mix(in_oklab,var(--color-success)_70%,white)] [--btn-line:var(--color-success)]",
45
45
  info: "[--morph-on:var(--color-info)] dark:[--morph-on:color-mix(in_oklab,var(--color-info)_60%,var(--color-background))] [--morph-on-fg:var(--color-info-foreground)] [--btn-focus:var(--color-info)] [--btn-line:var(--color-info)]",
46
46
  warning: "[--morph-on:var(--color-warning)] dark:[--morph-on:color-mix(in_oklab,var(--color-warning)_60%,var(--color-background))] [--morph-on-fg:var(--color-warning-foreground)] [--btn-focus:color-mix(in_oklab,var(--color-warning)_85%,black)] dark:[--btn-focus:color-mix(in_oklab,var(--color-warning)_85%,white)] [--btn-line:var(--color-warning)]",
47
- error: "[--morph-on:var(--color-error)] [--morph-on-fg:var(--color-error-foreground)] [--btn-focus:color-mix(in_oklab,var(--color-error)_85%,black)] dark:[--btn-focus:color-mix(in_oklab,var(--color-error)_70%,white)] [--btn-line:var(--color-error)]",
47
+ error: "[--morph-on:var(--color-error)] [--morph-on-fg:var(--color-error-foreground)] [--btn-focus:color-mix(in_oklab,var(--color-error)_80%,black)] dark:[--btn-focus:color-mix(in_oklab,var(--color-error)_70%,white)] [--btn-line:var(--color-error)]",
48
48
  default: "[--morph-on:var(--color-foreground)] [--morph-on-fg:var(--color-background)] [--btn-focus:var(--color-foreground)] [--btn-line:var(--color-foreground)]"
49
49
  }, d = 1e3;
50
50
  function f(e, a) {
@@ -1,9 +1,9 @@
1
1
  import { t as e } from "./utils-BLfntITJ.js";
2
- import { a as t, c as n, d as r, i, n as a, r as o, t as s, u as c } from "./alert-dialog-DgHtXZuB.js";
3
- import { a as l } from "./button-_vm-xSBU.js";
4
- import { i as u } from "./translate-material-CZTGqgen.js";
5
- import { t as d } from "./use-material-translation-BJucC_xe.js";
6
- import { _ as f, g as p, h as m, m as h, t as g, v as _ } from "./date.utils-DUvqcjan.js";
2
+ import { a as t, c as n, d as r, i, n as a, r as o, t as s, u as c } from "./alert-dialog-DXLJ4AZP.js";
3
+ import { a as l } from "./button-CSKUtMI3.js";
4
+ import { i as u } from "./translate-material-D4rn5UaQ.js";
5
+ import { t as d } from "./use-material-translation-DYrfV_JJ.js";
6
+ import { _ as f, g as p, h as m, t as h, v as g, y as _ } from "./date.utils-JTEIo3mM.js";
7
7
  import { useCallback as v, useEffect as y, useMemo as b, useRef as x, useState as S } from "react";
8
8
  import { Fragment as C, jsx as w, jsxs as T } from "react/jsx-runtime";
9
9
  import { busySettler as E, busySettler as D, createSubmitGuard as O, createSubmitGuard as k } from "@nxgt/material-core/submit-guard";
@@ -254,7 +254,7 @@ var A = {
254
254
  ], a;
255
255
  return a = n === 1 ? r && i.includes(r) ? "ère" : "er" : "ème", n + a;
256
256
  }, z = ["MMM", "MMMM"], B = {
257
- en: h,
257
+ en: m,
258
258
  fr: {
259
259
  code: "fr",
260
260
  formatDistance: j,
@@ -266,35 +266,35 @@ var A = {
266
266
  value: "d"
267
267
  } : e),
268
268
  ordinalNumber: R,
269
- era: f({
269
+ era: g({
270
270
  values: ee,
271
271
  defaultWidth: "wide"
272
272
  }),
273
- quarter: f({
273
+ quarter: g({
274
274
  values: te,
275
275
  defaultWidth: "wide",
276
276
  argumentCallback: (e) => e - 1
277
277
  }),
278
- month: f({
278
+ month: g({
279
279
  values: F,
280
280
  defaultWidth: "wide"
281
281
  }),
282
- day: f({
282
+ day: g({
283
283
  values: I,
284
284
  defaultWidth: "wide"
285
285
  }),
286
- dayPeriod: f({
286
+ dayPeriod: g({
287
287
  values: L,
288
288
  defaultWidth: "wide"
289
289
  })
290
290
  },
291
291
  match: {
292
- ordinalNumber: m({
292
+ ordinalNumber: p({
293
293
  matchPattern: /^(\d+)(ième|ère|ème|er|e)?/i,
294
294
  parsePattern: /\d+/i,
295
295
  valueCallback: (e) => parseInt(e)
296
296
  }),
297
- era: p({
297
+ era: f({
298
298
  matchPatterns: {
299
299
  narrow: /^(av\.J\.C|ap\.J\.C|ap\.J\.-C)/i,
300
300
  abbreviated: /^(av\.J\.-C|av\.J-C|apr\.J\.-C|apr\.J-C|ap\.J-C)/i,
@@ -304,7 +304,7 @@ var A = {
304
304
  parsePatterns: { any: [/^av/i, /^ap/i] },
305
305
  defaultParseWidth: "any"
306
306
  }),
307
- quarter: p({
307
+ quarter: f({
308
308
  matchPatterns: {
309
309
  narrow: /^T?[1234]/i,
310
310
  abbreviated: /^[1234](er|ème|e)? trim\.?/i,
@@ -320,7 +320,7 @@ var A = {
320
320
  defaultParseWidth: "any",
321
321
  valueCallback: (e) => e + 1
322
322
  }),
323
- month: p({
323
+ month: f({
324
324
  matchPatterns: {
325
325
  narrow: /^[jfmasond]/i,
326
326
  abbreviated: /^(janv|févr|mars|avr|mai|juin|juill|juil|août|sept|oct|nov|déc)\.?/i,
@@ -359,7 +359,7 @@ var A = {
359
359
  },
360
360
  defaultParseWidth: "any"
361
361
  }),
362
- day: p({
362
+ day: f({
363
363
  matchPatterns: {
364
364
  narrow: /^[lmjvsd]/i,
365
365
  short: /^(di|lu|ma|me|je|ve|sa)/i,
@@ -389,7 +389,7 @@ var A = {
389
389
  },
390
390
  defaultParseWidth: "any"
391
391
  }),
392
- dayPeriod: p({
392
+ dayPeriod: f({
393
393
  matchPatterns: {
394
394
  narrow: /^(a|p|minuit|midi|mat\.?|ap\.?m\.?|soir|nuit)/i,
395
395
  any: /^([ap]\.?\s?m\.?|du matin|de l'après[-\s]midi|du soir|de la nuit)/i
@@ -415,11 +415,11 @@ var A = {
415
415
  }
416
416
  };
417
417
  function V() {
418
- let e = u()?.language ?? "en", t = b(() => B[e] ?? h, [e]);
418
+ let e = u()?.language ?? "en", t = b(() => B[e] ?? m, [e]);
419
419
  return {
420
420
  language: e,
421
421
  locale: t,
422
- format: v((e, n) => g.format(e, n, t), [t])
422
+ format: v((e, n) => h.format(e, n, t), [t])
423
423
  };
424
424
  }
425
425
  //#endregion
@@ -1,4 +1,4 @@
1
- import { i as e, t } from "./translate-material-CZTGqgen.js";
1
+ import { i as e, t } from "./translate-material-D4rn5UaQ.js";
2
2
  import { useCallback as n } from "react";
3
3
  //#region lib/i18n/use-material-translation.ts
4
4
  function r() {
@@ -1,6 +1,6 @@
1
1
  import { o as e, r as t } from "./dist-DVRQkaTK.js";
2
2
  import { i as n, n as r, r as i, t as a } from "./dist-DWZXlCZR.js";
3
- import { C as o, S as s, x as c } from "./spinner-RITOxvpH.js";
3
+ import { C as o, S as s, x as c } from "./spinner-DH1mm8hH.js";
4
4
  import { a as l, i as u, r as d } from "./dist-DHdUUM1w.js";
5
5
  import { c as f, i as p, l as m, o as h, r as g, s as ee } from "./in-dialog-D61Yeaol.js";
6
6
  import { r as te } from "./escape-owner-DpaP0lm1.js";
package/docs/README.md CHANGED
@@ -26,7 +26,7 @@ detail: every option, the second example, and the traps.
26
26
  | [Streaming (HLS)](guides/media-streaming.md) | You play `.m3u8` streams in `MediaPlayer` with `@nxgt/material/media-hls` |
27
27
  | [Maps](guides/maps.md) | You render Leaflet maps or a location field with `@nxgt/material/map` |
28
28
  | [Drawing](guides/drawing.md) | You draw diagrams, badges or illustrations from code with `@nxgt/material/draw`: scenes and JSX primitives, roles and tones, a strict CSP, accessibility and events, sketch, animation, PNG, PDF and e-mail |
29
- | [Illustrations](guides/illustrations.md) | You draw state illustrations from your Font Awesome sprites with `@nxgt/material/illustrations`: the twelve scenes and the health and education sets, `EmptyState` / `ErrorState` / `DataTable` with `illustration`, `IllustrationDefaults` for an app, sizes, motion and CSP |
29
+ | [Illustrations](guides/illustrations.md) | You draw state illustrations from your Font Awesome sprites with `@nxgt/material/illustrations`: the twelve scenes and the health, education and game sets, `EmptyState` / `ErrorState` / `DataTable` with `illustration`, `IllustrationDefaults` for an app, sizes, motion and CSP |
30
30
  | [Icon motifs](guides/icon-motifs.md) | You use icon backgrounds, celestial skies, illustrations or a theme that names a motif: serving your own Font Awesome sprites (the package ships none), `IconSpritesProvider`, `icons` / `iconStyle` / `iconMotif`, `sky`, the health and education sets, Free fallbacks for Pro-only names |
31
31
  | [Backgrounds](guides/backgrounds.md) | You want a gradient or patterned backdrop: tones, intensity, the opt-in `backgrounds.css`, custom colours, motion and CSP |
32
32
  | [SSR](guides/ssr.md) | Your app renders on a server and bundles this package |
@@ -21,6 +21,7 @@ from `@nxgt/material/components`.
21
21
  | <img src="../previews/data-display-metric-cards.webp" width="96" alt=""> | [Metric cards](#metric-cards) | Sixteen dashboard tiles |
22
22
  | <img src="../previews/data-display-card-presets.webp" width="96" alt=""> | [Card presets](#card-presets) | Twenty-seven ready-made cards |
23
23
  | <img src="../previews/data-display-statusindicator.webp" width="96" alt=""> | [StatusIndicator](#statusindicator) | Coloured status dot with a label |
24
+ | <img src="../previews/data-display-tubecap.webp" width="96" alt=""> | [TubeCap](#tubecap) | A specimen tube's chip: a cap in its fixed convention colour, the container's name beside it, optional volume, accession and action |
24
25
  | <img src="../previews/data-display-summarydata.webp" width="96" alt=""> | [SummaryData](#summarydata) | Label / value pairs as a grid or inline |
25
26
  | <img src="../previews/data-display-description.webp" width="96" alt=""> | [Description](#description) | One read-only label / value pair |
26
27
  | <img src="../previews/data-display-descriptionlist.webp" width="96" alt=""> | [DescriptionList](#descriptionlist) | A dense auto-fill grid of label / value pairs in a `dl`, with a panels variant |
@@ -193,7 +194,7 @@ no `style` attribute and a strict CSP is fine.
193
194
  - Seat changes are not animated beyond a colour fade, which stops under
194
195
  `prefers-reduced-motion`.
195
196
 
196
- **Storybook:** `Data display/SeatRing` · **Vue:** not yet (#1931)
197
+ **Storybook:** `Data display/SeatRing` · **Vue:** not yet (#2470) (#1931)
197
198
 
198
199
  ---
199
200
 
@@ -268,7 +269,7 @@ const [open, setOpen] = useState(false);
268
269
  the layout follows `dir` (RTL) and dark mode.
269
270
  - Keep `front` and `description` free of the secret: they are always in the page.
270
271
 
271
- **Storybook:** `Data display/SecretCard` · **Vue:** not yet (#1938)
272
+ **Storybook:** `Data display/SecretCard` · **Vue:** not yet (#2470) (#1938)
272
273
 
273
274
  ---
274
275
 
@@ -425,7 +426,7 @@ import { CountBadge, IconButton } from '@nxgt/material/components';
425
426
 
426
427
  ![HighlightedText](../previews/data-display-highlightedtext.webp)
427
428
 
428
- A string with the words of a search query in a themed `<mark>`. Case, accents and ligatures are folded (`œ` is `oe`, `æ` is `ae`, `ß` is `ss`), and a query word matches at the start of a word, as a search engine's normaliser does.
429
+ A string with the words of a search query in a themed `<mark>`. Case, accents and ligatures are folded (`œ` is `oe`, `æ` is `ae`, `ß` is `ss`), and a query word matches at the start of a word, as a search engine's normaliser does. The `Guides/Recipes/Search dialog and results page` story marks the matches in command rows and result cards with it.
429
430
 
430
431
  ```tsx
431
432
  import { HighlightedText, highlightRanges } from '@nxgt/material/components';
@@ -790,6 +791,33 @@ import { StatusIndicator } from '@nxgt/material/components';
790
791
 
791
792
  ---
792
793
 
794
+ ## TubeCap
795
+
796
+ ![TubeCap](../previews/data-display-tubecap.webp)
797
+
798
+ A chip for a specimen tube. The cap colour comes from the international tube convention, so it is a fixed value: it does not follow the theme and is the same in light and dark. The container's name is written beside the cap (`label` is required), so colour never carries the meaning alone; the text uses the theme's foreground, which keeps the theme's 4.5:1 contrast.
799
+
800
+ ```tsx
801
+ import { TubeCap } from '@nxgt/material/components';
802
+
803
+ <TubeCap kind="edta" label="EDTA" volume="4 mL" accession="L00000042-1" />
804
+ ```
805
+
806
+ | Prop | Type | Default | Effect |
807
+ | --- | --- | --- | --- |
808
+ | `kind` | `'edta' \| 'heparin' \| 'citrate' \| 'esr' \| 'serum' \| 'gel' \| 'fluoride' \| 'trace' \| 'other'` | required | the cap colour: lavender, green, light blue, black, red, gold, grey, royal blue, plain |
809
+ | `label` | `ReactNode` | required | the container's name beside the cap; it also names the chip (`role="group"`) |
810
+ | `volume` | `ReactNode` | none | the volume, as text |
811
+ | `accession` | `ReactNode` | none | the tube's accession label |
812
+ | `action` | `ReactNode` | none | trailing slot, such as an `IconButton` |
813
+ | `size` | `'sm' \| 'default' \| 'lg'` | `'default'` | cap of 20, 28 or 40px |
814
+
815
+ `TUBE_CAP_KINDS` lists the kinds. The look is CSS on `data-kind`, with no inline style (strict CSP). The cap is `aria-hidden`.
816
+
817
+ **Storybook:** `Data display/TubeCap` · **Vue:** `TubeCap` (`@nxgt/material-vue/components`)
818
+
819
+ ---
820
+
793
821
  ## SummaryData
794
822
 
795
823
  ![SummaryData](../previews/data-display-summarydata.webp)
@@ -1339,7 +1367,7 @@ and the user's zone) and both sides agree:
1339
1367
  <ChangeHistory entries={entries} now={loaderData.now} timeZone={loaderData.timeZone} />
1340
1368
  ```
1341
1369
 
1342
- **Storybook:** `Data display/ChangeHistory` · **Vue:** not yet (tracked as a `vue` issue)
1370
+ **Storybook:** `Data display/ChangeHistory` · **Vue:** not yet (#2470) (tracked as a `vue` issue)
1343
1371
 
1344
1372
  ---
1345
1373
 
@@ -1376,7 +1404,7 @@ The offset follows the instant (`Europe/Paris` is `UTC+1` in winter and
1376
1404
  `UTC+2` in summer). `ChangeHistory` takes a `timeZone` and `RelativeTime` a `now`
1377
1405
  the same way; pass the app's zone from the server so every screen agrees.
1378
1406
 
1379
- **Storybook:** `Data display/ZonedTime` · **Vue:** not yet (tracked as a `vue` issue)
1407
+ **Storybook:** `Data display/ZonedTime` · **Vue:** not yet (#2470) (tracked as a `vue` issue)
1380
1408
 
1381
1409
  ---
1382
1410
 
@@ -1445,7 +1473,7 @@ own, so they add up to less.
1445
1473
  - Bar widths are SVG attributes, so server markup has no `style` attribute and
1446
1474
  a strict CSP is fine.
1447
1475
 
1448
- **Storybook:** `Data display/VoteTally` · **Vue:** not yet (#1940)
1476
+ **Storybook:** `Data display/VoteTally` · **Vue:** not yet (#2470) (#1940)
1449
1477
 
1450
1478
  ---
1451
1479
 
@@ -1778,7 +1806,7 @@ lock set by another code form disables its field and leaves Cancel usable.
1778
1806
  button by `getByRole('button', { name: 'Activate' })` and the lock by
1779
1807
  `getByRole('alert')`.
1780
1808
 
1781
- **Storybook:** `Data display/EnrolmentCard`, `…/Shared lock`, `Guides/Accessibility/Touch targets/Verification code` · **Vue:** not yet
1809
+ **Storybook:** `Data display/EnrolmentCard`, `…/Shared lock`, `Guides/Accessibility/Touch targets/Verification code` · **Vue:** not yet (#2470)
1782
1810
 
1783
1811
  ---
1784
1812
 
@@ -1921,4 +1949,4 @@ import { RecoveryCodesStatus, VerificationLockProvider } from '@nxgt/material/co
1921
1949
 
1922
1950
  It reads the page-wide lock like [EnrolmentCard](#enrolmentcard) does. With `requireCode={false}` there is no code form, so `error` and `attemptsLeft` are not shown: the plain button follows `pending`, and is disabled by `locked` and by the page-wide lock; then the lock's text (the message of the answer that locked, or "Too many wrong codes. Try again later.") shows above it as a muted line that describes the button.
1923
1951
 
1924
- **Storybook:** `Data display/RecoveryCodesStatus`, `Data display/EnrolmentCard/Shared lock` · **Vue:** not yet
1952
+ **Storybook:** `Data display/RecoveryCodesStatus`, `Data display/EnrolmentCard/Shared lock` · **Vue:** not yet (#2470)
@@ -864,8 +864,9 @@ export default function Students() {
864
864
  search. When rows come back while the focus was on the empty page, it
865
865
  goes to the search; rows that come back on their own leave the focus
866
866
  alone. The empty page stays while the next answer loads (`data-busy`).
867
- - **A failed fetch.** Pass `error` (`{ title?, description?, cause?, kind? }`,
868
- `null` when there is none; `kind` takes `ErrorState`'s kinds, `generic`
867
+ - **A failed fetch.** Pass `error` (`{ title?, description?, cause?, kind?, illustration? }`,
868
+ `null` when there is none; `illustration` is a scene id, a node, `true` or
869
+ `false`, as on `ListEmptyPage`, and replaces the scene `kind` picks; `kind` takes `ErrorState`'s kinds, `generic`
869
870
  (default), `offline` or `maintenance`; `title` and `description` default to
870
871
  the two strings below, `cause` to none) and `onRetry`: the failure replaces the rows,
871
872
  the header, the footer and the empty page, with a Retry (a returned promise,
@@ -1667,6 +1668,7 @@ import { DayGroupedList } from '@nxgt/material/components';
1667
1668
  | `labels` | `Partial<Record<DayGroupKey, ReactNode>>` | — | replaces a heading |
1668
1669
  | `sticky` | `boolean` | `true` | headings stick while their items scroll |
1669
1670
  | `showCount` | `boolean` | `false` | the count beside each heading |
1671
+ | `headingLevel` | `2 \| 3 \| 4 \| 5 \| 6` | `3` | the level of each heading (`<h2>`…`<h6>`), to follow the heading above the list |
1670
1672
  | `headerClassName` / `listClassName` | `string` | — | a heading's background on a tinted parent, the `<ul>`'s dividers |
1671
1673
 
1672
1674
  **Storybook:** `Data table, filter & lists/DayGroupedList` · **Vue:** — (later)
@@ -2992,7 +2994,9 @@ import { Avatar, ListPane, ListPaneItem } from '@nxgt/material/components';
2992
2994
  `trailing`, `selected`, `focused` (a ring while its `group/listbox` parent has
2993
2995
  visible focus), `disabled`, `density` (`comfortable` or `compact`, one line),
2994
2996
  `appearance` (`plain` or `card`) and `lifted` (raised, as under a drag). It is
2995
- purely visual: give it the role and the events of the list it sits in. The
2997
+ purely visual: give it the role and the events of the list it sits in. A
2998
+ `title` or `description` cut by the ellipsis keeps its full text in a `title`
2999
+ attribute (when it is a string). The
2996
3000
  badge steps aside when the pane is narrower than 20rem.
2997
3001
 
2998
3002
  The height reaches the body as a CSS custom property set from script, never
@@ -245,6 +245,7 @@ export function PlanForm({ onSave }: { onSave: (values: Values) => void }) {
245
245
  | `reorderable` | `boolean` | `false` | move up / move down buttons on each row |
246
246
  | `max` | `number` | — | at this many rows, Add is disabled and says why |
247
247
  | `min` | `number` | — | at this many rows, Remove is disabled |
248
+ | `removeAtMin` | `'disable' \| 'hide'` | `'disable'` | at `min` rows, `'hide'` does not render Remove instead of disabling it |
248
249
  | `removable` | `(row, index) => boolean` | every row | a row it refuses keeps its Remove button `aria-disabled` (focusable; activating it does nothing). `row` is the `useFieldArray` field (`RepeatableGroupFormField`) or your item |
249
250
  | `removeDisabledReason` | `(row, index) => string \| undefined` | — | why a row cannot be removed: its Remove button's tooltip (on hover, focus, tap and click) and `aria-describedby` description |
250
251
  | `emptyTitle`, `emptyDescription` | `ReactNode` | — | an `EmptyState` when there is no row |
@@ -1347,6 +1348,11 @@ import { InlineEdit } from '@nxgt/material/components';
1347
1348
  | `onSave` | `(value: string) => void` | — | on save (Enter or the check button), not on each key |
1348
1349
  | `placeholder` | `string` | "Click to edit" | shown while empty |
1349
1350
  | `disabled` | `boolean` | `false` | read-only text |
1351
+ | `variant` | Typography step (`headline-lg`, …) | body | the type step of the saved text and of the input, so a title reads as a title |
1352
+ | `headingLevel` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | none | renders the text (and the input while editing) in an `<h1>`…`<h6>`, so the field can be a document's title; the button and input keep their name |
1353
+ | `defaultEditing` | `boolean` | `false` | starts with the input open, and focuses it on mount (a new, untitled document) |
1354
+ | `aria-label` | `string` | — | the input's name when there is no `label`; with `headingLevel`, set `label` or `aria-label` (the heading then reads the value, then the label) |
1355
+ | `editing` / `onEditingChange` | `boolean` / `(editing: boolean) => void` | — | controls whether the input is open (`disabled` keeps it closed) |
1350
1356
 
1351
1357
  Enter, Escape, Save and Cancel return focus to the field, while a click
1352
1358
  elsewhere saves and leaves focus where it went.
@@ -624,6 +624,7 @@ with a `+N` tile).
624
624
  | `selectable` | `boolean` | — | checkboxes on tiles; `onSelectionChange(ids)` |
625
625
  | `open` / `defaultOpen` / `onOpenChange` | `boolean` | — | control the lightbox |
626
626
  | `onDelete` | `(item) => void` | — | adds a delete button on each tile |
627
+ | `aria-label` / `aria-labelledby` | `string` | `'Gallery'` label | names the gallery (the root has `role="application"`); a given `aria-labelledby` replaces the default label. `MiniGallery` and `LightboxGallery` take both. Other `div` props (`id`, `data-*`, `onKeyDown`) reach the root element |
627
628
 
628
629
  **States.** Tiles use the `md` corner and round to `lg-plus` on hover (an
629
630
  effects easing, so the radius never overshoots) with a spring press scale. A
@@ -536,8 +536,9 @@ the page. It takes a `div`'s props.
536
536
  `Button`; a second filled `Button` among the children warns once in
537
537
  development, make the others `outlined` or `tonal`.
538
538
  - **Surface.** Below `md` the bar paints `--background` (it is stuck over the content); from `md`, and with `sticky={false}`, it paints nothing and takes its parent's surface, such as a `Card`.
539
+ - **Edges.** The bar adds no horizontal padding: its leading and trailing edges are those of the column it sits in, sticky or inline, so its buttons line up with the cards and fields above. Pad the column, not the bar.
539
540
  - **Safe areas.** Under `viewport-fit=cover` the bar pads clear of
540
- `env(safe-area-inset-left | right | bottom)`, as classes.
541
+ `env(safe-area-inset-left | right | bottom)` (the sides only below `md`), as classes.
541
542
  - **Keyboard.** From the visual viewport (`visualViewport`), the bar rises by
542
543
  the height the on-screen keyboard covers (`--nxgt-keyboard-inset` on the bar,
543
544
  `data-keyboard="open"` while it is open) and drops the bottom safe-area
@@ -1006,6 +1007,7 @@ const [order, setOrder] = useState(savedLayout); // string[] of tile ids
1006
1007
  | --- | --- | --- | --- |
1007
1008
  | `id` | `string` | required | The tile's name in `order`; unique in the grid |
1008
1009
  | `title` | `ReactNode` | none | An `h3` at the top, the tile's accessible name and its handle's name |
1010
+ | `headingLevel` | `2 \| 3 \| 4 \| 5 \| 6` | `3` | The level of the title (`<h2>`…`<h6>`), to follow the heading above the grid |
1009
1011
  | `span` / `rowSpan` | as `ResponsiveGrid.Item` | 1 | Columns and rows covered, held to the grid's columns at each width |
1010
1012
  | `plain` | `boolean` | `false` | No card of its own (border, background, padding), for a child that is a card already |
1011
1013
  | `label` | `string` | the title, else the id | The name used by the handle and the announcements when `title` is not text |
@@ -157,8 +157,12 @@ The look lives in the stylesheet (`nxgt-dialog`, `nxgt-scrim`), under the
157
157
  utilities: a class you pass wins. The flat look of 2.x before it is
158
158
  `className="rounded-lg border bg-background shadow-lg"` on `DialogContent`
159
159
  (and `className="bg-black/50"` on a `DialogOverlay` of your own).
160
- `DialogFooter` stacks its actions full width on a phone, the main one at the
161
- bottom. `stackOnPhone={false}` keeps one row instead, the first
160
+ `DialogFooter` stacks its actions full width on a phone. **One order for
161
+ `DialogFooter`, `SheetFooter` and `AlertDialogFooter`:** write the actions in
162
+ reading order, the secondary first and the main one last (`Cancel`, `Save`).
163
+ On a wide screen the dialog puts them in a row with the main one on the right;
164
+ on a phone the stack puts the main one on top and the secondary below it, in a
165
+ dialog and in a sheet alike. `stackOnPhone={false}` keeps one row instead, the first
162
166
  action on the left and the last on the right, for two short actions such as
163
167
  Cancel and Confirm. With `prefers-reduced-motion`, nothing moves: it appears and goes.
164
168
 
@@ -271,7 +275,9 @@ Parts: `Sheet`, `SheetTrigger`, `SheetContent`, `SheetHeader`, `SheetTitle`,
271
275
 
272
276
  `SheetContent` scrolls inside itself, and a top or bottom sheet is capped at
273
277
  `100dvh - 3.5rem`. Override the cap with `max-h-*` through `className`.
274
- `SheetFooter` makes its buttons full width. The former look is
278
+ `SheetFooter` makes its buttons full width, stacked in the same order as
279
+ `DialogFooter` on a phone: write the secondary first and the main action last,
280
+ the main one is on top. The former look is
275
281
  `className="rounded-none border-l bg-background shadow-lg"` (the border on the
276
282
  side facing the page), and `className="w-3/4"` for the former width.
277
283
 
@@ -339,7 +345,7 @@ A bottom drawer spans a phone's width and is centred, at most 672 px wide,
339
345
  from 640 px up; `className="mx-auto max-w-md"` on `DrawerContent` centres a
340
346
  narrower one, `sm:max-w-none` spans the viewport again, and
341
347
  `className="rounded-t-lg bg-background"` is the former look. `DrawerFooter`
342
- makes its buttons full width. Prefer it to `Sheet` on touch screens.
348
+ makes its buttons full width and keeps source order (first written on top). Prefer it to `Sheet` on touch screens.
343
349
  `DrawerContent` moves focus in on open and hands it back on close as
344
350
  `DialogContent` does (`Drawer` turns Vaul's `autoFocus` on; `autoFocus={false}`
345
351
  keeps focus where it was): back to the
@@ -641,7 +647,7 @@ in a desktop application.
641
647
 
642
648
  ![Command](../previews/overlays-menus-command.webp)
643
649
 
644
- Command palette with filtering, groups and shortcuts. `CommandDialog` shows it in a modal, with a visible close button (`showCloseButton`, on by default; `false` removes it) at the end of the input row: named "Close" (`material.buttons.close`) and 44px on a coarse pointer, since a phone has no Escape key.
650
+ Command palette with filtering, groups and shortcuts. `CommandDialog` shows it in a modal, with a visible close button (`showCloseButton`, on by default; `false` removes it) at the end of the input row: named "Close" (`material.buttons.close`) and 44px on a coarse pointer, since a phone has no Escape key. For results the server already filtered (`commandProps={{ shouldFilter: false }}`), with debounced suggestions grouped by type, see the `Guides/Recipes/Search dialog and results page` story.
645
651
 
646
652
  ```tsx
647
653
  import {
@@ -252,6 +252,8 @@ cannot be dragged to another tab; use Move to or Ctrl/Cmd + arrows.
252
252
 
253
253
  Month / week / day / agenda calendar with drag, resize and slot creation, a
254
254
  now line, an optional mini-month side panel and a phone layout of its own.
255
+ With `mode="template"` it edits a recurring week instead (weekday columns, no
256
+ dates), see [Template week](#eventcalendar-template-week).
255
257
 
256
258
  It reads its **own width**, not the window's, so it adapts inside a narrow
257
259
  column too. From 1024px `sidebar` shows a [MiniMonth](date-and-time.md#minimonth)
@@ -307,6 +309,7 @@ function Schedule() {
307
309
  | `scrollToHour` | `number` | `7` | the hour the week and day views open on when today is not shown |
308
310
  | `resources` | `EventCalendarResource[]` | — | the day view draws one column per resource, see [Resources](#eventcalendar-resources) |
309
311
  | `timeZone` | `string` | the machine's | an IANA zone the grid, today and the times follow, see [Time zone](#eventcalendar-time-zone) |
312
+ | `mode` | `'dated' \| 'template'` | `'dated'` | `'template'` is a recurring week with weekday columns and no dates, see [Template week](#eventcalendar-template-week) |
310
313
 
311
314
  **Short events.** Events that do not overlap in time share a column and keep the
312
315
  full width. An event is drawn at least 24px tall only where there is free space
@@ -372,6 +375,87 @@ const events: CalendarEvent[] = [
372
375
  date (`Room A, Thursday, March 12th, 2026`), and an event's name includes
373
376
  its resource (`Visit 1, Room A`).
374
377
 
378
+ ### EventCalendar template week
379
+
380
+ `mode="template"` edits a **recurring week** instead of a dated one: a weekly
381
+ timetable, an opening-hours grid. The columns are named by weekday only (a
382
+ letter on a phone, the full name from 1024px), and there is no date
383
+ navigation, no Today button, no today marker and no now line. Events,
384
+ slots and changes are keyed by **ISO weekday** (1 Monday to 7 Sunday) and
385
+ `HH:mm` times, never by a date. Create (click or drag across free hours),
386
+ move, resize, the keyboard moves and the preview popover keep working.
387
+ The month, day and agenda views do not apply: `view` is `'week'` (the
388
+ default) or `'workweek'` (Monday to Friday), and the week always starts on
389
+ Monday.
390
+
391
+ ```tsx
392
+ import { useState } from 'react';
393
+ import { EventCalendar, type EventCalendarTemplateEvent } from '@nxgt/material/components';
394
+
395
+ function Timetable() {
396
+ const [events, setEvents] = useState<EventCalendarTemplateEvent[]>([
397
+ { id: 'm', title: 'Maths', weekday: 1, start: '08:00', end: '09:00' },
398
+ { id: 'p', title: 'Physics', weekday: 1, start: '09:00', end: '10:30', lane: 'A' },
399
+ { id: 'c', title: 'Chemistry', weekday: 1, start: '09:00', end: '10:30', lane: 'B' },
400
+ ]);
401
+ return (
402
+ <EventCalendar
403
+ mode="template"
404
+ events={events}
405
+ onEventsChange={setEvents}
406
+ lanes={[
407
+ { id: 'A', label: 'Week A' },
408
+ { id: 'B', label: 'Week B' },
409
+ ]}
410
+ onSlotClick={({ weekday, start, end, lane }) =>
411
+ setEvents((current) => [
412
+ ...current,
413
+ { id: crypto.randomUUID(), title: 'New lesson', weekday, start, end, lane },
414
+ ])
415
+ }
416
+ />
417
+ );
418
+ }
419
+ ```
420
+
421
+ | Prop | Type | Default | Effect |
422
+ | --- | --- | --- | --- |
423
+ | `mode` | `'template'` | `'dated'` | the recurring week; `'dated'` (the default) is the calendar above |
424
+ | `events` / `defaultEvents` | `EventCalendarTemplateEvent[]` | — | `{ id, title, weekday, start, end, lane?, color?, description?, disabled? }`; `end` may be `'24:00'` |
425
+ | `onEventsChange` | `(events: EventCalendarTemplateEvent[]) => void` | — | the whole list after a move, a resize, an edit or a delete |
426
+ | `onChange` | `(change: EventCalendarTemplateChange) => void` | — | `{ events, event, previous }` after a move, a resize or an edit |
427
+ | `onEventClick` | `(event: EventCalendarTemplateEvent) => void` | — | an event was clicked |
428
+ | `onSlotClick` | `(slot: EventCalendarTemplateSlot) => void` | — | `{ weekday, start, end, lane? }` of the free slot clicked or dragged over; `lane` is the one picked in the switch |
429
+ | `view` / `defaultView` / `onViewChange` | `'week' \| 'workweek'` | `'week'` | the visible view |
430
+ | `renderEvent` | `(event: EventCalendarTemplateEvent) => ReactNode` | — | replaces how an event is drawn |
431
+ | `className` | `string` | — | on the calendar's root |
432
+ | `lanes` | `EventCalendarLane[]` | — | `{ id, label }` of the alternating weeks (`A`, `B`); adds a switch to the toolbar |
433
+ | `lane` / `defaultLane` / `onLaneChange` | `string \| null` | `null` (all) | the lane shown; `null` shows them all |
434
+
435
+ **Lanes.** An event with a `lane` belongs to that alternating week only; one
436
+ with none belongs to every week. Picking a lane in the switch hides the other
437
+ lanes' events, so one week is edited at a time; with **All weeks** every event shows,
438
+ and the ones with a lane carry its label (`A · Physics`), side by side where they
439
+ overlap. A slot made while a lane is picked reports it in `slot.lane`.
440
+ Moving and resizing keep an event's lane.
441
+
442
+ The shared options (`startHour`, `endHour`, `hourHeight`,
443
+ `slotDurationMinutes`, `density`, `eventVariant`, `scrollToHour`, `toolbar`,
444
+ `searchable`, `disabled`) work as in the dated calendar; `renderEvent` and
445
+ `className` are in the table above. `date`, `resources`, `timeZone`,
446
+ `sidebar`, `phoneView`, `views` and `weekStartsOn` do not apply. All-day
447
+ events do not exist in a template week.
448
+
449
+ Ctrl+Arrow moves the focused event within the visible days (Friday wraps to
450
+ Monday in a `workweek`) and stops at 00:00 and 24:00. `ComponentProps<typeof
451
+ EventCalendar>` is the union of the dated and template props: type a wrapper
452
+ with `EventCalendarProps` or `EventCalendarTemplateProps`.
453
+
454
+ The types are exported from `@nxgt/material/components`: `EventCalendarTemplateProps`
455
+ (the props of this mode), `EventCalendarTemplateEvent`, `EventCalendarTemplateSlot`,
456
+ `EventCalendarTemplateChange`, `EventCalendarLane` and `EventCalendarWeekday`
457
+ (`1 | 2 | … | 7`, the ISO weekday).
458
+
375
459
  ### EventCalendar time zone
376
460
 
377
461
  `timeZone` (an IANA name such as `Africa/Lagos`) makes the calendar read that
@@ -409,7 +493,7 @@ zone's wall clock. Without it, nothing changes: the machine's clock.
409
493
  <EventCalendar view="day" date="2026-03-12" timeZone="Asia/Tokyo" events={events} />
410
494
  ```
411
495
 
412
- **Storybook:** `Rich widgets/EventCalendar`, `Rich widgets/EventCalendar/Layouts`, `Rich widgets/EventCalendar/Resources`, `Rich widgets/EventCalendar/Resources on a phone`, `Rich widgets/EventCalendar/Resources a11y`, `Rich widgets/EventCalendar/Time zone`, `Rich widgets/EventCalendar/Back to back`, `Rich widgets/EventCalendar/Resource tone`, `Rich widgets/EventCalendar/Hydration` · **Vue:** `EventCalendar` (the new props and layouts, `resources` and `timeZone`: React only for now, #892 and #1780)
496
+ **Storybook:** `Rich widgets/EventCalendar`, `Rich widgets/EventCalendar/Layouts`, `Rich widgets/EventCalendar/Resources`, `Rich widgets/EventCalendar/Resources on a phone`, `Rich widgets/EventCalendar/Resources a11y`, `Rich widgets/EventCalendar/Time zone`, `Rich widgets/EventCalendar/Back to back`, `Rich widgets/EventCalendar/Resource tone`, `Rich widgets/EventCalendar/Hydration`, `Rich widgets/EventCalendar/Template week` · **Vue:** `EventCalendar` (the new props and layouts, `resources`, `timeZone` and `mode="template"`: React only for now, #892, #1780 and #2458)
413
497
 
414
498
  ---
415
499