@papyrus-sdk/ui-react 0.2.7 → 0.2.9

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/dist/index.js CHANGED
@@ -17,15 +17,15 @@ var __copyProps = (to, from, except, desc) => {
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
 
19
19
  // index.ts
20
- var index_exports = {};
21
- __export(index_exports, {
20
+ var ui_react_exports = {};
21
+ __export(ui_react_exports, {
22
22
  PageRenderer: () => PageRenderer_default,
23
23
  SidebarLeft: () => SidebarLeft_default,
24
24
  SidebarRight: () => SidebarRight_default,
25
25
  Topbar: () => Topbar_default,
26
26
  Viewer: () => Viewer_default
27
27
  });
28
- module.exports = __toCommonJS(index_exports);
28
+ module.exports = __toCommonJS(ui_react_exports);
29
29
 
30
30
  // components/Topbar.tsx
31
31
  var import_react = require("react");
@@ -57,16 +57,31 @@ var Topbar = ({
57
57
  triggerScrollToPage
58
58
  } = (0, import_core.useViewerStore)();
59
59
  const fileInputRef = (0, import_react.useRef)(null);
60
+ const zoomTimerRef = (0, import_react.useRef)(null);
61
+ const pendingZoomRef = (0, import_react.useRef)(null);
60
62
  const [pageInput, setPageInput] = (0, import_react.useState)(currentPage.toString());
63
+ const pageDigits = Math.max(2, String(pageCount || 1).length);
61
64
  const [showPageThemes, setShowPageThemes] = (0, import_react.useState)(false);
62
65
  const isDark = uiTheme === "dark";
63
66
  (0, import_react.useEffect)(() => {
64
67
  setPageInput(currentPage.toString());
65
68
  }, [currentPage]);
69
+ (0, import_react.useEffect)(() => () => {
70
+ if (zoomTimerRef.current) clearTimeout(zoomTimerRef.current);
71
+ }, []);
66
72
  const handleZoom = (delta) => {
67
- const newZoom = Math.max(0.2, Math.min(5, zoom + delta));
68
- engine.setZoom(newZoom);
69
- setDocumentState({ zoom: newZoom });
73
+ const baseZoom = pendingZoomRef.current ?? zoom;
74
+ const nextZoom = Math.max(0.2, Math.min(5, baseZoom + delta));
75
+ pendingZoomRef.current = nextZoom;
76
+ if (zoomTimerRef.current) return;
77
+ zoomTimerRef.current = setTimeout(() => {
78
+ zoomTimerRef.current = null;
79
+ const targetZoom = pendingZoomRef.current;
80
+ pendingZoomRef.current = null;
81
+ if (targetZoom == null) return;
82
+ engine.setZoom(targetZoom);
83
+ setDocumentState({ zoom: targetZoom });
84
+ }, 80);
70
85
  };
71
86
  const handlePageChange = (page) => {
72
87
  if (pageCount <= 0) return;
@@ -81,95 +96,103 @@ var Topbar = ({
81
96
  { id: "dark", name: "Invertido", color: "bg-gray-800" },
82
97
  { id: "high-contrast", name: "Contraste", color: "bg-black" }
83
98
  ];
84
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `papyrus-topbar h-14 border-b flex items-center justify-between px-4 z-50 transition-colors duration-200 ${isDark ? "bg-[#1a1a1a] border-[#333] text-white" : "bg-white border-gray-200 text-gray-800"}`, children: [
85
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center space-x-3", children: [
86
- showSidebarLeftToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: toggleSidebarLeft, className: `p-2 rounded-md ${isDark ? "hover:bg-white/10" : "hover:bg-gray-100"}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" }) }) }),
87
- showBrand && (brand ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "font-bold text-lg tracking-tight", style: { color: accentColor }, children: [
88
- "Papyrus",
89
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: isDark ? "text-white" : "text-gray-900", children: "Core" })
90
- ] })),
91
- title && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `text-sm font-semibold truncate max-w-[220px] ${isDark ? "text-gray-200" : "text-gray-700"}`, title: typeof title === "string" ? title : void 0, children: title })
92
- ] }),
93
- (showPageControls || showZoomControls) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center space-x-4", children: [
94
- showPageControls && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `flex items-center rounded-lg p-1 space-x-1 border ${isDark ? "bg-[#2a2a2a] border-[#444]" : "bg-gray-50 border-gray-200"}`, children: [
95
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => handlePageChange(currentPage - 1), className: "p-1.5 rounded transition-all hover:brightness-110", style: { color: accentColor }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }) }),
96
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
97
- "input",
98
- {
99
- type: "text",
100
- className: "w-10 text-center bg-transparent focus:outline-none font-bold text-sm",
101
- value: pageInput,
102
- onChange: (e) => setPageInput(e.target.value),
103
- onKeyDown: (e) => e.key === "Enter" && handlePageChange(parseInt(pageInput)),
104
- onBlur: () => handlePageChange(parseInt(pageInput))
105
- }
106
- ),
107
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "opacity-40 px-1", children: "/" }),
108
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "opacity-80 text-sm", children: pageCount > 0 ? pageCount : "\u2014" }),
109
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => handlePageChange(currentPage + 1), className: "p-1.5 rounded transition-all hover:brightness-110", style: { color: accentColor }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) }) })
110
- ] }),
111
- showZoomControls && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `flex items-center rounded-lg p-1 border ${isDark ? "bg-[#2a2a2a] border-[#444]" : "bg-gray-50 border-gray-200"}`, children: [
112
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => handleZoom(-0.1), className: "p-1.5 rounded hover:brightness-110", style: { color: accentColor }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 12H4" }) }) }),
113
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "px-3 text-xs font-bold min-w-[50px] text-center", children: [
114
- Math.round(zoom * 100),
115
- "%"
99
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
100
+ "div",
101
+ {
102
+ "data-papyrus-theme": uiTheme,
103
+ className: `papyrus-topbar papyrus-theme h-14 border-b flex items-center justify-between px-4 z-50 transition-colors duration-200 ${isDark ? "bg-[#1a1a1a] border-[#333] text-white" : "bg-white border-gray-200 text-gray-800"}`,
104
+ children: [
105
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center space-x-3", children: [
106
+ showSidebarLeftToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: toggleSidebarLeft, className: `p-2 rounded-md ${isDark ? "hover:bg-white/10" : "hover:bg-gray-100"}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" }) }) }),
107
+ showBrand && (brand ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "font-bold text-lg tracking-tight", style: { color: accentColor }, children: [
108
+ "Papyrus",
109
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: isDark ? "text-white" : "text-gray-900", children: "Core" })
110
+ ] })),
111
+ title && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `text-sm font-semibold truncate max-w-[220px] ${isDark ? "text-gray-200" : "text-gray-700"}`, title: typeof title === "string" ? title : void 0, children: title })
116
112
  ] }),
117
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => handleZoom(0.1), className: "p-1.5 rounded hover:brightness-110", style: { color: accentColor }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 4v16m8-8H4" }) }) })
118
- ] })
119
- ] }),
120
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center space-x-3", children: [
121
- showPageThemeSelector && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative", children: [
122
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
123
- "button",
124
- {
125
- onClick: () => setShowPageThemes(!showPageThemes),
126
- className: `flex items-center space-x-2 px-3 py-1.5 rounded-md text-xs font-bold border transition-all ${isDark ? "bg-[#2a2a2a] border-[#444]" : "bg-gray-50 border-gray-200"}`,
127
- children: [
128
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `w-3 h-3 rounded-full border ${themes.find((t) => t.id === pageTheme)?.color}` }),
129
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "TEMA" })
130
- ]
131
- }
132
- ),
133
- showPageThemes && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `absolute top-full right-0 mt-2 w-48 rounded-lg shadow-xl border p-2 z-[60] ${isDark ? "bg-[#2a2a2a] border-[#444]" : "bg-white border-gray-200"}`, children: themes.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
134
- "button",
135
- {
136
- onClick: () => {
137
- setDocumentState({ pageTheme: t.id });
138
- setShowPageThemes(false);
139
- },
140
- className: `w-full flex items-center space-x-3 px-3 py-2 rounded-md text-sm ${pageTheme === t.id ? "text-white" : isDark ? "hover:bg-white/10 text-gray-300" : "hover:bg-gray-50 text-gray-700"}`,
141
- style: pageTheme === t.id ? { backgroundColor: accentColor } : void 0,
142
- children: [
143
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `w-3 h-3 rounded-full border ${t.color}` }),
144
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t.name })
145
- ]
146
- },
147
- t.id
148
- )) })
149
- ] }),
150
- showUIToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setDocumentState({ uiTheme: isDark ? "light" : "dark" }), className: `p-2 rounded-full ${isDark ? "bg-yellow-500/10 text-yellow-500" : "bg-gray-100 text-gray-500"}`, children: isDark ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414z" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" }) }) }),
151
- showUpload && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
152
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
153
- "button",
154
- {
155
- onClick: () => fileInputRef.current?.click(),
156
- className: "px-4 py-1.5 text-white rounded-md text-sm font-bold shadow-md active:scale-95",
157
- style: { backgroundColor: accentColor },
158
- children: "UPLOAD"
159
- }
160
- ),
161
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "file", ref: fileInputRef, className: "hidden", accept: ".pdf,.epub,.txt", onChange: async (e) => {
162
- const file = e.target.files?.[0];
163
- if (file) {
164
- setDocumentState({ isLoaded: false });
165
- await engine.load(file);
166
- setDocumentState({ isLoaded: true, pageCount: engine.getPageCount(), currentPage: 1, outline: await engine.getOutline() });
167
- }
168
- } })
169
- ] }),
170
- showSearch && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => toggleSidebarRight("search"), className: `p-2 rounded-md ${isDark ? "hover:bg-white/10" : "hover:bg-gray-100"}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }) })
171
- ] })
172
- ] });
113
+ (showPageControls || showZoomControls) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center space-x-4", children: [
114
+ showPageControls && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `papyrus-control flex items-center rounded-lg p-1 space-x-1 border ${isDark ? "bg-[#2a2a2a] border-[#444]" : "bg-gray-50 border-gray-200"}`, children: [
115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => handlePageChange(currentPage - 1), className: "p-1.5 rounded transition-all hover:brightness-110", style: { color: accentColor }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }) }),
116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
117
+ "input",
118
+ {
119
+ type: "text",
120
+ className: "papyrus-input text-center bg-transparent focus:outline-none font-bold text-sm shrink-0",
121
+ style: { width: `${pageDigits + 1.75}ch` },
122
+ value: pageInput,
123
+ onChange: (e) => setPageInput(e.target.value),
124
+ onKeyDown: (e) => e.key === "Enter" && handlePageChange(parseInt(pageInput)),
125
+ onBlur: () => handlePageChange(parseInt(pageInput))
126
+ }
127
+ ),
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "opacity-40 px-1", children: "/" }),
129
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "opacity-80 text-sm", children: pageCount > 0 ? pageCount : "\u2014" }),
130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => handlePageChange(currentPage + 1), className: "p-1.5 rounded transition-all hover:brightness-110", style: { color: accentColor }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) }) })
131
+ ] }),
132
+ showZoomControls && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `papyrus-control flex items-center rounded-lg p-1 border ${isDark ? "bg-[#2a2a2a] border-[#444]" : "bg-gray-50 border-gray-200"}`, children: [
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => handleZoom(-0.1), className: "p-1.5 rounded hover:brightness-110", style: { color: accentColor }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 12H4" }) }) }),
134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "px-3 text-xs font-bold min-w-[50px] text-center", children: [
135
+ Math.round(zoom * 100),
136
+ "%"
137
+ ] }),
138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => handleZoom(0.1), className: "p-1.5 rounded hover:brightness-110", style: { color: accentColor }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 4v16m8-8H4" }) }) })
139
+ ] })
140
+ ] }),
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center space-x-3", children: [
142
+ showPageThemeSelector && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative", children: [
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
144
+ "button",
145
+ {
146
+ onClick: () => setShowPageThemes(!showPageThemes),
147
+ className: `papyrus-control flex items-center space-x-2 px-3 py-1.5 rounded-md text-xs font-bold border transition-all ${isDark ? "bg-[#2a2a2a] border-[#444]" : "bg-gray-50 border-gray-200"}`,
148
+ children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `w-3 h-3 rounded-full border ${themes.find((t) => t.id === pageTheme)?.color}` }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "TEMA" })
151
+ ]
152
+ }
153
+ ),
154
+ showPageThemes && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `papyrus-popover absolute top-full right-0 mt-2 w-48 rounded-lg shadow-xl border p-2 z-[60] ${isDark ? "bg-[#2a2a2a] border-[#444]" : "bg-white border-gray-200"}`, children: themes.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
155
+ "button",
156
+ {
157
+ onClick: () => {
158
+ setDocumentState({ pageTheme: t.id });
159
+ setShowPageThemes(false);
160
+ },
161
+ className: `w-full flex items-center space-x-3 px-3 py-2 rounded-md text-sm ${pageTheme === t.id ? "text-white" : isDark ? "hover:bg-white/10 text-gray-300" : "hover:bg-gray-50 text-gray-700"}`,
162
+ style: pageTheme === t.id ? { backgroundColor: accentColor } : void 0,
163
+ children: [
164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `w-3 h-3 rounded-full border ${t.color}` }),
165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t.name })
166
+ ]
167
+ },
168
+ t.id
169
+ )) })
170
+ ] }),
171
+ showUIToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setDocumentState({ uiTheme: isDark ? "light" : "dark" }), className: `p-2 rounded-full ${isDark ? "bg-yellow-500/10 text-yellow-500" : "bg-gray-100 text-gray-500"}`, children: isDark ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414z" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" }) }) }),
172
+ showUpload && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
174
+ "button",
175
+ {
176
+ onClick: () => fileInputRef.current?.click(),
177
+ className: "px-4 py-1.5 text-white rounded-md text-sm font-bold shadow-md active:scale-95",
178
+ style: { backgroundColor: accentColor },
179
+ children: "UPLOAD"
180
+ }
181
+ ),
182
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "file", ref: fileInputRef, className: "hidden", accept: ".pdf,.epub,.txt", onChange: async (e) => {
183
+ const file = e.target.files?.[0];
184
+ if (file) {
185
+ setDocumentState({ isLoaded: false });
186
+ await engine.load(file);
187
+ setDocumentState({ isLoaded: true, pageCount: engine.getPageCount(), currentPage: 1, outline: await engine.getOutline() });
188
+ }
189
+ } })
190
+ ] }),
191
+ showSearch && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => toggleSidebarRight("search"), className: `p-2 rounded-md ${isDark ? "hover:bg-white/10" : "hover:bg-gray-100"}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }) })
192
+ ] })
193
+ ]
194
+ }
195
+ );
173
196
  };
174
197
  var Topbar_default = Topbar;
175
198
 
@@ -187,22 +210,44 @@ var withAlpha = (hex, alpha) => {
187
210
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
188
211
  };
189
212
  var Thumbnail = ({ engine, pageIndex, active, isDark, accentColor, onClick }) => {
213
+ const wrapperRef = (0, import_react2.useRef)(null);
190
214
  const canvasRef = (0, import_react2.useRef)(null);
191
215
  const htmlRef = (0, import_react2.useRef)(null);
192
216
  const accentSoft = withAlpha(accentColor, 0.12);
193
217
  const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
218
+ const [isVisible, setIsVisible] = (0, import_react2.useState)(false);
194
219
  (0, import_react2.useEffect)(() => {
195
- if (renderTargetType === "element") return;
220
+ const target = wrapperRef.current;
221
+ if (!target) return;
222
+ if (typeof IntersectionObserver === "undefined") {
223
+ setIsVisible(true);
224
+ return;
225
+ }
226
+ const root = target.closest(".custom-scrollbar");
227
+ const observer = new IntersectionObserver((entries) => {
228
+ entries.forEach((entry) => {
229
+ if (entry.isIntersecting) {
230
+ setIsVisible(true);
231
+ observer.disconnect();
232
+ }
233
+ });
234
+ }, { root: root ?? null, rootMargin: "200px" });
235
+ observer.observe(target);
236
+ return () => observer.disconnect();
237
+ }, []);
238
+ (0, import_react2.useEffect)(() => {
239
+ if (renderTargetType === "element" || !isVisible) return;
196
240
  const target = canvasRef.current;
197
241
  if (target) {
198
242
  engine.renderPage(pageIndex, target, 0.15).catch((err) => {
199
243
  console.error("[Papyrus] Thumbnail render failed:", err);
200
244
  });
201
245
  }
202
- }, [engine, pageIndex, renderTargetType]);
246
+ }, [engine, pageIndex, renderTargetType, isVisible]);
203
247
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
204
248
  "div",
205
249
  {
250
+ ref: wrapperRef,
206
251
  onClick,
207
252
  className: `p-3 cursor-pointer transition-all rounded-lg border-2 ${active ? "shadow-sm" : "border-transparent"}`,
208
253
  style: active ? { borderColor: accentColor, backgroundColor: accentSoft } : void 0,
@@ -300,39 +345,46 @@ var SidebarLeft = ({ engine }) => {
300
345
  } = (0, import_core2.useViewerStore)();
301
346
  const isDark = uiTheme === "dark";
302
347
  if (!sidebarLeftOpen) return null;
303
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `papyrus-sidebar-left w-72 border-r flex flex-col h-full shrink-0 overflow-hidden transition-colors duration-200 ${isDark ? "bg-[#2a2a2a] border-[#3a3a3a]" : "bg-[#fcfcfc] border-gray-200"}`, children: [
304
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `p-4 border-b flex flex-col space-y-4 ${isDark ? "border-[#3a3a3a]" : "border-gray-100"}`, children: [
305
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between", children: [
306
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: `text-sm font-bold uppercase tracking-widest ${isDark ? "text-gray-100" : "text-gray-800"}`, children: sidebarLeftTab === "thumbnails" ? "Thumbnails" : "Sum\xE1rio" }),
307
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => setDocumentState({ sidebarLeftOpen: false }), className: "text-gray-400 hover:text-gray-600", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
308
- ] }),
309
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-1", children: [
310
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
311
- "button",
312
- {
313
- onClick: () => setSidebarLeftTab("thumbnails"),
314
- className: `p-2 rounded-md ${sidebarLeftTab === "thumbnails" ? isDark ? "bg-white/10 text-white" : "bg-white shadow-sm border border-gray-200" : "text-gray-400"}`,
315
- style: sidebarLeftTab === "thumbnails" && !isDark ? { color: accentColor } : void 0,
316
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2 2V6z" }) })
317
- }
318
- ),
319
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
320
- "button",
321
- {
322
- onClick: () => setSidebarLeftTab("summary"),
323
- className: `p-2 rounded-md ${sidebarLeftTab === "summary" ? isDark ? "bg-white/10 text-white" : "bg-white shadow-sm border border-gray-200" : "text-gray-400"}`,
324
- style: sidebarLeftTab === "summary" && !isDark ? { color: accentColor } : void 0,
325
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h7" }) })
326
- }
327
- )
328
- ] })
329
- ] }),
330
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-3", children: sidebarLeftTab === "thumbnails" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-1", children: Array.from({ length: pageCount }).map((_, idx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Thumbnail, { engine, pageIndex: idx, isDark, accentColor, active: currentPage === idx + 1, onClick: () => {
331
- engine.goToPage(idx + 1);
332
- setDocumentState({ currentPage: idx + 1 });
333
- triggerScrollToPage(idx);
334
- } }, idx)) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-col space-y-0.5", children: outline.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(OutlineNode, { item, engine, isDark, accentColor }, i)) }) })
335
- ] });
348
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
349
+ "div",
350
+ {
351
+ "data-papyrus-theme": uiTheme,
352
+ className: `papyrus-sidebar-left papyrus-theme w-72 border-r flex flex-col h-full shrink-0 overflow-hidden transition-colors duration-200 ${isDark ? "bg-[#2a2a2a] border-[#3a3a3a]" : "bg-[#fcfcfc] border-gray-200"}`,
353
+ children: [
354
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `p-4 border-b flex flex-col space-y-4 ${isDark ? "border-[#3a3a3a]" : "border-gray-100"}`, children: [
355
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between", children: [
356
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: `text-sm font-bold uppercase tracking-widest ${isDark ? "text-gray-100" : "text-gray-800"}`, children: sidebarLeftTab === "thumbnails" ? "Thumbnails" : "Sum\xE1rio" }),
357
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => setDocumentState({ sidebarLeftOpen: false }), className: "text-gray-400 hover:text-gray-600", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
358
+ ] }),
359
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-1", children: [
360
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
361
+ "button",
362
+ {
363
+ onClick: () => setSidebarLeftTab("thumbnails"),
364
+ className: `p-2 rounded-md ${sidebarLeftTab === "thumbnails" ? isDark ? "bg-white/10 text-white" : "bg-white shadow-sm border border-gray-200" : "text-gray-400"}`,
365
+ style: sidebarLeftTab === "thumbnails" && !isDark ? { color: accentColor } : void 0,
366
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2 2V6z" }) })
367
+ }
368
+ ),
369
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
370
+ "button",
371
+ {
372
+ onClick: () => setSidebarLeftTab("summary"),
373
+ className: `p-2 rounded-md ${sidebarLeftTab === "summary" ? isDark ? "bg-white/10 text-white" : "bg-white shadow-sm border border-gray-200" : "text-gray-400"}`,
374
+ style: sidebarLeftTab === "summary" && !isDark ? { color: accentColor } : void 0,
375
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h7" }) })
376
+ }
377
+ )
378
+ ] })
379
+ ] }),
380
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-3", children: sidebarLeftTab === "thumbnails" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-1", children: Array.from({ length: pageCount }).map((_, idx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Thumbnail, { engine, pageIndex: idx, isDark, accentColor, active: currentPage === idx + 1, onClick: () => {
381
+ engine.goToPage(idx + 1);
382
+ setDocumentState({ currentPage: idx + 1 });
383
+ triggerScrollToPage(idx);
384
+ } }, idx)) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-col space-y-0.5", children: outline.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(OutlineNode, { item, engine, isDark, accentColor }, i)) }) })
385
+ ]
386
+ }
387
+ );
336
388
  };
337
389
  var SidebarLeft_default = SidebarLeft;
338
390
 
@@ -368,6 +420,7 @@ var SidebarRight = ({ engine }) => {
368
420
  const searchService = new import_core3.SearchService(engine);
369
421
  const isDark = uiTheme === "dark";
370
422
  const accentSoft = withAlpha2(accentColor, 0.12);
423
+ const resultsCount = searchResults.length;
371
424
  const handleSearch = async (e) => {
372
425
  e.preventDefault();
373
426
  if (!query.trim()) {
@@ -380,114 +433,124 @@ var SidebarRight = ({ engine }) => {
380
433
  setIsSearching(false);
381
434
  };
382
435
  if (!sidebarRightOpen) return null;
383
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `papyrus-sidebar-right w-80 border-l flex flex-col h-full shrink-0 transition-colors duration-200 shadow-2xl z-40 ${isDark ? "bg-[#1a1a1a] border-[#333]" : "bg-white border-gray-200"}`, children: [
384
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `p-4 border-b flex items-center justify-between shrink-0 ${isDark ? "border-[#333]" : "border-gray-100"}`, children: [
385
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex space-x-6", children: [
386
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
387
- "button",
388
- {
389
- onClick: () => toggleSidebarRight("search"),
390
- className: `text-[10px] font-black uppercase tracking-widest pb-1 transition-all ${sidebarRightTab === "search" ? "border-b-2" : "text-gray-400"}`,
391
- style: sidebarRightTab === "search" ? { color: accentColor, borderColor: accentColor } : void 0,
392
- children: "Busca"
393
- }
394
- ),
395
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
396
- "button",
397
- {
398
- onClick: () => toggleSidebarRight("annotations"),
399
- className: `text-[10px] font-black uppercase tracking-widest pb-1 transition-all ${sidebarRightTab === "annotations" ? "border-b-2" : "text-gray-400"}`,
400
- style: sidebarRightTab === "annotations" ? { color: accentColor, borderColor: accentColor } : void 0,
401
- children: "Notas"
402
- }
403
- )
404
- ] }),
405
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: () => toggleSidebarRight(), className: "text-gray-400 hover:text-red-500 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
406
- ] }),
407
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-1 overflow-y-auto p-4 custom-scrollbar bg-opacity-50", children: sidebarRightTab === "search" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-4", children: [
408
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: handleSearch, className: "relative mb-6", children: [
409
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
410
- "input",
411
- {
412
- type: "text",
413
- className: `w-full rounded-lg px-4 py-2.5 text-xs outline-none border transition-all shadow-inner font-medium ${isDark ? "bg-[#2a2a2a] text-white border-[#444] focus:border-blue-500" : "bg-gray-100 border-gray-200 focus:bg-white focus:border-blue-400"}`,
414
- placeholder: "O que voc\xEA procura?",
415
- value: query,
416
- onChange: (e) => setQuery(e.target.value)
417
- }
418
- ),
419
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "absolute right-3 top-2.5 text-gray-400 transition-colors", style: { color: accentColor }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }) })
420
- ] }),
421
- isSearching && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col items-center justify-center py-12 space-y-3", children: [
422
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-6 h-6 border-2 border-t-transparent rounded-full animate-spin", style: { borderColor: accentColor } }),
423
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[10px] font-bold text-gray-500 uppercase", children: "Varrendo documento..." })
424
- ] }),
425
- !isSearching && searchResults.map((res, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
426
- "div",
427
- {
428
- onClick: () => {
429
- setDocumentState({ activeSearchIndex: idx });
430
- triggerScrollToPage(res.pageIndex);
431
- },
432
- className: `p-4 rounded-xl border-2 cursor-pointer transition-all group hover:scale-[1.02] ${idx === activeSearchIndex ? "shadow-lg" : isDark ? "border-[#333] hover:border-[#555] bg-[#222]" : "border-gray-50 hover:border-gray-200 bg-gray-50/50 hover:bg-white"}`,
433
- style: idx === activeSearchIndex ? { borderColor: accentColor, backgroundColor: accentSoft } : void 0,
434
- children: [
435
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [
436
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
437
- "span",
438
- {
439
- className: `text-[10px] font-black uppercase tracking-tighter ${idx === activeSearchIndex ? "" : "text-gray-400"}`,
440
- style: idx === activeSearchIndex ? { color: accentColor } : void 0,
441
- children: [
442
- "P\xC1GINA ",
443
- res.pageIndex + 1
444
- ]
445
- }
446
- ),
447
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
448
- "svg",
449
- {
450
- className: `w-3 h-3 transition-transform ${idx === activeSearchIndex ? "" : "text-gray-300"}`,
451
- style: idx === activeSearchIndex ? { color: accentColor } : void 0,
452
- fill: "none",
453
- stroke: "currentColor",
454
- viewBox: "0 0 24 24",
455
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 3, d: "M9 5l7 7-7 7" })
456
- }
457
- )
458
- ] }),
459
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: `text-[11px] font-medium leading-relaxed italic ${isDark ? "text-gray-400" : "text-gray-600"}`, children: [
460
- "...",
461
- res.text,
462
- "..."
463
- ] })
464
- ]
465
- },
466
- idx
467
- ))
468
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-3", children: [
469
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-[9px] font-black text-gray-400 uppercase tracking-[0.2em] mb-6 flex items-center", children: [
470
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "WORKSET" }),
471
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-1 h-px bg-current ml-3 opacity-10" })
472
- ] }),
473
- annotations.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-center py-20", children: [
474
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-12 h-12 bg-gray-500/10 rounded-full flex items-center justify-center mx-auto mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-6 h-6 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) }) }),
475
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-[10px] font-bold text-gray-400 uppercase tracking-widest", children: "Sem anota\xE7\xF5es" })
476
- ] }) : annotations.map((ann) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `p-4 rounded-xl border group transition-all cursor-pointer ${isDark ? "bg-[#222] border-[#333] hover:border-[#444]" : "bg-white border-gray-100 shadow-sm hover:shadow-md"}`, children: [
477
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between mb-3", children: [
478
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-2", children: [
479
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-2.5 h-2.5 rounded-full", style: { backgroundColor: ann.color } }),
480
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "text-[10px] font-black", style: { color: accentColor }, children: [
481
- "P",
482
- ann.pageIndex + 1
483
- ] })
436
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
437
+ "div",
438
+ {
439
+ "data-papyrus-theme": uiTheme,
440
+ className: `papyrus-sidebar-right papyrus-theme w-80 border-l flex flex-col h-full shrink-0 transition-colors duration-200 shadow-2xl z-40 ${isDark ? "bg-[#1a1a1a] border-[#333]" : "bg-white border-gray-200"}`,
441
+ children: [
442
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `p-4 border-b flex items-center justify-between shrink-0 ${isDark ? "border-[#333]" : "border-gray-100"}`, children: [
443
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex space-x-6", children: [
444
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
445
+ "button",
446
+ {
447
+ onClick: () => toggleSidebarRight("search"),
448
+ className: `text-[10px] font-black uppercase tracking-widest pb-1 transition-all ${sidebarRightTab === "search" ? "border-b-2" : "text-gray-400"}`,
449
+ style: sidebarRightTab === "search" ? { color: accentColor, borderColor: accentColor } : void 0,
450
+ children: "Busca"
451
+ }
452
+ ),
453
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
454
+ "button",
455
+ {
456
+ onClick: () => toggleSidebarRight("annotations"),
457
+ className: `text-[10px] font-black uppercase tracking-widest pb-1 transition-all ${sidebarRightTab === "annotations" ? "border-b-2" : "text-gray-400"}`,
458
+ style: sidebarRightTab === "annotations" ? { color: accentColor, borderColor: accentColor } : void 0,
459
+ children: "Notas"
460
+ }
461
+ )
484
462
  ] }),
485
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[9px] text-gray-400 font-bold", children: new Date(ann.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) })
463
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: () => toggleSidebarRight(), className: "papyrus-unstyled-button text-gray-400 hover:text-red-500 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })
486
464
  ] }),
487
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: `text-[11px] font-bold uppercase tracking-tight ${isDark ? "text-gray-200" : "text-gray-700"}`, children: ann.type })
488
- ] }, ann.id))
489
- ] }) })
490
- ] });
465
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-1 overflow-y-auto p-4 custom-scrollbar bg-opacity-50", children: sidebarRightTab === "search" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-4", children: [
466
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: handleSearch, className: "relative mb-6", children: [
467
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
468
+ "input",
469
+ {
470
+ type: "text",
471
+ className: `papyrus-input w-full rounded-lg px-4 py-2.5 text-xs outline-none border transition-all shadow-inner font-medium ${isDark ? "bg-[#2a2a2a] text-white border-[#444] focus:border-blue-500" : "bg-gray-100 border-gray-200 focus:bg-white focus:border-blue-400"}`,
472
+ placeholder: "O que voc\xEA procura?",
473
+ value: query,
474
+ onChange: (e) => setQuery(e.target.value)
475
+ }
476
+ ),
477
+ resultsCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "absolute right-9 top-2.5 text-[10px] font-bold text-gray-400", children: resultsCount }),
478
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "papyrus-unstyled-button absolute right-3 top-2.5 text-gray-400 transition-colors", style: { color: accentColor }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }) })
479
+ ] }),
480
+ isSearching && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col items-center justify-center py-12 space-y-3", children: [
481
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-6 h-6 border-2 border-t-transparent rounded-full animate-spin", style: { borderColor: accentColor } }),
482
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[10px] font-bold text-gray-500 uppercase", children: "Varrendo documento..." })
483
+ ] }),
484
+ !isSearching && searchResults.map((res, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
485
+ "div",
486
+ {
487
+ onClick: () => {
488
+ const page = res.pageIndex + 1;
489
+ engine.goToPage(page);
490
+ setDocumentState({ activeSearchIndex: idx, currentPage: page });
491
+ triggerScrollToPage(res.pageIndex);
492
+ },
493
+ className: `p-4 rounded-xl border-2 cursor-pointer transition-all group hover:scale-[1.02] ${idx === activeSearchIndex ? "shadow-lg" : isDark ? "border-[#333] hover:border-[#555] bg-[#222]" : "border-gray-50 hover:border-gray-200 bg-gray-50/50 hover:bg-white"}`,
494
+ style: idx === activeSearchIndex ? { borderColor: accentColor, backgroundColor: accentSoft } : void 0,
495
+ children: [
496
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [
497
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
498
+ "span",
499
+ {
500
+ className: `text-[10px] font-black uppercase tracking-tighter ${idx === activeSearchIndex ? "" : "text-gray-400"}`,
501
+ style: idx === activeSearchIndex ? { color: accentColor } : void 0,
502
+ children: [
503
+ "P\xC1GINA ",
504
+ res.pageIndex + 1
505
+ ]
506
+ }
507
+ ),
508
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
509
+ "svg",
510
+ {
511
+ className: `w-3 h-3 transition-transform ${idx === activeSearchIndex ? "" : "text-gray-300"}`,
512
+ style: idx === activeSearchIndex ? { color: accentColor } : void 0,
513
+ fill: "none",
514
+ stroke: "currentColor",
515
+ viewBox: "0 0 24 24",
516
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 3, d: "M9 5l7 7-7 7" })
517
+ }
518
+ )
519
+ ] }),
520
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: `text-[11px] font-medium leading-relaxed italic ${isDark ? "text-gray-400" : "text-gray-600"}`, children: [
521
+ "...",
522
+ res.text,
523
+ "..."
524
+ ] })
525
+ ]
526
+ },
527
+ idx
528
+ ))
529
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-3", children: [
530
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-[9px] font-black text-gray-400 uppercase tracking-[0.2em] mb-6 flex items-center", children: [
531
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "WORKSET" }),
532
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-1 h-px bg-current ml-3 opacity-10" })
533
+ ] }),
534
+ annotations.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "text-center py-20", children: [
535
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-12 h-12 bg-gray-500/10 rounded-full flex items-center justify-center mx-auto mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "w-6 h-6 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" }) }) }),
536
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-[10px] font-bold text-gray-400 uppercase tracking-widest", children: "Sem anota\xE7\xF5es" })
537
+ ] }) : annotations.map((ann) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `p-4 rounded-xl border group transition-all cursor-pointer ${isDark ? "bg-[#222] border-[#333] hover:border-[#444]" : "bg-white border-gray-100 shadow-sm hover:shadow-md"}`, children: [
538
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between mb-3", children: [
539
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-2", children: [
540
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-2.5 h-2.5 rounded-full", style: { backgroundColor: ann.color } }),
541
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "text-[10px] font-black", style: { color: accentColor }, children: [
542
+ "P",
543
+ ann.pageIndex + 1
544
+ ] })
545
+ ] }),
546
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[9px] text-gray-400 font-bold", children: new Date(ann.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) })
547
+ ] }),
548
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: `text-[11px] font-bold uppercase tracking-tight ${isDark ? "text-gray-200" : "text-gray-700"}`, children: ann.type })
549
+ ] }, ann.id))
550
+ ] }) })
551
+ ]
552
+ }
553
+ );
491
554
  };
492
555
  var SidebarRight_default = SidebarRight;
493
556
 
@@ -500,7 +563,7 @@ var import_react4 = require("react");
500
563
  var import_core4 = require("@papyrus-sdk/core");
501
564
  var import_types = require("@papyrus-sdk/types");
502
565
  var import_jsx_runtime4 = require("react/jsx-runtime");
503
- var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
566
+ var PageRenderer = ({ engine, pageIndex, availableWidth, onMeasuredSize }) => {
504
567
  const containerRef = (0, import_react4.useRef)(null);
505
568
  const canvasRef = (0, import_react4.useRef)(null);
506
569
  const htmlLayerRef = (0, import_react4.useRef)(null);
@@ -510,6 +573,10 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
510
573
  const [isDragging, setIsDragging] = (0, import_react4.useState)(false);
511
574
  const [startPos, setStartPos] = (0, import_react4.useState)({ x: 0, y: 0 });
512
575
  const [currentRect, setCurrentRect] = (0, import_react4.useState)({ x: 0, y: 0, w: 0, h: 0 });
576
+ const [textLayerVersion, setTextLayerVersion] = (0, import_react4.useState)(0);
577
+ const [selectionMenu, setSelectionMenu] = (0, import_react4.useState)(null);
578
+ const [isInkDrawing, setIsInkDrawing] = (0, import_react4.useState)(false);
579
+ const [inkPoints, setInkPoints] = (0, import_react4.useState)([]);
513
580
  const {
514
581
  zoom,
515
582
  rotation,
@@ -522,10 +589,20 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
522
589
  removeAnnotation,
523
590
  selectedAnnotationId,
524
591
  setSelectedAnnotation,
525
- accentColor
592
+ accentColor,
593
+ annotationColor,
594
+ searchQuery,
595
+ searchResults,
596
+ activeSearchIndex
526
597
  } = (0, import_core4.useViewerStore)();
527
598
  const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
528
599
  const isElementRender = renderTargetType === "element";
600
+ const textMarkupTools = /* @__PURE__ */ new Set(["highlight", "underline", "squiggly", "strikeout"]);
601
+ const canSelectText = activeTool === "select" || textMarkupTools.has(activeTool);
602
+ const hasSearchHits = (0, import_react4.useMemo)(
603
+ () => Boolean(searchQuery?.trim()) && searchResults.some((res) => res.pageIndex === pageIndex),
604
+ [searchQuery, searchResults, pageIndex]
605
+ );
529
606
  (0, import_react4.useEffect)(() => {
530
607
  let active = true;
531
608
  const loadSize = async () => {
@@ -558,8 +635,14 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
558
635
  };
559
636
  }, [pageSize, zoom, fitScale]);
560
637
  (0, import_react4.useEffect)(() => {
561
- if (scrollToPageSignal === pageIndex && containerRef.current) {
562
- containerRef.current.scrollIntoView({ behavior: "smooth", block: "start" });
638
+ if (!displaySize || !onMeasuredSize) return;
639
+ onMeasuredSize(pageIndex, {
640
+ width: Math.round(displaySize.width),
641
+ height: Math.round(displaySize.height)
642
+ });
643
+ }, [displaySize, onMeasuredSize, pageIndex]);
644
+ (0, import_react4.useEffect)(() => {
645
+ if (scrollToPageSignal === pageIndex) {
563
646
  setDocumentState({ scrollToPageSignal: null });
564
647
  }
565
648
  }, [scrollToPageSignal, pageIndex, setDocumentState]);
@@ -571,10 +654,15 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
571
654
  setLoading(true);
572
655
  try {
573
656
  const RENDER_SCALE = 2;
574
- const renderScale = isElementRender ? 1 : RENDER_SCALE * fitScale;
575
- await engine.renderPage(pageIndex, renderTarget, renderScale);
657
+ const canvasRenderScale = isElementRender ? 1 : RENDER_SCALE * fitScale;
658
+ const textRenderScale = isElementRender ? 1 : fitScale;
659
+ if (!isElementRender && canvasRef.current && displaySize) {
660
+ canvasRef.current.style.width = `${displaySize.width}px`;
661
+ canvasRef.current.style.height = `${displaySize.height}px`;
662
+ }
663
+ await engine.renderPage(pageIndex, renderTarget, canvasRenderScale);
576
664
  if (!isElementRender && !pageSize && canvasRef.current) {
577
- const denom = renderScale * Math.max(zoom, 0.01);
665
+ const denom = canvasRenderScale * Math.max(zoom, 0.01);
578
666
  if (denom > 0) {
579
667
  setPageSize({
580
668
  width: canvasRef.current.width / denom,
@@ -585,7 +673,7 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
585
673
  if (!active || !textLayerRef.current) return;
586
674
  if (!isElementRender) {
587
675
  textLayerRef.current.innerHTML = "";
588
- await engine.renderTextLayer(pageIndex, textLayerRef.current, renderScale);
676
+ await engine.renderTextLayer(pageIndex, textLayerRef.current, textRenderScale);
589
677
  }
590
678
  if (!active || !textLayerRef.current) return;
591
679
  if (!isElementRender && displaySize) {
@@ -596,6 +684,7 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
596
684
  textLayerRef.current.style.width = `${displaySize.width}px`;
597
685
  textLayerRef.current.style.height = `${displaySize.height}px`;
598
686
  }
687
+ setTextLayerVersion((v) => v + 1);
599
688
  } catch (err) {
600
689
  if (!active) return;
601
690
  console.error("[Papyrus] Falha na renderiza\xE7\xE3o:", err);
@@ -608,8 +697,68 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
608
697
  active = false;
609
698
  };
610
699
  }, [engine, pageIndex, zoom, rotation, isElementRender, fitScale, displaySize, pageSize]);
700
+ (0, import_react4.useEffect)(() => {
701
+ if (isElementRender) return;
702
+ const layer = textLayerRef.current;
703
+ if (!layer) return;
704
+ const query = searchQuery?.trim().toLowerCase();
705
+ const existingMarks = Array.from(layer.querySelectorAll("mark.papyrus-search-hit"));
706
+ existingMarks.forEach((mark) => {
707
+ const parent = mark.parentNode;
708
+ if (!parent) return;
709
+ while (mark.firstChild) parent.insertBefore(mark.firstChild, mark);
710
+ parent.removeChild(mark);
711
+ parent.normalize();
712
+ });
713
+ if (!query || !hasSearchHits) return;
714
+ const nodes = [];
715
+ const walker = document.createTreeWalker(layer, NodeFilter.SHOW_TEXT, {
716
+ acceptNode(node) {
717
+ const text = node.nodeValue ?? "";
718
+ if (!text.trim()) return NodeFilter.FILTER_REJECT;
719
+ return NodeFilter.FILTER_ACCEPT;
720
+ }
721
+ });
722
+ while (walker.nextNode()) {
723
+ nodes.push(walker.currentNode);
724
+ }
725
+ nodes.forEach((textNode) => {
726
+ const text = textNode.nodeValue ?? "";
727
+ const lower = text.toLowerCase();
728
+ if (!lower.includes(query)) return;
729
+ const fragment = document.createDocumentFragment();
730
+ let cursor = 0;
731
+ let index = lower.indexOf(query, cursor);
732
+ while (index !== -1) {
733
+ if (index > cursor) {
734
+ fragment.appendChild(document.createTextNode(text.slice(cursor, index)));
735
+ }
736
+ const mark = document.createElement("mark");
737
+ mark.className = "papyrus-search-hit";
738
+ mark.textContent = text.slice(index, index + query.length);
739
+ fragment.appendChild(mark);
740
+ cursor = index + query.length;
741
+ index = lower.indexOf(query, cursor);
742
+ }
743
+ if (cursor < text.length) {
744
+ fragment.appendChild(document.createTextNode(text.slice(cursor)));
745
+ }
746
+ const parent = textNode.parentNode;
747
+ if (parent) parent.replaceChild(fragment, textNode);
748
+ });
749
+ }, [searchQuery, hasSearchHits, pageIndex, isElementRender, activeSearchIndex, textLayerVersion]);
611
750
  const handleMouseDown = (e) => {
612
- if (activeTool === "select") return;
751
+ setSelectionMenu(null);
752
+ if (activeTool === "ink") {
753
+ const rect2 = containerRef.current?.getBoundingClientRect();
754
+ if (!rect2) return;
755
+ const x2 = (e.clientX - rect2.left) / rect2.width;
756
+ const y2 = (e.clientY - rect2.top) / rect2.height;
757
+ setIsInkDrawing(true);
758
+ setInkPoints([{ x: x2, y: y2 }]);
759
+ return;
760
+ }
761
+ if (canSelectText) return;
613
762
  const rect = containerRef.current?.getBoundingClientRect();
614
763
  if (!rect) return;
615
764
  setIsDragging(true);
@@ -619,6 +768,14 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
619
768
  setCurrentRect({ x, y, w: 0, h: 0 });
620
769
  };
621
770
  const handleMouseMove = (e) => {
771
+ if (isInkDrawing) {
772
+ const rect2 = containerRef.current?.getBoundingClientRect();
773
+ if (!rect2) return;
774
+ const x = (e.clientX - rect2.left) / rect2.width;
775
+ const y = (e.clientY - rect2.top) / rect2.height;
776
+ setInkPoints((prev) => [...prev, { x, y }]);
777
+ return;
778
+ }
622
779
  if (!isDragging) return;
623
780
  const rect = containerRef.current?.getBoundingClientRect();
624
781
  if (!rect) return;
@@ -632,6 +789,115 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
632
789
  });
633
790
  };
634
791
  const handleMouseUp = (e) => {
792
+ if (isInkDrawing) {
793
+ setIsInkDrawing(false);
794
+ if (inkPoints.length > 1) {
795
+ const xs = inkPoints.map((p) => p.x);
796
+ const ys = inkPoints.map((p) => p.y);
797
+ const minX = Math.min(...xs);
798
+ const maxX = Math.max(...xs);
799
+ const minY = Math.min(...ys);
800
+ const maxY = Math.max(...ys);
801
+ const width = Math.max(maxX - minX, 5e-4);
802
+ const height = Math.max(maxY - minY, 5e-4);
803
+ const path = inkPoints.map((p) => ({
804
+ x: Math.max(0, Math.min(1, p.x)),
805
+ y: Math.max(0, Math.min(1, p.y))
806
+ }));
807
+ addAnnotation({
808
+ id: Math.random().toString(36).substr(2, 9),
809
+ pageIndex,
810
+ type: "ink",
811
+ rect: { x: minX, y: minY, width, height },
812
+ path,
813
+ color: annotationColor,
814
+ createdAt: Date.now()
815
+ });
816
+ }
817
+ setInkPoints([]);
818
+ return;
819
+ }
820
+ const selection = window.getSelection();
821
+ const selectionText = selection?.toString().trim() ?? "";
822
+ if (selectionText && textLayerRef.current && containerRef.current && selection && selection.rangeCount > 0) {
823
+ const range = selection.getRangeAt(0);
824
+ if (textLayerRef.current.contains(range.commonAncestorContainer)) {
825
+ const containerRect = containerRef.current.getBoundingClientRect();
826
+ const clientRects = Array.from(range.getClientRects());
827
+ const rects = clientRects.filter((r) => r.width > 1 && r.height > 1).map((r) => {
828
+ const x = (r.left - containerRect.left) / containerRect.width;
829
+ const y = (r.top - containerRect.top) / containerRect.height;
830
+ const width = r.width / containerRect.width;
831
+ const height = r.height / containerRect.height;
832
+ return {
833
+ x: Math.max(0, Math.min(1, x)),
834
+ y: Math.max(0, Math.min(1, y)),
835
+ width: Math.max(0, Math.min(1, width)),
836
+ height: Math.max(0, Math.min(1, height))
837
+ };
838
+ });
839
+ const uniqueRects = rects.filter((rect, index, list) => {
840
+ const key = `${Math.round(rect.x * 1e4)}-${Math.round(rect.y * 1e4)}-${Math.round(rect.width * 1e4)}-${Math.round(rect.height * 1e4)}`;
841
+ return list.findIndex((r) => `${Math.round(r.x * 1e4)}-${Math.round(r.y * 1e4)}-${Math.round(r.width * 1e4)}-${Math.round(r.height * 1e4)}` === key) === index;
842
+ });
843
+ const mergedRects = uniqueRects.reduce((acc, rect) => {
844
+ const target = acc.find((r) => {
845
+ const closeY = Math.abs(r.y - rect.y) < 2e-3 && Math.abs(r.height - rect.height) < 2e-3;
846
+ const overlaps = rect.x <= r.x + r.width + 2e-3 && rect.x + rect.width >= r.x - 2e-3;
847
+ return closeY && overlaps;
848
+ });
849
+ if (!target) {
850
+ acc.push({ ...rect });
851
+ return acc;
852
+ }
853
+ const left = Math.min(target.x, rect.x);
854
+ const right = Math.max(target.x + target.width, rect.x + rect.width);
855
+ target.x = left;
856
+ target.width = right - left;
857
+ return acc;
858
+ }, []);
859
+ if (mergedRects.length) {
860
+ const xs = mergedRects.map((r) => r.x);
861
+ const ys = mergedRects.map((r) => r.y);
862
+ const xe = mergedRects.map((r) => r.x + r.width);
863
+ const ye = mergedRects.map((r) => r.y + r.height);
864
+ const rect = {
865
+ x: Math.min(...xs),
866
+ y: Math.min(...ys),
867
+ width: Math.max(...xe) - Math.min(...xs),
868
+ height: Math.max(...ye) - Math.min(...ys)
869
+ };
870
+ if (textMarkupTools.has(activeTool)) {
871
+ addAnnotation({
872
+ id: Math.random().toString(36).substr(2, 9),
873
+ pageIndex,
874
+ type: activeTool,
875
+ rect,
876
+ rects: mergedRects,
877
+ color: annotationColor,
878
+ content: selectionText,
879
+ createdAt: Date.now()
880
+ });
881
+ selection.removeAllRanges();
882
+ setSelectionMenu(null);
883
+ return;
884
+ }
885
+ if (activeTool === "select") {
886
+ const anchorX = (rect.x + rect.width) * containerRect.width;
887
+ const anchorY = rect.y * containerRect.height;
888
+ setSelectionMenu({
889
+ rects: mergedRects,
890
+ rect,
891
+ text: selectionText,
892
+ anchor: {
893
+ x: Math.max(12, Math.min(containerRect.width - 12, anchorX)),
894
+ y: Math.max(12, anchorY - 32)
895
+ }
896
+ });
897
+ }
898
+ }
899
+ }
900
+ }
635
901
  if (isDragging) {
636
902
  setIsDragging(false);
637
903
  if (currentRect.w > 5 && currentRect.h > 5) {
@@ -647,7 +913,7 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
647
913
  width: currentRect.w / rect.width,
648
914
  height: currentRect.h / rect.height
649
915
  },
650
- color: activeTool === "highlight" ? "#fbbf24" : activeTool === "strikeout" ? "#ef4444" : accentColor,
916
+ color: activeTool === "highlight" ? annotationColor : accentColor,
651
917
  content: activeTool === "text" || activeTool === "comment" ? "" : void 0,
652
918
  createdAt: Date.now()
653
919
  });
@@ -657,7 +923,6 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
657
923
  return;
658
924
  }
659
925
  if (activeTool === "select") {
660
- const selection = window.getSelection();
661
926
  const selectedText = selection?.toString().trim();
662
927
  if (selectedText) {
663
928
  import_core4.papyrusEvents.emit(import_types.PapyrusEventType.TEXT_SELECTED, {
@@ -683,7 +948,7 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
683
948
  "div",
684
949
  {
685
950
  ref: containerRef,
686
- className: `relative inline-block shadow-2xl bg-white mb-10 transition-all ${activeTool !== "select" ? "no-select cursor-crosshair" : ""}`,
951
+ className: `relative inline-block shadow-2xl bg-white mb-10 transition-all ${canSelectText ? "" : "no-select cursor-crosshair"}`,
687
952
  style: { scrollMarginTop: "20px", minHeight: "100px" },
688
953
  onMouseDown: handleMouseDown,
689
954
  onMouseMove: handleMouseMove,
@@ -712,7 +977,7 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
712
977
  ref: textLayerRef,
713
978
  className: "textLayer",
714
979
  style: {
715
- pointerEvents: isElementRender ? "none" : activeTool === "select" ? "auto" : "none",
980
+ pointerEvents: isElementRender ? "none" : canSelectText ? "auto" : "none",
716
981
  display: isElementRender ? "none" : "block"
717
982
  }
718
983
  }
@@ -722,8 +987,9 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
722
987
  {
723
988
  className: "absolute border-2 z-[40] pointer-events-none",
724
989
  style: {
725
- borderColor: accentColor,
726
- backgroundColor: `${accentColor}33`,
990
+ borderColor: activeTool === "highlight" ? annotationColor : accentColor,
991
+ backgroundColor: activeTool === "highlight" ? `${annotationColor}66` : `${accentColor}33`,
992
+ mixBlendMode: activeTool === "highlight" ? "multiply" : void 0,
727
993
  left: currentRect.x,
728
994
  top: currentRect.y,
729
995
  width: currentRect.w,
@@ -731,6 +997,60 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
731
997
  }
732
998
  }
733
999
  ),
1000
+ isInkDrawing && inkPoints.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1001
+ "svg",
1002
+ {
1003
+ className: "absolute inset-0 pointer-events-none z-[45]",
1004
+ viewBox: "0 0 1 1",
1005
+ preserveAspectRatio: "none",
1006
+ style: { width: "100%", height: "100%" },
1007
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1008
+ "path",
1009
+ {
1010
+ d: inkPoints.map((p, idx) => `${idx === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" "),
1011
+ fill: "none",
1012
+ stroke: annotationColor,
1013
+ strokeWidth: 8e-3,
1014
+ strokeLinecap: "round",
1015
+ strokeLinejoin: "round"
1016
+ }
1017
+ )
1018
+ }
1019
+ ),
1020
+ selectionMenu && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1021
+ "div",
1022
+ {
1023
+ className: "absolute z-[60] flex items-center gap-1 rounded-full border px-2 py-1 shadow-xl bg-white/95 backdrop-blur-md text-gray-700",
1024
+ style: { left: selectionMenu.anchor.x, top: selectionMenu.anchor.y },
1025
+ children: [
1026
+ { id: "highlight", label: "Marcar" },
1027
+ { id: "underline", label: "Sublinhar" },
1028
+ { id: "squiggly", label: "Onda" },
1029
+ { id: "strikeout", label: "Risco" }
1030
+ ].map((action) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1031
+ "button",
1032
+ {
1033
+ className: "text-[10px] font-bold px-2 py-1 rounded-full hover:bg-gray-100",
1034
+ onClick: () => {
1035
+ addAnnotation({
1036
+ id: Math.random().toString(36).substr(2, 9),
1037
+ pageIndex,
1038
+ type: action.id,
1039
+ rect: selectionMenu.rect,
1040
+ rects: selectionMenu.rects,
1041
+ content: selectionMenu.text,
1042
+ color: annotationColor,
1043
+ createdAt: Date.now()
1044
+ });
1045
+ window.getSelection()?.removeAllRanges();
1046
+ setSelectionMenu(null);
1047
+ },
1048
+ children: action.label
1049
+ },
1050
+ action.id
1051
+ ))
1052
+ }
1053
+ ),
734
1054
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute inset-0 pointer-events-none z-20", children: annotations.filter((a) => a.pageIndex === pageIndex).map((ann) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
735
1055
  AnnotationItem,
736
1056
  {
@@ -748,6 +1068,101 @@ var PageRenderer = ({ engine, pageIndex, availableWidth }) => {
748
1068
  };
749
1069
  var AnnotationItem = ({ ann, isSelected, accentColor, onDelete, onSelect }) => {
750
1070
  const isText = ann.type === "text" || ann.type === "comment";
1071
+ const isHighlight = ann.type === "highlight";
1072
+ const isMarkup = ann.type === "highlight" || ann.type === "underline" || ann.type === "squiggly" || ann.type === "strikeout";
1073
+ const rects = ann.rects && ann.rects.length > 0 ? ann.rects : [ann.rect];
1074
+ const isInk = ann.type === "ink" && ann.path && ann.path.length > 1;
1075
+ const renderMarkupRects = () => {
1076
+ if (!isMarkup) return null;
1077
+ return rects.map((r, idx) => {
1078
+ const left = ann.rect.width ? (r.x - ann.rect.x) / ann.rect.width * 100 : 0;
1079
+ const top = ann.rect.height ? (r.y - ann.rect.y) / ann.rect.height * 100 : 0;
1080
+ const width = ann.rect.width ? r.width / ann.rect.width * 100 : 100;
1081
+ const height = ann.rect.height ? r.height / ann.rect.height * 100 : 100;
1082
+ if (ann.type === "highlight") {
1083
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1084
+ "div",
1085
+ {
1086
+ className: "absolute rounded-sm",
1087
+ style: {
1088
+ left: `${left}%`,
1089
+ top: `${top}%`,
1090
+ width: `${width}%`,
1091
+ height: `${height}%`,
1092
+ backgroundColor: `${ann.color}88`,
1093
+ mixBlendMode: "multiply"
1094
+ }
1095
+ },
1096
+ idx
1097
+ );
1098
+ }
1099
+ const lineStyle = {
1100
+ left: `${left}%`,
1101
+ width: `${width}%`
1102
+ };
1103
+ if (ann.type === "underline") {
1104
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1105
+ "div",
1106
+ {
1107
+ className: "absolute",
1108
+ style: {
1109
+ ...lineStyle,
1110
+ top: `calc(${top}% + ${height}% - 2px)`,
1111
+ height: "2px",
1112
+ backgroundColor: ann.color
1113
+ }
1114
+ },
1115
+ idx
1116
+ );
1117
+ }
1118
+ if (ann.type === "strikeout") {
1119
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1120
+ "div",
1121
+ {
1122
+ className: "absolute",
1123
+ style: {
1124
+ ...lineStyle,
1125
+ top: `calc(${top}% + ${height * 0.5}% - 1px)`,
1126
+ height: "2px",
1127
+ backgroundColor: ann.color
1128
+ }
1129
+ },
1130
+ idx
1131
+ );
1132
+ }
1133
+ if (ann.type === "squiggly") {
1134
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1135
+ "div",
1136
+ {
1137
+ className: "absolute",
1138
+ style: {
1139
+ ...lineStyle,
1140
+ top: `calc(${top}% + ${height}% - 4px)`,
1141
+ height: "4px",
1142
+ backgroundImage: `linear-gradient(135deg, transparent 75%, ${ann.color} 0), linear-gradient(225deg, transparent 75%, ${ann.color} 0)`,
1143
+ backgroundSize: "6px 6px",
1144
+ backgroundPosition: "0 0, 3px 3px"
1145
+ }
1146
+ },
1147
+ idx
1148
+ );
1149
+ }
1150
+ return null;
1151
+ });
1152
+ };
1153
+ const renderInk = () => {
1154
+ if (!isInk || !ann.path) return null;
1155
+ const d = ann.path.map((p, idx) => `${idx === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
1156
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1157
+ "svg",
1158
+ {
1159
+ className: "absolute inset-0",
1160
+ viewBox: `${ann.rect.x} ${ann.rect.y} ${ann.rect.width} ${ann.rect.height}`,
1161
+ preserveAspectRatio: "none",
1162
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d, fill: "none", stroke: ann.color, strokeWidth: 8e-3, strokeLinecap: "round", strokeLinejoin: "round" })
1163
+ }
1164
+ );
1165
+ };
751
1166
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
752
1167
  "div",
753
1168
  {
@@ -757,8 +1172,9 @@ var AnnotationItem = ({ ann, isSelected, accentColor, onDelete, onSelect }) => {
757
1172
  top: `${ann.rect.y * 100}%`,
758
1173
  width: `${ann.rect.width * 100}%`,
759
1174
  height: `${ann.rect.height * 100}%`,
760
- backgroundColor: ann.type === "highlight" ? `${ann.color}77` : "transparent",
761
- borderBottom: ann.type === "strikeout" ? `2px solid ${ann.color}` : "none",
1175
+ backgroundColor: !isMarkup && isHighlight ? `${ann.color}88` : "transparent",
1176
+ mixBlendMode: !isMarkup && isHighlight ? "multiply" : void 0,
1177
+ borderBottom: ann.type === "strikeout" && !isMarkup ? `2px solid ${ann.color}` : "none",
762
1178
  outline: isSelected ? `2px solid ${accentColor}` : void 0
763
1179
  },
764
1180
  onClick: (e) => {
@@ -766,6 +1182,8 @@ var AnnotationItem = ({ ann, isSelected, accentColor, onDelete, onSelect }) => {
766
1182
  onSelect();
767
1183
  },
768
1184
  children: [
1185
+ renderMarkupRects(),
1186
+ renderInk(),
769
1187
  isText && isSelected && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute top-full mt-2 w-64 bg-white shadow-2xl rounded-xl p-4 border border-gray-100 z-50", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
770
1188
  "textarea",
771
1189
  {
@@ -795,14 +1213,36 @@ var PageRenderer_default = PageRenderer;
795
1213
 
796
1214
  // components/Viewer.tsx
797
1215
  var import_jsx_runtime5 = require("react/jsx-runtime");
1216
+ var BASE_OVERSCAN = 6;
798
1217
  var Viewer = ({ engine }) => {
799
- const { viewMode, pageCount, currentPage, activeTool, uiTheme, setDocumentState, accentColor } = (0, import_core5.useViewerStore)();
1218
+ const { pageCount, currentPage, zoom, activeTool, uiTheme, scrollToPageSignal, setDocumentState, accentColor, annotationColor, setAnnotationColor } = (0, import_core5.useViewerStore)();
800
1219
  const isDark = uiTheme === "dark";
801
1220
  const viewerRef = (0, import_react5.useRef)(null);
1221
+ const colorPickerRef = (0, import_react5.useRef)(null);
1222
+ const pageRefs = (0, import_react5.useRef)([]);
1223
+ const intersectionRatiosRef = (0, import_react5.useRef)({});
1224
+ const frameRef = (0, import_react5.useRef)(null);
1225
+ const jumpRef = (0, import_react5.useRef)(false);
1226
+ const jumpTimerRef = (0, import_react5.useRef)(null);
802
1227
  const [availableWidth, setAvailableWidth] = (0, import_react5.useState)(null);
1228
+ const [basePageSize, setBasePageSize] = (0, import_react5.useState)(null);
1229
+ const [pageSizes, setPageSizes] = (0, import_react5.useState)({});
1230
+ const [colorPickerOpen, setColorPickerOpen] = (0, import_react5.useState)(false);
803
1231
  const isCompact = availableWidth !== null && availableWidth < 820;
804
1232
  const paddingY = isCompact ? "py-10" : "py-16";
805
- const toolDockPosition = isCompact ? "bottom-6" : "bottom-10";
1233
+ const toolDockPosition = isCompact ? "bottom-4" : "bottom-8";
1234
+ const colorPalette = ["#fbbf24", "#f97316", "#ef4444", "#22c55e", "#06b6d4", "#3b82f6", "#8b5cf6", "#111827"];
1235
+ (0, import_react5.useEffect)(() => {
1236
+ if (!colorPickerOpen) return;
1237
+ const handleClick = (event) => {
1238
+ if (!colorPickerRef.current) return;
1239
+ if (!colorPickerRef.current.contains(event.target)) {
1240
+ setColorPickerOpen(false);
1241
+ }
1242
+ };
1243
+ document.addEventListener("mousedown", handleClick);
1244
+ return () => document.removeEventListener("mousedown", handleClick);
1245
+ }, [colorPickerOpen]);
806
1246
  (0, import_react5.useEffect)(() => {
807
1247
  const element = viewerRef.current;
808
1248
  if (!element) return;
@@ -819,44 +1259,239 @@ var Viewer = ({ engine }) => {
819
1259
  observer.observe(element);
820
1260
  return () => observer.disconnect();
821
1261
  }, []);
1262
+ (0, import_react5.useEffect)(() => {
1263
+ let active = true;
1264
+ if (!pageCount) return;
1265
+ const loadBaseSize = async () => {
1266
+ try {
1267
+ const size = await engine.getPageDimensions(0);
1268
+ if (!active || !size.width || !size.height) return;
1269
+ setBasePageSize(size);
1270
+ } catch {
1271
+ }
1272
+ };
1273
+ loadBaseSize();
1274
+ return () => {
1275
+ active = false;
1276
+ };
1277
+ }, [engine, pageCount]);
1278
+ (0, import_react5.useEffect)(() => {
1279
+ if (scrollToPageSignal == null) return;
1280
+ const root = viewerRef.current;
1281
+ const target = pageRefs.current[scrollToPageSignal];
1282
+ if (root) {
1283
+ setDocumentState({ currentPage: scrollToPageSignal + 1 });
1284
+ jumpRef.current = true;
1285
+ if (jumpTimerRef.current) clearTimeout(jumpTimerRef.current);
1286
+ const previousBehavior = root.style.scrollBehavior;
1287
+ root.style.scrollBehavior = "auto";
1288
+ let targetTop = null;
1289
+ if (target) {
1290
+ targetTop = target.offsetTop;
1291
+ } else if (basePageSize && availableWidth) {
1292
+ const fitScale = Math.min(1, Math.max(0, availableWidth - 48) / basePageSize.width);
1293
+ const estimatedPageHeight = basePageSize.height * fitScale * zoom + 64;
1294
+ targetTop = Math.max(0, estimatedPageHeight * scrollToPageSignal);
1295
+ } else if (pageCount > 1) {
1296
+ const maxScroll = Math.max(0, root.scrollHeight - root.clientHeight);
1297
+ const ratio = scrollToPageSignal / Math.max(1, pageCount - 1);
1298
+ targetTop = Math.max(0, maxScroll * ratio);
1299
+ }
1300
+ if (targetTop != null) {
1301
+ root.scrollTop = Math.max(0, targetTop - 12);
1302
+ }
1303
+ requestAnimationFrame(() => {
1304
+ root.style.scrollBehavior = previousBehavior;
1305
+ });
1306
+ jumpTimerRef.current = setTimeout(() => {
1307
+ jumpRef.current = false;
1308
+ }, 250);
1309
+ }
1310
+ setDocumentState({ scrollToPageSignal: null });
1311
+ }, [scrollToPageSignal, setDocumentState, basePageSize, availableWidth, zoom, pageCount]);
1312
+ (0, import_react5.useEffect)(() => {
1313
+ setPageSizes({});
1314
+ }, [zoom]);
822
1315
  (0, import_react5.useEffect)(() => {
823
1316
  const root = viewerRef.current;
824
1317
  if (!root) return;
1318
+ const flushCurrentPage = () => {
1319
+ if (jumpRef.current) return;
1320
+ const ratios = intersectionRatiosRef.current;
1321
+ const entries = Object.entries(ratios).filter(([, ratio]) => ratio > 0);
1322
+ if (!entries.length) return;
1323
+ const currentIndex = currentPage - 1;
1324
+ const currentRatio = ratios[currentIndex] ?? 0;
1325
+ const [bestIndexText, bestRatio] = entries.reduce(
1326
+ (best, candidate) => Number(candidate[1]) > Number(best[1]) ? candidate : best
1327
+ );
1328
+ const bestIndex = Number(bestIndexText);
1329
+ if (!Number.isFinite(bestIndex)) return;
1330
+ const bestPage = bestIndex + 1;
1331
+ const shouldSwitch = bestPage !== currentPage && (currentRatio <= 0 || bestRatio >= currentRatio + 0.1 || bestRatio >= 0.75);
1332
+ if (shouldSwitch) setDocumentState({ currentPage: bestPage });
1333
+ };
825
1334
  const observer = new IntersectionObserver((entries) => {
826
1335
  entries.forEach((entry) => {
827
- if (entry.isIntersecting) {
828
- const pageIndex = parseInt(entry.target.getAttribute("data-page-index") || "0");
829
- if (pageIndex + 1 !== currentPage) setDocumentState({ currentPage: pageIndex + 1 });
830
- }
1336
+ const pageIndex = parseInt(entry.target.getAttribute("data-page-index") || "0");
1337
+ if (!Number.isFinite(pageIndex)) return;
1338
+ intersectionRatiosRef.current[pageIndex] = entry.isIntersecting ? entry.intersectionRatio : 0;
1339
+ });
1340
+ if (frameRef.current != null) cancelAnimationFrame(frameRef.current);
1341
+ frameRef.current = requestAnimationFrame(() => {
1342
+ frameRef.current = null;
1343
+ flushCurrentPage();
831
1344
  });
832
- }, { root, threshold: 0.5 });
1345
+ }, { root, threshold: [0.25, 0.5, 0.75] });
833
1346
  const pageElements = root.querySelectorAll(".page-container");
834
1347
  pageElements.forEach((el) => observer.observe(el));
835
1348
  return () => {
1349
+ if (frameRef.current != null) {
1350
+ cancelAnimationFrame(frameRef.current);
1351
+ frameRef.current = null;
1352
+ }
836
1353
  pageElements.forEach((el) => observer.unobserve(el));
837
1354
  observer.disconnect();
838
1355
  };
839
1356
  }, [pageCount, setDocumentState, currentPage]);
1357
+ const virtualOverscan = zoom > 1.35 ? 4 : BASE_OVERSCAN;
1358
+ const virtualAnchor = currentPage - 1;
1359
+ const virtualStart = Math.max(0, virtualAnchor - virtualOverscan);
1360
+ const virtualEnd = Math.min(pageCount - 1, virtualAnchor + virtualOverscan);
1361
+ const fallbackSize = (0, import_react5.useMemo)(() => {
1362
+ if (basePageSize && availableWidth) {
1363
+ const fitScale = Math.min(1, Math.max(0, availableWidth - 48) / basePageSize.width);
1364
+ return {
1365
+ width: Math.round(basePageSize.width * fitScale * zoom),
1366
+ height: Math.round(basePageSize.height * fitScale * zoom)
1367
+ };
1368
+ }
1369
+ const base = availableWidth ? Math.max(680, availableWidth * 1.3) : 1100;
1370
+ return {
1371
+ width: Math.round((availableWidth ?? 860) - 48),
1372
+ height: Math.round(base * zoom)
1373
+ };
1374
+ }, [basePageSize, availableWidth, zoom]);
1375
+ const averagePageHeight = (0, import_react5.useMemo)(() => {
1376
+ const heights = Object.values(pageSizes).map((size) => size.height);
1377
+ if (!heights.length) return availableWidth ? Math.max(680, availableWidth * 1.3) : 1100;
1378
+ return Math.round(heights.reduce((sum, h) => sum + h, 0) / heights.length);
1379
+ }, [pageSizes, availableWidth]);
840
1380
  const pages = Array.from({ length: pageCount }).map((_, i) => i);
1381
+ const handlePageMeasured = (pageIndex, size) => {
1382
+ setPageSizes((prev) => {
1383
+ const current = prev[pageIndex];
1384
+ if (current && current.width === size.width && current.height === size.height) return prev;
1385
+ return { ...prev, [pageIndex]: size };
1386
+ });
1387
+ };
841
1388
  const tools = [
842
1389
  { id: "select", name: "Select", icon: "M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5" },
843
- { id: "highlight", name: "Marker", icon: "M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" },
844
- { id: "strikeout", name: "Strike", icon: "M13 10V3L4 14h7v7l9-11h-7z" },
1390
+ { id: "highlight", name: "Highlight", icon: "M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" },
1391
+ { id: "underline", name: "Underline", icon: "M6 3v6a6 6 0 0012 0V3M4 21h16" },
1392
+ { id: "squiggly", name: "Squiggly", icon: "M3 17c2-4 4-4 6 0s4 4 6 0 4-4 6 0" },
1393
+ { id: "strikeout", name: "Strike", icon: "M4 12h16M8 6h8M8 18h8" },
1394
+ { id: "ink", name: "Freehand", icon: "M4 19c4-6 7-9 10-9 3 0 5 2 6 5" },
845
1395
  { id: "comment", name: "Note", icon: "M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" }
846
1396
  ];
847
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: viewerRef, className: `papyrus-viewer flex-1 overflow-auto flex flex-col items-center ${paddingY} relative custom-scrollbar scroll-smooth ${isDark ? "bg-[#121212]" : "bg-[#e9ecef]"}`, children: [
848
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex flex-col items-center gap-6 w-full", children: pages.map((idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { "data-page-index": idx, className: "page-container", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PageRenderer_default, { engine, pageIndex: idx, availableWidth: availableWidth ?? void 0 }) }, idx)) }),
849
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: `fixed ${toolDockPosition} left-1/2 -translate-x-1/2 shadow-2xl rounded-2xl p-2 flex border z-50 ${isDark ? "bg-[#2a2a2a]/90 border-[#3a3a3a] backdrop-blur-xl" : "bg-white/95 border-gray-100 backdrop-blur-md"}`, children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
850
- "button",
851
- {
852
- onClick: () => setDocumentState({ activeTool: tool.id }),
853
- className: `w-10 h-10 rounded-xl flex items-center justify-center transition-all ${activeTool === tool.id ? "text-white shadow-lg" : "text-gray-400"}`,
854
- style: activeTool === tool.id ? { backgroundColor: accentColor } : void 0,
855
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: tool.icon }) })
856
- },
857
- tool.id
858
- )) })
859
- ] });
1397
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1398
+ "div",
1399
+ {
1400
+ ref: viewerRef,
1401
+ "data-papyrus-theme": uiTheme,
1402
+ className: `papyrus-viewer papyrus-theme min-w-0 w-full flex-1 overflow-y-scroll overflow-x-hidden flex flex-col items-center ${paddingY} relative custom-scrollbar scroll-smooth ${isDark ? "bg-[#121212]" : "bg-[#e9ecef]"}`,
1403
+ children: [
1404
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex flex-col items-center gap-6 w-full min-w-0", children: pages.map((idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1405
+ "div",
1406
+ {
1407
+ ref: (element) => {
1408
+ pageRefs.current[idx] = element;
1409
+ },
1410
+ "data-page-index": idx,
1411
+ className: "page-container",
1412
+ children: idx >= virtualStart && idx <= virtualEnd ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1413
+ PageRenderer_default,
1414
+ {
1415
+ engine,
1416
+ pageIndex: idx,
1417
+ availableWidth: availableWidth ?? void 0,
1418
+ onMeasuredSize: handlePageMeasured
1419
+ }
1420
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1421
+ "div",
1422
+ {
1423
+ className: `inline-block mb-10 shadow-2xl border ${isDark ? "bg-[#0f0f0f] border-[#2b2b2b]" : "bg-white border-gray-200"}`,
1424
+ style: {
1425
+ width: pageSizes[idx]?.width ?? fallbackSize.width,
1426
+ height: pageSizes[idx]?.height ?? Math.max(fallbackSize.height, averagePageHeight)
1427
+ }
1428
+ }
1429
+ )
1430
+ },
1431
+ idx
1432
+ )) }),
1433
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: `papyrus-tool-dock sticky ${toolDockPosition} w-full flex justify-center pointer-events-none z-[70]`, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `pointer-events-auto shadow-2xl rounded-2xl p-2 flex items-center border z-[80] ${isDark ? "bg-[#2a2a2a]/90 border-[#3a3a3a] backdrop-blur-xl" : "bg-white/95 border-gray-100 backdrop-blur-md"}`, children: [
1434
+ tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1435
+ "button",
1436
+ {
1437
+ title: tool.name,
1438
+ "aria-label": tool.name,
1439
+ onClick: () => setDocumentState({ activeTool: tool.id }),
1440
+ className: `w-10 h-10 rounded-xl flex items-center justify-center transition-all ${activeTool === tool.id ? "text-white shadow-lg" : "text-gray-400"}`,
1441
+ style: activeTool === tool.id ? { backgroundColor: accentColor } : void 0,
1442
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: tool.icon }) })
1443
+ },
1444
+ tool.id
1445
+ )),
1446
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-px h-7 mx-2 bg-white/10" }),
1447
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: colorPickerRef, className: "relative", children: [
1448
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1449
+ "button",
1450
+ {
1451
+ title: "Cor do marcador",
1452
+ "aria-label": "Cor do marcador",
1453
+ onClick: () => setColorPickerOpen((prev) => !prev),
1454
+ className: "w-9 h-9 rounded-full flex items-center justify-center border transition-all cursor-pointer relative",
1455
+ style: { borderColor: annotationColor },
1456
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "w-5 h-5 rounded-full", style: { backgroundColor: annotationColor } })
1457
+ }
1458
+ ),
1459
+ colorPickerOpen && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `absolute bottom-full left-1/2 -translate-x-1/2 mb-3 w-48 rounded-xl border p-3 shadow-2xl overflow-hidden ${isDark ? "bg-[#1f1f1f] border-[#333]" : "bg-white border-gray-200"}`, children: [
1460
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "grid grid-cols-4 gap-2 mb-3", children: colorPalette.map((color) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1461
+ "button",
1462
+ {
1463
+ onClick: () => {
1464
+ setAnnotationColor(color);
1465
+ setColorPickerOpen(false);
1466
+ },
1467
+ className: "w-7 h-7 rounded-full border transition-all",
1468
+ style: { backgroundColor: color, borderColor: color === annotationColor ? "#fff" : "transparent" }
1469
+ },
1470
+ color
1471
+ )) }),
1472
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2 w-full", children: [
1473
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[10px] uppercase tracking-widest text-gray-400 shrink-0", children: "Hex" }),
1474
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1475
+ "input",
1476
+ {
1477
+ type: "text",
1478
+ value: annotationColor.toUpperCase(),
1479
+ onChange: (e) => {
1480
+ const next = e.target.value.trim();
1481
+ if (next.startsWith("#") && (next.length === 4 || next.length === 7)) {
1482
+ setAnnotationColor(next);
1483
+ }
1484
+ },
1485
+ className: `flex-1 min-w-0 w-full text-xs rounded-md px-2 py-1 border ${isDark ? "bg-[#2a2a2a] border-[#444] text-white" : "bg-gray-100 border-gray-200 text-gray-700"}`
1486
+ }
1487
+ )
1488
+ ] })
1489
+ ] })
1490
+ ] })
1491
+ ] }) })
1492
+ ]
1493
+ }
1494
+ );
860
1495
  };
861
1496
  var Viewer_default = Viewer;
862
1497
  // Annotate the CommonJS export names for ESM import in node: