@nxgt/material 2.12.0 → 2.14.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 (134) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +19 -6
  3. package/dist/{_Uint8Array-azThpjEP.js → _Uint8Array-QCcRMhXd.js} +1 -1
  4. package/dist/{_overRest-DMn4Dt71.js → _overRest-C21ZmKyH.js} +1 -1
  5. package/dist/{async-slice-DhZDjEtw.js → async-slice-TBZ6Taw2.js} +2 -2
  6. package/dist/{blockDiagram-BEXU5L5S-CnFXw-7E.js → blockDiagram-BEXU5L5S-Cn9cG3vt.js} +2 -2
  7. package/dist/{chunk-3FUC2YCW-3vMiTgTl.js → chunk-3FUC2YCW-XrT5pZo5.js} +2 -2
  8. package/dist/{chunk-7M6MHVWA-NPaJJ6Gy.js → chunk-7M6MHVWA-Uf-8NCRF.js} +1 -1
  9. package/dist/{chunk-GNY47TPC-DCFqYc_z.js → chunk-GNY47TPC-CGgBmY0O.js} +4 -4
  10. package/dist/{chunk-NETBCI7D-BB4rjLSQ.js → chunk-NETBCI7D-g-jPsM9q.js} +1 -1
  11. package/dist/{chunk-Z7XXMR3K-vmLFt9Cr.js → chunk-Z7XXMR3K-BzO1efzA.js} +1 -1
  12. package/dist/{classDiagram-v2-NBCMYWYE-eUVWeh_6.js → classDiagram-v2-NBCMYWYE-7WgpfVcn.js} +2 -2
  13. package/dist/components/components.css +13 -2
  14. package/dist/components/ui/feedback/spinner/spinner.css +28 -0
  15. package/dist/components/ui/fields/otp-field/input-otp.css +48 -0
  16. package/dist/components/ui/media/pdf-viewer/pdf-viewer.css +24 -0
  17. package/dist/components/ui/overlays/drawer/vaul-drawer.css +199 -0
  18. package/dist/components/ui/overlays/drawer/vaul-keyframes.css +95 -0
  19. package/dist/{components-BYxsGbyO.js → components-qTUjhFuo.js} +116423 -127245
  20. package/dist/components.js +6 -6
  21. package/dist/{dagre-6A5THRUB-LmEtEC93.js → dagre-6A5THRUB-BOuRxJdA.js} +11 -11
  22. package/dist/{diagram-22UHCM2B-BwARaM-x.js → diagram-22UHCM2B-CF0vLJbn.js} +2 -2
  23. package/dist/{diagram-3UASUU5V-BkeqK_e8.js → diagram-3UASUU5V-RLo-MTfV.js} +1 -1
  24. package/dist/dnd.js +1 -1
  25. package/dist/{elk-276RUBZZ-DL4pxCSP.js → elk-276RUBZZ-Dr2yBM7k.js} +3 -3
  26. package/dist/{erDiagram-OPXOYQCR-Ajlj2lcb.js → erDiagram-OPXOYQCR-l7kPZ4FO.js} +2 -2
  27. package/dist/{field-blur-DgfIJceK.js → field-blur-BKGVq9_k.js} +865 -870
  28. package/dist/{flowDiagram-KWPJA3E3-BOEc9KSn.js → flowDiagram-KWPJA3E3-BQEK3XTl.js} +3 -3
  29. package/dist/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
  30. package/dist/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
  31. package/dist/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
  32. package/dist/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
  33. package/dist/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
  34. package/dist/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
  35. package/dist/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
  36. package/dist/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
  37. package/dist/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
  38. package/dist/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
  39. package/dist/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
  40. package/dist/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
  41. package/dist/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
  42. package/dist/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
  43. package/dist/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
  44. package/dist/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
  45. package/dist/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
  46. package/dist/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
  47. package/dist/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
  48. package/dist/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
  49. package/dist/{ganttDiagram-FUAMR5RP-DdxLcLNx.js → ganttDiagram-FUAMR5RP-WYm8XV7U.js} +1 -1
  50. package/dist/{graphlib-DLjE3QPd.js → graphlib-CbcZi5bx.js} +6 -6
  51. package/dist/hooks.js +26 -26
  52. package/dist/{i18n-provider-CQL7fqLy.js → i18n-provider-D5hkauu6.js} +1 -1
  53. package/dist/i18n.js +6 -6
  54. package/dist/{isArrayLikeObject-CO-5KAzG.js → isArrayLikeObject-COiPdhFP.js} +3 -3
  55. package/dist/{isEmpty-Bl1drJE7.js → isEmpty-B7UlCLih.js} +3 -3
  56. package/dist/{journeyDiagram-ZHPQQLJL-BIzB9BVn.js → journeyDiagram-ZHPQQLJL-p90NnenY.js} +1 -1
  57. package/dist/lib/components/ui/fields/attributes/attribute-form/consts.d.ts +5 -1
  58. package/dist/lib/components/ui/fields/attributes/attribute-form/types.d.ts +2 -2
  59. package/dist/lib/components/ui/fields/opening-hours/consts.d.ts +12 -1
  60. package/dist/lib/components/ui/fields/search-field/index.d.ts +2 -2
  61. package/dist/lib/components/ui/media/image-cropper/image-crop-aspects.d.ts +6 -0
  62. package/dist/lib/components/ui/media/image-cropper/use-frame-size.d.ts +4 -0
  63. package/dist/lib/components/ui/primitives/index.d.ts +1 -0
  64. package/dist/lib/components/ui/primitives/nonce-provider/index.d.ts +1 -0
  65. package/dist/lib/components/ui/primitives/nonce-provider/nonce-provider.d.ts +20 -0
  66. package/dist/lib/components/ui/selection/autocomplete/autocomplete.d.ts +2 -34
  67. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-announcer.d.ts +10 -0
  68. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-chips.d.ts +8 -3
  69. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-list-status.d.ts +13 -0
  70. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-popup.d.ts +26 -0
  71. package/dist/lib/components/ui/selection/autocomplete/types.d.ts +72 -0
  72. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-filter.d.ts +13 -0
  73. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-handlers.d.ts +17 -0
  74. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-search.d.ts +8 -0
  75. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-status.d.ts +28 -0
  76. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-values.d.ts +27 -0
  77. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete.d.ts +29 -0
  78. package/dist/lib/components/ui/selection/radio-group/radio-group.d.ts +7 -4
  79. package/dist/lib/components/ui/widgets/rich-text-editor/extensions/extensions.d.ts +2 -2
  80. package/dist/lib/components/ui/widgets/rich-text-editor/use-rich-text-editor.d.ts +17 -0
  81. package/dist/lib/hooks/index.d.ts +1 -0
  82. package/dist/lib/hooks/use-csp-nonce.d.ts +16 -0
  83. package/dist/lib/i18n/resources/en/en.d.ts +4 -0
  84. package/dist/lib/i18n/resources/en/material.json.d.ts +5 -1
  85. package/dist/lib/i18n/resources/fr/fr.d.ts +4 -0
  86. package/dist/lib/i18n/resources/fr/material.json.d.ts +5 -1
  87. package/dist/lib/i18n/resources/resources.d.ts +8 -0
  88. package/dist/lib/i18n/translator.d.ts +1 -1
  89. package/dist/lib/lib/index.d.ts +1 -0
  90. package/dist/lib/vite/index.d.ts +3 -2
  91. package/dist/lib.js +32 -31
  92. package/dist/main.js +16 -16
  93. package/dist/map.js +2 -2
  94. package/dist/material.css +25 -1
  95. package/dist/{merge-CwwtONfp.js → merge-KEf_u3Zy.js} +4 -4
  96. package/dist/{mermaid.core-6yWyNdZa.js → mermaid.core-CW8DO1T1.js} +21 -21
  97. package/dist/{mindmap-definition-NLK3R4M7-aHycauO9.js → mindmap-definition-NLK3R4M7-DJcCg9PL.js} +2 -2
  98. package/dist/{pieDiagram-5QR66LMP-DEiooyQu.js → pieDiagram-5QR66LMP-b_dHXzkV.js} +1 -1
  99. package/dist/print.js +2 -2
  100. package/dist/{quadrantDiagram-O4NWA36T-CLlaqEdw.js → quadrantDiagram-O4NWA36T-BSqAcBs7.js} +1 -1
  101. package/dist/{requirementDiagram-PLB6GJNP-DD74Pa6_.js → requirementDiagram-PLB6GJNP-BcxkmIFp.js} +2 -2
  102. package/dist/{sankeyDiagram-IPEJSGJF-DAR9G0yC.js → sankeyDiagram-IPEJSGJF-aD5r_n1q.js} +1 -1
  103. package/dist/{sortable-provider-D4S-mqLP.js → sortable-provider-C8mr3XP2.js} +1 -1
  104. package/dist/{stateDiagram-v2-GCMORJYK-pti7a_r-.js → stateDiagram-v2-GCMORJYK-Ct9ylaoP.js} +2 -2
  105. package/dist/{stored-language-BW32owje.js → stored-language-g121Yv0w.js} +10 -2
  106. package/dist/{swimlanes-2SLR337P-D10pSc8m.js → swimlanes-2SLR337P-BwCqwEoR.js} +3 -3
  107. package/dist/{swimlanesDiagram-TC7HE7FX-B8HgZod5.js → swimlanesDiagram-TC7HE7FX-WxBGUt0w.js} +3 -3
  108. package/dist/{timeline-definition-EJHVYXUP-D8CWcNj3.js → timeline-definition-EJHVYXUP-5Q8GU13Q.js} +1 -1
  109. package/dist/{translate-material-ZEzF-LKH.js → translate-material-D3Hp_AI5.js} +2 -2
  110. package/dist/{translator-BfqVlNGW.js → translator-BMFC0lBV.js} +1 -1
  111. package/dist/{use-heading-spy-DvZVu15k.js → use-heading-spy-wlOv87q3.js} +65 -59
  112. package/dist/{use-material-translation-B2GUtxUY.js → use-material-translation-BGFp1yXp.js} +2 -2
  113. package/dist/{usecaseDiagram-POWQR4AR-DIdH4Oia.js → usecaseDiagram-POWQR4AR-CzGOd4ic.js} +2 -2
  114. package/dist/vite.js +2 -2
  115. package/dist/{xychartDiagram-PMCCYNJV-aecZdRhd.js → xychartDiagram-PMCCYNJV-Bh_1zZoW.js} +1 -1
  116. package/docs/README.md +1 -0
  117. package/docs/components/data-display.md +5 -0
  118. package/docs/components/form-fields.md +14 -0
  119. package/docs/components/primitives.md +31 -0
  120. package/docs/components/selection-and-choice.md +74 -4
  121. package/docs/getting-started.md +5 -1
  122. package/docs/guides/csp.md +408 -0
  123. package/docs/guides/helpers.md +2 -0
  124. package/docs/guides/theming.md +27 -0
  125. package/docs/roadmap.md +21 -1
  126. package/docs/troubleshooting.md +220 -0
  127. package/lib/components/components.css +13 -2
  128. package/lib/components/ui/feedback/spinner/spinner.css +28 -0
  129. package/lib/components/ui/fields/otp-field/input-otp.css +48 -0
  130. package/lib/components/ui/media/pdf-viewer/pdf-viewer.css +24 -0
  131. package/lib/components/ui/overlays/drawer/vaul-drawer.css +199 -0
  132. package/lib/components/ui/overlays/drawer/vaul-keyframes.css +95 -0
  133. package/package.json +9 -4
  134. package/dist/lib/components/ui/fields/search-field/debounce.d.ts +0 -5
@@ -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)
@@ -140,6 +141,9 @@ words in your error.
140
141
  - [A test no longer finds `aria-invalid` on an upload field's group or drop zone](#a-test-no-longer-finds-aria-invalid-on-an-upload-fields-group-or-drop-zone)
141
142
  - [A test no longer finds `aria-invalid` on a `CheckboxGroup`](#a-test-no-longer-finds-aria-invalid-on-a-checkboxgroup)
142
143
  - [A test no longer finds `aria-invalid` on a `Switch` row or an `Autocomplete`'s chips](#a-test-no-longer-finds-aria-invalid-on-a-switch-row-or-an-autocompletes-chips)
144
+ - [An `Autocomplete` lists more options than before, or "ecole" finds "École"](#an-autocomplete-lists-more-options-than-before-or-ecole-finds-école)
145
+ - [`onQueryChange` runs more often after upgrading](#onquerychange-runs-more-often-after-upgrading)
146
+ - [An older search's results replace the newer ones in an `Autocomplete`](#an-older-searchs-results-replace-the-newer-ones-in-an-autocomplete)
143
147
  - [A test no longer finds `aria-invalid` on an `OtpField` slot](#a-test-no-longer-finds-aria-invalid-on-an-otpfield-slot)
144
148
  - [A test no longer finds the breadcrumb's home button](#a-test-no-longer-finds-the-breadcrumbs-home-button)
145
149
  - [A test no longer finds a sidebar entry by the `button` role](#a-test-no-longer-finds-a-sidebar-entry-by-the-button-role)
@@ -151,6 +155,7 @@ words in your error.
151
155
  - [A test no longer finds a calendar day by its number, or by "Today" or "selected"](#a-test-no-longer-finds-a-calendar-day-by-its-number-or-by-today-or-selected)
152
156
  - [`#<name>` no longer finds a `SwitchFormField` or `CheckboxFormField`](#name-no-longer-finds-a-switchformfield-or-checkboxformfield)
153
157
  - [`#<name>` or `#<option value>` no longer finds a radio or checkbox group or one of its options](#name-or-option-value-no-longer-finds-a-radio-or-checkbox-group-or-one-of-its-options)
158
+ - [One option of a `RadioGroup` cannot be disabled](#one-option-of-a-radiogroup-cannot-be-disabled)
154
159
  - [`#<name>` no longer finds a text, OTP, number, date or picker form field, or a second form's field has no name](#name-no-longer-finds-a-text-otp-number-date-or-picker-form-field-or-a-second-forms-field-has-no-name)
155
160
  - [A double click or Enter submits a form twice, or a test finds `aria-busy` in an idle `Button`](#a-double-click-or-enter-submits-a-form-twice-or-a-test-finds-aria-busy-in-an-idle-button)
156
161
  - [An empty `UploadField` or `TimeField` is no longer tinted](#an-empty-uploadfield-or-timefield-is-no-longer-tinted)
@@ -200,6 +205,14 @@ words in your error.
200
205
  - [`onPageChange` reports another page after opening on `initialPage`](#onpagechange-reports-another-page-after-opening-on-initialpage)
201
206
  - [`FilePreview` shows a file card with a Download link for a PDF](#filepreview-shows-a-file-card-with-a-download-link-for-a-pdf)
202
207
  - [`FilePreview` says "This file could not be previewed."](#filepreview-says-this-file-could-not-be-previewed)
208
+
209
+ **Content Security Policy**
210
+
211
+ - [`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)
212
+ - [`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)
213
+ - [`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)
214
+ - [`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)
215
+
203
216
  - [Reporting a bug](#reporting-a-bug)
204
217
 
205
218
  ---
@@ -368,6 +381,21 @@ first three, on an older version, add the ones you use after it:
368
381
  `CodeBlock`'s theme and the other components' CSS have no file you can
369
382
  import before 2.2.0: upgrade.
370
383
 
384
+ ### Math renders in the system fonts
385
+
386
+ **When:** a formula in `Markdown` (or `ChatMarkdown`) shows in a serif or
387
+ sans-serif system font: letters look upright or thin, `\mathbb{R}` is a
388
+ plain R, large brackets and roots are mis-sized, and the network tab shows a
389
+ `404` for `fonts/katex/KaTeX_*.woff2`, or no request at all.
390
+ **Why:** the compiled stylesheet (`dist/material.css`) loads KaTeX's fonts from
391
+ `dist/fonts/katex/`, through a relative `url()`. The stylesheet was served
392
+ without that folder beside it (copied alone to a public folder or a CDN).
393
+ **Fix:** serve `dist/fonts/` next to the stylesheet, at the same relative
394
+ path, or let your bundler import it through the `style` condition
395
+ (`@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
396
+ (`@nxgt/material/styles.css`) the fonts come from `katex` itself, and your bundler
397
+ emits them.
398
+
371
399
  ### `<Icon>` renders nothing
372
400
 
373
401
  **When:** every `Icon` (and anything that uses one) is blank. The network tab
@@ -2674,6 +2702,60 @@ await expect(page.getByRole('switch', { name: 'Notifications' })).toHaveAttribut
2674
2702
  await expect(page.getByRole('combobox', { name: 'Country' })).toHaveAttribute('aria-invalid', 'true');
2675
2703
  ```
2676
2704
 
2705
+ ### An `Autocomplete` lists more options than before, or "ecole" finds "École"
2706
+
2707
+ **When:** after upgrading to 2.12.0, typing in an `Autocomplete` with local
2708
+ `options` keeps options it used to drop: `e` keeps "Évry", `orle` keeps
2709
+ "Orléans".
2710
+ **Why:** local matching is now accent-insensitive by default (`filter`
2711
+ defaults to `'accent-insensitive'`): the label or value and the query are
2712
+ both lowercased in the provider's language and stripped of their accents
2713
+ before they are compared. Before, an accent had to be typed to match.
2714
+ **Fix:** nothing, if that is what your users expect. To match as before,
2715
+ pass your own `filter`; to show `options` exactly as you give them (they are
2716
+ filtered on your server), pass `filter="none"`:
2717
+
2718
+ ```tsx
2719
+ // Back to a plain case-insensitive match, accents counted:
2720
+ <Autocomplete
2721
+ mode="single"
2722
+ options={options}
2723
+ value={value}
2724
+ filter={(option, query) => option.label.toLowerCase().includes(query.toLowerCase())}
2725
+ />
2726
+ ```
2727
+
2728
+ ### `onQueryChange` runs more often after upgrading
2729
+
2730
+ **When:** after upgrading to 2.12.0, `onQueryChange` is called sooner after the
2731
+ user stops typing, so a server you fetch from there gets more requests.
2732
+ **Why:** the pause it waits for is now `debounceMs`, 250 ms by default; it
2733
+ was a fixed 1 s.
2734
+ **Fix:** set the pause you had, `debounceMs={1000}`, or move the fetch to `onSearch`, which
2735
+ also aborts the previous request and drops a stale response.
2736
+
2737
+ ### An older search's results replace the newer ones in an `Autocomplete`
2738
+
2739
+ **When:** you fetch `options` yourself from `onQueryChange`, and a slow response
2740
+ for an earlier query lands after a faster one for the latest: the list shows
2741
+ the results of what the user typed before.
2742
+ **Why:** each of your requests resolves whenever the server answers, and
2743
+ the last one to resolve wins.
2744
+ **Fix:** use `onSearch`. It hands each search an `AbortSignal` (pass it to
2745
+ `fetch`), aborts the previous search, and drops any response that lands for
2746
+ an older query, even from an API that ignores the signal:
2747
+
2748
+ ```tsx
2749
+ <Autocomplete
2750
+ mode="single"
2751
+ value={value}
2752
+ onValueChange={(next) => setValue(next ?? '')}
2753
+ onSearch={(query, { signal }) =>
2754
+ fetch(`/api/people?q=${encodeURIComponent(query)}`, { signal }).then((r) => r.json())
2755
+ }
2756
+ />
2757
+ ```
2758
+
2677
2759
  ### A test no longer finds `aria-invalid` on an `OtpField` slot
2678
2760
 
2679
2761
  **When:** `[data-slot="input-otp-slot"][aria-invalid]` (or
@@ -2884,6 +2966,29 @@ await page.locator('#married').getByRole('radio', { name: 'Yes' }).check();
2884
2966
  await page.locator('#married-0').check(); // the first option
2885
2967
  ```
2886
2968
 
2969
+ ### One option of a `RadioGroup` cannot be disabled
2970
+
2971
+ **When:** you need one choice greyed out (a person already linked, a plan the
2972
+ account cannot take) and `disabled` on the group disables all of them. Before
2973
+ this release the options had no `disabled`, so apps listed the unavailable ones
2974
+ apart or hand-built the group from `RadioGroupItem`.
2975
+ **Why:** an option was only `{ value, label }`; `disabled` was the group's.
2976
+ **Fix:** upgrade, and set `disabled` on the option. It is rendered and
2977
+ announced as disabled, its label is muted (still 4.5:1) and the arrow keys
2978
+ skip it; the group's own `disabled` still disables every option. An option
2979
+ that is disabled while it is the current `value` stays checked, as in the
2980
+ Radix and Reka primitives:
2981
+
2982
+ ```tsx
2983
+ <RadioGroup
2984
+ label="Person"
2985
+ options={[
2986
+ { value: 'ada', label: 'Ada' },
2987
+ { value: 'bob', label: 'Bob (already linked)', disabled: true },
2988
+ ]}
2989
+ />
2990
+ ```
2991
+
2887
2992
  ### `#<name>` no longer finds a text, OTP, number, date or picker form field, or a second form's field has no name
2888
2993
 
2889
2994
  **When:** two forms on one page have a field with the same `name` (a sign-in
@@ -3700,6 +3805,121 @@ beginning is shown, with "Showing the first … of this file." below.
3700
3805
 
3701
3806
  ---
3702
3807
 
3808
+ ## Content Security Policy
3809
+
3810
+ How to set up the policy is in the [Content Security Policy guide](guides/csp.md).
3811
+
3812
+ ### `Refused to apply inline style because it violates the following Content Security Policy directive`
3813
+
3814
+ **When:** a component mounts, or a modal opens, on a page that sends
3815
+ `style-src-elem` or `style-src` without `'unsafe-inline'`. The message ends
3816
+ with `style-src-elem` for a `<style>` element and `style-src-attr` for a
3817
+ `style="…"` attribute.
3818
+ **Why:** a `<style>` element needs the page's nonce, an attribute cannot carry
3819
+ one. The usual sources are the components listed in
3820
+ [the guide](guides/csp.md#style-elements-the-components-inject).
3821
+ `ScrollArea`, `Select`, `RichTextEditor` and the scroll lock of dialogs and
3822
+ sheets take the nonce from `NonceProvider`, and report this when the app has
3823
+ none (or gives it another nonce than the header's). A `<style>` in mermaid's
3824
+ SVG is a [known gap](guides/csp.md#known-gaps). `Toaster`, `Drawer`,
3825
+ `InputOTP`, `ImageCropper`, `Spinner` and `PdfReader` create none: their CSS is in the stylesheet, so if one of them looks unstyled, the
3826
+ app imports neither `styles.css` nor `dist/material.css`.
3827
+ **Fix:** for `style-src-attr`, allow attributes: they cannot be nonced.
3828
+
3829
+ ```http
3830
+ Content-Security-Policy: style-src-elem 'self' 'nonce-X'; style-src-attr 'unsafe-inline'
3831
+ ```
3832
+
3833
+ For `style-src-elem`, wrap the app in `NonceProvider`, on the server and on
3834
+ the client, with the nonce of the header
3835
+ ([the guide](guides/csp.md#nonceprovider)):
3836
+
3837
+ ```tsx
3838
+ import { NonceProvider } from '@nxgt/material';
3839
+
3840
+ <NonceProvider nonce={nonce}>
3841
+ <App />
3842
+ </NonceProvider>;
3843
+ ```
3844
+
3845
+ For a mermaid diagram, serve that route with `'unsafe-inline'` in
3846
+ `style-src-elem` instead of the nonce (a nonce in the same directive makes
3847
+ browsers ignore `'unsafe-inline'`).
3848
+
3849
+ 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.
3850
+
3851
+ ### `Refused to load the font 'data:font/woff2…' because it violates the following Content Security Policy directive: "font-src 'self'"`
3852
+
3853
+ **When:** a formula renders in `Markdown`, with `font-src 'self'`.
3854
+ **Why:** a KaTeX font reached the browser as a `data:` URI. Vite inlines any
3855
+ asset under its 4096-byte limit, and `KaTeX_Size3-Regular.woff2` is 3.6 KB:
3856
+ on the Tailwind route (`@nxgt/material/styles.css`), and on the compiled route
3857
+ when your Vite build processes `dist/material.css`. Earlier versions' compiled
3858
+ stylesheet inlined all 60 KaTeX fonts itself; it now ships them as files.
3859
+ **Fix:** upgrade, then stop Vite from inlining fonts:
3860
+
3861
+ ```ts
3862
+ // vite.config.ts
3863
+ import { defineConfig } from 'vite';
3864
+
3865
+ export default defineConfig({
3866
+ build: {
3867
+ assetsInlineLimit: (file) => (/\.(woff2?|ttf)$/.test(file) ? false : undefined),
3868
+ },
3869
+ });
3870
+ ```
3871
+
3872
+ ### `Refused to load the font 'https://fonts.gstatic.com/…' because it violates the following Content Security Policy directive: "font-src 'self'"`
3873
+
3874
+ **When:** the page loads a font from `https://fonts.googleapis.com` or
3875
+ `https://fonts.gstatic.com`, or from `rsms.me`, on a page whose `font-src` is
3876
+ `'self'` (the message names the font's URL). The stylesheet that declares it
3877
+ is refused too, as a `style-src-elem` violation.
3878
+ **Why:** the app's `<link>` or `@import` for Inter asks another origin for CSS
3879
+ and woff2 files, and the policy allows only your own.
3880
+ **Fix:** host the font: install `@fontsource-variable/inter`, import it, and set
3881
+ `--font-sans` to `"Inter Variable"` followed by the system stack in the root
3882
+ `@theme`. The woff2 files then come from your build
3883
+ ([CSP guide](guides/csp.md#inter--the-sans-font)):
3884
+
3885
+ ```css
3886
+ @import "@fontsource-variable/inter";
3887
+
3888
+ @theme {
3889
+ --font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
3890
+ }
3891
+ ```
3892
+
3893
+ Delete the Google Fonts `<link>`, and the `fonts.googleapis.com` and
3894
+ `fonts.gstatic.com` sources you may have added to the policy.
3895
+
3896
+ ### `Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script`
3897
+
3898
+ **When:** an `EvalError` with this message, or a CSP report with
3899
+ `effectiveDirective: script-src` and `sample: ''`, from a stack that ends in
3900
+ zod (`allowsEval`, in `zod/v4/core/util.js`), on a page whose `script-src`
3901
+ has no `'unsafe-eval'`. Nothing visibly breaks.
3902
+ **Why:** the first `z.object()` built in the page makes zod test whether
3903
+ `new Function` works. The error is caught, but the browser sends the
3904
+ violation report. The library builds no schema at import any more (zod is
3905
+ not bundled, its schemas are factories), so the report comes from a schema
3906
+ built before `z.config({ jitless: true })` ran: one of yours, or the first
3907
+ render of a component that validates. Versions that bundled zod built their
3908
+ own schemas at import, before the app could configure anything.
3909
+ **Fix:** switch zod to its interpreter in a module the app imports before
3910
+ anything else, and build your own schemas inside functions rather than at
3911
+ module scope when that module may load first
3912
+ ([CSP guide](guides/csp.md#zod-and-unsafe-eval)):
3913
+
3914
+ ```ts
3915
+ // zod-config.ts, imported first in the app entry
3916
+ import { z } from 'zod';
3917
+
3918
+ z.config({ jitless: true });
3919
+ ```
3920
+
3921
+ ---
3922
+
3703
3923
  ## Reporting a bug
3704
3924
 
3705
3925
  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
+ }