@nxgt/material 3.82.0 → 3.84.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 (107) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +4 -3
  3. package/dist/{alert-dialog-DXLJ4AZP.js → alert-dialog-1t6ULy5i.js} +1 -1
  4. package/dist/{async-slice-BfVokA46.js → async-slice-xyA9Uzct.js} +1 -1
  5. package/dist/{button-CSKUtMI3.js → button-DhiNGrdT.js} +1 -1
  6. package/dist/{calculator-Cv00UQgR.js → calculator-BcgxxScL.js} +10 -10
  7. package/dist/calculator-field.js +6 -6
  8. package/dist/{calculator-segment-CwnC2cV_.js → calculator-segment-C4WQAHcN.js} +1 -1
  9. package/dist/calculator.js +1 -1
  10. package/dist/charts.js +3 -3
  11. package/dist/{chip-BGn0V6Pt.js → chip-m0_usuzd.js} +2 -2
  12. package/dist/{code-editor-B25-mWhb.js → code-editor-BgKFrcld.js} +4 -4
  13. package/dist/code-editor.js +4 -4
  14. package/dist/{code-language-items-oRjtO3qV.js → code-language-items-BWw17W9s.js} +2 -2
  15. package/dist/components/theme-tokens.css +3 -3
  16. package/dist/components/ui/collections/server-table/server-table-phone-row.css +12 -0
  17. package/dist/{components-BmZk-git.js → components-JPnObL9i.js} +8914 -8812
  18. package/dist/components.js +37 -37
  19. package/dist/{conversion-variant-wZd-G6xR.js → conversion-variant-DultZZQm.js} +3 -3
  20. package/dist/{currency-field-DizBTAat.js → currency-field-CPbaM7ay.js} +4 -4
  21. package/dist/{dialog-B-3s-oXt.js → dialog-DmlG9opW.js} +3 -3
  22. package/dist/dnd.js +1 -1
  23. package/dist/{dropdown-menu-C6rcYGB9.js → dropdown-menu-CDKev8dk.js} +1 -1
  24. package/dist/editor.js +7 -7
  25. package/dist/{empty-state-CSkghnD9.js → empty-state-C56000TB.js} +1 -1
  26. package/dist/{financial-variant-igpGRbGw.js → financial-variant-BYjQ690K.js} +5 -5
  27. package/dist/{hooks-k8_f3B0k.js → hooks-BTwLAy_A.js} +119 -83
  28. package/dist/hooks.js +3 -3
  29. package/dist/{hover-card-DuaVVKMb.js → hover-card-CDtW6utN.js} +1 -1
  30. package/dist/i18n.js +3 -3
  31. package/dist/{icon-badge-3ThP6EOl.js → icon-badge-ec7MmUGA.js} +1 -1
  32. package/dist/{icon-button-4PKlaI1g.js → icon-button-CG4aO8Gh.js} +1 -1
  33. package/dist/lib/components/ui/display/index.d.ts +1 -0
  34. package/dist/lib/components/ui/display/totals-list/index.d.ts +2 -0
  35. package/dist/lib/components/ui/display/totals-list/totals-list.d.ts +43 -0
  36. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  37. package/dist/lib/components/ui/media/galleries/types.d.ts +8 -0
  38. package/dist/lib/components/ui/media/galleries/use-gallery.d.ts +1 -0
  39. package/dist/lib/components/ui/widgets/event-calendar/event-calendar-context.d.ts +6 -1
  40. package/dist/lib/components/ui/widgets/event-calendar/event-calendar-shell.d.ts +1 -1
  41. package/dist/lib/components/ui/widgets/event-calendar/index.d.ts +1 -1
  42. package/dist/lib/components/ui/widgets/event-calendar/partials/event-preview-title.d.ts +6 -2
  43. package/dist/lib/components/ui/widgets/event-calendar/partials/event-preview.d.ts +3 -1
  44. package/dist/lib/components/ui/widgets/event-calendar/template.types.d.ts +5 -1
  45. package/dist/lib/components/ui/widgets/event-calendar/types.d.ts +20 -0
  46. package/dist/lib/components/ui/widgets/event-calendar/use-template-calendar.d.ts +3 -1
  47. package/dist/lib/hooks/index.d.ts +1 -0
  48. package/dist/lib/hooks/poll.d.ts +26 -0
  49. package/dist/lib/hooks/use-poll.d.ts +30 -0
  50. package/dist/lib/i18n/resources/en/en.d.ts +1 -0
  51. package/dist/lib/i18n/resources/en/material.json.d.ts +1 -0
  52. package/dist/lib/i18n/resources/fr/fr.d.ts +1 -0
  53. package/dist/lib/i18n/resources/fr/material.json.d.ts +1 -0
  54. package/dist/lib/i18n/resources/resources.d.ts +2 -0
  55. package/dist/lib/i18n/translator.d.ts +1 -1
  56. package/dist/lib.js +1 -1
  57. package/dist/main.js +53 -53
  58. package/dist/map.js +7 -7
  59. package/dist/material.css +1 -1
  60. package/dist/pdf-reader.js +1 -1
  61. package/dist/{percent-field-QwU1l2Q4.js → percent-field-8_pILs15.js} +1 -1
  62. package/dist/{picker-surface-CiTycWgV.js → picker-surface-pyGv-Qh9.js} +4 -4
  63. package/dist/{popover-YmbzbKQG.js → popover-3EF4q2CT.js} +1 -1
  64. package/dist/{pos-types-BFrapNmx.js → pos-types-Dv2UxKSN.js} +1 -1
  65. package/dist/{pos-variant-CctfqieO.js → pos-variant-DJvtN9N8.js} +4 -4
  66. package/dist/print.js +1 -1
  67. package/dist/{programmer-variant-Dd0YOixn.js → programmer-variant-Bmcq2KzC.js} +2 -2
  68. package/dist/{public-CshR_aW7.js → public-BNebh4z0.js} +9 -9
  69. package/dist/{rich-code-block-BuJLDoBu.js → rich-code-block-DmX7d58S.js} +5 -5
  70. package/dist/{scientific-variant-CjErQxuZ.js → scientific-variant-DijUrpP8.js} +2 -2
  71. package/dist/{sortable-provider-DU2nvpFc.js → sortable-provider-Cnmt_gWu.js} +2 -2
  72. package/dist/{spinner-DH1mm8hH.js → spinner-C6LP2Rw8.js} +1 -1
  73. package/dist/{tabs-Um0SFyf1.js → tabs-Bj1zOe7-.js} +1 -1
  74. package/dist/theme-picker.js +2 -2
  75. package/dist/{theme-toggle-C-Ih4uQU.js → theme-toggle-Cxku5iBP.js} +1 -1
  76. package/dist/{toolbar-items-BoEY5bkt.js → toolbar-items-CEIuSxU_.js} +4 -4
  77. package/dist/{translate-material-D4rn5UaQ.js → translate-material-DI3nmQ6I.js} +1 -1
  78. package/dist/{translator-CcEgQ-H7.js → translator-BstxG_hN.js} +177 -163
  79. package/dist/{use-chat-composer-BfFcmDua.js → use-chat-composer-BqLkk_iH.js} +71 -66
  80. package/dist/{use-heading-spy-gDNtK1sU.js → use-heading-spy-nFyb9_KE.js} +4 -4
  81. package/dist/{use-material-translation-DYrfV_JJ.js → use-material-translation-1kCHtB0p.js} +1 -1
  82. package/dist/{use-return-focus-C_Qz_yTk.js → use-return-focus-Hv8rn55-.js} +1 -1
  83. package/docs/components/data-display.md +38 -8
  84. package/docs/components/media-and-files.md +11 -0
  85. package/docs/components/rich-widgets.md +22 -1
  86. package/docs/guides/helpers.md +40 -0
  87. package/docs/guides/layouts.md +5 -2
  88. package/docs/guides/theming.md +2 -2
  89. package/docs/roadmap.md +9 -4
  90. package/docs/troubleshooting.md +2 -2
  91. package/lib/components/theme-tokens.css +3 -3
  92. package/lib/components/ui/collections/server-table/server-table-phone-row.css +12 -0
  93. package/lib/themes/ambre.css +2 -2
  94. package/lib/themes/ardoise.css +2 -2
  95. package/lib/themes/bleumono.css +2 -2
  96. package/lib/themes/bluemoon.css +2 -2
  97. package/lib/themes/corail.css +2 -2
  98. package/lib/themes/foret.css +2 -2
  99. package/lib/themes/lavande.css +2 -2
  100. package/lib/themes/menthe.css +2 -2
  101. package/lib/themes/ocean.css +2 -2
  102. package/lib/themes/pink.css +2 -2
  103. package/lib/themes/prune.css +1 -1
  104. package/lib/themes/sable.css +2 -2
  105. package/lib/themes/slate.css +1 -1
  106. package/lib/themes/teal.css +2 -2
  107. package/package.json +4 -4
@@ -4,28 +4,28 @@ import { t as n } from "./utils-BLfntITJ.js";
4
4
  import { n as r } from "./use-shape-morph-BKA_R5P5.js";
5
5
  import { n as i, o as a, t as o } from "./dist-DVRQkaTK.js";
6
6
  import { i as s, n as c, r as l, t as u } from "./dist-DWZXlCZR.js";
7
- import { _ as d, a as f, b as p, g as m, h, i as g, m as _, n as v, o as y, r as b, s as x, t as S, v as C, y as w } from "./spinner-DH1mm8hH.js";
7
+ import { _ as d, a as f, b as p, g as m, h, i as g, m as _, n as v, o as y, r as b, s as x, t as S, v as C, y as w } from "./spinner-C6LP2Rw8.js";
8
8
  import { a as T, i as E, n as D, r as O, t as k } from "./dist-DHdUUM1w.js";
9
9
  import { a as A, i as ee } from "./dist-DOK3zo5C.js";
10
10
  import { i as j, n as M } from "./in-dialog-D61Yeaol.js";
11
- import { c as N, l as P, n as te, r as F, t as ne } from "./alert-dialog-DXLJ4AZP.js";
11
+ import { c as N, l as P, n as te, r as F, t as ne } from "./alert-dialog-1t6ULy5i.js";
12
12
  import { A as re, C as ie, D as ae, E as oe, O as se, S as I, T as ce, _ as le, a as ue, b as de, d as fe, f as pe, g as me, h as he, i as ge, k as _e, l as ve, m as ye, n as be, o as xe, p as Se, r as Ce, s as we, t as Te, u as Ee, v as De, w as Oe, x as ke, y as Ae } from "./floating-ui.dom-KkZSW8_E.js";
13
- import { D as je, E as Me, N as Ne, O as Pe, c as Fe, d as Ie, f as Le, i as Re, k as ze, m as Be, n as Ve, o as He, p as Ue, t as We, u as Ge } from "./dropdown-menu-C6rcYGB9.js";
13
+ import { D as je, E as Me, N as Ne, O as Pe, c as Fe, d as Ie, f as Le, i as Re, k as ze, m as Be, n as Ve, o as He, p as Ue, t as We, u as Ge } from "./dropdown-menu-CDKev8dk.js";
14
14
  import { a as Ke, i as qe, n as Je } from "./field-blur-context-BLGgf_3z.js";
15
- import { i as Ye, n as Xe, r as Ze, t as Qe } from "./popover-YmbzbKQG.js";
15
+ import { i as Ye, n as Xe, r as Ze, t as Qe } from "./popover-3EF4q2CT.js";
16
16
  import { a as $e, i as et, n as tt, o as nt, t as rt } from "./bubble-input-CQwVSoU3.js";
17
- import { n as it, r as at, t as ot, u as st } from "./tabs-Um0SFyf1.js";
18
- import { a as ct, n as lt, o as ut, s as dt, v as ft, y as pt } from "./toolbar-items-BoEY5bkt.js";
17
+ import { n as it, r as at, t as ot, u as st } from "./tabs-Bj1zOe7-.js";
18
+ import { a as ct, n as lt, o as ut, s as dt, v as ft, y as pt } from "./toolbar-items-CEIuSxU_.js";
19
19
  import { t as mt } from "./dist-DkQbkF9T.js";
20
20
  import { o as ht, s as gt, t as _t } from "./use-click-lock-Bwg_caHO.js";
21
21
  import { a as vt, r as yt } from "./icon-sprites-CaYXzOL4.js";
22
22
  import { t as bt } from "./warn-once-C_e47JM8.js";
23
- import { r as xt, t as St } from "./button-CSKUtMI3.js";
24
- import { a as Ct } from "./translator-CcEgQ-H7.js";
23
+ import { r as xt, t as St } from "./button-DhiNGrdT.js";
24
+ import { a as Ct } from "./translator-BstxG_hN.js";
25
25
  import { h as wt } from "./get-CTzJAwfc.js";
26
26
  import { t as Tt } from "./isEmpty-F_g3jLf8.js";
27
- import { t as L } from "./use-material-translation-DYrfV_JJ.js";
28
- import { t as R } from "./icon-button-4PKlaI1g.js";
27
+ import { t as L } from "./use-material-translation-1kCHtB0p.js";
28
+ import { t as R } from "./icon-button-CG4aO8Gh.js";
29
29
  import { t as Et } from "./use-ssr-media-query-C6M5m8sf.js";
30
30
  import { n as Dt, t as Ot } from "./typography-CYEquVeb.js";
31
31
  import { n as kt, t as At } from "./util-4heV2LPI.js";
@@ -36,14 +36,14 @@ import { a as Bt } from "./toggle-group-CVMxEpWi.js";
36
36
  import { n as Vt } from "./use-slice-reducer-BGkeGtML.js";
37
37
  import { t as Ht } from "./browser-only-DQ94lWF8.js";
38
38
  import { t as Ut } from "./compose-refs-G4J4kaUA.js";
39
- import { n as Wt, r as Gt, t as Kt } from "./chip-BGn0V6Pt.js";
39
+ import { n as Wt, r as Gt, t as Kt } from "./chip-m0_usuzd.js";
40
40
  import { a as qt, c as Jt, o as Yt, r as Xt, s as Zt, t as Qt } from "./input-CC2joTjB.js";
41
41
  import { t as $t } from "./text-field-BAdY-kpO.js";
42
42
  import { r as en } from "./use-csp-nonce-ZKs9k-ov.js";
43
- import { t as tn } from "./empty-state-CSkghnD9.js";
43
+ import { t as tn } from "./empty-state-C56000TB.js";
44
44
  import { t as nn } from "./skeleton-DSdtr2qc.js";
45
- import { l as rn, n as an, o as on, r as sn, s as cn, t as ln, u as un } from "./dialog-B-3s-oXt.js";
46
- import { t as dn } from "./icon-badge-3ThP6EOl.js";
45
+ import { l as rn, n as an, o as on, r as sn, s as cn, t as ln, u as un } from "./dialog-DmlG9opW.js";
46
+ import { t as dn } from "./icon-badge-ec7MmUGA.js";
47
47
  import { H as fn, V as pn, X as mn, bt as hn, it as gn, lt as _n, n as vn, t as yn, xt as bn, yt as xn } from "./dist-moutbfbd.js";
48
48
  import { t as Sn } from "./markdown-Dw_uH7j7.js";
49
49
  import { r as Cn, t as wn } from "./create-store-Dqp-PLLn.js";
@@ -11229,7 +11229,7 @@ var aS = (e = {}) => Cn({
11229
11229
  let [{ default: t }, { lowlight: n }, { richCodeBlock: r }] = await Promise.all([
11230
11230
  import("./dist-CRMFGjng.js"),
11231
11231
  import("./lowlight-OtcjU2Sa.js"),
11232
- import("./rich-code-block-BuJLDoBu.js")
11232
+ import("./rich-code-block-DmX7d58S.js")
11233
11233
  ]);
11234
11234
  return [r(t, {
11235
11235
  lowlight: n,
@@ -20005,18 +20005,22 @@ function RN({ className: e }) {
20005
20005
  //#endregion
20006
20006
  //#region lib/components/ui/media/galleries/partials/gallery-selection-bar.tsx
20007
20007
  function zN() {
20008
- let { state: e, selectable: t, clearSelection: n } = KC(), { t: r } = L();
20008
+ let { state: e, selectable: t, selectionActions: n, clearSelection: r } = KC(), { t: i } = L();
20009
20009
  return !t || e.selectedIds.length === 0 ? null : /* @__PURE__ */ K("div", {
20010
20010
  "data-slot": "gallery-selection-bar",
20011
20011
  className: "mb-2 flex flex-wrap items-center justify-between gap-2 rounded-(--nxgt-shape-corner-lg) border border-border bg-background px-3 py-2",
20012
20012
  children: [/* @__PURE__ */ G(Ot, {
20013
20013
  className: "text-sm",
20014
- children: r("material.gallery.selected", "{count} selected", { count: e.selectedIds.length })
20015
- }), /* @__PURE__ */ G(St, {
20016
- variant: "ghost",
20017
- color: "default",
20018
- onClick: n,
20019
- children: r("material.gallery.clear-selection", "Clear selection")
20014
+ children: i("material.gallery.selected", "{count} selected", { count: e.selectedIds.length })
20015
+ }), /* @__PURE__ */ K("div", {
20016
+ "data-slot": "gallery-selection-actions",
20017
+ className: "flex flex-wrap items-center gap-2",
20018
+ children: [typeof n == "function" ? n(e.selectedIds) : n, /* @__PURE__ */ G(St, {
20019
+ variant: "ghost",
20020
+ color: "default",
20021
+ onClick: r,
20022
+ children: i("material.gallery.clear-selection", "Clear selection")
20023
+ })]
20020
20024
  })]
20021
20025
  });
20022
20026
  }
@@ -20342,68 +20346,69 @@ function oP({ open: e, close: t, currentIndex: n, lightboxOpen: r }) {
20342
20346
  //#endregion
20343
20347
  //#region lib/components/ui/media/galleries/use-gallery.ts
20344
20348
  function sP(e) {
20345
- let { items: t, defaultIndex: n = 0, defaultOpen: r = !1, onDelete: i, trigger: a, selectable: o = !1 } = e, s = eP(e), [c, l] = Vt(UM, {
20349
+ let { items: t, defaultIndex: n = 0, defaultOpen: r = !1, onDelete: i, trigger: a, selectable: o = !1, selectionActions: s } = e, c = eP(e), [l, u] = Vt(UM, {
20346
20350
  currentIndex: n,
20347
- loop: s.loop ?? !1,
20351
+ loop: c.loop ?? !1,
20348
20352
  lightboxOpen: r
20349
20353
  });
20350
- nP(e, c, l);
20351
- let { next: u, prev: d, select: f, downloadCurrent: p } = XN(t, c, l), m = oP({
20352
- lightboxOpen: c.lightboxOpen,
20353
- open: l.openLightbox,
20354
- close: l.closeLightbox,
20355
- currentIndex: c.currentIndex
20356
- }), h = B((e, t) => {
20357
- s.lightbox ? m.openWith(e, t) : l.setIndex(e);
20354
+ nP(e, l, u);
20355
+ let { next: d, prev: f, select: p, downloadCurrent: m } = XN(t, l, u), h = oP({
20356
+ lightboxOpen: l.lightboxOpen,
20357
+ open: u.openLightbox,
20358
+ close: u.closeLightbox,
20359
+ currentIndex: l.currentIndex
20360
+ }), g = B((e, t) => {
20361
+ c.lightbox ? h.openWith(e, t) : u.setIndex(e);
20358
20362
  }, [
20359
- s.lightbox,
20360
- l,
20361
- m.openWith
20362
- ]), g = qN({
20363
+ c.lightbox,
20364
+ u,
20365
+ h.openWith
20366
+ ]), _ = qN({
20363
20367
  items: t,
20364
- state: c,
20365
- sliceActions: l,
20366
- resolved: s,
20367
- next: u,
20368
- prev: d,
20369
- close: m.closeWith,
20370
- downloadCurrent: p,
20371
- zoomable: t[c.currentIndex]?.kind !== "video"
20368
+ state: l,
20369
+ sliceActions: u,
20370
+ resolved: c,
20371
+ next: d,
20372
+ prev: f,
20373
+ close: h.closeWith,
20374
+ downloadCurrent: m,
20375
+ zoomable: t[l.currentIndex]?.kind !== "video"
20372
20376
  });
20373
20377
  return {
20374
20378
  items: t,
20375
- resolved: s,
20376
- state: c,
20377
- current: WM.selectCurrent(c, t),
20378
- canNext: WM.selectCanNext(c, t.length),
20379
- canPrev: WM.selectCanPrev(c, t.length),
20379
+ resolved: c,
20380
+ state: l,
20381
+ current: WM.selectCurrent(l, t),
20382
+ canNext: WM.selectCanNext(l, t.length),
20383
+ canPrev: WM.selectCanPrev(l, t.length),
20380
20384
  onDelete: i,
20381
20385
  trigger: a,
20382
- openAt: h,
20383
- select: f,
20384
- next: u,
20385
- prev: d,
20386
- closeLightbox: m.closeWith,
20387
- setZoom: l.setZoom,
20388
- setPan: l.setPan,
20389
- morph: m.morph,
20390
- toggleLoop: l.toggleLoop,
20391
- goToFirst: () => l.setIndex(0),
20392
- goToLast: () => l.setIndex(Math.max(0, t.length - 1)),
20393
- downloadCurrent: p,
20386
+ openAt: g,
20387
+ select: p,
20388
+ next: d,
20389
+ prev: f,
20390
+ closeLightbox: h.closeWith,
20391
+ setZoom: u.setZoom,
20392
+ setPan: u.setPan,
20393
+ morph: h.morph,
20394
+ toggleLoop: u.toggleLoop,
20395
+ goToFirst: () => u.setIndex(0),
20396
+ goToLast: () => u.setIndex(Math.max(0, t.length - 1)),
20397
+ downloadCurrent: m,
20394
20398
  selectable: o,
20395
- toggleSelected: l.toggleSelected,
20396
- clearSelection: l.clearSelection,
20397
- onKeyDown: g
20399
+ selectionActions: s,
20400
+ toggleSelected: u.toggleSelected,
20401
+ clearSelection: u.clearSelection,
20402
+ onKeyDown: _
20398
20403
  };
20399
20404
  }
20400
20405
  //#endregion
20401
20406
  //#region lib/components/ui/media/galleries/gallery.tsx
20402
20407
  function cP(e) {
20403
- let t = sP(e), { items: n, preset: r, layout: i, columns: a, aspect: o, loop: s, maxItems: c, lightbox: l, captions: u, shape: d, carousel: f, carouselAlign: p, carouselOrientation: m, defaultIndex: h, currentIndex: g, onIndexChange: _, open: v, defaultOpen: y, onOpenChange: b, onDelete: x, selectable: S, onSelectionChange: C, trigger: w, ...T } = e;
20408
+ let t = sP(e), { items: n, preset: r, layout: i, columns: a, aspect: o, loop: s, maxItems: c, lightbox: l, captions: u, shape: d, carousel: f, carouselAlign: p, carouselOrientation: m, defaultIndex: h, currentIndex: g, onIndexChange: _, open: v, defaultOpen: y, onOpenChange: b, onDelete: x, selectable: S, onSelectionChange: C, selectionActions: w, trigger: T, ...E } = e;
20404
20409
  return /* @__PURE__ */ G(GC, {
20405
20410
  value: t,
20406
- children: /* @__PURE__ */ G(lP, { ...T })
20411
+ children: /* @__PURE__ */ G(lP, { ...E })
20407
20412
  });
20408
20413
  }
20409
20414
  function lP({ className: e, onKeyDown: t, ...r }) {
@@ -1,8 +1,8 @@
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-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";
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-1t6ULy5i.js";
3
+ import { a as l } from "./button-DhiNGrdT.js";
4
+ import { i as u } from "./translate-material-DI3nmQ6I.js";
5
+ import { t as d } from "./use-material-translation-1kCHtB0p.js";
6
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";
@@ -1,4 +1,4 @@
1
- import { i as e, t } from "./translate-material-D4rn5UaQ.js";
1
+ import { i as e, t } from "./translate-material-DI3nmQ6I.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-DH1mm8hH.js";
3
+ import { C as o, S as s, x as c } from "./spinner-C6LP2Rw8.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";
@@ -18,6 +18,7 @@ from `@nxgt/material/components`.
18
18
  | <img src="../previews/data-display-periodcards.webp" width="96" alt=""> | [PeriodCards](#periodcards) | Consecutive date ranges (terms, sprints) as cards, with a current marker and an optional link per card |
19
19
  | <img src="../previews/data-display-chip.webp" width="96" alt=""> | [Chip](#chip) | Three looks and a dashed add pill, with slots, a selected state and a dismiss button |
20
20
  | <img src="../previews/data-display-statcard.webp" width="96" alt=""> | [StatCard](#statcard) | KPI tile with value, hint, icon and toned delta; `href` makes it one link |
21
+ | | [TotalsList](#totalslist) | Labelled figures in a `dl`, amounts right-aligned, a ruled grand-total row; the screen twin of `PrintTotals` |
21
22
  | <img src="../previews/data-display-metric-cards.webp" width="96" alt=""> | [Metric cards](#metric-cards) | Sixteen dashboard tiles |
22
23
  | <img src="../previews/data-display-card-presets.webp" width="96" alt=""> | [Card presets](#card-presets) | Twenty-seven ready-made cards |
23
24
  | <img src="../previews/data-display-statusindicator.webp" width="96" alt=""> | [StatusIndicator](#statusindicator) | Coloured status dot with a label |
@@ -194,7 +195,7 @@ no `style` attribute and a strict CSP is fine.
194
195
  - Seat changes are not animated beyond a colour fade, which stops under
195
196
  `prefers-reduced-motion`.
196
197
 
197
- **Storybook:** `Data display/SeatRing` · **Vue:** not yet (#2470) (#1931)
198
+ **Storybook:** `Data display/SeatRing` · **Vue:** not yet (#1931)
198
199
 
199
200
  ---
200
201
 
@@ -269,7 +270,7 @@ const [open, setOpen] = useState(false);
269
270
  the layout follows `dir` (RTL) and dark mode.
270
271
  - Keep `front` and `description` free of the secret: they are always in the page.
271
272
 
272
- **Storybook:** `Data display/SecretCard` · **Vue:** not yet (#2470) (#1938)
273
+ **Storybook:** `Data display/SecretCard` · **Vue:** not yet (#1938)
273
274
 
274
275
  ---
275
276
 
@@ -603,6 +604,35 @@ pass `disabled` with a `Spinner` in `leading`, or use a `Button`.
603
604
 
604
605
  ---
605
606
 
607
+ ## TotalsList
608
+
609
+ On-screen counterpart of `PrintTotals`: a `dl` of labelled figures, labels at the start, amounts right-aligned in tabular figures, one column at any width (a side rail, a phone). Rows have the shape of `PrintTotalRow` (`label`, `value`, `last`), so one array feeds the screen and its print.
610
+
611
+ ```tsx
612
+ import { TotalsList } from '@nxgt/material/components';
613
+
614
+ <TotalsList
615
+ label="Order totals"
616
+ rows={[
617
+ { label: 'Subtotal', value: '1,000.00' },
618
+ { label: 'Tax (19%)', value: '190.00' },
619
+ { label: 'Total', value: '1,190.00', last: true },
620
+ ]}
621
+ />
622
+ ```
623
+
624
+ | Prop | Type | Default | Effect |
625
+ | --- | --- | --- | --- |
626
+ | `rows` | `{ label, value, last?, strong?, tone? }[]` | required | `last` makes the grand total larger and bold with a rule above; `strong` emphasises a row with no rule; `tone` colours the amount (`error` for a due or negative amount, `success` for a credit; an `AppTones` tone works too) |
627
+ | `label` | `string` | — | accessible name: the block becomes a `group` named by it |
628
+ | `variant` | `'card' \| 'plain'` | `'card'` | `plain` has no border or padding, to sit inside a card you already have |
629
+
630
+ Format the amounts for the locale before passing them (`Intl.NumberFormat`); a long label wraps and the amount stays whole.
631
+
632
+ **Storybook:** `Data display/TotalsList`
633
+
634
+ ---
635
+
606
636
  ## StatCard
607
637
 
608
638
  ![StatCard](../previews/data-display-statcard.webp)
@@ -812,7 +842,7 @@ import { TubeCap } from '@nxgt/material/components';
812
842
  | `action` | `ReactNode` | none | trailing slot, such as an `IconButton` |
813
843
  | `size` | `'sm' \| 'default' \| 'lg'` | `'default'` | cap of 20, 28 or 40px |
814
844
 
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`.
845
+ `TUBE_CAP_KINDS` lists the kinds, and `TubeCapKind` is the exported type of `kind`. The look is CSS on `data-kind`, with no inline style (strict CSP). The cap is `aria-hidden`.
816
846
 
817
847
  **Storybook:** `Data display/TubeCap` · **Vue:** `TubeCap` (`@nxgt/material-vue/components`)
818
848
 
@@ -1367,7 +1397,7 @@ and the user's zone) and both sides agree:
1367
1397
  <ChangeHistory entries={entries} now={loaderData.now} timeZone={loaderData.timeZone} />
1368
1398
  ```
1369
1399
 
1370
- **Storybook:** `Data display/ChangeHistory` · **Vue:** not yet (#2470) (tracked as a `vue` issue)
1400
+ **Storybook:** `Data display/ChangeHistory` · **Vue:** not yet (tracked as a `vue` issue)
1371
1401
 
1372
1402
  ---
1373
1403
 
@@ -1404,7 +1434,7 @@ The offset follows the instant (`Europe/Paris` is `UTC+1` in winter and
1404
1434
  `UTC+2` in summer). `ChangeHistory` takes a `timeZone` and `RelativeTime` a `now`
1405
1435
  the same way; pass the app's zone from the server so every screen agrees.
1406
1436
 
1407
- **Storybook:** `Data display/ZonedTime` · **Vue:** not yet (#2470) (tracked as a `vue` issue)
1437
+ **Storybook:** `Data display/ZonedTime` · **Vue:** not yet (tracked as a `vue` issue)
1408
1438
 
1409
1439
  ---
1410
1440
 
@@ -1473,7 +1503,7 @@ own, so they add up to less.
1473
1503
  - Bar widths are SVG attributes, so server markup has no `style` attribute and
1474
1504
  a strict CSP is fine.
1475
1505
 
1476
- **Storybook:** `Data display/VoteTally` · **Vue:** not yet (#2470) (#1940)
1506
+ **Storybook:** `Data display/VoteTally` · **Vue:** not yet (#1940)
1477
1507
 
1478
1508
  ---
1479
1509
 
@@ -1806,7 +1836,7 @@ lock set by another code form disables its field and leaves Cancel usable.
1806
1836
  button by `getByRole('button', { name: 'Activate' })` and the lock by
1807
1837
  `getByRole('alert')`.
1808
1838
 
1809
- **Storybook:** `Data display/EnrolmentCard`, `…/Shared lock`, `Guides/Accessibility/Touch targets/Verification code` · **Vue:** not yet (#2470)
1839
+ **Storybook:** `Data display/EnrolmentCard`, `…/Shared lock`, `Guides/Accessibility/Touch targets/Verification code` · **Vue:** not yet
1810
1840
 
1811
1841
  ---
1812
1842
 
@@ -1949,4 +1979,4 @@ import { RecoveryCodesStatus, VerificationLockProvider } from '@nxgt/material/co
1949
1979
 
1950
1980
  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.
1951
1981
 
1952
- **Storybook:** `Data display/RecoveryCodesStatus`, `Data display/EnrolmentCard/Shared lock` · **Vue:** not yet (#2470)
1982
+ **Storybook:** `Data display/RecoveryCodesStatus`, `Data display/EnrolmentCard/Shared lock` · **Vue:** not yet
@@ -602,6 +602,14 @@ const items: GalleryItem[] = [
602
602
 
603
603
  <Gallery items={items} preset="grid" selectable onSelectionChange={console.log} />;
604
604
 
605
+ // Bulk actions on the selection: they appear in the "n selected" bar.
606
+ <Gallery
607
+ items={items}
608
+ preset="grid"
609
+ selectable
610
+ selectionActions={(ids) => <Button onClick={() => removeAll(ids)}>Delete</Button>}
611
+ />;
612
+
605
613
  <LightboxGallery items={items} trigger={<button type="button">Open gallery</button>} />;
606
614
 
607
615
  <MiniGallery items={items} maxItems={5} />;
@@ -622,6 +630,7 @@ with a `+N` tile).
622
630
  | `carouselAlign` / `carouselOrientation` | `'start' \| 'center'` / `'horizontal' \| 'vertical'` | `'start'` / `'horizontal'` | where the large tile of `hero` sits; the scroll axis |
623
631
  | `lightbox` | `boolean` | from `preset` | click opens the full-screen viewer (gestures, zoom, filmstrip, download, keyboard) |
624
632
  | `selectable` | `boolean` | — | checkboxes on tiles; `onSelectionChange(ids)` |
633
+ | `selectionActions` | `ReactNode \| (selectedIds: string[]) => ReactNode` | — | bulk actions (download, move, delete) at the end of the "n selected" bar of a `selectable` gallery; a render function receives the selected ids. Hidden while nothing is selected; wraps under the count on a phone |
625
634
  | `open` / `defaultOpen` / `onOpenChange` | `boolean` | — | control the lightbox |
626
635
  | `onDelete` | `(item) => void` | — | adds a delete button on each tile |
627
636
  | `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 |
@@ -1913,6 +1922,8 @@ Parts: `PrintDocument`, `PrintHeader`, `PrintSection`, `PrintInfoRow`,
1913
1922
  `definePrintColumns` and the helpers `resolvePrintTable`,
1914
1923
  `resolvePrintFields`, `toPrintValue` and `formatPrintValue`.
1915
1924
 
1925
+ For the same rows on screen, use [`TotalsList`](data-display.md#totalslist).
1926
+
1916
1927
  **To get the file**, pass the document to one function: `downloadPdf`
1917
1928
  saves it, `openPdf` opens it in a new tab (a download when a popup blocker
1918
1929
  stops the tab), `usePdfFile` keeps a preview current, and `pdfResponse`
@@ -8,7 +8,7 @@ the app owns the data.
8
8
  | --- | --- | --- |
9
9
  | <img src="../previews/rich-widgets-chat-overview.webp" width="96" alt=""> | [Chat kit](#chat-kit) | Inbox, thread, grouped bubbles with receipts and replies, composer with quick replies, floating widget |
10
10
  | <img src="../previews/rich-widgets-kanban.webp" width="96" alt=""> | [Kanban](#kanban) | Board with columns, swimlanes, WIP limits and drag-and-drop |
11
- | <img src="../previews/rich-widgets-eventcalendar.webp" width="96" alt=""> | [EventCalendar](#eventcalendar) | Month / week / day / agenda calendar, a day view split by resource, a named time zone, phone to desktop |
11
+ | <img src="../previews/rich-widgets-eventcalendar.webp" width="96" alt=""> | [EventCalendar](#eventcalendar) | Month / week / day / agenda calendar, a day view split by resource, a named time zone, an event preview you can route to your app, phone to desktop |
12
12
  | <img src="../previews/rich-widgets-native-charts-linechart.webp" width="96" alt=""> | [Charts](#charts) | Native SVG charts (line, area, bar, pie, donut, gauge, radial, funnel, heat map, radar, scatter, combo) with keyboard, summary and View data; the Nivo ones were removed in 3.0 |
13
13
  | <img src="../previews/rich-widgets-drawing.webp" width="96" alt=""> | [Drawing](#drawing) | Vector drawing from a scene of plain data or JSX primitives: theme colours, accessible, focusable nodes, strict-CSP safe |
14
14
  | <img src="../previews/rich-widgets-barcode.webp" width="96" alt=""> | [Barcode](#barcode) | Code 128 and Data Matrix as accessible SVG, themed and dark-aware, black on white in print, with `LabelSheet` for a printed sheet of labels |
@@ -289,11 +289,28 @@ function Schedule() {
289
289
  }
290
290
  ```
291
291
 
292
+ To edit and delete in your own dialog instead of the built-in inline fields:
293
+
294
+ ```tsx
295
+ <EventCalendar
296
+ events={events}
297
+ onEventsChange={setEvents}
298
+ previewFields={[]}
299
+ onEventEdit={(e) => openEditor(e.id)}
300
+ onEventDelete={(e) => confirmThenRemove(e.id)}
301
+ />
302
+ ```
303
+
292
304
  | Prop | Type | Default | Effect |
293
305
  | --- | --- | --- | --- |
294
306
  | `events` / `defaultEvents` | `CalendarEvent[]` | — | controlled / uncontrolled events (ISO `start` / `end`) |
295
307
  | `onChange` | `(change: EventCalendarChange) => void` | — | after a drag or resize, with the previous event |
296
308
  | `onSlotClick` | `(slot: EventCalendarSlot) => void` | — | click on an empty slot or day — create an event there |
309
+ | `onEventClick` | `(event: CalendarEvent) => void` | — | an event was clicked; it runs even with `onSlotClick` set (an event click never reports a slot) |
310
+ | `eventPreview` | `boolean` | `true` | `false` skips the preview popover, so `onEventClick` alone handles the click |
311
+ | `previewFields` | `EventCalendarPreviewField[]` | all three | the fields the preview edits in place (`'title' \| 'color' \| 'description'`); `[]` keeps the time range and the actions |
312
+ | `onEventEdit` | `(event: CalendarEvent) => void` | — | adds an Edit button to the preview; the app opens its own editor; hidden when the calendar or the event is `disabled` |
313
+ | `onEventDelete` | `(event: CalendarEvent) => void` | — | the preview's delete button calls this instead of the built-in confirmation and removal; the app confirms and deletes; hidden when the calendar or the event is `disabled`, so it never runs for those |
297
314
  | `view` / `defaultView` | `'month' \| 'week' \| 'workweek' \| 'day' \| 'agenda'` | `'month'` | the visible view |
298
315
  | `date` / `defaultDate` | `string` | today | the date the view is centred on |
299
316
  | `startHour` / `endHour` | `number` | `0` / `24` | hours shown in the time grids |
@@ -425,6 +442,10 @@ function Timetable() {
425
442
  | `onEventsChange` | `(events: EventCalendarTemplateEvent[]) => void` | — | the whole list after a move, a resize, an edit or a delete |
426
443
  | `onChange` | `(change: EventCalendarTemplateChange) => void` | — | `{ events, event, previous }` after a move, a resize or an edit |
427
444
  | `onEventClick` | `(event: EventCalendarTemplateEvent) => void` | — | an event was clicked |
445
+ | `onEventEdit` | `(event: EventCalendarTemplateEvent) => void` | — | adds an Edit button to the preview and receives the `EventCalendarTemplateEvent`; both buttons are hidden when the calendar or the event is `disabled` |
446
+ | `onEventDelete` | `(event: EventCalendarTemplateEvent) => void` | — | the Delete button calls this with the `EventCalendarTemplateEvent`, skipping the built-in confirmation and removal; both buttons are hidden when the calendar or the event is `disabled` |
447
+ | `eventPreview` | `boolean` | `true` | `false` skips the preview popover; see the dated table |
448
+ | `previewFields` | `EventCalendarPreviewField[]` | all three | the fields the preview edits (`'title' \| 'color' \| 'description'`); see the dated table |
428
449
  | `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
450
  | `view` / `defaultView` / `onViewChange` | `'week' \| 'workweek'` | `'week'` | the visible view |
430
451
  | `renderEvent` | `(event: EventCalendarTemplateEvent) => ReactNode` | — | replaces how an event is drawn |
@@ -142,6 +142,7 @@ Vue: the Redux helpers are Pinia store factories: `defineLocalStore`
142
142
  | `useSubmitOnce(busy?)` | One submit in flight: wraps a submit handler so a double click or a second Enter is dropped until the request ends (the handler's promise, or `busy` back to false). See [below](#usesubmitonce). |
143
143
  | `useDrafts<T>(storageKey, { serialize?, parse?, storage? })`, `clearDrafts(storageKey, storage?)` | Unsent values kept by key in `sessionStorage`: `{ get, set, drop, clear, keys, loaded }`. Read after the first render (no hydration mismatch), storage errors swallowed, `drop(key)` once saved; `clearDrafts` empties a store key without a hook (sign-out). See [below](#usedrafts). |
144
144
  | `useUnsavedGuard(dirty, { block, … })`, `useDiscardGuard(dirty)`, `useReactRouterBlock` | Stop losing a dirty form: a "Leave without saving?" dialog on in-app navigation and on closing a dialog form, plus the browser's own `beforeunload` prompt. See [below](#useunsavedguard). |
145
+ | `usePoll({ ms, enabled?, read, onError? })`, `startPoll({ ms, read, active?, visible?, onError? })` | Reads again every `ms` while `enabled` and the page is visible, one read at a time; the timer is cleared on unmount and when `enabled` turns false. `startPoll` is the framework-free loop, and returns its stop. See [below](#usepoll). |
145
146
  | `createSubmitGuard()`, `busySettler(guard)` | The framework-free guard behind `useSubmitOnce` (`@nxgt/material-core/submit-guard`): `{ once(run, { settleSync? }), settle(run?), pendingRun() }`, and the function that settles it from a busy flag. |
146
147
 
147
148
  ```tsx
@@ -436,6 +437,45 @@ drafts keep what was typed, the guard asks before it is dropped; they combine.
436
437
  Storybook: `Guides/Hooks/useUnsavedGuard` (leave, stay, release after save,
437
438
  query change, flush, dialog form). No Vue twin yet (`vue` issue #2019).
438
439
 
440
+ ### `usePoll`
441
+
442
+ Several screens re-read a resource every few seconds while something is under
443
+ way (a sync, a pending state). The hook owns the interval, the visibility check
444
+ and the single-flight guard.
445
+
446
+ ```tsx
447
+ import { usePoll } from '@nxgt/material/hooks';
448
+
449
+ export function JobStatus({ job, reload }: { job: Job; reload: () => Promise<void> }) {
450
+ usePoll({
451
+ ms: 3000,
452
+ enabled: job.status === 'running',
453
+ read: reload,
454
+ onError: (error) => console.warn('poll failed', error),
455
+ });
456
+ return <p>{job.status}</p>;
457
+ }
458
+ ```
459
+
460
+ - **Visible pages only.** A tick that falls while
461
+ `document.visibilityState` is not `visible` is skipped, not queued, so
462
+ returning to the tab waits for the next tick.
463
+ - **Never two reads at once.** A tick that falls while `read` (its promise) is
464
+ still running is skipped, also after a restart (a new `ms`, `enabled` back on).
465
+ - **Stops cleanly.** The timer is cleared on unmount and when `enabled` turns
466
+ false; turning it back on starts a new interval.
467
+ - **First read after `ms`.** Call `read` yourself for an immediate one.
468
+ - **Latest `read`.** `read` and `onError` may be new on every render; the
469
+ interval does not restart. Only `ms` and `enabled` restart it.
470
+ - **Errors.** A read that throws or rejects is passed to `onError` and the poll
471
+ goes on; without `onError` it is ignored.
472
+ - **Server.** Nothing runs during render or on the server.
473
+ - **Outside React**, `startPoll({ ms, read, active?, visible? })` is the same
474
+ loop; `active()` and `visible()` are checked at each tick, and the call
475
+ returns the function that stops it.
476
+
477
+ No Vue twin yet (`vue` issue #2498).
478
+
439
479
  ## `@nxgt/material/i18n`
440
480
 
441
481
  | Export | What it does |
@@ -204,8 +204,11 @@ The props of each are in
204
204
  The `Guides/Recipes` stories in Storybook are tested whole screens (the code
205
205
  ships with the `screen-recipes` skill of the `nxgt-material` plugin): a home
206
206
  dashboard, a list and detail, a form and dialog, a table and settings, a dense
207
- detail page, a status board, a landing page and a search dialog with its
208
- results page. Start from the closest one and replace the data.
207
+ detail page, a status board, a landing page, a search dialog with its results
208
+ page, a team feed, the sign-in, sign-up and forgotten-password screens, a
209
+ gallery of pictures, settings with sections, a messages thread, an overview of
210
+ rearrangeable widgets on `DashboardGrid` and a document editor. Start from the
211
+ closest one and replace the data.
209
212
 
210
213
  ## See also
211
214
 
@@ -68,7 +68,7 @@ at the top are needed is in [getting started](../getting-started.md#styles).
68
68
  | `--accent`, `--accent-foreground` | `bg-accent`, … | hover surfaces |
69
69
  | `--nxgt-accent` | `[--nxgt-accent:…]` | the mark colour of the selection controls: the fill, handle, state layer and stop dots of `Slider`, the on state of `Switch`, the box and dot of `Checkbox` and `Radio`. It defaults to `--primary` (`--primary-text` in dark) and is set per control, so override it on the control itself, not on `:root` |
70
70
  | `--error`, `--success`, `--info`, `--warning` (+ `-foreground`) | `bg-error`, `text-success`, … | the status colours of `Button`, `Alert`, `Badge`, toasts |
71
- | `--primary-text` | `text-primary-text` | primary drawn as text or an icon on a page, card or popover surface: `Typography tone="primary"`, links, the `Alert` primary icon, selected labels. In light, `--primary` mixed toward black: 96 % at most, solved lower on a tinted page so a tonal Button label on a striped row, or straight on the page ground, reaches 4.6:1 (92 % on teal, 88 % on corail, 92 % on pink, 95 % on menthe); in dark, `--primary` mixed 60 % with white (`color-mix(in oklab, var(--primary) 40%, white)`), well over 4.5:1 on `--background` and `--card` |
71
+ | `--primary-text` | `text-primary-text` | primary drawn as text or an icon on a page, card or popover surface: `Typography tone="primary"`, links, the `Alert` primary icon, selected labels. In light, `--primary` mixed toward black: 96 % at most, solved lower on a tinted page so a tonal Button label on a striped, selected or header row, or straight on the page ground, reaches 4.6:1 (79 % on teal, 76 % on corail; read the shipped theme file for each preset); in dark, `--primary` mixed 60 % with white (`color-mix(in oklab, var(--primary) 40%, white)`), well over 4.5:1 on `--background` and `--card` |
72
72
  | `--primary-indicator` | `bg-primary-indicator`, `border-primary-indicator`, `ring-primary-indicator`, `stroke-primary-indicator` | primary drawn as a non-text mark that carries a state (WCAG 1.4.11, 3:1): an active bar or dot, a selected or focused border, a ring, a spinner arc. Equal to `--primary` in light; in dark, `--primary` mixed 25 % with white (`color-mix(in oklab, var(--primary) 75%, white)`), 4.8:1 on `--background`, 4.5:1 on `--card`, 4.2:1 on `--muted` (`--primary` itself gives 2.7, 2.5 and 2.3:1) |
73
73
  | `--error-text` | `text-error-text` | error drawn as text: field messages, `HelperText` in its error state, form and dialog errors, `variant="error"` menu items |
74
74
  | `--destructive` | `bg-destructive`, `text-destructive` | deprecated alias of `--error`: the light theme sets it to `var(--error)`, the dark theme keeps its own value; use `--error` |
@@ -1012,7 +1012,7 @@ override on the root, or use a [theme zone](#themes), which re-declares them.
1012
1012
  for the same reason: the brand colour is a dark fill (it keeps near-white text
1013
1013
  readable on a button) and reads at only about 2:1 as text on the dark surfaces.
1014
1014
  The light value is `color-mix(in oklab, var(--primary) 96%, black)` at most (primary
1015
- 4 % toward black, 7.9:1 on the white page; the share is solved per theme and drops when a tinted page would leave a tonal Button label under 4.6:1 on a striped row or on the page ground, to 92 % on teal); the dark one is
1015
+ 4 % toward black, 7.9:1 on the white page; the share is solved per theme and drops when a tinted page would leave a tonal Button label under 4.6:1 on a striped, selected or header row or on the page ground, to 79 % on teal); the dark one is
1016
1016
  `color-mix(in oklab, var(--primary) 40%, white)` (primary mixed 60 % with
1017
1017
  white, the recipe of the tonal `Button` labels). `Typography tone="primary"`,
1018
1018
  the `Alert` primary icon, links, selected labels and the other primary text of
package/docs/roadmap.md CHANGED
@@ -6,6 +6,12 @@ version they shipped in, and planned entries may name the major they target.
6
6
 
7
7
  ## Now
8
8
 
9
+ - **App shell header refresh** — `AppHeader` and the look `TopAppBar`
10
+ shares sit flat on the page ground and tint when content scrolls under
11
+ them; the actions are grouped in a pill, the search is a pill field (an
12
+ icon on a phone), and the menu button is a ghost icon button. The new look
13
+ is the default in a minor, with no API change; an A/B preview comes first
14
+ ([#2503](https://github.com/softistx/nxgt-material/issues/2503)).
9
15
  - **Calculator** — `@nxgt/material/calculator` is an exact-decimal calculator
10
16
  (Standard, Scientific, point of sale, Financial, Conversion, Programmer;
11
17
  every variant but Standard loads on demand)
@@ -166,13 +172,10 @@ version they shipped in, and planned entries may name the major they target.
166
172
  descriptions, a small empty state, loading rows and an in-list search
167
173
  pill. What changes on upgrade:
168
174
  [The field look (2.x)](guides/field-look-2-x.md)
175
+ - **`TotalsList`** — the on-screen `PrintTotals` (#2490): React only for now; Vue twin is #2492.
169
176
 
170
177
  ## Next
171
178
 
172
- - **Seven more layout recipes** — Feed, Gallery, Auth, Thread, settings,
173
- dashboard and editor screens, each a tested Storybook recipe under
174
- Guides/Recipes with its code beside it, on the layouts the shell now ships
175
- ([#2413](https://github.com/softistx/nxgt-material/issues/2413)).
176
179
  - **FilterBar "Add a filter" stays on its row** — the button no longer wraps
177
180
  alone onto a second row on a desktop screen ([#1752](https://github.com/softistx/nxgt-material/issues/1752)).
178
181
 
@@ -383,6 +386,8 @@ version they shipped in, and planned entries may name the major they target.
383
386
 
384
387
  ## Shipped
385
388
 
389
+ - **Seven more layout recipes (next minor)** — whole screens to start from under Guides/Recipes, each a tested Storybook story with its code in the plugin's `screen-recipes` skill: a team feed, a gallery of pictures by day, sign-in, sign-up and forgotten-password screens, a messages thread, settings with sections, an overview of rearrangeable widgets and a document editor, on the layouts the shell ships ([#2413](https://github.com/softistx/nxgt-material/issues/2413)).
390
+
386
391
  - **Search dialog and results page recipe (next patch)** — a command dialog (Ctrl/Cmd+K and a header button), debounced grouped suggestions, type chips, a conditional filter and highlighted result cards, with its code in the plugin's `screen-recipes` skill ([#2333](https://github.com/softistx/nxgt-material/issues/2333)).
387
392
 
388
393
  - **Layouts: adaptive navigation, headers and page layouts (3.80.0)** — `ActivityLayout` takes `navigation` (a bottom bar with More and badges, a rail with a FAB or expanded, a standard or modal drawer, a collapsible sidebar, a top navigation with dropdowns, two-level navigation, a floating toolbar or none, chosen for phone, tablet and desktop) and `header` (small, center-aligned, medium and large collapsing on scroll, hide on scroll, a search bar that opens full screen on a phone, sticky tabs), and a route adds a back arrow with `handle.back`; each piece is also exported. Page layouts grow with it: an inspector pane and the selection in the URL for `ListDetailsLayout`, phone modes and a container breakpoint for `PaneLayout`, a centred form and wizard for `ThreadLayout`, a split `AuthCard`, spans and masonry for `ResponsiveGrid`, tabs for `Kanban` on a phone, `Doc` drawn inside the shell, and new `SettingsLayout`, `DashboardGrid` (tiles the user rearranges), `EditorLayout` and `LandingLayout` (with `LandingSection` and `SiteFooter`). Everything is opt-in; the shell's default is unchanged. [Layouts guide](guides/layouts.md) ([#2103](https://github.com/softistx/nxgt-material/issues/2103)). Follow-ups: a resize handle for `EditorLayout`'s properties panel and a separate open state for its phone sheet.
@@ -1182,7 +1182,7 @@ given, `leading` replaces `icon` (and warns in development).
1182
1182
  **When:** axe (or Lighthouse) reports `color-contrast` on a `tonal` or
1183
1183
  `outlined` `Button` or `IconButton` (`primary` in the dark theme, `error`,
1184
1184
  `warning` and `success`), at rest or hovered, focused or pressed; on the
1185
- subtitle of a selected `ListTile`; or on a `Badge` link while it is hovered.
1185
+ subtitle of a selected `ListTile`; or on a `Badge` link while it is hovered; or on a tonal primary `Button` in a selected `Table` row or a table header cell (light, 4.1 to 4.4:1 before the share of `--primary-text` was solved against those surfaces, #2495).
1186
1186
  **Why:** those variants drew their text in the token itself, and some tokens
1187
1187
  do not reach 4.5:1 as text on the page and on their own tint: the dark
1188
1188
  `--primary` is a dark indigo (2.2:1 on `--card`), the light `--error` gave
@@ -1245,7 +1245,7 @@ on the page; the catalogue's own primary text passes.
1245
1245
  **Why:** it is drawn with `text-primary`, the fill colour. The brand colour is
1246
1246
  dark so that near-white text reads on it, which makes it too dark to read as
1247
1247
  text on the dark surfaces. Primary text has its own token, `--primary-text`:
1248
- `--primary` mixed toward black in light (96 % at most, solved per theme), `--primary` mixed 60 % with white in dark.
1248
+ `--primary` mixed toward black in light (96 % at most, solved per theme, lower when a tonal label sits on a tinted row, selected row or header), `--primary` mixed 60 % with white in dark.
1249
1249
  **Fix:** draw primary text with `text-primary-text`; keep `text-primary` for
1250
1250
  fills, borders and rings:
1251
1251
 
@@ -21,9 +21,9 @@
21
21
  --primary: oklch(46% 0.1135 276.35);
22
22
  --primary-foreground: oklch(0.984 0.003 247.858);
23
23
  /* Primary drawn as text or an icon on a normal surface (`text-primary-text`):
24
- the colour mixed 4 % toward black in light, which keeps it 7.9:1 on
25
- the white page and on a Card (the colour itself gives 7.3:1); the dark theme lifts it. */
26
- --primary-text: color-mix(in oklab, var(--primary) 96%, black);
24
+ the colour mixed 10 % toward black in light, which keeps it above 4.5:1 on
25
+ the page, a Card and the tinted row, header and ground surfaces a tonal Button label sits on; the dark theme lifts it. */
26
+ --primary-text: color-mix(in oklab, var(--primary) 90%, black);
27
27
  /* Primary drawn as a non-text mark (WCAG 1.4.11, 3:1): an indicator bar or dot,
28
28
  a selected or focused border, a ring, a spinner arc, a progress fill
29
29
  (`bg-primary-indicator`, `border-primary-indicator`...). Light is --primary;
@@ -184,6 +184,18 @@
184
184
  min-height: 2.75rem;
185
185
  }
186
186
  }
187
+ /* Nothing to show (#2497): no select-all, no sort, no "Filters" trigger, no
188
+ * actions and no bulk bar leave both slots empty, and the row would be an
189
+ * empty 40px gap under the filter bar. Any control fills a slot, and the row
190
+ * is back. */
191
+ [data-slot="server-table-phone-row"]:has(
192
+ > [data-slot="server-table-phone-row-start"]:empty
193
+ ):has(
194
+ > [data-slot="server-table-phone-row-end"]
195
+ > [data-slot="server-table-phone-sort"]:only-child:empty
196
+ ) {
197
+ display: none;
198
+ }
187
199
  @media (pointer: coarse) {
188
200
  /* The 32px bulk buttons reach 44px with an invisible 6px margin: a gap of
189
201
  * 12px keeps neighbours from taking each other's edge. */