@nxgt/material 2.13.0 → 2.15.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.
- package/CHANGELOG.md +89 -0
- package/README.md +18 -5
- package/dist/{blockDiagram-BEXU5L5S-CQaNV8rX.js → blockDiagram-BEXU5L5S-BozTHxCb.js} +1 -1
- package/dist/{chunk-3FUC2YCW-DmtO_Y9L.js → chunk-3FUC2YCW-zWkkqKwC.js} +1 -1
- package/dist/{chunk-7M6MHVWA-BQ0cJz5u.js → chunk-7M6MHVWA-CGHIsno5.js} +1 -1
- package/dist/{chunk-GNY47TPC-DiVMvIit.js → chunk-GNY47TPC-BWB3R7nr.js} +4 -4
- package/dist/{chunk-NETBCI7D-B5Av2fJk.js → chunk-NETBCI7D-DwmNwFxb.js} +1 -1
- package/dist/{chunk-Z7XXMR3K-D4uKosJW.js → chunk-Z7XXMR3K-0DGn7Rxi.js} +1 -1
- package/dist/{classDiagram-v2-NBCMYWYE-BYktffst.js → classDiagram-v2-NBCMYWYE-BERam5ND.js} +2 -2
- package/dist/components/components.css +13 -2
- package/dist/components/ui/feedback/spinner/spinner.css +28 -0
- package/dist/components/ui/fields/otp-field/input-otp.css +48 -0
- package/dist/components/ui/media/pdf-viewer/pdf-viewer.css +24 -0
- package/dist/components/ui/overlays/drawer/vaul-drawer.css +199 -0
- package/dist/components/ui/overlays/drawer/vaul-keyframes.css +95 -0
- package/dist/{components-C-y3kTRm.js → components-B-xPQ6XX.js} +118106 -129100
- package/dist/components.js +6 -6
- package/dist/{dagre-6A5THRUB-BvQAvoOX.js → dagre-6A5THRUB-CnIZQoNt.js} +3 -3
- package/dist/{diagram-22UHCM2B-BYd0lEdS.js → diagram-22UHCM2B-DN4-okl5.js} +2 -2
- package/dist/{diagram-3UASUU5V-B3ndNq2p.js → diagram-3UASUU5V-DcrNf7TY.js} +1 -1
- package/dist/{elk-276RUBZZ-BxXn1nvc.js → elk-276RUBZZ-B7yctkbs.js} +3 -3
- package/dist/{erDiagram-OPXOYQCR-DjYy4h_r.js → erDiagram-OPXOYQCR-BVrTNrkn.js} +2 -2
- package/dist/{field-blur-V8o79CDN.js → field-blur-DDfQkuSJ.js} +866 -871
- package/dist/{flowDiagram-KWPJA3E3-BD7xvcMh.js → flowDiagram-KWPJA3E3-BKxvqo0x.js} +3 -3
- package/dist/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/{ganttDiagram-FUAMR5RP-BAuk7X6i.js → ganttDiagram-FUAMR5RP-Cv9W4LOS.js} +1 -1
- package/dist/hooks.js +25 -25
- package/dist/{journeyDiagram-ZHPQQLJL-h2S56BMC.js → journeyDiagram-ZHPQQLJL-BwSv9L1j.js} +1 -1
- package/dist/lib/components/ui/dates/calendar/use-stable-component.d.ts +10 -0
- package/dist/lib/components/ui/dates/date-field/date-field-trigger.d.ts +1 -1
- package/dist/lib/components/ui/dates/date-range-field/date-range-field-trigger.d.ts +1 -1
- package/dist/lib/components/ui/dates/picker-trigger/use-trigger-locale.d.ts +10 -0
- package/dist/lib/components/ui/fields/attributes/attribute-form/consts.d.ts +5 -1
- package/dist/lib/components/ui/fields/attributes/attribute-form/types.d.ts +2 -2
- package/dist/lib/components/ui/fields/opening-hours/consts.d.ts +12 -1
- package/dist/lib/components/ui/media/image-cropper/image-crop-aspects.d.ts +6 -0
- package/dist/lib/components/ui/media/image-cropper/use-frame-size.d.ts +4 -0
- package/dist/lib/components/ui/primitives/index.d.ts +1 -0
- package/dist/lib/components/ui/primitives/nonce-provider/index.d.ts +1 -0
- package/dist/lib/components/ui/primitives/nonce-provider/nonce-provider.d.ts +20 -0
- package/dist/lib/components/ui/primitives/typography/typography.d.ts +7 -2
- package/dist/lib/components/ui/widgets/rich-text-editor/extensions/extensions.d.ts +2 -2
- package/dist/lib/components/ui/widgets/rich-text-editor/use-rich-text-editor.d.ts +17 -0
- package/dist/lib/hooks/index.d.ts +1 -0
- package/dist/lib/hooks/use-csp-nonce.d.ts +16 -0
- package/dist/lib/lib/index.d.ts +1 -0
- package/dist/lib/vite/index.d.ts +3 -2
- package/dist/lib.js +30 -29
- package/dist/main.js +16 -16
- package/dist/map.js +1 -1
- package/dist/material.css +25 -1
- package/dist/{mermaid.core-BWF4Tq7X.js → mermaid.core-D_eXACPr.js} +21 -21
- package/dist/{mindmap-definition-NLK3R4M7-Q-4J7KMu.js → mindmap-definition-NLK3R4M7-GMAPXP2T.js} +2 -2
- package/dist/{pieDiagram-5QR66LMP-BIxQ164y.js → pieDiagram-5QR66LMP--qZHRFqG.js} +1 -1
- package/dist/{quadrantDiagram-O4NWA36T-BlI0otnv.js → quadrantDiagram-O4NWA36T-BxFGymBB.js} +1 -1
- package/dist/{requirementDiagram-PLB6GJNP-CuVPUh0S.js → requirementDiagram-PLB6GJNP-D4aL7HiM.js} +2 -2
- package/dist/{sankeyDiagram-IPEJSGJF-Cke5i_9w.js → sankeyDiagram-IPEJSGJF-zX1FL2aL.js} +1 -1
- package/dist/{stateDiagram-v2-GCMORJYK-CT53U-pL.js → stateDiagram-v2-GCMORJYK-CDm1mDv2.js} +2 -2
- package/dist/{swimlanes-2SLR337P-BwEtm09G.js → swimlanes-2SLR337P-DNwIhCCu.js} +3 -3
- package/dist/{swimlanesDiagram-TC7HE7FX-36JXV6X2.js → swimlanesDiagram-TC7HE7FX-BaR9zAzq.js} +3 -3
- package/dist/{timeline-definition-EJHVYXUP-BeX-S1oM.js → timeline-definition-EJHVYXUP-B6Xd8Spe.js} +1 -1
- package/dist/{use-heading-spy--7KHY3lw.js → use-heading-spy-wlOv87q3.js} +63 -57
- package/dist/{usecaseDiagram-POWQR4AR-Dfd0rXLr.js → usecaseDiagram-POWQR4AR-XW5of8Kj.js} +2 -2
- package/dist/vite.js +2 -2
- package/dist/{xychartDiagram-PMCCYNJV-D0UPPflL.js → xychartDiagram-PMCCYNJV-DTV6AzhO.js} +1 -1
- package/docs/README.md +1 -0
- package/docs/components/data-display.md +5 -0
- package/docs/components/date-and-time.md +6 -0
- package/docs/components/feedback-and-status.md +3 -1
- package/docs/components/form-fields.md +14 -0
- package/docs/components/primitives.md +41 -2
- package/docs/getting-started.md +5 -1
- package/docs/guides/csp.md +408 -0
- package/docs/guides/helpers.md +2 -0
- package/docs/guides/i18n.md +2 -0
- package/docs/guides/theming.md +27 -0
- package/docs/roadmap.md +17 -1
- package/docs/troubleshooting.md +180 -0
- package/lib/components/components.css +13 -2
- package/lib/components/ui/feedback/spinner/spinner.css +28 -0
- package/lib/components/ui/fields/otp-field/input-otp.css +48 -0
- package/lib/components/ui/media/pdf-viewer/pdf-viewer.css +24 -0
- package/lib/components/ui/overlays/drawer/vaul-drawer.css +199 -0
- package/lib/components/ui/overlays/drawer/vaul-keyframes.css +95 -0
- package/package.json +4 -4
|
@@ -744,6 +744,8 @@ Parts: `AttributesField` (the editable list), `AttributesList` (display with
|
|
|
744
744
|
attribute), `AttributeValuesForm` (fill values for a set of attributes).
|
|
745
745
|
Form wrappers: `AttributesFormField` (the definitions),
|
|
746
746
|
`AttributeValuesFormFieldSet` and `AttributeValueFormField` (the values).
|
|
747
|
+
`AttributeForm` builds its zod schema when it first renders, not when the
|
|
748
|
+
module loads ([CSP](../guides/csp.md#zod-and-unsafe-eval)).
|
|
747
749
|
|
|
748
750
|
| Prop | Type | Default | Effect |
|
|
749
751
|
| --- | --- | --- | --- |
|
|
@@ -856,6 +858,18 @@ const hours: OpeningHour[] = [
|
|
|
856
858
|
Parts: `OpeningHours` (the list) and `OpeningHoursDialog` (add / edit).
|
|
857
859
|
`OpeningHoursItem`, a day row bound to a react-hook-form array, is
|
|
858
860
|
**deprecated** and removed in 3.0: use `OpeningHoursFormField`.
|
|
861
|
+
`openingHoursSchema()` returns the zod schema of one `OpeningHour`
|
|
862
|
+
(`dayOfWeek` coerced from a string), built on the first call; the
|
|
863
|
+
`OpeningHoursSchema` constant is **deprecated** and removed in 3.0. Under a
|
|
864
|
+
strict CSP, call `z.config({ jitless: true })` first: see
|
|
865
|
+
[CSP](../guides/csp.md#zod-and-unsafe-eval).
|
|
866
|
+
|
|
867
|
+
```ts
|
|
868
|
+
import { openingHoursSchema } from '@nxgt/material/components';
|
|
869
|
+
import { z } from 'zod';
|
|
870
|
+
|
|
871
|
+
const schema = z.object({ hours: z.array(openingHoursSchema()) });
|
|
872
|
+
```
|
|
859
873
|
|
|
860
874
|
| Prop | Type | Default | Effect |
|
|
861
875
|
| --- | --- | --- | --- |
|
|
@@ -27,6 +27,7 @@ other pages are built from these.
|
|
|
27
27
|
| <img src="../previews/primitives-toggle.webp" width="96" alt=""> | [Toggle](#toggle) | Pressable on / off button |
|
|
28
28
|
| <img src="../previews/primitives-togglegroup.webp" width="96" alt=""> | [ToggleGroup](#togglegroup) | Single or multiple toggle group |
|
|
29
29
|
| | [Activity](#activity) | React `<Activity>` switched by a boolean |
|
|
30
|
+
| | [NonceProvider](#nonceprovider) | Gives the page's CSP nonce to every component below |
|
|
30
31
|
|
|
31
32
|
## Accordion
|
|
32
33
|
|
|
@@ -175,9 +176,17 @@ import { Typography } from '@nxgt/material/components';
|
|
|
175
176
|
| Prop | Type | Default | Effect |
|
|
176
177
|
| --- | --- | --- | --- |
|
|
177
178
|
| `variant` | `'huge-large' \| 'huge-medium' \| 'huge-small' \| 'headline-larger' \| 'headline-large' \| 'headline-medium' \| 'headline-small' \| 'title-large' \| 'title-medium' \| 'title-small' \| 'body-medium' \| 'body-small' \| 'caption' \| 'normal'` | `'normal'` | size and weight; `caption` is also muted |
|
|
179
|
+
| `as` | an intrinsic tag name (`keyof JSX.IntrinsicElements`) | `'div'` | the element to render, keeping the variant, e.g. `as="h2"` or `as="span"` |
|
|
180
|
+
| `asChild` | `boolean` | `false` | merge the classes onto the single child instead of rendering an element |
|
|
178
181
|
|
|
179
|
-
It renders a `<div
|
|
180
|
-
|
|
182
|
+
It renders a `<div>` unless `as` or `asChild` says otherwise:
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
<Typography as="span" variant="title-small">Inline</Typography>
|
|
186
|
+
<Typography asChild variant="body-medium"><a href="/docs">Docs</a></Typography>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The variants are also exported as `typographyVariants` for use on your own elements.
|
|
181
190
|
|
|
182
191
|
**Storybook:** `Primitives/Typography` · **Vue:** `Typography`
|
|
183
192
|
|
|
@@ -773,3 +782,33 @@ import { Activity } from '@nxgt/material/components';
|
|
|
773
782
|
| `visible` | `boolean` | `false` | `true` renders with `mode="visible"`; `false` hides the subtree and keeps its state |
|
|
774
783
|
|
|
775
784
|
**Storybook:** — · **Vue:** `Activity`
|
|
785
|
+
|
|
786
|
+
## NonceProvider
|
|
787
|
+
|
|
788
|
+
No preview: it draws nothing of its own, it hands the page's Content Security
|
|
789
|
+
Policy nonce down.
|
|
790
|
+
|
|
791
|
+
Gives this request's CSP nonce to every component below it: the `<style>`
|
|
792
|
+
elements of `ScrollArea` and `Select`, the stylesheet tiptap injects for
|
|
793
|
+
`RichTextEditor`, and, on the client, the scroll lock of every modal. Render it once, at the root, on the server and on
|
|
794
|
+
the client, with the same nonce.
|
|
795
|
+
|
|
796
|
+
```tsx
|
|
797
|
+
import { NonceProvider } from '@nxgt/material/components';
|
|
798
|
+
|
|
799
|
+
<NonceProvider nonce={nonce}>
|
|
800
|
+
<App />
|
|
801
|
+
</NonceProvider>;
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
| Prop | Type | Default | Effect |
|
|
805
|
+
| --- | --- | --- | --- |
|
|
806
|
+
| `nonce` | `string \| undefined` | — | the nonce of the page's `style-src-elem 'nonce-…'`; `undefined` or `''` inherits the nearest outer `NonceProvider`'s nonce (none at the root) |
|
|
807
|
+
|
|
808
|
+
`useCspNonce()` (`@nxgt/material/hooks`) reads it in a component of your own;
|
|
809
|
+
`getCspNonce()` and `setCspNonce()` (`@nxgt/material/lib`) read and keep the
|
|
810
|
+
page's nonce outside React. The server entry, the client entry and what each
|
|
811
|
+
component needs are in the [Content Security Policy
|
|
812
|
+
guide](../guides/csp.md#nonceprovider).
|
|
813
|
+
|
|
814
|
+
**Storybook:** `Primitives/NonceProvider` · **Vue:** `NonceProvider`
|
package/docs/getting-started.md
CHANGED
|
@@ -30,7 +30,9 @@ does not import that entry never has them in its tree.
|
|
|
30
30
|
| `@nxgt/material/print` | `bun add @react-pdf/renderer@^4.9 react-pdf-tailwind` ([printing](guides/printing.md)) |
|
|
31
31
|
| `@nxgt/material/map` | `bun add leaflet react-leaflet @react-leaflet/core @adamscybot/react-leaflet-component-marker react-geolocated` ([maps](guides/maps.md)) |
|
|
32
32
|
|
|
33
|
-
The tarball contains the built `dist
|
|
33
|
+
The tarball contains the built `dist/` (with KaTeX's fonts in
|
|
34
|
+
`dist/fonts/katex/`, which the compiled stylesheet loads),
|
|
35
|
+
`lib/styles.css`, the component
|
|
34
36
|
stylesheets it imports (`lib/components/**/*.css`), `lib/map/map.css`,
|
|
35
37
|
these docs and `CHANGELOG.md` (from the release after 2.6.1). There is no
|
|
36
38
|
`postinstall` build step.
|
|
@@ -45,6 +47,8 @@ In the app's root stylesheet, in this order:
|
|
|
45
47
|
@source "../node_modules/@nxgt/material/";
|
|
46
48
|
```
|
|
47
49
|
|
|
50
|
+
Toaster, Drawer and InputOTP take their CSS from this stylesheet: without it they render unstyled.
|
|
51
|
+
|
|
48
52
|
- **`styles.css` is source, not compiled CSS.** It holds the Tailwind v4
|
|
49
53
|
`@theme` layer (colours, `--radius`, the `dark` variant, base styles), so it
|
|
50
54
|
runs through *your* Tailwind build. Nothing injects it: import it
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# Content Security Policy
|
|
2
|
+
|
|
3
|
+
This page is for an app that serves a strict Content Security Policy (CSP) and
|
|
4
|
+
wants `@nxgt/material` to run under it: the directives to send, the few
|
|
5
|
+
places the library still needs a relaxation (and which components cause it),
|
|
6
|
+
and the settings that quiet the console. Nothing here is needed on a page
|
|
7
|
+
without a CSP.
|
|
8
|
+
|
|
9
|
+
## Start from this policy
|
|
10
|
+
|
|
11
|
+
```http
|
|
12
|
+
Content-Security-Policy:
|
|
13
|
+
default-src 'self';
|
|
14
|
+
script-src 'nonce-{NONCE}' 'strict-dynamic';
|
|
15
|
+
style-src-elem 'self' 'nonce-{NONCE}';
|
|
16
|
+
style-src-attr 'unsafe-inline';
|
|
17
|
+
font-src 'self';
|
|
18
|
+
img-src 'self' data: blob:;
|
|
19
|
+
worker-src 'self';
|
|
20
|
+
connect-src 'self';
|
|
21
|
+
object-src 'none';
|
|
22
|
+
base-uri 'self'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Directive | Why |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `script-src 'nonce-X' 'strict-dynamic'` | Your entry script carries the nonce; the chunks it loads are trusted through `strict-dynamic`. The library needs no `'unsafe-eval'` once the zod setting below is applied |
|
|
28
|
+
| `style-src-elem 'self' 'nonce-X'` | Your stylesheet and the `<style>` elements that carry the nonce. No component needs `'unsafe-inline'` here, apart from the [known gaps](#known-gaps): see [the `<style>` elements the components inject](#style-elements-the-components-inject) |
|
|
29
|
+
| `style-src-attr 'unsafe-inline'` | The `style="…"` attributes in the server-rendered HTML and those set through `setAttribute('style', …)`. CSP blocks only these two; `el.style.x = …` and `style.setProperty` are not blocked. An attribute cannot carry a nonce, so this is the only way to allow it. It does not allow `<style>` elements or scripts |
|
|
30
|
+
| `font-src 'self'` | The library ships no web font (host Inter yourself: [Inter / the sans font](#inter--the-sans-font)), and KaTeX's fonts are files, never `data:` URIs: see [KaTeX fonts](#katex-fonts) for the one Vite setting the Tailwind route needs |
|
|
31
|
+
| `img-src 'self' data: blob:` | `data:` for the cropper's output, a captured `Camera` photo, a `Markdown` mermaid diagram and the PDF viewer's annotation icons (SVG `data:` images; the icons the components draw are inline `<svg>` elements and need none); `blob:` for previews of a file the user picked. Drop what you do not use |
|
|
32
|
+
| `worker-src 'self'` | The pdf.js worker of `PdfReader` is a same-origin file your bundler emits (Monaco: see below) |
|
|
33
|
+
| `connect-src 'self'` | `PdfReader` fetches the PDF from its `url`: add that origin when it is not yours |
|
|
34
|
+
|
|
35
|
+
Components outside this table need nothing more. Measured per component:
|
|
36
|
+
|
|
37
|
+
| Component | Extra directive | Status |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `PdfReader`, `PdfSidebar`, `PdfPageList`, `PdfThumbnailSidebar` | `worker-src 'self'` (the worker is a bundled asset), `connect-src` for the PDF's origin | worker URL read from the code; fonts: see [gaps](#known-gaps) |
|
|
40
|
+
| `CodeEditor`, `CodeDiff` (Monaco) | `@monaco-editor/react` loads Monaco from `https://cdn.jsdelivr.net/npm/monaco-editor@…/min/vs` by default: that host in `style-src-elem` (Monaco's CSS) and `font-src` (its icon font), plus `worker-src blob:` | not yet verified in a browser. Safe default: allow `https://cdn.jsdelivr.net` in `script-src`, `style-src-elem` and `font-src`, and `worker-src blob:`; or stay on `'self'` by [serving Monaco yourself](#monaco-on-self) |
|
|
41
|
+
| `Markdown` with a `mermaid` block | mermaid is imported lazily and runs with `securityLevel: 'strict'` | whether mermaid needs `'unsafe-eval'` is not yet verified. Safe default: keep `'unsafe-eval'` out and report any violation you see; its SVG carries a `<style>` element, see [gaps](#known-gaps) |
|
|
42
|
+
| `Camera` | none: `getUserMedia` is not governed by CSP; the captured photo is a `data:` image | `media-src` for the live preview is not yet verified. Safe default: `media-src 'self' blob:` (a `srcObject` stream usually needs nothing) |
|
|
43
|
+
|
|
44
|
+
### Monaco on `'self'`
|
|
45
|
+
|
|
46
|
+
Copy Monaco's `min/vs` folder to your public files (for example `/monaco/vs`)
|
|
47
|
+
and point the loader at it, before the first editor mounts:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { loader } from '@monaco-editor/react';
|
|
51
|
+
|
|
52
|
+
loader.config({ paths: { vs: '/monaco/vs' } });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Packages the app imports itself
|
|
56
|
+
|
|
57
|
+
`zod` and `@monaco-editor/react` are dependencies of the library, and
|
|
58
|
+
neither is bundled: the library imports them at runtime. A strict package
|
|
59
|
+
manager (pnpm) does not let the app import a package it does not list, so
|
|
60
|
+
install what you import: `bun add zod @monaco-editor/react`. Keep your zod
|
|
61
|
+
inside `^4.4.3` (zod 4.4 or later shares its configuration through
|
|
62
|
+
`globalThis`) and the app and the library share one copy.
|
|
63
|
+
|
|
64
|
+
## NonceProvider
|
|
65
|
+
|
|
66
|
+
Render `NonceProvider` once, at the root, with this request's nonce, on the
|
|
67
|
+
server and on the client. Every component below it takes the nonce: the
|
|
68
|
+
`<style>` elements of `ScrollArea` and `Select`, the stylesheet tiptap
|
|
69
|
+
injects for `RichTextEditor` (its `injectNonce` option, unless you pass your
|
|
70
|
+
own), and, on the client, the scroll lock of every modal.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
// app.tsx
|
|
74
|
+
import { NonceProvider } from '@nxgt/material';
|
|
75
|
+
import type { ReactNode } from 'react';
|
|
76
|
+
|
|
77
|
+
export function App({ nonce, children }: { nonce?: string; children: ReactNode }) {
|
|
78
|
+
return <NonceProvider nonce={nonce}>{children}</NonceProvider>;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
On the server, the nonce goes down through React context only. On the
|
|
83
|
+
client, `NonceProvider` also keeps it for the code that runs outside React
|
|
84
|
+
(`claimCspNonce`, in an insertion effect, so never on a server: one
|
|
85
|
+
request's nonce never reaches another's). With nested providers, the
|
|
86
|
+
innermost one mounted is the page's nonce, and it falls back to the outer
|
|
87
|
+
one's when it unmounts. The server entry is under [SSR](#ssr). On the
|
|
88
|
+
client, read the nonce the server wrote into the page and render the same
|
|
89
|
+
tree:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
// entry.client.tsx
|
|
93
|
+
import { getCspNonce } from '@nxgt/material';
|
|
94
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
95
|
+
import { App } from './app';
|
|
96
|
+
import { Routes } from './routes';
|
|
97
|
+
|
|
98
|
+
hydrateRoot(document, <App nonce={getCspNonce()}><Routes /></App>);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`getCspNonce()` reads, in order: the value given to `setCspNonce`,
|
|
102
|
+
`<meta property="csp-nonce" nonce="…">` (what Vite's `html.cspNonce` writes)
|
|
103
|
+
or `<meta name="csp-nonce" content="…">`, the first `<script nonce>` or
|
|
104
|
+
`<style nonce>` of the page, then `__webpack_nonce__`. A browser hides a
|
|
105
|
+
`nonce` attribute's value once the page is parsed; `getCspNonce()` reads the
|
|
106
|
+
`nonce` property, which keeps it.
|
|
107
|
+
|
|
108
|
+
A component of your own that renders a `<style>` takes the nonce with
|
|
109
|
+
`useCspNonce()`:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { useCspNonce } from '@nxgt/material/hooks';
|
|
113
|
+
|
|
114
|
+
export function Keyframes({ css }: { css: string }) {
|
|
115
|
+
return <style nonce={useCspNonce()}>{css}</style>;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`useCspNonce()` returns the provider's nonce, or `undefined` outside one. It
|
|
120
|
+
never reads the page while rendering, so the server and the client render the
|
|
121
|
+
same attribute and hydration does not mismatch. Code that runs in an effect
|
|
122
|
+
(an imperative widget creating its own `<style>`) can call `getCspNonce()`
|
|
123
|
+
instead.
|
|
124
|
+
|
|
125
|
+
## `<style>` elements the components inject
|
|
126
|
+
|
|
127
|
+
`style-src-elem 'self' 'nonce-X'` is enough for every component below: your
|
|
128
|
+
stylesheet carries the CSS the dependencies used to inject, and under a
|
|
129
|
+
`NonceProvider` the few `<style>` elements left carry the nonce. No route
|
|
130
|
+
needs `'unsafe-inline'` in `style-src-elem` for them; mermaid and pdf.js's
|
|
131
|
+
font fallback are the [known gaps](#known-gaps).
|
|
132
|
+
|
|
133
|
+
| Component | What it needs | How |
|
|
134
|
+
| --- | --- | --- |
|
|
135
|
+
| `Toaster` (sonner), `Drawer` (vaul), `InputOTP` (input-otp) | nothing | their CSS is in the stylesheet you import (`styles.css`, or `dist/material.css`); the library build removes the `<style>` they would inject |
|
|
136
|
+
| `ImageCropper` (react-easy-crop) | nothing | its CSS is in the stylesheet; it renders with `disableAutomaticStylesInjection` |
|
|
137
|
+
| `Spinner` (variant `4`), `PdfReader` (search highlights, night mode) | nothing | their rules are in the stylesheet (`spinner.css`, `pdf-viewer.css`) |
|
|
138
|
+
| `ScrollArea`, `Select` (Radix) | the nonce | Radix renders a `<style>` element with `NonceProvider`'s nonce: wrap the app in [`NonceProvider`](#nonceprovider) |
|
|
139
|
+
| `RichTextEditor` (tiptap) | the nonce | tiptap, on mount, with `NonceProvider`'s nonce as `injectNonce` |
|
|
140
|
+
| `Dialog`, `AlertDialog`, `Sheet`, `ConfirmationDialog`, … (Radix scroll lock) | the nonce | react-style-singleton, when a modal opens: see below |
|
|
141
|
+
|
|
142
|
+
Your app must import the library's stylesheet for the first three rows, as
|
|
143
|
+
the [README](../../README.md) already asks: `@import
|
|
144
|
+
"@nxgt/material/styles.css"` in a Tailwind app, or `dist/material.css`. An
|
|
145
|
+
app that imports only the JavaScript renders toasts and drawers unstyled.
|
|
146
|
+
|
|
147
|
+
The library's CI checks this on every build: it fails when the built
|
|
148
|
+
JavaScript creates a `<style>`, or calls `insertRule`, `Function` or `eval`,
|
|
149
|
+
outside a short list, each entry with its reason (the scroll lock, tiptap,
|
|
150
|
+
the gaps below). It then imports every built entry and fails if importing
|
|
151
|
+
calls `Function`: a schema built at import would. A story renders a
|
|
152
|
+
`Toaster` with a toast, an open `Drawer` and `Dialog`, an `InputOTP`,
|
|
153
|
+
`Spinner` and `ImageCropper` under this policy and counts the violations:
|
|
154
|
+
none. Another adds `script-src 'self' 'nonce-X'`, no `'unsafe-eval'`, calls
|
|
155
|
+
`z.config({ jitless: true })`, then renders `AttributeForm`, submits it and
|
|
156
|
+
parses with `openingHoursSchema()`: no `script-src` violation.
|
|
157
|
+
|
|
158
|
+
### Allowed by the check, nothing for you to do
|
|
159
|
+
|
|
160
|
+
Code that stays in the build and the CSP never sees, or only in a case the
|
|
161
|
+
library avoids:
|
|
162
|
+
|
|
163
|
+
| Where | What it is | Why it is no violation |
|
|
164
|
+
| --- | --- | --- |
|
|
165
|
+
| `Resizable` (react-resizable-panels) | `insertRule` | on a constructed stylesheet (`adoptedStyleSheets`), which CSP does not govern |
|
|
166
|
+
| `ImageCropper` (react-easy-crop) | `createElement('style')` | the branch is skipped: `disableAutomaticStylesInjection` is passed |
|
|
167
|
+
| Nivo charts (react-virtualized-auto-sizer) | `<style>` of a resize detector | only in a browser without `ResizeObserver`, which no supported browser is |
|
|
168
|
+
| `PdfReader` (pdf.js) | the `isEvalSupported` probe, `new Function("")` | unreachable: `PdfReader` passes `isEvalSupported: false`. If you call `pdfjs.getDocument` yourself, pass it too |
|
|
169
|
+
| lodash (a dependency of some chart code) | `Function("return this")()` | only when neither `global` nor `self` exists: never in a browser |
|
|
170
|
+
| `InputOTP` (input-otp) | a `<style>` inside `<noscript>` | rendered only with scripts off, where no CSP report is sent |
|
|
171
|
+
|
|
172
|
+
### The scroll lock of modals
|
|
173
|
+
|
|
174
|
+
Radix locks the page scroll with `react-style-singleton`, which adds a
|
|
175
|
+
`<style>` to `<head>` when a modal opens and takes its nonce from
|
|
176
|
+
`get-nonce`. The library bundles it pointed at its own nonce store
|
|
177
|
+
(`@nxgt/material-core/csp-nonce`), so it reads what `NonceProvider` keeps,
|
|
178
|
+
else what `getCspNonce()` finds in the page (the meta tag, a
|
|
179
|
+
`<script nonce>`). Nothing else to set: the `__webpack_nonce__` assignment
|
|
180
|
+
earlier versions of this guide asked for is no longer needed.
|
|
181
|
+
|
|
182
|
+
A page without a `NonceProvider` (one that renders no `ScrollArea`, `Select`
|
|
183
|
+
or `RichTextEditor`) needs nothing either, as long as the server writes the
|
|
184
|
+
nonce into the page (the meta tag, or its `<script nonce>`): the scroll lock
|
|
185
|
+
reads it through `getCspNonce()`. Call `setCspNonce(nonce)` on the client only
|
|
186
|
+
when the page carries it nowhere. `setCspNonce` does nothing on a server (no
|
|
187
|
+
`document`), so a request's nonce never lands in a module global there: see
|
|
188
|
+
[SSR](#ssr).
|
|
189
|
+
|
|
190
|
+
### Known gaps
|
|
191
|
+
|
|
192
|
+
Not fixable by configuration yet, and still reported by a strict policy:
|
|
193
|
+
|
|
194
|
+
- the `<style>` element inside mermaid's SVG, and the one cytoscape adds to
|
|
195
|
+
`<head>` for mermaid's mindmap and architecture diagrams;
|
|
196
|
+
- the font fallback of pdf.js, when a PDF font is not embedded;
|
|
197
|
+
- this repository's Storybook, or an app that imports react-use's barrel
|
|
198
|
+
itself on a dev server: react-use served as one module, whose `useCss`
|
|
199
|
+
makes nano-css create three `<style>` elements at import. Our `dist`
|
|
200
|
+
bundles the hooks it uses and never imports react-use or nano-css, so
|
|
201
|
+
an app that only uses `@nxgt/material` does not report it.
|
|
202
|
+
|
|
203
|
+
## zod and `'unsafe-eval'`
|
|
204
|
+
|
|
205
|
+
Building a `z.object()` makes zod probe `new Function` once, to choose
|
|
206
|
+
between its compiled parser and its interpreter. Without `'unsafe-eval'` the
|
|
207
|
+
browser reports a violation. The error is caught, so nothing breaks, but the
|
|
208
|
+
report is sent. Switch zod to its interpreter **before the first schema is
|
|
209
|
+
built**, in a module the app imports first:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
// zod-config.ts
|
|
213
|
+
import { z } from 'zod';
|
|
214
|
+
|
|
215
|
+
z.config({ jitless: true });
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
// entry.client.tsx
|
|
220
|
+
import './zod-config'; // first
|
|
221
|
+
import './app';
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The library builds no schema while it loads, so that is all it takes:
|
|
225
|
+
|
|
226
|
+
- **zod is not bundled.** `@nxgt/material` imports the zod your app has (a
|
|
227
|
+
dependency, `^4.4.3`), so your `z.config` is the one its schemas see. zod
|
|
228
|
+
4.4 or later shares its configuration through `globalThis`, so a
|
|
229
|
+
second copy of zod 4 that a package manager nested still sees it; that is
|
|
230
|
+
why the range is `^4.4.3`.
|
|
231
|
+
- **Its schemas are factories**, built on the first call and the same schema
|
|
232
|
+
after: `openingHoursSchema()`, and the attribute form's, which
|
|
233
|
+
`AttributeForm` builds when it renders. A component that validates builds
|
|
234
|
+
its schema when it first renders, never at import.
|
|
235
|
+
|
|
236
|
+
| Deprecated constant (removed in 3.0) | Use |
|
|
237
|
+
| --- | --- |
|
|
238
|
+
| `OpeningHoursSchema` | `openingHoursSchema()` |
|
|
239
|
+
|
|
240
|
+
The deprecated constants still work: each is built on its first use and
|
|
241
|
+
behaves as the schema its factory returns (`.parse`, `.safeParse`, `.shape`,
|
|
242
|
+
`.extend`, `instanceof z.ZodObject`). It is not the same object, so do not
|
|
243
|
+
compare it with `===` or register it: a `.meta()` or `registry.add()` on
|
|
244
|
+
the stand-in is not seen through the factory's schema.
|
|
245
|
+
|
|
246
|
+
## KaTeX fonts
|
|
247
|
+
|
|
248
|
+
`Markdown` renders formulas with KaTeX, which needs its fonts
|
|
249
|
+
(`KaTeX_*.woff2`). They are files on both routes, so `font-src 'self'` allows
|
|
250
|
+
them, and a browser downloads a font only when a formula draws a glyph from
|
|
251
|
+
it: a page with no math downloads none.
|
|
252
|
+
|
|
253
|
+
- **Compiled route** (`dist/material.css`): the `@font-face` rules point at
|
|
254
|
+
`dist/fonts/katex/*.woff2`, shipped beside the stylesheet, through a
|
|
255
|
+
relative `url()`. A bundler emits them as files of your build; an app that
|
|
256
|
+
serves `dist/` as it is must serve `dist/fonts/` with it. Copy only the
|
|
257
|
+
stylesheet and math falls back to the system fonts
|
|
258
|
+
([troubleshooting](../troubleshooting.md#math-renders-in-the-system-fonts)).
|
|
259
|
+
Earlier versions inlined the 60 fonts as `data:` URIs (1.4 MB of the
|
|
260
|
+
stylesheet), and `font-src` needed `data:`.
|
|
261
|
+
- **Tailwind route** (`@import "@nxgt/material/styles.css"`): your bundler emits
|
|
262
|
+
KaTeX's fonts as files, but Vite inlines any asset under
|
|
263
|
+
`assetsInlineLimit` (4096 bytes) as a `data:` URI, and
|
|
264
|
+
`KaTeX_Size3-Regular.woff2` is 3.6 KB, so it is inlined and `font-src 'self'`
|
|
265
|
+
blocks it. Never inline fonts, with `cspAssetsInlineLimit` from
|
|
266
|
+
`@nxgt/material/vite` (the same helper as [Inter](#inter--the-sans-font)):
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
// vite.config.ts
|
|
270
|
+
import { cspAssetsInlineLimit } from '@nxgt/material/vite';
|
|
271
|
+
import { defineConfig } from 'vite';
|
|
272
|
+
|
|
273
|
+
export default defineConfig({ build: { assetsInlineLimit: cspAssetsInlineLimit } });
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
It returns `false` for a font and leaves every other asset to Vite's
|
|
277
|
+
default. (`assetsInlineLimit: 0` also works, and inlines nothing at all.)
|
|
278
|
+
The compiled route through Vite needs the same setting, for the same file.
|
|
279
|
+
|
|
280
|
+
The library's CI fails when a stylesheet in `dist/` carries a `data:` font.
|
|
281
|
+
|
|
282
|
+
## Inter / the sans font
|
|
283
|
+
|
|
284
|
+
The library ships no web font. Its sans text uses the Tailwind token
|
|
285
|
+
`--font-sans`, which the theme does not set, so it is Tailwind's system stack
|
|
286
|
+
(`ui-sans-serif, system-ui, sans-serif`, then the emoji fonts). Many apps name
|
|
287
|
+
Inter there and load it from Google Fonts or rsms.me; both are blocked by
|
|
288
|
+
`font-src 'self'` and `style-src-elem 'self' 'nonce-X'`, because the page then
|
|
289
|
+
asks for another origin's stylesheet and fonts. Host Inter yourself instead:
|
|
290
|
+
|
|
291
|
+
```sh
|
|
292
|
+
bun add @fontsource-variable/inter
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
```css
|
|
296
|
+
/* the app's root stylesheet */
|
|
297
|
+
@import "tailwindcss";
|
|
298
|
+
@import "@nxgt/material/styles.css";
|
|
299
|
+
@import "@fontsource-variable/inter";
|
|
300
|
+
@source "../node_modules/@nxgt/material/";
|
|
301
|
+
|
|
302
|
+
@theme {
|
|
303
|
+
--font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
(`@fontsource-variable/inter` is the variable font, family `Inter Variable`.
|
|
308
|
+
For fixed weights install `@fontsource/inter` instead, import
|
|
309
|
+
`@fontsource/inter/400.css` and the weights you use, and name the family
|
|
310
|
+
`"Inter"`.) A JavaScript entry works as well: `import '@fontsource-variable/inter';`.
|
|
311
|
+
|
|
312
|
+
Set it in the root `@theme`, like the colours: `@theme` entries are resolved
|
|
313
|
+
on the root, so a value set on an inner element does not change what the
|
|
314
|
+
rest of the page inherits. See [theming](theming.md#fonts).
|
|
315
|
+
|
|
316
|
+
- **Under the policy.** The package's stylesheet is bundled into yours (no
|
|
317
|
+
second origin, so `style-src-elem 'self'` holds) and its `@font-face` rules
|
|
318
|
+
point at `.woff2` files that your bundler emits into the build. They load
|
|
319
|
+
from your own origin, so `font-src 'self'` allows them. A browser downloads
|
|
320
|
+
only the subsets a page draws: Latin first, the other scripts on demand.
|
|
321
|
+
- **Vite and `assetsInlineLimit`.** Vite inlines an asset under 4096 bytes as
|
|
322
|
+
a `data:` URI, which `font-src 'self'` blocks. Fontsource's files are
|
|
323
|
+
larger, so they are emitted as files without any setting. A small subset
|
|
324
|
+
file can still fall under the limit, and the KaTeX route needs it anyway:
|
|
325
|
+
keep the one setting that never inlines a font, `cspAssetsInlineLimit`
|
|
326
|
+
from `@nxgt/material/vite`:
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
// vite.config.ts
|
|
330
|
+
import { cspAssetsInlineLimit } from '@nxgt/material/vite';
|
|
331
|
+
import { defineConfig } from 'vite';
|
|
332
|
+
|
|
333
|
+
export default defineConfig({ build: { assetsInlineLimit: cspAssetsInlineLimit } });
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
([KaTeX fonts](#katex-fonts) uses the same setting.)
|
|
337
|
+
- **If Inter is not loaded.** Nothing breaks and no violation is reported: the
|
|
338
|
+
browser skips `"Inter Variable"`, which names no installed font, and draws
|
|
339
|
+
the next entry of the stack, the system sans. Only the look changes. This is
|
|
340
|
+
also what you get when you keep the token at its default.
|
|
341
|
+
- **Troubleshooting.** A request to `fonts.googleapis.com` or
|
|
342
|
+
`fonts.gstatic.com` is not ours: see
|
|
343
|
+
[the font refused for another host](../troubleshooting.md#refused-to-load-the-font-httpsfontsgstaticcom-because-it-violates-the-following-content-security-policy-directive-font-src-self).
|
|
344
|
+
|
|
345
|
+
## SSR
|
|
346
|
+
|
|
347
|
+
Generate the nonce **per request**, put it in the header and hand it to the
|
|
348
|
+
renderer. React's stream takes it as an option, and stamps the scripts it
|
|
349
|
+
emits:
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
// entry.server.tsx
|
|
353
|
+
import { randomBytes } from 'node:crypto';
|
|
354
|
+
import { NonceProvider } from '@nxgt/material';
|
|
355
|
+
import { renderToPipeableStream } from 'react-dom/server';
|
|
356
|
+
import { PassThrough } from 'node:stream';
|
|
357
|
+
import type { ReactNode } from 'react';
|
|
358
|
+
|
|
359
|
+
export function render(app: ReactNode, respond: (html: PassThrough, csp: string) => void) {
|
|
360
|
+
const nonce = randomBytes(16).toString('base64');
|
|
361
|
+
const csp = [
|
|
362
|
+
`script-src 'nonce-${nonce}' 'strict-dynamic'`,
|
|
363
|
+
`style-src-elem 'self' 'nonce-${nonce}'`,
|
|
364
|
+
`style-src-attr 'unsafe-inline'`,
|
|
365
|
+
`font-src 'self'`,
|
|
366
|
+
].join('; ');
|
|
367
|
+
|
|
368
|
+
const body = new PassThrough();
|
|
369
|
+
const { pipe } = renderToPipeableStream(<NonceProvider nonce={nonce}>{app}</NonceProvider>, {
|
|
370
|
+
nonce,
|
|
371
|
+
onShellReady() {
|
|
372
|
+
respond(body, csp);
|
|
373
|
+
pipe(body);
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
The stream stamps its own scripts with `nonce`; `NonceProvider` hands the same
|
|
380
|
+
value to the components. Also give the nonce to your `<script>` and
|
|
381
|
+
inline-style tags (`<Scripts nonce>` in React Router) and to a
|
|
382
|
+
`meta[name="csp-nonce"]` tag, which `getCspNonce()` reads on the client.
|
|
383
|
+
|
|
384
|
+
**Never keep the nonce in a module-level variable on the server.** Two
|
|
385
|
+
requests handled at once would overwrite each other, and one page would carry
|
|
386
|
+
the other's nonce. Pass it down the request: as an argument, through
|
|
387
|
+
`NonceProvider` (React context) or `AsyncLocalStorage`. `setCspNonce` is a
|
|
388
|
+
no-op there for that reason.
|
|
389
|
+
|
|
390
|
+
See also the [SSR guide](ssr.md).
|
|
391
|
+
|
|
392
|
+
## Status
|
|
393
|
+
|
|
394
|
+
Nothing is left to ship for the policy. What was done:
|
|
395
|
+
|
|
396
|
+
- the CSS the dependencies injected at runtime (`Toaster`, `Drawer`,
|
|
397
|
+
`InputOTP`, `ImageCropper`) and the `<style>` of `Spinner` and `PdfReader`
|
|
398
|
+
are in the stylesheet;
|
|
399
|
+
- the few `<style>` elements left carry the nonce (`NonceProvider`);
|
|
400
|
+
- KaTeX's fonts ship as files, so `font-src 'self'` needs no `data:`, and
|
|
401
|
+
Inter is documented as a self-hosted font;
|
|
402
|
+
- zod is an external dependency whose schemas are built on first use (see
|
|
403
|
+
above).
|
|
404
|
+
|
|
405
|
+
What remains are the [known gaps](#known-gaps) and the rows marked "not yet
|
|
406
|
+
verified" in the tables above; each is waiting on a dependency, not on this
|
|
407
|
+
library. Progress is on the [roadmap](../roadmap.md). A report you cannot match
|
|
408
|
+
to an entry is in [troubleshooting](../troubleshooting.md#content-security-policy).
|
package/docs/guides/helpers.md
CHANGED
|
@@ -14,6 +14,7 @@ differences are listed under each table.
|
|
|
14
14
|
| --- | --- |
|
|
15
15
|
| `cn(...classes)` | `clsx` + `tailwind-merge`: joins conditional classes, last Tailwind utility wins. |
|
|
16
16
|
| `clientOnly(Component)` | Wraps a component so it renders nothing on the server and mounts only on the client. See [SSR](ssr.md). |
|
|
17
|
+
| `getCspNonce(doc?)`, `setCspNonce(nonce)` | The page's Content Security Policy nonce, from `@nxgt/material-core/csp-nonce`: `getCspNonce` reads what `setCspNonce` kept, else the `csp-nonce` meta tag, a `<script nonce>` or `<style nonce>`, else `__webpack_nonce__`; `undefined` on a server, where `setCspNonce` does nothing. See [CSP](csp.md#nonceprovider). |
|
|
17
18
|
| `renderSlot(slot, context)` | Resolves a `ContentSlot`: a `ReactNode` or a `(context) => ReactNode`. |
|
|
18
19
|
| `DATE_UTILS` | `format(iso, 'PP', locale?)` (`'-'` when empty), `parseFromTimestring('HH:mm')`, `parseFromISOString`, `formatDateRangeFilter`. |
|
|
19
20
|
| `formatDateRangeFilter(range)` | Turns a day-picker range into `{ from, to }` ISO strings at start / end of day; `undefined` when empty. |
|
|
@@ -119,6 +120,7 @@ Vue: the Redux helpers are Pinia store factories: `defineLocalStore`
|
|
|
119
120
|
| `useTheme()` | `{ theme, preference, updateTheme }`: light / dark / system, stored in `localStorage`, applied as the `dark` class. See [theming](theming.md#usetheme). |
|
|
120
121
|
| `resolveTheme(stored, prefersDark)`, `applyResolvedTheme(theme)` | The pure theme logic behind `useTheme`, for a boot script. |
|
|
121
122
|
| `useIsClient()` | `false` on the server and first render, `true` after mount: a hydration guard. |
|
|
123
|
+
| `useCspNonce()` | The nonce of the nearest `NonceProvider`, for a `<style>` of your own (`<style nonce={useCspNonce()}>`); `undefined` outside one, on the server and on the client alike. See [CSP](csp.md#nonceprovider). |
|
|
122
124
|
| `useIsMobile()` | `true` below 768 px (`ResponsiveButton` no longer uses it: it collapses with CSS). `false` on the server and while hydrating, so SSR HTML is the desktop layout; a phone switches right after. |
|
|
123
125
|
| `useHeadingSpy(ids, onActive, options?)` | IntersectionObserver over heading ids; reports the topmost visible one. |
|
|
124
126
|
| `useLocalizedDate()` | `{ language, locale, format }`: `DATE_UTILS.format` bound to the `I18nProvider` language (`en` without a provider). |
|
package/docs/guides/i18n.md
CHANGED
|
@@ -449,6 +449,8 @@ and dates shown to a user, `useFormatters()` (`formatAmount`, `formatDate`)
|
|
|
449
449
|
prints what the PDF builders print, with the decimals `CurrencyField` shows. See
|
|
450
450
|
[helpers](helpers.md#nxgtmaterialhooks).
|
|
451
451
|
|
|
452
|
+
The trigger of a `DateField`, `DateRangeField` or `TimeField` follows the same language (a `DateField`'s own `locale` wins): `01/09/2028` on a French page, `09/01/2028` in English.
|
|
453
|
+
|
|
452
454
|
## Reference
|
|
453
455
|
|
|
454
456
|
Everything `@nxgt/material/i18n` exports is listed in
|
package/docs/guides/theming.md
CHANGED
|
@@ -68,6 +68,27 @@ Some colours are derived rather than tokens of their own:
|
|
|
68
68
|
`bg-primary-container`, `bg-secondary-container`, `bg-accent-container`,
|
|
69
69
|
`bg-error-container`. They follow the token they are computed from.
|
|
70
70
|
|
|
71
|
+
## Fonts
|
|
72
|
+
|
|
73
|
+
The library ships no font. Text uses Tailwind's `--font-sans`, which the theme
|
|
74
|
+
does not set, so the default is the system sans stack
|
|
75
|
+
(`ui-sans-serif, system-ui, sans-serif`, then the emoji fonts); there is
|
|
76
|
+
nothing to download. To use Inter, self-host it and set the token in the root
|
|
77
|
+
`@theme`:
|
|
78
|
+
|
|
79
|
+
```css
|
|
80
|
+
@import "@fontsource-variable/inter";
|
|
81
|
+
|
|
82
|
+
@theme {
|
|
83
|
+
--font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
If Inter is not loaded, the browser draws the next font of the stack. Loading it
|
|
88
|
+
from Google Fonts or rsms.me is blocked by a strict CSP: the install, the
|
|
89
|
+
bundler settings and the policy are in
|
|
90
|
+
[Inter / the sans font](csp.md#inter--the-sans-font).
|
|
91
|
+
|
|
71
92
|
## Overriding tokens
|
|
72
93
|
|
|
73
94
|
Redefine the variables **after** the import, for light and dark separately:
|
|
@@ -374,6 +395,12 @@ other bundlers that support package `exports` in CSS. The file is not exported
|
|
|
374
395
|
under a path of its own, so `import '@nxgt/material/dist/material.css'` does
|
|
375
396
|
not resolve.
|
|
376
397
|
|
|
398
|
+
The compiled stylesheet loads KaTeX's fonts (`Markdown` maths) from
|
|
399
|
+
`dist/fonts/katex/`, beside it: your bundler emits them as files (Vite inlines the 3.6 KB `KaTeX_Size3` font unless `assetsInlineLimit` is set: see the CSP guide's KaTeX fonts section), and a
|
|
400
|
+
browser fetches one only when a formula needs it. Serving `dist/material.css`
|
|
401
|
+
without `dist/fonts/` makes math fall back to the system fonts
|
|
402
|
+
([troubleshooting](../troubleshooting.md#math-renders-in-the-system-fonts)).
|
|
403
|
+
|
|
377
404
|
Tokens can be overridden the same way, after the import. What you cannot do
|
|
378
405
|
without Tailwind is use the token utilities (`bg-primary`…) in your own
|
|
379
406
|
markup beyond those the library already uses.
|
package/docs/roadmap.md
CHANGED
|
@@ -6,6 +6,21 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
6
6
|
|
|
7
7
|
## Now
|
|
8
8
|
|
|
9
|
+
- **A strict Content Security Policy** — a page that sends `script-src` with
|
|
10
|
+
a nonce and no `'unsafe-eval'`, and `style-src-elem` with a nonce and no
|
|
11
|
+
`'unsafe-inline'`, runs every component with no violation report: a
|
|
12
|
+
`NonceProvider` forwards the nonce to each component, the CSS the
|
|
13
|
+
dependencies inject moves into the stylesheet, zod becomes external with
|
|
14
|
+
schemas built lazily, and the KaTeX fonts ship as files. Done so far:
|
|
15
|
+
KaTeX's fonts are files beside the compiled stylesheet instead of 1.4 MB of
|
|
16
|
+
`data:` URIs inside it, so `font-src 'self'` needs no `data:`;
|
|
17
|
+
`NonceProvider` takes the nonce to `ScrollArea`, `Select`,
|
|
18
|
+
`RichTextEditor` and the scroll lock of modals; `Toaster`, `Drawer`,
|
|
19
|
+
`InputOTP`, `ImageCropper`, `Spinner` and `PdfReader` inject no `<style>`,
|
|
20
|
+
their CSS is in the stylesheet; zod is no longer bundled and no schema is
|
|
21
|
+
built at import, so the app's `z.config({ jitless: true })` keeps zod from
|
|
22
|
+
probing `new Function`. Today the
|
|
23
|
+
[Content Security Policy guide](guides/csp.md) lists what is left to relax.
|
|
9
24
|
- **Empty and error states that look designed** — `EmptyState` and a new
|
|
10
25
|
`ErrorState` in three sizes (`default`, `compact`, `inline`), with in-house
|
|
11
26
|
inline SVG illustrations or a slot for your own. `ErrorState` is a
|
|
@@ -84,7 +99,8 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
84
99
|
`ConfirmationDialog` and the attribute editors, so an app without
|
|
85
100
|
react-hook-form never installs it. `ContactItem` and `OpeningHoursItem`,
|
|
86
101
|
deprecated in 2.x, are removed then: use `ContactsFormField` and
|
|
87
|
-
`OpeningHoursFormField`.
|
|
102
|
+
`OpeningHoursFormField`. So is the `OpeningHoursSchema` constant: call
|
|
103
|
+
`openingHoursSchema()`.
|
|
88
104
|
The `Switch` row's misspelt `data-filed` attribute goes too: select
|
|
89
105
|
`[data-checked="true"]` instead. `DateField` / `DateRangeField` stop
|
|
90
106
|
accepting a day rule as `disabled`: pass it as `disabledDays`.
|