@nxgt/material-vue 0.31.0 → 0.33.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 (104) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +20 -7
  3. package/dist/{Button-CNbJT5qV.js → Button-DEA5W6O3.js} +1 -1
  4. package/dist/{HoverCardTrigger-BTAHE6wK.js → HoverCardTrigger-CjSteFgk.js} +2 -2
  5. package/dist/{camera-rJmgxIEn.js → camera-B063QUeM.js} +4 -4
  6. package/dist/components/components.css +3 -1
  7. package/dist/components/ui/fields/otp-field/input-otp.css +48 -0
  8. package/dist/{components-Dxy9DvEe.js → components-CmIOfRO4.js} +13547 -17345
  9. package/dist/components.js +8 -8
  10. package/dist/composables.js +25 -25
  11. package/dist/dnd.js +1 -1
  12. package/dist/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
  13. package/dist/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
  14. package/dist/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
  15. package/dist/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
  16. package/dist/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
  17. package/dist/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
  18. package/dist/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
  19. package/dist/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
  20. package/dist/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
  21. package/dist/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
  22. package/dist/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
  23. package/dist/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
  24. package/dist/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
  25. package/dist/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
  26. package/dist/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
  27. package/dist/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
  28. package/dist/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
  29. package/dist/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
  30. package/dist/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
  31. package/dist/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
  32. package/dist/{form-scope-BhNw4Mm6.js → form-scope-DuDx5Jyx.js} +1 -1
  33. package/dist/{i18n-provider-BPK68eqM.js → i18n-provider-CiVi05fs.js} +1 -1
  34. package/dist/i18n.js +4 -4
  35. package/dist/{icon-button-DXXjxBgr.js → icon-button-CWcOcNHr.js} +1 -1
  36. package/dist/lib/components/ui/fields/attributes/form-schema.d.ts +11 -2
  37. package/dist/lib/components/ui/fields/contacts/consts.d.ts +1 -1
  38. package/dist/lib/components/ui/fields/opening-hours/consts.d.ts +12 -1
  39. package/dist/lib/components/ui/fields/search-field/index.d.ts +2 -1
  40. package/dist/lib/components/ui/primitives/index.d.ts +1 -0
  41. package/dist/lib/components/ui/primitives/nonce-provider/index.d.ts +2 -0
  42. package/dist/lib/components/ui/primitives/nonce-provider/nonce-provider.vue.d.ts +20 -0
  43. package/dist/lib/components/ui/selection/autocomplete/autocomplete-items.d.ts +5 -0
  44. package/dist/lib/components/ui/selection/autocomplete/autocomplete.vue.d.ts +6 -3
  45. package/dist/lib/components/ui/selection/autocomplete/filter-options.d.ts +9 -2
  46. package/dist/lib/components/ui/selection/autocomplete/index.d.ts +1 -0
  47. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-list.vue.d.ts +8 -1
  48. package/dist/lib/components/ui/selection/autocomplete/search-types.d.ts +34 -0
  49. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-items.d.ts +20 -3
  50. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-list.d.ts +28 -0
  51. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-search.d.ts +11 -0
  52. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-status.d.ts +32 -0
  53. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-texts.d.ts +1 -3
  54. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete.d.ts +10 -5
  55. package/dist/lib/components/ui/selection/radio-group/radio-group-field.vue.d.ts +2 -0
  56. package/dist/lib/composables/index.d.ts +1 -0
  57. package/dist/lib/composables/use-csp-nonce.d.ts +18 -0
  58. package/dist/lib/i18n/resources/en/en.d.ts +4 -0
  59. package/dist/lib/i18n/resources/en/material.json.d.ts +5 -1
  60. package/dist/lib/i18n/resources/fr/fr.d.ts +4 -0
  61. package/dist/lib/i18n/resources/fr/material.json.d.ts +5 -1
  62. package/dist/lib/i18n/resources/resources.d.ts +8 -0
  63. package/dist/lib/i18n/translator.d.ts +1 -1
  64. package/dist/lib/lib/index.d.ts +1 -0
  65. package/dist/lib/nuxt/auto-imports.d.ts +1 -1
  66. package/dist/lib/nuxt/component-names.d.ts +1 -1
  67. package/dist/lib/vite/index.d.ts +3 -2
  68. package/dist/lib.js +50 -49
  69. package/dist/main.js +17 -17
  70. package/dist/map.js +4 -4
  71. package/dist/material-vue.css +1 -1
  72. package/dist/{pdf-outline-sidebar-6pIxLP_o.js → pdf-outline-sidebar-CzY59j_Y.js} +2 -2
  73. package/dist/{pdf-page-list-ojy2YSTr.js → pdf-page-list-BMxNOnPk.js} +2 -2
  74. package/dist/{pdf-page--zE3j8bc.js → pdf-page-nM_fvTHh.js} +1 -1
  75. package/dist/{pdf-reader-CcSJaFFp.js → pdf-reader-Dpv3oWe1.js} +52 -49
  76. package/dist/{pdf-sidebar-8DC9VdCG.js → pdf-sidebar-DkefUgWg.js} +3 -3
  77. package/dist/{pdf-thumbnail-sidebar-BbPcfCYm.js → pdf-thumbnail-sidebar-I6i1lv5J.js} +3 -3
  78. package/dist/{pdf-toolbar-B_MDicKL.js → pdf-toolbar-Da6UMt1O.js} +4 -4
  79. package/dist/pdf.js +2 -2
  80. package/dist/{sortable-provider-Cb3tm9Cy.js → sortable-provider-Cvpqk8tp.js} +2 -2
  81. package/dist/{stored-language-UnInBB6T.js → stored-language-GDRdeYD8.js} +10 -2
  82. package/dist/{stx-player-CjGKQF6K.js → stx-player-CN7Ic8tP.js} +14 -14
  83. package/dist/{translator-J3_vaV_A.js → translator-BDFEmlcL.js} +1 -1
  84. package/dist/{use-heading-spy-_dk8kGwL.js → use-heading-spy-DimKb49j.js} +54 -46
  85. package/dist/{use-material-translation-Do6ztdtV.js → use-material-translation-CaHS6oZz.js} +3 -3
  86. package/dist/vite.js +2 -2
  87. package/docs/README.md +1 -0
  88. package/docs/components/data-display.md +5 -0
  89. package/docs/components/form-fields.md +23 -0
  90. package/docs/components/primitives.md +41 -0
  91. package/docs/components/selection-and-choice.md +75 -4
  92. package/docs/getting-started.md +5 -1
  93. package/docs/guides/csp.md +420 -0
  94. package/docs/guides/helpers.md +3 -1
  95. package/docs/guides/theming.md +27 -0
  96. package/docs/roadmap.md +21 -0
  97. package/docs/troubleshooting.md +217 -0
  98. package/lib/components/components.css +3 -1
  99. package/lib/components/ui/fields/otp-field/input-otp.css +48 -0
  100. package/lib/nuxt/auto-imports.ts +1 -0
  101. package/lib/nuxt/component-names.ts +1 -0
  102. package/lib/vite/index.ts +6 -2
  103. package/package.json +9 -4
  104. package/dist/lib/components/ui/fields/search-field/debounce.d.ts +0 -5
@@ -1,28 +1,29 @@
1
- import { a as e } from "./i18n-provider-BPK68eqM.js";
1
+ import { a as e } from "./i18n-provider-CiVi05fs.js";
2
2
  import { c as t, d as n, f as r, l as i, n as a, u as o } from "./number.utils-Cy1z6qI7.js";
3
- import { computed as s, getCurrentInstance as c, onMounted as l, ref as u, toValue as d } from "vue";
4
- import { useIntersectionObserver as f, useMediaQuery as p, useSSRWidth as m, useStorage as h } from "@vueuse/core";
3
+ import { computed as s, getCurrentInstance as c, inject as l, onMounted as u, ref as d, toValue as f } from "vue";
4
+ import { injectConfigProviderContext as p } from "reka-ui";
5
+ import { useIntersectionObserver as m, useMediaQuery as h, useSSRWidth as g, useStorage as _ } from "@vueuse/core";
5
6
  //#region lib/composables/resolve-theme.ts
6
- function g(e, t) {
7
+ function v(e, t) {
7
8
  let n = e === "light" || e === "dark" ? e : "system";
8
9
  return {
9
10
  theme: n === "system" ? t ? "dark" : "light" : n,
10
11
  preference: n
11
12
  };
12
13
  }
13
- function _(e) {
14
+ function y(e) {
14
15
  typeof document > "u" || (document.documentElement.classList.toggle("dark", e === "dark"), document.documentElement.style.colorScheme = e);
15
16
  }
16
17
  //#endregion
17
18
  //#region lib/composables/use-ssr-media-query.ts
18
- var v = 1024;
19
- function y(e) {
20
- return c() ? p(e, { ssrWidth: m() ?? v }) : p(e);
19
+ var b = 1024;
20
+ function x(e) {
21
+ return c() ? h(e, { ssrWidth: g() ?? b }) : h(e);
21
22
  }
22
23
  //#endregion
23
24
  //#region lib/composables/use-theme.ts
24
- function b() {
25
- let e = y("(prefers-color-scheme: dark)"), t = h("theme", null, void 0, { initOnMounted: !0 }), n = s(() => g(t.value, !!e.value));
25
+ function S() {
26
+ let e = x("(prefers-color-scheme: dark)"), t = _("theme", null, void 0, { initOnMounted: !0 }), n = s(() => v(t.value, !!e.value));
26
27
  return {
27
28
  get theme() {
28
29
  return n.value.theme;
@@ -31,19 +32,19 @@ function b() {
31
32
  return n.value.preference;
32
33
  },
33
34
  updateTheme: (n) => {
34
- n === "system" ? t.value = null : t.value = n, _(g(n === "system" ? void 0 : n, !!e.value).theme);
35
+ n === "system" ? t.value = null : t.value = n, y(v(n === "system" ? void 0 : n, !!e.value).theme);
35
36
  }
36
37
  };
37
38
  }
38
39
  //#endregion
39
40
  //#region lib/composables/use-mobile.ts
40
- var x = 768;
41
- function S() {
42
- return y(`(max-width: ${x - 1}px)`);
41
+ var C = 768;
42
+ function w() {
43
+ return x(`(max-width: ${C - 1}px)`);
43
44
  }
44
45
  //#endregion
45
46
  //#region ../../node_modules/date-fns/locale/fr/_lib/formatDistance.js
46
- var C = {
47
+ var T = {
47
48
  lessThanXSeconds: {
48
49
  one: "moins d’une seconde",
49
50
  other: "moins de {{count}} secondes"
@@ -105,10 +106,10 @@ var C = {
105
106
  one: "presqu’un an",
106
107
  other: "presque {{count}} ans"
107
108
  }
108
- }, w = (e, t, n) => {
109
- let r, i = C[e];
109
+ }, E = (e, t, n) => {
110
+ let r, i = T[e];
110
111
  return r = typeof i == "string" ? i : t === 1 ? i.one : i.other.replace("{{count}}", String(t)), n?.addSuffix ? n.comparison && n.comparison > 0 ? "dans " + r : "il y a " + r : r;
111
- }, T = {
112
+ }, D = {
112
113
  date: r({
113
114
  formats: {
114
115
  full: "EEEE d MMMM y",
@@ -136,18 +137,18 @@ var C = {
136
137
  },
137
138
  defaultWidth: "full"
138
139
  })
139
- }, E = {
140
+ }, O = {
140
141
  lastWeek: "eeee 'dernier à' p",
141
142
  yesterday: "'hier à' p",
142
143
  today: "'aujourd’hui à' p",
143
144
  tomorrow: "'demain à' p'",
144
145
  nextWeek: "eeee 'prochain à' p",
145
146
  other: "P"
146
- }, D = (e, t, n, r) => E[e], O = {
147
+ }, k = (e, t, n, r) => O[e], A = {
147
148
  narrow: ["av. J.-C", "ap. J.-C"],
148
149
  abbreviated: ["av. J.-C", "ap. J.-C"],
149
150
  wide: ["avant Jésus-Christ", "après Jésus-Christ"]
150
- }, k = {
151
+ }, j = {
151
152
  narrow: [
152
153
  "T1",
153
154
  "T2",
@@ -166,7 +167,7 @@ var C = {
166
167
  "3ème trimestre",
167
168
  "4ème trimestre"
168
169
  ]
169
- }, A = {
170
+ }, M = {
170
171
  narrow: [
171
172
  "J",
172
173
  "F",
@@ -209,7 +210,7 @@ var C = {
209
210
  "novembre",
210
211
  "décembre"
211
212
  ]
212
- }, j = {
213
+ }, N = {
213
214
  narrow: [
214
215
  "D",
215
216
  "L",
@@ -246,7 +247,7 @@ var C = {
246
247
  "vendredi",
247
248
  "samedi"
248
249
  ]
249
- }, M = {
250
+ }, P = {
250
251
  narrow: {
251
252
  am: "AM",
252
253
  pm: "PM",
@@ -277,7 +278,7 @@ var C = {
277
278
  evening: "du soir",
278
279
  night: "du matin"
279
280
  }
280
- }, N = (e, t) => {
281
+ }, F = (e, t) => {
281
282
  let n = Number(e), r = t?.unit;
282
283
  if (n === 0) return "0";
283
284
  let i = [
@@ -288,38 +289,38 @@ var C = {
288
289
  "second"
289
290
  ], a;
290
291
  return a = n === 1 ? r && i.includes(r) ? "ère" : "er" : "ème", n + a;
291
- }, P = ["MMM", "MMMM"], F = {
292
+ }, I = ["MMM", "MMMM"], L = {
292
293
  en: t,
293
294
  fr: {
294
295
  code: "fr",
295
- formatDistance: w,
296
- formatLong: T,
297
- formatRelative: D,
296
+ formatDistance: E,
297
+ formatLong: D,
298
+ formatRelative: k,
298
299
  localize: {
299
- preprocessor: (e, t) => e.getDate() === 1 || !t.some((e) => e.isToken && P.includes(e.value)) ? t : t.map((e) => e.isToken && e.value === "do" ? {
300
+ preprocessor: (e, t) => e.getDate() === 1 || !t.some((e) => e.isToken && I.includes(e.value)) ? t : t.map((e) => e.isToken && e.value === "do" ? {
300
301
  isToken: !0,
301
302
  value: "d"
302
303
  } : e),
303
- ordinalNumber: N,
304
+ ordinalNumber: F,
304
305
  era: n({
305
- values: O,
306
+ values: A,
306
307
  defaultWidth: "wide"
307
308
  }),
308
309
  quarter: n({
309
- values: k,
310
+ values: j,
310
311
  defaultWidth: "wide",
311
312
  argumentCallback: (e) => e - 1
312
313
  }),
313
314
  month: n({
314
- values: A,
315
+ values: M,
315
316
  defaultWidth: "wide"
316
317
  }),
317
318
  day: n({
318
- values: j,
319
+ values: N,
319
320
  defaultWidth: "wide"
320
321
  }),
321
322
  dayPeriod: n({
322
- values: M,
323
+ values: P,
323
324
  defaultWidth: "wide"
324
325
  })
325
326
  },
@@ -449,8 +450,8 @@ var C = {
449
450
  }
450
451
  }
451
452
  };
452
- function I() {
453
- let n = e(), r = s(() => n?.language ?? "en"), i = s(() => F[r.value] ?? t);
453
+ function R() {
454
+ let n = e(), r = s(() => n?.language ?? "en"), i = s(() => L[r.value] ?? t);
454
455
  return {
455
456
  get language() {
456
457
  return r.value;
@@ -462,19 +463,26 @@ function I() {
462
463
  };
463
464
  }
464
465
  //#endregion
466
+ //#region lib/composables/use-csp-nonce.ts
467
+ var z = Symbol("nxgt-csp-nonce"), B = Symbol("nxgt-csp-nonce-depth");
468
+ function V() {
469
+ let e = l(z, null), t = p(null);
470
+ return s(() => e?.value || t?.nonce?.value || void 0);
471
+ }
472
+ //#endregion
465
473
  //#region lib/composables/use-heading-spy.ts
466
- var L = "0px 0px -65% 0px", R = [0, 1];
467
- function z(e, t, n) {
468
- let r = u(!1);
469
- l(() => {
474
+ var H = "0px 0px -65% 0px", U = [0, 1];
475
+ function W(e, t, n) {
476
+ let r = d(!1);
477
+ u(() => {
470
478
  r.value = !0;
471
- }), f(s(() => r.value ? d(e).map((e) => document.getElementById(e)).filter((e) => e != null) : []), (e) => {
479
+ }), m(s(() => r.value ? f(e).map((e) => document.getElementById(e)).filter((e) => e != null) : []), (e) => {
472
480
  let n = e.filter((e) => e.isIntersecting).sort((e, t) => e.boundingClientRect.top - t.boundingClientRect.top)[0]?.target.id;
473
481
  n && t(n);
474
482
  }, {
475
- rootMargin: n?.rootMargin ?? L,
476
- threshold: n?.threshold ?? R
483
+ rootMargin: n?.rootMargin ?? H,
484
+ threshold: n?.threshold ?? U
477
485
  });
478
486
  }
479
487
  //#endregion
480
- export { y as a, b as i, I as n, _ as o, S as r, g as s, z as t };
488
+ export { R as a, x as c, V as i, y as l, B as n, w as o, z as r, S as s, W as t, v as u };
@@ -1,6 +1,6 @@
1
- import "./stored-language-UnInBB6T.js";
2
- import { a as e } from "./i18n-provider-BPK68eqM.js";
3
- import { i as t, r as n } from "./translator-J3_vaV_A.js";
1
+ import "./stored-language-GDRdeYD8.js";
2
+ import { a as e } from "./i18n-provider-CiVi05fs.js";
3
+ import { i as t, r as n } from "./translator-BDFEmlcL.js";
4
4
  //#region lib/i18n/translate-material.ts
5
5
  function r(e, r, i, a) {
6
6
  if (e) for (let t of /* @__PURE__ */ new Set([e, e.split("-")[0]])) {
package/dist/vite.js CHANGED
@@ -1,2 +1,2 @@
1
- import { pdfkitStandardFonts as e } from "@nxgt/material-core/vite";
2
- export { e as pdfkitStandardFonts };
1
+ import { cspAssetsInlineLimit as e, pdfkitStandardFonts as t } from "@nxgt/material-core/vite";
2
+ export { e as cspAssetsInlineLimit, t as pdfkitStandardFonts };
package/docs/README.md CHANGED
@@ -15,6 +15,7 @@ detail: every option, the second example, and the traps.
15
15
  | [PDF documents](guides/pdf.md) | You produce a PDF file from data with `@nxgt/material-vue/pdf`: in the browser (`usePdf`, `PdfDownloadButton`) or from a Nuxt server route (`pdfResponse`), fonts on an inlined server |
16
16
  | [Maps](guides/maps.md) | You render Leaflet maps or a location field with `@nxgt/material-vue/map` |
17
17
  | [SSR](guides/ssr.md) | Your app renders on a server — Nuxt or a Vite SSR server — and bundles this package |
18
+ | [Content Security Policy](guides/csp.md) | Your app sends a strict CSP: the directives to use, the components that still inject a `<style>`, the zod and KaTeX settings, self-hosting a font, the per-request nonce |
18
19
  | [Helpers](guides/helpers.md) | You need a utility, composable, Pinia store factory or type from `/lib`, `/composables`, `/i18n`, `/models`, `/types` or `/dnd` |
19
20
  | [Troubleshooting](troubleshooting.md) | Something failed and you have an error message or a symptom |
20
21
  | [Roadmap](roadmap.md) | You want to know what is planned, what shipped, and what will not be done |
@@ -706,6 +706,11 @@ left alone. Mermaid diagrams
706
706
  render in the browser only, after mount; on the server the fence is left
707
707
  empty.
708
708
 
709
+ Math needs no import of its own: KaTeX's CSS is in the stylesheet, and its
710
+ fonts are files the stylesheet loads (`dist/fonts/katex/` on the compiled
711
+ route), allowed by `font-src 'self'`. Math in system fonts means the fonts
712
+ were not served ([troubleshooting](../troubleshooting.md#math-renders-in-the-system-fonts)).
713
+
709
714
  `MermaidDiagram` renders one chart outside markdown, the same way: client-only,
710
715
  with `mermaid` imported on mount.
711
716
 
@@ -499,6 +499,7 @@ const code = ref('');
499
499
  | `v-model` | `string` | — | the digits typed so far |
500
500
  | `@complete` | `(value: string) => void` | — | once every slot is filled (from `vue-input-otp`) |
501
501
  | `error` | `boolean` | — | error colour on the slots (`data-invalid` on each); `aria-invalid` goes on the input only, the one element a screen reader reads |
502
+ | `nonce` | `string` | the `NonceProvider`'s | the CSP nonce of the `<style>` `vue-input-otp` injects; an explicit one wins (passed down to `InputOTP`, [CSP](../guides/csp.md)) |
502
503
  | `id` | `string` | from `useId()` | the input's id, which the label points to; it differs per field — `OtpFormField` too (not the field `name`), so two code forms on one page each keep their name |
503
504
 
504
505
  `InputOTP`, `InputOTPGroup`, `InputOTPSlot` and `InputOTPSeparator` are the
@@ -891,6 +892,16 @@ attribute), `AttributeValuesForm` (fill values for a set of attributes).
891
892
  Form wrappers: `AttributesFormField` (the definitions),
892
893
  `AttributeValuesFormFieldSet` and `AttributeValueFormField` (the values).
893
894
  Add and edit confirmations use a toast, so mount a `Toaster` once in the app.
895
+ `attributeFormSchema()` returns `AttributeForm`'s zod schema, built on the
896
+ first call (in the form's setup), and `toAttributeFormSchema()` the same as
897
+ vee-validate's typed schema; the `schema` constant is **deprecated** and
898
+ removed in 1.0 ([CSP](../guides/csp.md#zod-and-unsafe-eval)).
899
+
900
+ ```ts
901
+ import { attributeFormSchema } from '@nxgt/material-vue/components';
902
+
903
+ const result = attributeFormSchema().safeParse({ label: 'Color', name: 'color', type: 'COLOR' });
904
+ ```
894
905
 
895
906
  | Prop | Type | Default | Effect |
896
907
  | --- | --- | --- | --- |
@@ -1046,6 +1057,18 @@ function onEdit(value: OpeningHour, index: number) {}
1046
1057
  Parts: `OpeningHours` (the list) and `OpeningHoursDialog` (add / edit).
1047
1058
  `OpeningHoursItem`, a day row bound to a vee-validate array, is
1048
1059
  **deprecated** and removed in 1.0: use `OpeningHoursFormField`.
1060
+ `openingHoursSchema()` returns the zod schema of one `OpeningHour`
1061
+ (`dayOfWeek` coerced from a string), built on the first call; the
1062
+ `OpeningHoursSchema` constant is **deprecated** and removed in 1.0. Under a
1063
+ strict CSP, call `z.config({ jitless: true })` first: see
1064
+ [CSP](../guides/csp.md#zod-and-unsafe-eval).
1065
+
1066
+ ```ts
1067
+ import { openingHoursSchema } from '@nxgt/material-vue/components';
1068
+ import { z } from 'zod';
1069
+
1070
+ const schema = z.object({ hours: z.array(openingHoursSchema()) });
1071
+ ```
1049
1072
 
1050
1073
  | Prop | Type | Default | Effect |
1051
1074
  | --- | --- | --- | --- |
@@ -29,6 +29,7 @@ work on them unchanged.
29
29
  | <img src="../previews/primitives-togglegroup.webp" width="96" alt=""> | [ToggleGroup](#togglegroup) | Single or multiple toggle group |
30
30
  | <img src="../previews/primitives-activity.webp" width="96" alt=""> | [Activity](#activity) | Keeps its content mounted and hides it with a boolean |
31
31
  | <img src="../previews/primitives-scrollarea.webp" width="96" alt=""> | [ScrollArea](#scrollarea) | Scroll container with a thin custom scrollbar |
32
+ | | [NonceProvider](#nonceprovider) | Gives the page's CSP nonce to every component below |
32
33
 
33
34
  ## Accordion
34
35
 
@@ -859,3 +860,43 @@ through.
859
860
  The React `ScrollArea` takes the same props, camel-cased (`scrollHideDelay`).
860
861
 
861
862
  **Storybook:** `Primitives/ScrollArea` · **React:** `ScrollArea`, `ScrollBar`
863
+
864
+ ---
865
+
866
+ ## NonceProvider
867
+
868
+ No preview: it draws nothing of its own, it hands the page's Content Security
869
+ Policy nonce down.
870
+
871
+ Gives this request's CSP nonce to every component below it: reka's `Select`,
872
+ `Combobox`, `ScrollArea` and `Splitter` (it renders a reka `ConfigProvider`
873
+ with `nonce`, carrying over a parent `ConfigProvider`'s other settings),
874
+ `InputOTP` when it has no `nonce` of its own, and the stylesheet tiptap
875
+ injects for `RichTextEditor`. Render it once, at the root, on the server and
876
+ on the client, with the same nonce. The Nuxt module registers it.
877
+
878
+ ```vue
879
+ <script setup lang="ts">
880
+ import { NonceProvider } from '@nxgt/material-vue/components';
881
+
882
+ defineProps<{ nonce?: string }>();
883
+ </script>
884
+
885
+ <template>
886
+ <NonceProvider :nonce="nonce">
887
+ <RouterView />
888
+ </NonceProvider>
889
+ </template>
890
+ ```
891
+
892
+ | Prop | Type | Default | Effect |
893
+ | --- | --- | --- | --- |
894
+ | `nonce` | `string` | `undefined` | the nonce of the page's `style-src-elem 'nonce-…'`; `undefined` or `''` inherits the nearest outer `NonceProvider`'s (or `ConfigProvider`'s) nonce (none at the root) |
895
+
896
+ `useCspNonce()` (`@nxgt/material-vue/composables`) reads it in a component of
897
+ your own; `getCspNonce()` and `setCspNonce()` (`@nxgt/material-vue/lib`) read
898
+ and keep the page's nonce outside Vue. The Nuxt `app.vue`, the client entry
899
+ and what each component needs are in the [Content Security Policy
900
+ guide](../guides/csp.md#nonceprovider).
901
+
902
+ **Storybook:** `Primitives/NonceProvider` · **React:** `NonceProvider`
@@ -231,7 +231,7 @@ options by label. Picking the selected option again clears it.
231
231
 
232
232
  ![Autocomplete](../previews/selection-choice-autocomplete.webp)
233
233
 
234
- Single or multiple (chips) autocomplete typed by its options, with free-solo values, a query event and option slots.
234
+ Single or multiple (chips) autocomplete typed by its options, with free-solo values, accent-insensitive matching, a debounced server search and option slots.
235
235
 
236
236
  ```vue
237
237
  <script setup lang="ts">
@@ -262,16 +262,81 @@ const picked = ref<string[]>(['nuxt']);
262
262
  | --- | --- | --- | --- |
263
263
  | `mode` | `'single' \| 'multiple'` | `'single'` | decides the `v-model` type |
264
264
  | `v-model` | `string \| string[] \| null` | `null` | per `mode` |
265
- | `options` | `T[]`, `T extends { value: string; label: string }` | — | required; the items, typed by your own option type |
265
+ | `options` | `T[]`, `T extends { value: string; label: string }` | `[]` | the items, typed by your own option type; with `on-search`, the list before a search and the selected options' labels |
266
266
  | `get-option-value` | `(option: T) => string` | `option.value` | the value an option stands for |
267
267
  | `get-option-label` | `(option: T) => string` | `option.label` | the text it shows and is searched by |
268
+ | `filter` | `'accent-insensitive' \| 'none' \| (option: T, query: string) => boolean` | `'accent-insensitive'`, `'none'` with `on-search` | how the list is filtered as the user types: label or value, ignoring case and accents (`e` finds "École") in the provider's language; `'none'` shows `options` as given; a function gets the trimmed query, never an empty one |
268
269
  | `free-solo` | `boolean` | `false` | accept a typed value that is not an option ("Add …") |
269
270
  | `free-solo-label` | `(query: string) => string` | `Add "{query}"`, translated | the free-solo button's text |
270
- | `@query-change` | `(query: string) => void` | — | the typed text, debounced 300 ms — fetch options here |
271
+ | `@search` (prop `on-search`) | `(query: string, { signal }: { signal: AbortSignal }) => Promise<T[]>` | — | server search: called once typing pauses; its results become the list. Each search aborts the previous one, and a response that lands after a newer one is dropped |
272
+ | `@search-error` | `(error: unknown) => void` | — | `on-search` rejected (an abort is not a rejection) |
273
+ | `min-query-length` | `number` | `1` | `on-search` waits for this many characters; below it the list says "Type at least N characters to search." |
274
+ | `debounce-ms` | `number` | `250` | the pause before `on-search` and `@query-change` run |
275
+ | `@query-change` | `(query: string) => void` | — | the typed text once typing pauses — fetch `options` yourself here |
276
+ | `loading` | `boolean` | — | with `@query-change`: "Loading…" in the list, `aria-busy` on it, announced |
277
+ | `search-error` | `string` | — | with `@query-change`: shown and announced in place of the options |
271
278
  | `empty` | `string` | "No option found." | text when nothing matches |
272
279
  | `required` | `boolean` | — | the asterisk, and `aria-required` on the input, which the label names |
273
280
  | `error` | `boolean` | — | red border on the box, error helper text, `aria-invalid` on the input |
274
281
 
282
+ ### Searching a server
283
+
284
+ `on-search` is a function prop (so `@search="fn"` binds it too): it takes
285
+ the query and an `AbortSignal`; hand the signal to `fetch` so a search the
286
+ user typed past is cancelled. The field shows "Loading…", keeps the last
287
+ results until the new ones arrive, and never lets an older response
288
+ overwrite a newer one, even from an API that ignores the signal. Pass the
289
+ selected option in `options` so its chip has a label before any search; a
290
+ picked option keeps its chip once a new search no longer returns it.
291
+
292
+ ```vue
293
+ <script setup lang="ts">
294
+ import { ref } from 'vue';
295
+ import { Autocomplete } from '@nxgt/material-vue/components';
296
+
297
+ type Person = { value: string; label: string };
298
+
299
+ async function searchPeople(query: string, { signal }: { signal: AbortSignal }) {
300
+ const response = await fetch(`/api/people?q=${encodeURIComponent(query)}`, { signal });
301
+ if (!response.ok) throw new Error(`Search failed: ${response.status}`);
302
+ return (await response.json()) as Person[];
303
+ }
304
+
305
+ const person = ref<string | null>(null);
306
+ </script>
307
+
308
+ <template>
309
+ <Autocomplete
310
+ v-model="person"
311
+ label="Person"
312
+ placeholder="Search by name"
313
+ :on-search="searchPeople"
314
+ :min-query-length="2"
315
+ :debounce-ms="300"
316
+ />
317
+ </template>
318
+ ```
319
+
320
+ With `on-search`, `min-query-length` defaults to 1, `debounce-ms` to 250 and `filter` to `'none'`
321
+ (the server filters). A rejected search shows and announces "The search
322
+ failed. Try again." by itself and hands the error to `@search-error`; an abort is
323
+ never an error. Type your search with `AutocompleteSearch<T>` (its second
324
+ argument is `AutocompleteSearchContext`, `{ signal }`); `AutocompleteFilter<T>`
325
+ and `AutocompleteSearchProps<T>` are exported from `@nxgt/material-vue/components` too.
326
+ `filterAutocompleteOptions(options, query, locale?)`, exported beside the
327
+ component, is the default filter as a function: label or value, ignoring
328
+ case and accents, lowercased for `locale`.
329
+
330
+ To fetch it yourself instead, keep `options` and `loading` in your state,
331
+ update them on `@query-change`, and pass `filter="none"` when the server
332
+ already filtered.
333
+
334
+ **Accessibility.** A polite live region (`role="status"`) says "Loading…",
335
+ then "3 results" or "No results" (or the failure) once the search answers or
336
+ typing pauses — never on every keystroke. The list is `aria-busy` while
337
+ loading. The strings are `material.autocomplete.loading`, `.results`,
338
+ `.search-error` and `.min-query`. The keyboard works as before.
339
+
275
340
  It is a generic component: `T` is inferred from `options`, and the slots —
276
341
  React's render props — receive it whole:
277
342
 
@@ -450,7 +515,7 @@ React the labelled one is called `RadioGroup`.
450
515
 
451
516
  | Prop | Type | Default | Effect |
452
517
  | --- | --- | --- | --- |
453
- | `options` | `{ value: string; label: string }[]` | — | required; the radios |
518
+ | `options` | `{ value: string; label: string; disabled?: boolean }[]` | — | required; the radios; `disabled` on an option disables only it |
454
519
  | `v-model` | `string` | — | the selected `value` |
455
520
  | `orientation` | `'vertical' \| 'horizontal'` | `'horizontal'` | layout |
456
521
  | `label`, `helper-text`, `error`, `disabled` | | — | the field's text and state |
@@ -463,6 +528,12 @@ put on `RadioGroupField` land on the `radiogroup` (`class` and `style` stay on
463
528
  the layout wrapper): your own `aria-label` / `aria-labelledby` wins, and your
464
529
  `aria-describedby` ids come first.
465
530
 
531
+ An option's type is exported as `RadioGroupOption`. A disabled option (`disabled: true` on it) is announced as disabled, skipped by
532
+ the arrow keys (as the Reka UI primitives do) and drawn with a muted label and a
533
+ faded radio; the label keeps 4.5:1 contrast in both themes. The group's own
534
+ `disabled` disables every option. An option disabled while it is the current
535
+ value stays checked.
536
+
466
537
  **Emits:** `update:modelValue` with `string`.
467
538
  **Form wrapper:** `RadioGroupFormField` — `name` inside a vee-validate form, see
468
539
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
@@ -32,7 +32,9 @@ does not import that entry never has them in its tree.
32
32
  | `@nxgt/material-vue/pdf` | `bun add react @react-pdf/renderer@^4.9`, to render; building documents needs neither ([PDF documents](guides/pdf.md)) |
33
33
  | `@nxgt/material-vue/nuxt` | a Nuxt app, `nuxt >= 3.13` ([below](#nuxt)) |
34
34
 
35
- The tarball contains the built `dist/`, `lib/styles.css`, the component
35
+ The tarball contains the built `dist/` (with KaTeX's fonts in
36
+ `dist/fonts/katex/`, which the compiled stylesheet loads),
37
+ `lib/styles.css`, the component
36
38
  stylesheets it imports (`lib/components/**/*.css`), `lib/map/map.css`, the
37
39
  Nuxt module (`lib/nuxt`, with the
38
40
  `lib/vite/index.ts` it imports), these docs and `CHANGELOG.md` (from the
@@ -48,6 +50,8 @@ In the app's root stylesheet, right after Tailwind:
48
50
  @import "@nxgt/material-vue/styles.css";
49
51
  ```
50
52
 
53
+ Toaster and InputOTP take their CSS from this stylesheet: without it they render unstyled.
54
+
51
55
  - **`styles.css` is source, not compiled CSS.** It holds the Tailwind v4
52
56
  `@theme` layer (colours, `--radius`, the `dark` variant, base styles, a few
53
57
  utilities), so it runs through *your* Tailwind build. Nothing injects it,