@ztwoint/z-ui 0.1.149 → 0.1.151

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.
@@ -1,5 +1,6 @@
1
- import { useRef as c, useState as U, useCallback as h, useEffect as B } from "react";
2
- function W(r) {
1
+ import { useRef as l, useState as U, useCallback as g, useEffect as q } from "react";
2
+ import { injectPdfTitle as z } from "./pdf-title-injector.js";
3
+ function G(r) {
3
4
  if (typeof r == "string")
4
5
  try {
5
6
  const n = new URL(r, "http://localhost").pathname.split("/").pop();
@@ -8,66 +9,66 @@ function W(r) {
8
9
  }
9
10
  return r instanceof File ? r.name : "PDF Preview";
10
11
  }
11
- function z(r) {
12
- const { src: a, open: n, onOpenChange: u, onError: C, onLoad: w, downloadFilename: L } = r, i = c(a);
13
- i.current = a;
14
- const f = c(w);
15
- f.current = w;
16
- const d = c(C);
17
- d.current = C;
18
- const P = c(L);
19
- P.current = L;
20
- const [O, S] = U(!1), [j, p] = U("idle"), [D, m] = U(null), [T, g] = U(null), b = c(null), v = c(null), y = n !== void 0, F = y ? n : O, o = h(() => {
12
+ function K(r) {
13
+ const { src: a, open: n, onOpenChange: i, onError: w, onLoad: P, downloadFilename: C } = r, u = l(a);
14
+ u.current = a;
15
+ const f = l(P);
16
+ f.current = P;
17
+ const d = l(w);
18
+ d.current = w;
19
+ const L = l(C);
20
+ L.current = C;
21
+ const [O, S] = U(!1), [T, p] = U("idle"), [B, m] = U(null), [D, h] = U(null), b = l(null), v = l(null), y = n !== void 0, F = y ? n : O, o = g(() => {
21
22
  b.current && (URL.revokeObjectURL(b.current), b.current = null);
22
- }, []), R = h(async () => {
23
- var s, E, k;
24
- (s = v.current) == null || s.abort();
23
+ }, []), R = g(async () => {
24
+ var c, E, j;
25
+ (c = v.current) == null || c.abort();
25
26
  const e = new AbortController();
26
- v.current = e, p("loading"), g(null), m(null), o();
27
+ v.current = e, p("loading"), h(null), m(null), o();
27
28
  try {
28
29
  let t;
29
- if (typeof i.current == "function" ? t = await i.current(e.signal) : t = i.current, e.signal.aborted) return;
30
+ if (typeof u.current == "function" ? t = await u.current(e.signal) : t = u.current, e.signal.aborted) return;
30
31
  if (typeof t == "string")
31
32
  m(t);
32
33
  else {
33
- const l = URL.createObjectURL(t);
34
- b.current = l;
35
- const I = P.current || (t instanceof File ? t.name : null);
36
- m(I ? `${l}#${encodeURIComponent(I)}` : l);
34
+ const s = L.current || (t instanceof File ? t.name : null), W = s ? await z(t, s) : t;
35
+ if (e.signal.aborted) return;
36
+ const k = URL.createObjectURL(W);
37
+ b.current = k, m(k);
37
38
  }
38
39
  p("ready"), (E = f.current) == null || E.call(f);
39
40
  } catch (t) {
40
41
  if (e.signal.aborted) return;
41
- const l = {
42
+ const s = {
42
43
  message: t instanceof Error ? t.message : "Failed to load PDF",
43
44
  cause: t
44
45
  };
45
- g(l), p("error"), (k = d.current) == null || k.call(d, l);
46
+ h(s), p("error"), (j = d.current) == null || j.call(d, s);
46
47
  }
47
- }, [o]), $ = h(
48
+ }, [o]), I = g(
48
49
  (e) => {
49
- var s;
50
- y || S(e), u == null || u(e), e || ((s = v.current) == null || s.abort(), p("idle"), m(null), g(null), o());
50
+ var c;
51
+ y || S(e), i == null || i(e), e || ((c = v.current) == null || c.abort(), p("idle"), m(null), h(null), o());
51
52
  },
52
- [y, u, o]
53
+ [y, i, o]
53
54
  );
54
- B(() => (F && R(), () => {
55
+ q(() => (F && R(), () => {
55
56
  var e;
56
57
  (e = v.current) == null || e.abort(), o();
57
58
  }), [F, R, o]);
58
- const x = h(() => {
59
+ const x = g(() => {
59
60
  R();
60
- }, [R]), A = W(a);
61
+ }, [R]), A = G(a);
61
62
  return {
62
63
  open: F,
63
- handleOpenChange: $,
64
- status: j,
65
- resolvedUrl: D,
66
- error: T,
64
+ handleOpenChange: I,
65
+ status: T,
66
+ resolvedUrl: B,
67
+ error: D,
67
68
  retry: x,
68
69
  resolvedTitle: A
69
70
  };
70
71
  }
71
72
  export {
72
- z as usePdfPreview
73
+ K as usePdfPreview
73
74
  };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Injects a /Title into a PDF's metadata via an incremental update.
3
+ *
4
+ * ## Problem
5
+ * When displaying a PDF via a blob URL (`blob:http://.../<uuid>`), Chrome's
6
+ * built-in PDF viewer (PDFium) shows the blob UUID as the document name in
7
+ * the toolbar — e.g. "f48f9554-646a-4...". There is no standard web API to
8
+ * control this; the File constructor name, URL fragments (#filename=), and
9
+ * Content-Disposition headers are all ignored for blob URLs.
10
+ *
11
+ * ## Solution
12
+ * Chrome's PDF viewer reads the `/Title` field from the PDF's Info dictionary
13
+ * and displays it in the toolbar when present. This function appends a new
14
+ * Info dictionary object containing the desired title using a PDF incremental
15
+ * update — the original content is never modified, only ~200 bytes are
16
+ * appended at the end. This keeps it fast even for large PDFs (50MB+ with
17
+ * heavy images), since we never re-parse or re-serialize the PDF body.
18
+ *
19
+ * ## What it covers
20
+ * - Chrome toolbar title: Shows the injected /Title instead of the UUID
21
+ * - Download button: Handled separately by the component via <a download="filename">
22
+ *
23
+ * ## What it does NOT cover
24
+ * - Ctrl+S "Save As" filename: Chrome still derives this from the blob URL,
25
+ * not the PDF metadata. Only a Service Worker + Cache API approach can fix
26
+ * this (see https://stackoverflow.com/q/53548182), which is too complex
27
+ * for this use case.
28
+ *
29
+ * ## Edge cases (falls back silently to original blob — UUID shown)
30
+ * - Password-protected / encrypted PDFs: The appended Info dictionary is
31
+ * unencrypted, which Chrome may ignore. For full encrypted PDF support,
32
+ * use `pdf-lib` (lazy-loaded via `await import('pdf-lib')`) which
33
+ * understands PDF encryption and can set metadata correctly.
34
+ * - PDFs with cross-reference streams (PDF 1.5+): Works if `/Size` appears
35
+ * in plain text within the xref stream object dictionary (most common).
36
+ * May fail on heavily compressed xref streams where `/Size` is not
37
+ * visible as plain text.
38
+ * - Malformed or non-PDF blobs: Regex finds no `startxref` → returns
39
+ * original blob unchanged.
40
+ *
41
+ * ## Performance
42
+ * - Reads the blob once as ArrayBuffer (already in memory from the fetch)
43
+ * - Decodes to string for two regex searches (`/Size`, `startxref`)
44
+ * - Appends ~200 bytes (new object + xref + trailer)
45
+ * - No re-parsing, no re-serialization of the PDF body
46
+ * - Significantly lighter than pdf-lib which parses + rewrites everything
47
+ */
48
+ export declare function injectPdfTitle(blob: Blob, title: string): Promise<Blob>;
@@ -0,0 +1,39 @@
1
+ async function O(t, r) {
2
+ try {
3
+ const n = await t.arrayBuffer(), e = new Uint8Array(n), s = new TextDecoder("latin1").decode(e), i = s.lastIndexOf("startxref");
4
+ if (i === -1) return t;
5
+ const y = s.substring(i + 9).trim(), f = parseInt(y, 10);
6
+ if (isNaN(f)) return t;
7
+ const o = s.match(/\/Size\s+(\d+)/g);
8
+ if (!o) return t;
9
+ const d = o[o.length - 1].match(/\d+/);
10
+ if (!d) return t;
11
+ const l = parseInt(d[0], 10), c = l, w = l + 1, $ = F(r), p = e[e.length - 1], x = p !== 10 && p !== 13 ? `
12
+ ` : "", S = new TextEncoder(), j = S.encode(x), h = e.length + j.length, g = `${c} 0 obj
13
+ << /Title ${$} >>
14
+ endobj
15
+ `, z = S.encode(g), B = h + z.length, I = String(h).padStart(10, "0"), T = `xref
16
+ ${c} 1
17
+ ${I} 00000 n\r
18
+ `, m = `trailer
19
+ << /Size ${w} /Info ${c} 0 R /Prev ${f} >>
20
+ startxref
21
+ ${B}
22
+ %%EOF
23
+ `, E = x + g + T + m, u = new TextEncoder().encode(E), a = new Uint8Array(e.length + u.length);
24
+ return a.set(e), a.set(u, e.length), new Blob([a], { type: "application/pdf" });
25
+ } catch {
26
+ return t;
27
+ }
28
+ }
29
+ function F(t) {
30
+ let r = "FEFF";
31
+ for (let n = 0; n < t.length; n++) {
32
+ const e = t.charCodeAt(n);
33
+ r += e.toString(16).padStart(4, "0").toUpperCase();
34
+ }
35
+ return `<${r}>`;
36
+ }
37
+ export {
38
+ O as injectPdfTitle
39
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Injects a /Title into a PDF's metadata via an incremental update.
3
+ *
4
+ * ## Problem
5
+ * When displaying a PDF via a blob URL (`blob:http://.../<uuid>`), Chrome's
6
+ * built-in PDF viewer (PDFium) shows the blob UUID as the document name in
7
+ * the toolbar — e.g. "f48f9554-646a-4...". There is no standard web API to
8
+ * control this; the File constructor name, URL fragments (#filename=), and
9
+ * Content-Disposition headers are all ignored for blob URLs.
10
+ *
11
+ * ## Solution
12
+ * Chrome's PDF viewer reads the `/Title` field from the PDF's Info dictionary
13
+ * and displays it in the toolbar when present. This function appends a new
14
+ * Info dictionary object containing the desired title using a PDF incremental
15
+ * update — the original content is never modified, only ~200 bytes are
16
+ * appended at the end. This keeps it fast even for large PDFs (50MB+ with
17
+ * heavy images), since we never re-parse or re-serialize the PDF body.
18
+ *
19
+ * ## What it covers
20
+ * - Chrome toolbar title: Shows the injected /Title instead of the UUID
21
+ * - Download button: Handled separately by the component via <a download="filename">
22
+ *
23
+ * ## What it does NOT cover
24
+ * - Ctrl+S "Save As" filename: Chrome still derives this from the blob URL,
25
+ * not the PDF metadata. Only a Service Worker + Cache API approach can fix
26
+ * this (see https://stackoverflow.com/q/53548182), which is too complex
27
+ * for this use case.
28
+ *
29
+ * ## Edge cases (falls back silently to original blob — UUID shown)
30
+ * - Password-protected / encrypted PDFs: The appended Info dictionary is
31
+ * unencrypted, which Chrome may ignore. For full encrypted PDF support,
32
+ * use `pdf-lib` (lazy-loaded via `await import('pdf-lib')`) which
33
+ * understands PDF encryption and can set metadata correctly.
34
+ * - PDFs with cross-reference streams (PDF 1.5+): Works if `/Size` appears
35
+ * in plain text within the xref stream object dictionary (most common).
36
+ * May fail on heavily compressed xref streams where `/Size` is not
37
+ * visible as plain text.
38
+ * - Malformed or non-PDF blobs: Regex finds no `startxref` → returns
39
+ * original blob unchanged.
40
+ *
41
+ * ## Performance
42
+ * - Reads the blob once as ArrayBuffer (already in memory from the fetch)
43
+ * - Decodes to string for two regex searches (`/Size`, `startxref`)
44
+ * - Appends ~200 bytes (new object + xref + trailer)
45
+ * - No re-parsing, no re-serialization of the PDF body
46
+ * - Significantly lighter than pdf-lib which parses + rewrites everything
47
+ */
48
+ export declare function injectPdfTitle(blob: Blob, title: string): Promise<Blob>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ztwoint/z-ui",
3
- "version": "0.1.149",
3
+ "version": "0.1.151",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",