@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.mjs CHANGED
@@ -3,6 +3,8 @@ import { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { useViewerStore } from "@papyrus-sdk/core";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
+ var MOBILE_LANDSCAPE_MAX_HEIGHT_PX = 500;
7
+ var MOBILE_VIEWPORT_QUERY = `(max-width: 639px), (orientation: landscape) and (max-height: ${MOBILE_LANDSCAPE_MAX_HEIGHT_PX}px)`;
6
8
  var Topbar = ({
7
9
  engine,
8
10
  showBrand = false,
@@ -57,7 +59,7 @@ var Topbar = ({
57
59
  }, [hasMobileMenu]);
58
60
  useEffect(() => {
59
61
  if (!canUseDOM || typeof window.matchMedia !== "function") return;
60
- const mediaQuery = window.matchMedia("(max-width: 639px)");
62
+ const mediaQuery = window.matchMedia(MOBILE_VIEWPORT_QUERY);
61
63
  const updateViewport = () => setIsMobileViewport(mediaQuery.matches);
62
64
  updateViewport();
63
65
  if (typeof mediaQuery.addEventListener === "function") {
@@ -1102,10 +1104,15 @@ var SidebarRight = ({ engine, style }) => {
1102
1104
  setDocumentState,
1103
1105
  triggerScrollToPage,
1104
1106
  annotations,
1105
- accentColor
1107
+ accentColor,
1108
+ updateAnnotation,
1109
+ addAnnotationReply,
1110
+ setSelectedAnnotation
1106
1111
  } = useViewerStore3();
1107
1112
  const [query, setQuery] = useState3("");
1108
1113
  const [isSearching, setIsSearching] = useState3(false);
1114
+ const [contentDrafts, setContentDrafts] = useState3({});
1115
+ const [replyDrafts, setReplyDrafts] = useState3({});
1109
1116
  const searchService = new SearchService(engine);
1110
1117
  const isDark = uiTheme === "dark";
1111
1118
  const accentSoft = withAlpha2(accentColor, 0.12);
@@ -1121,6 +1128,41 @@ var SidebarRight = ({ engine, style }) => {
1121
1128
  setSearch(query, results);
1122
1129
  setIsSearching(false);
1123
1130
  };
1131
+ const jumpToAnnotation = (annotation) => {
1132
+ const page = annotation.pageIndex + 1;
1133
+ engine.goToPage(page);
1134
+ setDocumentState({ currentPage: page });
1135
+ setSelectedAnnotation(annotation.id);
1136
+ triggerScrollToPage(annotation.pageIndex);
1137
+ };
1138
+ const getContentDraft = (annotation) => {
1139
+ if (Object.prototype.hasOwnProperty.call(contentDrafts, annotation.id)) {
1140
+ return contentDrafts[annotation.id];
1141
+ }
1142
+ return annotation.content ?? "";
1143
+ };
1144
+ const updateContentDraft = (annotationId, nextValue) => {
1145
+ setContentDrafts((prev) => ({ ...prev, [annotationId]: nextValue }));
1146
+ };
1147
+ const submitContent = (annotation) => {
1148
+ const nextContent = getContentDraft(annotation).trim();
1149
+ const currentContent = (annotation.content ?? "").trim();
1150
+ if (nextContent === currentContent) return;
1151
+ updateAnnotation(annotation.id, {
1152
+ content: nextContent,
1153
+ updatedAt: Date.now()
1154
+ });
1155
+ };
1156
+ const getReplyDraft = (annotationId) => replyDrafts[annotationId] ?? "";
1157
+ const updateReplyDraft = (annotationId, nextValue) => {
1158
+ setReplyDrafts((prev) => ({ ...prev, [annotationId]: nextValue }));
1159
+ };
1160
+ const submitReply = (annotationId) => {
1161
+ const nextReply = getReplyDraft(annotationId).trim();
1162
+ if (!nextReply) return;
1163
+ addAnnotationReply(annotationId, nextReply);
1164
+ setReplyDrafts((prev) => ({ ...prev, [annotationId]: "" }));
1165
+ };
1124
1166
  if (!sidebarRightOpen) return null;
1125
1167
  return /* @__PURE__ */ jsxs3(
1126
1168
  "div",
@@ -1322,48 +1364,152 @@ var SidebarRight = ({ engine, style }) => {
1322
1364
  }
1323
1365
  ) }),
1324
1366
  /* @__PURE__ */ jsx3("p", { className: "text-[10px] font-bold text-gray-400 uppercase tracking-widest", children: "Sem anota\xE7\xF5es" })
1325
- ] }) : annotations.map((ann) => /* @__PURE__ */ jsxs3(
1326
- "div",
1327
- {
1328
- 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"}`,
1329
- children: [
1330
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between mb-3", children: [
1331
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center space-x-2", children: [
1367
+ ] }) : annotations.slice().sort(
1368
+ (a, b) => (b.updatedAt ?? b.createdAt) - (a.updatedAt ?? a.createdAt)
1369
+ ).map((ann) => {
1370
+ const isCommentThread = ann.type === "comment" || ann.type === "text";
1371
+ const replies = ann.replies ?? [];
1372
+ const contentDraft = getContentDraft(ann);
1373
+ const replyDraft = getReplyDraft(ann.id);
1374
+ const hasExistingContent = Boolean((ann.content ?? "").trim());
1375
+ return /* @__PURE__ */ jsxs3(
1376
+ "div",
1377
+ {
1378
+ className: "rounded-xl border p-4 transition-colors",
1379
+ style: {
1380
+ background: "var(--papyrus-surface-2-resolved, var(--papyrus-surface-2, #1f2937))",
1381
+ borderColor: "var(--papyrus-border-resolved, var(--papyrus-border, #374151))",
1382
+ color: "var(--papyrus-text-resolved, var(--papyrus-text, #e5e7eb))"
1383
+ },
1384
+ children: [
1385
+ /* @__PURE__ */ jsxs3("div", { className: "mb-3 flex items-center justify-between", children: [
1386
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center space-x-2", children: [
1387
+ /* @__PURE__ */ jsx3(
1388
+ "div",
1389
+ {
1390
+ className: "h-2.5 w-2.5 rounded-full",
1391
+ style: { backgroundColor: ann.color }
1392
+ }
1393
+ ),
1394
+ /* @__PURE__ */ jsxs3(
1395
+ "span",
1396
+ {
1397
+ className: "text-[10px] font-black uppercase tracking-wide",
1398
+ style: { color: accentColor },
1399
+ children: [
1400
+ "P",
1401
+ ann.pageIndex + 1
1402
+ ]
1403
+ }
1404
+ ),
1405
+ /* @__PURE__ */ jsx3("span", { className: "text-[10px] font-semibold opacity-70 uppercase tracking-wide", children: ann.type })
1406
+ ] }),
1332
1407
  /* @__PURE__ */ jsx3(
1333
- "div",
1408
+ "button",
1409
+ {
1410
+ type: "button",
1411
+ className: "rounded-md border px-2 py-1 text-[10px] font-semibold uppercase tracking-wide",
1412
+ onClick: () => jumpToAnnotation(ann),
1413
+ style: {
1414
+ borderColor: accentColor,
1415
+ color: accentColor
1416
+ },
1417
+ children: "Ir para pagina"
1418
+ }
1419
+ )
1420
+ ] }),
1421
+ isCommentThread ? /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
1422
+ /* @__PURE__ */ jsx3(
1423
+ "textarea",
1334
1424
  {
1335
- className: "w-2.5 h-2.5 rounded-full",
1336
- style: { backgroundColor: ann.color }
1425
+ className: "w-full resize-none rounded-md border p-2 text-xs focus:outline-none",
1426
+ style: {
1427
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #111827))",
1428
+ borderColor: "var(--papyrus-border-resolved, var(--papyrus-border, #374151))",
1429
+ color: "var(--papyrus-text-resolved, var(--papyrus-text, #e5e7eb))"
1430
+ },
1431
+ rows: 3,
1432
+ placeholder: "Escreva seu comentario...",
1433
+ value: contentDraft,
1434
+ onChange: (event) => updateContentDraft(ann.id, event.target.value),
1435
+ onKeyDown: (event) => {
1436
+ if (event.key === "Enter" && !event.shiftKey) {
1437
+ event.preventDefault();
1438
+ submitContent(ann);
1439
+ }
1440
+ }
1337
1441
  }
1338
1442
  ),
1339
- /* @__PURE__ */ jsxs3(
1340
- "span",
1443
+ /* @__PURE__ */ jsx3("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx3(
1444
+ "button",
1341
1445
  {
1342
- className: "text-[10px] font-black",
1343
- style: { color: accentColor },
1344
- children: [
1345
- "P",
1346
- ann.pageIndex + 1
1347
- ]
1446
+ type: "button",
1447
+ className: "rounded-md px-3 py-1.5 text-[11px] font-semibold text-white",
1448
+ style: { backgroundColor: accentColor },
1449
+ onClick: () => submitContent(ann),
1450
+ children: hasExistingContent ? "Atualizar comentario" : "Enviar comentario"
1451
+ }
1452
+ ) })
1453
+ ] }) : null,
1454
+ replies.length > 0 ? /* @__PURE__ */ jsx3("div", { className: "mt-3 space-y-2", children: replies.map((reply) => /* @__PURE__ */ jsxs3(
1455
+ "div",
1456
+ {
1457
+ className: "rounded-md border p-2",
1458
+ style: {
1459
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #111827))",
1460
+ borderColor: "var(--papyrus-border-resolved, var(--papyrus-border, #374151))"
1461
+ },
1462
+ children: [
1463
+ /* @__PURE__ */ jsx3("p", { className: "text-xs leading-relaxed", children: reply.content }),
1464
+ /* @__PURE__ */ jsx3("p", { className: "mt-1 text-[10px] opacity-70", children: new Date(reply.createdAt).toLocaleTimeString(
1465
+ [],
1466
+ {
1467
+ hour: "2-digit",
1468
+ minute: "2-digit"
1469
+ }
1470
+ ) })
1471
+ ]
1472
+ },
1473
+ reply.id
1474
+ )) }) : null,
1475
+ isCommentThread ? /* @__PURE__ */ jsxs3("div", { className: "mt-3 flex items-center gap-2", children: [
1476
+ /* @__PURE__ */ jsx3(
1477
+ "input",
1478
+ {
1479
+ type: "text",
1480
+ className: "flex-1 rounded-md border px-2 py-1.5 text-xs focus:outline-none",
1481
+ style: {
1482
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #111827))",
1483
+ borderColor: "var(--papyrus-border-resolved, var(--papyrus-border, #374151))",
1484
+ color: "var(--papyrus-text-resolved, var(--papyrus-text, #e5e7eb))"
1485
+ },
1486
+ value: replyDraft,
1487
+ placeholder: "Responder...",
1488
+ onChange: (event) => updateReplyDraft(ann.id, event.target.value),
1489
+ onKeyDown: (event) => {
1490
+ if (event.key === "Enter") {
1491
+ event.preventDefault();
1492
+ submitReply(ann.id);
1493
+ }
1494
+ }
1495
+ }
1496
+ ),
1497
+ /* @__PURE__ */ jsx3(
1498
+ "button",
1499
+ {
1500
+ type: "button",
1501
+ className: "rounded-md px-3 py-1.5 text-[11px] font-semibold text-white",
1502
+ style: { backgroundColor: accentColor },
1503
+ onClick: () => submitReply(ann.id),
1504
+ children: "Responder"
1348
1505
  }
1349
1506
  )
1350
- ] }),
1351
- /* @__PURE__ */ jsx3("span", { className: "text-[9px] text-gray-400 font-bold", children: new Date(ann.createdAt).toLocaleTimeString([], {
1352
- hour: "2-digit",
1353
- minute: "2-digit"
1354
- }) })
1355
- ] }),
1356
- /* @__PURE__ */ jsx3(
1357
- "p",
1358
- {
1359
- className: `text-[11px] font-bold uppercase tracking-tight ${isDark ? "text-gray-200" : "text-gray-700"}`,
1360
- children: ann.type
1361
- }
1362
- )
1363
- ]
1364
- },
1365
- ann.id
1366
- ))
1507
+ ] }) : null
1508
+ ]
1509
+ },
1510
+ ann.id
1511
+ );
1512
+ })
1367
1513
  ] }) })
1368
1514
  ]
1369
1515
  }
@@ -1393,6 +1539,10 @@ var PageRenderer = ({
1393
1539
  const canvasRef = useRef3(null);
1394
1540
  const htmlLayerRef = useRef3(null);
1395
1541
  const textLayerRef = useRef3(null);
1542
+ const skipNextAnnotationSelectRef = useRef3(false);
1543
+ const skipSelectResetTimerRef = useRef3(
1544
+ null
1545
+ );
1396
1546
  const [loading, setLoading] = useState4(true);
1397
1547
  const [pageSize, setPageSize] = useState4(null);
1398
1548
  const [isDragging, setIsDragging] = useState4(false);
@@ -1410,6 +1560,8 @@ var PageRenderer = ({
1410
1560
  setDocumentState,
1411
1561
  annotations,
1412
1562
  addAnnotation,
1563
+ addAnnotationReply,
1564
+ updateAnnotation,
1413
1565
  activeTool,
1414
1566
  removeAnnotation,
1415
1567
  selectedAnnotationId,
@@ -1433,6 +1585,24 @@ var PageRenderer = ({
1433
1585
  () => Boolean(searchQuery?.trim()) && searchResults.some((res) => res.pageIndex === pageIndex),
1434
1586
  [searchQuery, searchResults, pageIndex]
1435
1587
  );
1588
+ const suppressNextAnnotationSelect = () => {
1589
+ skipNextAnnotationSelectRef.current = true;
1590
+ if (skipSelectResetTimerRef.current) {
1591
+ clearTimeout(skipSelectResetTimerRef.current);
1592
+ }
1593
+ skipSelectResetTimerRef.current = setTimeout(() => {
1594
+ skipNextAnnotationSelectRef.current = false;
1595
+ skipSelectResetTimerRef.current = null;
1596
+ }, 0);
1597
+ };
1598
+ useEffect3(
1599
+ () => () => {
1600
+ if (skipSelectResetTimerRef.current) {
1601
+ clearTimeout(skipSelectResetTimerRef.current);
1602
+ }
1603
+ },
1604
+ []
1605
+ );
1436
1606
  useEffect3(() => {
1437
1607
  let active = true;
1438
1608
  const loadSize = async () => {
@@ -1602,13 +1772,27 @@ var PageRenderer = ({
1602
1772
  activeSearchIndex,
1603
1773
  textLayerVersion
1604
1774
  ]);
1605
- const handleMouseDown = (e) => {
1775
+ const getTouchPoint = (event) => {
1776
+ const touch = event.touches[0] ?? event.changedTouches[0];
1777
+ if (!touch) return null;
1778
+ return { x: touch.clientX, y: touch.clientY };
1779
+ };
1780
+ const handlePointerDown = (clientX, clientY, target) => {
1781
+ const clickedInsideAnnotation = Boolean(
1782
+ target?.closest("[data-papyrus-annotation-id]")
1783
+ );
1784
+ const clickedSelectionMenu = Boolean(
1785
+ target?.closest("[data-papyrus-selection-menu]")
1786
+ );
1787
+ if (!clickedInsideAnnotation && !clickedSelectionMenu) {
1788
+ setSelectedAnnotation(null);
1789
+ }
1606
1790
  setSelectionMenu(null);
1607
1791
  if (activeTool === "ink") {
1608
1792
  const rect2 = containerRef.current?.getBoundingClientRect();
1609
1793
  if (!rect2) return;
1610
- const x2 = (e.clientX - rect2.left) / rect2.width;
1611
- const y2 = (e.clientY - rect2.top) / rect2.height;
1794
+ const x2 = (clientX - rect2.left) / rect2.width;
1795
+ const y2 = (clientY - rect2.top) / rect2.height;
1612
1796
  setIsInkDrawing(true);
1613
1797
  setInkPoints([{ x: x2, y: y2 }]);
1614
1798
  return;
@@ -1617,25 +1801,25 @@ var PageRenderer = ({
1617
1801
  const rect = containerRef.current?.getBoundingClientRect();
1618
1802
  if (!rect) return;
1619
1803
  setIsDragging(true);
1620
- const x = e.clientX - rect.left;
1621
- const y = e.clientY - rect.top;
1804
+ const x = clientX - rect.left;
1805
+ const y = clientY - rect.top;
1622
1806
  setStartPos({ x, y });
1623
1807
  setCurrentRect({ x, y, w: 0, h: 0 });
1624
1808
  };
1625
- const handleMouseMove = (e) => {
1809
+ const handlePointerMove = (clientX, clientY) => {
1626
1810
  if (isInkDrawing) {
1627
1811
  const rect2 = containerRef.current?.getBoundingClientRect();
1628
1812
  if (!rect2) return;
1629
- const x = (e.clientX - rect2.left) / rect2.width;
1630
- const y = (e.clientY - rect2.top) / rect2.height;
1813
+ const x = (clientX - rect2.left) / rect2.width;
1814
+ const y = (clientY - rect2.top) / rect2.height;
1631
1815
  setInkPoints((prev) => [...prev, { x, y }]);
1632
1816
  return;
1633
1817
  }
1634
1818
  if (!isDragging) return;
1635
1819
  const rect = containerRef.current?.getBoundingClientRect();
1636
1820
  if (!rect) return;
1637
- const currentX = e.clientX - rect.left;
1638
- const currentY = e.clientY - rect.top;
1821
+ const currentX = clientX - rect.left;
1822
+ const currentY = clientY - rect.top;
1639
1823
  setCurrentRect({
1640
1824
  x: Math.min(startPos.x, currentX),
1641
1825
  y: Math.min(startPos.y, currentY),
@@ -1643,7 +1827,7 @@ var PageRenderer = ({
1643
1827
  h: Math.abs(currentY - startPos.y)
1644
1828
  });
1645
1829
  };
1646
- const handleMouseUp = (e) => {
1830
+ const handlePointerUp = () => {
1647
1831
  if (isInkDrawing) {
1648
1832
  setIsInkDrawing(false);
1649
1833
  if (inkPoints.length > 1) {
@@ -1659,6 +1843,7 @@ var PageRenderer = ({
1659
1843
  x: Math.max(0, Math.min(1, p.x)),
1660
1844
  y: Math.max(0, Math.min(1, p.y))
1661
1845
  }));
1846
+ suppressNextAnnotationSelect();
1662
1847
  addAnnotation({
1663
1848
  id: Math.random().toString(36).substr(2, 9),
1664
1849
  pageIndex,
@@ -1739,6 +1924,7 @@ var PageRenderer = ({
1739
1924
  height: Math.max(...ye) - Math.min(...ys)
1740
1925
  };
1741
1926
  if (textMarkupTools.has(activeTool)) {
1927
+ suppressNextAnnotationSelect();
1742
1928
  addAnnotation({
1743
1929
  id: Math.random().toString(36).substr(2, 9),
1744
1930
  pageIndex,
@@ -1774,6 +1960,9 @@ var PageRenderer = ({
1774
1960
  if (currentRect.w > 5 && currentRect.h > 5) {
1775
1961
  const rect = containerRef.current?.getBoundingClientRect();
1776
1962
  if (rect) {
1963
+ if (activeTool !== "text" && activeTool !== "comment") {
1964
+ suppressNextAnnotationSelect();
1965
+ }
1777
1966
  addAnnotation({
1778
1967
  id: Math.random().toString(36).substr(2, 9),
1779
1968
  pageIndex,
@@ -1803,6 +1992,37 @@ var PageRenderer = ({
1803
1992
  }
1804
1993
  }
1805
1994
  };
1995
+ const handleMouseDown = (e) => {
1996
+ handlePointerDown(e.clientX, e.clientY, e.target);
1997
+ };
1998
+ const handleMouseMove = (e) => {
1999
+ handlePointerMove(e.clientX, e.clientY);
2000
+ };
2001
+ const handleMouseUp = () => {
2002
+ handlePointerUp();
2003
+ };
2004
+ const handleTouchStart = (event) => {
2005
+ if (event.touches.length > 1) return;
2006
+ const point = getTouchPoint(event);
2007
+ if (!point) return;
2008
+ handlePointerDown(point.x, point.y, event.target);
2009
+ if ((activeTool === "ink" || !canSelectText) && event.cancelable) {
2010
+ event.preventDefault();
2011
+ }
2012
+ };
2013
+ const handleTouchMove = (event) => {
2014
+ if (event.touches.length > 1) return;
2015
+ const point = getTouchPoint(event);
2016
+ if (!point) return;
2017
+ handlePointerMove(point.x, point.y);
2018
+ if ((isInkDrawing || isDragging) && event.cancelable) {
2019
+ event.preventDefault();
2020
+ }
2021
+ };
2022
+ const handleTouchEnd = (event) => {
2023
+ if (event.touches.length > 0) return;
2024
+ handlePointerUp();
2025
+ };
1806
2026
  const getPageFilter = () => {
1807
2027
  switch (pageTheme) {
1808
2028
  case "sepia":
@@ -1820,10 +2040,18 @@ var PageRenderer = ({
1820
2040
  {
1821
2041
  ref: containerRef,
1822
2042
  className: `relative inline-block shadow-2xl bg-white mb-10 ${canSelectText ? "" : "no-select cursor-crosshair"}`,
1823
- style: { scrollMarginTop: "20px", minHeight: "100px" },
2043
+ style: {
2044
+ scrollMarginTop: "20px",
2045
+ minHeight: "100px",
2046
+ touchAction: activeTool === "ink" || activeTool === "text" || activeTool === "comment" ? "none" : "auto"
2047
+ },
1824
2048
  onMouseDown: handleMouseDown,
1825
2049
  onMouseMove: handleMouseMove,
1826
2050
  onMouseUp: handleMouseUp,
2051
+ onTouchStart: handleTouchStart,
2052
+ onTouchMove: handleTouchMove,
2053
+ onTouchEnd: handleTouchEnd,
2054
+ onTouchCancel: handleTouchEnd,
1827
2055
  children: [
1828
2056
  loading && /* @__PURE__ */ jsx4("div", { className: "absolute inset-0 bg-gray-50 flex items-center justify-center z-10 animate-pulse", children: /* @__PURE__ */ jsx4("span", { className: "text-[10px] font-black text-gray-400 uppercase tracking-widest", children: "Sincronizando..." }) }),
1829
2057
  /* @__PURE__ */ jsx4(
@@ -1897,6 +2125,7 @@ var PageRenderer = ({
1897
2125
  selectionMenu && /* @__PURE__ */ jsx4(
1898
2126
  "div",
1899
2127
  {
2128
+ "data-papyrus-selection-menu": "true",
1900
2129
  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",
1901
2130
  style: { left: selectionMenu.anchor.x, top: selectionMenu.anchor.y },
1902
2131
  children: [
@@ -1909,6 +2138,7 @@ var PageRenderer = ({
1909
2138
  {
1910
2139
  className: "text-[10px] font-bold px-2 py-1 rounded-full hover:bg-gray-100",
1911
2140
  onClick: () => {
2141
+ suppressNextAnnotationSelect();
1912
2142
  addAnnotation({
1913
2143
  id: Math.random().toString(36).substr(2, 9),
1914
2144
  pageIndex,
@@ -1935,7 +2165,15 @@ var PageRenderer = ({
1935
2165
  isSelected: selectedAnnotationId === ann.id,
1936
2166
  accentColor,
1937
2167
  onDelete: () => removeAnnotation(ann.id),
1938
- onSelect: () => setSelectedAnnotation(ann.id)
2168
+ onSelect: () => {
2169
+ if (skipNextAnnotationSelectRef.current) {
2170
+ skipNextAnnotationSelectRef.current = false;
2171
+ return;
2172
+ }
2173
+ setSelectedAnnotation(ann.id);
2174
+ },
2175
+ onUpdate: (updates) => updateAnnotation(ann.id, updates),
2176
+ onAddReply: (content) => addAnnotationReply(ann.id, content)
1939
2177
  },
1940
2178
  ann.id
1941
2179
  )) })
@@ -1943,12 +2181,43 @@ var PageRenderer = ({
1943
2181
  }
1944
2182
  );
1945
2183
  };
1946
- var AnnotationItem = ({ ann, isSelected, accentColor, onDelete, onSelect }) => {
2184
+ var AnnotationItem = ({
2185
+ ann,
2186
+ isSelected,
2187
+ accentColor,
2188
+ onDelete,
2189
+ onSelect,
2190
+ onUpdate,
2191
+ onAddReply
2192
+ }) => {
1947
2193
  const isText = ann.type === "text" || ann.type === "comment";
1948
2194
  const isHighlight = ann.type === "highlight";
1949
2195
  const isMarkup = ann.type === "highlight" || ann.type === "underline" || ann.type === "squiggly" || ann.type === "strikeout";
1950
2196
  const rects = ann.rects && ann.rects.length > 0 ? ann.rects : [ann.rect];
1951
2197
  const isInk = ann.type === "ink" && ann.path && ann.path.length > 1;
2198
+ const [draftContent, setDraftContent] = useState4(ann.content ?? "");
2199
+ const [draftReply, setDraftReply] = useState4("");
2200
+ useEffect3(() => {
2201
+ setDraftContent(ann.content ?? "");
2202
+ }, [ann.id, ann.content]);
2203
+ useEffect3(() => {
2204
+ setDraftReply("");
2205
+ }, [ann.id]);
2206
+ const handleSaveContent = () => {
2207
+ const nextContent = draftContent.trim();
2208
+ const currentContent = (ann.content ?? "").trim();
2209
+ if (nextContent === currentContent) return;
2210
+ onUpdate({
2211
+ content: nextContent,
2212
+ updatedAt: Date.now()
2213
+ });
2214
+ };
2215
+ const handleReplySubmit = () => {
2216
+ const nextReply = draftReply.trim();
2217
+ if (!nextReply) return;
2218
+ onAddReply(nextReply);
2219
+ setDraftReply("");
2220
+ };
1952
2221
  const renderMarkupRects = () => {
1953
2222
  if (!isMarkup) return null;
1954
2223
  return rects.map((r, idx) => {
@@ -2053,6 +2322,7 @@ var AnnotationItem = ({ ann, isSelected, accentColor, onDelete, onSelect }) => {
2053
2322
  return /* @__PURE__ */ jsxs4(
2054
2323
  "div",
2055
2324
  {
2325
+ "data-papyrus-annotation-id": ann.id,
2056
2326
  className: `absolute pointer-events-auto transition-all ${isSelected ? "shadow-xl z-30" : "z-10"}`,
2057
2327
  style: {
2058
2328
  left: `${ann.rect.x * 100}%`,
@@ -2071,16 +2341,151 @@ var AnnotationItem = ({ ann, isSelected, accentColor, onDelete, onSelect }) => {
2071
2341
  children: [
2072
2342
  renderMarkupRects(),
2073
2343
  renderInk(),
2074
- isText && isSelected && /* @__PURE__ */ jsx4("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__ */ jsx4(
2075
- "textarea",
2344
+ isText && !isSelected && /* @__PURE__ */ jsx4(
2345
+ "button",
2076
2346
  {
2077
- className: "w-full bg-transparent border-none focus:ring-0 p-0 text-gray-800 text-xs font-medium",
2078
- placeholder: "Escreva sua nota...",
2079
- rows: 3,
2080
- defaultValue: ann.content || "",
2081
- autoFocus: true
2347
+ type: "button",
2348
+ className: "absolute -top-2 -right-2 h-6 w-6 rounded-full flex items-center justify-center shadow-lg",
2349
+ style: {
2350
+ background: "var(--papyrus-surface-2-resolved, var(--papyrus-surface-2, #1f2937))",
2351
+ border: "1px solid var(--papyrus-border-resolved, #374151)",
2352
+ color: "var(--papyrus-text-resolved, #e5e7eb)"
2353
+ },
2354
+ title: "Abrir comentario",
2355
+ "aria-label": "Abrir comentario",
2356
+ onClick: (event) => {
2357
+ event.stopPropagation();
2358
+ onSelect();
2359
+ },
2360
+ children: /* @__PURE__ */ jsx4(
2361
+ "svg",
2362
+ {
2363
+ className: "h-3.5 w-3.5",
2364
+ fill: "none",
2365
+ stroke: "currentColor",
2366
+ viewBox: "0 0 24 24",
2367
+ children: /* @__PURE__ */ jsx4(
2368
+ "path",
2369
+ {
2370
+ strokeLinecap: "round",
2371
+ strokeLinejoin: "round",
2372
+ strokeWidth: 2,
2373
+ 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"
2374
+ }
2375
+ )
2376
+ }
2377
+ )
2082
2378
  }
2083
- ) }),
2379
+ ),
2380
+ isText && isSelected && /* @__PURE__ */ jsxs4(
2381
+ "div",
2382
+ {
2383
+ className: "absolute top-full mt-2 w-72 rounded-xl p-3 z-50",
2384
+ style: {
2385
+ background: "var(--papyrus-popover-resolved, var(--papyrus-popover, #ffffff))",
2386
+ border: "1px solid var(--papyrus-border-resolved, #d1d5db)",
2387
+ color: "var(--papyrus-text-resolved, #111827)",
2388
+ boxShadow: "0 20px 40px var(--papyrus-shadow-resolved, rgba(0, 0, 0, 0.3))"
2389
+ },
2390
+ onClick: (event) => event.stopPropagation(),
2391
+ children: [
2392
+ /* @__PURE__ */ jsxs4("div", { className: "mb-2 flex items-center justify-between", children: [
2393
+ /* @__PURE__ */ jsx4("span", { className: "text-[10px] font-bold uppercase tracking-wider opacity-70", children: ann.type === "comment" ? "Comentario" : "Nota" }),
2394
+ ann.replies?.length ? /* @__PURE__ */ jsxs4("span", { className: "text-[10px] opacity-70", children: [
2395
+ ann.replies.length,
2396
+ " resposta",
2397
+ ann.replies.length > 1 ? "s" : ""
2398
+ ] }) : null
2399
+ ] }),
2400
+ /* @__PURE__ */ jsx4(
2401
+ "textarea",
2402
+ {
2403
+ className: "w-full rounded-md border p-2 text-xs font-medium resize-none focus:outline-none",
2404
+ style: {
2405
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #ffffff))",
2406
+ borderColor: "var(--papyrus-border-resolved, #d1d5db)",
2407
+ color: "var(--papyrus-text-resolved, #111827)"
2408
+ },
2409
+ placeholder: "Escreva seu comentario...",
2410
+ rows: 3,
2411
+ value: draftContent,
2412
+ onChange: (event) => setDraftContent(event.target.value),
2413
+ onKeyDown: (event) => {
2414
+ if (event.key === "Enter" && !event.shiftKey) {
2415
+ event.preventDefault();
2416
+ handleSaveContent();
2417
+ }
2418
+ },
2419
+ autoFocus: true
2420
+ }
2421
+ ),
2422
+ /* @__PURE__ */ jsx4("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ jsx4(
2423
+ "button",
2424
+ {
2425
+ type: "button",
2426
+ className: "rounded-md px-3 py-1.5 text-[11px] font-semibold text-white",
2427
+ style: { backgroundColor: accentColor },
2428
+ onClick: (event) => {
2429
+ event.stopPropagation();
2430
+ handleSaveContent();
2431
+ },
2432
+ children: (ann.content ?? "").trim() ? "Atualizar" : "Enviar"
2433
+ }
2434
+ ) }),
2435
+ ann.replies && ann.replies.length > 0 ? /* @__PURE__ */ jsx4("div", { className: "mt-3 space-y-2", children: ann.replies.map((reply) => /* @__PURE__ */ jsxs4(
2436
+ "div",
2437
+ {
2438
+ className: "rounded-md border p-2",
2439
+ style: {
2440
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #ffffff))",
2441
+ borderColor: "var(--papyrus-border-resolved, #d1d5db)"
2442
+ },
2443
+ children: [
2444
+ /* @__PURE__ */ jsx4("p", { className: "text-xs", children: reply.content }),
2445
+ /* @__PURE__ */ jsx4("p", { className: "mt-1 text-[10px] opacity-70", children: new Date(reply.createdAt).toLocaleTimeString([], {
2446
+ hour: "2-digit",
2447
+ minute: "2-digit"
2448
+ }) })
2449
+ ]
2450
+ },
2451
+ reply.id
2452
+ )) }) : null,
2453
+ /* @__PURE__ */ jsxs4("div", { className: "mt-3 flex items-center gap-2", children: [
2454
+ /* @__PURE__ */ jsx4(
2455
+ "input",
2456
+ {
2457
+ type: "text",
2458
+ className: "flex-1 rounded-md border px-2 py-1.5 text-xs focus:outline-none",
2459
+ style: {
2460
+ background: "var(--papyrus-surface-resolved, var(--papyrus-surface, #ffffff))",
2461
+ borderColor: "var(--papyrus-border-resolved, #d1d5db)",
2462
+ color: "var(--papyrus-text-resolved, #111827)"
2463
+ },
2464
+ placeholder: "Responder...",
2465
+ value: draftReply,
2466
+ onChange: (event) => setDraftReply(event.target.value),
2467
+ onKeyDown: (event) => {
2468
+ if (event.key === "Enter") {
2469
+ event.preventDefault();
2470
+ handleReplySubmit();
2471
+ }
2472
+ }
2473
+ }
2474
+ ),
2475
+ /* @__PURE__ */ jsx4(
2476
+ "button",
2477
+ {
2478
+ type: "button",
2479
+ className: "rounded-md px-3 py-1.5 text-[11px] font-semibold text-white",
2480
+ style: { backgroundColor: accentColor },
2481
+ onClick: handleReplySubmit,
2482
+ children: "Responder"
2483
+ }
2484
+ )
2485
+ ] })
2486
+ ]
2487
+ }
2488
+ ),
2084
2489
  isSelected && /* @__PURE__ */ jsx4(
2085
2490
  "button",
2086
2491
  {
@@ -2122,9 +2527,12 @@ var MIN_ZOOM = 0.2;
2122
2527
  var MAX_ZOOM = 5;
2123
2528
  var WIDTH_SNAP_PX = 4;
2124
2529
  var WIDTH_HYSTERESIS_PX = 6;
2530
+ var HEIGHT_SNAP_PX = 4;
2531
+ var HEIGHT_HYSTERESIS_PX = 6;
2125
2532
  var MOBILE_HEADER_HIDE_DELTA_PX = 28;
2126
2533
  var MOBILE_HEADER_SHOW_DELTA_PX = 16;
2127
2534
  var MOBILE_HEADER_TOP_RESET_PX = 12;
2535
+ var MOBILE_LANDSCAPE_MAX_HEIGHT_PX2 = 500;
2128
2536
  var Viewer = ({ engine, style }) => {
2129
2537
  const viewerState = useViewerStore5();
2130
2538
  const {
@@ -2150,6 +2558,7 @@ var Viewer = ({ engine, style }) => {
2150
2558
  const jumpRef = useRef4(false);
2151
2559
  const jumpTimerRef = useRef4(null);
2152
2560
  const lastWidthRef = useRef4(null);
2561
+ const lastHeightRef = useRef4(null);
2153
2562
  const lastScrollTopRef = useRef4(0);
2154
2563
  const scrollDownAccumulatorRef = useRef4(0);
2155
2564
  const scrollUpAccumulatorRef = useRef4(0);
@@ -2163,11 +2572,14 @@ var Viewer = ({ engine, style }) => {
2163
2572
  rafId: null
2164
2573
  });
2165
2574
  const [availableWidth, setAvailableWidth] = useState5(null);
2575
+ const [availableHeight, setAvailableHeight] = useState5(null);
2166
2576
  const [basePageSize, setBasePageSize] = useState5(null);
2167
2577
  const [pageSizes, setPageSizes] = useState5({});
2168
2578
  const [colorPickerOpen, setColorPickerOpen] = useState5(false);
2169
- const isCompact = availableWidth !== null && availableWidth < 820;
2170
- const isMobileViewport = availableWidth !== null && availableWidth < 640;
2579
+ const isLandscape = availableWidth !== null && availableHeight !== null && availableWidth > availableHeight;
2580
+ const isLandscapeShort = isLandscape && availableHeight !== null && availableHeight <= MOBILE_LANDSCAPE_MAX_HEIGHT_PX2;
2581
+ const isCompact = availableWidth !== null && (availableWidth < 820 || isLandscapeShort);
2582
+ const isMobileViewport = availableWidth !== null && (availableWidth < 640 || isLandscapeShort);
2171
2583
  const paddingY = isCompact ? "py-10" : "py-16";
2172
2584
  const toolDockPosition = isCompact ? "bottom-4" : "bottom-8";
2173
2585
  const colorPalette = [
@@ -2218,21 +2630,32 @@ var Viewer = ({ engine, style }) => {
2218
2630
  const measurementTarget = viewerElement.parentElement ?? viewerElement;
2219
2631
  let rafId = null;
2220
2632
  const normalizeWidth = (rawWidth) => Math.max(0, Math.floor(rawWidth / WIDTH_SNAP_PX) * WIDTH_SNAP_PX);
2221
- const updateWidth = () => {
2633
+ const normalizeHeight = (rawHeight) => Math.max(0, Math.floor(rawHeight / HEIGHT_SNAP_PX) * HEIGHT_SNAP_PX);
2634
+ const updateSize = () => {
2222
2635
  const rawWidth = measurementTarget.getBoundingClientRect?.().width ?? measurementTarget.clientWidth ?? measurementTarget.offsetWidth;
2636
+ const rawHeight = measurementTarget.getBoundingClientRect?.().height ?? measurementTarget.clientHeight ?? measurementTarget.offsetHeight;
2223
2637
  const nextWidth = normalizeWidth(rawWidth);
2224
- if (nextWidth <= 0) return;
2638
+ const nextHeight = normalizeHeight(rawHeight);
2639
+ if (nextWidth <= 0 || nextHeight <= 0) return;
2225
2640
  const previousWidth = lastWidthRef.current;
2226
- if (previousWidth != null && Math.abs(nextWidth - previousWidth) < WIDTH_HYSTERESIS_PX)
2227
- return;
2228
- lastWidthRef.current = nextWidth;
2229
- setAvailableWidth(nextWidth);
2641
+ const previousHeight = lastHeightRef.current;
2642
+ const widthChanged = previousWidth == null || Math.abs(nextWidth - previousWidth) >= WIDTH_HYSTERESIS_PX;
2643
+ const heightChanged = previousHeight == null || Math.abs(nextHeight - previousHeight) >= HEIGHT_HYSTERESIS_PX;
2644
+ if (!widthChanged && !heightChanged) return;
2645
+ if (widthChanged) {
2646
+ lastWidthRef.current = nextWidth;
2647
+ setAvailableWidth(nextWidth);
2648
+ }
2649
+ if (heightChanged) {
2650
+ lastHeightRef.current = nextHeight;
2651
+ setAvailableHeight(nextHeight);
2652
+ }
2230
2653
  };
2231
2654
  const scheduleWidthUpdate = () => {
2232
2655
  if (rafId != null) cancelAnimationFrame(rafId);
2233
2656
  rafId = requestAnimationFrame(() => {
2234
2657
  rafId = null;
2235
- updateWidth();
2658
+ updateSize();
2236
2659
  });
2237
2660
  };
2238
2661
  scheduleWidthUpdate();