@pixerate/editor-react 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -914,8 +914,12 @@ function useSpreadsheetEditor(options = {}) {
914
914
  const [activeCell, setActiveCell] = (0, import_react15.useState)(controller.activeCell);
915
915
  const [selectedRange, setSelectedRange] = (0, import_react15.useState)(controller.selectedRange);
916
916
  const [editingCell, setEditingCell] = (0, import_react15.useState)(null);
917
- const [draftValue, setDraftValue] = (0, import_react15.useState)("");
917
+ const [draftValue, setDraftState] = (0, import_react15.useState)("");
918
918
  settersRef.current = { setDoc, setActiveCell, setSelectedRange };
919
+ const setDraftValue = (0, import_react15.useCallback)((value) => {
920
+ controller.draftValue = value;
921
+ setDraftState(value);
922
+ }, [controller]);
919
923
  const setCellValue = (0, import_react15.useCallback)((rowId, colId, raw) => {
920
924
  controller.setCellValue(rowId, colId, raw);
921
925
  setDoc({ ...controller.document });
@@ -1049,15 +1053,30 @@ function useSpreadsheetEditor(options = {}) {
1049
1053
 
1050
1054
  // src/spreadsheet/SpreadsheetEditor.tsx
1051
1055
  var import_react19 = require("react");
1052
- var import_spreadsheet4 = require("@pixerate/editor/spreadsheet");
1056
+ var import_spreadsheet5 = require("@pixerate/editor/spreadsheet");
1053
1057
 
1054
1058
  // src/spreadsheet/SpreadsheetContextMenu.tsx
1055
1059
  var import_react16 = __toESM(require("react"), 1);
1060
+ var import_spreadsheet2 = require("@pixerate/editor/spreadsheet");
1056
1061
  var import_jsx_runtime5 = require("react/jsx-runtime");
1062
+ var VIEWPORT_MARGIN = 4;
1057
1063
  var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
1058
1064
  const menuRef = (0, import_react16.useRef)(null);
1059
1065
  const onCloseRef = (0, import_react16.useRef)(onClose);
1060
1066
  onCloseRef.current = onClose;
1067
+ const [position, setPosition] = (0, import_react16.useState)({ x, y });
1068
+ (0, import_react16.useLayoutEffect)(() => {
1069
+ const menu = menuRef.current;
1070
+ if (!menu || typeof window === "undefined") return;
1071
+ const { width, height } = menu.getBoundingClientRect();
1072
+ const next = (0, import_spreadsheet2.clampMenuPosition)(
1073
+ { x, y },
1074
+ { width, height },
1075
+ { width: window.innerWidth, height: window.innerHeight },
1076
+ VIEWPORT_MARGIN
1077
+ );
1078
+ setPosition((prev) => prev.x === next.x && prev.y === next.y ? prev : next);
1079
+ }, [x, y, items]);
1061
1080
  (0, import_react16.useEffect)(() => {
1062
1081
  const handlePointer = (e) => {
1063
1082
  if (!menuRef.current?.contains(e.target)) onCloseRef.current();
@@ -1079,7 +1098,13 @@ var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
1079
1098
  role: "menu",
1080
1099
  "aria-label": "Table actions",
1081
1100
  className: "fixed z-50 min-w-[14rem] rounded-md border border-border bg-popover py-1 text-xs text-popover-foreground shadow-lg",
1082
- style: { left: x, top: y, background: "var(--popover, var(--background, #fff))" },
1101
+ style: {
1102
+ left: position.x,
1103
+ top: position.y,
1104
+ maxHeight: `calc(100vh - ${VIEWPORT_MARGIN * 2}px)`,
1105
+ overflowY: "auto",
1106
+ background: "var(--popover, var(--background, #fff))"
1107
+ },
1083
1108
  onContextMenu: (e) => e.preventDefault(),
1084
1109
  onKeyDown: (e) => e.stopPropagation(),
1085
1110
  children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react16.default.Fragment, { children: [
@@ -1116,7 +1141,7 @@ var SpreadsheetGroupRow = ({
1116
1141
  }) => {
1117
1142
  const labelColId = columns.some((c) => c.column.id === groupColId) ? groupColId : columns[0]?.column.id;
1118
1143
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("tr", { className: "border-b border-border bg-muted/40 font-semibold", "data-group-key": group.key, style: { height: 28 }, children: [
1119
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { className: "w-12 min-w-[3rem] sticky left-0 z-10 bg-muted/60 border-r border-border text-center", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1144
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { className: "w-12 min-w-[3rem] sticky left-0 z-10 bg-muted border-r border-border text-center", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1120
1145
  "button",
1121
1146
  {
1122
1147
  type: "button",
@@ -1148,7 +1173,7 @@ var SpreadsheetGroupRow = ({
1148
1173
 
1149
1174
  // src/spreadsheet/useSpreadsheetTableInteractions.ts
1150
1175
  var import_react17 = require("react");
1151
- var import_spreadsheet2 = require("@pixerate/editor/spreadsheet");
1176
+ var import_spreadsheet3 = require("@pixerate/editor/spreadsheet");
1152
1177
  function gridHasFocus(grid) {
1153
1178
  if (!grid || typeof document === "undefined") return false;
1154
1179
  const active = document.activeElement;
@@ -1189,7 +1214,8 @@ function useSpreadsheetClipboard(gridRef, state, readOnly) {
1189
1214
  setTimeout(() => {
1190
1215
  if (copiedRef.current) return;
1191
1216
  const s = stateRef.current;
1192
- navigator.clipboard?.writeText(s.exportToTsv(s.selectedRange));
1217
+ Promise.resolve(navigator.clipboard?.writeText(s.exportToTsv(s.selectedRange))).catch(() => {
1218
+ });
1193
1219
  }, 0);
1194
1220
  }, []);
1195
1221
  return { copyShortcutFallback };
@@ -1215,19 +1241,19 @@ function useFillHandle(state) {
1215
1241
  e.preventDefault();
1216
1242
  e.stopPropagation();
1217
1243
  const range = stateRef.current.selectedRange;
1218
- if (range) setDrag({ source: (0, import_spreadsheet2.normalizeRange)(range), target: null });
1244
+ if (range) setDrag({ source: (0, import_spreadsheet3.normalizeRange)(range), target: null });
1219
1245
  }, []);
1220
1246
  const extendFill = (0, import_react17.useCallback)((row, col) => {
1221
1247
  const current = dragRef.current;
1222
1248
  if (!current) return;
1223
- setDrag({ source: current.source, target: (0, import_spreadsheet2.computeFillTarget)(current.source, { row, col }) });
1249
+ setDrag({ source: current.source, target: (0, import_spreadsheet3.computeFillTarget)(current.source, { row, col }) });
1224
1250
  }, []);
1225
1251
  return { fillTarget: drag?.target ?? null, isFilling: dragging, startFill, extendFill };
1226
1252
  }
1227
1253
 
1228
1254
  // src/spreadsheet/FormulaHints.tsx
1229
1255
  var import_react18 = require("react");
1230
- var import_spreadsheet3 = require("@pixerate/editor/spreadsheet");
1256
+ var import_spreadsheet4 = require("@pixerate/editor/spreadsheet");
1231
1257
  var import_jsx_runtime7 = require("react/jsx-runtime");
1232
1258
  var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1233
1259
  const [caret, setCaret] = (0, import_react18.useState)(null);
@@ -1237,7 +1263,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1237
1263
  const [position, setPosition] = (0, import_react18.useState)(null);
1238
1264
  const pendingCaret = (0, import_react18.useRef)(null);
1239
1265
  const hints = (0, import_react18.useMemo)(
1240
- () => (0, import_spreadsheet3.getFormulaHints)(value, caret ?? value.length, { limit }),
1266
+ () => (0, import_spreadsheet4.getFormulaHints)(value, caret ?? value.length, { limit }),
1241
1267
  [value, caret, limit]
1242
1268
  );
1243
1269
  const suggestions = dismissed ? [] : hints.suggestions;
@@ -1249,7 +1275,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1249
1275
  const { suggestions: list, value: current, caret: at, onAccept: commit } = latest.current;
1250
1276
  const suggestion = list[index];
1251
1277
  if (!suggestion) return;
1252
- const next = (0, import_spreadsheet3.applyFormulaSuggestion)(current, at ?? current.length, suggestion.name);
1278
+ const next = (0, import_spreadsheet4.applyFormulaSuggestion)(current, at ?? current.length, suggestion.name);
1253
1279
  pendingCaret.current = next.caret;
1254
1280
  commit(next.value);
1255
1281
  };
@@ -1389,6 +1415,20 @@ function frozenCellStyle(viewRow, viewCol) {
1389
1415
  background: "var(--background, #fff)"
1390
1416
  };
1391
1417
  }
1418
+ var coordKey = (cell) => cell ? `${(0, import_spreadsheet5.indexToColName)(cell.col)}${cell.row + 1}` : "";
1419
+ function spillBorderStyle(range, row, col) {
1420
+ const line = "1px solid color-mix(in srgb, var(--primary, #6366f1) 60%, transparent)";
1421
+ return {
1422
+ position: "absolute",
1423
+ inset: 0,
1424
+ pointerEvents: "none",
1425
+ zIndex: 5,
1426
+ borderTop: row === range.startRow ? line : void 0,
1427
+ borderBottom: row === range.endRow ? line : void 0,
1428
+ borderLeft: col === range.startCol ? line : void 0,
1429
+ borderRight: col === range.endCol ? line : void 0
1430
+ };
1431
+ }
1392
1432
  var SpreadsheetEditor = ({
1393
1433
  state: externalState,
1394
1434
  readOnly = false,
@@ -1419,10 +1459,10 @@ var SpreadsheetEditor = ({
1419
1459
  if (readOnly) return;
1420
1460
  e.preventDefault();
1421
1461
  if (state.editingCell) state.commitEditing();
1422
- if (!state.selectedRange || !(0, import_spreadsheet4.rangeContains)(state.selectedRange, rIdx, cIdx)) {
1462
+ if (!state.selectedRange || !(0, import_spreadsheet5.rangeContains)(state.selectedRange, rIdx, cIdx)) {
1423
1463
  state.selectCell(rIdx, cIdx);
1424
1464
  }
1425
- setMenu({ x: e.clientX, y: e.clientY, items: (0, import_spreadsheet4.getSpreadsheetMenuItems)(state.controller, { row: rIdx, col: cIdx }) });
1465
+ setMenu({ x: e.clientX, y: e.clientY, items: (0, import_spreadsheet5.getSpreadsheetMenuItems)(state.controller, { row: rIdx, col: cIdx }) });
1426
1466
  }, [readOnly, state]);
1427
1467
  const activeRawValue = (0, import_react19.useMemo)(() => {
1428
1468
  if (!state.activeCell) return "";
@@ -1431,9 +1471,15 @@ var SpreadsheetEditor = ({
1431
1471
  if (!row || !col) return "";
1432
1472
  return state.getCell(row.id, col.id).raw;
1433
1473
  }, [state.activeCell, state.document]);
1474
+ const activeSpill = (0, import_react19.useMemo)(
1475
+ () => state.activeCell ? state.controller.getSpillAt(state.activeCell.row, state.activeCell.col) : null,
1476
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1477
+ [state.controller, state.activeCell, state.document]
1478
+ );
1479
+ const spillFormula = activeSpill && !activeRawValue && activeSpill.key !== coordKey(state.activeCell) ? activeSpill.formula : "";
1434
1480
  const coordLabel = (0, import_react19.useMemo)(() => {
1435
1481
  if (!state.activeCell) return "--";
1436
- const colName = (0, import_spreadsheet4.indexToColName)(state.activeCell.col);
1482
+ const colName = (0, import_spreadsheet5.indexToColName)(state.activeCell.col);
1437
1483
  return `${colName}${state.activeCell.row + 1}`;
1438
1484
  }, [state.activeCell]);
1439
1485
  const [formulaDraft, setFormulaDraft] = (0, import_react19.useState)(null);
@@ -1480,7 +1526,10 @@ var SpreadsheetEditor = ({
1480
1526
  e.preventDefault();
1481
1527
  state.commitEditing();
1482
1528
  state.moveCursor(e.shiftKey ? "left" : "right");
1529
+ } else {
1530
+ return;
1483
1531
  }
1532
+ gridRef.current?.focus();
1484
1533
  return;
1485
1534
  }
1486
1535
  if (!state.activeCell) return;
@@ -1528,6 +1577,7 @@ var SpreadsheetEditor = ({
1528
1577
  } else if (!readOnly && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
1529
1578
  const col = state.document.columns[state.activeCell.col];
1530
1579
  if (!col?.readOnly) {
1580
+ e.preventDefault();
1531
1581
  state.startEditing(state.activeCell.row, state.activeCell.col, e.key);
1532
1582
  }
1533
1583
  }
@@ -1720,7 +1770,9 @@ var SpreadsheetEditor = ({
1720
1770
  placeholder: "Enter a value or formula (e.g. =SUM(A1:A5))",
1721
1771
  "aria-label": "Formula bar",
1722
1772
  readOnly: formulaBarReadOnly,
1723
- value: state.editingCell ? state.draftValue : formulaDraft ?? activeRawValue,
1773
+ "data-spill-formula": !state.editingCell && formulaDraft === null && spillFormula ? "true" : void 0,
1774
+ style: !state.editingCell && formulaDraft === null && spillFormula ? { color: "var(--muted-foreground, #94a3b8)" } : void 0,
1775
+ value: state.editingCell ? state.draftValue : formulaDraft ?? (activeRawValue || spillFormula),
1724
1776
  onFocus: () => {
1725
1777
  if (!state.editingCell) setFormulaDraft(activeRawValue);
1726
1778
  },
@@ -1755,8 +1807,8 @@ var SpreadsheetEditor = ({
1755
1807
  )
1756
1808
  ] }),
1757
1809
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "relative flex-1 overflow-auto", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("table", { className: "border-collapse table-fixed w-max min-w-full", children: [
1758
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { className: "h-7 bg-muted/60 sticky top-0 z-20 border-b border-border shadow-xs", children: [
1759
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "w-12 min-w-[3rem] sticky left-0 z-30 bg-muted/90 border-r border-border text-center text-[10px] text-muted-foreground font-medium", children: "#" }),
1810
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { className: "h-7 bg-muted sticky top-0 z-20 border-b border-border shadow-xs", children: [
1811
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "w-12 min-w-[3rem] sticky left-0 z-30 bg-muted border-r border-border text-center text-[10px] text-muted-foreground font-medium", children: "#" }),
1760
1812
  view.columns.map(({ column: col, index: cIdx, frozen, stickyLeft }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1761
1813
  "th",
1762
1814
  {
@@ -1842,7 +1894,7 @@ var SpreadsheetEditor = ({
1842
1894
  );
1843
1895
  }
1844
1896
  const displayRowIndex = viewRow.index;
1845
- const selection = state.selectedRange ? (0, import_spreadsheet4.normalizeRange)(state.selectedRange) : null;
1897
+ const selection = state.selectedRange ? (0, import_spreadsheet5.normalizeRange)(state.selectedRange) : null;
1846
1898
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1847
1899
  "tr",
1848
1900
  {
@@ -1853,7 +1905,7 @@ var SpreadsheetEditor = ({
1853
1905
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1854
1906
  "td",
1855
1907
  {
1856
- className: "w-12 min-w-[3rem] sticky left-0 z-10 bg-muted/50 border-r border-border text-center text-[10px] text-muted-foreground font-mono select-none",
1908
+ className: "w-12 min-w-[3rem] sticky left-0 z-10 bg-muted border-r border-border text-center text-[10px] text-muted-foreground font-mono select-none",
1857
1909
  style: viewRow.frozen ? { top: COLUMN_HEADER_HEIGHT + viewRow.stickyTop, zIndex: 15 } : void 0,
1858
1910
  children: displayRowIndex + 1
1859
1911
  }
@@ -1869,8 +1921,8 @@ var SpreadsheetEditor = ({
1869
1921
  const selected = isCellSelected(rIdx, cIdx);
1870
1922
  const active = isActiveCell(rIdx, cIdx);
1871
1923
  const isEditing = state.editingCell?.row === rIdx && state.editingCell?.col === cIdx;
1872
- const inFillPreview = !!fillTarget && !selected && (0, import_spreadsheet4.rangeContains)(fillTarget, displayRowIndex, viewCol.index);
1873
- const showFillHandle = !readOnly && !isEditing && !!selection && (span.merge ? (0, import_spreadsheet4.rangeContains)(span.merge, selection.endRow, selection.endCol) : selection.endRow === rIdx && selection.endCol === cIdx);
1924
+ const inFillPreview = !!fillTarget && !selected && (0, import_spreadsheet5.rangeContains)(fillTarget, displayRowIndex, viewCol.index);
1925
+ const showFillHandle = !readOnly && !isEditing && !!selection && (span.merge ? (0, import_spreadsheet5.rangeContains)(span.merge, selection.endRow, selection.endCol) : selection.endRow === rIdx && selection.endCol === cIdx);
1874
1926
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1875
1927
  "td",
1876
1928
  {
@@ -1900,6 +1952,10 @@ var SpreadsheetEditor = ({
1900
1952
  value: state.draftValue,
1901
1953
  onChange: (e) => state.setDraftValue(e.target.value),
1902
1954
  onBlur: () => state.commitEditing(),
1955
+ onFocus: (e) => {
1956
+ const end = e.currentTarget.value.length;
1957
+ e.currentTarget.setSelectionRange(end, end);
1958
+ },
1903
1959
  autoFocus: true
1904
1960
  }
1905
1961
  ),
@@ -1912,6 +1968,7 @@ var SpreadsheetEditor = ({
1912
1968
  children: cell.error || (cell.value ?? "")
1913
1969
  }
1914
1970
  ),
1971
+ activeSpill && (0, import_spreadsheet5.rangeContains)(activeSpill.range, rIdx, cIdx) && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "aria-hidden": "true", className: "spreadsheet-spill-border", style: spillBorderStyle(activeSpill.range, rIdx, cIdx) }),
1915
1972
  showFillHandle && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1916
1973
  "div",
1917
1974
  {
@@ -3631,10 +3688,13 @@ var CommentThreadCard = ({
3631
3688
 
3632
3689
  // src/review/ReviewToolbar.tsx
3633
3690
  var import_jsx_runtime12 = require("react/jsx-runtime");
3691
+ var defaultDescriptionClass = "max-w-[16rem] truncate px-1 text-xs text-muted-foreground";
3634
3692
  var buttonClass2 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
3635
3693
  var ReviewToolbar = ({
3636
3694
  editor,
3637
- className = "flex flex-wrap items-center gap-1 rounded-md border border-border bg-background p-1 text-sm"
3695
+ className = "flex flex-wrap items-center gap-1 rounded-md border border-border bg-background p-1 text-sm",
3696
+ showDescription = true,
3697
+ descriptionClassName = defaultDescriptionClass
3638
3698
  }) => {
3639
3699
  const review = useTrackChanges(editor);
3640
3700
  const count = review.changes.length;
@@ -3676,6 +3736,18 @@ var ReviewToolbar = ({
3676
3736
  children: "\u203A"
3677
3737
  }
3678
3738
  ),
3739
+ showDescription && current && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3740
+ "span",
3741
+ {
3742
+ className: descriptionClassName,
3743
+ "data-change-type": current.type,
3744
+ title: `${current.author.name}: ${current.description}`,
3745
+ children: [
3746
+ current.author.name ? `${current.author.name}: ` : "",
3747
+ current.description
3748
+ ]
3749
+ }
3750
+ ),
3679
3751
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
3680
3752
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3681
3753
  "button",