@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.
Files changed (104) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +18 -5
  3. package/dist/{blockDiagram-BEXU5L5S-CQaNV8rX.js → blockDiagram-BEXU5L5S-BozTHxCb.js} +1 -1
  4. package/dist/{chunk-3FUC2YCW-DmtO_Y9L.js → chunk-3FUC2YCW-zWkkqKwC.js} +1 -1
  5. package/dist/{chunk-7M6MHVWA-BQ0cJz5u.js → chunk-7M6MHVWA-CGHIsno5.js} +1 -1
  6. package/dist/{chunk-GNY47TPC-DiVMvIit.js → chunk-GNY47TPC-BWB3R7nr.js} +4 -4
  7. package/dist/{chunk-NETBCI7D-B5Av2fJk.js → chunk-NETBCI7D-DwmNwFxb.js} +1 -1
  8. package/dist/{chunk-Z7XXMR3K-D4uKosJW.js → chunk-Z7XXMR3K-0DGn7Rxi.js} +1 -1
  9. package/dist/{classDiagram-v2-NBCMYWYE-BYktffst.js → classDiagram-v2-NBCMYWYE-BERam5ND.js} +2 -2
  10. package/dist/components/components.css +13 -2
  11. package/dist/components/ui/feedback/spinner/spinner.css +28 -0
  12. package/dist/components/ui/fields/otp-field/input-otp.css +48 -0
  13. package/dist/components/ui/media/pdf-viewer/pdf-viewer.css +24 -0
  14. package/dist/components/ui/overlays/drawer/vaul-drawer.css +199 -0
  15. package/dist/components/ui/overlays/drawer/vaul-keyframes.css +95 -0
  16. package/dist/{components-C-y3kTRm.js → components-B-xPQ6XX.js} +118106 -129100
  17. package/dist/components.js +6 -6
  18. package/dist/{dagre-6A5THRUB-BvQAvoOX.js → dagre-6A5THRUB-CnIZQoNt.js} +3 -3
  19. package/dist/{diagram-22UHCM2B-BYd0lEdS.js → diagram-22UHCM2B-DN4-okl5.js} +2 -2
  20. package/dist/{diagram-3UASUU5V-B3ndNq2p.js → diagram-3UASUU5V-DcrNf7TY.js} +1 -1
  21. package/dist/{elk-276RUBZZ-BxXn1nvc.js → elk-276RUBZZ-B7yctkbs.js} +3 -3
  22. package/dist/{erDiagram-OPXOYQCR-DjYy4h_r.js → erDiagram-OPXOYQCR-BVrTNrkn.js} +2 -2
  23. package/dist/{field-blur-V8o79CDN.js → field-blur-DDfQkuSJ.js} +866 -871
  24. package/dist/{flowDiagram-KWPJA3E3-BD7xvcMh.js → flowDiagram-KWPJA3E3-BKxvqo0x.js} +3 -3
  25. package/dist/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
  26. package/dist/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
  27. package/dist/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
  28. package/dist/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
  29. package/dist/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
  30. package/dist/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
  31. package/dist/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
  32. package/dist/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
  33. package/dist/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
  34. package/dist/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
  35. package/dist/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
  36. package/dist/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
  37. package/dist/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
  38. package/dist/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
  39. package/dist/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
  40. package/dist/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
  41. package/dist/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
  42. package/dist/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
  43. package/dist/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
  44. package/dist/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
  45. package/dist/{ganttDiagram-FUAMR5RP-BAuk7X6i.js → ganttDiagram-FUAMR5RP-Cv9W4LOS.js} +1 -1
  46. package/dist/hooks.js +25 -25
  47. package/dist/{journeyDiagram-ZHPQQLJL-h2S56BMC.js → journeyDiagram-ZHPQQLJL-BwSv9L1j.js} +1 -1
  48. package/dist/lib/components/ui/dates/calendar/use-stable-component.d.ts +10 -0
  49. package/dist/lib/components/ui/dates/date-field/date-field-trigger.d.ts +1 -1
  50. package/dist/lib/components/ui/dates/date-range-field/date-range-field-trigger.d.ts +1 -1
  51. package/dist/lib/components/ui/dates/picker-trigger/use-trigger-locale.d.ts +10 -0
  52. package/dist/lib/components/ui/fields/attributes/attribute-form/consts.d.ts +5 -1
  53. package/dist/lib/components/ui/fields/attributes/attribute-form/types.d.ts +2 -2
  54. package/dist/lib/components/ui/fields/opening-hours/consts.d.ts +12 -1
  55. package/dist/lib/components/ui/media/image-cropper/image-crop-aspects.d.ts +6 -0
  56. package/dist/lib/components/ui/media/image-cropper/use-frame-size.d.ts +4 -0
  57. package/dist/lib/components/ui/primitives/index.d.ts +1 -0
  58. package/dist/lib/components/ui/primitives/nonce-provider/index.d.ts +1 -0
  59. package/dist/lib/components/ui/primitives/nonce-provider/nonce-provider.d.ts +20 -0
  60. package/dist/lib/components/ui/primitives/typography/typography.d.ts +7 -2
  61. package/dist/lib/components/ui/widgets/rich-text-editor/extensions/extensions.d.ts +2 -2
  62. package/dist/lib/components/ui/widgets/rich-text-editor/use-rich-text-editor.d.ts +17 -0
  63. package/dist/lib/hooks/index.d.ts +1 -0
  64. package/dist/lib/hooks/use-csp-nonce.d.ts +16 -0
  65. package/dist/lib/lib/index.d.ts +1 -0
  66. package/dist/lib/vite/index.d.ts +3 -2
  67. package/dist/lib.js +30 -29
  68. package/dist/main.js +16 -16
  69. package/dist/map.js +1 -1
  70. package/dist/material.css +25 -1
  71. package/dist/{mermaid.core-BWF4Tq7X.js → mermaid.core-D_eXACPr.js} +21 -21
  72. package/dist/{mindmap-definition-NLK3R4M7-Q-4J7KMu.js → mindmap-definition-NLK3R4M7-GMAPXP2T.js} +2 -2
  73. package/dist/{pieDiagram-5QR66LMP-BIxQ164y.js → pieDiagram-5QR66LMP--qZHRFqG.js} +1 -1
  74. package/dist/{quadrantDiagram-O4NWA36T-BlI0otnv.js → quadrantDiagram-O4NWA36T-BxFGymBB.js} +1 -1
  75. package/dist/{requirementDiagram-PLB6GJNP-CuVPUh0S.js → requirementDiagram-PLB6GJNP-D4aL7HiM.js} +2 -2
  76. package/dist/{sankeyDiagram-IPEJSGJF-Cke5i_9w.js → sankeyDiagram-IPEJSGJF-zX1FL2aL.js} +1 -1
  77. package/dist/{stateDiagram-v2-GCMORJYK-CT53U-pL.js → stateDiagram-v2-GCMORJYK-CDm1mDv2.js} +2 -2
  78. package/dist/{swimlanes-2SLR337P-BwEtm09G.js → swimlanes-2SLR337P-DNwIhCCu.js} +3 -3
  79. package/dist/{swimlanesDiagram-TC7HE7FX-36JXV6X2.js → swimlanesDiagram-TC7HE7FX-BaR9zAzq.js} +3 -3
  80. package/dist/{timeline-definition-EJHVYXUP-BeX-S1oM.js → timeline-definition-EJHVYXUP-B6Xd8Spe.js} +1 -1
  81. package/dist/{use-heading-spy--7KHY3lw.js → use-heading-spy-wlOv87q3.js} +63 -57
  82. package/dist/{usecaseDiagram-POWQR4AR-Dfd0rXLr.js → usecaseDiagram-POWQR4AR-XW5of8Kj.js} +2 -2
  83. package/dist/vite.js +2 -2
  84. package/dist/{xychartDiagram-PMCCYNJV-D0UPPflL.js → xychartDiagram-PMCCYNJV-DTV6AzhO.js} +1 -1
  85. package/docs/README.md +1 -0
  86. package/docs/components/data-display.md +5 -0
  87. package/docs/components/date-and-time.md +6 -0
  88. package/docs/components/feedback-and-status.md +3 -1
  89. package/docs/components/form-fields.md +14 -0
  90. package/docs/components/primitives.md +41 -2
  91. package/docs/getting-started.md +5 -1
  92. package/docs/guides/csp.md +408 -0
  93. package/docs/guides/helpers.md +2 -0
  94. package/docs/guides/i18n.md +2 -0
  95. package/docs/guides/theming.md +27 -0
  96. package/docs/roadmap.md +17 -1
  97. package/docs/troubleshooting.md +180 -0
  98. package/lib/components/components.css +13 -2
  99. package/lib/components/ui/feedback/spinner/spinner.css +28 -0
  100. package/lib/components/ui/fields/otp-field/input-otp.css +48 -0
  101. package/lib/components/ui/media/pdf-viewer/pdf-viewer.css +24 -0
  102. package/lib/components/ui/overlays/drawer/vaul-drawer.css +199 -0
  103. package/lib/components/ui/overlays/drawer/vaul-keyframes.css +95 -0
  104. package/package.json +4 -4
@@ -18,6 +18,7 @@ words in your error.
18
18
  **Styles & assets**
19
19
 
20
20
  - [Components render unstyled, with no error](#components-render-unstyled-with-no-error)
21
+ - [Math renders in the system fonts](#math-renders-in-the-system-fonts)
21
22
  - [`<Icon>` renders nothing](#icon-renders-nothing)
22
23
  - [An accessibility audit reports low contrast on a badge, a button, a toast or a menu entry](#an-accessibility-audit-reports-low-contrast-on-a-badge-a-button-a-toast-or-a-menu-entry)
23
24
  - [`ARIA hidden element must not be focusable or contain focusable elements`](#aria-hidden-element-must-not-be-focusable-or-contain-focusable-elements)
@@ -122,6 +123,7 @@ words in your error.
122
123
  - [A test that finds a chip by `aria-selected` or `getByRole('option')` no longer finds it](#a-test-that-finds-a-chip-by-aria-selected-or-getbyroleoption-no-longer-finds-it)
123
124
  - [The `ref` of a `SelectChipField` focuses nothing](#the-ref-of-a-selectchipfield-focuses-nothing)
124
125
  - [`touchedFields` stays empty after leaving a field, or `mode: 'onBlur'` never validates](#touchedfields-stays-empty-after-leaving-a-field-or-mode-onblur-never-validates)
126
+ - ["Required" shows as soon as the calendar opens, or while a date is being picked](#required-shows-as-soon-as-the-calendar-opens-or-while-a-date-is-being-picked)
125
127
  - [`TypeError: values.map is not a function` (a `SliderFormField`)](#typeerror-valuesmap-is-not-a-function)
126
128
  - [An end-to-end test no longer finds `aria-expanded` on a `ResponsiveButton`](#an-end-to-end-test-no-longer-finds-aria-expanded-on-a-responsivebutton)
127
129
  - [A multi-step form reopens at a later step](#a-multi-step-form-reopens-at-a-later-step)
@@ -134,6 +136,7 @@ words in your error.
134
136
  - [A button named "Icon plus Add" is now named "Add"](#a-button-named-icon-plus-add-is-now-named-add)
135
137
  - [`An invalid form control with name='…' is not focusable` (an `UploadField`)](#an-invalid-form-control-with-name-is-not-focusable)
136
138
  - [A `DateField` or `DateRangeField` with `disabled` still opens](#a-datefield-or-daterangefield-with-disabled-still-opens)
139
+ - [The date field shows MM/DD/YYYY on a French page](#the-date-field-shows-mmddyyyy-on-a-french-page)
137
140
  - [An `id`, `aria-describedby` or `data-testid` on a `DateField`, `DateRangeField` or `TimeField` is not on the trigger](#an-id-aria-describedby-or-data-testid-on-a-datefield-daterangefield-or-timefield-is-not-on-the-trigger)
138
141
  - [A test no longer finds the label or helper text inside a date or time picker's trigger](#a-test-no-longer-finds-the-label-or-helper-text-inside-a-date-or-time-pickers-trigger)
139
142
  - [A test no longer finds a `DurationField` input by the field's label, or a `Slider`'s `aria-label` on its root](#a-test-no-longer-finds-a-durationfield-input-by-the-fields-label-or-a-sliders-aria-label-on-its-root)
@@ -204,6 +207,14 @@ words in your error.
204
207
  - [`onPageChange` reports another page after opening on `initialPage`](#onpagechange-reports-another-page-after-opening-on-initialpage)
205
208
  - [`FilePreview` shows a file card with a Download link for a PDF](#filepreview-shows-a-file-card-with-a-download-link-for-a-pdf)
206
209
  - [`FilePreview` says "This file could not be previewed."](#filepreview-says-this-file-could-not-be-previewed)
210
+
211
+ **Content Security Policy**
212
+
213
+ - [`Refused to apply inline style because it violates the following Content Security Policy directive`](#refused-to-apply-inline-style-because-it-violates-the-following-content-security-policy-directive)
214
+ - [`Refused to load the font 'data:font/woff2…' because it violates the following Content Security Policy directive: "font-src 'self'"`](#refused-to-load-the-font-datafontwoff2-because-it-violates-the-following-content-security-policy-directive-font-src-self)
215
+ - [`Refused to load the font 'https://fonts.gstatic.com/…' because it violates the following Content Security Policy directive: "font-src 'self'"`](#refused-to-load-the-font-httpsfontsgstaticcom-because-it-violates-the-following-content-security-policy-directive-font-src-self)
216
+ - [`Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script`](#refused-to-evaluate-a-string-as-javascript-because-unsafe-eval-is-not-an-allowed-source-of-script)
217
+
207
218
  - [Reporting a bug](#reporting-a-bug)
208
219
 
209
220
  ---
@@ -372,6 +383,21 @@ first three, on an older version, add the ones you use after it:
372
383
  `CodeBlock`'s theme and the other components' CSS have no file you can
373
384
  import before 2.2.0: upgrade.
374
385
 
386
+ ### Math renders in the system fonts
387
+
388
+ **When:** a formula in `Markdown` (or `ChatMarkdown`) shows in a serif or
389
+ sans-serif system font: letters look upright or thin, `\mathbb{R}` is a
390
+ plain R, large brackets and roots are mis-sized, and the network tab shows a
391
+ `404` for `fonts/katex/KaTeX_*.woff2`, or no request at all.
392
+ **Why:** the compiled stylesheet (`dist/material.css`) loads KaTeX's fonts from
393
+ `dist/fonts/katex/`, through a relative `url()`. The stylesheet was served
394
+ without that folder beside it (copied alone to a public folder or a CDN).
395
+ **Fix:** serve `dist/fonts/` next to the stylesheet, at the same relative
396
+ path, or let your bundler import it through the `style` condition
397
+ (`@import "@nxgt/material";`), which emits the fonts as files (Vite inlines the 3.6 KB `KaTeX_Size3` font unless `assetsInlineLimit` is set: see the CSP guide's KaTeX fonts section). On the Tailwind route
398
+ (`@nxgt/material/styles.css`) the fonts come from `katex` itself, and your bundler
399
+ emits them.
400
+
375
401
  ### `<Icon>` renders nothing
376
402
 
377
403
  **When:** every `Icon` (and anything that uses one) is blank. The network tab
@@ -2237,6 +2263,24 @@ For a field of your own, pass `field.onBlur` to the control that loses focus:
2237
2263
  What counts as leaving, per kind of field:
2238
2264
  [forms](guides/forms.md#touched-on-blur).
2239
2265
 
2266
+ ### "Required" shows as soon as the calendar opens, or while a date is being picked
2267
+
2268
+ **When:** a `DateFormField` or `DateRangeFormField` that is empty and required,
2269
+ in a form with `mode: 'onTouched'` or `'onBlur'`, shows its error (and
2270
+ `touchedFields` flips) after the user changes the month, opens the year view
2271
+ or goes to today, before any date is chosen. 2.14.0 and earlier.
2272
+
2273
+ **Why:** `Calendar` gave `react-day-picker` new `Nav`, `CaptionLabel` and
2274
+ `MonthGrid` components each time the month or the view changed. The button that
2275
+ had focus was unmounted, focus fell to the page, and the field, which is
2276
+ touched when focus leaves it and its popover, took that for leaving.
2277
+
2278
+ **Fix:** upgrade to 2.15.0 or later. Until then, validate on
2279
+ submit (`mode: 'onSubmit'`) or hide the message until the popover is closed.
2280
+ From that version, opening, moving in the calendar, Escape and picking leave
2281
+ the field untouched; it is touched when the popover closes without a value by a
2282
+ click outside, or when the trigger loses focus with the popover closed.
2283
+
2240
2284
  ### `TypeError: values.map is not a function`
2241
2285
 
2242
2286
  **When:** a `SliderFormField` with one thumb crashes (in `SliderReadout`) as
@@ -2541,6 +2585,27 @@ react-day-picker's day rule, so `true` meant "every day".
2541
2585
  The same release moves `id`, `aria-describedby` and `data-*` to the trigger:
2542
2586
  see the next entry.
2543
2587
 
2588
+ ### The date field shows MM/DD/YYYY on a French page
2589
+
2590
+ **When:** a `DateField`, `DateFormField`, `DateRangeField` or `DateRangeFormField` (and a `TimeField`, which shows `2:30 PM` instead of `14:30`) under a French
2591
+ `I18nProvider` shows 1 September 2028 as `09/01/2028` in its trigger, while the
2592
+ calendar inside it is in French. No error is printed.
2593
+ **Why:** the trigger formatted the date without a locale, so it was always
2594
+ English (`MM/DD/YYYY`), whatever the language of the page.
2595
+ **Fix:** upgrade: the trigger now writes the date (and the time, for a
2596
+ `TimeField` or a `DateRangeField` with `withTime`) in the language of the
2597
+ calendar: the field's own `locale`, else the `I18nProvider` language, else
2598
+ English. Mount the provider above the field:
2599
+
2600
+ ```tsx
2601
+ <I18nProvider initialLanguage="fr">
2602
+ <DateField label="Livraison" value={value} onValueChange={setValue} />
2603
+ </I18nProvider>
2604
+ ```
2605
+
2606
+ With no provider, or in English, the output is unchanged (`09/01/2028`). The
2607
+ document's `<html lang>` and the browser's language are not read.
2608
+
2544
2609
  ### An `id`, `aria-describedby` or `data-testid` on a `DateField`, `DateRangeField` or `TimeField` is not on the trigger
2545
2610
 
2546
2611
  **When:** before 2.2.0: `getByTestId` finds the calendar (and only while the
@@ -3781,6 +3846,121 @@ beginning is shown, with "Showing the first … of this file." below.
3781
3846
 
3782
3847
  ---
3783
3848
 
3849
+ ## Content Security Policy
3850
+
3851
+ How to set up the policy is in the [Content Security Policy guide](guides/csp.md).
3852
+
3853
+ ### `Refused to apply inline style because it violates the following Content Security Policy directive`
3854
+
3855
+ **When:** a component mounts, or a modal opens, on a page that sends
3856
+ `style-src-elem` or `style-src` without `'unsafe-inline'`. The message ends
3857
+ with `style-src-elem` for a `<style>` element and `style-src-attr` for a
3858
+ `style="…"` attribute.
3859
+ **Why:** a `<style>` element needs the page's nonce, an attribute cannot carry
3860
+ one. The usual sources are the components listed in
3861
+ [the guide](guides/csp.md#style-elements-the-components-inject).
3862
+ `ScrollArea`, `Select`, `RichTextEditor` and the scroll lock of dialogs and
3863
+ sheets take the nonce from `NonceProvider`, and report this when the app has
3864
+ none (or gives it another nonce than the header's). A `<style>` in mermaid's
3865
+ SVG is a [known gap](guides/csp.md#known-gaps). `Toaster`, `Drawer`,
3866
+ `InputOTP`, `ImageCropper`, `Spinner` and `PdfReader` create none: their CSS is in the stylesheet, so if one of them looks unstyled, the
3867
+ app imports neither `styles.css` nor `dist/material.css`.
3868
+ **Fix:** for `style-src-attr`, allow attributes: they cannot be nonced.
3869
+
3870
+ ```http
3871
+ Content-Security-Policy: style-src-elem 'self' 'nonce-X'; style-src-attr 'unsafe-inline'
3872
+ ```
3873
+
3874
+ For `style-src-elem`, wrap the app in `NonceProvider`, on the server and on
3875
+ the client, with the nonce of the header
3876
+ ([the guide](guides/csp.md#nonceprovider)):
3877
+
3878
+ ```tsx
3879
+ import { NonceProvider } from '@nxgt/material';
3880
+
3881
+ <NonceProvider nonce={nonce}>
3882
+ <App />
3883
+ </NonceProvider>;
3884
+ ```
3885
+
3886
+ For a mermaid diagram, serve that route with `'unsafe-inline'` in
3887
+ `style-src-elem` instead of the nonce (a nonce in the same directive makes
3888
+ browsers ignore `'unsafe-inline'`).
3889
+
3890
+ Once a page has a header-delivered CSP, browsers blank the `nonce` attribute, so React's development build may warn of an attribute mismatch when it hydrates a server-rendered `<style nonce>`. The warning is harmless.
3891
+
3892
+ ### `Refused to load the font 'data:font/woff2…' because it violates the following Content Security Policy directive: "font-src 'self'"`
3893
+
3894
+ **When:** a formula renders in `Markdown`, with `font-src 'self'`.
3895
+ **Why:** a KaTeX font reached the browser as a `data:` URI. Vite inlines any
3896
+ asset under its 4096-byte limit, and `KaTeX_Size3-Regular.woff2` is 3.6 KB:
3897
+ on the Tailwind route (`@nxgt/material/styles.css`), and on the compiled route
3898
+ when your Vite build processes `dist/material.css`. Earlier versions' compiled
3899
+ stylesheet inlined all 60 KaTeX fonts itself; it now ships them as files.
3900
+ **Fix:** upgrade, then stop Vite from inlining fonts:
3901
+
3902
+ ```ts
3903
+ // vite.config.ts
3904
+ import { defineConfig } from 'vite';
3905
+
3906
+ export default defineConfig({
3907
+ build: {
3908
+ assetsInlineLimit: (file) => (/\.(woff2?|ttf)$/.test(file) ? false : undefined),
3909
+ },
3910
+ });
3911
+ ```
3912
+
3913
+ ### `Refused to load the font 'https://fonts.gstatic.com/…' because it violates the following Content Security Policy directive: "font-src 'self'"`
3914
+
3915
+ **When:** the page loads a font from `https://fonts.googleapis.com` or
3916
+ `https://fonts.gstatic.com`, or from `rsms.me`, on a page whose `font-src` is
3917
+ `'self'` (the message names the font's URL). The stylesheet that declares it
3918
+ is refused too, as a `style-src-elem` violation.
3919
+ **Why:** the app's `<link>` or `@import` for Inter asks another origin for CSS
3920
+ and woff2 files, and the policy allows only your own.
3921
+ **Fix:** host the font: install `@fontsource-variable/inter`, import it, and set
3922
+ `--font-sans` to `"Inter Variable"` followed by the system stack in the root
3923
+ `@theme`. The woff2 files then come from your build
3924
+ ([CSP guide](guides/csp.md#inter--the-sans-font)):
3925
+
3926
+ ```css
3927
+ @import "@fontsource-variable/inter";
3928
+
3929
+ @theme {
3930
+ --font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
3931
+ }
3932
+ ```
3933
+
3934
+ Delete the Google Fonts `<link>`, and the `fonts.googleapis.com` and
3935
+ `fonts.gstatic.com` sources you may have added to the policy.
3936
+
3937
+ ### `Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script`
3938
+
3939
+ **When:** an `EvalError` with this message, or a CSP report with
3940
+ `effectiveDirective: script-src` and `sample: ''`, from a stack that ends in
3941
+ zod (`allowsEval`, in `zod/v4/core/util.js`), on a page whose `script-src`
3942
+ has no `'unsafe-eval'`. Nothing visibly breaks.
3943
+ **Why:** the first `z.object()` built in the page makes zod test whether
3944
+ `new Function` works. The error is caught, but the browser sends the
3945
+ violation report. The library builds no schema at import any more (zod is
3946
+ not bundled, its schemas are factories), so the report comes from a schema
3947
+ built before `z.config({ jitless: true })` ran: one of yours, or the first
3948
+ render of a component that validates. Versions that bundled zod built their
3949
+ own schemas at import, before the app could configure anything.
3950
+ **Fix:** switch zod to its interpreter in a module the app imports before
3951
+ anything else, and build your own schemas inside functions rather than at
3952
+ module scope when that module may load first
3953
+ ([CSP guide](guides/csp.md#zod-and-unsafe-eval)):
3954
+
3955
+ ```ts
3956
+ // zod-config.ts, imported first in the app entry
3957
+ import { z } from 'zod';
3958
+
3959
+ z.config({ jitless: true });
3960
+ ```
3961
+
3962
+ ---
3963
+
3784
3964
  ## Reporting a bug
3785
3965
 
3786
3966
  If an error comes from inside this package and none of the entries above
@@ -3,10 +3,21 @@
3
3
  * styles.css and never dist/material.css, gets it too: only
4
4
  * dist/material.css collected a component's own CSS import.
5
5
  *
6
- * PdfReader: react-pdf's text layer (selection, search highlights) and
7
- * pdf.js's annotation layer (links, notes, form widgets). */
6
+ * PdfReader: react-pdf's text layer (selection, search highlights),
7
+ * pdf.js's annotation layer (links, notes, form widgets), and our highlights
8
+ * and night mode. */
8
9
  @import "react-pdf/dist/Page/TextLayer.css";
9
10
  @import "react-pdf/dist/Page/AnnotationLayer.css";
11
+ @import "./ui/media/pdf-viewer/pdf-viewer.css";
12
+ /* What sonner (Toaster), vaul (Drawer) and input-otp (InputOTP) would inject
13
+ * as `<style>` elements at runtime: the build strips those injections
14
+ * (build/strip-style-injection.ts), so a strict CSP needs no 'unsafe-inline'.
15
+ * Spinner's variant 4 keyframes, for the same reason. */
16
+ @import "sonner/dist/styles.css";
17
+ @import "./ui/overlays/drawer/vaul-drawer.css";
18
+ @import "./ui/overlays/drawer/vaul-keyframes.css";
19
+ @import "./ui/fields/otp-field/input-otp.css";
20
+ @import "./ui/feedback/spinner/spinner.css";
10
21
  /* KaTeX (Markdown), highlight.js (RichTextEditor code blocks),
11
22
  * react-easy-crop (ImageCropper), the code theme (CodeBlock), and the few
12
23
  * components styled by a stylesheet of their own rather than by classes. */
@@ -0,0 +1,28 @@
1
+ @keyframes spin {
2
+ to {
3
+ transform: rotate(360deg);
4
+ }
5
+ }
6
+
7
+ @keyframes spin2 {
8
+ 0% {
9
+ stroke-dasharray: 1, 800;
10
+ stroke-dashoffset: 0;
11
+ }
12
+ 50% {
13
+ stroke-dasharray: 400, 400;
14
+ stroke-dashoffset: -200px;
15
+ }
16
+ 100% {
17
+ stroke-dasharray: 800, 1;
18
+ stroke-dashoffset: -800px;
19
+ }
20
+ }
21
+
22
+ .spin2 {
23
+ transform-origin: center;
24
+ animation:
25
+ spin2 1.5s ease-in-out infinite,
26
+ spin 2s linear infinite;
27
+ animation-direction: alternate;
28
+ }
@@ -0,0 +1,48 @@
1
+ /* The rules input-otp (React, 1.4.2) and vue-input-otp (0.4.0) add on mount,
2
+ * through `insertRule` into a `<style id="input-otp-style">`: the hidden
3
+ * input's caret, selection and autofill stay invisible behind the slots.
4
+ * Here so a strict CSP blocks nothing. @nxgt/material strips input-otp's
5
+ * injection at build (build/strip-style-injection.ts); vue-input-otp is
6
+ * external, still injects, and takes the nonce (NonceProvider). The same file
7
+ * in both libraries (scripts/shared-paths.ts). */
8
+ /* biome-ignore-all lint/complexity/noImportantStyles: input-otp's own rules */
9
+ [data-input-otp]::selection {
10
+ background: transparent !important;
11
+ color: transparent !important;
12
+ }
13
+
14
+ /* Two rules: a browser that does not know one pseudo-class drops the list. */
15
+ [data-input-otp]:autofill {
16
+ background: transparent !important;
17
+ color: transparent !important;
18
+ border-color: transparent !important;
19
+ opacity: 0 !important;
20
+ box-shadow: none !important;
21
+ -webkit-box-shadow: none !important;
22
+ -webkit-text-fill-color: transparent !important;
23
+ }
24
+
25
+ [data-input-otp]:-webkit-autofill {
26
+ background: transparent !important;
27
+ color: transparent !important;
28
+ border-color: transparent !important;
29
+ opacity: 0 !important;
30
+ box-shadow: none !important;
31
+ -webkit-box-shadow: none !important;
32
+ -webkit-text-fill-color: transparent !important;
33
+ }
34
+
35
+ @supports (-webkit-touch-callout: none) {
36
+ [data-input-otp] {
37
+ letter-spacing: -0.6em !important;
38
+ font-weight: 100 !important;
39
+ font-stretch: ultra-condensed;
40
+ font-optical-sizing: none !important;
41
+ left: -1px !important;
42
+ right: 1px !important;
43
+ }
44
+ }
45
+
46
+ [data-input-otp] + * {
47
+ pointer-events: all !important;
48
+ }
@@ -0,0 +1,24 @@
1
+ /* PdfReader's search highlights and night mode. react-pdf's text and
2
+ * annotation layers are imported from react-pdf by components.css. The same
3
+ * rules as @nxgt/material-vue's pdf-viewer.css, which also holds its text
4
+ * layer. */
5
+
6
+ .pdf-highlight {
7
+ background: oklch(0.9 0.18 85);
8
+ color: black;
9
+ border-radius: 2px;
10
+ }
11
+
12
+ .pdf-highlight[data-current] {
13
+ outline: 2px solid var(--color-primary);
14
+ outline-offset: 1px;
15
+ }
16
+
17
+ .dark .pdf-highlight {
18
+ background: oklch(0.75 0.18 85);
19
+ color: black;
20
+ }
21
+
22
+ .pdf-night-page {
23
+ filter: invert(0.9) hue-rotate(180deg);
24
+ }
@@ -0,0 +1,199 @@
1
+ /*! vaul 1.1.2's stylesheet (MIT, Emil Kowalski), its rules. vaul injects it as a
2
+ * `<style>` at module load; the library build strips that call
3
+ * (build/strip-style-injection.ts, which pins the injected text) and ships
4
+ * it here instead, so a strict CSP needs no 'unsafe-inline'. `vaul/style.css`
5
+ * is not in vaul's `exports`, hence the copy: update it with vaul.
6
+ *
7
+ * Left out: `[data-vaul-handle-hitarea]: { … }` under `(pointer: fine)`, an
8
+ * invalid selector every browser drops.
9
+ *
10
+ * MIT License. Copyright (c) 2023 Emil Kowalski
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ * of this software and associated documentation files (the "Software"), to deal
14
+ * in the Software without restriction, including without limitation the rights
15
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ * copies of the Software, and to permit persons to whom the Software is
17
+ * furnished to do so, subject to the following conditions:
18
+ *
19
+ * The above copyright notice and this permission notice shall be included in
20
+ * all copies or substantial portions of the Software.
21
+ *
22
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ * IMPLIED. See vaul's LICENSE.md for the full text.
24
+ */
25
+ [data-vaul-drawer] {
26
+ touch-action: none;
27
+ will-change: transform;
28
+ transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
29
+ animation-duration: 0.5s;
30
+ animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
31
+ }
32
+
33
+ [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="bottom"][data-state="open"] {
34
+ animation-name: slideFromBottom;
35
+ }
36
+ [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="bottom"][data-state="closed"] {
37
+ animation-name: slideToBottom;
38
+ }
39
+
40
+ [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="top"][data-state="open"] {
41
+ animation-name: slideFromTop;
42
+ }
43
+ [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="top"][data-state="closed"] {
44
+ animation-name: slideToTop;
45
+ }
46
+
47
+ [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="left"][data-state="open"] {
48
+ animation-name: slideFromLeft;
49
+ }
50
+ [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="left"][data-state="closed"] {
51
+ animation-name: slideToLeft;
52
+ }
53
+
54
+ [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="right"][data-state="open"] {
55
+ animation-name: slideFromRight;
56
+ }
57
+ [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="right"][data-state="closed"] {
58
+ animation-name: slideToRight;
59
+ }
60
+
61
+ [data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="bottom"] {
62
+ transform: translate3d(0, var(--initial-transform, 100%), 0);
63
+ }
64
+
65
+ [data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="top"] {
66
+ transform: translate3d(0, calc(var(--initial-transform, 100%) * -1), 0);
67
+ }
68
+
69
+ [data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="left"] {
70
+ transform: translate3d(calc(var(--initial-transform, 100%) * -1), 0, 0);
71
+ }
72
+
73
+ [data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="right"] {
74
+ transform: translate3d(var(--initial-transform, 100%), 0, 0);
75
+ }
76
+
77
+ [data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="top"] {
78
+ transform: translate3d(0, var(--snap-point-height, 0), 0);
79
+ }
80
+
81
+ [data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="bottom"] {
82
+ transform: translate3d(0, var(--snap-point-height, 0), 0);
83
+ }
84
+
85
+ [data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="left"] {
86
+ transform: translate3d(var(--snap-point-height, 0), 0, 0);
87
+ }
88
+
89
+ [data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="right"] {
90
+ transform: translate3d(var(--snap-point-height, 0), 0, 0);
91
+ }
92
+
93
+ [data-vaul-overlay][data-vaul-snap-points="false"] {
94
+ animation-duration: 0.5s;
95
+ animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
96
+ }
97
+ [data-vaul-overlay][data-vaul-snap-points="false"][data-state="open"] {
98
+ animation-name: fadeIn;
99
+ }
100
+ [data-vaul-overlay][data-state="closed"] {
101
+ animation-name: fadeOut;
102
+ }
103
+
104
+ [data-vaul-animate="false"] {
105
+ /* biome-ignore lint/complexity/noImportantStyles: vaul's own rule */
106
+ animation: none !important;
107
+ }
108
+
109
+ [data-vaul-overlay][data-vaul-snap-points="true"] {
110
+ opacity: 0;
111
+ transition: opacity 0.5s cubic-bezier(0.32, 0.72, 0, 1);
112
+ }
113
+
114
+ [data-vaul-overlay][data-vaul-snap-points="true"] {
115
+ opacity: 1;
116
+ }
117
+
118
+ [data-vaul-drawer]:not([data-vaul-custom-container="true"])::after {
119
+ content: "";
120
+ position: absolute;
121
+ background: inherit;
122
+ background-color: inherit;
123
+ }
124
+
125
+ [data-vaul-drawer][data-vaul-drawer-direction="top"]::after {
126
+ top: initial;
127
+ bottom: 100%;
128
+ left: 0;
129
+ right: 0;
130
+ height: 200%;
131
+ }
132
+
133
+ [data-vaul-drawer][data-vaul-drawer-direction="bottom"]::after {
134
+ top: 100%;
135
+ bottom: initial;
136
+ left: 0;
137
+ right: 0;
138
+ height: 200%;
139
+ }
140
+
141
+ [data-vaul-drawer][data-vaul-drawer-direction="left"]::after {
142
+ left: initial;
143
+ right: 100%;
144
+ top: 0;
145
+ bottom: 0;
146
+ width: 200%;
147
+ }
148
+
149
+ [data-vaul-drawer][data-vaul-drawer-direction="right"]::after {
150
+ left: 100%;
151
+ right: initial;
152
+ top: 0;
153
+ bottom: 0;
154
+ width: 200%;
155
+ }
156
+
157
+ [data-vaul-overlay][data-vaul-snap-points="true"]:not(
158
+ [data-vaul-snap-points-overlay="true"]
159
+ ):not([data-state="closed"]) {
160
+ opacity: 0;
161
+ }
162
+
163
+ [data-vaul-overlay][data-vaul-snap-points-overlay="true"] {
164
+ opacity: 1;
165
+ }
166
+
167
+ [data-vaul-handle] {
168
+ display: block;
169
+ position: relative;
170
+ opacity: 0.7;
171
+ background: #e2e2e4;
172
+ margin-left: auto;
173
+ margin-right: auto;
174
+ height: 5px;
175
+ width: 32px;
176
+ border-radius: 1rem;
177
+ touch-action: pan-y;
178
+ }
179
+
180
+ [data-vaul-handle]:active,
181
+ [data-vaul-handle]:hover {
182
+ opacity: 1;
183
+ }
184
+
185
+ [data-vaul-handle-hitarea] {
186
+ position: absolute;
187
+ left: 50%;
188
+ top: 50%;
189
+ transform: translate(-50%, -50%);
190
+ width: max(100%, 2.75rem); /* 44px */
191
+ height: max(100%, 2.75rem); /* 44px */
192
+ touch-action: inherit;
193
+ }
194
+
195
+ @media (hover: hover) and (pointer: fine) {
196
+ [data-vaul-drawer] {
197
+ user-select: none;
198
+ }
199
+ }
@@ -0,0 +1,95 @@
1
+ /*! vaul 1.1.2's stylesheet (MIT, Emil Kowalski), its keyframes. vaul injects it as a
2
+ * `<style>` at module load; the library build strips that call
3
+ * (build/strip-style-injection.ts, which pins the injected text) and ships
4
+ * it here instead, so a strict CSP needs no 'unsafe-inline'. `vaul/style.css`
5
+ * is not in vaul's `exports`, hence the copy: update it with vaul.
6
+ *
7
+ * MIT License. Copyright (c) 2023 Emil Kowalski
8
+ *
9
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ * of this software and associated documentation files (the "Software"), to deal
11
+ * in the Software without restriction, including without limitation the rights
12
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ * copies of the Software, and to permit persons to whom the Software is
14
+ * furnished to do so, subject to the following conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall be included in
17
+ * all copies or substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ * IMPLIED. See vaul's LICENSE.md for the full text.
21
+ */
22
+ @keyframes fadeIn {
23
+ from {
24
+ opacity: 0;
25
+ }
26
+ to {
27
+ opacity: 1;
28
+ }
29
+ }
30
+
31
+ @keyframes fadeOut {
32
+ to {
33
+ opacity: 0;
34
+ }
35
+ }
36
+
37
+ @keyframes slideFromBottom {
38
+ from {
39
+ transform: translate3d(0, var(--initial-transform, 100%), 0);
40
+ }
41
+ to {
42
+ transform: translate3d(0, 0, 0);
43
+ }
44
+ }
45
+
46
+ @keyframes slideToBottom {
47
+ to {
48
+ transform: translate3d(0, var(--initial-transform, 100%), 0);
49
+ }
50
+ }
51
+
52
+ @keyframes slideFromTop {
53
+ from {
54
+ transform: translate3d(0, calc(var(--initial-transform, 100%) * -1), 0);
55
+ }
56
+ to {
57
+ transform: translate3d(0, 0, 0);
58
+ }
59
+ }
60
+
61
+ @keyframes slideToTop {
62
+ to {
63
+ transform: translate3d(0, calc(var(--initial-transform, 100%) * -1), 0);
64
+ }
65
+ }
66
+
67
+ @keyframes slideFromLeft {
68
+ from {
69
+ transform: translate3d(calc(var(--initial-transform, 100%) * -1), 0, 0);
70
+ }
71
+ to {
72
+ transform: translate3d(0, 0, 0);
73
+ }
74
+ }
75
+
76
+ @keyframes slideToLeft {
77
+ to {
78
+ transform: translate3d(calc(var(--initial-transform, 100%) * -1), 0, 0);
79
+ }
80
+ }
81
+
82
+ @keyframes slideFromRight {
83
+ from {
84
+ transform: translate3d(var(--initial-transform, 100%), 0, 0);
85
+ }
86
+ to {
87
+ transform: translate3d(0, 0, 0);
88
+ }
89
+ }
90
+
91
+ @keyframes slideToRight {
92
+ to {
93
+ transform: translate3d(var(--initial-transform, 100%), 0, 0);
94
+ }
95
+ }