@nxgt/material-vue 0.42.0 → 0.43.1
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 +117 -0
- package/README.md +18 -5
- package/dist/{Button-CWmzvv9p.js → Button-CEvi_i4g.js} +4 -4
- package/dist/{HoverCardTrigger-C5AbAyHE.js → HoverCardTrigger-B7m5Zvj1.js} +69 -62
- package/dist/backgrounds/backgrounds.css +8 -0
- package/dist/backgrounds/css/base.css +169 -0
- package/dist/backgrounds/css/gradient.css +53 -0
- package/dist/backgrounds/css/grain.css +19 -0
- package/dist/backgrounds/css/motion.css +30 -0
- package/dist/backgrounds.js +111 -0
- package/dist/{camera-C-9slBUV.js → camera-DP3Qfhhc.js} +4 -4
- package/dist/{chunk-7M6MHVWA-wgE1MsQ9.js → chunk-7M6MHVWA-DzYWbKT9.js} +1 -1
- package/dist/{chunk-GNY47TPC-BByCj-tp.js → chunk-GNY47TPC-D4sl5ZSh.js} +1 -1
- package/dist/{classDiagram-v2-NBCMYWYE-LDc3yyTu.js → classDiagram-v2-NBCMYWYE-BtmPeDVI.js} +1 -1
- package/dist/components/base.css +5 -2
- package/dist/components/components.css +5 -0
- package/dist/components/theme-tokens.css +21 -0
- package/dist/{components-DX4EycMi.js → components-zm5JC2dA.js} +14077 -13577
- package/dist/components.js +8 -8
- package/dist/composables.js +3 -3
- package/dist/{dagre-6A5THRUB-CCigPI-W.js → dagre-6A5THRUB-CmmqjQ01.js} +1 -1
- package/dist/{diagram-22UHCM2B-vav-BOss.js → diagram-22UHCM2B-BYL1soMk.js} +1 -1
- package/dist/dnd.js +1 -1
- package/dist/{erDiagram-OPXOYQCR-BV3UGLoQ.js → erDiagram-OPXOYQCR-DhNOupcl.js} +1 -1
- package/dist/{flowDiagram-KWPJA3E3-B3AfwLQk.js → flowDiagram-KWPJA3E3-_S6NO9vf.js} +2 -2
- package/dist/{form-scope-CN68PTXc.js → form-scope-XQGlh8m3.js} +1 -1
- package/dist/{i18n-provider-DmZSTBnp.js → i18n-provider-tL27-a8y.js} +1 -1
- package/dist/i18n.js +26 -9
- package/dist/{icon-button-DErh1aJS.js → icon-button-BNA3fXM2.js} +4 -4
- package/dist/lib/backgrounds/background/background.vue.d.ts +22 -0
- package/dist/lib/backgrounds/background/index.d.ts +2 -0
- package/dist/lib/backgrounds/background/partials/background-layers.vue.d.ts +9 -0
- package/dist/lib/backgrounds/background/partials/scene-svg.d.ts +10 -0
- package/dist/lib/backgrounds/background/types.d.ts +24 -0
- package/dist/lib/backgrounds/index.d.ts +13 -0
- package/dist/lib/components/ui/actions/button/button-tones.d.ts +3 -3
- package/dist/lib/components/ui/actions/responsive-button/responsive-button.vue.d.ts +3 -3
- package/dist/lib/components/ui/collections/data-table/data-table-context.d.ts +4 -0
- package/dist/lib/components/ui/collections/data-table/data-table.vue.d.ts +8 -0
- package/dist/lib/components/ui/collections/data-table/partials/data-table-body.vue.d.ts +8 -0
- package/dist/lib/components/ui/collections/data-table/partials/data-table-empty.vue.d.ts +5 -1
- package/dist/lib/components/ui/collections/data-table/partials/data-table-toolbar.d.ts +16 -3
- package/dist/lib/components/ui/collections/data-table/select-checkbox.d.ts +36 -0
- package/dist/lib/components/ui/collections/data-table/types.d.ts +15 -0
- package/dist/lib/components/ui/collections/data-table/use-data-table.d.ts +2 -0
- package/dist/lib/components/ui/feedback/circular-progress/circular-progress.vue.d.ts +1 -1
- package/dist/lib/components/ui/feedback/empty-state/empty-state.vue.d.ts +21 -8
- package/dist/lib/components/ui/feedback/empty-state/index.d.ts +1 -1
- package/dist/lib/components/ui/feedback/error-state/error-state.vue.d.ts +54 -0
- package/dist/lib/components/ui/feedback/error-state/index.d.ts +3 -0
- package/dist/lib/components/ui/feedback/error-state/partials/error-state-details.vue.d.ts +7 -0
- package/dist/lib/components/ui/feedback/index.d.ts +1 -0
- package/dist/lib/components/ui/feedback/state-frame/state-frame.vue.d.ts +36 -0
- package/dist/lib/components/ui/feedback/state-frame/state-icon.vue.d.ts +7 -0
- package/dist/lib/components/ui/feedback/state-frame/state-illustration.vue.d.ts +8 -0
- package/dist/lib/components/ui/feedback/state-frame/state-tones.d.ts +11 -0
- package/dist/lib/components/ui/fields/contacts/consts.d.ts +1 -1
- package/dist/lib/components/ui/layout/app-shell/use-truncated-title.d.ts +10 -0
- package/dist/lib/components/ui/layout/background/background.vue.d.ts +18 -3
- package/dist/lib/components/ui/layout/doc/partials/doc-empty.vue.d.ts +7 -4
- package/dist/lib/components/ui/media/stx-player/stx-player-viewport.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/command/CommandSeparator.vue.d.ts +3 -1
- package/dist/lib/components/ui/overlays/dialog/in-dialog.d.ts +1 -1
- package/dist/lib/components/ui/overlays/dialog/use-escape-forward.d.ts +13 -0
- package/dist/lib/components/ui/overlays/dialog/use-modal-content.d.ts +2 -0
- package/dist/lib/components/ui/primitives/nonce-provider/nonce-provider.vue.d.ts +4 -1
- package/dist/lib/components/ui/selection/autocomplete/autocomplete-keyboard.d.ts +22 -4
- package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-box.vue.d.ts +6 -2
- package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-list.vue.d.ts +7 -0
- package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-active.d.ts +17 -0
- package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-keydown.d.ts +21 -0
- package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-open.d.ts +3 -3
- package/dist/lib/components/ui/selection/autocomplete/use-autocomplete.d.ts +2 -1
- package/dist/lib/components/ui/selection/autocomplete/use-drawer-room.d.ts +11 -0
- package/dist/lib/components/ui/selection/autocomplete/use-query-change.d.ts +8 -0
- package/dist/lib/components/ui/widgets/charts/chart-container.vue.d.ts +2 -2
- package/dist/lib/components/ui/widgets/kanban/partials/kanban-empty.vue.d.ts +1 -0
- package/dist/lib/composables/use-background-activity.d.ts +8 -0
- package/dist/lib/i18n/resources/en/en.d.ts +24 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +25 -1
- package/dist/lib/i18n/resources/fr/fr.d.ts +24 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +25 -1
- package/dist/lib/i18n/resources/resources.d.ts +48 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib/lib/index.d.ts +1 -0
- package/dist/lib/lib/is-production.d.ts +11 -0
- package/dist/lib/nuxt/component-names.d.ts +1 -1
- package/dist/lib/nuxt/module.d.ts +2 -11
- package/dist/lib/nuxt/pdf-fonts.d.ts +1 -1
- package/dist/lib/nuxt/plugin-options.d.ts +58 -0
- package/dist/lib/vite/index.d.ts +4 -8
- package/dist/lib.js +52 -51
- package/dist/main.js +17 -17
- package/dist/map.js +4 -4
- package/dist/material-vue.css +1 -1
- package/dist/{merge-BzRhCAF6.js → merge-CUkhQBjf.js} +1 -1
- package/dist/{mermaid.core-B2ydTQ4X.js → mermaid.core-B70hvAGW.js} +11 -11
- package/dist/{mindmap-definition-NLK3R4M7-DCb99I7s.js → mindmap-definition-NLK3R4M7-B59LL3gV.js} +1 -1
- package/dist/{pdf-outline-sidebar-BU4o_hBI.js → pdf-outline-sidebar-ChDcGkbS.js} +2 -2
- package/dist/{pdf-page-anNe78Lc.js → pdf-page-DFvf3lsq.js} +1 -1
- package/dist/{pdf-page-list-CSY76HH6.js → pdf-page-list-K-ncOHkY.js} +2 -2
- package/dist/{pdf-reader-BhZNrW7R.js → pdf-reader-BPq6yCdy.js} +15 -15
- package/dist/{pdf-sidebar-DcIpG7eh.js → pdf-sidebar-divDHLe7.js} +3 -3
- package/dist/{pdf-thumbnail-sidebar-CUC0V4if.js → pdf-thumbnail-sidebar-C8XMXCq0.js} +4 -4
- package/dist/{pdf-toolbar-C373S-ul.js → pdf-toolbar-DQdedyLO.js} +4 -4
- package/dist/pdf.js +2 -2
- package/dist/{print-format.utils-C81c0weJ.js → print-format.utils-DutRk52h.js} +2 -2
- package/dist/{requirementDiagram-PLB6GJNP-DnkvMOW6.js → requirementDiagram-PLB6GJNP-BvzdTtRf.js} +1 -1
- package/dist/{sortable-provider-CL2UGGn8.js → sortable-provider-CwnKfRFb.js} +2 -2
- package/dist/{stateDiagram-v2-GCMORJYK-BCh7NZQn.js → stateDiagram-v2-GCMORJYK-VPagBr-W.js} +1 -1
- package/dist/{stored-language-Wtcj2HsU.js → stored-language-DVP9KzRu.js} +50 -2
- package/dist/{stx-player-CMmM3F5Z.js → stx-player-NiBlUGm4.js} +12 -12
- package/dist/styles.css +4 -0
- package/dist/{swimlanesDiagram-TC7HE7FX-D4-k5-iq.js → swimlanesDiagram-TC7HE7FX-BA-757S-.js} +2 -2
- package/dist/{translator-D5Dp-dob.js → translator-Dpfm1mAC.js} +171 -159
- package/dist/use-background-activity-DdTBbNpX.js +17 -0
- package/dist/{use-heading-spy-BIBHtUXN.js → use-heading-spy-jn8f2ioE.js} +13 -13
- package/dist/{use-material-translation-COoBTGpB.js → use-material-translation-CHacJbhU.js} +3 -3
- package/dist/{usecaseDiagram-POWQR4AR-Bi5we59l.js → usecaseDiagram-POWQR4AR-1f4iixDN.js} +1 -1
- package/dist/vite.js +7 -2
- package/docs/README.md +3 -0
- package/docs/components/buttons-and-actions.md +2 -0
- package/docs/components/data-table-filter-and-lists.md +59 -8
- package/docs/components/feedback-and-status.md +79 -8
- package/docs/components/media-and-files.md +2 -0
- package/docs/components/navigation-and-layout.md +13 -5
- package/docs/components/overlays-and-menus.md +20 -3
- package/docs/components/primitives.md +5 -3
- package/docs/components/rich-widgets.md +1 -1
- package/docs/components/selection-and-choice.md +14 -4
- package/docs/getting-started.md +22 -3
- package/docs/guides/app-shell.md +1 -1
- package/docs/guides/backgrounds.md +143 -0
- package/docs/guides/csp.md +48 -6
- package/docs/guides/helpers.md +1 -0
- package/docs/guides/i18n.md +51 -1
- package/docs/guides/migrate-to-vite-plugin.md +259 -0
- package/docs/guides/pdf.md +11 -4
- package/docs/guides/ssr.md +1 -1
- package/docs/guides/theming.md +52 -4
- package/docs/guides/vite-plugin.md +194 -0
- package/docs/previews/backgrounds-matrix.webp +0 -0
- package/docs/previews/feedback-status-emptystate.webp +0 -0
- package/docs/previews/feedback-status-errorstate.webp +0 -0
- package/docs/roadmap.md +60 -32
- package/docs/troubleshooting.md +251 -7
- package/lib/backgrounds/backgrounds.css +8 -0
- package/lib/backgrounds/css/base.css +169 -0
- package/lib/backgrounds/css/gradient.css +53 -0
- package/lib/backgrounds/css/grain.css +19 -0
- package/lib/backgrounds/css/motion.css +30 -0
- package/lib/components/base.css +5 -2
- package/lib/components/components.css +5 -0
- package/lib/components/theme-tokens.css +21 -0
- package/lib/nuxt/component-names.ts +1 -0
- package/lib/nuxt/module.ts +29 -15
- package/lib/nuxt/pdf-fonts.ts +1 -1
- package/lib/nuxt/plugin-options.ts +92 -0
- package/lib/nuxt/runtime/inter.css +6 -0
- package/lib/styles.css +4 -0
- package/lib/vite/index.ts +19 -2
- package/package.json +24 -2
package/docs/guides/app-shell.md
CHANGED
|
@@ -180,7 +180,7 @@ partitionMenu(items); // { content, footer }
|
|
|
180
180
|
| `#logo` | slot | — | The logo as a node (an inline SVG), in place of `logo`; it wins when both are given |
|
|
181
181
|
| `name` | `string` | — | The app's name, already translated: the logo's accessible name and tooltip in the rail, shown beside it in the sheet |
|
|
182
182
|
| `#brand` | slot | — | Replaces the sidebar header entirely, in the rail and in the menu sheet; wins over `logo` and `name` |
|
|
183
|
-
| `#header-title` | slot | — | The header's leading title: the context the app is in (the active organisation's name), right after the phone's menu button, truncated when long |
|
|
183
|
+
| `#header-title` | slot | — | The header's leading title: the context the app is in (the active organisation's name), right after the phone's menu button, truncated when long; when it is cut off, its full text is the `title` tooltip (#629) |
|
|
184
184
|
| `#header-actions` | slot | — | In the header, before the avatar: notifications, help, `LanguageSwitcher`, `ThemeToggle` |
|
|
185
185
|
| `#avatar` | slot | the `avatar-src` image | Replaces the avatar entirely |
|
|
186
186
|
| default | slot | — | The routed content, usually `<RouterView />` |
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Backgrounds
|
|
2
|
+
|
|
3
|
+
`Background` is a backdrop that centres its content. The one in
|
|
4
|
+
`@nxgt/material-vue/components` is the **gradient**: a tint with three blurred blobs, and
|
|
5
|
+
it needs only `styles.css`. `@nxgt/material-vue/backgrounds` is the entry for the
|
|
6
|
+
preset family and shares its tones, its intensities and its CSS with
|
|
7
|
+
that gradient. There, `Background` is a superset of the `/components` one under
|
|
8
|
+
the same name (same props, plus the preset ones); the other presets are being
|
|
9
|
+
added slice by slice, so for now it draws the gradient and the grain.
|
|
10
|
+
|
|
11
|
+
## Tone and intensity
|
|
12
|
+
|
|
13
|
+
```vue
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import { Background } from '@nxgt/material-vue/components';
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<Background tone="success" intensity="subtle" class="h-96 w-full">
|
|
20
|
+
...
|
|
21
|
+
</Background>
|
|
22
|
+
</template>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Prop | Values | Default |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `tone` | `primary`, `secondary`, `tertiary`, `neutral`, `success`, `warning`, `error`, `info`, `mixed`, `custom` | `primary` |
|
|
28
|
+
| `intensity` | `subtle`, `default`, `vivid` | `default` |
|
|
29
|
+
| `preset` | `gradient` (the only one on `/components`; `/backgrounds` adds `grain`) | `gradient` |
|
|
30
|
+
|
|
31
|
+
`mixed` draws from primary, secondary and tertiary; `custom` reads your own
|
|
32
|
+
colours (see below).
|
|
33
|
+
|
|
34
|
+
## The `/backgrounds` component
|
|
35
|
+
|
|
36
|
+
`Background` from `@nxgt/material-vue/backgrounds` takes everything the `/components` one
|
|
37
|
+
takes, and more. Import the stylesheet of the presets you use (below).
|
|
38
|
+
|
|
39
|
+
```vue
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { Background } from '@nxgt/material-vue/backgrounds';
|
|
42
|
+
import '@nxgt/material-vue/backgrounds.css';
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<Background preset="grain" tone="mixed" animated class="h-96 w-full">
|
|
47
|
+
...
|
|
48
|
+
</Background>
|
|
49
|
+
</template>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
| Prop | Values | Default |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| `preset` | `gradient`, `grain` (`SHIPPED_BACKGROUND_PRESETS`); any other id draws the gradient and warns outside production | `gradient` |
|
|
55
|
+
| `tone`, `intensity` | as above | `primary`, `default` |
|
|
56
|
+
| `animated` | `boolean`: drifts the layers, see Motion | `false` |
|
|
57
|
+
| `grain` | `boolean`: film noise over the preset; needs `grain.css` | `false` |
|
|
58
|
+
| `seed` | `number`: moves a seeded preset's geometry and the grain | `0` |
|
|
59
|
+
| `fill` | `viewport` (the viewport's size) or `container` (its parent's size: the parent needs a definite height) | `viewport` |
|
|
60
|
+
|
|
61
|
+
`BACKGROUND_PRESETS` is the planned table of all twenty presets, with their
|
|
62
|
+
family and kind; `SHIPPED_BACKGROUND_PRESETS` is what `Background` draws today.
|
|
63
|
+
|
|
64
|
+
The root states `preset`, `tone`, `intensity`, `animated`, `grain` and `fill` as
|
|
65
|
+
`data-*` attributes (`data-animated` and `data-grain` are empty attributes), and
|
|
66
|
+
`data-paused` while it is paused. Select on `data-preset` (`grain` for the grain
|
|
67
|
+
preset), not on `data-background`. That one is the CSS key and stays
|
|
68
|
+
`gradient` for it. The default slot is centred above the
|
|
69
|
+
decoration.
|
|
70
|
+
|
|
71
|
+
`grain` draws an SVG `feTurbulence` filter above the preset's layers, under
|
|
72
|
+
your content; `intensity` scales its opacity. `preset="grain"` is the gradient
|
|
73
|
+
with the grain on. The grain's look may still change. Its SVG is built from
|
|
74
|
+
plain data by `@nxgt/material-core/backgrounds` (`buildGrain`), with no `style`
|
|
75
|
+
attribute: ids come from `useId`, so the server and the client render the same
|
|
76
|
+
markup, and nothing is random or measured.
|
|
77
|
+
|
|
78
|
+
## Dark mode
|
|
79
|
+
|
|
80
|
+
In dark the drawn colours are lifted to a lightness floor: the dark `--primary` is as dim as the light one, which made the
|
|
81
|
+
gradient dull. The lift uses the relative colour syntax (Chrome 119, Safari 18,
|
|
82
|
+
Firefox 128); older browsers get a `color-mix()` toward white.
|
|
83
|
+
|
|
84
|
+
## Your own colours
|
|
85
|
+
|
|
86
|
+
`tone="custom"` reads `--nxgt-bg-custom-1` to `--nxgt-bg-custom-3`, which you set in your
|
|
87
|
+
own stylesheet. There is no colour prop, because one would write a `style`
|
|
88
|
+
attribute.
|
|
89
|
+
|
|
90
|
+
```css
|
|
91
|
+
.brand-backdrop {
|
|
92
|
+
--nxgt-bg-custom-1: oklch(0.62 0.2 30);
|
|
93
|
+
--nxgt-bg-custom-2: oklch(0.7 0.15 80);
|
|
94
|
+
--nxgt-bg-custom-3: var(--primary);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Overriding the drawn colours
|
|
99
|
+
|
|
100
|
+
The tone sets `--nxgt-bg-c1..c3`, the three colours the layers draw with. Set
|
|
101
|
+
them on the element to pick your own. The element carries `data-background`, `data-tone` and `data-intensity`, which
|
|
102
|
+
you can select on:
|
|
103
|
+
|
|
104
|
+
```css
|
|
105
|
+
[data-background][data-tone="primary"] {
|
|
106
|
+
--nxgt-bg-c1: oklch(0.62 0.2 30);
|
|
107
|
+
--nxgt-bg-c2: oklch(0.7 0.15 80);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Stylesheets
|
|
112
|
+
|
|
113
|
+
`styles.css` carries the base, the gradient and the motion rules. Everything
|
|
114
|
+
else is opt-in, because CSS cannot be tree-shaken. `animated` on the
|
|
115
|
+
`/components` `Background` needs only `styles.css`; `grain` needs `grain.css` (or
|
|
116
|
+
`backgrounds.css`) and renders nothing, with no error, without it:
|
|
117
|
+
|
|
118
|
+
```css
|
|
119
|
+
@import "@nxgt/material-vue/backgrounds.css"; /* every opt-in file */
|
|
120
|
+
/* or just the one you use: */
|
|
121
|
+
@import "@nxgt/material-vue/backgrounds/grain.css"; /* the grain overlay, over any preset */
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The base, the gradient and the motion already come with `styles.css`: the
|
|
125
|
+
`/components` `Background` needs none of `base.css`, `gradient.css` or
|
|
126
|
+
`motion.css`. Only `grain.css` is extra.
|
|
127
|
+
|
|
128
|
+
The CSS sits in `@layer components`: a utility such as `h-96 w-full` or
|
|
129
|
+
`overflow-hidden` on the element beats it.
|
|
130
|
+
|
|
131
|
+
## Motion, accessibility and CSP
|
|
132
|
+
|
|
133
|
+
- Static by default. `animated` drifts the layers, and only under
|
|
134
|
+
`@media (prefers-reduced-motion: no-preference)`: with reduced motion it
|
|
135
|
+
never moves. It sets `data-paused`, which stops the animation, while the page
|
|
136
|
+
is hidden (another tab) or the background is off screen. The observers start
|
|
137
|
+
once mounted and only when `animated` is set, and are removed on unmount.
|
|
138
|
+
`animated` is on the `/components` `Background` too, with the same behaviour.
|
|
139
|
+
- Every decorative layer is `aria-hidden="true"` and takes no pointer; under
|
|
140
|
+
`forced-colors: active` the layers are hidden. Put text on a `Card` when you
|
|
141
|
+
use `vivid`.
|
|
142
|
+
- The markup carries no `style` attribute and no `<style>` element, so a
|
|
143
|
+
strict CSP needs nothing beyond the [baseline](csp.md).
|
package/docs/guides/csp.md
CHANGED
|
@@ -101,9 +101,30 @@ On the server, the nonce goes down through `provide`/`inject` only. On the
|
|
|
101
101
|
client, `NonceProvider` also keeps it for the code that runs outside Vue
|
|
102
102
|
(`claimCspNonce`, once mounted, so never on a server: one request's nonce
|
|
103
103
|
never reaches another's). With nested providers, the innermost one mounted is
|
|
104
|
-
the page's nonce, and it falls back to the outer one's when it unmounts.
|
|
105
|
-
|
|
106
|
-
the
|
|
104
|
+
the page's nonce, and it falls back to the outer one's when it unmounts.
|
|
105
|
+
|
|
106
|
+
On the client, `<NonceProvider>` with no `nonce` prop works: it falls back to
|
|
107
|
+
the nonce the page carries (`getCspNonce()`: the `csp-nonce` meta tag, a
|
|
108
|
+
`<script nonce>`, …), read in `setup`, so it is there from the first render (a
|
|
109
|
+
`<style>` a child creates in its own `onMounted` carries it). A server has no
|
|
110
|
+
page to read and keeps no nonce in a global: it passes the prop, the nonce the
|
|
111
|
+
page carries, so the hydration pass renders what the server did. A server that
|
|
112
|
+
renders without the prop leaves its `<style>` elements blocked: a browser
|
|
113
|
+
checks the policy when it parses them, and a nonce set after hydration does
|
|
114
|
+
not bring them back. A parent `ConfigProvider`'s nonce still wins over the
|
|
115
|
+
page's, and the prop wins over both.
|
|
116
|
+
|
|
117
|
+
```vue
|
|
118
|
+
<NonceProvider><App /></NonceProvider>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
"The page's nonce" here is what `getCspNonce()` returns: first a nonce a mounted
|
|
122
|
+
provider has already claimed, else the `csp-nonce` meta tag or a nonced
|
|
123
|
+
`<script>`/`<style>`. A provider whose own `nonce` goes from a value to
|
|
124
|
+
`undefined` therefore keeps its earlier nonce.
|
|
125
|
+
|
|
126
|
+
The server always passes the nonce itself. To hand the client the one the
|
|
127
|
+
server wrote, read it from the page and give the root the same prop:
|
|
107
128
|
|
|
108
129
|
```ts
|
|
109
130
|
// entry-client.ts
|
|
@@ -147,8 +168,8 @@ const Keyframes = () => h('style', { nonce: nonce.value }, props.css);
|
|
|
147
168
|
|
|
148
169
|
`useCspNonce()` is a computed ref: `NonceProvider`'s nonce, else the nearest
|
|
149
170
|
reka `ConfigProvider`'s, else `undefined`. It never reads the page in
|
|
150
|
-
`setup
|
|
151
|
-
|
|
171
|
+
`setup` itself. A `NonceProvider` without a prop does, on the client, which
|
|
172
|
+
is why a server-rendered app passes the prop to the provider on both sides. Code that runs once mounted (an imperative
|
|
152
173
|
widget creating its own `<style>`) can call `getCspNonce()` instead.
|
|
153
174
|
|
|
154
175
|
## `<style>` elements the components inject
|
|
@@ -200,6 +221,8 @@ Not fixable by configuration yet, and still reported by a strict policy:
|
|
|
200
221
|
|
|
201
222
|
## zod and `'unsafe-eval'`
|
|
202
223
|
|
|
224
|
+
> **With the Vite plugin** (`nxgtMaterial()` from `@nxgt/material-vue/vite`, the [Vite plugin guide](vite-plugin.md)) the KaTeX and zod steps below are done for you, and so is the Inter step once `@fontsource-variable/inter` is installed (without it the plugin warns and keeps the system font), and the build writes the CSP header (by hash, or with a nonce placeholder). What follows is the route without it.
|
|
225
|
+
|
|
203
226
|
Building a `z.object()` makes zod probe `new Function` once, to choose
|
|
204
227
|
between its compiled parser and its interpreter. Without `'unsafe-eval'` the
|
|
205
228
|
browser reports a violation. The error is caught, so nothing breaks, but the
|
|
@@ -244,6 +267,8 @@ the stand-in is not seen through the factory's schema.
|
|
|
244
267
|
|
|
245
268
|
## KaTeX fonts
|
|
246
269
|
|
|
270
|
+
> **With the Vite plugin** (`nxgtMaterial()` from `@nxgt/material-vue/vite`, the [Vite plugin guide](vite-plugin.md)) the KaTeX and zod steps below are done for you, and so is the Inter step once `@fontsource-variable/inter` is installed (without it the plugin warns and keeps the system font), and the build writes the CSP header (by hash, or with a nonce placeholder). What follows is the route without it.
|
|
271
|
+
|
|
247
272
|
`Markdown` renders formulas with KaTeX, which needs its fonts
|
|
248
273
|
(`KaTeX_*.woff2`). They are files on both routes, so `font-src 'self'` allows
|
|
249
274
|
them, and a browser downloads a font only when a formula draws a glyph from
|
|
@@ -280,6 +305,8 @@ The library's CI fails when a stylesheet in `dist/` carries a `data:` font.
|
|
|
280
305
|
|
|
281
306
|
## Inter / the sans font
|
|
282
307
|
|
|
308
|
+
> **With the Vite plugin** (`nxgtMaterial()` from `@nxgt/material-vue/vite`, the [Vite plugin guide](vite-plugin.md)) the KaTeX and zod steps below are done for you, and so is the Inter step once `@fontsource-variable/inter` is installed (without it the plugin warns and keeps the system font), and the build writes the CSP header (by hash, or with a nonce placeholder). What follows is the route without it.
|
|
309
|
+
|
|
283
310
|
The library ships no web font. Its sans text uses the Tailwind token
|
|
284
311
|
`--font-sans`, which the theme does not set, so it is Tailwind's system stack
|
|
285
312
|
(`ui-sans-serif, system-ui, sans-serif`, then the emoji fonts). Many apps name
|
|
@@ -388,14 +415,29 @@ stamps Nuxt's scripts with it), so both render the same tree.
|
|
|
388
415
|
|
|
389
416
|
On your own Vite SSR server, generate the nonce in the request handler, put it
|
|
390
417
|
in the header and in every `<script>` and `<style>` you emit, and pass it to
|
|
391
|
-
`NonceProvider` through your root component's props
|
|
418
|
+
`NonceProvider` through your root component's props. `createCspNonce()` and
|
|
419
|
+
`cspPolicy({ nonce }).header` from `@nxgt/material-vue/lib` make both (they
|
|
420
|
+
use `globalThis.crypto`, so the same code runs on Node, Bun, Deno and
|
|
421
|
+
workers); add your own origins with the `directives` option. Never serve the
|
|
422
|
+
build placeholder `CSP_NONCE_PLACEHOLDER` as a nonce: both helpers refuse it.
|
|
423
|
+
A page that cannot have a per-request nonce (static hosting) allows its inline
|
|
424
|
+
scripts by hash instead: `await cspHash(script)`, then
|
|
425
|
+
`cspPolicy({ hashes: { script: [hash] } })`, whose
|
|
426
|
+
`style-src-elem` is then `'self' 'unsafe-inline'`: the components create
|
|
427
|
+
`<style>` elements at runtime, which have no hash, and a style hash would void
|
|
428
|
+
`'unsafe-inline'` and block them. A nonce is the strict option for styles. For zod without `'unsafe-eval'`,
|
|
429
|
+
put `zodJitlessTag(nonce)` first in the head.
|
|
392
430
|
|
|
393
431
|
```ts
|
|
394
432
|
// entry-server.ts
|
|
395
433
|
import { createSSRApp } from 'vue';
|
|
396
434
|
import { renderToString } from 'vue/server-renderer';
|
|
435
|
+
import { createCspNonce, cspPolicy } from '@nxgt/material-vue/lib';
|
|
397
436
|
import App from './App.vue';
|
|
398
437
|
|
|
438
|
+
// In the request handler:
|
|
439
|
+
// const nonce = createCspNonce();
|
|
440
|
+
// response.setHeader('Content-Security-Policy', cspPolicy({ nonce }).header);
|
|
399
441
|
export function render(nonce: string) {
|
|
400
442
|
return renderToString(createSSRApp(App, { nonce }));
|
|
401
443
|
}
|
package/docs/guides/helpers.md
CHANGED
|
@@ -18,6 +18,7 @@ differences are listed under each table. React's `/hooks` is
|
|
|
18
18
|
| --- | --- |
|
|
19
19
|
| `cn(...classes)` | `clsx` + `tailwind-merge`: joins conditional classes, last Tailwind utility wins. |
|
|
20
20
|
| `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). |
|
|
21
|
+
| `createCspNonce(bytes?)`, `cspPolicy({ nonce, dev, hashes, directives })`, `cspHash(source)`, `applyCspNonce(html, nonce)`, `CSP_NONCE_PLACEHOLDER`, `ZOD_JITLESS_SCRIPT`, `zodJitlessTag(nonce?)` | The server and build side of the CSP, from `@nxgt/material-core/csp` (no DOM; Node, Bun, Deno and workers): a nonce per request, the recommended policy as a `header`, the `'sha256-…'` source of an inline script, the nonce filled into built HTML, zod's jitless script. The public placeholder is refused as a nonce. See [CSP](csp.md#ssr). |
|
|
21
22
|
| `printOnly(element, name?)`, `PRINT_ATTRIBUTE`, `PRINT_TARGET_ATTRIBUTE` | Opens the print dialog for one element of the page instead of the whole page (the marks are removed once printing is over), from `@nxgt/material-core/print-only`; the `@media print` rules are in the package's `styles.css`. See [printing](printing.md#printing-one-element-of-the-page). |
|
|
22
23
|
| `clientOnly(component)` | Wraps a component so it renders nothing on the server and on the first client render, and mounts only after `onMounted`. Pair it with `defineAsyncComponent(() => import(…))`. See [SSR](ssr.md). |
|
|
23
24
|
| `renderSlot(slot, context)`, `ContentSlot<T>` | Resolves a `ContentSlot`: a `VNode`, a string or number, or a `(context) => VNode \| string \| number`. |
|
package/docs/guides/i18n.md
CHANGED
|
@@ -310,7 +310,8 @@ names and text, which reka-ui left in English (`material.pagination.first`,
|
|
|
310
310
|
step announces after its title, "Completed" or "Error"
|
|
311
311
|
(`material.stepper.completed` / `error`, "Terminée" / "Erreur" in French);
|
|
312
312
|
`CommandDialog`'s
|
|
313
|
-
default title and description (`material.command
|
|
313
|
+
default title and description (`material.command.title` / `description`) and
|
|
314
|
+
`CommandList`'s accessible name (`material.command.list`); the carousel's
|
|
314
315
|
`aria-roledescription` (`material.carousel.roledescription` / `slide`); the
|
|
315
316
|
filter's empty state and `+N more` chip (`material.filter.empty` / `more`) and
|
|
316
317
|
the Yes/No, From, Until and Up to of its chips (`material.filter.value.*`;
|
|
@@ -455,6 +456,55 @@ reported with `console.warn`, one line per language:
|
|
|
455
456
|
|
|
456
457
|
If you see that warning and did not mean it, move your key out of `material`.
|
|
457
458
|
|
|
459
|
+
### Empty and error states
|
|
460
|
+
|
|
461
|
+
`EmptyState` reads `material['empty-state'].<kind>.title` and `description` for
|
|
462
|
+
its four kinds, and `ErrorState` reads `material['error-state'].<kind>.title`
|
|
463
|
+
and `description` for its three. The other keys the two components and the
|
|
464
|
+
places that use them read:
|
|
465
|
+
|
|
466
|
+
| Key | English | French | Read by |
|
|
467
|
+
| --- | --- | --- | --- |
|
|
468
|
+
| `material.empty-state.empty.title` / `description` | No Data Found / There is nothing to display here at the moment. | Aucune donnée / Il n'y a rien à afficher pour le moment. | `EmptyState kind="empty"` |
|
|
469
|
+
| `material.empty-state.search.title` / `description` | No results / Try a different query or clear the filters. | Aucun résultat / Essayez une autre recherche ou retirez les filtres. | `kind="search"` |
|
|
470
|
+
| `material.empty-state.not-found.title` / `description` | Page Not Found / The page you are looking for doesn't exist or has been moved. | Page introuvable / La page que vous recherchez n'existe pas ou a été déplacée. | `kind="not-found"` |
|
|
471
|
+
| `material.empty-state.unauthorized.title` / `description` | Access Denied / You don't have permission to view this content. | Accès refusé / Vous n'avez pas la permission de consulter ce contenu. | `kind="unauthorized"` |
|
|
472
|
+
| `material.error-state.generic.title` / `description` | Something Went Wrong / An unexpected error occurred. Please try again later. | Une erreur est survenue / Une erreur inattendue s'est produite. Veuillez réessayer plus tard. | `ErrorState kind="generic"` |
|
|
473
|
+
| `material.error-state.offline.title` / `description` | You are offline / Check your connection and try again. | Vous êtes hors ligne / Vérifiez votre connexion et réessayez. | `kind="offline"` |
|
|
474
|
+
| `material.error-state.maintenance.title` / `description` | We'll be right back / The service is down for a short update. | On revient tout de suite / Le service est en courte mise à jour. | `kind="maintenance"` |
|
|
475
|
+
| `material.error-state.retry` | Retry | Réessayer | `ErrorState`'s button |
|
|
476
|
+
| `material.error-state.details` | Technical details | Détails techniques | the collapsible summary |
|
|
477
|
+
| `material.error-state.details-name` / `details-message` / `details-cause` | Name / Message / Cause | Nom / Message / Cause | the rows inside it |
|
|
478
|
+
| `material.table.labels.no-data` | No data available | Données non disponibles | `DataTable`'s default for a table with no data |
|
|
479
|
+
| `material.table.labels.no-results` / `no-results-description` | No results / Try a different search or clear the filters. | Aucun résultat / Essayez une autre recherche ou retirez les filtres. | `DataTable`'s default when a filter hides every row, also announced by its status |
|
|
480
|
+
| `material.command.empty` | No results | Aucun résultat | `CommandEmpty` with no children |
|
|
481
|
+
| `material.pdf-reader.retry` | Retry | Réessayer | not read yet: the Vue `PdfReader` has no Retry button (React only for now, [#702](https://github.com/softistx/nxgt-material/issues/702)) |
|
|
482
|
+
|
|
483
|
+
`material.empty-state.generic.*`, `offline.*` and `maintenance.*` still exist
|
|
484
|
+
for the deprecated `variant`; `ErrorState` does not read them. An app that
|
|
485
|
+
overrode those three for an `EmptyState` error variant copies its overrides to
|
|
486
|
+
`material.error-state.<kind>.title` / `description`:
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
import { mergeResources, resources as materialResources } from '@nxgt/material-vue/i18n';
|
|
490
|
+
|
|
491
|
+
const appResources = {
|
|
492
|
+
en: {
|
|
493
|
+
material: {
|
|
494
|
+
'error-state': {
|
|
495
|
+
offline: { title: 'No connection', description: 'Reconnect to continue.' },
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
export const merged = mergeResources(materialResources, appResources);
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Text you pass (`title`, `description`, `retryLabel`) always wins over these keys.
|
|
505
|
+
A `title` of your own drops the default description, so pass a `description`
|
|
506
|
+
to keep a line under it.
|
|
507
|
+
|
|
458
508
|
## Zod messages
|
|
459
509
|
|
|
460
510
|
```ts
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Migrating to the Vite plugin
|
|
2
|
+
|
|
3
|
+
This page is for an app that already sends a strict Content Security Policy
|
|
4
|
+
(CSP) with `@nxgt/material-vue` by hand, and wants `nxgtMaterial()` to do that
|
|
5
|
+
work. It is written as search → replace pairs, so a person or an upgrade agent
|
|
6
|
+
can run each `grep`, change what it finds and check the result. Read the
|
|
7
|
+
[Vite plugin guide](vite-plugin.md) first for what each option does.
|
|
8
|
+
|
|
9
|
+
Nothing here is breaking: the old exports (`pdfkitStandardFonts`,
|
|
10
|
+
`cspAssetsInlineLimit`, `createCspNonce`, `cspPolicy`, `applyCspNonce`) stay,
|
|
11
|
+
and an app that changes nothing keeps working. Migrate pair by pair.
|
|
12
|
+
|
|
13
|
+
## Step 0: add the plugin
|
|
14
|
+
|
|
15
|
+
Search `vite.config.*` for `plugins:`. Add the plugin last, spread (it returns an
|
|
16
|
+
array). Inter needs no install to build: without `@fontsource-variable/inter` the plugin warns once and keeps the system font (pair 4). Nuxt: skip to pair 8; the module calls the plugin for you.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// before
|
|
20
|
+
plugins: [vue(), tailwindcss()],
|
|
21
|
+
|
|
22
|
+
// after
|
|
23
|
+
import { nxgtMaterial } from '@nxgt/material-vue/vite';
|
|
24
|
+
|
|
25
|
+
plugins: [vue(), tailwindcss(), ...nxgtMaterial()],
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
bun add @fontsource-variable/inter # optional: to get Inter. Without it the plugin warns and keeps the system font;
|
|
30
|
+
# pass fonts: { sans: false } to keep it silently
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Verify**: `vite build` ends with a log line holding a ready
|
|
34
|
+
`Content-Security-Policy` header, and `dist/.vite/nxgt-csp.json` exists.
|
|
35
|
+
|
|
36
|
+
## The pairs
|
|
37
|
+
|
|
38
|
+
| # | Search for | Replace with |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| 1 | a hand-written static CSP header for a static host | [the generated header](#1-a-hand-written-static-csp-header) |
|
|
41
|
+
| 2 | `randomBytes(16).toString('base64')` plus a hand-written header | [`csp: { mode: 'nonce' }` and the helpers](#2-a-server-nonce-setup) |
|
|
42
|
+
| 3 | `build.assetsInlineLimit: cspAssetsInlineLimit` | [remove it](#3-assetsinlinelimit) |
|
|
43
|
+
| 4 | `@import "@fontsource-variable/inter"` plus `--font-sans` in `@theme` | [remove it, or `fonts: { sans: false }`](#4-the-inter-import) |
|
|
44
|
+
| 5 | a `zod-config.ts` with `z.config({ jitless: true })` | [remove it](#5-zod-configts) |
|
|
45
|
+
| 6 | `pdfkitStandardFonts()` plus `ssr.noExternal: ['@nxgt/material-vue', …]` | [`pdf: { bundle: true }`](#6-pdf-on-a-bundled-server) (Vite SSR server; for Nuxt see pair 8) |
|
|
46
|
+
| 7 | `<NonceProvider :nonce="getCspNonce()">` on the client | [`<NonceProvider>`](#7-nonceprovider-on-the-client) |
|
|
47
|
+
| 8 | Nuxt: a hand-made CSP of your own | [stays with `nuxt-security`](#8-nuxt) |
|
|
48
|
+
|
|
49
|
+
### 1. A hand-written static CSP header
|
|
50
|
+
|
|
51
|
+
Search the host configuration (`_headers`, a CDN rule, `nginx.conf`, a
|
|
52
|
+
`<meta http-equiv="Content-Security-Policy">`) for `script-src`. For a
|
|
53
|
+
statically hosted SPA, the old route was a header you wrote yourself, often
|
|
54
|
+
with `'unsafe-inline'` or `'unsafe-eval'` to make zod and Vite's inline code
|
|
55
|
+
run.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
# before (_headers)
|
|
59
|
+
/*
|
|
60
|
+
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; …
|
|
61
|
+
|
|
62
|
+
# after: the header the build prints and writes to dist/.vite/nxgt-csp.json
|
|
63
|
+
/*
|
|
64
|
+
Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-…'; style-src-elem 'self' 'unsafe-inline'; …
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Copy `header` from `dist/.vite/nxgt-csp.json` (or from the build log) into the
|
|
68
|
+
host's configuration. Rebuild, and recopy it: the hashes follow the HTML. To
|
|
69
|
+
extend it (an API origin, say), use `nxgtMaterial({ csp: { policy: { directives: { 'connect-src': ["'self'", 'https://api.example.com'] } } } })`
|
|
70
|
+
and recopy.
|
|
71
|
+
|
|
72
|
+
**Verify**: open the deployed app with the console open: no `Refused to
|
|
73
|
+
execute inline script` and no `unsafe-eval` report. `nxgt-csp.json` lists
|
|
74
|
+
`scriptHashes` that cover the page's inline `<script>` elements.
|
|
75
|
+
|
|
76
|
+
### 2. A server nonce setup
|
|
77
|
+
|
|
78
|
+
Search `randomBytes(`, `crypto.randomUUID()` used as a nonce, `nonce-${`.
|
|
79
|
+
If the app has a server that serves or renders its HTML, pick nonce mode and
|
|
80
|
+
the library helpers (they use `globalThis.crypto`, so Node, Bun, Deno and
|
|
81
|
+
workers alike).
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// before (server)
|
|
85
|
+
const nonce = randomBytes(16).toString('base64');
|
|
86
|
+
res.setHeader('Content-Security-Policy', `default-src 'self'; script-src 'nonce-${nonce}' 'strict-dynamic'; …`);
|
|
87
|
+
const html = template.replaceAll('__NONCE__', nonce);
|
|
88
|
+
|
|
89
|
+
// vite.config.ts, after
|
|
90
|
+
nxgtMaterial({ csp: { mode: 'nonce' } })
|
|
91
|
+
|
|
92
|
+
// server, after
|
|
93
|
+
import { applyCspNonce, createCspNonce, cspPolicy } from '@nxgt/material-vue/lib';
|
|
94
|
+
|
|
95
|
+
const nonce = createCspNonce();
|
|
96
|
+
const html = applyCspNonce(builtHtml, nonce);
|
|
97
|
+
res.setHeader('Content-Security-Policy', cspPolicy({ nonce }).header);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The built HTML carries the public placeholder `NXGT_CSP_NONCE` (or the one you
|
|
101
|
+
set with `csp.placeholder` / Vite's `html.cspNonce`, which the plugin reuses):
|
|
102
|
+
**never serve it as it is**. `applyCspNonce` and `cspPolicy({ nonce })` throw
|
|
103
|
+
when given the placeholder. The `header` in `nxgt-csp.json` is a template in
|
|
104
|
+
this mode, not something to serve. Keep your own placeholder replacement only
|
|
105
|
+
if you must: then set `csp.placeholder` to its token.
|
|
106
|
+
|
|
107
|
+
**Verify**: `curl` a page twice: the `nonce="…"` values differ between
|
|
108
|
+
requests, equal the nonce in the response header, and the string
|
|
109
|
+
`NXGT_CSP_NONCE` appears nowhere in the response. No CSP violation in the
|
|
110
|
+
console.
|
|
111
|
+
|
|
112
|
+
### 3. `assetsInlineLimit`
|
|
113
|
+
|
|
114
|
+
Search `cspAssetsInlineLimit` and `assetsInlineLimit` in `vite.config.*`. The
|
|
115
|
+
plugin's `fonts.katex` (on by default) keeps every font out of `data:` URIs,
|
|
116
|
+
and composes with a `build.assetsInlineLimit` of your own.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// before
|
|
120
|
+
build: { assetsInlineLimit: cspAssetsInlineLimit },
|
|
121
|
+
|
|
122
|
+
// after (delete the line; keep a limit of your own if you have one)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Verify**: after `vite build`, `grep -l "data:font" dist/assets/*` finds
|
|
126
|
+
nothing, and the KaTeX `.woff2` files are listed under `dist/assets/`.
|
|
127
|
+
|
|
128
|
+
### 4. The Inter import
|
|
129
|
+
|
|
130
|
+
Search the app stylesheet for `@fontsource-variable/inter` and `--font-sans`.
|
|
131
|
+
The plugin imports Inter in the stylesheet that imports
|
|
132
|
+
`@nxgt/material-vue/styles.css` and sets `--font-sans` unlayered, and it skips
|
|
133
|
+
both when it finds your own, so the old lines are optional to remove. Inter needs
|
|
134
|
+
`@fontsource-variable/inter` installed (`bun add @fontsource-variable/inter`):
|
|
135
|
+
without it the plugin warns once and keeps the system font, and the build still
|
|
136
|
+
succeeds. `fonts: { sans: false }` keeps the system font without the warning.
|
|
137
|
+
|
|
138
|
+
```css
|
|
139
|
+
/* before */
|
|
140
|
+
@import "@fontsource-variable/inter";
|
|
141
|
+
@theme { --font-sans: "Inter Variable", sans-serif; }
|
|
142
|
+
|
|
143
|
+
/* after: delete both lines (the plugin adds them), or keep them
|
|
144
|
+
and write nxgtMaterial({ fonts: { sans: false } }) */
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
A JavaScript `import '@fontsource-variable/inter'` is not detected: keep it only
|
|
148
|
+
with `fonts: { sans: false }`. If you use another font, also pass
|
|
149
|
+
`fonts: { sans: false }`.
|
|
150
|
+
|
|
151
|
+
**Verify**: the built CSS has `@font-face` rules for `Inter Variable` and
|
|
152
|
+
`:root{--font-sans:…}`, the font files are same-origin, and no `font-src`
|
|
153
|
+
violation shows in the console.
|
|
154
|
+
|
|
155
|
+
### 5. `zod-config.ts`
|
|
156
|
+
|
|
157
|
+
Search `z.config({ jitless: true })` and the `import './zod-config'` that
|
|
158
|
+
precedes the app. Remove both: the plugin injects zod's jitless script
|
|
159
|
+
(`ZOD_JITLESS_SCRIPT`) first in `index.html`, and hashes it (hash mode) or
|
|
160
|
+
stamps it with the nonce (nonce mode).
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// before
|
|
164
|
+
import './zod-config'; // first
|
|
165
|
+
import './app';
|
|
166
|
+
|
|
167
|
+
// after
|
|
168
|
+
import './app';
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Keep the file in an app that renders HTML on its own server with no
|
|
172
|
+
`index.html` (an SSR framework or Nuxt): there, put `zodJitlessTag(nonce)` first in
|
|
173
|
+
the head instead (see [CSP: SSR](csp.md#ssr)).
|
|
174
|
+
|
|
175
|
+
**Verify**: `grep -c "__zod_globalConfig" dist/index.html` prints at least `1`, the
|
|
176
|
+
script has no `nonce` in hash mode, and the console shows no `unsafe-eval`
|
|
177
|
+
report when a form mounts.
|
|
178
|
+
|
|
179
|
+
### 6. PDF on a bundled server
|
|
180
|
+
|
|
181
|
+
Search `pdfkitStandardFonts` and `noExternal` in `vite.config.*`. This is the
|
|
182
|
+
pair for a Vite SSR server that renders `@nxgt/material-vue/pdf` and ships its
|
|
183
|
+
bundle with no `node_modules`.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// before
|
|
187
|
+
plugins: [pdfkitStandardFonts()],
|
|
188
|
+
ssr: { noExternal: ['@nxgt/material-vue', '@nxgt/material-core'] },
|
|
189
|
+
|
|
190
|
+
// after
|
|
191
|
+
plugins: [...nxgtMaterial({ pdf: { bundle: true } })],
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Keep an `ssr.noExternal` entry for what the plugin does not walk (your
|
|
195
|
+
app's own dependencies). Your own array is merged, never replaced.
|
|
196
|
+
|
|
197
|
+
**Verify**: `vite build` for the server, run the server from a folder with no
|
|
198
|
+
`node_modules`, and request a PDF: no `ENOENT … .afm` error.
|
|
199
|
+
|
|
200
|
+
### 7. `NonceProvider` on the client
|
|
201
|
+
|
|
202
|
+
Search `getCspNonce()` next to `NonceProvider` in the client entry. On the
|
|
203
|
+
client, a provider without a `nonce` prop reads the nonce the page carries in
|
|
204
|
+
`setup`, from its first render: the one the server passed, so the hydration
|
|
205
|
+
pass renders what the server did. The server keeps passing its request's
|
|
206
|
+
nonce: it has no page to read.
|
|
207
|
+
|
|
208
|
+
```vue
|
|
209
|
+
<!-- before (client) -->
|
|
210
|
+
<NonceProvider :nonce="getCspNonce()"><RouterView /></NonceProvider>
|
|
211
|
+
|
|
212
|
+
<!-- after -->
|
|
213
|
+
<NonceProvider><RouterView /></NonceProvider>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Leave the server render as it is. In hash mode there is no nonce at all:
|
|
217
|
+
`<NonceProvider>` renders its children, and the `<style>` elements the
|
|
218
|
+
components create are allowed by the header's `style-src-elem 'self'
|
|
219
|
+
'unsafe-inline'`. Nonce mode is the strict option for styles.
|
|
220
|
+
|
|
221
|
+
**Verify**: no hydration warning in the console, and a `Select` or a
|
|
222
|
+
`ScrollArea` `<style>` carries the page's nonce in nonce mode.
|
|
223
|
+
|
|
224
|
+
### 8. Nuxt
|
|
225
|
+
|
|
226
|
+
The Nuxt module calls `nxgtMaterial()` for you with its own options, and keeps
|
|
227
|
+
the CSP **off**: a Nuxt page is rendered per request, so there is no build-time
|
|
228
|
+
`index.html` to hash or stamp. Pairs 1 and 2 do not apply: keep `nuxt-security`
|
|
229
|
+
for the policy. What does apply:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
// nuxt.config.ts, before
|
|
233
|
+
export default defineNuxtConfig({
|
|
234
|
+
nxgtMaterial: { pdf: { bundleStandardFonts: true } },
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// after
|
|
238
|
+
export default defineNuxtConfig({
|
|
239
|
+
nxgtMaterial: { pdf: { bundle: true } },
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`pdf.bundleStandardFonts` still works and warns until you rename it. For zod
|
|
244
|
+
(pair 5), keep the `z.config` file or set `nxgtMaterial: { csp: { zodJitless: true } }`,
|
|
245
|
+
which puts the script in every head; `nuxt-security` with `nonce: true`
|
|
246
|
+
stamps it.
|
|
247
|
+
|
|
248
|
+
**Verify**: `nuxt build` prints no `bundleStandardFonts is deprecated` warning,
|
|
249
|
+
and the server output renders a PDF without `node_modules`.
|
|
250
|
+
|
|
251
|
+
## Checking the whole migration
|
|
252
|
+
|
|
253
|
+
1. `vite build` succeeds and prints the header (hash mode), or `nxgt-csp.json`
|
|
254
|
+
exists (both modes).
|
|
255
|
+
2. Serve the build with that header (or the nonce flow of pair 2).
|
|
256
|
+
3. Open the app and exercise a form, a select and a modal. The console
|
|
257
|
+
shows no CSP violation.
|
|
258
|
+
4. `git grep -n "zod-config\|cspAssetsInlineLimit\|fontsource-variable/inter"`
|
|
259
|
+
lists only what you kept on purpose.
|
package/docs/guides/pdf.md
CHANGED
|
@@ -281,20 +281,27 @@ artifact, `nitro: { noExternals: true }`), bundle the fonts in:
|
|
|
281
281
|
// nuxt.config.ts
|
|
282
282
|
export default defineNuxtConfig({
|
|
283
283
|
modules: ['@nxgt/material-vue/nuxt'],
|
|
284
|
-
nxgtMaterial: { pdf: {
|
|
284
|
+
nxgtMaterial: { pdf: { bundle: true } }, // was pdf.bundleStandardFonts
|
|
285
285
|
});
|
|
286
286
|
```
|
|
287
287
|
|
|
288
288
|
```ts
|
|
289
289
|
// vite.config.ts (a Vite SSR server without Nuxt)
|
|
290
|
-
import {
|
|
290
|
+
import { nxgtMaterial } from '@nxgt/material-vue/vite';
|
|
291
291
|
|
|
292
292
|
export default defineConfig({
|
|
293
|
-
plugins: [
|
|
294
|
-
ssr: { noExternal: ['@nxgt/material-vue', '@nxgt/material-core'] },
|
|
293
|
+
plugins: [...nxgtMaterial({ pdf: { bundle: true } })],
|
|
295
294
|
});
|
|
296
295
|
```
|
|
297
296
|
|
|
297
|
+
`pdf: { bundle: true }` adds the standard-fonts transform
|
|
298
|
+
(`pdfkitStandardFonts()`, still exported on its own) and puts the library, core,
|
|
299
|
+
the renderer, pdfkit and the renderer's runtime dependencies into
|
|
300
|
+
`ssr.noExternal`, merged with your own (`react` and your app's own dependencies
|
|
301
|
+
are yours to bundle). It replaces
|
|
302
|
+
the hand-written `plugins: [pdfkitStandardFonts()]` plus `ssr.noExternal` pair
|
|
303
|
+
([Vite plugin guide](vite-plugin.md#rendering-pdfs-on-a-bundled-server)).
|
|
304
|
+
|
|
298
305
|
`@nxgt/material-core`, which the library's built code imports by name, is
|
|
299
306
|
bundled with it: leave it out and the server fails at boot with
|
|
300
307
|
[`Cannot find package '@nxgt/material-core'`](../troubleshooting.md#cannot-find-package-nxgtmaterial-core).
|
package/docs/guides/ssr.md
CHANGED
|
@@ -134,7 +134,7 @@ What to know in a Nuxt app:
|
|
|
134
134
|
- **A PDF from `@nxgt/material-vue/pdf`** is imported where you use it, like
|
|
135
135
|
the map. `usePdf` renders nothing on the server, and a Nitro route answers
|
|
136
136
|
with `pdfResponse`; a server with no `node_modules` needs
|
|
137
|
-
`nxgtMaterial: { pdf: {
|
|
137
|
+
`nxgtMaterial: { pdf: { bundle: true } }` ([PDF documents](pdf.md)).
|
|
138
138
|
- **Your own browser-only component**: name it `*.client.vue` (Nuxt then
|
|
139
139
|
leaves it out of the server bundle), or use `clientOnly` +
|
|
140
140
|
`defineAsyncComponent` as in the next section. Wrapping a statically
|