@kitsy/pdf 0.0.0-stage → 0.1.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 (132) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +16 -0
  4. package/README.md +229 -2
  5. package/THIRD_PARTY_NOTICES.md +48 -0
  6. package/dist/chunk-2K5X7L5J.js +43 -0
  7. package/dist/chunk-2K5X7L5J.js.map +1 -0
  8. package/dist/chunk-4EAYBJ6G.js +362 -0
  9. package/dist/chunk-4EAYBJ6G.js.map +1 -0
  10. package/dist/chunk-4LBJTDV5.js +165 -0
  11. package/dist/chunk-4LBJTDV5.js.map +1 -0
  12. package/dist/chunk-4USCGC24.js +304 -0
  13. package/dist/chunk-4USCGC24.js.map +1 -0
  14. package/dist/chunk-5OIMR74G.js +273 -0
  15. package/dist/chunk-5OIMR74G.js.map +1 -0
  16. package/dist/chunk-6NFNAKWA.js +257 -0
  17. package/dist/chunk-6NFNAKWA.js.map +1 -0
  18. package/dist/chunk-7MTEMQLL.js +47 -0
  19. package/dist/chunk-7MTEMQLL.js.map +1 -0
  20. package/dist/chunk-7V4SWTUK.js +83 -0
  21. package/dist/chunk-7V4SWTUK.js.map +1 -0
  22. package/dist/chunk-7XH7MFZ4.js +295 -0
  23. package/dist/chunk-7XH7MFZ4.js.map +1 -0
  24. package/dist/chunk-BK3MYSUS.js +61 -0
  25. package/dist/chunk-BK3MYSUS.js.map +1 -0
  26. package/dist/chunk-BLHNKR3S.js +35 -0
  27. package/dist/chunk-BLHNKR3S.js.map +1 -0
  28. package/dist/chunk-CCJ43SIX.js +342 -0
  29. package/dist/chunk-CCJ43SIX.js.map +1 -0
  30. package/dist/chunk-CNEDWATL.js +210 -0
  31. package/dist/chunk-CNEDWATL.js.map +1 -0
  32. package/dist/chunk-CYTWDJE2.js +152 -0
  33. package/dist/chunk-CYTWDJE2.js.map +1 -0
  34. package/dist/chunk-DKVOICYZ.js +45 -0
  35. package/dist/chunk-DKVOICYZ.js.map +1 -0
  36. package/dist/chunk-DPEJU4P6.js +79 -0
  37. package/dist/chunk-DPEJU4P6.js.map +1 -0
  38. package/dist/chunk-DQPABZTU.js +22 -0
  39. package/dist/chunk-DQPABZTU.js.map +1 -0
  40. package/dist/chunk-EKHEY3LI.js +46 -0
  41. package/dist/chunk-EKHEY3LI.js.map +1 -0
  42. package/dist/chunk-G467LFKC.js +191 -0
  43. package/dist/chunk-G467LFKC.js.map +1 -0
  44. package/dist/chunk-GLYWSVSB.js +250 -0
  45. package/dist/chunk-GLYWSVSB.js.map +1 -0
  46. package/dist/chunk-GXBIMUO7.js +135 -0
  47. package/dist/chunk-GXBIMUO7.js.map +1 -0
  48. package/dist/chunk-H6EERRCR.js +180 -0
  49. package/dist/chunk-H6EERRCR.js.map +1 -0
  50. package/dist/chunk-HDTM3LXD.js +35 -0
  51. package/dist/chunk-HDTM3LXD.js.map +1 -0
  52. package/dist/chunk-IGMX56C3.js +180 -0
  53. package/dist/chunk-IGMX56C3.js.map +1 -0
  54. package/dist/chunk-J4IRICCJ.js +74 -0
  55. package/dist/chunk-J4IRICCJ.js.map +1 -0
  56. package/dist/chunk-JXKQ7S6W.js +331 -0
  57. package/dist/chunk-JXKQ7S6W.js.map +1 -0
  58. package/dist/chunk-KY6DXS6S.js +34 -0
  59. package/dist/chunk-KY6DXS6S.js.map +1 -0
  60. package/dist/chunk-NOFGCYGS.js +22 -0
  61. package/dist/chunk-NOFGCYGS.js.map +1 -0
  62. package/dist/chunk-OSHUJRKD.js +131 -0
  63. package/dist/chunk-OSHUJRKD.js.map +1 -0
  64. package/dist/chunk-PMH7OWPF.js +40 -0
  65. package/dist/chunk-PMH7OWPF.js.map +1 -0
  66. package/dist/chunk-PRBKSX3A.js +299 -0
  67. package/dist/chunk-PRBKSX3A.js.map +1 -0
  68. package/dist/chunk-SVPUX7AM.js +48 -0
  69. package/dist/chunk-SVPUX7AM.js.map +1 -0
  70. package/dist/chunk-TR6HJ4NO.js +659 -0
  71. package/dist/chunk-TR6HJ4NO.js.map +1 -0
  72. package/dist/chunk-TV3E4YUF.js +69 -0
  73. package/dist/chunk-TV3E4YUF.js.map +1 -0
  74. package/dist/chunk-U2ROTOPF.js +256 -0
  75. package/dist/chunk-U2ROTOPF.js.map +1 -0
  76. package/dist/chunk-VCTN77K2.js +112 -0
  77. package/dist/chunk-VCTN77K2.js.map +1 -0
  78. package/dist/chunk-WEOPDD3P.js +120 -0
  79. package/dist/chunk-WEOPDD3P.js.map +1 -0
  80. package/dist/chunk-ZBZ7OC6I.js +228 -0
  81. package/dist/chunk-ZBZ7OC6I.js.map +1 -0
  82. package/dist/config-CJYo46X9.d.ts +19 -0
  83. package/dist/index.d.ts +487 -0
  84. package/dist/index.js +23 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/react/certificate-sign.d.ts +5 -0
  87. package/dist/react/certificate-sign.js +14 -0
  88. package/dist/react/certificate-sign.js.map +1 -0
  89. package/dist/react/compress.d.ts +5 -0
  90. package/dist/react/compress.js +8 -0
  91. package/dist/react/compress.js.map +1 -0
  92. package/dist/react/fill-form.d.ts +5 -0
  93. package/dist/react/fill-form.js +8 -0
  94. package/dist/react/fill-form.js.map +1 -0
  95. package/dist/react/images-to-pdf.d.ts +5 -0
  96. package/dist/react/images-to-pdf.js +7 -0
  97. package/dist/react/images-to-pdf.js.map +1 -0
  98. package/dist/react/merge.d.ts +5 -0
  99. package/dist/react/merge.js +8 -0
  100. package/dist/react/merge.js.map +1 -0
  101. package/dist/react/metadata.d.ts +5 -0
  102. package/dist/react/metadata.js +8 -0
  103. package/dist/react/metadata.js.map +1 -0
  104. package/dist/react/organize.d.ts +5 -0
  105. package/dist/react/organize.js +12 -0
  106. package/dist/react/organize.js.map +1 -0
  107. package/dist/react/pdf-to-images.d.ts +5 -0
  108. package/dist/react/pdf-to-images.js +13 -0
  109. package/dist/react/pdf-to-images.js.map +1 -0
  110. package/dist/react/protect.d.ts +5 -0
  111. package/dist/react/protect.js +8 -0
  112. package/dist/react/protect.js.map +1 -0
  113. package/dist/react/sign.d.ts +5 -0
  114. package/dist/react/sign.js +13 -0
  115. package/dist/react/sign.js.map +1 -0
  116. package/dist/react/split.d.ts +5 -0
  117. package/dist/react/split.js +11 -0
  118. package/dist/react/split.js.map +1 -0
  119. package/dist/react/verify-signatures.d.ts +5 -0
  120. package/dist/react/verify-signatures.js +8 -0
  121. package/dist/react/verify-signatures.js.map +1 -0
  122. package/dist/react/watermark.d.ts +5 -0
  123. package/dist/react/watermark.js +10 -0
  124. package/dist/react/watermark.js.map +1 -0
  125. package/dist/react.d.ts +40 -0
  126. package/dist/react.js +40 -0
  127. package/dist/react.js.map +1 -0
  128. package/dist/styles.css +2 -0
  129. package/dist/vite.d.ts +5 -0
  130. package/dist/vite.js +9 -0
  131. package/dist/vite.js.map +1 -0
  132. package/package.json +163 -4
@@ -0,0 +1,250 @@
1
+ import { planSplit, splitPdf } from './chunk-TV3E4YUF.js';
2
+ import { createZip } from './chunk-7V4SWTUK.js';
3
+ import { useSinglePdf } from './chunk-BK3MYSUS.js';
4
+ import { PdfToolPage, PdfToolColumns, cardClass, SummaryList, SideTips, SelectedPdfCard, FileDropzone, fieldLabelClass, inputClass, StatusBanner, primaryButtonClass, BackToPdfTools } from './chunk-H6EERRCR.js';
5
+ import { toBaseName, downloadBlob, formatBytes } from './chunk-EKHEY3LI.js';
6
+ import { useState, useEffect, useCallback, useMemo } from 'react';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+
9
+ var MODES = [
10
+ {
11
+ id: "ranges",
12
+ label: "By ranges",
13
+ hint: "One PDF per range."
14
+ },
15
+ {
16
+ id: "every",
17
+ label: "Every N pages",
18
+ hint: "Equal chunks, e.g. every page."
19
+ },
20
+ {
21
+ id: "extract",
22
+ label: "Extract pages",
23
+ hint: "Selected pages into one PDF."
24
+ }
25
+ ];
26
+ function describePages(indices) {
27
+ const first = indices[0] + 1;
28
+ const last = indices[indices.length - 1] + 1;
29
+ if (indices.length === 1) return `Page ${first}`;
30
+ const contiguous = indices.every((value, i) => i === 0 || value === indices[i - 1] + 1);
31
+ return contiguous ? `Pages ${first}-${last}` : `${indices.length} pages`;
32
+ }
33
+ function SplitPdf() {
34
+ const [mode, setMode] = useState("ranges");
35
+ const [ranges, setRanges] = useState("");
36
+ const [every, setEvery] = useState("1");
37
+ const [outputName, setOutputName] = useState("");
38
+ const [isSplitting, setIsSplitting] = useState(false);
39
+ const [status, setStatus] = useState({
40
+ tone: "info",
41
+ text: "Add a PDF, choose how to split it, and download the parts."
42
+ });
43
+ useEffect(() => {
44
+ document.title = "Split PDF | Apps";
45
+ }, []);
46
+ const onError = useCallback((text) => setStatus({ tone: "error", text }), []);
47
+ const onLoaded = useCallback(({ file, pageCount }) => {
48
+ setOutputName(toBaseName(file.name, "document"));
49
+ setRanges("");
50
+ setStatus({
51
+ tone: "success",
52
+ text: `${file.name} loaded with ${pageCount} page${pageCount === 1 ? "" : "s"}.`
53
+ });
54
+ }, []);
55
+ const pdf = useSinglePdf({ onError, onLoaded });
56
+ const plan = useMemo(() => {
57
+ if (!pdf.pageCount) return { groups: [], error: "" };
58
+ try {
59
+ return { groups: planSplit(pdf.pageCount, { mode, ranges, every: Number(every) }), error: "" };
60
+ } catch (error) {
61
+ return { groups: [], error: error.message };
62
+ }
63
+ }, [pdf.pageCount, mode, ranges, every]);
64
+ const handleSplit = async (event) => {
65
+ event.preventDefault();
66
+ if (!pdf.bytes || !plan.groups.length) return;
67
+ setIsSplitting(true);
68
+ setStatus({ tone: "info", text: "Splitting your PDF..." });
69
+ try {
70
+ const baseName = toBaseName(outputName, "document");
71
+ const outputs = await splitPdf(pdf.bytes, {
72
+ mode,
73
+ ranges,
74
+ every: Number(every),
75
+ baseName
76
+ });
77
+ let blob;
78
+ if (outputs.length === 1) {
79
+ blob = new Blob([outputs[0].bytes], { type: "application/pdf" });
80
+ downloadBlob(blob, outputs[0].name);
81
+ } else {
82
+ blob = new Blob([createZip(outputs)], { type: "application/zip" });
83
+ downloadBlob(blob, `${baseName}-split.zip`);
84
+ }
85
+ setStatus({
86
+ tone: "success",
87
+ text: outputs.length === 1 ? `Created ${outputs[0].name} (${formatBytes(blob.size)}). Your download has started.` : `Created ${outputs.length} PDFs in a ZIP (${formatBytes(blob.size)}). Your download has started.`
88
+ });
89
+ } catch (error) {
90
+ setStatus({ tone: "error", text: `Split failed: ${error?.message || "Please try again."}` });
91
+ } finally {
92
+ setIsSplitting(false);
93
+ }
94
+ };
95
+ const busy = isSplitting || pdf.isReading;
96
+ const rangePlaceholder = pdf.pageCount > 1 ? `e.g. 1-${Math.ceil(pdf.pageCount / 2)}, ${Math.ceil(pdf.pageCount / 2) + 1}-` : "e.g. 1";
97
+ return /* @__PURE__ */ jsx(
98
+ PdfToolPage,
99
+ {
100
+ title: "Split PDF",
101
+ description: "Break a PDF into parts by page ranges or fixed chunks, or pull out just the pages you need. Runs in your browser; files never leave your device.",
102
+ stats: [
103
+ { value: pdf.pageCount || "-", label: "Pages" },
104
+ { value: plan.groups.length || "-", label: "Outputs" }
105
+ ],
106
+ children: /* @__PURE__ */ jsx(
107
+ PdfToolColumns,
108
+ {
109
+ main: /* @__PURE__ */ jsxs("form", { onSubmit: handleSplit, children: [
110
+ pdf.file ? /* @__PURE__ */ jsx(
111
+ SelectedPdfCard,
112
+ {
113
+ signed: pdf.signed,
114
+ file: pdf.file,
115
+ pageCount: pdf.pageCount,
116
+ onRemove: pdf.clear,
117
+ disabled: isSplitting
118
+ }
119
+ ) : /* @__PURE__ */ jsx(
120
+ FileDropzone,
121
+ {
122
+ accept: ".pdf,application/pdf",
123
+ disabled: busy,
124
+ onFiles: pdf.selectFiles,
125
+ title: "Drag and drop a PDF here",
126
+ hint: "or click to browse from your device"
127
+ }
128
+ ),
129
+ /* @__PURE__ */ jsxs("fieldset", { className: "mt-5", disabled: busy || !pdf.pageCount, children: [
130
+ /* @__PURE__ */ jsx("legend", { className: fieldLabelClass, children: "Split mode" }),
131
+ /* @__PURE__ */ jsx("div", { className: "mt-2 grid gap-2 sm:grid-cols-3", children: MODES.map((option) => /* @__PURE__ */ jsxs(
132
+ "label",
133
+ {
134
+ className: `cursor-pointer rounded-xl border p-3 text-sm transition ${mode === option.id ? "border-[#1F8CFF] bg-blue-50 dark:bg-blue-500/10" : "border-slate-200 bg-white hover:border-slate-300 dark:border-slate-700 dark:bg-slate-900"}`,
135
+ children: [
136
+ /* @__PURE__ */ jsx(
137
+ "input",
138
+ {
139
+ type: "radio",
140
+ name: "split-mode",
141
+ value: option.id,
142
+ checked: mode === option.id,
143
+ onChange: () => setMode(option.id),
144
+ className: "sr-only"
145
+ }
146
+ ),
147
+ /* @__PURE__ */ jsx("span", { className: "block font-semibold text-slate-900 dark:text-slate-100", children: option.label }),
148
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block text-xs text-slate-600 dark:text-slate-300", children: option.hint })
149
+ ]
150
+ },
151
+ option.id
152
+ )) }),
153
+ mode === "every" ? /* @__PURE__ */ jsxs("label", { className: `mt-4 ${fieldLabelClass}`, children: [
154
+ "Pages per file",
155
+ /* @__PURE__ */ jsx(
156
+ "input",
157
+ {
158
+ type: "number",
159
+ min: 1,
160
+ max: pdf.pageCount || void 0,
161
+ value: every,
162
+ onChange: (event) => setEvery(event.target.value),
163
+ className: `mt-2 ${inputClass}`
164
+ }
165
+ )
166
+ ] }) : /* @__PURE__ */ jsxs("label", { className: `mt-4 ${fieldLabelClass}`, children: [
167
+ mode === "extract" ? "Pages to extract" : "Page ranges",
168
+ /* @__PURE__ */ jsx(
169
+ "input",
170
+ {
171
+ type: "text",
172
+ value: ranges,
173
+ onChange: (event) => setRanges(event.target.value),
174
+ placeholder: rangePlaceholder,
175
+ className: `mt-2 ${inputClass}`
176
+ }
177
+ )
178
+ ] }),
179
+ /* @__PURE__ */ jsxs("label", { className: `mt-4 ${fieldLabelClass}`, children: [
180
+ "File name prefix",
181
+ /* @__PURE__ */ jsx(
182
+ "input",
183
+ {
184
+ type: "text",
185
+ value: outputName,
186
+ onChange: (event) => setOutputName(event.target.value),
187
+ placeholder: "document",
188
+ className: `mt-2 ${inputClass}`
189
+ }
190
+ )
191
+ ] })
192
+ ] }),
193
+ pdf.pageCount && plan.error && (ranges.trim() || mode === "every") ? /* @__PURE__ */ jsx("p", { className: "mt-3 text-xs text-rose-700 dark:text-rose-300", children: plan.error }) : null,
194
+ /* @__PURE__ */ jsx(StatusBanner, { status }),
195
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 flex flex-wrap gap-3", children: [
196
+ /* @__PURE__ */ jsxs(
197
+ "button",
198
+ {
199
+ type: "submit",
200
+ disabled: busy || !plan.groups.length,
201
+ className: primaryButtonClass,
202
+ children: [
203
+ /* @__PURE__ */ jsx("i", { className: `fa-solid ${isSplitting ? "fa-spinner fa-spin" : "fa-scissors"}` }),
204
+ isSplitting ? "Splitting..." : "Split PDF"
205
+ ]
206
+ }
207
+ ),
208
+ /* @__PURE__ */ jsx(BackToPdfTools, {})
209
+ ] })
210
+ ] }),
211
+ side: /* @__PURE__ */ jsxs(Fragment, { children: [
212
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-slate-900 dark:text-slate-100", children: "Output" }),
213
+ plan.groups.length ? /* @__PURE__ */ jsx("ol", { className: "mt-4 max-h-80 space-y-2 overflow-y-auto pr-1", children: plan.groups.map((indices, index) => /* @__PURE__ */ jsxs("li", { className: `${cardClass} !p-2.5 text-sm`, children: [
214
+ /* @__PURE__ */ jsxs("p", { className: "font-medium text-slate-900 dark:text-slate-100", children: [
215
+ "File ",
216
+ index + 1
217
+ ] }),
218
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500 dark:text-slate-400", children: describePages(indices) })
219
+ ] }, index)) }) : /* @__PURE__ */ jsx(
220
+ SummaryList,
221
+ {
222
+ items: [{ label: "Files to create", value: pdf.pageCount ? 0 : "-" }]
223
+ }
224
+ ),
225
+ /* @__PURE__ */ jsx(
226
+ SideTips,
227
+ {
228
+ tips: [
229
+ /* @__PURE__ */ jsxs(Fragment, { children: [
230
+ "Ranges: ",
231
+ /* @__PURE__ */ jsx("code", { children: "1-3, 4-6" }),
232
+ " makes two files. ",
233
+ /* @__PURE__ */ jsx("code", { children: "8-" }),
234
+ " means 8 to the end."
235
+ ] }),
236
+ "More than one output downloads as a ZIP.",
237
+ "Extract keeps pages in the order you type them."
238
+ ]
239
+ }
240
+ )
241
+ ] })
242
+ }
243
+ )
244
+ }
245
+ );
246
+ }
247
+
248
+ export { SplitPdf };
249
+ //# sourceMappingURL=chunk-GLYWSVSB.js.map
250
+ //# sourceMappingURL=chunk-GLYWSVSB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/split/SplitPdf.tsx"],"names":[],"mappings":"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","file":"chunk-GLYWSVSB.js","sourcesContent":["import React, { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { downloadBlob, formatBytes, toBaseName } from \"../shared/files\";\nimport {\n BackToPdfTools,\n FileDropzone,\n PdfToolColumns,\n PdfToolPage,\n SelectedPdfCard,\n SideTips,\n StatusBanner,\n SummaryList,\n cardClass,\n fieldLabelClass,\n inputClass,\n primaryButtonClass,\n} from \"../shared/ToolLayout\";\nimport { useSinglePdf } from \"../shared/useSinglePdf\";\nimport { createZip } from \"../shared/zip\";\nimport { planSplit, splitPdf, type SplitMode } from \"./splitPdfCore\";\n\nconst MODES = [\n {\n id: \"ranges\",\n label: \"By ranges\",\n hint: \"One PDF per range.\",\n },\n {\n id: \"every\",\n label: \"Every N pages\",\n hint: \"Equal chunks, e.g. every page.\",\n },\n {\n id: \"extract\",\n label: \"Extract pages\",\n hint: \"Selected pages into one PDF.\",\n },\n];\n\nfunction describePages(indices) {\n const first = indices[0] + 1;\n const last = indices[indices.length - 1] + 1;\n if (indices.length === 1) return `Page ${first}`;\n const contiguous = indices.every((value, i) => i === 0 || value === indices[i - 1] + 1);\n return contiguous ? `Pages ${first}-${last}` : `${indices.length} pages`;\n}\n\nexport default function SplitPdf() {\n const [mode, setMode] = useState<SplitMode>(\"ranges\");\n const [ranges, setRanges] = useState(\"\");\n const [every, setEvery] = useState(\"1\");\n const [outputName, setOutputName] = useState(\"\");\n const [isSplitting, setIsSplitting] = useState(false);\n const [status, setStatus] = useState({\n tone: \"info\",\n text: \"Add a PDF, choose how to split it, and download the parts.\",\n });\n\n useEffect(() => {\n document.title = \"Split PDF | Apps\";\n }, []);\n\n const onError = useCallback((text) => setStatus({ tone: \"error\", text }), []);\n const onLoaded = useCallback(({ file, pageCount }) => {\n setOutputName(toBaseName(file.name, \"document\"));\n setRanges(\"\");\n setStatus({\n tone: \"success\",\n text: `${file.name} loaded with ${pageCount} page${pageCount === 1 ? \"\" : \"s\"}.`,\n });\n }, []);\n const pdf = useSinglePdf({ onError, onLoaded });\n\n const plan = useMemo(() => {\n if (!pdf.pageCount) return { groups: [], error: \"\" };\n try {\n return { groups: planSplit(pdf.pageCount, { mode, ranges, every: Number(every) }), error: \"\" };\n } catch (error) {\n return { groups: [], error: error.message };\n }\n }, [pdf.pageCount, mode, ranges, every]);\n\n const handleSplit = async (event) => {\n event.preventDefault();\n if (!pdf.bytes || !plan.groups.length) return;\n\n setIsSplitting(true);\n setStatus({ tone: \"info\", text: \"Splitting your PDF...\" });\n\n try {\n const baseName = toBaseName(outputName, \"document\");\n const outputs = await splitPdf(pdf.bytes, {\n mode,\n ranges,\n every: Number(every),\n baseName,\n });\n\n let blob;\n if (outputs.length === 1) {\n blob = new Blob([outputs[0].bytes as BlobPart], { type: \"application/pdf\" });\n downloadBlob(blob, outputs[0].name);\n } else {\n blob = new Blob([createZip(outputs) as BlobPart], { type: \"application/zip\" });\n downloadBlob(blob, `${baseName}-split.zip`);\n }\n\n setStatus({\n tone: \"success\",\n text:\n outputs.length === 1\n ? `Created ${outputs[0].name} (${formatBytes(blob.size)}). Your download has started.`\n : `Created ${outputs.length} PDFs in a ZIP (${formatBytes(blob.size)}). Your download has started.`,\n });\n } catch (error) {\n setStatus({ tone: \"error\", text: `Split failed: ${error?.message || \"Please try again.\"}` });\n } finally {\n setIsSplitting(false);\n }\n };\n\n const busy = isSplitting || pdf.isReading;\n const rangePlaceholder =\n pdf.pageCount > 1 ? `e.g. 1-${Math.ceil(pdf.pageCount / 2)}, ${Math.ceil(pdf.pageCount / 2) + 1}-` : \"e.g. 1\";\n\n return (\n <PdfToolPage\n title=\"Split PDF\"\n description=\"Break a PDF into parts by page ranges or fixed chunks, or pull out just the pages you need. Runs in your browser; files never leave your device.\"\n stats={[\n { value: pdf.pageCount || \"-\", label: \"Pages\" },\n { value: plan.groups.length || \"-\", label: \"Outputs\" },\n ]}\n >\n <PdfToolColumns\n main={\n <form onSubmit={handleSplit}>\n {pdf.file ? (\n <SelectedPdfCard\n signed={pdf.signed}\n file={pdf.file}\n pageCount={pdf.pageCount}\n onRemove={pdf.clear}\n disabled={isSplitting}\n />\n ) : (\n <FileDropzone\n accept=\".pdf,application/pdf\"\n disabled={busy}\n onFiles={pdf.selectFiles}\n title=\"Drag and drop a PDF here\"\n hint=\"or click to browse from your device\"\n />\n )}\n\n <fieldset className=\"mt-5\" disabled={busy || !pdf.pageCount}>\n <legend className={fieldLabelClass}>Split mode</legend>\n <div className=\"mt-2 grid gap-2 sm:grid-cols-3\">\n {MODES.map((option) => (\n <label\n key={option.id}\n className={`cursor-pointer rounded-xl border p-3 text-sm transition ${\n mode === option.id\n ? \"border-[#1F8CFF] bg-blue-50 dark:bg-blue-500/10\"\n : \"border-slate-200 bg-white hover:border-slate-300 dark:border-slate-700 dark:bg-slate-900\"\n }`}\n >\n <input\n type=\"radio\"\n name=\"split-mode\"\n value={option.id}\n checked={mode === option.id}\n onChange={() => setMode(option.id as SplitMode)}\n className=\"sr-only\"\n />\n <span className=\"block font-semibold text-slate-900 dark:text-slate-100\">\n {option.label}\n </span>\n <span className=\"mt-1 block text-xs text-slate-600 dark:text-slate-300\">\n {option.hint}\n </span>\n </label>\n ))}\n </div>\n\n {mode === \"every\" ? (\n <label className={`mt-4 ${fieldLabelClass}`}>\n Pages per file\n <input\n type=\"number\"\n min={1}\n max={pdf.pageCount || undefined}\n value={every}\n onChange={(event) => setEvery(event.target.value)}\n className={`mt-2 ${inputClass}`}\n />\n </label>\n ) : (\n <label className={`mt-4 ${fieldLabelClass}`}>\n {mode === \"extract\" ? \"Pages to extract\" : \"Page ranges\"}\n <input\n type=\"text\"\n value={ranges}\n onChange={(event) => setRanges(event.target.value)}\n placeholder={rangePlaceholder}\n className={`mt-2 ${inputClass}`}\n />\n </label>\n )}\n\n <label className={`mt-4 ${fieldLabelClass}`}>\n File name prefix\n <input\n type=\"text\"\n value={outputName}\n onChange={(event) => setOutputName(event.target.value)}\n placeholder=\"document\"\n className={`mt-2 ${inputClass}`}\n />\n </label>\n </fieldset>\n\n {pdf.pageCount && plan.error && (ranges.trim() || mode === \"every\") ? (\n <p className=\"mt-3 text-xs text-rose-700 dark:text-rose-300\">{plan.error}</p>\n ) : null}\n\n <StatusBanner status={status} />\n\n <div className=\"mt-5 flex flex-wrap gap-3\">\n <button\n type=\"submit\"\n disabled={busy || !plan.groups.length}\n className={primaryButtonClass}\n >\n <i className={`fa-solid ${isSplitting ? \"fa-spinner fa-spin\" : \"fa-scissors\"}`} />\n {isSplitting ? \"Splitting...\" : \"Split PDF\"}\n </button>\n <BackToPdfTools />\n </div>\n </form>\n }\n side={\n <>\n <h2 className=\"text-base font-semibold text-slate-900 dark:text-slate-100\">Output</h2>\n {plan.groups.length ? (\n <ol className=\"mt-4 max-h-80 space-y-2 overflow-y-auto pr-1\">\n {plan.groups.map((indices, index) => (\n <li key={index} className={`${cardClass} !p-2.5 text-sm`}>\n <p className=\"font-medium text-slate-900 dark:text-slate-100\">\n File {index + 1}\n </p>\n <p className=\"text-xs text-slate-500 dark:text-slate-400\">\n {describePages(indices)}\n </p>\n </li>\n ))}\n </ol>\n ) : (\n <SummaryList\n items={[{ label: \"Files to create\", value: pdf.pageCount ? 0 : \"-\" }]}\n />\n )}\n <SideTips\n tips={[\n <>\n Ranges: <code>1-3, 4-6</code> makes two files. <code>8-</code> means 8 to the end.\n </>,\n \"More than one output downloads as a ZIP.\",\n \"Extract keeps pages in the order you type them.\",\n ]}\n />\n </>\n }\n />\n </PdfToolPage>\n );\n}\n"]}
@@ -0,0 +1,135 @@
1
+ import { normalizeRotation } from './chunk-PMH7OWPF.js';
2
+ import { loadPdf } from './chunk-EKHEY3LI.js';
3
+ import { degrees, PDFDict, PDFNumber, PDFArray, PDFRef, PDFName } from 'pdf-lib';
4
+
5
+ async function organizePdf(bytes, edits) {
6
+ if (!edits.length) {
7
+ throw new Error("Keep at least one page.");
8
+ }
9
+ const doc = await loadPdf(bytes);
10
+ const pages = doc.getPages();
11
+ const seen = /* @__PURE__ */ new Set();
12
+ for (const edit of edits) {
13
+ if (!Number.isInteger(edit.source) || edit.source < 0 || edit.source >= pages.length) {
14
+ throw new Error(`Page ${edit.source + 1} does not exist.`);
15
+ }
16
+ if (seen.has(edit.source)) {
17
+ throw new Error(`Page ${edit.source + 1} is listed twice.`);
18
+ }
19
+ seen.add(edit.source);
20
+ }
21
+ const removed = pages.filter((_, index) => !seen.has(index));
22
+ for (let index = pages.length - 1; index >= 0; index -= 1) {
23
+ doc.removePage(index);
24
+ }
25
+ for (const edit of edits) {
26
+ const page = pages[edit.source];
27
+ const turn = normalizeRotation(edit.rotation || 0);
28
+ if (turn) {
29
+ page.setRotation(degrees(normalizeRotation(page.getRotation().angle + turn)));
30
+ }
31
+ doc.addPage(page);
32
+ }
33
+ pruneDanglingReferences(doc, removed);
34
+ return doc.save();
35
+ }
36
+ var N = (name) => PDFName.of(name);
37
+ function destinationPage(item) {
38
+ let dest = item.lookup(N("Dest"));
39
+ const action = item.lookup(N("A"));
40
+ if (!dest && action instanceof PDFDict && action.lookup(N("S")) === N("GoTo")) {
41
+ dest = action.lookup(N("D"));
42
+ }
43
+ if (dest instanceof PDFArray && dest.size() > 0) {
44
+ const target = dest.get(0);
45
+ return target instanceof PDFRef ? target.toString() : null;
46
+ }
47
+ return null;
48
+ }
49
+ function pruneOutlineLevel(doc, parent, removed) {
50
+ const kept = [];
51
+ let ref = parent.get(N("First"));
52
+ const visited = /* @__PURE__ */ new Set();
53
+ while (ref instanceof PDFRef && !visited.has(ref.toString())) {
54
+ visited.add(ref.toString());
55
+ const item = doc.context.lookup(ref);
56
+ if (!(item instanceof PDFDict)) break;
57
+ const next = item.get(N("Next"));
58
+ const childCount = pruneOutlineLevel(doc, item, removed);
59
+ const hasChildren = item.get(N("First")) instanceof PDFRef;
60
+ const target = destinationPage(item);
61
+ if (target && removed.has(target)) {
62
+ if (hasChildren) {
63
+ item.delete(N("Dest"));
64
+ item.delete(N("A"));
65
+ kept.push({ ref, item });
66
+ }
67
+ } else {
68
+ kept.push({ ref, item });
69
+ }
70
+ const previous = item.lookup(N("Count"));
71
+ if (hasChildren) {
72
+ const open = !(previous instanceof PDFNumber) || previous.asNumber() >= 0;
73
+ item.set(N("Count"), PDFNumber.of(open ? childCount : -childCount));
74
+ } else {
75
+ item.delete(N("Count"));
76
+ }
77
+ ref = next;
78
+ }
79
+ kept.forEach(({ item }, index) => {
80
+ if (index > 0) item.set(N("Prev"), kept[index - 1].ref);
81
+ else item.delete(N("Prev"));
82
+ if (index < kept.length - 1) item.set(N("Next"), kept[index + 1].ref);
83
+ else item.delete(N("Next"));
84
+ });
85
+ if (kept.length) {
86
+ parent.set(N("First"), kept[0].ref);
87
+ parent.set(N("Last"), kept[kept.length - 1].ref);
88
+ } else {
89
+ parent.delete(N("First"));
90
+ parent.delete(N("Last"));
91
+ }
92
+ return kept.reduce((sum, { item }) => {
93
+ const count = item.lookup(N("Count"));
94
+ return sum + 1 + (count instanceof PDFNumber && count.asNumber() > 0 ? count.asNumber() : 0);
95
+ }, 0);
96
+ }
97
+ function pruneField(doc, ref, removedWidgets) {
98
+ if (!(ref instanceof PDFRef)) return true;
99
+ const field = doc.context.lookup(ref);
100
+ if (!(field instanceof PDFDict)) return true;
101
+ const kids = field.lookup(N("Kids"));
102
+ if (kids instanceof PDFArray) {
103
+ const keptKids = kids.asArray().filter((kid) => pruneField(doc, kid, removedWidgets));
104
+ if (!keptKids.length) return false;
105
+ field.set(N("Kids"), doc.context.obj(keptKids));
106
+ return true;
107
+ }
108
+ return !removedWidgets.has(ref.toString());
109
+ }
110
+ function pruneDanglingReferences(doc, removedPages) {
111
+ if (!removedPages.length) return;
112
+ const removedRefs = new Set(removedPages.map((page) => page.ref.toString()));
113
+ const outlines = doc.catalog.lookup(N("Outlines"));
114
+ if (outlines instanceof PDFDict) {
115
+ const visible = pruneOutlineLevel(doc, outlines, removedRefs);
116
+ if (visible) outlines.set(N("Count"), PDFNumber.of(visible));
117
+ else outlines.delete(N("Count"));
118
+ }
119
+ const removedWidgets = /* @__PURE__ */ new Set();
120
+ for (const page of removedPages) {
121
+ const annots = page.node.lookup(N("Annots"));
122
+ if (annots instanceof PDFArray) {
123
+ annots.asArray().forEach((annot) => annot instanceof PDFRef && removedWidgets.add(annot.toString()));
124
+ }
125
+ }
126
+ const acroForm = doc.catalog.lookup(N("AcroForm"));
127
+ const fields = acroForm instanceof PDFDict ? acroForm.lookup(N("Fields")) : null;
128
+ if (removedWidgets.size && acroForm instanceof PDFDict && fields instanceof PDFArray) {
129
+ acroForm.set(N("Fields"), doc.context.obj(fields.asArray().filter((field) => pruneField(doc, field, removedWidgets))));
130
+ }
131
+ }
132
+
133
+ export { organizePdf };
134
+ //# sourceMappingURL=chunk-GXBIMUO7.js.map
135
+ //# sourceMappingURL=chunk-GXBIMUO7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/organize/organizePdfCore.ts"],"names":[],"mappings":"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","file":"chunk-GXBIMUO7.js","sourcesContent":["import { PDFArray, PDFDict, PDFDocument, PDFName, PDFNumber, PDFPage, PDFRef, degrees } from \"pdf-lib\";\nimport { loadPdf } from \"../shared/files\";\nimport { normalizeRotation } from \"../shared/pageGeometry\";\n\n/** One page of the output: which source page (0-based) and extra rotation. */\nexport type PageEdit = { source: number; rotation?: number };\n\n/**\n * Reorder, rotate, and delete pages. Pages not listed are deleted. Edits the\n * document in place so metadata and document-level settings are kept.\n */\nexport async function organizePdf(bytes: ArrayBuffer | Uint8Array, edits: PageEdit[]) {\n if (!edits.length) {\n throw new Error(\"Keep at least one page.\");\n }\n\n const doc = await loadPdf(bytes);\n const pages = doc.getPages();\n const seen = new Set<number>();\n for (const edit of edits) {\n if (!Number.isInteger(edit.source) || edit.source < 0 || edit.source >= pages.length) {\n throw new Error(`Page ${edit.source + 1} does not exist.`);\n }\n if (seen.has(edit.source)) {\n throw new Error(`Page ${edit.source + 1} is listed twice.`);\n }\n seen.add(edit.source);\n }\n\n const removed = pages.filter((_, index) => !seen.has(index));\n\n for (let index = pages.length - 1; index >= 0; index -= 1) {\n doc.removePage(index);\n }\n\n for (const edit of edits) {\n const page = pages[edit.source];\n const turn = normalizeRotation(edit.rotation || 0);\n if (turn) {\n page.setRotation(degrees(normalizeRotation(page.getRotation().angle + turn)));\n }\n doc.addPage(page);\n }\n\n pruneDanglingReferences(doc, removed);\n return doc.save();\n}\n\nconst N = (name: string) => PDFName.of(name);\n\n/** Page a destination points at, if it is an explicit [page ...] array. */\nfunction destinationPage(item: PDFDict) {\n let dest = item.lookup(N(\"Dest\"));\n const action = item.lookup(N(\"A\"));\n if (!dest && action instanceof PDFDict && action.lookup(N(\"S\")) === N(\"GoTo\")) {\n dest = action.lookup(N(\"D\"));\n }\n if (dest instanceof PDFArray && dest.size() > 0) {\n const target = dest.get(0);\n return target instanceof PDFRef ? target.toString() : null;\n }\n return null;\n}\n\n/**\n * Rebuild one level of the outline (bookmark) tree without items that point\n * at deleted pages. An item that still has children keeps them and only loses\n * its dead link. Returns the number of items visible when this level is open.\n */\nfunction pruneOutlineLevel(doc: PDFDocument, parent: PDFDict, removed: Set<string>): number {\n const kept: Array<{ ref: PDFRef; item: PDFDict }> = [];\n let ref = parent.get(N(\"First\"));\n const visited = new Set<string>();\n while (ref instanceof PDFRef && !visited.has(ref.toString())) {\n visited.add(ref.toString());\n const item = doc.context.lookup(ref);\n if (!(item instanceof PDFDict)) break;\n const next = item.get(N(\"Next\"));\n\n const childCount = pruneOutlineLevel(doc, item, removed);\n const hasChildren = item.get(N(\"First\")) instanceof PDFRef;\n const target = destinationPage(item);\n if (target && removed.has(target)) {\n if (hasChildren) {\n item.delete(N(\"Dest\"));\n item.delete(N(\"A\"));\n kept.push({ ref, item });\n }\n } else {\n kept.push({ ref, item });\n }\n\n // Count > 0 means open (children visible); < 0 closed.\n const previous = item.lookup(N(\"Count\"));\n if (hasChildren) {\n const open = !(previous instanceof PDFNumber) || previous.asNumber() >= 0;\n item.set(N(\"Count\"), PDFNumber.of(open ? childCount : -childCount));\n } else {\n item.delete(N(\"Count\"));\n }\n ref = next;\n }\n\n kept.forEach(({ item }, index) => {\n if (index > 0) item.set(N(\"Prev\"), kept[index - 1].ref);\n else item.delete(N(\"Prev\"));\n if (index < kept.length - 1) item.set(N(\"Next\"), kept[index + 1].ref);\n else item.delete(N(\"Next\"));\n });\n if (kept.length) {\n parent.set(N(\"First\"), kept[0].ref);\n parent.set(N(\"Last\"), kept[kept.length - 1].ref);\n } else {\n parent.delete(N(\"First\"));\n parent.delete(N(\"Last\"));\n }\n\n return kept.reduce((sum, { item }) => {\n const count = item.lookup(N(\"Count\"));\n return sum + 1 + (count instanceof PDFNumber && count.asNumber() > 0 ? count.asNumber() : 0);\n }, 0);\n}\n\n/** Drop form fields whose widgets were all on deleted pages; trim the rest. */\nfunction pruneField(doc: PDFDocument, ref: unknown, removedWidgets: Set<string>): boolean {\n if (!(ref instanceof PDFRef)) return true;\n const field = doc.context.lookup(ref);\n if (!(field instanceof PDFDict)) return true;\n const kids = field.lookup(N(\"Kids\"));\n if (kids instanceof PDFArray) {\n const keptKids = kids.asArray().filter((kid) => pruneField(doc, kid, removedWidgets));\n if (!keptKids.length) return false;\n field.set(N(\"Kids\"), doc.context.obj(keptKids));\n return true;\n }\n return !removedWidgets.has(ref.toString());\n}\n\n/**\n * Deleting pages leaves bookmarks and form fields pointing at pages that no\n * longer exist. Remove those references so viewers don't show dead entries.\n */\nexport function pruneDanglingReferences(doc: PDFDocument, removedPages: PDFPage[]) {\n if (!removedPages.length) return;\n const removedRefs = new Set(removedPages.map((page) => page.ref.toString()));\n\n const outlines = doc.catalog.lookup(N(\"Outlines\"));\n if (outlines instanceof PDFDict) {\n const visible = pruneOutlineLevel(doc, outlines, removedRefs);\n if (visible) outlines.set(N(\"Count\"), PDFNumber.of(visible));\n else outlines.delete(N(\"Count\"));\n }\n\n const removedWidgets = new Set<string>();\n for (const page of removedPages) {\n const annots = page.node.lookup(N(\"Annots\"));\n if (annots instanceof PDFArray) {\n annots.asArray().forEach((annot) => annot instanceof PDFRef && removedWidgets.add(annot.toString()));\n }\n }\n const acroForm = doc.catalog.lookup(N(\"AcroForm\"));\n const fields = acroForm instanceof PDFDict ? acroForm.lookup(N(\"Fields\")) : null;\n if (removedWidgets.size && acroForm instanceof PDFDict && fields instanceof PDFArray) {\n acroForm.set(N(\"Fields\"), doc.context.obj(fields.asArray().filter((field) => pruneField(doc, field, removedWidgets))));\n }\n}\n"]}
@@ -0,0 +1,180 @@
1
+ import { createContext, useContext, useRef, useState } from 'react';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+
4
+ // src/shared/ToolLayout.tsx
5
+ var PdfToolsContext = createContext({});
6
+ function PdfToolsProvider({ children, ...options }) {
7
+ return /* @__PURE__ */ jsx(PdfToolsContext.Provider, { value: options, children });
8
+ }
9
+ var pageShellClass = "relative overflow-hidden bg-[radial-gradient(circle_at_top_left,_#e0f2fe_0%,_#f8fafc_42%,_#eef2ff_100%)] dark:bg-none dark:bg-slate-950";
10
+ var pageContainerClass = "relative mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-10";
11
+ var glassPanelClass = "rounded-3xl border border-slate-200/80 bg-white/80 shadow-lg shadow-slate-200/60 backdrop-blur dark:border-white/8 dark:bg-slate-950/72 dark:shadow-none";
12
+ var primaryButtonClass = "inline-flex items-center gap-2 rounded-xl border border-[#1F8CFF] bg-[#1F8CFF] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-[#1579E6] disabled:cursor-not-allowed disabled:opacity-60";
13
+ var secondaryButtonClass = "inline-flex items-center gap-2 rounded-xl border border-slate-300 bg-white px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-60 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800";
14
+ var iconButtonClass = "inline-flex h-8 w-8 items-center justify-center rounded-lg border border-slate-200 bg-white text-xs text-slate-600 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-40 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800";
15
+ var inputClass = "w-full min-w-0 rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-sm normal-case tracking-normal text-slate-900 outline-none focus:border-[#1F8CFF] disabled:opacity-60 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100";
16
+ var fieldLabelClass = "block text-xs font-semibold uppercase tracking-[0.12em] text-slate-600 dark:text-slate-300";
17
+ var cardClass = "rounded-2xl border border-slate-200 bg-white/90 p-3 sm:p-4 dark:border-slate-700 dark:bg-slate-900/80";
18
+ function PdfToolPage({ title, description, stats = [], children }) {
19
+ const { header, className = "py-8 sm:py-12" } = useContext(PdfToolsContext);
20
+ return /* @__PURE__ */ jsx("div", { className: `kitsy-pdf ${pageShellClass} ${className}`, children: /* @__PURE__ */ jsxs("div", { className: pageContainerClass, children: [
21
+ header,
22
+ /* @__PURE__ */ jsxs("section", { className: "relative overflow-hidden rounded-3xl border border-slate-200/70 bg-gradient-to-br from-slate-900 via-slate-800 to-rose-900 p-8 text-white shadow-2xl shadow-slate-300/40 sm:p-10", children: [
23
+ /* @__PURE__ */ jsx("div", { className: "absolute -right-20 -top-16 h-52 w-52 rounded-full bg-white/10 blur-3xl" }),
24
+ /* @__PURE__ */ jsx("div", { className: "absolute -left-16 bottom-0 h-36 w-36 rounded-full bg-rose-300/20 blur-3xl" }),
25
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-wrap items-end justify-between gap-6", children: [
26
+ /* @__PURE__ */ jsxs("div", { className: "max-w-2xl", children: [
27
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-[0.28em] text-rose-200/90", children: "PDF Utility" }),
28
+ /* @__PURE__ */ jsx("h1", { className: "mt-3 text-3xl font-semibold leading-tight sm:text-4xl", children: title }),
29
+ /* @__PURE__ */ jsx("p", { className: "mt-4 text-base text-slate-200", children: description })
30
+ ] }),
31
+ stats.length ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3", children: stats.map((stat) => /* @__PURE__ */ jsxs(
32
+ "div",
33
+ {
34
+ className: "rounded-2xl border border-white/15 bg-white/10 px-4 py-3 text-center backdrop-blur",
35
+ children: [
36
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-semibold", children: stat.value }),
37
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs uppercase tracking-[0.2em] text-slate-200", children: stat.label })
38
+ ]
39
+ },
40
+ stat.label
41
+ )) }) : null
42
+ ] })
43
+ ] }),
44
+ children
45
+ ] }) });
46
+ }
47
+ function PdfToolColumns({ main, side }) {
48
+ return /* @__PURE__ */ jsxs("div", { className: "mt-8 grid gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]", children: [
49
+ /* @__PURE__ */ jsx("section", { className: `${glassPanelClass} p-5 sm:p-6`, children: main }),
50
+ /* @__PURE__ */ jsx("section", { className: `${glassPanelClass} p-5 sm:p-6`, children: side })
51
+ ] });
52
+ }
53
+ function SideTips({ tips }) {
54
+ return /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-xl border border-dashed border-slate-300 bg-slate-50 p-4 text-xs leading-relaxed text-slate-600 dark:border-slate-700 dark:bg-slate-900/70 dark:text-slate-300", children: [
55
+ /* @__PURE__ */ jsx("p", { className: "font-semibold text-slate-800 dark:text-slate-200", children: "Tips" }),
56
+ /* @__PURE__ */ jsx("ul", { className: "mt-2 list-disc space-y-1 pl-4", children: tips.map((tip, index) => /* @__PURE__ */ jsx("li", { children: tip }, index)) })
57
+ ] });
58
+ }
59
+ function SummaryList({ items }) {
60
+ return /* @__PURE__ */ jsx("dl", { className: "mt-4 space-y-2 text-sm", children: items.map((item) => /* @__PURE__ */ jsxs(
61
+ "div",
62
+ {
63
+ className: "flex justify-between gap-3 rounded-xl border border-slate-200 bg-white/90 px-3 py-2 dark:border-slate-700 dark:bg-slate-900/80",
64
+ children: [
65
+ /* @__PURE__ */ jsx("dt", { className: "text-slate-600 dark:text-slate-300", children: item.label }),
66
+ /* @__PURE__ */ jsx("dd", { className: "truncate font-semibold text-slate-900 dark:text-slate-100", children: item.value })
67
+ ]
68
+ },
69
+ item.label
70
+ )) });
71
+ }
72
+ function StatusBanner({ status }) {
73
+ let toneClass = "border-slate-200 bg-slate-50 text-slate-700 dark:border-slate-700 dark:bg-slate-900/80 dark:text-slate-300";
74
+ if (status.tone === "error") {
75
+ toneClass = "border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-300";
76
+ } else if (status.tone === "success") {
77
+ toneClass = "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-300";
78
+ }
79
+ return /* @__PURE__ */ jsx("div", { role: "status", className: `mt-4 rounded-xl border px-4 py-3 text-sm ${toneClass}`, children: status.text });
80
+ }
81
+ function FileDropzone({
82
+ accept,
83
+ multiple = false,
84
+ disabled = false,
85
+ onFiles,
86
+ title,
87
+ hint,
88
+ icon = "fa-solid fa-file-pdf"
89
+ }) {
90
+ const inputRef = useRef(null);
91
+ const [isDropActive, setIsDropActive] = useState(false);
92
+ const handleDrop = (event) => {
93
+ event.preventDefault();
94
+ setIsDropActive(false);
95
+ if (!disabled) onFiles(Array.from(event.dataTransfer.files || []));
96
+ };
97
+ const handleDragOver = (event) => {
98
+ event.preventDefault();
99
+ setIsDropActive(true);
100
+ };
101
+ const handleDragLeave = (event) => {
102
+ event.preventDefault();
103
+ setIsDropActive(false);
104
+ };
105
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
106
+ /* @__PURE__ */ jsx(
107
+ "input",
108
+ {
109
+ ref: inputRef,
110
+ type: "file",
111
+ accept,
112
+ multiple,
113
+ onChange: (event) => {
114
+ onFiles(Array.from(event.target.files || []));
115
+ event.target.value = "";
116
+ },
117
+ disabled,
118
+ className: "hidden"
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsx(
122
+ "button",
123
+ {
124
+ type: "button",
125
+ onClick: () => inputRef.current?.click(),
126
+ onDrop: handleDrop,
127
+ onDragOver: handleDragOver,
128
+ onDragLeave: handleDragLeave,
129
+ disabled,
130
+ className: `group w-full rounded-2xl border-2 border-dashed p-8 text-left transition ${isDropActive ? "border-[#1F8CFF] bg-blue-50 dark:bg-blue-500/10" : "border-slate-300 bg-white hover:border-[#1F8CFF] hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900/70 dark:hover:border-[#1F8CFF] dark:hover:bg-slate-800"}`,
131
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center text-center", children: [
132
+ /* @__PURE__ */ jsx("span", { className: "inline-flex h-14 w-14 items-center justify-center rounded-2xl bg-[#1F8CFF]/10 text-[#1F8CFF]", children: /* @__PURE__ */ jsx("i", { className: `${icon} text-xl` }) }),
133
+ /* @__PURE__ */ jsx("p", { className: "mt-4 text-lg font-semibold text-slate-900 dark:text-slate-100", children: title }),
134
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-slate-600 dark:text-slate-300", children: hint })
135
+ ] })
136
+ }
137
+ )
138
+ ] });
139
+ }
140
+ function BackToPdfTools() {
141
+ const { backLink, renderLink } = useContext(PdfToolsContext);
142
+ if (!backLink) return null;
143
+ const children = /* @__PURE__ */ jsxs(Fragment, { children: [
144
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-arrow-left" }),
145
+ backLink.label || "Back to PDF Tools"
146
+ ] });
147
+ return renderLink ? /* @__PURE__ */ jsx(Fragment, { children: renderLink({ href: backLink.href, className: secondaryButtonClass, children }) }) : /* @__PURE__ */ jsx("a", { href: backLink.href, className: secondaryButtonClass, children });
148
+ }
149
+ function SelectedPdfCard({ file, pageCount, onRemove, disabled, detail = void 0, signed = false }) {
150
+ const card = /* @__PURE__ */ jsxs("div", { className: `mt-5 flex items-center gap-3 ${cardClass}`, children: [
151
+ /* @__PURE__ */ jsx("span", { className: "inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-rose-500/10 text-rose-600 dark:text-rose-300", children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-file-pdf" }) }),
152
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
153
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-slate-900 dark:text-slate-100", children: file.name }),
154
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500 dark:text-slate-400", children: detail ?? (pageCount ? `${pageCount} page${pageCount === 1 ? "" : "s"}` : "reading...") })
155
+ ] }),
156
+ /* @__PURE__ */ jsx(
157
+ "button",
158
+ {
159
+ type: "button",
160
+ onClick: onRemove,
161
+ disabled,
162
+ className: iconButtonClass,
163
+ "aria-label": `Remove ${file.name}`,
164
+ children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-xmark" })
165
+ }
166
+ )
167
+ ] });
168
+ if (!signed) return card;
169
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
170
+ card,
171
+ /* @__PURE__ */ jsxs("p", { className: "mt-2 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100", children: [
172
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-triangle-exclamation mr-1" }),
173
+ "This PDF is digitally signed. Saving a changed copy will invalidate its signature; the original file is not changed."
174
+ ] })
175
+ ] });
176
+ }
177
+
178
+ export { BackToPdfTools, FileDropzone, PdfToolColumns, PdfToolPage, PdfToolsProvider, SelectedPdfCard, SideTips, StatusBanner, SummaryList, cardClass, fieldLabelClass, iconButtonClass, inputClass, primaryButtonClass, secondaryButtonClass };
179
+ //# sourceMappingURL=chunk-H6EERRCR.js.map
180
+ //# sourceMappingURL=chunk-H6EERRCR.js.map