@papyrus-sdk/ui-react 0.2.20 → 0.2.22

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
@@ -32,6 +32,8 @@ var import_react = require("react");
32
32
  var import_react_dom = require("react-dom");
33
33
  var import_core = require("@papyrus-sdk/core");
34
34
  var import_jsx_runtime = require("react/jsx-runtime");
35
+ var MOBILE_LANDSCAPE_MAX_HEIGHT_PX = 500;
36
+ var MOBILE_VIEWPORT_QUERY = `(max-width: 639px), (orientation: landscape) and (max-height: ${MOBILE_LANDSCAPE_MAX_HEIGHT_PX}px)`;
35
37
  var Topbar = ({
36
38
  engine,
37
39
  showBrand = false,
@@ -86,7 +88,7 @@ var Topbar = ({
86
88
  }, [hasMobileMenu]);
87
89
  (0, import_react.useEffect)(() => {
88
90
  if (!canUseDOM || typeof window.matchMedia !== "function") return;
89
- const mediaQuery = window.matchMedia("(max-width: 639px)");
91
+ const mediaQuery = window.matchMedia(MOBILE_VIEWPORT_QUERY);
90
92
  const updateViewport = () => setIsMobileViewport(mediaQuery.matches);
91
93
  updateViewport();
92
94
  if (typeof mediaQuery.addEventListener === "function") {
@@ -1131,10 +1133,15 @@ var SidebarRight = ({ engine, style }) => {
1131
1133
  setDocumentState,
1132
1134
  triggerScrollToPage,
1133
1135
  annotations,
1134
- accentColor
1136
+ accentColor,
1137
+ updateAnnotation,
1138
+ addAnnotationReply,
1139
+ setSelectedAnnotation
1135
1140
  } = (0, import_core3.useViewerStore)();
1136
1141
  const [query, setQuery] = (0, import_react3.useState)("");
1137
1142
  const [isSearching, setIsSearching] = (0, import_react3.useState)(false);
1143
+ const [contentDrafts, setContentDrafts] = (0, import_react3.useState)({});
1144
+ const [replyDrafts, setReplyDrafts] = (0, import_react3.useState)({});
1138
1145
  const searchService = new import_core3.SearchService(engine);
1139
1146
  const isDark = uiTheme === "dark";
1140
1147
  const accentSoft = withAlpha2(accentColor, 0.12);
@@ -1150,6 +1157,41 @@ var SidebarRight = ({ engine, style }) => {
1150
1157
  setSearch(query, results);
1151
1158
  setIsSearching(false);
1152
1159
  };
1160
+ const jumpToAnnotation = (annotation) => {
1161
+ const page = annotation.pageIndex + 1;
1162
+ engine.goToPage(page);
1163
+ setDocumentState({ currentPage: page });
1164
+ setSelectedAnnotation(annotation.id);
1165
+ triggerScrollToPage(annotation.pageIndex);
1166
+ };
1167
+ const getContentDraft = (annotation) => {
1168
+ if (Object.prototype.hasOwnProperty.call(contentDrafts, annotation.id)) {
1169
+ return contentDrafts[annotation.id];
1170
+ }
1171
+ return annotation.content ?? "";
1172
+ };
1173
+ const updateContentDraft = (annotationId, nextValue) => {
1174
+ setContentDrafts((prev) => ({ ...prev, [annotationId]: nextValue }));
1175
+ };
1176
+ const submitContent = (annotation) => {
1177
+ const nextContent = getContentDraft(annotation).trim();
1178
+ const currentContent = (annotation.content ?? "").trim();
1179
+ if (nextContent === currentContent) return;
1180
+ updateAnnotation(annotation.id, {
1181
+ content: nextContent,
1182
+ updatedAt: Date.now()
1183
+ });
1184
+ };
1185
+ const getReplyDraft = (annotationId) => replyDrafts[annotationId] ?? "";
1186
+ const updateReplyDraft = (annotationId, nextValue) => {
1187
+ setReplyDrafts((prev) => ({ ...prev, [annotationId]: nextValue }));
1188
+ };
1189
+ const submitReply = (annotationId) => {
1190
+ const nextReply = getReplyDraft(annotationId).trim();
1191
+ if (!nextReply) return;
1192
+ addAnnotationReply(annotationId, nextReply);
1193
+ setReplyDrafts((prev) => ({ ...prev, [annotationId]: "" }));
1194
+ };
1153
1195
  if (!sidebarRightOpen) return null;
1154
1196
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1155
1197
  "div",
@@ -1351,48 +1393,152 @@ var SidebarRight = ({ engine, style }) => {
1351
1393
  }
1352
1394
  ) }),
1353
1395
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-[10px] font-bold text-gray-400 uppercase tracking-widest", children: "Sem anota\xE7\xF5es" })
1354
- ] }) : annotations.map((ann) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1355
- "div",
1356
- {
1357
- 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"}`,
1358
- children: [
1359
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between mb-3", children: [
1360
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-2", children: [
1396
+ ] }) : annotations.slice().sort(
1397
+ (a, b) => (b.updatedAt ?? b.createdAt) - (a.updatedAt ?? a.createdAt)
1398
+ ).map((ann) => {
1399
+ const isCommentThread = ann.type === "comment" || ann.type === "text";
1400
+ const replies = ann.replies ?? [];
1401
+ const contentDraft = getContentDraft(ann);
1402
+ const replyDraft = getReplyDraft(ann.id);
1403
+ const hasExistingContent = Boolean((ann.content ?? "").trim());
1404
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1405
+ "div",
1406
+ {
1407
+ className: "rounded-xl border p-4 transition-colors",
1408
+ style: {
1409
+ background: "var(--papyrus-surface-2-resolved, var(--papyrus-surface-2, #1f2937))",
1410
+ borderColor: "var(--papyrus-border-resolved, var(--papyrus-border, #374151))",
1411
+ color: "var(--papyrus-text-resolved, var(--papyrus-text, #e5e7eb))"
1412
+ },
1413
+ children: [
1414
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mb-3 flex items-center justify-between", children: [
1415
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center space-x-2", children: [
1416
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1417
+ "div",
1418
+ {
1419
+ className: "h-2.5 w-2.5 rounded-full",
1420
+ style: { backgroundColor: ann.color }
1421
+ }
1422
+ ),
1423
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1424
+ "span",
1425
+ {
1426
+ className: "text-[10px] font-black uppercase tracking-wide",
1427
+ style: { color: accentColor },
1428
+ children: [
1429
+ "P",
1430
+ ann.pageIndex + 1
1431
+ ]
1432
+ }
1433
+ ),
1434
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[10px] font-semibold opacity-70 uppercase tracking-wide", children: ann.type })
1435
+ ] }),
1361
1436
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1362
- "div",
1437
+ "button",
1438
+ {
1439
+ type: "button",
1440
+ className: "rounded-md border px-2 py-1 text-[10px] font-semibold uppercase tracking-wide",
1441
+ onClick: () => jumpToAnnotation(ann),
1442
+ style: {
1443
+ borderColor: accentColor,
1444
+ color: accentColor
1445
+ },
1446
+ children: "Ir para pagina"
1447
+ }
1448
+ )
1449
+ ] }),
1450
+ isCommentThread ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
1451
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1452
+ "textarea",
1363
1453
  {
1364
- className: "w-2.5 h-2.5 rounded-full",
1365
- style: { backgroundColor: ann.color }
1454
+ className: "w-full resize-none rounded-md border p-2 text-xs focus:outline-none",
1455
+ style: {
1456
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #111827))",
1457
+ borderColor: "var(--papyrus-border-resolved, var(--papyrus-border, #374151))",
1458
+ color: "var(--papyrus-text-resolved, var(--papyrus-text, #e5e7eb))"
1459
+ },
1460
+ rows: 3,
1461
+ placeholder: "Escreva seu comentario...",
1462
+ value: contentDraft,
1463
+ onChange: (event) => updateContentDraft(ann.id, event.target.value),
1464
+ onKeyDown: (event) => {
1465
+ if (event.key === "Enter" && !event.shiftKey) {
1466
+ event.preventDefault();
1467
+ submitContent(ann);
1468
+ }
1469
+ }
1366
1470
  }
1367
1471
  ),
1368
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1369
- "span",
1472
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1473
+ "button",
1370
1474
  {
1371
- className: "text-[10px] font-black",
1372
- style: { color: accentColor },
1373
- children: [
1374
- "P",
1375
- ann.pageIndex + 1
1376
- ]
1475
+ type: "button",
1476
+ className: "rounded-md px-3 py-1.5 text-[11px] font-semibold text-white",
1477
+ style: { backgroundColor: accentColor },
1478
+ onClick: () => submitContent(ann),
1479
+ children: hasExistingContent ? "Atualizar comentario" : "Enviar comentario"
1480
+ }
1481
+ ) })
1482
+ ] }) : null,
1483
+ replies.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-3 space-y-2", children: replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1484
+ "div",
1485
+ {
1486
+ className: "rounded-md border p-2",
1487
+ style: {
1488
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #111827))",
1489
+ borderColor: "var(--papyrus-border-resolved, var(--papyrus-border, #374151))"
1490
+ },
1491
+ children: [
1492
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs leading-relaxed", children: reply.content }),
1493
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-1 text-[10px] opacity-70", children: new Date(reply.createdAt).toLocaleTimeString(
1494
+ [],
1495
+ {
1496
+ hour: "2-digit",
1497
+ minute: "2-digit"
1498
+ }
1499
+ ) })
1500
+ ]
1501
+ },
1502
+ reply.id
1503
+ )) }) : null,
1504
+ isCommentThread ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-3 flex items-center gap-2", children: [
1505
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1506
+ "input",
1507
+ {
1508
+ type: "text",
1509
+ className: "flex-1 rounded-md border px-2 py-1.5 text-xs focus:outline-none",
1510
+ style: {
1511
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #111827))",
1512
+ borderColor: "var(--papyrus-border-resolved, var(--papyrus-border, #374151))",
1513
+ color: "var(--papyrus-text-resolved, var(--papyrus-text, #e5e7eb))"
1514
+ },
1515
+ value: replyDraft,
1516
+ placeholder: "Responder...",
1517
+ onChange: (event) => updateReplyDraft(ann.id, event.target.value),
1518
+ onKeyDown: (event) => {
1519
+ if (event.key === "Enter") {
1520
+ event.preventDefault();
1521
+ submitReply(ann.id);
1522
+ }
1523
+ }
1524
+ }
1525
+ ),
1526
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1527
+ "button",
1528
+ {
1529
+ type: "button",
1530
+ className: "rounded-md px-3 py-1.5 text-[11px] font-semibold text-white",
1531
+ style: { backgroundColor: accentColor },
1532
+ onClick: () => submitReply(ann.id),
1533
+ children: "Responder"
1377
1534
  }
1378
1535
  )
1379
- ] }),
1380
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[9px] text-gray-400 font-bold", children: new Date(ann.createdAt).toLocaleTimeString([], {
1381
- hour: "2-digit",
1382
- minute: "2-digit"
1383
- }) })
1384
- ] }),
1385
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1386
- "p",
1387
- {
1388
- className: `text-[11px] font-bold uppercase tracking-tight ${isDark ? "text-gray-200" : "text-gray-700"}`,
1389
- children: ann.type
1390
- }
1391
- )
1392
- ]
1393
- },
1394
- ann.id
1395
- ))
1536
+ ] }) : null
1537
+ ]
1538
+ },
1539
+ ann.id
1540
+ );
1541
+ })
1396
1542
  ] }) })
1397
1543
  ]
1398
1544
  }
@@ -1420,6 +1566,10 @@ var PageRenderer = ({
1420
1566
  const canvasRef = (0, import_react4.useRef)(null);
1421
1567
  const htmlLayerRef = (0, import_react4.useRef)(null);
1422
1568
  const textLayerRef = (0, import_react4.useRef)(null);
1569
+ const skipNextAnnotationSelectRef = (0, import_react4.useRef)(false);
1570
+ const skipSelectResetTimerRef = (0, import_react4.useRef)(
1571
+ null
1572
+ );
1423
1573
  const [loading, setLoading] = (0, import_react4.useState)(true);
1424
1574
  const [pageSize, setPageSize] = (0, import_react4.useState)(null);
1425
1575
  const [isDragging, setIsDragging] = (0, import_react4.useState)(false);
@@ -1437,6 +1587,8 @@ var PageRenderer = ({
1437
1587
  setDocumentState,
1438
1588
  annotations,
1439
1589
  addAnnotation,
1590
+ addAnnotationReply,
1591
+ updateAnnotation,
1440
1592
  activeTool,
1441
1593
  removeAnnotation,
1442
1594
  selectedAnnotationId,
@@ -1460,6 +1612,24 @@ var PageRenderer = ({
1460
1612
  () => Boolean(searchQuery?.trim()) && searchResults.some((res) => res.pageIndex === pageIndex),
1461
1613
  [searchQuery, searchResults, pageIndex]
1462
1614
  );
1615
+ const suppressNextAnnotationSelect = () => {
1616
+ skipNextAnnotationSelectRef.current = true;
1617
+ if (skipSelectResetTimerRef.current) {
1618
+ clearTimeout(skipSelectResetTimerRef.current);
1619
+ }
1620
+ skipSelectResetTimerRef.current = setTimeout(() => {
1621
+ skipNextAnnotationSelectRef.current = false;
1622
+ skipSelectResetTimerRef.current = null;
1623
+ }, 0);
1624
+ };
1625
+ (0, import_react4.useEffect)(
1626
+ () => () => {
1627
+ if (skipSelectResetTimerRef.current) {
1628
+ clearTimeout(skipSelectResetTimerRef.current);
1629
+ }
1630
+ },
1631
+ []
1632
+ );
1463
1633
  (0, import_react4.useEffect)(() => {
1464
1634
  let active = true;
1465
1635
  const loadSize = async () => {
@@ -1629,13 +1799,27 @@ var PageRenderer = ({
1629
1799
  activeSearchIndex,
1630
1800
  textLayerVersion
1631
1801
  ]);
1632
- const handleMouseDown = (e) => {
1802
+ const getTouchPoint = (event) => {
1803
+ const touch = event.touches[0] ?? event.changedTouches[0];
1804
+ if (!touch) return null;
1805
+ return { x: touch.clientX, y: touch.clientY };
1806
+ };
1807
+ const handlePointerDown = (clientX, clientY, target) => {
1808
+ const clickedInsideAnnotation = Boolean(
1809
+ target?.closest("[data-papyrus-annotation-id]")
1810
+ );
1811
+ const clickedSelectionMenu = Boolean(
1812
+ target?.closest("[data-papyrus-selection-menu]")
1813
+ );
1814
+ if (!clickedInsideAnnotation && !clickedSelectionMenu) {
1815
+ setSelectedAnnotation(null);
1816
+ }
1633
1817
  setSelectionMenu(null);
1634
1818
  if (activeTool === "ink") {
1635
1819
  const rect2 = containerRef.current?.getBoundingClientRect();
1636
1820
  if (!rect2) return;
1637
- const x2 = (e.clientX - rect2.left) / rect2.width;
1638
- const y2 = (e.clientY - rect2.top) / rect2.height;
1821
+ const x2 = (clientX - rect2.left) / rect2.width;
1822
+ const y2 = (clientY - rect2.top) / rect2.height;
1639
1823
  setIsInkDrawing(true);
1640
1824
  setInkPoints([{ x: x2, y: y2 }]);
1641
1825
  return;
@@ -1644,25 +1828,25 @@ var PageRenderer = ({
1644
1828
  const rect = containerRef.current?.getBoundingClientRect();
1645
1829
  if (!rect) return;
1646
1830
  setIsDragging(true);
1647
- const x = e.clientX - rect.left;
1648
- const y = e.clientY - rect.top;
1831
+ const x = clientX - rect.left;
1832
+ const y = clientY - rect.top;
1649
1833
  setStartPos({ x, y });
1650
1834
  setCurrentRect({ x, y, w: 0, h: 0 });
1651
1835
  };
1652
- const handleMouseMove = (e) => {
1836
+ const handlePointerMove = (clientX, clientY) => {
1653
1837
  if (isInkDrawing) {
1654
1838
  const rect2 = containerRef.current?.getBoundingClientRect();
1655
1839
  if (!rect2) return;
1656
- const x = (e.clientX - rect2.left) / rect2.width;
1657
- const y = (e.clientY - rect2.top) / rect2.height;
1840
+ const x = (clientX - rect2.left) / rect2.width;
1841
+ const y = (clientY - rect2.top) / rect2.height;
1658
1842
  setInkPoints((prev) => [...prev, { x, y }]);
1659
1843
  return;
1660
1844
  }
1661
1845
  if (!isDragging) return;
1662
1846
  const rect = containerRef.current?.getBoundingClientRect();
1663
1847
  if (!rect) return;
1664
- const currentX = e.clientX - rect.left;
1665
- const currentY = e.clientY - rect.top;
1848
+ const currentX = clientX - rect.left;
1849
+ const currentY = clientY - rect.top;
1666
1850
  setCurrentRect({
1667
1851
  x: Math.min(startPos.x, currentX),
1668
1852
  y: Math.min(startPos.y, currentY),
@@ -1670,7 +1854,7 @@ var PageRenderer = ({
1670
1854
  h: Math.abs(currentY - startPos.y)
1671
1855
  });
1672
1856
  };
1673
- const handleMouseUp = (e) => {
1857
+ const handlePointerUp = () => {
1674
1858
  if (isInkDrawing) {
1675
1859
  setIsInkDrawing(false);
1676
1860
  if (inkPoints.length > 1) {
@@ -1686,6 +1870,7 @@ var PageRenderer = ({
1686
1870
  x: Math.max(0, Math.min(1, p.x)),
1687
1871
  y: Math.max(0, Math.min(1, p.y))
1688
1872
  }));
1873
+ suppressNextAnnotationSelect();
1689
1874
  addAnnotation({
1690
1875
  id: Math.random().toString(36).substr(2, 9),
1691
1876
  pageIndex,
@@ -1766,6 +1951,7 @@ var PageRenderer = ({
1766
1951
  height: Math.max(...ye) - Math.min(...ys)
1767
1952
  };
1768
1953
  if (textMarkupTools.has(activeTool)) {
1954
+ suppressNextAnnotationSelect();
1769
1955
  addAnnotation({
1770
1956
  id: Math.random().toString(36).substr(2, 9),
1771
1957
  pageIndex,
@@ -1801,6 +1987,9 @@ var PageRenderer = ({
1801
1987
  if (currentRect.w > 5 && currentRect.h > 5) {
1802
1988
  const rect = containerRef.current?.getBoundingClientRect();
1803
1989
  if (rect) {
1990
+ if (activeTool !== "text" && activeTool !== "comment") {
1991
+ suppressNextAnnotationSelect();
1992
+ }
1804
1993
  addAnnotation({
1805
1994
  id: Math.random().toString(36).substr(2, 9),
1806
1995
  pageIndex,
@@ -1830,6 +2019,37 @@ var PageRenderer = ({
1830
2019
  }
1831
2020
  }
1832
2021
  };
2022
+ const handleMouseDown = (e) => {
2023
+ handlePointerDown(e.clientX, e.clientY, e.target);
2024
+ };
2025
+ const handleMouseMove = (e) => {
2026
+ handlePointerMove(e.clientX, e.clientY);
2027
+ };
2028
+ const handleMouseUp = () => {
2029
+ handlePointerUp();
2030
+ };
2031
+ const handleTouchStart = (event) => {
2032
+ if (event.touches.length > 1) return;
2033
+ const point = getTouchPoint(event);
2034
+ if (!point) return;
2035
+ handlePointerDown(point.x, point.y, event.target);
2036
+ if ((activeTool === "ink" || !canSelectText) && event.cancelable) {
2037
+ event.preventDefault();
2038
+ }
2039
+ };
2040
+ const handleTouchMove = (event) => {
2041
+ if (event.touches.length > 1) return;
2042
+ const point = getTouchPoint(event);
2043
+ if (!point) return;
2044
+ handlePointerMove(point.x, point.y);
2045
+ if ((isInkDrawing || isDragging) && event.cancelable) {
2046
+ event.preventDefault();
2047
+ }
2048
+ };
2049
+ const handleTouchEnd = (event) => {
2050
+ if (event.touches.length > 0) return;
2051
+ handlePointerUp();
2052
+ };
1833
2053
  const getPageFilter = () => {
1834
2054
  switch (pageTheme) {
1835
2055
  case "sepia":
@@ -1847,10 +2067,18 @@ var PageRenderer = ({
1847
2067
  {
1848
2068
  ref: containerRef,
1849
2069
  className: `relative inline-block shadow-2xl bg-white mb-10 ${canSelectText ? "" : "no-select cursor-crosshair"}`,
1850
- style: { scrollMarginTop: "20px", minHeight: "100px" },
2070
+ style: {
2071
+ scrollMarginTop: "20px",
2072
+ minHeight: "100px",
2073
+ touchAction: activeTool === "ink" || activeTool === "text" || activeTool === "comment" ? "none" : "auto"
2074
+ },
1851
2075
  onMouseDown: handleMouseDown,
1852
2076
  onMouseMove: handleMouseMove,
1853
2077
  onMouseUp: handleMouseUp,
2078
+ onTouchStart: handleTouchStart,
2079
+ onTouchMove: handleTouchMove,
2080
+ onTouchEnd: handleTouchEnd,
2081
+ onTouchCancel: handleTouchEnd,
1854
2082
  children: [
1855
2083
  loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute inset-0 bg-gray-50 flex items-center justify-center z-10 animate-pulse", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-[10px] font-black text-gray-400 uppercase tracking-widest", children: "Sincronizando..." }) }),
1856
2084
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -1924,6 +2152,7 @@ var PageRenderer = ({
1924
2152
  selectionMenu && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1925
2153
  "div",
1926
2154
  {
2155
+ "data-papyrus-selection-menu": "true",
1927
2156
  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",
1928
2157
  style: { left: selectionMenu.anchor.x, top: selectionMenu.anchor.y },
1929
2158
  children: [
@@ -1936,6 +2165,7 @@ var PageRenderer = ({
1936
2165
  {
1937
2166
  className: "text-[10px] font-bold px-2 py-1 rounded-full hover:bg-gray-100",
1938
2167
  onClick: () => {
2168
+ suppressNextAnnotationSelect();
1939
2169
  addAnnotation({
1940
2170
  id: Math.random().toString(36).substr(2, 9),
1941
2171
  pageIndex,
@@ -1962,7 +2192,15 @@ var PageRenderer = ({
1962
2192
  isSelected: selectedAnnotationId === ann.id,
1963
2193
  accentColor,
1964
2194
  onDelete: () => removeAnnotation(ann.id),
1965
- onSelect: () => setSelectedAnnotation(ann.id)
2195
+ onSelect: () => {
2196
+ if (skipNextAnnotationSelectRef.current) {
2197
+ skipNextAnnotationSelectRef.current = false;
2198
+ return;
2199
+ }
2200
+ setSelectedAnnotation(ann.id);
2201
+ },
2202
+ onUpdate: (updates) => updateAnnotation(ann.id, updates),
2203
+ onAddReply: (content) => addAnnotationReply(ann.id, content)
1966
2204
  },
1967
2205
  ann.id
1968
2206
  )) })
@@ -1970,12 +2208,43 @@ var PageRenderer = ({
1970
2208
  }
1971
2209
  );
1972
2210
  };
1973
- var AnnotationItem = ({ ann, isSelected, accentColor, onDelete, onSelect }) => {
2211
+ var AnnotationItem = ({
2212
+ ann,
2213
+ isSelected,
2214
+ accentColor,
2215
+ onDelete,
2216
+ onSelect,
2217
+ onUpdate,
2218
+ onAddReply
2219
+ }) => {
1974
2220
  const isText = ann.type === "text" || ann.type === "comment";
1975
2221
  const isHighlight = ann.type === "highlight";
1976
2222
  const isMarkup = ann.type === "highlight" || ann.type === "underline" || ann.type === "squiggly" || ann.type === "strikeout";
1977
2223
  const rects = ann.rects && ann.rects.length > 0 ? ann.rects : [ann.rect];
1978
2224
  const isInk = ann.type === "ink" && ann.path && ann.path.length > 1;
2225
+ const [draftContent, setDraftContent] = (0, import_react4.useState)(ann.content ?? "");
2226
+ const [draftReply, setDraftReply] = (0, import_react4.useState)("");
2227
+ (0, import_react4.useEffect)(() => {
2228
+ setDraftContent(ann.content ?? "");
2229
+ }, [ann.id, ann.content]);
2230
+ (0, import_react4.useEffect)(() => {
2231
+ setDraftReply("");
2232
+ }, [ann.id]);
2233
+ const handleSaveContent = () => {
2234
+ const nextContent = draftContent.trim();
2235
+ const currentContent = (ann.content ?? "").trim();
2236
+ if (nextContent === currentContent) return;
2237
+ onUpdate({
2238
+ content: nextContent,
2239
+ updatedAt: Date.now()
2240
+ });
2241
+ };
2242
+ const handleReplySubmit = () => {
2243
+ const nextReply = draftReply.trim();
2244
+ if (!nextReply) return;
2245
+ onAddReply(nextReply);
2246
+ setDraftReply("");
2247
+ };
1979
2248
  const renderMarkupRects = () => {
1980
2249
  if (!isMarkup) return null;
1981
2250
  return rects.map((r, idx) => {
@@ -2080,6 +2349,7 @@ var AnnotationItem = ({ ann, isSelected, accentColor, onDelete, onSelect }) => {
2080
2349
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2081
2350
  "div",
2082
2351
  {
2352
+ "data-papyrus-annotation-id": ann.id,
2083
2353
  className: `absolute pointer-events-auto transition-all ${isSelected ? "shadow-xl z-30" : "z-10"}`,
2084
2354
  style: {
2085
2355
  left: `${ann.rect.x * 100}%`,
@@ -2098,16 +2368,151 @@ var AnnotationItem = ({ ann, isSelected, accentColor, onDelete, onSelect }) => {
2098
2368
  children: [
2099
2369
  renderMarkupRects(),
2100
2370
  renderInk(),
2101
- 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)(
2102
- "textarea",
2371
+ isText && !isSelected && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2372
+ "button",
2103
2373
  {
2104
- className: "w-full bg-transparent border-none focus:ring-0 p-0 text-gray-800 text-xs font-medium",
2105
- placeholder: "Escreva sua nota...",
2106
- rows: 3,
2107
- defaultValue: ann.content || "",
2108
- autoFocus: true
2374
+ type: "button",
2375
+ className: "absolute -top-2 -right-2 h-6 w-6 rounded-full flex items-center justify-center shadow-lg",
2376
+ style: {
2377
+ background: "var(--papyrus-surface-2-resolved, var(--papyrus-surface-2, #1f2937))",
2378
+ border: "1px solid var(--papyrus-border-resolved, #374151)",
2379
+ color: "var(--papyrus-text-resolved, #e5e7eb)"
2380
+ },
2381
+ title: "Abrir comentario",
2382
+ "aria-label": "Abrir comentario",
2383
+ onClick: (event) => {
2384
+ event.stopPropagation();
2385
+ onSelect();
2386
+ },
2387
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2388
+ "svg",
2389
+ {
2390
+ className: "h-3.5 w-3.5",
2391
+ fill: "none",
2392
+ stroke: "currentColor",
2393
+ viewBox: "0 0 24 24",
2394
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2395
+ "path",
2396
+ {
2397
+ strokeLinecap: "round",
2398
+ strokeLinejoin: "round",
2399
+ strokeWidth: 2,
2400
+ d: "M8 10h.01M12 10h.01M16 10h.01M7 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-4l-4 4v-4z"
2401
+ }
2402
+ )
2403
+ }
2404
+ )
2109
2405
  }
2110
- ) }),
2406
+ ),
2407
+ isText && isSelected && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2408
+ "div",
2409
+ {
2410
+ className: "absolute top-full mt-2 w-72 rounded-xl p-3 z-50",
2411
+ style: {
2412
+ background: "var(--papyrus-popover-resolved, var(--papyrus-popover, #ffffff))",
2413
+ border: "1px solid var(--papyrus-border-resolved, #d1d5db)",
2414
+ color: "var(--papyrus-text-resolved, #111827)",
2415
+ boxShadow: "0 20px 40px var(--papyrus-shadow-resolved, rgba(0, 0, 0, 0.3))"
2416
+ },
2417
+ onClick: (event) => event.stopPropagation(),
2418
+ children: [
2419
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mb-2 flex items-center justify-between", children: [
2420
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-[10px] font-bold uppercase tracking-wider opacity-70", children: ann.type === "comment" ? "Comentario" : "Nota" }),
2421
+ ann.replies?.length ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "text-[10px] opacity-70", children: [
2422
+ ann.replies.length,
2423
+ " resposta",
2424
+ ann.replies.length > 1 ? "s" : ""
2425
+ ] }) : null
2426
+ ] }),
2427
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2428
+ "textarea",
2429
+ {
2430
+ className: "w-full rounded-md border p-2 text-xs font-medium resize-none focus:outline-none",
2431
+ style: {
2432
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #ffffff))",
2433
+ borderColor: "var(--papyrus-border-resolved, #d1d5db)",
2434
+ color: "var(--papyrus-text-resolved, #111827)"
2435
+ },
2436
+ placeholder: "Escreva seu comentario...",
2437
+ rows: 3,
2438
+ value: draftContent,
2439
+ onChange: (event) => setDraftContent(event.target.value),
2440
+ onKeyDown: (event) => {
2441
+ if (event.key === "Enter" && !event.shiftKey) {
2442
+ event.preventDefault();
2443
+ handleSaveContent();
2444
+ }
2445
+ },
2446
+ autoFocus: true
2447
+ }
2448
+ ),
2449
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2450
+ "button",
2451
+ {
2452
+ type: "button",
2453
+ className: "rounded-md px-3 py-1.5 text-[11px] font-semibold text-white",
2454
+ style: { backgroundColor: accentColor },
2455
+ onClick: (event) => {
2456
+ event.stopPropagation();
2457
+ handleSaveContent();
2458
+ },
2459
+ children: (ann.content ?? "").trim() ? "Atualizar" : "Enviar"
2460
+ }
2461
+ ) }),
2462
+ ann.replies && ann.replies.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-3 space-y-2", children: ann.replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2463
+ "div",
2464
+ {
2465
+ className: "rounded-md border p-2",
2466
+ style: {
2467
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #ffffff))",
2468
+ borderColor: "var(--papyrus-border-resolved, #d1d5db)"
2469
+ },
2470
+ children: [
2471
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-xs", children: reply.content }),
2472
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mt-1 text-[10px] opacity-70", children: new Date(reply.createdAt).toLocaleTimeString([], {
2473
+ hour: "2-digit",
2474
+ minute: "2-digit"
2475
+ }) })
2476
+ ]
2477
+ },
2478
+ reply.id
2479
+ )) }) : null,
2480
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mt-3 flex items-center gap-2", children: [
2481
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2482
+ "input",
2483
+ {
2484
+ type: "text",
2485
+ className: "flex-1 rounded-md border px-2 py-1.5 text-xs focus:outline-none",
2486
+ style: {
2487
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #ffffff))",
2488
+ borderColor: "var(--papyrus-border-resolved, #d1d5db)",
2489
+ color: "var(--papyrus-text-resolved, #111827)"
2490
+ },
2491
+ placeholder: "Responder...",
2492
+ value: draftReply,
2493
+ onChange: (event) => setDraftReply(event.target.value),
2494
+ onKeyDown: (event) => {
2495
+ if (event.key === "Enter") {
2496
+ event.preventDefault();
2497
+ handleReplySubmit();
2498
+ }
2499
+ }
2500
+ }
2501
+ ),
2502
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2503
+ "button",
2504
+ {
2505
+ type: "button",
2506
+ className: "rounded-md px-3 py-1.5 text-[11px] font-semibold text-white",
2507
+ style: { backgroundColor: accentColor },
2508
+ onClick: handleReplySubmit,
2509
+ children: "Responder"
2510
+ }
2511
+ )
2512
+ ] })
2513
+ ]
2514
+ }
2515
+ ),
2111
2516
  isSelected && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2112
2517
  "button",
2113
2518
  {
@@ -2149,9 +2554,12 @@ var MIN_ZOOM = 0.2;
2149
2554
  var MAX_ZOOM = 5;
2150
2555
  var WIDTH_SNAP_PX = 4;
2151
2556
  var WIDTH_HYSTERESIS_PX = 6;
2557
+ var HEIGHT_SNAP_PX = 4;
2558
+ var HEIGHT_HYSTERESIS_PX = 6;
2152
2559
  var MOBILE_HEADER_HIDE_DELTA_PX = 28;
2153
2560
  var MOBILE_HEADER_SHOW_DELTA_PX = 16;
2154
2561
  var MOBILE_HEADER_TOP_RESET_PX = 12;
2562
+ var MOBILE_LANDSCAPE_MAX_HEIGHT_PX2 = 500;
2155
2563
  var Viewer = ({ engine, style }) => {
2156
2564
  const viewerState = (0, import_core5.useViewerStore)();
2157
2565
  const {
@@ -2177,6 +2585,7 @@ var Viewer = ({ engine, style }) => {
2177
2585
  const jumpRef = (0, import_react5.useRef)(false);
2178
2586
  const jumpTimerRef = (0, import_react5.useRef)(null);
2179
2587
  const lastWidthRef = (0, import_react5.useRef)(null);
2588
+ const lastHeightRef = (0, import_react5.useRef)(null);
2180
2589
  const lastScrollTopRef = (0, import_react5.useRef)(0);
2181
2590
  const scrollDownAccumulatorRef = (0, import_react5.useRef)(0);
2182
2591
  const scrollUpAccumulatorRef = (0, import_react5.useRef)(0);
@@ -2190,11 +2599,14 @@ var Viewer = ({ engine, style }) => {
2190
2599
  rafId: null
2191
2600
  });
2192
2601
  const [availableWidth, setAvailableWidth] = (0, import_react5.useState)(null);
2602
+ const [availableHeight, setAvailableHeight] = (0, import_react5.useState)(null);
2193
2603
  const [basePageSize, setBasePageSize] = (0, import_react5.useState)(null);
2194
2604
  const [pageSizes, setPageSizes] = (0, import_react5.useState)({});
2195
2605
  const [colorPickerOpen, setColorPickerOpen] = (0, import_react5.useState)(false);
2196
- const isCompact = availableWidth !== null && availableWidth < 820;
2197
- const isMobileViewport = availableWidth !== null && availableWidth < 640;
2606
+ const isLandscape = availableWidth !== null && availableHeight !== null && availableWidth > availableHeight;
2607
+ const isLandscapeShort = isLandscape && availableHeight !== null && availableHeight <= MOBILE_LANDSCAPE_MAX_HEIGHT_PX2;
2608
+ const isCompact = availableWidth !== null && (availableWidth < 820 || isLandscapeShort);
2609
+ const isMobileViewport = availableWidth !== null && (availableWidth < 640 || isLandscapeShort);
2198
2610
  const paddingY = isCompact ? "py-10" : "py-16";
2199
2611
  const toolDockPosition = isCompact ? "bottom-4" : "bottom-8";
2200
2612
  const colorPalette = [
@@ -2245,21 +2657,32 @@ var Viewer = ({ engine, style }) => {
2245
2657
  const measurementTarget = viewerElement.parentElement ?? viewerElement;
2246
2658
  let rafId = null;
2247
2659
  const normalizeWidth = (rawWidth) => Math.max(0, Math.floor(rawWidth / WIDTH_SNAP_PX) * WIDTH_SNAP_PX);
2248
- const updateWidth = () => {
2660
+ const normalizeHeight = (rawHeight) => Math.max(0, Math.floor(rawHeight / HEIGHT_SNAP_PX) * HEIGHT_SNAP_PX);
2661
+ const updateSize = () => {
2249
2662
  const rawWidth = measurementTarget.getBoundingClientRect?.().width ?? measurementTarget.clientWidth ?? measurementTarget.offsetWidth;
2663
+ const rawHeight = measurementTarget.getBoundingClientRect?.().height ?? measurementTarget.clientHeight ?? measurementTarget.offsetHeight;
2250
2664
  const nextWidth = normalizeWidth(rawWidth);
2251
- if (nextWidth <= 0) return;
2665
+ const nextHeight = normalizeHeight(rawHeight);
2666
+ if (nextWidth <= 0 || nextHeight <= 0) return;
2252
2667
  const previousWidth = lastWidthRef.current;
2253
- if (previousWidth != null && Math.abs(nextWidth - previousWidth) < WIDTH_HYSTERESIS_PX)
2254
- return;
2255
- lastWidthRef.current = nextWidth;
2256
- setAvailableWidth(nextWidth);
2668
+ const previousHeight = lastHeightRef.current;
2669
+ const widthChanged = previousWidth == null || Math.abs(nextWidth - previousWidth) >= WIDTH_HYSTERESIS_PX;
2670
+ const heightChanged = previousHeight == null || Math.abs(nextHeight - previousHeight) >= HEIGHT_HYSTERESIS_PX;
2671
+ if (!widthChanged && !heightChanged) return;
2672
+ if (widthChanged) {
2673
+ lastWidthRef.current = nextWidth;
2674
+ setAvailableWidth(nextWidth);
2675
+ }
2676
+ if (heightChanged) {
2677
+ lastHeightRef.current = nextHeight;
2678
+ setAvailableHeight(nextHeight);
2679
+ }
2257
2680
  };
2258
2681
  const scheduleWidthUpdate = () => {
2259
2682
  if (rafId != null) cancelAnimationFrame(rafId);
2260
2683
  rafId = requestAnimationFrame(() => {
2261
2684
  rafId = null;
2262
- updateWidth();
2685
+ updateSize();
2263
2686
  });
2264
2687
  };
2265
2688
  scheduleWidthUpdate();