@nxgt/material-core 0.11.1 → 0.13.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 (114) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +283 -24
  3. package/dist/backgrounds/activity.d.ts +11 -0
  4. package/dist/backgrounds/activity.d.ts.map +1 -0
  5. package/dist/backgrounds/build.d.ts +9 -0
  6. package/dist/backgrounds/build.d.ts.map +1 -0
  7. package/dist/backgrounds/id-prefix.d.ts +8 -0
  8. package/dist/backgrounds/id-prefix.d.ts.map +1 -0
  9. package/dist/backgrounds/index.d.ts +20 -0
  10. package/dist/backgrounds/index.d.ts.map +1 -0
  11. package/dist/backgrounds/index.js +467 -0
  12. package/dist/backgrounds/index.js.map +20 -0
  13. package/dist/backgrounds/noise.d.ts +7 -0
  14. package/dist/backgrounds/noise.d.ts.map +1 -0
  15. package/dist/backgrounds/presets/grain.d.ts +9 -0
  16. package/dist/backgrounds/presets/grain.d.ts.map +1 -0
  17. package/dist/backgrounds/presets.d.ts +16 -0
  18. package/dist/backgrounds/presets.d.ts.map +1 -0
  19. package/dist/backgrounds/primitives.d.ts +22 -0
  20. package/dist/backgrounds/primitives.d.ts.map +1 -0
  21. package/dist/backgrounds/rng.d.ts +12 -0
  22. package/dist/backgrounds/rng.d.ts.map +1 -0
  23. package/dist/backgrounds/scatter.d.ts +25 -0
  24. package/dist/backgrounds/scatter.d.ts.map +1 -0
  25. package/dist/backgrounds/scene.d.ts +21 -0
  26. package/dist/backgrounds/scene.d.ts.map +1 -0
  27. package/dist/backgrounds/tones.d.ts +4 -0
  28. package/dist/backgrounds/tones.d.ts.map +1 -0
  29. package/dist/backgrounds/types.d.ts +53 -0
  30. package/dist/backgrounds/types.d.ts.map +1 -0
  31. package/dist/chunks/index-mw5fa8qa.js +119 -0
  32. package/dist/chunks/index-mw5fa8qa.js.map +12 -0
  33. package/dist/csp/index.d.ts +10 -0
  34. package/dist/csp/index.d.ts.map +1 -0
  35. package/dist/csp/index.js +21 -0
  36. package/dist/csp/index.js.map +9 -0
  37. package/dist/csp/nonce.d.ts +35 -0
  38. package/dist/csp/nonce.d.ts.map +1 -0
  39. package/dist/csp/policy.d.ts +33 -0
  40. package/dist/csp/policy.d.ts.map +1 -0
  41. package/dist/csp/zod-jitless.d.ts +4 -0
  42. package/dist/csp/zod-jitless.d.ts.map +1 -0
  43. package/dist/lib/drawer-room.d.ts +8 -0
  44. package/dist/lib/drawer-room.d.ts.map +1 -0
  45. package/dist/lib/drawer-room.js +17 -0
  46. package/dist/lib/drawer-room.js.map +10 -0
  47. package/dist/lib/escape-forward.d.ts +14 -0
  48. package/dist/lib/escape-forward.d.ts.map +1 -1
  49. package/dist/lib/escape-forward.js +22 -2
  50. package/dist/lib/escape-forward.js.map +3 -3
  51. package/dist/lib/initials.d.ts +12 -0
  52. package/dist/lib/initials.d.ts.map +1 -0
  53. package/dist/lib/initials.js +15 -0
  54. package/dist/lib/initials.js.map +10 -0
  55. package/dist/pdf-viewer/index.d.ts +1 -0
  56. package/dist/pdf-viewer/index.d.ts.map +1 -1
  57. package/dist/pdf-viewer/index.js +68 -38
  58. package/dist/pdf-viewer/index.js.map +6 -5
  59. package/dist/pdf-viewer/pdf-reveal-hit.d.ts +7 -0
  60. package/dist/pdf-viewer/pdf-reveal-hit.d.ts.map +1 -0
  61. package/dist/pdf-viewer/pdf-scroll-spy.d.ts +24 -8
  62. package/dist/pdf-viewer/pdf-scroll-spy.d.ts.map +1 -1
  63. package/dist/states/copy.d.ts +46 -0
  64. package/dist/states/copy.d.ts.map +1 -0
  65. package/dist/states/describe-error.d.ts +18 -0
  66. package/dist/states/describe-error.d.ts.map +1 -0
  67. package/dist/states/illustrations.d.ts +24 -0
  68. package/dist/states/illustrations.d.ts.map +1 -0
  69. package/dist/states/index.d.ts +11 -0
  70. package/dist/states/index.d.ts.map +1 -0
  71. package/dist/states/index.js +253 -0
  72. package/dist/states/index.js.map +13 -0
  73. package/dist/states/kinds.d.ts +28 -0
  74. package/dist/states/kinds.d.ts.map +1 -0
  75. package/dist/vite/csp-dev-header.d.ts +11 -0
  76. package/dist/vite/csp-dev-header.d.ts.map +1 -0
  77. package/dist/vite/csp-hashes.d.ts +23 -0
  78. package/dist/vite/csp-hashes.d.ts.map +1 -0
  79. package/dist/vite/csp-header.d.ts +10 -0
  80. package/dist/vite/csp-header.d.ts.map +1 -0
  81. package/dist/vite/csp-plugin.d.ts +12 -0
  82. package/dist/vite/csp-plugin.d.ts.map +1 -0
  83. package/dist/vite/css-chain.d.ts +13 -0
  84. package/dist/vite/css-chain.d.ts.map +1 -0
  85. package/dist/vite/dependency-closure.d.ts +8 -0
  86. package/dist/vite/dependency-closure.d.ts.map +1 -0
  87. package/dist/vite/fonts-inter.d.ts +15 -0
  88. package/dist/vite/fonts-inter.d.ts.map +1 -0
  89. package/dist/vite/fonts-plugin.d.ts +12 -0
  90. package/dist/vite/fonts-plugin.d.ts.map +1 -0
  91. package/dist/vite/index.d.ts +17 -81
  92. package/dist/vite/index.d.ts.map +1 -1
  93. package/dist/vite/index.js +478 -2
  94. package/dist/vite/index.js.map +18 -4
  95. package/dist/vite/libraries.d.ts +13 -0
  96. package/dist/vite/libraries.d.ts.map +1 -0
  97. package/dist/vite/nxgt-material.d.ts +12 -0
  98. package/dist/vite/nxgt-material.d.ts.map +1 -0
  99. package/dist/vite/options.d.ts +56 -0
  100. package/dist/vite/options.d.ts.map +1 -0
  101. package/dist/vite/pdf-plugin.d.ts +19 -0
  102. package/dist/vite/pdf-plugin.d.ts.map +1 -0
  103. package/dist/vite/pdfkit-standard-fonts.d.ts +74 -0
  104. package/dist/vite/pdfkit-standard-fonts.d.ts.map +1 -0
  105. package/dist/vite/styles-plugin.d.ts +27 -0
  106. package/dist/vite/styles-plugin.d.ts.map +1 -0
  107. package/dist/vite/styles-scan.d.ts +14 -0
  108. package/dist/vite/styles-scan.d.ts.map +1 -0
  109. package/docs/README.md +4 -0
  110. package/docs/backgrounds.md +95 -0
  111. package/docs/roadmap.md +37 -23
  112. package/docs/states.md +85 -0
  113. package/docs/troubleshooting.md +174 -3
  114. package/package.json +40 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # @nxgt/material-core
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#640](https://github.com/softistx/nxgt-material/pull/640) [`f39e0b7`](https://github.com/softistx/nxgt-material/commit/f39e0b7321c6e753896f48119f354bd316cdd909) Thanks [@SteveGT96](https://github.com/SteveGT96)! - New `@nxgt/material-core/backgrounds` subpath: the engine behind the coming background presets, framework-free and with no DOM at import. It holds the types (`BackgroundPreset`, `BackgroundTone`, `BackgroundIntensity`, `MotifOptions`, `SceneNode`...), the `BACKGROUND_PRESETS` table, a seeded generator (`mulberry32`), `valueNoise2`, `scatter`, the motif shapes (`motifShape`, `place`, `MOTIF_SHAPES`, `MOTIF_DEFAULTS`), a plain SVG scene format with `assertSafeScene` (no `style`, `on*`, `href` or external URL, so it is safe under a strict Content Security Policy), `svgIdPrefix` for ids inside `url(#...)`, and `observeBackgroundActivity`, which pauses an animation when the page is hidden, the element is off screen or the user prefers reduced motion.
8
+
9
+ - [#690](https://github.com/softistx/nxgt-material/pull/690) [`9b656e8`](https://github.com/softistx/nxgt-material/commit/9b656e80aef7afb299151e9772c7271c0e3fa377) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `@nxgt/material-core/backgrounds` gets its first scene builder: `buildGrain` (also `buildBackgroundScene('grain', ...)`), a film-noise overlay drawn by an `feTurbulence` filter on one rect, safe under `assertSafeScene`, with ids from the `idPrefix` and the `seed` moving the noise. It also exports `SHIPPED_BACKGROUND_PRESETS`, the `ShippedBackgroundPreset` type (`'gradient' | 'grain'`, widened as presets land) and `resolveShippedPreset`, which falls back to `'gradient'` with a one-time warning outside production.
10
+
11
+ - [#642](https://github.com/softistx/nxgt-material/pull/642) [`2087342`](https://github.com/softistx/nxgt-material/commit/2087342f0b5cd3d987ff0903c0e90e5889c629d4) Thanks [@SteveGT96](https://github.com/SteveGT96)! - New subpath `@nxgt/material-core/csp`, the server and build side of the Content Security Policy (it reads no DOM, so it runs on Node, Bun, Deno, workers and browsers; `/csp-nonce` stays the browser's side). `createCspNonce()` makes a per-request nonce; `cspPolicy({ nonce, dev, hashes, directives })` returns the recommended policy as directives and a `header` (without a nonce, `style-src-elem 'self' 'unsafe-inline'` for the `<style>` elements the libraries create at runtime, unless `hashes.style` is given); `cspHash(source)` gives the `'sha256-…'` source of an inline script or style; `applyCspNonce(html, nonce)` fills the `CSP_NONCE_PLACEHOLDER` a build leaves; `ZOD_JITLESS_SCRIPT` and `zodJitlessTag(nonce)` switch zod's `new Function` probe off from an inline script. `cspPolicy` and `applyCspNonce` throw when given the public placeholder as the nonce, and `applyCspNonce` throws when the HTML holds no placeholder (a hash-mode build, or a custom placeholder not passed as its third argument).
12
+
13
+ - [#649](https://github.com/softistx/nxgt-material/pull/649) [`0fd651b`](https://github.com/softistx/nxgt-material/commit/0fd651b98403966aee8ecd4059048eabbe8c91f6) Thanks [@SteveGT96](https://github.com/SteveGT96)! - Add `@nxgt/material-core/drawer-room`: `drawerRoom(element, top)`, the height an open floating list may take under `top` inside a Drawer without covering its footer (`null` outside a Drawer). `Autocomplete` uses it in both libraries ([#582](https://github.com/softistx/nxgt-material/issues/582)).
14
+
15
+ - [#687](https://github.com/softistx/nxgt-material/pull/687) [`970cf63`](https://github.com/softistx/nxgt-material/commit/970cf63a763be215c4d27cca58a5698d915d6f46) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `nxgtMaterial()` in `@nxgt/material-core/vite` takes a `pdf` option. It is on by default and adds `pdfkitStandardFonts()` (a no-op unless pdfkit goes through the bundle). `pdf: { bundle: true }` also adds the library, `@nxgt/material-core`, `pdfkit`, `@react-pdf/*` and every package the renderer, pdfkit and core need at runtime (their transitive dependencies, read from your installed packages) to `ssr.noExternal` (merged with yours; `noExternal: true` is left alone), for an SSR server whose runtime image has no `node_modules`. `pdf: false` turns it off.
16
+
17
+ - [#651](https://github.com/softistx/nxgt-material/pull/651) [`0da112a`](https://github.com/softistx/nxgt-material/commit/0da112a9353e4bf369a58a8ef62e5ff3b9238f6a) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `@nxgt/material-core/vite` gains `nxgtMaterial(options, library?)`, one Vite plugin for the build-time work of a strict Content Security Policy. Default `csp.mode: 'hash'`: zod's jitless script is injected into `index.html`, the sha256 of every inline script and style is written to `dist/.vite/nxgt-csp.json` (`scriptHashes`, `styleHashes`, `header`) and the header logged at the end of the build: scripts by hash, `style-src-elem 'self' 'unsafe-inline'` with no style hash (the components create `<style>` elements at runtime, and a hash would void `'unsafe-inline'`; nonce mode is the strict option for styles), and no placeholder ever lands in the HTML. `csp: { mode: 'nonce' }` opts into Vite's `html.cspNonce` placeholder for a server that calls `applyCspNonce` per request. It also keeps fonts out of `data:` URIs (composing your `build.assetsInlineLimit`), loads self-hosted Inter and sets `--font-sans` only when the optional peer `@fontsource-variable/inter` is installed (otherwise one warning, in dev and in build, and the system font stays; the build never fails), sends a Report-Only header on the dev server (in hash mode it carries the jitless script's hash), and, given a library name, fails `vite build` with the exact line to add when the stylesheet is missing (neither `styles.css` nor the compiled stylesheet through `@import "<library>";`) or the React `@source` is (`styles.css` only). On Vite 5, which has no build environments, every check runs as for the client. `/vite` now reads stylesheets and installed `package.json` files with `node:fs/promises`, `node:fs` and `node:path` (build-time code, like `/pdfkit-fonts`); it also re-exports `CSP_NONCE_PLACEHOLDER` and `ZOD_JITLESS_SCRIPT`.
18
+
19
+ - [#639](https://github.com/softistx/nxgt-material/pull/639) [`009f7af`](https://github.com/softistx/nxgt-material/commit/009f7afaf99d44cdc960f9666cdb546fd4fc13f7) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `@nxgt/material-core/states`: the kinds of `EmptyState` and `ErrorState` (`EMPTY_STATE_KINDS`, `ERROR_STATE_KINDS`, `resolveEmptyStateKind`, `isErrorKind`), their i18n keys and English fallbacks (`stateCopy`, `STATE_ACTION_KEYS`), `describeError` (an error's name, message and cause chain, never its stack) and the geometry of the illustrations (`STATE_ILLUSTRATIONS`, `STATE_ACCENT`, `STATE_ICON`).
20
+
21
+ `@nxgt/material` and `@nxgt/material-vue` ship new i18n keys in `en` and `fr`, for the components that follow: `material.error-state.*` (`generic`, `offline` and `maintenance` with a title and a description, `retry`, `details`, `details-name`, `details-message`, `details-cause`), `material.table.labels.no-results` and `no-results-description`, `material.command.empty` and `material.pdf-reader.retry`.
22
+
23
+ - [#647](https://github.com/softistx/nxgt-material/pull/647) [`0f88cbd`](https://github.com/softistx/nxgt-material/commit/0f88cbdd0ee2045901d568df2eb769196acaf73b) Thanks [@SteveGT96](https://github.com/SteveGT96)! - Escape closes only an open `ComboboxField`, `Combobox` or `Autocomplete` list inside a `Dialog`, `Sheet`, `Drawer`, `AlertDialog` or `Popover`; a second Escape closes the layer ([#631](https://github.com/softistx/nxgt-material/issues/631)). Before, the first Escape closed the whole layer. A `Select` list has always taken the first Escape, as a layer of its own, and is unchanged. The layer's own `onEscapeKeyDown` (`@escape-key-down`) now sees `defaultPrevented` true for an Escape that only closes a list. `@nxgt/material-core/escape-forward` gains `keepListEscape(event)` for your own overlay.
24
+
25
+ - [#679](https://github.com/softistx/nxgt-material/pull/679) [`c17e6c2`](https://github.com/softistx/nxgt-material/commit/c17e6c235e471990a3315c03a463bfd0c3d0b3e8) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `PdfReader`: a scroll with no input the reader hears (find in page, a screen reader's cursor, setting `scrollTop`) that lands just after the opening page's pin corrects the view is reported once the scroll spy's hold ends, and the pin lets go; before, the page number stayed on the opening page and the next correction scrolled back to it. Fixes [#677](https://github.com/softistx/nxgt-material/issues/677). `@nxgt/material-core/pdf-viewer`'s `createScrollSpy` takes an options object as its fourth argument, `{ idleMs?, readerPosition? }` (a bare number is still `idleMs`): two frames after a hold ends, a scroll it held is measured once when `readerPosition` says the view is no longer where the reader put it; input in the page area during a hold measures a scroll it held. `pageInView(scroller, pages)`, the page most on screen, is exported.
26
+
27
+ ### Patch Changes
28
+
29
+ - [#708](https://github.com/softistx/nxgt-material/pull/708) [`4c5658b`](https://github.com/softistx/nxgt-material/commit/4c5658bba08aac0d5b0224683f9374a1411296ba) Thanks [@SteveGT96](https://github.com/SteveGT96)! - Docs: the getting-started `/backgrounds` rows describe what ships, the roadmaps list the base as shipped and the remaining presets as next, and the troubleshooting pages explain the development warning for a preset that is not available yet, a grain that looks bare without `backgrounds.css`, and (in core) `assertSafeScene`'s errors.
30
+
31
+ - [#691](https://github.com/softistx/nxgt-material/pull/691) [`8feb417`](https://github.com/softistx/nxgt-material/commit/8feb41728fc4d4f9ce3ad4be7c78fd4ddd7b5b67) Thanks [@SteveGT96](https://github.com/SteveGT96)! - The README links the libraries' new "Migrating to the Vite plugin" guide, which moves a hand-written CSP, zod, font and PDF setup to `nxgtMaterial()`.
32
+
33
+ ## 0.12.0
34
+
35
+ ### Minor Changes
36
+
37
+ - [#624](https://github.com/softistx/nxgt-material/pull/624) [`94a77a7`](https://github.com/softistx/nxgt-material/commit/94a77a76b54834bda18d1e2acc494d5a089d245d) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `@nxgt/material-core/initials`: `initials(name)`, an avatar's initials from a name (`'Ada Lovelace'` → `'AL'`), the first letter of the first and last word, upper-cased, a whole code point each. Both libraries' new `AccountMenu` use it ([#585](https://github.com/softistx/nxgt-material/issues/585)).
38
+
3
39
  ## 0.11.1
4
40
 
5
41
  ### Patch Changes
package/README.md CHANGED
@@ -6,20 +6,20 @@ The framework-free core that [`@nxgt/material`](https://www.npmjs.com/package/@n
6
6
  the print formatters, the PDF file helpers, the PDF viewer's framework-free
7
7
  half (page geometry, scroll spy, text search), the currency field's decimals,
8
8
  the file-size formatter, the slider's tick marks, the emoji list's groups,
9
- the date range presets, a debounce, accent-insensitive text matching and a race-free async search (`/debounce`, `/text-match`, `/async-search`), the file preview's kind detection, the fields' blur
9
+ the date range presets, a debounce, accent-insensitive text matching and a race-free async search (`/debounce`, `/text-match`, `/async-search`), the room an open list has above a Drawer's footer (`/drawer-room`), the file preview's kind detection, the fields' blur
10
10
  rule, a field's first reachable control (`/focus-control`) and its `aria-describedby`
11
11
  (`/described-by`), where the focus goes in a repeatable row group
12
12
  (`/repeatable-group`), the page behind a modal layer made inert
13
- (`/inert-others`), Escape pressed while an overlay closes
14
- (`/escape-forward`), the Toaster's guard against a tap's emulated hover
13
+ (`/inert-others`), Escape pressed while an overlay closes or in a field whose
14
+ list is open (`/escape-forward`), the Toaster's guard against a tap's emulated hover
15
15
  (`/touch-hover`), the focus a toast's action hands back (`/toast-focus`), a
16
16
  table row's activation, the zod error map's issue messages
17
17
  (`/zod-issue-messages`), a zod error map that words an issue by its code
18
- (`/code-errors`), the page's Content Security Policy nonce (`/csp-nonce`), an
18
+ (`/code-errors`), the page's Content Security Policy nonce (`/csp-nonce`), the server and build side of the CSP (`/csp`), an
19
19
  object built on its first use (`/lazy-object`), one submit in flight
20
20
  (`/submit-guard`), the two-step verification helpers (`/two-factor`: a TOTP
21
21
  secret grouped to be typed, the recovery codes' text file, a text download),
22
- printing one element of the page (`/print-only`), the Vite plugin that
22
+ the empty and error states' kinds, copy keys, error details and illustrations (`/states`), printing one element of the page (`/print-only`), the engine behind the background presets (`/backgrounds`), the Vite plugin that
23
23
  bundles pdfkit's standard fonts and a `build.assetsInlineLimit` that keeps
24
24
  fonts out of `data:` URIs, and the Node-only resolution of those fonts that
25
25
  the Next.js helper and the Nuxt module run at build time.
@@ -30,7 +30,7 @@ re-export what they always exported, at the same subpaths:
30
30
  from here, and so do their `PdfReader`, `CurrencyField`, form fields (their
31
31
  focus, blur, `aria-describedby` and zod messages), file list and upload
32
32
  field (`formatFileSize`), modal layers (the inert page behind, Escape while
33
- closing), `Toaster`, `DataTable` rows, `@nxgt/material/next` and
33
+ closing, Escape in a field whose list is open), `Toaster`, `DataTable` rows, `@nxgt/material/next` and
34
34
  `@nxgt/material-vue/nuxt`. Import it directly when you want the model or a
35
35
  formatter without a UI library, or one of these helpers in a component of
36
36
  your own.
@@ -60,13 +60,15 @@ files are plain ES modules and also load in Node without a bundler.
60
60
  | Optional peer | Needed by |
61
61
  | --- | --- |
62
62
  | `react`, `@react-pdf/renderer@^4.9` | `@nxgt/material-core/pdf/render` and `/pdf` — rendering a document |
63
- | `vite@^6 \|\| ^7 \|\| ^8` | `@nxgt/material-core/vite` — its declarations name Vite's `Plugin` type (types only); the plugin itself runs in your Vite config |
63
+ | `vite@^5.2 \|\| ^6 \|\| ^7 \|\| ^8` | `@nxgt/material-core/vite` — its declarations name Vite's `Plugin` type (types only); the plugin itself runs in your Vite config |
64
+ | `@fontsource-variable/inter@>=5` | `@nxgt/material-core/vite` — `nxgtMaterial()` loads Inter only when it is installed, else warns and keeps the system font; `fonts: { sans: false }` silences the warning |
64
65
 
65
66
  Every other subpath imports none of them, and is safe to import on a server
66
- with no DOM. One subpath is **Node only**: `/pdfkit-fonts` imports `node:module`
67
- and `node:path`, for a framework's config at build time — never import it
68
- from code a browser bundle can reach. No other subpath imports a `node:`
69
- built-in.
67
+ with no DOM. Two subpaths are **Node only**: `/pdfkit-fonts` imports `node:module`
68
+ and `node:path`, and `/vite` imports `node:fs/promises`, `node:fs` and `node:path`
69
+ (the styles check reads stylesheets from disk; `pdf: { bundle: true }` reads installed `package.json` files), both for a framework's or a
70
+ Vite config at build time — never import them from code a browser bundle
71
+ can reach. No other subpath imports a `node:` built-in.
70
72
 
71
73
  ## Subpaths
72
74
 
@@ -85,14 +87,15 @@ built-in.
85
87
  | `/pdf-source` | `PdfSource` and its helpers (`toPdfjsSource`, `pdfSourceKey`, …) |
86
88
  | `/pdf-file-name` | `resolvePdfFileName`, `DEFAULT_PDF_FILE_NAME` |
87
89
  | `/pdf-download` | `downloadPdfFile`, `printPdfFile` — in a browser |
88
- | `/pdf-viewer` | The PDF viewer's pure half: page geometry and fit (`readerFitScale`, `placeholderSize`, `rotatedSize`, `PdfPageSize`, `FitMode`), page sizes (`readPageSizes`, `createPageSizesLoader`), the scroll spy (`createScrollSpy`, `revealHit`, `scrollIntoArea`), the opening page's pin (`pinToPage`, whose optional `onRelease` runs once when it lets go, and `pinHolds`, whether the view is still where it puts the page), text search (`pageTextFromItems`, `findTextMatches`, `searchDocument`), `escapeHtml`, `mapPdfOutline` |
90
+ | `/pdf-viewer` | The PDF viewer's pure half: page geometry and fit (`readerFitScale`, `placeholderSize`, `rotatedSize`, `PdfPageSize`, `FitMode`), page sizes (`readPageSizes`, `createPageSizesLoader`), the scroll spy (`createScrollSpy` and its `ScrollSpyOptions`, `pageInView`, `revealHit`, `scrollIntoArea`), the opening page's pin (`pinToPage`, whose optional `onRelease` runs once when it lets go, and `pinHolds`, whether the view is still where it puts the page), text search (`pageTextFromItems`, `findTextMatches`, `searchDocument`), `escapeHtml`, `mapPdfOutline` |
89
91
  | `/currency-digits` | `currencyFractionDigits` — the decimals `CurrencyField` shows |
90
92
  | `/field-blur-layers` | `layerOf`, `openerOf`, `FieldLayers` — whose overlay a layer of the page is, for a field's blur |
91
93
  | `/focus-control` | `firstControl`, `focusHandle`, `FocusHandle` — the first control of a field a user can reach, and a stand-in element that focuses it for a form library |
92
94
  | `/repeatable-group` | `focusAfterRemove`, `focusAfterMove`, `focusRowTarget`, `rowMoveTarget`, `canAddRow`, `canRemoveRow`, `RowFocusTarget`, `RowMoveDirection`, `RowAnnouncement`, `ROW_SLOT`, `ROW_FIELDS_SLOT`, `ADD_ATTRIBUTE` — where the focus goes after a row of a `RepeatableGroup` is added, removed or moved, and what its live region says |
93
- | `/escape-forward` | `forwardEscape` — Escape pressed while an overlay closes, pressed again for the overlay below once it has gone |
95
+ | `/escape-forward` | `forwardEscape`, `keepListEscape` — Escape pressed while an overlay closes, pressed again for the overlay below once it has gone; Escape pressed in a field whose list is open, kept from closing the overlay around it |
94
96
  | `/described-by` | `describedBy` — an `aria-describedby` value from the caller's ids and a field's help and error ids |
95
97
  | `/inert-others` | `inertOthers`, `INERT_MARKER` — the rest of the page `inert` behind a modal layer, live regions kept, nested layers counted |
98
+ | `/backgrounds` | `buildBackgroundScene`, `buildGrain`, `assertSafeScene`, `svgIdPrefix`, `observeBackgroundActivity`, `mulberry32`, `valueNoise2`, `scatter`, `motifShape`, `place`, `BACKGROUND_PRESETS`, `SHIPPED_BACKGROUND_PRESETS`, `resolveShippedPreset`, `BACKGROUND_TONES`, `MOTIF_SHAPES`, `MOTIF_DEFAULTS` and the types `BackgroundPreset`, `ShippedBackgroundPreset`, `BackgroundTone`, `BackgroundIntensity`, `MotifOptions`, `SceneNode`, `PresetInfo` — the engine behind the libraries' background presets: a seeded generator, scattering, motif shapes, a plain SVG scene format with its safety check, ids for `url(#…)`, and the observer that pauses an animation |
96
99
  | `/touch-hover` | `ignoreTouchHover`, `TOASTER_PHONE_QUERY`, `TOASTER_PHONE_POSITION` — keeps a tap from hovering an element, so a toast a finger touched does not stay paused; where the `Toaster` puts toasts on a phone |
97
100
  | `/toast-focus` | `focusOrigin`, `handFocusBack`, `trackToasterOrigin`, `TOASTER_SELECTOR` — where the focus was before it entered a toast, and handing it back there before the toast's action runs, so a dialog the action opens keeps the focus |
98
101
  | `/zod-issue-messages` | `sizeMessage`, `invalidFormatMessage`, `IssueText` — the translated text of a zod `too_big` / `too_small` and `invalid_format` issue |
@@ -102,16 +105,20 @@ built-in.
102
105
  | `/emoji-groups` | `groupKey`, `groupEmojis`, `searchEmojis`, `groupName`, `GROUP_NAMES` and the type `EmojiItem` — how the emoji list groups, searches and names its emojis |
103
106
  | `/date-range` | `DATE_RANGE_PRESET_IDS`, `presetRange`, `resolvePresetRange`, `isSameDayRange`, `isRangeDisabled`, `withTimeOf`, `endOfDayTime`, `clampRangeEnd`, `keepRangeTimes` and the types `DayRange`, `PartialDayRange`, `DateRangePreset`, `DateRangePresetId`, `DateRangePresetOption`, `DateRangeRules` — `DateRangeField`'s presets and times |
104
107
  | `/csp-nonce` | `getCspNonce`, `setCspNonce`, `claimCspNonce(nonce, depth)`, `createNoncedStyle`, and `getNonce` / `setNonce` (the `get-nonce` API) — the page's Content Security Policy nonce, for a `<style>` created at runtime; browser only |
108
+ | `/csp` | `createCspNonce`, `cspPolicy`, `cspHash`, `applyCspNonce`, `CSP_NONCE_PLACEHOLDER`, `ZOD_JITLESS_SCRIPT`, `zodJitlessTag` and the types `CspPolicyOptions`, `CspDirectives` — the Content Security Policy for a server or a build: a nonce per request, the recommended header, hashes of inline code, zod's jitless switch; no DOM |
105
109
  | `/lazy-object` | `lazyObject`, `buildOnce` — a value built on its first use rather than when its module loads: the libraries' zod schemas |
106
110
  | `/debounce` | `debounce`, the type `Debounced` — a function called once calls pause, with `cancel` and `flush` (`SearchField`'s `debounceMs`) |
111
+ | `/initials` | `initials` — an avatar's initials from a name, `'Ada Lovelace'` → `'AL'` (`AccountMenu`) |
107
112
  | `/text-match` | `foldText`, `matchesText` — case- and accent-insensitive matching (`'e'` finds "École"), lowercased for a locale |
108
113
  | `/async-search` | `createAsyncSearch`, `searchAnnouncement`, `DEFAULT_SEARCH_DEBOUNCE_MS`, `DEFAULT_MIN_QUERY_LENGTH` and the types `AsyncSearch`, `AsyncSearchConfig`, `AsyncSearchState`, `AsyncSearchStatus`, `SearchFn`, `SearchContext`, `SearchAnnouncement` — `Autocomplete`'s server search: debounced, each run aborting the last, a stale response dropped |
114
+ | `/drawer-room` | `drawerRoom(element, top)` — the height in px an open floating list may take under `top` inside a Drawer without covering its footer (at least 80, an 8px gap kept); `null` outside a Drawer (`Autocomplete`) |
109
115
  | `/submit-guard` | `createSubmitGuard`, `busySettler` and the types `SubmitGuard`, `SubmitOnceOptions` — one submit in flight until its request ends, behind both libraries' `useSubmitOnce` |
110
116
  | `/code-errors` | `createCodeErrorMap`, `defaultCodeOf`, `isEmptyRequired` and the types `CodeErrorMap`, `CodeErrorMapOptions`, `CodeIssue` — a zod 4 error map that words an issue by the code it carries (a refinement's `params.code`), behind both libraries' `useCodeResolver` |
111
117
  | `/two-factor` | `groupSecret`, `recoveryCodesText`, `downloadText` and the types `RecoveryCodesTextOptions`, `DownloadTextOptions` — a TOTP secret in groups of four, the recovery codes as a text file, and saving a text file, behind both libraries' `EnrolmentSecret` and `RecoveryCodes` |
112
118
  | `/print-only` | `printOnly`, `PRINT_ATTRIBUTE`, `PRINT_TARGET_ATTRIBUTE` — printing one element of the page with no inline style, behind `RecoveryCodes`' Print button; re-exported by both libraries' `/lib` |
113
119
  | `/file-preview` | `fileKind`, `describeFile`, `safePreviewUrl`, `readTextPreview`, `FilePreviewHttpError`, `FilePreviewUnsafeUrlError`, `TEXT_PREVIEW_MAX_BYTES`, `fileExtension`, `fileNameFromUrl`, `dataUrlType`, `kindOfMimeType`, `normaliseMimeType`, `isTextFileKind`, `TEXT_FILE_KINDS` and the types `FileKind`, `FileKindInput`, `DescribedFile`, `FilePreviewSource`, `TextPreview` — `FilePreview`'s kind detection, URL allow-list and text reader |
114
- | `/vite` | `pdfkitStandardFonts()`, `cspAssetsInlineLimit` — see below |
120
+ | `/states` | `EMPTY_STATE_KINDS`, `ERROR_STATE_KINDS`, `resolveEmptyStateKind`, `isErrorKind`, `stateCopy`, `STATE_ACTION_KEYS`, `describeError`, `STATE_ILLUSTRATIONS`, `STATE_ACCENT`, `STATE_ICON` and the types `EmptyStateKind`, `ErrorStateKind`, `StateKind`, `LegacyEmptyStateVariant`, `StateFamily`, `StateCopy`, `ErrorDetail`, `StateIllustration`, `IllustrationShape`, `IllustrationTone`, `StateAccent`, `StateIconName` — the kinds, copy keys, error details and illustrations behind `EmptyState` and `ErrorState` |
121
+ | `/vite` | `nxgtMaterial()`, `pdfkitStandardFonts()`, `cspAssetsInlineLimit`, `CSP_NONCE_PLACEHOLDER`, `ZOD_JITLESS_SCRIPT` and the types `NxgtMaterialOptions`, `NxgtMaterialCspOptions`, `NxgtMaterialLibrary` — see below |
115
122
  | `/pdfkit-fonts` | **Node only.** `pdfkitStandardFontDirs`, `pdfkitStandardFontFiles`, `PDFKIT_STANDARD_FONTS`, the types `PdfkitStandardFont` and `PdfkitFontsWarn` — where the app's pdfkit keeps its standard fonts, for a server tracer |
116
123
 
117
124
  ## Usage
@@ -327,13 +334,30 @@ server). `pinHolds(node)` tells whether the view still shows `node` where the
327
334
  pin puts it: at the top, or as near as the area can scroll at its end. A
328
335
  scroll that leaves it elsewhere came from find in page, a screen reader's
329
336
  cursor or your own `scrollTop`, none of which the pin hears: treat it as the
330
- user's.
337
+ user's. Such a scroll can land within the hold a correction starts, which
338
+ ignores it: give `createScrollSpy` the option `readerPosition`, which says
339
+ whether the view is still where the reader put it, and the spy measures once,
340
+ two frames after release (past a resize's correction), when a held scroll
341
+ left it elsewhere. Its fourth argument is `{ idleMs?, readerPosition? }`; a
342
+ bare number is still `idleMs`. Not covered: a second correction within the
343
+ same hold puts the view back where the reader put it, and that scroll is lost.
331
344
 
332
345
  ```ts
333
- import { pinHolds, pinToPage } from '@nxgt/material-core/pdf-viewer';
346
+ import {
347
+ createScrollSpy,
348
+ pinHolds,
349
+ pinToPage,
350
+ } from '@nxgt/material-core/pdf-viewer';
334
351
 
335
- // pages: Map<number, HTMLElement>; spy: from createScrollSpy
352
+ // pages: Map<number, HTMLElement>
336
353
  let pinned = true;
354
+ const spy = createScrollSpy(
355
+ () => area, // the reader's page area
356
+ () => pages,
357
+ onScroll,
358
+ // still where the reader put it
359
+ { readerPosition: () => !pinned || pinHolds(pages.get(3)) },
360
+ );
337
361
  const unpin = pinToPage(
338
362
  () => pages.get(3), // the page's element, looked up again on each correction
339
363
  () => spy.hold(), // the corrections are not the user's scrolling
@@ -347,6 +371,14 @@ function onScroll(page: number) {
347
371
  }
348
372
  ```
349
373
 
374
+ `pageInView(area, pages)` is the spy's own measure, on demand: the page most
375
+ on screen, the last one in view once the area is scrolled to its end, or `null` when
376
+ no page is in view.
377
+
378
+ ```ts
379
+ const page = pageInView(area, pages); // 3
380
+ ```
381
+
350
382
  ### A currency's decimals: `/currency-digits`
351
383
 
352
384
  ```ts
@@ -407,6 +439,20 @@ save('ab'); // 300 ms later: "save ab", once
407
439
  save.flush(); // or now
408
440
  ```
409
441
 
442
+ ### An avatar's initials: `/initials`
443
+
444
+ What both libraries' `AccountMenu` shows in its avatar when there is no
445
+ picture: the first letter of the first and of the last word, upper-cased, a
446
+ whole code point each.
447
+
448
+ ```ts
449
+ import { initials } from '@nxgt/material-core/initials';
450
+
451
+ initials('Ada Lovelace'); // 'AL'
452
+ initials('Ada'); // 'A'
453
+ initials(' '); // ''
454
+ ```
455
+
410
456
  ### Matching text as the user types: `/text-match`
411
457
 
412
458
  `foldText(text, locale?)` lowercases `text` for `locale` (the runtime's when
@@ -467,6 +513,25 @@ what a combobox's polite live region says: the failure, else "loading",
467
513
  else the count once the query has settled — `null` while the user is still
468
514
  typing.
469
515
 
516
+ ### An open list above a Drawer's footer: `/drawer-room`
517
+
518
+ `drawerRoom(element, top)` looks for the `[data-slot="drawer-content"]` around
519
+ `element` and answers how many pixels a floating list that starts at `top`
520
+ (a viewport coordinate) may take before it reaches the Drawer's
521
+ `[data-slot="drawer-footer"]`, or the Drawer's bottom edge without a footer,
522
+ or the window's. It keeps an 8px gap and never answers less than 80, so the
523
+ list stays usable. Outside a Drawer it answers `null`: the list keeps its own
524
+ height.
525
+
526
+ ```ts
527
+ import { drawerRoom } from '@nxgt/material-core/drawer-room';
528
+
529
+ const room = drawerRoom(field, field.getBoundingClientRect().bottom + 6);
530
+ list.style.maxHeight = room == null ? '' : `${room}px`;
531
+ ```
532
+
533
+ Measure again when the field or the Drawer resizes or scrolls.
534
+
470
535
  ### One submit in flight: `/submit-guard`
471
536
 
472
537
  `createSubmitGuard()` returns `{ once, settle, pendingRun }`. `once(run)` wraps a
@@ -694,6 +759,54 @@ document.head.append(style);
694
759
  [`get-nonce`](https://www.npmjs.com/package/get-nonce)'s names, so a
695
760
  bundler alias can point that package here.
696
761
 
762
+ ### The CSP for a server or a build: `/csp`
763
+
764
+ `/csp-nonce` reads the nonce in a browser; `/csp` is for the code that
765
+ chooses it: an SSR request handler, a build script, a Vite plugin. It uses
766
+ only `globalThis.crypto`, `btoa` and strings, so it runs on Node, Bun, Deno,
767
+ workers and browsers, and keeps no state.
768
+
769
+ ```ts
770
+ import {
771
+ applyCspNonce,
772
+ createCspNonce,
773
+ cspHash,
774
+ cspPolicy,
775
+ zodJitlessTag,
776
+ } from '@nxgt/material-core/csp';
777
+
778
+ // Per request (SSR): a fresh nonce, the header, the nonce in the HTML.
779
+ const nonce = createCspNonce(); // 16 random bytes, base64
780
+ const { header } = cspPolicy({ nonce }); // the policy of the CSP guide
781
+ response.setHeader('Content-Security-Policy', header);
782
+ const html = `<head>${zodJitlessTag(nonce)}…</head>`; // zod without new Function
783
+
784
+ // A build that left CSP_NONCE_PLACEHOLDER in the HTML fills it per request:
785
+ const page = applyCspNonce(builtHtml, nonce);
786
+
787
+ // Static hosting has no per-request nonce: allow inline code by its hash.
788
+ const hash = await cspHash("console.log('hi')"); // "'sha256-…'"
789
+ cspPolicy({ hashes: { script: [hash] } }); // script-src 'self' 'sha256-…'
790
+ ```
791
+
792
+ - `cspPolicy({ nonce?, dev?, hashes?, directives? })` returns
793
+ `{ directives, header }`. Without a nonce there is no `'strict-dynamic'`:
794
+ scripts fall back to `'self'` (and their hashes), style elements to
795
+ `'self' 'unsafe-inline'`, since the libraries create `<style>` elements at
796
+ runtime that no build can hash (a `hashes.style` entry replaces
797
+ `'unsafe-inline'`, which a browser ignores beside a hash); `dev` adds `ws:` and
798
+ `wss:` to `connect-src`; `directives` is merged per directive (sources are
799
+ appended), and a directive set to `null` or `[]` is dropped.
800
+ - `applyCspNonce(html, nonce, placeholder?)` replaces every placeholder and
801
+ throws on a nonce outside base64 (`"`, `<`, a space would break out of the
802
+ attribute), and when `html` holds no placeholder (a hash-mode build, or a
803
+ custom placeholder not passed as the third argument).
804
+ - **Never serve `CSP_NONCE_PLACEHOLDER` as a nonce**: it is public, so it
805
+ voids the policy. `applyCspNonce` and `cspPolicy` throw when given it.
806
+ - `ZOD_JITLESS_SCRIPT` is `(globalThis.__zod_globalConfig??={}).jitless=true;`
807
+ (zod 4.4 and later), for a page whose policy has no `'unsafe-eval'`.
808
+ Each library tests it against the zod it runs on.
809
+
697
810
  ### Built on first use: `/lazy-object`
698
811
 
699
812
  `buildOnce(build)` returns a function that calls `build` on its first call
@@ -773,6 +886,45 @@ const { text, truncated } = await readTextPreview('/logs/today.log', {
773
886
  });
774
887
  ```
775
888
 
889
+ ### The kinds of an empty or failed screen: `/states`
890
+
891
+ The framework-free half of `EmptyState` and `ErrorState`. Four **empty kinds**
892
+ (`empty`, `search`, `not-found`, `unauthorized`) are an absence; three **error
893
+ kinds** (`generic`, `offline`, `maintenance`) are something that failed.
894
+ `resolveEmptyStateKind({ kind, variant })` picks `kind`, else the legacy
895
+ `variant`, else `'generic'` (the default `EmptyState` has always had).
896
+ `stateCopy(kind, family)` gives the i18n keys and the English fallbacks:
897
+ `material.empty-state.<kind>.*` for the empty kinds, and for `EmptyState`
898
+ given a legacy error `variant`; `material.error-state.<kind>.*` for the error
899
+ kinds of `ErrorState` (`family` is `'empty-state'` or `'error-state'`).
900
+ `STATE_ILLUSTRATIONS` is each kind's SVG geometry as data (shapes with a
901
+ `tone`, no colour and no style); the libraries paint it with classes on theme
902
+ tokens, so it is CSP-safe. `STATE_ACCENT` and `STATE_ICON` name each kind's
903
+ accent and its lucide icon (kebab-case).
904
+
905
+ `describeError(error, { maxDepth })` is what an `ErrorState` may show of a
906
+ thrown value: `{ name, message }` for the error then each `cause` (3 entries
907
+ by default), for an `Error`, a `{ message }` object, a string or a primitive,
908
+ stopping at a cycle. It **never reads `.stack`**: a stack names files and
909
+ paths. [More in docs/states.md](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/states.md).
910
+
911
+ ```ts
912
+ import {
913
+ describeError,
914
+ resolveEmptyStateKind,
915
+ stateCopy,
916
+ } from '@nxgt/material-core/states';
917
+
918
+ resolveEmptyStateKind({}); // 'generic' — the legacy default
919
+ resolveEmptyStateKind({ kind: 'search', variant: 'offline' }); // 'search'
920
+
921
+ stateCopy('offline', 'error-state').titleKey; // 'material.error-state.offline.title'
922
+ stateCopy('offline', 'empty-state').title; // 'You are offline'
923
+
924
+ describeError(new Error('Could not save', { cause: new TypeError('Failed to fetch') }));
925
+ // [{ name: 'Error', message: 'Could not save' }, { name: 'TypeError', message: 'Failed to fetch' }]
926
+ ```
927
+
776
928
  ### A field's blur across overlays: `/field-blur-layers`
777
929
 
778
930
  A field whose popover, listbox or dialog renders into `<body>` must not count
@@ -851,7 +1003,7 @@ const said: RowAnnouncement = { kind: 'removed', index: index + 1 };
851
1003
  status.textContent = said.kind === 'removed' ? `Removed period ${said.index}` : '';
852
1004
  ```
853
1005
 
854
- ### Escape while an overlay closes: `/escape-forward`
1006
+ ### Escape while an overlay closes or a list is open: `/escape-forward`
855
1007
 
856
1008
  Radix's and reka's dismissable layers give Escape to the highest layer only,
857
1009
  and a dialog's layer leaves their stack when its content unmounts, not when
@@ -877,6 +1029,35 @@ function onEscapeKeyDown(event: KeyboardEvent) {
877
1029
  }
878
1030
  ```
879
1031
 
1032
+ `keepListEscape(event)` is the other half: Escape pressed in a field whose
1033
+ list is open belongs to the list. A Base UI combobox (`ComboboxField`,
1034
+ `Combobox`) closes its list from a handler on its input, but a Radix or reka
1035
+ overlay listens on the document first and closes itself with the list: the
1036
+ dialog, sheet, drawer or popover around the field vanished on the first
1037
+ Escape. Called from the content's escape handler, before the caller's own,
1038
+ `keepListEscape` prevents the default when the event's target is an open
1039
+ list's input, and returns whether it did; the list still closes, and the
1040
+ second Escape closes the overlay. The caller's handler then sees
1041
+ `event.defaultPrevented` true for an Escape that only closes a list. The
1042
+ target is read from the event's composed path and tested by `nodeType`, so a
1043
+ field in an iframe or a shadow root counts. The input counts as open when it
1044
+ is a `role="combobox"` with `aria-expanded="true"` whose `aria-controls`
1045
+ list sits in a `[data-slot="combobox-content"]` popup (a command palette's
1046
+ input is not), or when it carries `data-list-open="true"` (the Vue
1047
+ `Autocomplete`'s textbox).
1048
+
1049
+ ```ts
1050
+ import { forwardEscape, keepListEscape } from '@nxgt/material-core/escape-forward';
1051
+
1052
+ const dialog = document.querySelector<HTMLElement>('[role="dialog"]');
1053
+
1054
+ function onEscapeKeyDown(event: KeyboardEvent) {
1055
+ keepListEscape(event); // an open list takes this Escape
1056
+ if (event.defaultPrevented) return; // the dialog stays open
1057
+ forwardEscape(dialog, event);
1058
+ }
1059
+ ```
1060
+
880
1061
  ### A field's `aria-describedby`: `/described-by`
881
1062
 
882
1063
  `describedBy(own, ...ids)` joins the ids a field's control is described by:
@@ -1016,22 +1197,99 @@ row.addEventListener('click', (event) => {
1016
1197
  });
1017
1198
  ```
1018
1199
 
1200
+ ### Background presets: `/backgrounds`
1201
+
1202
+ The engine under the libraries' background presets, with the grain built so far; the other presets follow.
1203
+ A background is a **scene**: a plain tree `{ tag, attrs, children }` of SVG
1204
+ elements that each library renders with `createElement` or `h` — never as an
1205
+ HTML string. `assertSafeScene` throws for a `style`, an `on*` handler, an
1206
+ `href`, a URL, or a `url(…)` that is not the id of an element in the same
1207
+ scene, so a scene can never need `style-src-attr 'unsafe-inline'`. Everything
1208
+ is **seeded**: the same `seed` gives the same scene on the server and in the
1209
+ browser (no `Math.random`, nothing measured).
1210
+
1211
+ ```ts
1212
+ import {
1213
+ assertSafeScene,
1214
+ mulberry32,
1215
+ motifShape,
1216
+ observeBackgroundActivity,
1217
+ place,
1218
+ scatter,
1219
+ svgIdPrefix,
1220
+ } from '@nxgt/material-core/backgrounds';
1221
+
1222
+ const rand = mulberry32(42);
1223
+ const dots = scatter(rand, [60, 40, 24, 12]).map((p) =>
1224
+ place({ x: p.x, y: p.y, className: 'k1', opacity: 0.6 }, motifShape('ring', p.s, false)),
1225
+ );
1226
+ const scene = { tag: 'svg', attrs: { viewBox: '0 0 1600 1000' }, children: dots } as const;
1227
+ assertSafeScene(scene, svgIdPrefix(':r1:')); // throws if anything is unsafe
1228
+
1229
+ // pause an animation when the page is hidden, the element is off screen or
1230
+ // the user prefers reduced motion; call it from an effect, once mounted
1231
+ const stop = observeBackgroundActivity(element, (paused) => {
1232
+ element.toggleAttribute('data-paused', paused);
1233
+ });
1234
+ stop();
1235
+ ```
1236
+
1237
+ `svgIdPrefix(useId())` makes a generated id safe inside `url(#…)`
1238
+ (`':r1:'` becomes `'bg-r1'`). `BACKGROUND_PRESETS` is the table of the 20
1239
+ preset ids with their family, how each is drawn and whether `seed` changes
1240
+ it. `buildBackgroundScene(preset, { idPrefix, seed })` returns the scene of a
1241
+ preset, or `null` for a CSS-only one or one with no builder yet (only `grain` has one so far).
1242
+ `SHIPPED_BACKGROUND_PRESETS` (`gradient`, `grain`) is what the libraries draw today, and
1243
+ `resolveShippedPreset(id)` returns `id` when it ships, else `'gradient'`, with a one-time
1244
+ warning outside production. See
1245
+ [Backgrounds](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/backgrounds.md).
1246
+
1247
+ ### One plugin: `nxgtMaterial()` (`/vite`)
1248
+
1249
+ ```ts
1250
+ // vite.config.ts
1251
+ import { nxgtMaterial } from '@nxgt/material-core/vite';
1252
+ import { defineConfig } from 'vite';
1253
+
1254
+ export default defineConfig({ plugins: [...nxgtMaterial({ csp: { mode: 'hash' } })] });
1255
+ ```
1256
+
1257
+ It sets up a strict Content Security Policy (zod's jitless script, then the
1258
+ sha256 of every inline script and style of `index.html` written to
1259
+ `dist/.vite/nxgt-csp.json` with a ready header that uses the script hashes, or `csp: { mode: 'nonce' }`
1260
+ for a placeholder your server fills with `applyCspNonce`), keeps fonts out of
1261
+ `data:` URIs, and, when called with a library name (the libraries'
1262
+ `/vite` do it), loads Inter (when `@fontsource-variable/inter` is installed; otherwise it warns and keeps the system font) and fails the build when the library's stylesheet
1263
+ is not wired (neither `styles.css` nor the compiled stylesheet,
1264
+ `@import "<library>";`). The libraries' [Vite plugin guide](https://cdn.jsdelivr.net/npm/@nxgt/material@2/docs/guides/vite-plugin.md)
1265
+ has every option, and the [migration guide](https://cdn.jsdelivr.net/npm/@nxgt/material@2/docs/guides/migrate-to-vite-plugin.md)
1266
+ moves a hand-written setup over. It reads stylesheets and the renderer's
1267
+ `package.json` files from disk (`node:fs`, `node:fs/promises`, `node:path`), so
1268
+ `/vite` is build-time code like `/pdfkit-fonts`.
1269
+
1019
1270
  ### Bundling pdfkit's fonts: `/vite`
1020
1271
 
1021
1272
  An SSR server bundled into a single file with no `node_modules` needs
1022
- pdfkit's standard fonts inlined, or it throws on every request:
1273
+ pdfkit's standard fonts inlined, or it throws on every request. With a
1274
+ library name (the libraries' `/vite` pass it), `nxgtMaterial()` does both
1275
+ halves:
1023
1276
 
1024
1277
  ```ts
1025
1278
  // vite.config.ts
1026
- import { pdfkitStandardFonts } from '@nxgt/material-core/vite';
1279
+ import { nxgtMaterial } from '@nxgt/material-core/vite';
1027
1280
  import { defineConfig } from 'vite';
1028
1281
 
1029
- export default defineConfig({ plugins: [pdfkitStandardFonts()] });
1282
+ export default defineConfig({
1283
+ plugins: [...nxgtMaterial({ pdf: { bundle: true } }, '@nxgt/material')],
1284
+ });
1030
1285
  ```
1031
1286
 
1032
- `@nxgt/material/vite` and `@nxgt/material-vue/vite` export the same
1033
- function. The doc comment on `pdfkitStandardFonts` explains when you need it
1034
- and when keeping the library external is the lighter answer.
1287
+ `pdf` is on by default (`pdfkitStandardFonts()`, a no-op unless pdfkit goes
1288
+ through the bundle); `pdf: { bundle: true }` also puts the library, core,
1289
+ `pdfkit`, `@react-pdf/*` and the renderer's runtime dependencies into
1290
+ `ssr.noExternal` (`react` and your own dependencies stay yours to bundle). `@nxgt/material/vite` and
1291
+ `@nxgt/material-vue/vite` export `pdfkitStandardFonts` on its own too; its doc
1292
+ comment explains when keeping the library external is the lighter answer.
1035
1293
 
1036
1294
  ### Fonts under a strict CSP: `cspAssetsInlineLimit` (`/vite`)
1037
1295
 
@@ -1106,6 +1364,7 @@ Every message, with its cause, is in [troubleshooting](https://cdn.jsdelivr.net/
1106
1364
  ## Documentation
1107
1365
 
1108
1366
  - [docs/README.md](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/README.md) — every page, and when to read it
1367
+ - [States](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/states.md) — the kinds, copy keys, `describeError` and illustrations of `EmptyState` and `ErrorState`
1109
1368
  - [Troubleshooting](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/troubleshooting.md) — every error message, and its fix
1110
1369
  - [Roadmap](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/roadmap.md) — what is next
1111
1370
 
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tells `onChange` whether an animated background should be paused: the page
3
+ * is hidden, the element is off screen, or the user prefers reduced motion.
4
+ * Calls `onChange` once with the current state, then on each change only.
5
+ * Returns the function that removes every listener and observer.
6
+ *
7
+ * Reads the DOM when called, never at import: call it from an effect or
8
+ * `onMounted`, and only when the background is `animated`.
9
+ */
10
+ export declare function observeBackgroundActivity(el: Element, onChange: (paused: boolean) => void): () => void;
11
+ //# sourceMappingURL=activity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../../src/backgrounds/activity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACxC,EAAE,EAAE,OAAO,EACX,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GACjC,MAAM,IAAI,CAiCZ"}
@@ -0,0 +1,9 @@
1
+ import type { BackgroundPreset, SceneNode, SceneOptions } from './types';
2
+ /**
3
+ * The SVG scene of a preset, or `null` when it has none (the CSS-only
4
+ * presets, and a preset whose builder is not there yet). The same `seed`
5
+ * always gives the same scene. The scene is checked with `assertSafeScene`
6
+ * before it is returned.
7
+ */
8
+ export declare function buildBackgroundScene(preset: BackgroundPreset, opts: SceneOptions): SceneNode | null;
9
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/backgrounds/build.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,gBAAgB,EAEhB,SAAS,EACT,YAAY,EACZ,MAAM,SAAS,CAAC;AAWjB;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,YAAY,GAChB,SAAS,GAAG,IAAI,CAIlB"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Turns a framework's generated id (React's `useId` gives `:r1:` or `«r1»`,
3
+ * Vue's `v-0`) into a prefix that is safe inside `url(#…)` and as an `id`:
4
+ * only `A-Z a-z 0-9 _ -`, and a letter first. Ids in a scene are
5
+ * `<prefix>-<name>`.
6
+ */
7
+ export declare function svgIdPrefix(raw: string): string;
8
+ //# sourceMappingURL=id-prefix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-prefix.d.ts","sourceRoot":"","sources":["../../src/backgrounds/id-prefix.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/C"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `@nxgt/material-core/backgrounds` — the engine behind the background
3
+ * presets: types, the preset table, a seeded random generator, noise,
4
+ * scattering, the motif primitives, the scene format and its safety check,
5
+ * ids for `url(#…)`, and the activity observer that pauses an animation.
6
+ * Framework-free; the libraries render a scene with `createElement` / `h`.
7
+ */
8
+ export { observeBackgroundActivity } from './activity';
9
+ export { buildBackgroundScene } from './build';
10
+ export { svgIdPrefix } from './id-prefix';
11
+ export { valueNoise2 } from './noise';
12
+ export { BACKGROUND_PRESETS, resolveShippedPreset, SHIPPED_BACKGROUND_PRESETS, } from './presets';
13
+ export { buildGrain } from './presets/grain';
14
+ export { MOTIF_DEFAULTS, MOTIF_SHAPES, motifShape, type PlaceOptions, place, } from './primitives';
15
+ export { mulberry32, pick, round1 } from './rng';
16
+ export { type ScatterOptions, type ScatterPoint, scatter, } from './scatter';
17
+ export { assertSafeScene, el, SCENE_ATTRIBUTES, SCENE_TAGS, } from './scene';
18
+ export { BACKGROUND_INTENSITIES, BACKGROUND_TONES } from './tones';
19
+ export type { BackgroundIntensity, BackgroundPreset, BackgroundTone, MotifDensity, MotifOptions, MotifShape, MotifSize, OpacitySpread, PresetInfo, SceneBuilder, SceneNode, SceneOptions, SceneTag, ShapeFill, ShippedBackgroundPreset, } from './types';
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backgrounds/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EACN,kBAAkB,EAClB,oBAAoB,EACpB,0BAA0B,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACN,cAAc,EACd,YAAY,EACZ,UAAU,EACV,KAAK,YAAY,EACjB,KAAK,GACL,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,OAAO,GACP,MAAM,WAAW,CAAC;AACnB,OAAO,EACN,eAAe,EACf,EAAE,EACF,gBAAgB,EAChB,UAAU,GACV,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AACnE,YAAY,EACX,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,SAAS,EACT,aAAa,EACb,UAAU,EACV,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,uBAAuB,GACvB,MAAM,SAAS,CAAC"}