@nxgt/material-core 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @nxgt/material-core
2
2
 
3
+ ## 0.9.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#541](https://github.com/softistx/nxgt-material/pull/541) [`6d10133`](https://github.com/softistx/nxgt-material/commit/6d1013356e8c42631166b6281081811912e31323) Thanks [@SteveGT96](https://github.com/SteveGT96)! - The roadmaps plan one Vite plugin for the setup, and list the strict Content
8
+ Security Policy as shipped.
9
+
10
+ ## 0.9.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [#471](https://github.com/softistx/nxgt-material/pull/471) [`3a2889d`](https://github.com/softistx/nxgt-material/commit/3a2889d71f1bfc9039ef43db916f3be3bf287ccd) Thanks [@SteveGT96](https://github.com/SteveGT96)! - New subpath `@nxgt/material-core/csp-nonce`, for a page under a strict Content Security Policy: `getCspNonce()` returns the page's nonce, from the first of the value given to `setCspNonce(nonce)`, `<meta property="csp-nonce" nonce="…">` (what Vite's `html.cspNonce` writes) or `<meta name="csp-nonce" content="…">`, the first `<script nonce>` / `<style nonce>`, then `globalThis.__webpack_nonce__`. `createNoncedStyle(doc?)` creates a `<style>` that carries it. Browser only: without a `document`, `setCspNonce` does nothing and `getCspNonce` returns `undefined`, so a request's nonce never reaches another request through a global. The value lives on `globalThis` under `Symbol.for('nxgt.cspNonce')`, shared by duplicate copies of the package. `getNonce` / `setNonce` are the same functions under `get-nonce`'s names, for a bundler alias.
15
+
16
+ `@nxgt/material-core/vite` also exports `cspAssetsInlineLimit`, a `build.assetsInlineLimit` that never inlines a font (`.woff`, `.woff2`, `.ttf`, `.otf`, `.eot`) as a `data:` URI, which `font-src 'self'` blocks, and leaves every other asset to Vite's default: `build: { assetsInlineLimit: cspAssetsInlineLimit }`.
17
+
18
+ `claimCspNonce(nonce, depth = 0)` is for a provider component: it registers its nonce and returns the function that releases it, so nested providers stay right. The stored nonce is that of the deepest claim still held (the latest among equals): an inner provider unmounting leaves its parent's nonce in place, and releasing the last claim puts back the nonce the app had set with `setCspNonce` before the first one. Children mount before their parents, so `depth` (how many providers enclose it) says which one is the innermost, not the order of the calls.
19
+
20
+ `@nxgt/material` and `@nxgt/material-vue` re-export `getCspNonce` and `setCspNonce` from `/lib` and `cspAssetsInlineLimit` from `/vite`, keep the nonce in this store through `NonceProvider` (which calls `claimCspNonce`), and `@nxgt/material` aliases `get-nonce` onto it.
21
+
22
+ - [#480](https://github.com/softistx/nxgt-material/pull/480) [`634a58e`](https://github.com/softistx/nxgt-material/commit/634a58e3353ebe340e87b0f21858396aa4354654) Thanks [@SteveGT96](https://github.com/SteveGT96)! - New subpath `@nxgt/material-core/lazy-object`: `buildOnce(build)` returns a function that builds its value on the first call and returns that same value after; `lazyObject(build)` returns a stand-in that builds the object on the first operation of any kind (a property read, a method call, `in`, `Object.keys`, `instanceof`) and then answers as it does. The stand-in is not the built object (`===` tells them apart, and a `.meta()` or `registry.add()` on it is not seen through the factory's schema). Both libraries build their zod schemas with them, so importing a library builds none.
23
+
3
24
  ## 0.8.0
4
25
 
5
26
  ### Minor Changes
package/README.md CHANGED
@@ -12,10 +12,12 @@ rule, a field's first reachable control (`/focus-control`) and its `aria-describ
12
12
  (`/inert-others`), Escape pressed while an overlay closes
13
13
  (`/escape-forward`), the Toaster's guard against a tap's emulated hover
14
14
  (`/touch-hover`), the focus a toast's action hands back (`/toast-focus`), a
15
- table row's activation, the zod error map's issue
16
- messages (`/zod-issue-messages`), the Vite plugin that bundles pdfkit's
17
- standard fonts, and the Node-only resolution of those fonts that the Next.js
18
- helper and the Nuxt module run at build time.
15
+ table row's activation, the zod error map's issue messages
16
+ (`/zod-issue-messages`), the page's Content Security Policy nonce
17
+ (`/csp-nonce`), an object built on its first use (`/lazy-object`), the Vite
18
+ plugin that bundles pdfkit's standard fonts and a `build.assetsInlineLimit`
19
+ that keeps fonts out of `data:` URIs, and the Node-only resolution of those
20
+ fonts that the Next.js helper and the Nuxt module run at build time.
19
21
 
20
22
  **You usually do not install it yourself.** Both libraries depend on it and
21
23
  re-export what they always exported, at the same subpaths:
@@ -93,11 +95,13 @@ built-in.
93
95
  | `/slider-ticks` | `tickValues` — where a slider's tick marks sit |
94
96
  | `/emoji-groups` | `groupKey`, `groupEmojis`, `searchEmojis`, `groupName`, `GROUP_NAMES` and the type `EmojiItem` — how the emoji list groups, searches and names its emojis |
95
97
  | `/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 |
98
+ | `/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 |
99
+ | `/lazy-object` | `lazyObject`, `buildOnce` — a value built on its first use rather than when its module loads: the libraries' zod schemas |
96
100
  | `/debounce` | `debounce`, the type `Debounced` — a function called once calls pause, with `cancel` and `flush` (`SearchField`'s `debounceMs`) |
97
101
  | `/text-match` | `foldText`, `matchesText` — case- and accent-insensitive matching (`'e'` finds "École"), lowercased for a locale |
98
102
  | `/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 |
99
103
  | `/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 |
100
- | `/vite` | `pdfkitStandardFonts()` — see below |
104
+ | `/vite` | `pdfkitStandardFonts()`, `cspAssetsInlineLimit` — see below |
101
105
  | `/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 |
102
106
 
103
107
  ## Usage
@@ -508,6 +512,81 @@ the end;
508
512
  `withTimeOf(day, time)` and `endOfDayTime(day)` set one end's time;
509
513
  `isSameDayRange(a, b)` compares two ranges by their days.
510
514
 
515
+ ### The page's CSP nonce: `/csp-nonce`
516
+
517
+ Under a strict Content Security Policy (`style-src-elem 'nonce-X'`), a
518
+ `<style>` element created at runtime is blocked unless it carries the page's
519
+ nonce. `getCspNonce()` finds it, from the first of: the value given to
520
+ `setCspNonce`, `<meta property="csp-nonce" nonce="…">` (what Vite's
521
+ `html.cspNonce` writes) or `<meta name="csp-nonce" content="…">`, the first
522
+ `<script nonce>` or `<style nonce>` of the page, then
523
+ `globalThis.__webpack_nonce__`. It reads an element's `nonce` property,
524
+ since browsers hide the attribute's value once the page is parsed.
525
+ `createNoncedStyle()` returns a new, unattached `<style>` that carries it.
526
+
527
+ ```ts
528
+ import { createNoncedStyle, setCspNonce } from '@nxgt/material-core/csp-nonce';
529
+
530
+ // Nothing to set when the page carries Vite's tag (html.cspNonce):
531
+ // <meta property="csp-nonce" nonce="…">
532
+ // With a server-rendered nonce, set it in entry.client.ts before the first
533
+ // component renders:
534
+ setCspNonce(nonceFromTheServer);
535
+
536
+ const style = createNoncedStyle();
537
+ style.textContent = '.spin { animation: spin 1s linear infinite }';
538
+ document.head.append(style);
539
+ ```
540
+
541
+ - A `content` attribute is not hidden from CSS selectors, so prefer the
542
+ `nonce` attribute.
543
+ - **Browser only.** Without a `document` (a server), `setCspNonce` does
544
+ nothing and `getCspNonce` returns `undefined`: a nonce belongs to one
545
+ request, and a module global would hand it to the next. Pass it down the
546
+ request instead. `createNoncedStyle()` with no `document` needs an explicit
547
+ `doc` argument, otherwise it throws. An SSR setup that installs a global
548
+ `document` (jsdom or domino) makes `setCspNonce` a global shared by every
549
+ request, so do not call it on the server.
550
+ - The value is kept on `globalThis` under `Symbol.for('nxgt.cspNonce')`, so
551
+ two copies of this package in one bundle share it.
552
+ - **For a provider component** (the libraries' `NonceProvider`): call
553
+ `claimCspNonce(nonce, depth)` once mounted, with how many providers enclose
554
+ it, and the function it returns on unmount. The deepest claim held is the
555
+ stored nonce, so nested providers stay right, and releasing the last one
556
+ puts back what the app had set with `setCspNonce`.
557
+ - `getNonce` and `setNonce` are the same two functions under
558
+ [`get-nonce`](https://www.npmjs.com/package/get-nonce)'s names, so a
559
+ bundler alias can point that package here.
560
+
561
+ ### Built on first use: `/lazy-object`
562
+
563
+ `buildOnce(build)` returns a function that calls `build` on its first call
564
+ and returns that same value after. `lazyObject(build)` returns a stand-in
565
+ for `build`'s object that builds it on the first operation of any kind
566
+ (a property read, a method call, `in`, `Object.keys`, `instanceof`), then
567
+ answers as that object does. Both libraries build their zod schemas this
568
+ way, so importing them builds none: building a `z.object()` makes zod probe
569
+ `new Function`, which a strict Content Security Policy reports, and the app
570
+ must be able to call `z.config({ jitless: true })` first.
571
+
572
+ ```ts
573
+ import { buildOnce, lazyObject } from '@nxgt/material-core/lazy-object';
574
+ import { z } from 'zod';
575
+
576
+ export const userSchema = buildOnce(() => z.object({ name: z.string() }));
577
+
578
+ /** @deprecated Use `userSchema()`. */
579
+ export const UserSchema = lazyObject(userSchema);
580
+
581
+ userSchema() === userSchema(); // true: built once
582
+ UserSchema.parse({ name: 'Ada' }); // builds it here, then parses
583
+ ```
584
+
585
+ - The stand-in is not the built object: `UserSchema === userSchema()` is
586
+ `false`. Use it for a value its callers read, never one they compare by
587
+ identity. A property written on it lands on the stand-in's copy.
588
+ - Nothing here imports zod: any object works.
589
+
511
590
  ### A file's preview: `/file-preview`
512
591
 
513
592
  The pure half of both libraries' `FilePreview`. `fileKind` picks the kind of
@@ -780,6 +859,24 @@ export default defineConfig({ plugins: [pdfkitStandardFonts()] });
780
859
  function. The doc comment on `pdfkitStandardFonts` explains when you need it
781
860
  and when keeping the library external is the lighter answer.
782
861
 
862
+ ### Fonts under a strict CSP: `cspAssetsInlineLimit` (`/vite`)
863
+
864
+ Vite inlines an asset under 4096 bytes as a `data:` URI, and `font-src
865
+ 'self'` blocks a font served that way (KaTeX's `KaTeX_Size3-Regular.woff2`
866
+ is 3.6 KB). `cspAssetsInlineLimit` never inlines a font (`.woff`, `.woff2`,
867
+ `.ttf`, `.otf`, `.eot`) and leaves every other asset to Vite's default:
868
+
869
+ ```ts
870
+ // vite.config.ts
871
+ import { cspAssetsInlineLimit } from '@nxgt/material-core/vite';
872
+ import { defineConfig } from 'vite';
873
+
874
+ export default defineConfig({ build: { assetsInlineLimit: cspAssetsInlineLimit } });
875
+ ```
876
+
877
+ To keep more files out, wrap it:
878
+ `(file, content) => (file.endsWith('.svg') ? false : cspAssetsInlineLimit(file, content))`.
879
+
783
880
  ### Tracing pdfkit's fonts: `/pdfkit-fonts` (Node only)
784
881
 
785
882
  A server that keeps `node_modules` but copies only what a tracer finds
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The page's Content Security Policy nonce, for the code that creates a
3
+ * `<style>` (or `<script>`) element at runtime: a strict `style-src-elem
4
+ * 'nonce-X'` blocks any element that does not carry it.
5
+ *
6
+ * Browser only. On a server the nonce belongs to one request, so it is never
7
+ * kept in a module global there: `setCspNonce` does nothing without a
8
+ * `document`, and `getCspNonce` then answers `undefined`. Pass the nonce down
9
+ * the request instead (an argument, a context, `AsyncLocalStorage`).
10
+ */
11
+ /**
12
+ * Keeps `nonce` for the page, read first by `getCspNonce`. `undefined` (or
13
+ * `''`) forgets it. A no-op on a server (no `document`), so a request's nonce
14
+ * never reaches another request.
15
+ */
16
+ export declare function setCspNonce(nonce: string | undefined): void;
17
+ /**
18
+ * Registers a provider's `nonce` as the page's, and returns the function that
19
+ * releases it. For nested providers: `setCspNonce` is then called with the
20
+ * nonce of the deepest claim still held (the latest among equals), the one a
21
+ * component below resolves through context, so an inner provider unmounting
22
+ * leaves its parent's nonce in place, and releasing the last claim puts back
23
+ * the nonce stored before the first one (an app's own `setCspNonce`; one set
24
+ * while a provider is mounted is replaced and not kept). `depth` is the
25
+ * provider's nesting level (0 for the outermost): children mount before their parents, so the order of
26
+ * the calls says nothing about nesting. A no-op on a server (no `document`).
27
+ * Calling the release function twice is harmless.
28
+ */
29
+ export declare function claimCspNonce(nonce: string | undefined, depth?: number): () => void;
30
+ /**
31
+ * The page's nonce, from the first of:
32
+ *
33
+ * 1. the value given to `setCspNonce`;
34
+ * 2. `<meta property="csp-nonce" nonce="…">`, which Vite's `html.cspNonce`
35
+ * writes, or `<meta name="csp-nonce" content="…">` (its `nonce`, else its
36
+ * `content`);
37
+ * 3. the first `<script nonce>` or `<style nonce>` of the page;
38
+ * 4. `__webpack_nonce__`.
39
+ *
40
+ * `doc` defaults to `globalThis.document`. `undefined` when none has one, and
41
+ * always on a server.
42
+ */
43
+ export declare function getCspNonce(doc?: Document | undefined): string | undefined;
44
+ /**
45
+ * A new `<style>` element of `doc`, not attached, carrying the page's nonce
46
+ * when there is one. Without a global `document`, pass `doc`.
47
+ */
48
+ export declare function createNoncedStyle(doc?: Document): HTMLStyleElement;
49
+ /**
50
+ * `get-nonce`'s API (`getNonce` / `setNonce`), so a bundler can alias that
51
+ * package here and the libraries' bundled dependencies (Radix's scroll lock,
52
+ * through `react-style-singleton`) read the same nonce.
53
+ */
54
+ export declare const getNonce: () => string | undefined;
55
+ export declare const setNonce: (nonce: string) => void;
56
+ //# sourceMappingURL=csp-nonce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csp-nonce.d.ts","sourceRoot":"","sources":["../../src/lib/csp-nonce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAwBH;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAI3D;AAuBD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC5B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,KAAK,SAAI,GACP,MAAM,IAAI,CAYZ;AAwBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAC1B,GAAG,GAAE,QAAQ,GAAG,SAA+B,GAC7C,MAAM,GAAG,SAAS,CAgBpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAChC,GAAG,GAAE,QAA8B,GACjC,gBAAgB,CAKlB;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,EAAE,MAAM,MAAM,GAAG,SAAuB,CAAC;AAC9D,eAAO,MAAM,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAkB,CAAC"}
@@ -0,0 +1,88 @@
1
+ // src/lib/csp-nonce.ts
2
+ var KEY = Symbol.for("nxgt.cspNonce");
3
+ function holder() {
4
+ return globalThis;
5
+ }
6
+ function hasDocument() {
7
+ return typeof document !== "undefined";
8
+ }
9
+ function setCspNonce(nonce) {
10
+ if (!hasDocument())
11
+ return;
12
+ if (nonce)
13
+ holder()[KEY] = nonce;
14
+ else
15
+ delete holder()[KEY];
16
+ }
17
+ var claims = [];
18
+ var unclaimed;
19
+ function winner() {
20
+ let top;
21
+ for (const claim of claims) {
22
+ if (!top || claim.depth >= top.depth)
23
+ top = claim;
24
+ }
25
+ return top;
26
+ }
27
+ function claimCspNonce(nonce, depth = 0) {
28
+ if (!hasDocument())
29
+ return () => {};
30
+ const claim = { nonce, depth };
31
+ if (claims.length === 0)
32
+ unclaimed = holder()[KEY];
33
+ claims.push(claim);
34
+ setCspNonce(winner()?.nonce);
35
+ return () => {
36
+ const index = claims.indexOf(claim);
37
+ if (index < 0)
38
+ return;
39
+ claims.splice(index, 1);
40
+ setCspNonce(claims.length ? winner()?.nonce : unclaimed);
41
+ };
42
+ }
43
+ function nonceOf(element) {
44
+ return element.nonce || element.getAttribute("nonce") || element.getAttribute("content") || undefined;
45
+ }
46
+ function firstNonce(doc, selector) {
47
+ for (const element of doc.querySelectorAll(selector)) {
48
+ const nonce = nonceOf(element);
49
+ if (nonce)
50
+ return nonce;
51
+ }
52
+ return;
53
+ }
54
+ function getCspNonce(doc = globalThis.document) {
55
+ if (!doc)
56
+ return;
57
+ const stored = holder()[KEY];
58
+ if (stored)
59
+ return stored;
60
+ const meta = firstNonce(doc, 'meta[property="csp-nonce"], meta[name="csp-nonce"]');
61
+ if (meta)
62
+ return meta;
63
+ const tag = firstNonce(doc, "script[nonce], style[nonce]");
64
+ if (tag)
65
+ return tag;
66
+ const webpack = typeof __webpack_nonce__ !== "undefined" ? __webpack_nonce__ : holder().__webpack_nonce__;
67
+ return typeof webpack === "string" && webpack ? webpack : undefined;
68
+ }
69
+ function createNoncedStyle(doc = globalThis.document) {
70
+ const style = doc.createElement("style");
71
+ const nonce = getCspNonce(doc);
72
+ if (nonce)
73
+ style.setAttribute("nonce", nonce);
74
+ return style;
75
+ }
76
+ var getNonce = getCspNonce;
77
+ var setNonce = setCspNonce;
78
+ export {
79
+ claimCspNonce,
80
+ createNoncedStyle,
81
+ getCspNonce,
82
+ getNonce,
83
+ setCspNonce,
84
+ setNonce
85
+ };
86
+
87
+ //# debugId=79F62D9AAEDF578C64756E2164756E21
88
+ //# sourceMappingURL=csp-nonce.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lib/csp-nonce.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * The page's Content Security Policy nonce, for the code that creates a\n * `<style>` (or `<script>`) element at runtime: a strict `style-src-elem\n * 'nonce-X'` blocks any element that does not carry it.\n *\n * Browser only. On a server the nonce belongs to one request, so it is never\n * kept in a module global there: `setCspNonce` does nothing without a\n * `document`, and `getCspNonce` then answers `undefined`. Pass the nonce down\n * the request instead (an argument, a context, `AsyncLocalStorage`).\n */\n\n/**\n * Where the value lives: a registered symbol on `globalThis`, so two copies of\n * this package in one bundle (a library's and the app's) share it.\n */\nconst KEY = Symbol.for('nxgt.cspNonce');\n\n/** webpack rewrites this free identifier; read it before the global. */\ndeclare const __webpack_nonce__: unknown;\n\ntype NonceHolder = { [KEY]?: string; __webpack_nonce__?: unknown };\n\n/** An element whose `nonce` the browser may have hidden from its attribute. */\ntype NoncedElement = Element & { nonce?: string };\n\nfunction holder(): NonceHolder {\n\treturn globalThis as unknown as NonceHolder;\n}\n\nfunction hasDocument(): boolean {\n\treturn typeof document !== 'undefined';\n}\n\n/**\n * Keeps `nonce` for the page, read first by `getCspNonce`. `undefined` (or\n * `''`) forgets it. A no-op on a server (no `document`), so a request's nonce\n * never reaches another request.\n */\nexport function setCspNonce(nonce: string | undefined): void {\n\tif (!hasDocument()) return;\n\tif (nonce) holder()[KEY] = nonce;\n\telse delete holder()[KEY];\n}\n\n/** A provider's claim on the stored nonce. */\ntype Claim = { nonce: string | undefined; depth: number };\n\n/** The claims of the mounted providers, in mounting order. */\nconst claims: Claim[] = [];\n\n/**\n * What `setCspNonce` had stored before the first claim, put back once the\n * last one is released.\n */\nlet unclaimed: string | undefined;\n\n/** The deepest claim, the latest one among equals. */\nfunction winner(): Claim | undefined {\n\tlet top: Claim | undefined;\n\tfor (const claim of claims) {\n\t\tif (!top || claim.depth >= top.depth) top = claim;\n\t}\n\treturn top;\n}\n\n/**\n * Registers a provider's `nonce` as the page's, and returns the function that\n * releases it. For nested providers: `setCspNonce` is then called with the\n * nonce of the deepest claim still held (the latest among equals), the one a\n * component below resolves through context, so an inner provider unmounting\n * leaves its parent's nonce in place, and releasing the last claim puts back\n * the nonce stored before the first one (an app's own `setCspNonce`; one set\n * while a provider is mounted is replaced and not kept). `depth` is the\n * provider's nesting level (0 for the outermost): children mount before their parents, so the order of\n * the calls says nothing about nesting. A no-op on a server (no `document`).\n * Calling the release function twice is harmless.\n */\nexport function claimCspNonce(\n\tnonce: string | undefined,\n\tdepth = 0,\n): () => void {\n\tif (!hasDocument()) return () => {};\n\tconst claim: Claim = { nonce, depth };\n\tif (claims.length === 0) unclaimed = holder()[KEY];\n\tclaims.push(claim);\n\tsetCspNonce(winner()?.nonce);\n\treturn () => {\n\t\tconst index = claims.indexOf(claim);\n\t\tif (index < 0) return;\n\t\tclaims.splice(index, 1);\n\t\tsetCspNonce(claims.length ? winner()?.nonce : unclaimed);\n\t};\n}\n\n/**\n * Browsers hide a `nonce` attribute's value once the page is parsed (it reads\n * `''`), and keep it on the `nonce` property: read that first.\n */\nfunction nonceOf(element: NoncedElement): string | undefined {\n\treturn (\n\t\telement.nonce ||\n\t\telement.getAttribute('nonce') ||\n\t\telement.getAttribute('content') ||\n\t\tundefined\n\t);\n}\n\n/** The first non-empty nonce among the elements `selector` matches. */\nfunction firstNonce(doc: Document, selector: string): string | undefined {\n\tfor (const element of doc.querySelectorAll(selector)) {\n\t\tconst nonce = nonceOf(element);\n\t\tif (nonce) return nonce;\n\t}\n\treturn undefined;\n}\n\n/**\n * The page's nonce, from the first of:\n *\n * 1. the value given to `setCspNonce`;\n * 2. `<meta property=\"csp-nonce\" nonce=\"…\">`, which Vite's `html.cspNonce`\n * writes, or `<meta name=\"csp-nonce\" content=\"…\">` (its `nonce`, else its\n * `content`);\n * 3. the first `<script nonce>` or `<style nonce>` of the page;\n * 4. `__webpack_nonce__`.\n *\n * `doc` defaults to `globalThis.document`. `undefined` when none has one, and\n * always on a server.\n */\nexport function getCspNonce(\n\tdoc: Document | undefined = globalThis.document,\n): string | undefined {\n\tif (!doc) return undefined;\n\tconst stored = holder()[KEY];\n\tif (stored) return stored;\n\tconst meta = firstNonce(\n\t\tdoc,\n\t\t'meta[property=\"csp-nonce\"], meta[name=\"csp-nonce\"]',\n\t);\n\tif (meta) return meta;\n\tconst tag = firstNonce(doc, 'script[nonce], style[nonce]');\n\tif (tag) return tag;\n\tconst webpack =\n\t\ttypeof __webpack_nonce__ !== 'undefined'\n\t\t\t? __webpack_nonce__\n\t\t\t: holder().__webpack_nonce__;\n\treturn typeof webpack === 'string' && webpack ? webpack : undefined;\n}\n\n/**\n * A new `<style>` element of `doc`, not attached, carrying the page's nonce\n * when there is one. Without a global `document`, pass `doc`.\n */\nexport function createNoncedStyle(\n\tdoc: Document = globalThis.document,\n): HTMLStyleElement {\n\tconst style = doc.createElement('style');\n\tconst nonce = getCspNonce(doc);\n\tif (nonce) style.setAttribute('nonce', nonce);\n\treturn style;\n}\n\n/**\n * `get-nonce`'s API (`getNonce` / `setNonce`), so a bundler can alias that\n * package here and the libraries' bundled dependencies (Radix's scroll lock,\n * through `react-style-singleton`) read the same nonce.\n */\nexport const getNonce: () => string | undefined = getCspNonce;\nexport const setNonce: (nonce: string) => void = setCspNonce;\n"
6
+ ],
7
+ "mappings": ";AAeA,IAAM,MAAM,OAAO,IAAI,eAAe;AAUtC,SAAS,MAAM,GAAgB;AAAA,EAC9B,OAAO;AAAA;AAGR,SAAS,WAAW,GAAY;AAAA,EAC/B,OAAO,OAAO,aAAa;AAAA;AAQrB,SAAS,WAAW,CAAC,OAAiC;AAAA,EAC5D,IAAI,CAAC,YAAY;AAAA,IAAG;AAAA,EACpB,IAAI;AAAA,IAAO,OAAO,EAAE,OAAO;AAAA,EACtB;AAAA,WAAO,OAAO,EAAE;AAAA;AAOtB,IAAM,SAAkB,CAAC;AAMzB,IAAI;AAGJ,SAAS,MAAM,GAAsB;AAAA,EACpC,IAAI;AAAA,EACJ,WAAW,SAAS,QAAQ;AAAA,IAC3B,IAAI,CAAC,OAAO,MAAM,SAAS,IAAI;AAAA,MAAO,MAAM;AAAA,EAC7C;AAAA,EACA,OAAO;AAAA;AAeD,SAAS,aAAa,CAC5B,OACA,QAAQ,GACK;AAAA,EACb,IAAI,CAAC,YAAY;AAAA,IAAG,OAAO,MAAM;AAAA,EACjC,MAAM,QAAe,EAAE,OAAO,MAAM;AAAA,EACpC,IAAI,OAAO,WAAW;AAAA,IAAG,YAAY,OAAO,EAAE;AAAA,EAC9C,OAAO,KAAK,KAAK;AAAA,EACjB,YAAY,OAAO,GAAG,KAAK;AAAA,EAC3B,OAAO,MAAM;AAAA,IACZ,MAAM,QAAQ,OAAO,QAAQ,KAAK;AAAA,IAClC,IAAI,QAAQ;AAAA,MAAG;AAAA,IACf,OAAO,OAAO,OAAO,CAAC;AAAA,IACtB,YAAY,OAAO,SAAS,OAAO,GAAG,QAAQ,SAAS;AAAA;AAAA;AAQzD,SAAS,OAAO,CAAC,SAA4C;AAAA,EAC5D,OACC,QAAQ,SACR,QAAQ,aAAa,OAAO,KAC5B,QAAQ,aAAa,SAAS,KAC9B;AAAA;AAKF,SAAS,UAAU,CAAC,KAAe,UAAsC;AAAA,EACxE,WAAW,WAAW,IAAI,iBAAiB,QAAQ,GAAG;AAAA,IACrD,MAAM,QAAQ,QAAQ,OAAO;AAAA,IAC7B,IAAI;AAAA,MAAO,OAAO;AAAA,EACnB;AAAA,EACA;AAAA;AAgBM,SAAS,WAAW,CAC1B,MAA4B,WAAW,UAClB;AAAA,EACrB,IAAI,CAAC;AAAA,IAAK;AAAA,EACV,MAAM,SAAS,OAAO,EAAE;AAAA,EACxB,IAAI;AAAA,IAAQ,OAAO;AAAA,EACnB,MAAM,OAAO,WACZ,KACA,oDACD;AAAA,EACA,IAAI;AAAA,IAAM,OAAO;AAAA,EACjB,MAAM,MAAM,WAAW,KAAK,6BAA6B;AAAA,EACzD,IAAI;AAAA,IAAK,OAAO;AAAA,EAChB,MAAM,UACL,OAAO,sBAAsB,cAC1B,oBACA,OAAO,EAAE;AAAA,EACb,OAAO,OAAO,YAAY,YAAY,UAAU,UAAU;AAAA;AAOpD,SAAS,iBAAiB,CAChC,MAAgB,WAAW,UACR;AAAA,EACnB,MAAM,QAAQ,IAAI,cAAc,OAAO;AAAA,EACvC,MAAM,QAAQ,YAAY,GAAG;AAAA,EAC7B,IAAI;AAAA,IAAO,MAAM,aAAa,SAAS,KAAK;AAAA,EAC5C,OAAO;AAAA;AAQD,IAAM,WAAqC;AAC3C,IAAM,WAAoC;",
8
+ "debugId": "79F62D9AAEDF578C64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * An object built the first time it is used, not when its module loads: for a
3
+ * constant whose construction has a cost or a side effect the importer must be
4
+ * able to configure first. The libraries' deprecated zod schema constants are
5
+ * the reason: building a `z.object()` makes zod probe `new Function`, which a
6
+ * strict Content Security Policy reports, and an app can only switch the probe
7
+ * off (`z.config({ jitless: true })`) before the first schema is built.
8
+ *
9
+ * The stand-in is a `Proxy`. Its first trap of any kind builds the object, then
10
+ * copies the object's own properties and prototype onto the proxy's target, so
11
+ * every later operation sees what the built object holds: property reads and
12
+ * method calls, `in`, `Object.keys`, a spread, `instanceof` (through the
13
+ * prototype, or a `Symbol.hasInstance` that reads the object's properties),
14
+ * and the invariants a `Proxy` must keep for non-configurable properties.
15
+ *
16
+ * The stand-in is not the built object itself (`===` tells them apart), and a
17
+ * property written on it lands on the copy. Use it for a value its callers
18
+ * read, never for one they compare by identity, or register: a `.meta()` or
19
+ * `registry.add()` on the stand-in is not seen through the factory's schema.
20
+ *
21
+ * A read of the stand-in while `build` runs throws; if `build` throws, the
22
+ * stand-in goes back to unbuilt and the next use builds again.
23
+ */
24
+ /** `build`'s object, built on the first operation on the returned stand-in. */
25
+ export declare function lazyObject<T extends object>(build: () => T): T;
26
+ /**
27
+ * A function that builds `build`'s value on its first call and returns that
28
+ * same value on every call after: a schema factory.
29
+ */
30
+ export declare function buildOnce<T>(build: () => T): () => T;
31
+ //# sourceMappingURL=lazy-object.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy-object.d.ts","sourceRoot":"","sources":["../../src/lib/lazy-object.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAuC9D;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,CAgBpD"}
@@ -0,0 +1,61 @@
1
+ // src/lib/lazy-object.ts
2
+ function lazyObject(build) {
3
+ const target = {};
4
+ let state = "idle";
5
+ const ensure = () => {
6
+ if (state === "building") {
7
+ throw new Error("lazyObject: the object was read while it was building");
8
+ }
9
+ if (state === "idle") {
10
+ state = "building";
11
+ try {
12
+ const value = build();
13
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(value));
14
+ Object.setPrototypeOf(target, Object.getPrototypeOf(value));
15
+ state = "built";
16
+ } catch (error) {
17
+ state = "idle";
18
+ throw error;
19
+ }
20
+ }
21
+ return target;
22
+ };
23
+ return new Proxy(target, {
24
+ get: (_, key) => Reflect.get(ensure(), key),
25
+ set: (_, key, value) => Reflect.set(ensure(), key, value),
26
+ has: (_, key) => Reflect.has(ensure(), key),
27
+ ownKeys: () => Reflect.ownKeys(ensure()),
28
+ getOwnPropertyDescriptor: (_, key) => Reflect.getOwnPropertyDescriptor(ensure(), key),
29
+ defineProperty: (_, key, descriptor) => Reflect.defineProperty(ensure(), key, descriptor),
30
+ deleteProperty: (_, key) => Reflect.deleteProperty(ensure(), key),
31
+ getPrototypeOf: () => Reflect.getPrototypeOf(ensure()),
32
+ setPrototypeOf: (_, proto) => Reflect.setPrototypeOf(ensure(), proto),
33
+ isExtensible: () => Reflect.isExtensible(ensure()),
34
+ preventExtensions: () => Reflect.preventExtensions(ensure())
35
+ });
36
+ }
37
+ function buildOnce(build) {
38
+ let value;
39
+ let building = false;
40
+ return () => {
41
+ if (value)
42
+ return value.current;
43
+ if (building) {
44
+ throw new Error("buildOnce: the value was read while it was building");
45
+ }
46
+ building = true;
47
+ try {
48
+ value = { current: build() };
49
+ } finally {
50
+ building = false;
51
+ }
52
+ return value.current;
53
+ };
54
+ }
55
+ export {
56
+ buildOnce,
57
+ lazyObject
58
+ };
59
+
60
+ //# debugId=FF185767F4125A5364756E2164756E21
61
+ //# sourceMappingURL=lazy-object.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lib/lazy-object.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * An object built the first time it is used, not when its module loads: for a\n * constant whose construction has a cost or a side effect the importer must be\n * able to configure first. The libraries' deprecated zod schema constants are\n * the reason: building a `z.object()` makes zod probe `new Function`, which a\n * strict Content Security Policy reports, and an app can only switch the probe\n * off (`z.config({ jitless: true })`) before the first schema is built.\n *\n * The stand-in is a `Proxy`. Its first trap of any kind builds the object, then\n * copies the object's own properties and prototype onto the proxy's target, so\n * every later operation sees what the built object holds: property reads and\n * method calls, `in`, `Object.keys`, a spread, `instanceof` (through the\n * prototype, or a `Symbol.hasInstance` that reads the object's properties),\n * and the invariants a `Proxy` must keep for non-configurable properties.\n *\n * The stand-in is not the built object itself (`===` tells them apart), and a\n * property written on it lands on the copy. Use it for a value its callers\n * read, never for one they compare by identity, or register: a `.meta()` or\n * `registry.add()` on the stand-in is not seen through the factory's schema.\n *\n * A read of the stand-in while `build` runs throws; if `build` throws, the\n * stand-in goes back to unbuilt and the next use builds again.\n */\n\n/** `build`'s object, built on the first operation on the returned stand-in. */\nexport function lazyObject<T extends object>(build: () => T): T {\n\tconst target = {} as T;\n\tlet state: 'idle' | 'building' | 'built' = 'idle';\n\tconst ensure = (): T => {\n\t\tif (state === 'building') {\n\t\t\tthrow new Error('lazyObject: the object was read while it was building');\n\t\t}\n\t\tif (state === 'idle') {\n\t\t\tstate = 'building';\n\t\t\ttry {\n\t\t\t\tconst value = build();\n\t\t\t\tObject.defineProperties(\n\t\t\t\t\ttarget,\n\t\t\t\t\tObject.getOwnPropertyDescriptors(value),\n\t\t\t\t);\n\t\t\t\tObject.setPrototypeOf(target, Object.getPrototypeOf(value));\n\t\t\t\tstate = 'built';\n\t\t\t} catch (error) {\n\t\t\t\tstate = 'idle';\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\treturn target;\n\t};\n\treturn new Proxy(target, {\n\t\tget: (_, key) => Reflect.get(ensure(), key),\n\t\tset: (_, key, value) => Reflect.set(ensure(), key, value),\n\t\thas: (_, key) => Reflect.has(ensure(), key),\n\t\townKeys: () => Reflect.ownKeys(ensure()),\n\t\tgetOwnPropertyDescriptor: (_, key) =>\n\t\t\tReflect.getOwnPropertyDescriptor(ensure(), key),\n\t\tdefineProperty: (_, key, descriptor) =>\n\t\t\tReflect.defineProperty(ensure(), key, descriptor),\n\t\tdeleteProperty: (_, key) => Reflect.deleteProperty(ensure(), key),\n\t\tgetPrototypeOf: () => Reflect.getPrototypeOf(ensure()),\n\t\tsetPrototypeOf: (_, proto) => Reflect.setPrototypeOf(ensure(), proto),\n\t\tisExtensible: () => Reflect.isExtensible(ensure()),\n\t\tpreventExtensions: () => Reflect.preventExtensions(ensure()),\n\t});\n}\n\n/**\n * A function that builds `build`'s value on its first call and returns that\n * same value on every call after: a schema factory.\n */\nexport function buildOnce<T>(build: () => T): () => T {\n\tlet value: { current: T } | undefined;\n\tlet building = false;\n\treturn () => {\n\t\tif (value) return value.current;\n\t\tif (building) {\n\t\t\tthrow new Error('buildOnce: the value was read while it was building');\n\t\t}\n\t\tbuilding = true;\n\t\ttry {\n\t\t\tvalue = { current: build() };\n\t\t} finally {\n\t\t\tbuilding = false;\n\t\t}\n\t\treturn value.current;\n\t};\n}\n"
6
+ ],
7
+ "mappings": ";AAyBO,SAAS,UAA4B,CAAC,OAAmB;AAAA,EAC/D,MAAM,SAAS,CAAC;AAAA,EAChB,IAAI,QAAuC;AAAA,EAC3C,MAAM,SAAS,MAAS;AAAA,IACvB,IAAI,UAAU,YAAY;AAAA,MACzB,MAAM,IAAI,MAAM,uDAAuD;AAAA,IACxE;AAAA,IACA,IAAI,UAAU,QAAQ;AAAA,MACrB,QAAQ;AAAA,MACR,IAAI;AAAA,QACH,MAAM,QAAQ,MAAM;AAAA,QACpB,OAAO,iBACN,QACA,OAAO,0BAA0B,KAAK,CACvC;AAAA,QACA,OAAO,eAAe,QAAQ,OAAO,eAAe,KAAK,CAAC;AAAA,QAC1D,QAAQ;AAAA,QACP,OAAO,OAAO;AAAA,QACf,QAAQ;AAAA,QACR,MAAM;AAAA;AAAA,IAER;AAAA,IACA,OAAO;AAAA;AAAA,EAER,OAAO,IAAI,MAAM,QAAQ;AAAA,IACxB,KAAK,CAAC,GAAG,QAAQ,QAAQ,IAAI,OAAO,GAAG,GAAG;AAAA,IAC1C,KAAK,CAAC,GAAG,KAAK,UAAU,QAAQ,IAAI,OAAO,GAAG,KAAK,KAAK;AAAA,IACxD,KAAK,CAAC,GAAG,QAAQ,QAAQ,IAAI,OAAO,GAAG,GAAG;AAAA,IAC1C,SAAS,MAAM,QAAQ,QAAQ,OAAO,CAAC;AAAA,IACvC,0BAA0B,CAAC,GAAG,QAC7B,QAAQ,yBAAyB,OAAO,GAAG,GAAG;AAAA,IAC/C,gBAAgB,CAAC,GAAG,KAAK,eACxB,QAAQ,eAAe,OAAO,GAAG,KAAK,UAAU;AAAA,IACjD,gBAAgB,CAAC,GAAG,QAAQ,QAAQ,eAAe,OAAO,GAAG,GAAG;AAAA,IAChE,gBAAgB,MAAM,QAAQ,eAAe,OAAO,CAAC;AAAA,IACrD,gBAAgB,CAAC,GAAG,UAAU,QAAQ,eAAe,OAAO,GAAG,KAAK;AAAA,IACpE,cAAc,MAAM,QAAQ,aAAa,OAAO,CAAC;AAAA,IACjD,mBAAmB,MAAM,QAAQ,kBAAkB,OAAO,CAAC;AAAA,EAC5D,CAAC;AAAA;AAOK,SAAS,SAAY,CAAC,OAAyB;AAAA,EACrD,IAAI;AAAA,EACJ,IAAI,WAAW;AAAA,EACf,OAAO,MAAM;AAAA,IACZ,IAAI;AAAA,MAAO,OAAO,MAAM;AAAA,IACxB,IAAI,UAAU;AAAA,MACb,MAAM,IAAI,MAAM,qDAAqD;AAAA,IACtE;AAAA,IACA,WAAW;AAAA,IACX,IAAI;AAAA,MACH,QAAQ,EAAE,SAAS,MAAM,EAAE;AAAA,cAC1B;AAAA,MACD,WAAW;AAAA;AAAA,IAEZ,OAAO,MAAM;AAAA;AAAA;",
8
+ "debugId": "FF185767F4125A5364756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Vite's `build.assetsInlineLimit` for a page under a strict Content Security
3
+ * Policy: a font is **never** inlined, every other asset keeps Vite's default
4
+ * (inlined under 4096 bytes).
5
+ *
6
+ * Vite turns a small asset into a `data:` URI, and `font-src 'self'` blocks a
7
+ * font served that way (KaTeX's `KaTeX_Size3-Regular.woff2` is 3.6 KB). Images
8
+ * are usually fine as `data:` URIs (`img-src 'self' data:`), so only fonts are
9
+ * kept as files. Returning `undefined` hands the decision back to Vite.
10
+ *
11
+ * ```ts
12
+ * // vite.config.ts
13
+ * import { cspAssetsInlineLimit } from '@nxgt/material-core/vite';
14
+ * import { defineConfig } from 'vite';
15
+ *
16
+ * export default defineConfig({ build: { assetsInlineLimit: cspAssetsInlineLimit } });
17
+ * ```
18
+ *
19
+ * To keep more out (an SVG, say), wrap it:
20
+ * `(file, content) => file.endsWith('.svg') ? false : cspAssetsInlineLimit(file, content)`.
21
+ *
22
+ * `content` is typed as a `Uint8Array` (Vite passes a `Buffer`, which is one),
23
+ * so the declaration needs no Node types; it is not read.
24
+ */
25
+ export declare function cspAssetsInlineLimit(file: string, _content?: Uint8Array): boolean | undefined;
26
+ //# sourceMappingURL=csp-assets-inline-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csp-assets-inline-limit.d.ts","sourceRoot":"","sources":["../../src/vite/csp-assets-inline-limit.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,oBAAoB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,UAAU,GACnB,OAAO,GAAG,SAAS,CAErB"}
@@ -8,10 +8,14 @@
8
8
  * not have to rediscover it — and because there is exactly one copy of the fix
9
9
  * to maintain, shared by `@nxgt/material` and `@nxgt/material-vue`.
10
10
  *
11
+ * It also holds `cspAssetsInlineLimit`, the `build.assetsInlineLimit` that
12
+ * keeps fonts out of `data:` URIs for a strict Content Security Policy.
13
+ *
11
14
  * It imports nothing at runtime: `Plugin` is a type, erased by the compiler, so
12
15
  * the built `dist/vite.js` has no dependency on `vite` at all.
13
16
  */
14
17
  import type { Plugin } from 'vite';
18
+ export { cspAssetsInlineLimit } from './csp-assets-inline-limit';
15
19
  /**
16
20
  * Bundles pdfkit's fourteen standard fonts instead of leaving them to a
17
21
  * `require` that cannot work in a bundle.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CA6C5C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CA6C5C"}
@@ -1,3 +1,9 @@
1
+ // src/vite/csp-assets-inline-limit.ts
2
+ var FONT = /\.(woff2?|ttf|otf|eot)(\?.*)?$/i;
3
+ function cspAssetsInlineLimit(file, _content) {
4
+ return FONT.test(file) ? false : undefined;
5
+ }
6
+
1
7
  // src/vite/index.ts
2
8
  function pdfkitStandardFonts() {
3
9
  const CALL = /[A-Za-z_$][\w$]*\(\s*(['"])#standard-fonts\/(\w+)\1\s*\)/g;
@@ -24,8 +30,9 @@ ${out}`, map: null };
24
30
  };
25
31
  }
26
32
  export {
33
+ cspAssetsInlineLimit,
27
34
  pdfkitStandardFonts
28
35
  };
29
36
 
30
- //# debugId=03891F2267D73DE964756E2164756E21
37
+ //# debugId=C84B59B7E2D9781E64756E2164756E21
31
38
  //# sourceMappingURL=index.js.map
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/vite/index.ts"],
3
+ "sources": ["../src/vite/csp-assets-inline-limit.ts", "../src/vite/index.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * Build-time helpers for the apps that consume this library.\n *\n * This entry is the odd one out: everything else here runs in a browser or in\n * an SSR server, and the `/vite` entry is Node code that runs inside a\n * consumer's `vite.config.ts` (or a Nuxt app's Nitro build). It is here anyway\n * because the problem it solves is this package's to own — a consumer should\n * not have to rediscover it — and because there is exactly one copy of the fix\n * to maintain, shared by `@nxgt/material` and `@nxgt/material-vue`.\n *\n * It imports nothing at runtime: `Plugin` is a type, erased by the compiler, so\n * the built `dist/vite.js` has no dependency on `vite` at all.\n */\n\nimport type { Plugin } from 'vite';\n\n/**\n * Bundles pdfkit's fourteen standard fonts instead of leaving them to a\n * `require` that cannot work in a bundle.\n *\n * **Only needed by an app that renders PDFs** (`@nxgt/material/print`, or\n * `@nxgt/material-vue/pdf`). No other entry reaches `@react-pdf/renderer`, so\n * an app that does not print never sees this at all.\n * If you do print, read on, because the failure is spectacular and silent until\n * production.\n *\n * Both are built on `@react-pdf/renderer`, which is built on\n * `pdfkit`, whose node build ends with:\n *\n * ```js\n * const require$1 = createRequire(import.meta.url);\n * registerStdFontLoaders({ Helvetica: () => require$1('#standard-fonts/Helvetica'), … });\n * ```\n *\n * `#standard-fonts/*` is a package-`imports` entry **private to pdfkit**, and\n * that is a RUNTIME call, not an import — so no bundler resolves it. Bundle\n * pdfkit into an SSR server (`ssr.noExternal`) and the literal survives into the\n * output, where `createRequire` is rooted at the build directory and the importer\n * is no longer inside pdfkit. The server then throws on **every request**,\n * `/health` included:\n *\n * ```\n * error: Cannot find module '#standard-fonts/Helvetica' from '…/build/app.js'\n * ```\n *\n * A compose healthcheck that fetches a route never goes healthy either, so the\n * container looks like it is failing to start rather than failing to serve.\n *\n * This plugin rewrites those calls into static imports of\n * `./standard-fonts/<name>.mjs`, so the font data lands in the bundle and the\n * server stays self-contained. Use it when the runtime image has **no\n * `node_modules`** — a single-file `bun build` artifact copied alone into the\n * image. When the image does keep `node_modules`, the lighter answer is to leave\n * the library external instead (`--external` it in the bundle step, and keep it\n * out of `ssr.noExternal`), which also keeps ~12 MB of\n * pdfkit out of the artifact. In Nuxt, `nxgtMaterial: { pdf: {\n * bundleStandardFonts: true } }` adds this plugin to the Nitro build.\n *\n * ```ts\n * // `@nxgt/material/vite` in a React app: the same function\n * import { pdfkitStandardFonts } from '@nxgt/material-vue/vite';\n * import { defineConfig } from 'vite';\n *\n * export default defineConfig({\n * plugins: [pdfkitStandardFonts()],\n * ssr: { noExternal: ['@nxgt/material-vue', '@nxgt/material-core'] },\n * });\n * ```\n *\n * Two things that look like fixes and are not:\n *\n * - **`resolve.alias`** mapping `#standard-fonts/` to `pdfkit/standard-fonts/`.\n * An alias rewrites specifiers the resolver is *asked* about; this one is a\n * string handed to `createRequire` at runtime, which the resolver never sees.\n * - **`--external pdfkit`**. It is a transitive dependency, so the runtime cannot\n * resolve it from the app's own directory: `Cannot find package 'pdfkit'`.\n *\n * The plugin throws if a `#standard-fonts/` reference survives the rewrite, so a\n * future pdfkit that changes the shape of those calls fails the build instead of\n * the first request.\n */\nexport function pdfkitStandardFonts(): Plugin {\n\t/**\n\t * Any call whose only argument is that literal. Deliberately not pinned to\n\t * `require$1`, which is a name rollup generated for pdfkit's own build and is\n\t * free to change on any release — there is nothing else one can do with a\n\t * `#standard-fonts/*` specifier but require it.\n\t */\n\tconst CALL = /[A-Za-z_$][\\w$]*\\(\\s*(['\"])#standard-fonts\\/(\\w+)\\1\\s*\\)/g;\n\n\treturn {\n\t\tname: 'nxgt-material:pdfkit-standard-fonts',\n\t\tenforce: 'pre',\n\t\ttransform(code, id) {\n\t\t\tif (!id.includes('pdfkit') || !code.includes('#standard-fonts/')) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst fonts = new Set<string>();\n\t\t\tconst out = code.replace(CALL, (_match, _quote, font: string) => {\n\t\t\t\tfonts.add(font);\n\t\t\t\treturn `__stdFont_${font}`;\n\t\t\t});\n\n\t\t\t// Fail the build rather than ship a server that dies on its first\n\t\t\t// request: an unmatched occurrence means pdfkit changed the shape of\n\t\t\t// these calls, and the whole point of this plugin is that nothing\n\t\t\t// downstream notices the difference until runtime.\n\t\t\tif (out.includes('#standard-fonts/')) {\n\t\t\t\tthis.error(\n\t\t\t\t\t`${id} still references '#standard-fonts/' after the transform — ` +\n\t\t\t\t\t\t'pdfkit changed how it loads its standard fonts. See the doc ' +\n\t\t\t\t\t\t'comment on pdfkitStandardFonts() in the /vite entry.',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst imports = [...fonts]\n\t\t\t\t.map(\n\t\t\t\t\t(font) =>\n\t\t\t\t\t\t`import __stdFont_${font} from './standard-fonts/${font}.mjs';`,\n\t\t\t\t)\n\t\t\t\t.join('\\n');\n\n\t\t\treturn { code: `${imports}\\n${out}`, map: null };\n\t\t},\n\t};\n}\n"
5
+ "/** A font file, by extension, with or without a query string. */\nconst FONT = /\\.(woff2?|ttf|otf|eot)(\\?.*)?$/i;\n\n/**\n * Vite's `build.assetsInlineLimit` for a page under a strict Content Security\n * Policy: a font is **never** inlined, every other asset keeps Vite's default\n * (inlined under 4096 bytes).\n *\n * Vite turns a small asset into a `data:` URI, and `font-src 'self'` blocks a\n * font served that way (KaTeX's `KaTeX_Size3-Regular.woff2` is 3.6 KB). Images\n * are usually fine as `data:` URIs (`img-src 'self' data:`), so only fonts are\n * kept as files. Returning `undefined` hands the decision back to Vite.\n *\n * ```ts\n * // vite.config.ts\n * import { cspAssetsInlineLimit } from '@nxgt/material-core/vite';\n * import { defineConfig } from 'vite';\n *\n * export default defineConfig({ build: { assetsInlineLimit: cspAssetsInlineLimit } });\n * ```\n *\n * To keep more out (an SVG, say), wrap it:\n * `(file, content) => file.endsWith('.svg') ? false : cspAssetsInlineLimit(file, content)`.\n *\n * `content` is typed as a `Uint8Array` (Vite passes a `Buffer`, which is one),\n * so the declaration needs no Node types; it is not read.\n */\nexport function cspAssetsInlineLimit(\n\tfile: string,\n\t_content?: Uint8Array,\n): boolean | undefined {\n\treturn FONT.test(file) ? false : undefined;\n}\n",
6
+ "/**\n * Build-time helpers for the apps that consume this library.\n *\n * This entry is the odd one out: everything else here runs in a browser or in\n * an SSR server, and the `/vite` entry is Node code that runs inside a\n * consumer's `vite.config.ts` (or a Nuxt app's Nitro build). It is here anyway\n * because the problem it solves is this package's to own — a consumer should\n * not have to rediscover it — and because there is exactly one copy of the fix\n * to maintain, shared by `@nxgt/material` and `@nxgt/material-vue`.\n *\n * It also holds `cspAssetsInlineLimit`, the `build.assetsInlineLimit` that\n * keeps fonts out of `data:` URIs for a strict Content Security Policy.\n *\n * It imports nothing at runtime: `Plugin` is a type, erased by the compiler, so\n * the built `dist/vite.js` has no dependency on `vite` at all.\n */\n\nimport type { Plugin } from 'vite';\n\nexport { cspAssetsInlineLimit } from './csp-assets-inline-limit';\n\n/**\n * Bundles pdfkit's fourteen standard fonts instead of leaving them to a\n * `require` that cannot work in a bundle.\n *\n * **Only needed by an app that renders PDFs** (`@nxgt/material/print`, or\n * `@nxgt/material-vue/pdf`). No other entry reaches `@react-pdf/renderer`, so\n * an app that does not print never sees this at all.\n * If you do print, read on, because the failure is spectacular and silent until\n * production.\n *\n * Both are built on `@react-pdf/renderer`, which is built on\n * `pdfkit`, whose node build ends with:\n *\n * ```js\n * const require$1 = createRequire(import.meta.url);\n * registerStdFontLoaders({ Helvetica: () => require$1('#standard-fonts/Helvetica'), … });\n * ```\n *\n * `#standard-fonts/*` is a package-`imports` entry **private to pdfkit**, and\n * that is a RUNTIME call, not an import — so no bundler resolves it. Bundle\n * pdfkit into an SSR server (`ssr.noExternal`) and the literal survives into the\n * output, where `createRequire` is rooted at the build directory and the importer\n * is no longer inside pdfkit. The server then throws on **every request**,\n * `/health` included:\n *\n * ```\n * error: Cannot find module '#standard-fonts/Helvetica' from '…/build/app.js'\n * ```\n *\n * A compose healthcheck that fetches a route never goes healthy either, so the\n * container looks like it is failing to start rather than failing to serve.\n *\n * This plugin rewrites those calls into static imports of\n * `./standard-fonts/<name>.mjs`, so the font data lands in the bundle and the\n * server stays self-contained. Use it when the runtime image has **no\n * `node_modules`** — a single-file `bun build` artifact copied alone into the\n * image. When the image does keep `node_modules`, the lighter answer is to leave\n * the library external instead (`--external` it in the bundle step, and keep it\n * out of `ssr.noExternal`), which also keeps ~12 MB of\n * pdfkit out of the artifact. In Nuxt, `nxgtMaterial: { pdf: {\n * bundleStandardFonts: true } }` adds this plugin to the Nitro build.\n *\n * ```ts\n * // `@nxgt/material/vite` in a React app: the same function\n * import { pdfkitStandardFonts } from '@nxgt/material-vue/vite';\n * import { defineConfig } from 'vite';\n *\n * export default defineConfig({\n * plugins: [pdfkitStandardFonts()],\n * ssr: { noExternal: ['@nxgt/material-vue', '@nxgt/material-core'] },\n * });\n * ```\n *\n * Two things that look like fixes and are not:\n *\n * - **`resolve.alias`** mapping `#standard-fonts/` to `pdfkit/standard-fonts/`.\n * An alias rewrites specifiers the resolver is *asked* about; this one is a\n * string handed to `createRequire` at runtime, which the resolver never sees.\n * - **`--external pdfkit`**. It is a transitive dependency, so the runtime cannot\n * resolve it from the app's own directory: `Cannot find package 'pdfkit'`.\n *\n * The plugin throws if a `#standard-fonts/` reference survives the rewrite, so a\n * future pdfkit that changes the shape of those calls fails the build instead of\n * the first request.\n */\nexport function pdfkitStandardFonts(): Plugin {\n\t/**\n\t * Any call whose only argument is that literal. Deliberately not pinned to\n\t * `require$1`, which is a name rollup generated for pdfkit's own build and is\n\t * free to change on any release — there is nothing else one can do with a\n\t * `#standard-fonts/*` specifier but require it.\n\t */\n\tconst CALL = /[A-Za-z_$][\\w$]*\\(\\s*(['\"])#standard-fonts\\/(\\w+)\\1\\s*\\)/g;\n\n\treturn {\n\t\tname: 'nxgt-material:pdfkit-standard-fonts',\n\t\tenforce: 'pre',\n\t\ttransform(code, id) {\n\t\t\tif (!id.includes('pdfkit') || !code.includes('#standard-fonts/')) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst fonts = new Set<string>();\n\t\t\tconst out = code.replace(CALL, (_match, _quote, font: string) => {\n\t\t\t\tfonts.add(font);\n\t\t\t\treturn `__stdFont_${font}`;\n\t\t\t});\n\n\t\t\t// Fail the build rather than ship a server that dies on its first\n\t\t\t// request: an unmatched occurrence means pdfkit changed the shape of\n\t\t\t// these calls, and the whole point of this plugin is that nothing\n\t\t\t// downstream notices the difference until runtime.\n\t\t\tif (out.includes('#standard-fonts/')) {\n\t\t\t\tthis.error(\n\t\t\t\t\t`${id} still references '#standard-fonts/' after the transform — ` +\n\t\t\t\t\t\t'pdfkit changed how it loads its standard fonts. See the doc ' +\n\t\t\t\t\t\t'comment on pdfkitStandardFonts() in the /vite entry.',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst imports = [...fonts]\n\t\t\t\t.map(\n\t\t\t\t\t(font) =>\n\t\t\t\t\t\t`import __stdFont_${font} from './standard-fonts/${font}.mjs';`,\n\t\t\t\t)\n\t\t\t\t.join('\\n');\n\n\t\t\treturn { code: `${imports}\\n${out}`, map: null };\n\t\t},\n\t};\n}\n"
6
7
  ],
7
- "mappings": ";AAiFO,SAAS,mBAAmB,GAAW;AAAA,EAO7C,MAAM,OAAO;AAAA,EAEb,OAAO;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,CAAC,MAAM,IAAI;AAAA,MACnB,IAAI,CAAC,GAAG,SAAS,QAAQ,KAAK,CAAC,KAAK,SAAS,kBAAkB,GAAG;AAAA,QACjE,OAAO;AAAA,MACR;AAAA,MAEA,MAAM,QAAQ,IAAI;AAAA,MAClB,MAAM,MAAM,KAAK,QAAQ,MAAM,CAAC,QAAQ,QAAQ,SAAiB;AAAA,QAChE,MAAM,IAAI,IAAI;AAAA,QACd,OAAO,aAAa;AAAA,OACpB;AAAA,MAMD,IAAI,IAAI,SAAS,kBAAkB,GAAG;AAAA,QACrC,KAAK,MACJ,GAAG,kEACF,iEACA,sDACF;AAAA,MACD;AAAA,MAEA,MAAM,UAAU,CAAC,GAAG,KAAK,EACvB,IACA,CAAC,SACA,oBAAoB,+BAA+B,YACrD,EACC,KAAK;AAAA,CAAI;AAAA,MAEX,OAAO,EAAE,MAAM,GAAG;AAAA,EAAY,OAAO,KAAK,KAAK;AAAA;AAAA,EAEjD;AAAA;",
8
- "debugId": "03891F2267D73DE964756E2164756E21",
8
+ "mappings": ";AACA,IAAM,OAAO;AA0BN,SAAS,oBAAoB,CACnC,MACA,UACsB;AAAA,EACtB,OAAO,KAAK,KAAK,IAAI,IAAI,QAAQ;AAAA;;;ACuD3B,SAAS,mBAAmB,GAAW;AAAA,EAO7C,MAAM,OAAO;AAAA,EAEb,OAAO;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,CAAC,MAAM,IAAI;AAAA,MACnB,IAAI,CAAC,GAAG,SAAS,QAAQ,KAAK,CAAC,KAAK,SAAS,kBAAkB,GAAG;AAAA,QACjE,OAAO;AAAA,MACR;AAAA,MAEA,MAAM,QAAQ,IAAI;AAAA,MAClB,MAAM,MAAM,KAAK,QAAQ,MAAM,CAAC,QAAQ,QAAQ,SAAiB;AAAA,QAChE,MAAM,IAAI,IAAI;AAAA,QACd,OAAO,aAAa;AAAA,OACpB;AAAA,MAMD,IAAI,IAAI,SAAS,kBAAkB,GAAG;AAAA,QACrC,KAAK,MACJ,GAAG,kEACF,iEACA,sDACF;AAAA,MACD;AAAA,MAEA,MAAM,UAAU,CAAC,GAAG,KAAK,EACvB,IACA,CAAC,SACA,oBAAoB,+BAA+B,YACrD,EACC,KAAK;AAAA,CAAI;AAAA,MAEX,OAAO,EAAE,MAAM,GAAG;AAAA,EAAY,OAAO,KAAK,KAAK;AAAA;AAAA,EAEjD;AAAA;",
9
+ "debugId": "C84B59B7E2D9781E64756E2164756E21",
9
10
  "names": []
10
11
  }
package/docs/roadmap.md CHANGED
@@ -6,10 +6,33 @@ major they target.
6
6
 
7
7
  ## Now
8
8
 
9
- Nothing in progress right now; see Next.
9
+ Nothing is in progress.
10
10
 
11
11
  ## Next
12
12
 
13
+ - **One Vite plugin for the whole setup** — `nxgtMaterial()` is implemented
14
+ once in `/vite` and re-exported by `@nxgt/material/vite` and
15
+ `@nxgt/material-vue/vite`, so both libraries get the same one-call setup.
16
+ Every option is on by default and each can be turned off or refined; the
17
+ helpers that exist today (`cspAssetsInlineLimit` and the others) stay
18
+ exported.
19
+ - `csp`: a nonce placeholder through `html.cspNonce` and
20
+ `applyCspNonce(html, nonce)`; zod's jitless mode as an inline nonced
21
+ script in `index.html` (`ZOD_JITLESS_SCRIPT` for server rendering); the
22
+ recommended policy sent by the dev server as Report-Only.
23
+ - `fonts`: the KaTeX fonts emitted as files, and `sans: 'inter'` for a
24
+ self-hosted Inter.
25
+ - `styles`: a build that fails with the exact fix when the stylesheet import
26
+ or the `@source` line is missing.
27
+ - `pdf`: pdfkit's standard fonts handled automatically when
28
+ `@react-pdf/renderer` is installed, and `bundle: true` (the Nuxt module's
29
+ `pdf.bundleStandardFonts` today) for a server that has no
30
+ `node_modules`.
31
+ - Nuxt: the Nuxt module takes the same options.
32
+ - Server helpers: `createCspNonce()` (Web Crypto) and `cspPolicy({ ... })`,
33
+ which returns `{ directives, header }` as `@nxgt/material`'s
34
+ Content Security Policy guide describes; `NonceProvider`'s `nonce`
35
+ becomes optional on the client. Ships as minors.
13
36
  - **The shared media player** — the player state machine (playback, queue and
14
37
  shuffle, chapters, transcript sync, resume position, quality levels, Wake
15
38
  Lock), its keyboard shortcuts and the shortcut-help list, so
@@ -38,6 +61,8 @@ Nothing in progress right now; see Next.
38
61
 
39
62
  ## Not planned
40
63
 
64
+ - **A Next.js plugin** — the plugin targets Vite only. No supported consumer
65
+ builds with Next.js, and `withNxgtPrint` stays the config helper for it.
41
66
  - **Components.** This package stays framework-free: React components are in
42
67
  `@nxgt/material`, Vue ones in `@nxgt/material-vue`.
43
68
  - **A fake DOM for tests.** The one the libraries' own tests use is the
@@ -51,6 +76,13 @@ Nothing in progress right now; see Next.
51
76
 
52
77
  ## Shipped
53
78
 
79
+ - **The page's Content Security Policy nonce** (`/csp-nonce`) — one place both
80
+ libraries read the nonce from, so every `<style>` they create carries it
81
+ under a strict `style-src-elem`, and `cspAssetsInlineLimit` (`/vite`), which
82
+ keeps fonts out of `data:` URIs. 0.9.0.
83
+ - **Built on first use** (`/lazy-object`) — `buildOnce` and `lazyObject`, with
84
+ which both libraries build their zod schemas on first use rather than at
85
+ import, so an app under a strict CSP can configure zod first. 0.9.0.
54
86
  - **Debounce, text matching and the async search** (`/debounce`,
55
87
  `/text-match`, `/async-search`) — the debounce both libraries'
56
88
  `SearchField` kept a copy of each, accent- and case-insensitive matching,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nxgt/material-core",
3
- "version": "0.8.0",
4
- "description": "The framework-free core of @nxgt/material and @nxgt/material-vue: the PDF document model and its react-pdf renderer, print formatters, PDF file helpers, the PDF viewer's pure modules, the currency digits, the file-size formatter, the field-blur layers, a field's first focusable control, the aria-describedby joiner, the inert background of a modal layer, Escape forwarded while an overlay closes, the Toaster's touch-hover guard and its focus hand-back, the zod issue messages, the date range presets, the debounce, accent-insensitive text matching and the race-free async search behind Autocomplete and SearchField, the file preview's kind detection and text reader, the pdfkit fonts Vite plugin and the Node-only pdfkit fonts resolution for Next.js and Nuxt builds",
3
+ "version": "0.9.1",
4
+ "description": "The framework-free core of @nxgt/material and @nxgt/material-vue: the PDF document model and its react-pdf renderer, print formatters, PDF file helpers, the PDF viewer's pure modules, the currency digits, the file-size formatter, the field-blur layers, a field's first focusable control, the aria-describedby joiner, the inert background of a modal layer, Escape forwarded while an overlay closes, the Toaster's touch-hover guard and its focus hand-back, the page's Content Security Policy nonce, an object built on first use, the zod issue messages, the date range presets, the debounce, accent-insensitive text matching and the race-free async search behind Autocomplete and SearchField, the file preview's kind detection and text reader, the pdfkit fonts Vite plugin, a CSP-safe Vite assetsInlineLimit and the Node-only pdfkit fonts resolution for Next.js and Nuxt builds",
5
5
  "license": "MIT",
6
6
  "bugs": {
7
7
  "email": "stevegires@gmail.com"
@@ -158,6 +158,16 @@
158
158
  "import": "./dist/lib/date-range.js",
159
159
  "default": "./dist/lib/date-range.js"
160
160
  },
161
+ "./csp-nonce": {
162
+ "types": "./dist/lib/csp-nonce.d.ts",
163
+ "import": "./dist/lib/csp-nonce.js",
164
+ "default": "./dist/lib/csp-nonce.js"
165
+ },
166
+ "./lazy-object": {
167
+ "types": "./dist/lib/lazy-object.d.ts",
168
+ "import": "./dist/lib/lazy-object.js",
169
+ "default": "./dist/lib/lazy-object.js"
170
+ },
161
171
  "./debounce": {
162
172
  "types": "./dist/lib/debounce.d.ts",
163
173
  "import": "./dist/lib/debounce.js",
@@ -249,6 +259,8 @@
249
259
  "src/lib/slider-ticks.ts",
250
260
  "src/lib/emoji-groups.ts",
251
261
  "src/lib/date-range.ts",
262
+ "src/lib/csp-nonce.ts",
263
+ "src/lib/lazy-object.ts",
252
264
  "src/lib/debounce.ts",
253
265
  "src/lib/text-match.ts",
254
266
  "src/lib/async-search.ts",