@pixerate/editor-react 0.12.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/README.md +7 -4
- package/dist/canvas/index.cjs +180 -68
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +36 -6
- package/dist/canvas/index.d.ts +36 -6
- package/dist/canvas/index.js +113 -1
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-25S5EHH4.js +1160 -0
- package/dist/chunk-25S5EHH4.js.map +1 -0
- package/dist/image-editor/index.d.cts +3 -3
- package/dist/image-editor/index.d.ts +3 -3
- package/dist/index.cjs +109 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -175
- package/dist/index.d.ts +30 -175
- package/dist/index.js +84 -1153
- package/dist/index.js.map +1 -1
- package/dist/spreadsheet/index.cjs +1191 -0
- package/dist/spreadsheet/index.cjs.map +1 -0
- package/dist/spreadsheet/index.d.cts +159 -0
- package/dist/spreadsheet/index.d.ts +159 -0
- package/dist/spreadsheet/index.js +19 -0
- package/dist/spreadsheet/index.js.map +1 -0
- package/package.json +15 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FormulaHints,
|
|
3
|
+
SpreadsheetContextMenu,
|
|
4
|
+
SpreadsheetEditor,
|
|
5
|
+
SpreadsheetGroupRow,
|
|
6
|
+
useFillHandle,
|
|
7
|
+
useSpreadsheetClipboard,
|
|
8
|
+
useSpreadsheetEditor
|
|
9
|
+
} from "./chunk-25S5EHH4.js";
|
|
1
10
|
import {
|
|
2
11
|
ImageEditor,
|
|
3
12
|
useImageEditor
|
|
@@ -853,1104 +862,11 @@ import {
|
|
|
853
862
|
createHistoryManager as createHistoryManager2
|
|
854
863
|
} from "@pixerate/editor";
|
|
855
864
|
|
|
856
|
-
// src/spreadsheet/useSpreadsheetEditor.ts
|
|
857
|
-
import { useState as useState5, useRef as useRef7, useCallback as useCallback4, useMemo as useMemo6 } from "react";
|
|
858
|
-
import {
|
|
859
|
-
SpreadsheetController,
|
|
860
|
-
bindSpreadsheetTableActions
|
|
861
|
-
} from "@pixerate/editor";
|
|
862
|
-
function useSpreadsheetEditor(options = {}) {
|
|
863
|
-
const optionsRef = useRef7(options);
|
|
864
|
-
optionsRef.current = options;
|
|
865
|
-
const settersRef = useRef7({});
|
|
866
|
-
const [controller] = useState5(
|
|
867
|
-
() => new SpreadsheetController({
|
|
868
|
-
...options,
|
|
869
|
-
onDocumentChange: (updated) => {
|
|
870
|
-
settersRef.current.setDoc?.({ ...updated });
|
|
871
|
-
optionsRef.current.onDocumentChange?.(updated);
|
|
872
|
-
},
|
|
873
|
-
onSelectionChange: (active, range) => {
|
|
874
|
-
settersRef.current.setActiveCell?.(active);
|
|
875
|
-
settersRef.current.setSelectedRange?.(range);
|
|
876
|
-
optionsRef.current.onSelectionChange?.(active, range);
|
|
877
|
-
},
|
|
878
|
-
onCellCommit: (rowId, colId, raw, value) => {
|
|
879
|
-
optionsRef.current.onCellCommit?.(rowId, colId, raw, value);
|
|
880
|
-
}
|
|
881
|
-
})
|
|
882
|
-
);
|
|
883
|
-
const [doc, setDoc] = useState5(() => ({ ...controller.document }));
|
|
884
|
-
const [activeCell, setActiveCell] = useState5(controller.activeCell);
|
|
885
|
-
const [selectedRange, setSelectedRange] = useState5(controller.selectedRange);
|
|
886
|
-
const [editingCell, setEditingCell] = useState5(null);
|
|
887
|
-
const [draftValue, setDraftValue] = useState5("");
|
|
888
|
-
settersRef.current = { setDoc, setActiveCell, setSelectedRange };
|
|
889
|
-
const setCellValue = useCallback4((rowId, colId, raw) => {
|
|
890
|
-
controller.setCellValue(rowId, colId, raw);
|
|
891
|
-
setDoc({ ...controller.document });
|
|
892
|
-
}, [controller]);
|
|
893
|
-
const selectCell = useCallback4((row, col, extendSelection = false) => {
|
|
894
|
-
controller.selectCell(row, col, extendSelection);
|
|
895
|
-
setActiveCell(controller.activeCell);
|
|
896
|
-
setSelectedRange(controller.selectedRange);
|
|
897
|
-
}, [controller]);
|
|
898
|
-
const selectRange = useCallback4((range) => {
|
|
899
|
-
controller.selectRange(range);
|
|
900
|
-
setSelectedRange(controller.selectedRange);
|
|
901
|
-
}, [controller]);
|
|
902
|
-
const startEditing = useCallback4((row, col, initialVal) => {
|
|
903
|
-
controller.startEditing(row, col, initialVal);
|
|
904
|
-
setActiveCell(controller.activeCell);
|
|
905
|
-
setEditingCell(controller.editingCell);
|
|
906
|
-
setDraftValue(controller.draftValue);
|
|
907
|
-
}, [controller]);
|
|
908
|
-
const commitEditing = useCallback4((val) => {
|
|
909
|
-
controller.commitEditing(val);
|
|
910
|
-
setEditingCell(null);
|
|
911
|
-
setDraftValue("");
|
|
912
|
-
setDoc({ ...controller.document });
|
|
913
|
-
}, [controller]);
|
|
914
|
-
const cancelEditing = useCallback4(() => {
|
|
915
|
-
controller.cancelEditing();
|
|
916
|
-
setEditingCell(null);
|
|
917
|
-
setDraftValue("");
|
|
918
|
-
}, [controller]);
|
|
919
|
-
const moveCursor = useCallback4((dir, extend = false) => {
|
|
920
|
-
controller.moveCursor(dir, extend);
|
|
921
|
-
setActiveCell(controller.activeCell);
|
|
922
|
-
setSelectedRange(controller.selectedRange);
|
|
923
|
-
}, [controller]);
|
|
924
|
-
const insertColumn = useCallback4((index, config) => {
|
|
925
|
-
controller.insertColumn(index, config);
|
|
926
|
-
setDoc({ ...controller.document });
|
|
927
|
-
}, [controller]);
|
|
928
|
-
const deleteColumn = useCallback4((colId) => {
|
|
929
|
-
controller.deleteColumn(colId);
|
|
930
|
-
setDoc({ ...controller.document });
|
|
931
|
-
}, [controller]);
|
|
932
|
-
const insertRow = useCallback4((index, config) => {
|
|
933
|
-
controller.insertRow(index, config);
|
|
934
|
-
setDoc({ ...controller.document });
|
|
935
|
-
}, [controller]);
|
|
936
|
-
const deleteRow = useCallback4((rowId) => {
|
|
937
|
-
controller.deleteRow(rowId);
|
|
938
|
-
setDoc({ ...controller.document });
|
|
939
|
-
}, [controller]);
|
|
940
|
-
const setColumnWidth = useCallback4((colId, width) => {
|
|
941
|
-
controller.setColumnWidth(colId, width);
|
|
942
|
-
setDoc({ ...controller.document });
|
|
943
|
-
}, [controller]);
|
|
944
|
-
const autoFitColumnWidth = useCallback4((colId, customWidth) => {
|
|
945
|
-
controller.autoFitColumnWidth(colId, customWidth);
|
|
946
|
-
setDoc({ ...controller.document });
|
|
947
|
-
}, [controller]);
|
|
948
|
-
const registerDataSource = useCallback4((ds) => {
|
|
949
|
-
controller.registerDataSource(ds);
|
|
950
|
-
setDoc({ ...controller.document });
|
|
951
|
-
}, [controller]);
|
|
952
|
-
const setPrimaryDataSource = useCallback4((id) => {
|
|
953
|
-
controller.setPrimaryDataSource(id);
|
|
954
|
-
setDoc({ ...controller.document });
|
|
955
|
-
}, [controller]);
|
|
956
|
-
const recalculateAll = useCallback4(() => {
|
|
957
|
-
controller.recalculateAll();
|
|
958
|
-
setDoc({ ...controller.document });
|
|
959
|
-
}, [controller]);
|
|
960
|
-
const undo = useCallback4(() => {
|
|
961
|
-
controller.undo();
|
|
962
|
-
setDoc({ ...controller.document });
|
|
963
|
-
}, [controller]);
|
|
964
|
-
const redo = useCallback4(() => {
|
|
965
|
-
controller.redo();
|
|
966
|
-
setDoc({ ...controller.document });
|
|
967
|
-
}, [controller]);
|
|
968
|
-
const tableActions = useMemo6(() => bindSpreadsheetTableActions(controller), [controller]);
|
|
969
|
-
const viewModel = useMemo6(() => controller.getViewModel(), [controller, doc]);
|
|
970
|
-
return {
|
|
971
|
-
document: doc,
|
|
972
|
-
activeCell,
|
|
973
|
-
selectedRange,
|
|
974
|
-
editingCell,
|
|
975
|
-
draftValue,
|
|
976
|
-
setDraftValue,
|
|
977
|
-
controller,
|
|
978
|
-
getCell: (rowId, colId) => controller.getCell(rowId, colId),
|
|
979
|
-
setCellValue,
|
|
980
|
-
selectCell,
|
|
981
|
-
selectRange,
|
|
982
|
-
startEditing,
|
|
983
|
-
commitEditing,
|
|
984
|
-
cancelEditing,
|
|
985
|
-
moveCursor,
|
|
986
|
-
insertColumn,
|
|
987
|
-
deleteColumn,
|
|
988
|
-
insertRow,
|
|
989
|
-
deleteRow,
|
|
990
|
-
setColumnWidth,
|
|
991
|
-
autoFitColumnWidth,
|
|
992
|
-
loadDocument: (document2) => {
|
|
993
|
-
controller.loadDocument(document2);
|
|
994
|
-
setDoc({ ...controller.document });
|
|
995
|
-
setActiveCell(null);
|
|
996
|
-
setSelectedRange(null);
|
|
997
|
-
setEditingCell(null);
|
|
998
|
-
setDraftValue("");
|
|
999
|
-
},
|
|
1000
|
-
syncWithDataSource: () => {
|
|
1001
|
-
controller.syncWithDataSource();
|
|
1002
|
-
controller.recalculateAll();
|
|
1003
|
-
setDoc({ ...controller.document });
|
|
1004
|
-
},
|
|
1005
|
-
registerDataSource,
|
|
1006
|
-
setPrimaryDataSource,
|
|
1007
|
-
recalculateAll,
|
|
1008
|
-
exportToTsv: (range) => controller.exportToTsv(range),
|
|
1009
|
-
importFromTsv: (data) => {
|
|
1010
|
-
controller.importFromTsv(data);
|
|
1011
|
-
setDoc({ ...controller.document });
|
|
1012
|
-
},
|
|
1013
|
-
...tableActions,
|
|
1014
|
-
viewModel,
|
|
1015
|
-
undo,
|
|
1016
|
-
redo
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
// src/spreadsheet/SpreadsheetEditor.tsx
|
|
1021
|
-
import { useRef as useRef11, useState as useState8, useMemo as useMemo8, useEffect as useEffect10, useCallback as useCallback6 } from "react";
|
|
1022
|
-
import {
|
|
1023
|
-
indexToColName,
|
|
1024
|
-
getSpreadsheetMenuItems,
|
|
1025
|
-
normalizeRange as normalizeRange2,
|
|
1026
|
-
rangeContains
|
|
1027
|
-
} from "@pixerate/editor";
|
|
1028
|
-
|
|
1029
|
-
// src/spreadsheet/SpreadsheetContextMenu.tsx
|
|
1030
|
-
import React4, { useEffect as useEffect7, useRef as useRef8 } from "react";
|
|
1031
|
-
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1032
|
-
var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
|
|
1033
|
-
const menuRef = useRef8(null);
|
|
1034
|
-
const onCloseRef = useRef8(onClose);
|
|
1035
|
-
onCloseRef.current = onClose;
|
|
1036
|
-
useEffect7(() => {
|
|
1037
|
-
const handlePointer = (e) => {
|
|
1038
|
-
if (!menuRef.current?.contains(e.target)) onCloseRef.current();
|
|
1039
|
-
};
|
|
1040
|
-
const handleKey = (e) => {
|
|
1041
|
-
if (e.key === "Escape") onCloseRef.current();
|
|
1042
|
-
};
|
|
1043
|
-
window.addEventListener("mousedown", handlePointer);
|
|
1044
|
-
window.addEventListener("keydown", handleKey);
|
|
1045
|
-
return () => {
|
|
1046
|
-
window.removeEventListener("mousedown", handlePointer);
|
|
1047
|
-
window.removeEventListener("keydown", handleKey);
|
|
1048
|
-
};
|
|
1049
|
-
}, []);
|
|
1050
|
-
return /* @__PURE__ */ jsx5(
|
|
1051
|
-
"div",
|
|
1052
|
-
{
|
|
1053
|
-
ref: menuRef,
|
|
1054
|
-
role: "menu",
|
|
1055
|
-
"aria-label": "Table actions",
|
|
1056
|
-
className: "fixed z-50 min-w-[14rem] rounded-md border border-border bg-popover py-1 text-xs text-popover-foreground shadow-lg",
|
|
1057
|
-
style: { left: x, top: y, background: "var(--popover, var(--background, #fff))" },
|
|
1058
|
-
onContextMenu: (e) => e.preventDefault(),
|
|
1059
|
-
onKeyDown: (e) => e.stopPropagation(),
|
|
1060
|
-
children: items.map((item) => /* @__PURE__ */ jsxs4(React4.Fragment, { children: [
|
|
1061
|
-
item.separatorBefore && /* @__PURE__ */ jsx5("div", { role: "separator", className: "my-1 h-px bg-border" }),
|
|
1062
|
-
/* @__PURE__ */ jsx5(
|
|
1063
|
-
"button",
|
|
1064
|
-
{
|
|
1065
|
-
type: "button",
|
|
1066
|
-
role: "menuitem",
|
|
1067
|
-
"data-action": item.id,
|
|
1068
|
-
disabled: item.disabled,
|
|
1069
|
-
className: "block w-full px-3 py-1 text-left hover:bg-muted disabled:cursor-default disabled:opacity-40 disabled:hover:bg-transparent",
|
|
1070
|
-
onClick: () => {
|
|
1071
|
-
onClose();
|
|
1072
|
-
item.run();
|
|
1073
|
-
},
|
|
1074
|
-
children: item.label
|
|
1075
|
-
}
|
|
1076
|
-
)
|
|
1077
|
-
] }, item.id))
|
|
1078
|
-
}
|
|
1079
|
-
);
|
|
1080
|
-
};
|
|
1081
|
-
|
|
1082
|
-
// src/spreadsheet/SpreadsheetGroupRow.tsx
|
|
1083
|
-
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1084
|
-
var formatSummary = (value) => value === null ? "\u2013" : String(value);
|
|
1085
|
-
var SpreadsheetGroupRow = ({
|
|
1086
|
-
group,
|
|
1087
|
-
columns,
|
|
1088
|
-
groupColId,
|
|
1089
|
-
onToggle,
|
|
1090
|
-
trailingCell
|
|
1091
|
-
}) => {
|
|
1092
|
-
const labelColId = columns.some((c) => c.column.id === groupColId) ? groupColId : columns[0]?.column.id;
|
|
1093
|
-
return /* @__PURE__ */ jsxs5("tr", { className: "border-b border-border bg-muted/40 font-semibold", "data-group-key": group.key, style: { height: 28 }, children: [
|
|
1094
|
-
/* @__PURE__ */ jsx6("td", { className: "w-12 min-w-[3rem] sticky left-0 z-10 bg-muted/60 border-r border-border text-center", children: /* @__PURE__ */ jsx6(
|
|
1095
|
-
"button",
|
|
1096
|
-
{
|
|
1097
|
-
type: "button",
|
|
1098
|
-
"aria-label": `${group.collapsed ? "Expand" : "Collapse"} group ${group.label}`,
|
|
1099
|
-
"aria-expanded": !group.collapsed,
|
|
1100
|
-
className: "h-5 w-5 rounded text-[10px] text-muted-foreground hover:bg-muted",
|
|
1101
|
-
onClick: (e) => {
|
|
1102
|
-
e.stopPropagation();
|
|
1103
|
-
onToggle(group.key);
|
|
1104
|
-
},
|
|
1105
|
-
children: group.collapsed ? "\u25B8" : "\u25BE"
|
|
1106
|
-
}
|
|
1107
|
-
) }),
|
|
1108
|
-
columns.map(({ column }) => {
|
|
1109
|
-
const summary = group.summaries[column.id];
|
|
1110
|
-
return /* @__PURE__ */ jsx6("td", { className: "border-r border-border px-2 py-1 truncate max-w-0 text-foreground", style: { maxWidth: 0, overflow: "hidden" }, children: column.id === labelColId ? /* @__PURE__ */ jsxs5("span", { className: "truncate", children: [
|
|
1111
|
-
group.label,
|
|
1112
|
-
" ",
|
|
1113
|
-
/* @__PURE__ */ jsxs5("span", { className: "font-normal text-muted-foreground", children: [
|
|
1114
|
-
"(",
|
|
1115
|
-
group.rowIndices.length,
|
|
1116
|
-
")"
|
|
1117
|
-
] })
|
|
1118
|
-
] }) : summary !== void 0 ? /* @__PURE__ */ jsx6("span", { className: "truncate", "data-summary": column.id, children: formatSummary(summary) }) : null }, column.id);
|
|
1119
|
-
}),
|
|
1120
|
-
trailingCell && /* @__PURE__ */ jsx6("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
|
|
1121
|
-
] });
|
|
1122
|
-
};
|
|
1123
|
-
|
|
1124
|
-
// src/spreadsheet/useSpreadsheetTableInteractions.ts
|
|
1125
|
-
import { useCallback as useCallback5, useEffect as useEffect8, useRef as useRef9, useState as useState6 } from "react";
|
|
1126
|
-
import {
|
|
1127
|
-
computeFillTarget,
|
|
1128
|
-
normalizeRange
|
|
1129
|
-
} from "@pixerate/editor";
|
|
1130
|
-
function gridHasFocus(grid) {
|
|
1131
|
-
if (!grid || typeof document === "undefined") return false;
|
|
1132
|
-
const active = document.activeElement;
|
|
1133
|
-
if (!active || !grid.contains(active)) return false;
|
|
1134
|
-
return !(active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement);
|
|
1135
|
-
}
|
|
1136
|
-
function useSpreadsheetClipboard(gridRef, state, readOnly) {
|
|
1137
|
-
const stateRef = useRef9(state);
|
|
1138
|
-
stateRef.current = state;
|
|
1139
|
-
const readOnlyRef = useRef9(readOnly);
|
|
1140
|
-
readOnlyRef.current = readOnly;
|
|
1141
|
-
const copiedRef = useRef9(false);
|
|
1142
|
-
useEffect8(() => {
|
|
1143
|
-
if (typeof document === "undefined") return;
|
|
1144
|
-
const handleCopy = (e) => {
|
|
1145
|
-
const s = stateRef.current;
|
|
1146
|
-
if (!gridHasFocus(gridRef.current) || s.editingCell || !e.clipboardData || !s.selectedRange) return;
|
|
1147
|
-
for (const [type, data] of Object.entries(s.copyToClipboardData(s.selectedRange))) {
|
|
1148
|
-
e.clipboardData.setData(type, data);
|
|
1149
|
-
}
|
|
1150
|
-
copiedRef.current = true;
|
|
1151
|
-
e.preventDefault();
|
|
1152
|
-
};
|
|
1153
|
-
const handlePaste = (e) => {
|
|
1154
|
-
const s = stateRef.current;
|
|
1155
|
-
if (readOnlyRef.current || !gridHasFocus(gridRef.current) || s.editingCell || !e.clipboardData) return;
|
|
1156
|
-
if (s.pasteClipboardData(e.clipboardData)) e.preventDefault();
|
|
1157
|
-
};
|
|
1158
|
-
document.addEventListener("copy", handleCopy);
|
|
1159
|
-
document.addEventListener("paste", handlePaste);
|
|
1160
|
-
return () => {
|
|
1161
|
-
document.removeEventListener("copy", handleCopy);
|
|
1162
|
-
document.removeEventListener("paste", handlePaste);
|
|
1163
|
-
};
|
|
1164
|
-
}, [gridRef]);
|
|
1165
|
-
const copyShortcutFallback = useCallback5(() => {
|
|
1166
|
-
copiedRef.current = false;
|
|
1167
|
-
setTimeout(() => {
|
|
1168
|
-
if (copiedRef.current) return;
|
|
1169
|
-
const s = stateRef.current;
|
|
1170
|
-
navigator.clipboard?.writeText(s.exportToTsv(s.selectedRange));
|
|
1171
|
-
}, 0);
|
|
1172
|
-
}, []);
|
|
1173
|
-
return { copyShortcutFallback };
|
|
1174
|
-
}
|
|
1175
|
-
function useFillHandle(state) {
|
|
1176
|
-
const [drag, setDrag] = useState6(null);
|
|
1177
|
-
const dragRef = useRef9(drag);
|
|
1178
|
-
dragRef.current = drag;
|
|
1179
|
-
const stateRef = useRef9(state);
|
|
1180
|
-
stateRef.current = state;
|
|
1181
|
-
const dragging = drag !== null;
|
|
1182
|
-
useEffect8(() => {
|
|
1183
|
-
if (!dragging) return;
|
|
1184
|
-
const handleMouseUp = () => {
|
|
1185
|
-
const current = dragRef.current;
|
|
1186
|
-
setDrag(null);
|
|
1187
|
-
if (current?.target) stateRef.current.autofill(current.source, current.target);
|
|
1188
|
-
};
|
|
1189
|
-
window.addEventListener("mouseup", handleMouseUp);
|
|
1190
|
-
return () => window.removeEventListener("mouseup", handleMouseUp);
|
|
1191
|
-
}, [dragging]);
|
|
1192
|
-
const startFill = useCallback5((e) => {
|
|
1193
|
-
e.preventDefault();
|
|
1194
|
-
e.stopPropagation();
|
|
1195
|
-
const range = stateRef.current.selectedRange;
|
|
1196
|
-
if (range) setDrag({ source: normalizeRange(range), target: null });
|
|
1197
|
-
}, []);
|
|
1198
|
-
const extendFill = useCallback5((row, col) => {
|
|
1199
|
-
const current = dragRef.current;
|
|
1200
|
-
if (!current) return;
|
|
1201
|
-
setDrag({ source: current.source, target: computeFillTarget(current.source, { row, col }) });
|
|
1202
|
-
}, []);
|
|
1203
|
-
return { fillTarget: drag?.target ?? null, isFilling: dragging, startFill, extendFill };
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
// src/spreadsheet/FormulaHints.tsx
|
|
1207
|
-
import { useEffect as useEffect9, useLayoutEffect, useMemo as useMemo7, useRef as useRef10, useState as useState7 } from "react";
|
|
1208
|
-
import { applyFormulaSuggestion, getFormulaHints } from "@pixerate/editor";
|
|
1209
|
-
import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1210
|
-
var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
|
|
1211
|
-
const [caret, setCaret] = useState7(null);
|
|
1212
|
-
const [focused, setFocused] = useState7(false);
|
|
1213
|
-
const [selected, setSelected] = useState7(0);
|
|
1214
|
-
const [dismissed, setDismissed] = useState7(false);
|
|
1215
|
-
const [position, setPosition] = useState7(null);
|
|
1216
|
-
const pendingCaret = useRef10(null);
|
|
1217
|
-
const hints = useMemo7(
|
|
1218
|
-
() => getFormulaHints(value, caret ?? value.length, { limit }),
|
|
1219
|
-
[value, caret, limit]
|
|
1220
|
-
);
|
|
1221
|
-
const suggestions = dismissed ? [] : hints.suggestions;
|
|
1222
|
-
const selectedIndex = Math.max(0, Math.min(selected, suggestions.length - 1));
|
|
1223
|
-
const visible = focused && value.trimStart().startsWith("=") && (suggestions.length > 0 || !!hints.activeCall);
|
|
1224
|
-
const latest = useRef10({ suggestions, selected: selectedIndex, value, caret, onAccept });
|
|
1225
|
-
latest.current = { suggestions, selected: selectedIndex, value, caret, onAccept };
|
|
1226
|
-
const accept = (index) => {
|
|
1227
|
-
const { suggestions: list, value: current, caret: at, onAccept: commit } = latest.current;
|
|
1228
|
-
const suggestion = list[index];
|
|
1229
|
-
if (!suggestion) return;
|
|
1230
|
-
const next = applyFormulaSuggestion(current, at ?? current.length, suggestion.name);
|
|
1231
|
-
pendingCaret.current = next.caret;
|
|
1232
|
-
commit(next.value);
|
|
1233
|
-
};
|
|
1234
|
-
useEffect9(() => {
|
|
1235
|
-
const input = inputRef.current;
|
|
1236
|
-
if (!input) return;
|
|
1237
|
-
const syncCaret = () => setCaret(input.selectionStart ?? input.value.length);
|
|
1238
|
-
const onInput = () => {
|
|
1239
|
-
setDismissed(false);
|
|
1240
|
-
setSelected(0);
|
|
1241
|
-
syncCaret();
|
|
1242
|
-
};
|
|
1243
|
-
const onFocus = () => {
|
|
1244
|
-
setFocused(true);
|
|
1245
|
-
syncCaret();
|
|
1246
|
-
};
|
|
1247
|
-
const onBlur = () => setFocused(false);
|
|
1248
|
-
const onKeyDown = (e) => {
|
|
1249
|
-
const { suggestions: list } = latest.current;
|
|
1250
|
-
if (list.length === 0) return;
|
|
1251
|
-
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
1252
|
-
e.preventDefault();
|
|
1253
|
-
e.stopPropagation();
|
|
1254
|
-
const step = e.key === "ArrowDown" ? 1 : -1;
|
|
1255
|
-
setSelected((i) => (i + step + list.length) % list.length);
|
|
1256
|
-
} else if (e.key === "Tab" && !e.shiftKey) {
|
|
1257
|
-
e.preventDefault();
|
|
1258
|
-
e.stopPropagation();
|
|
1259
|
-
accept(latest.current.selected);
|
|
1260
|
-
} else if (e.key === "Escape") {
|
|
1261
|
-
e.preventDefault();
|
|
1262
|
-
e.stopPropagation();
|
|
1263
|
-
setDismissed(true);
|
|
1264
|
-
}
|
|
1265
|
-
};
|
|
1266
|
-
input.addEventListener("input", onInput);
|
|
1267
|
-
input.addEventListener("keyup", syncCaret);
|
|
1268
|
-
input.addEventListener("click", syncCaret);
|
|
1269
|
-
input.addEventListener("select", syncCaret);
|
|
1270
|
-
input.addEventListener("focus", onFocus);
|
|
1271
|
-
input.addEventListener("blur", onBlur);
|
|
1272
|
-
input.addEventListener("keydown", onKeyDown);
|
|
1273
|
-
if (typeof document !== "undefined" && document.activeElement === input) onFocus();
|
|
1274
|
-
return () => {
|
|
1275
|
-
input.removeEventListener("input", onInput);
|
|
1276
|
-
input.removeEventListener("keyup", syncCaret);
|
|
1277
|
-
input.removeEventListener("click", syncCaret);
|
|
1278
|
-
input.removeEventListener("select", syncCaret);
|
|
1279
|
-
input.removeEventListener("focus", onFocus);
|
|
1280
|
-
input.removeEventListener("blur", onBlur);
|
|
1281
|
-
input.removeEventListener("keydown", onKeyDown);
|
|
1282
|
-
};
|
|
1283
|
-
}, [inputRef]);
|
|
1284
|
-
useLayoutEffect(() => {
|
|
1285
|
-
const input = inputRef.current;
|
|
1286
|
-
const at = pendingCaret.current;
|
|
1287
|
-
if (!input || at === null || input.value !== value) return;
|
|
1288
|
-
pendingCaret.current = null;
|
|
1289
|
-
input.setSelectionRange(at, at);
|
|
1290
|
-
setCaret(at);
|
|
1291
|
-
setSelected(0);
|
|
1292
|
-
}, [value, inputRef]);
|
|
1293
|
-
useLayoutEffect(() => {
|
|
1294
|
-
const input = inputRef.current;
|
|
1295
|
-
if (!visible || !input) return;
|
|
1296
|
-
const rect = input.getBoundingClientRect();
|
|
1297
|
-
setPosition({ top: rect.bottom + 2, left: rect.left, width: rect.width });
|
|
1298
|
-
}, [visible, value, inputRef]);
|
|
1299
|
-
if (!visible) return null;
|
|
1300
|
-
const { activeCall } = hints;
|
|
1301
|
-
return /* @__PURE__ */ jsxs6(
|
|
1302
|
-
"div",
|
|
1303
|
-
{
|
|
1304
|
-
"data-formula-hints": "",
|
|
1305
|
-
className: "rounded-md border border-border bg-popover text-popover-foreground shadow-md text-xs font-sans overflow-hidden",
|
|
1306
|
-
style: {
|
|
1307
|
-
position: "fixed",
|
|
1308
|
-
top: position?.top ?? 0,
|
|
1309
|
-
left: position?.left ?? 0,
|
|
1310
|
-
minWidth: Math.max(260, position?.width ?? 0),
|
|
1311
|
-
maxWidth: 440,
|
|
1312
|
-
zIndex: 50,
|
|
1313
|
-
visibility: position ? "visible" : "hidden"
|
|
1314
|
-
},
|
|
1315
|
-
onMouseDown: (e) => e.preventDefault(),
|
|
1316
|
-
children: [
|
|
1317
|
-
activeCall && /* @__PURE__ */ jsxs6("div", { "data-formula-signature": "", className: "px-2 py-1.5 border-b border-border bg-muted/30", children: [
|
|
1318
|
-
/* @__PURE__ */ jsx7("div", { className: "font-mono", children: activeCall.parts.map((part, i) => /* @__PURE__ */ jsx7(
|
|
1319
|
-
"span",
|
|
1320
|
-
{
|
|
1321
|
-
"data-active": part.active ? "" : void 0,
|
|
1322
|
-
className: part.active ? "font-bold text-primary underline" : "",
|
|
1323
|
-
children: part.text
|
|
1324
|
-
},
|
|
1325
|
-
i
|
|
1326
|
-
)) }),
|
|
1327
|
-
/* @__PURE__ */ jsx7("div", { className: "mt-0.5 text-muted-foreground", children: activeCall.argument ? /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1328
|
-
/* @__PURE__ */ jsx7("span", { className: "font-semibold", children: activeCall.argument.name }),
|
|
1329
|
-
": ",
|
|
1330
|
-
activeCall.argument.description
|
|
1331
|
-
] }) : activeCall.description })
|
|
1332
|
-
] }),
|
|
1333
|
-
suggestions.length > 0 && /* @__PURE__ */ jsx7("ul", { role: "listbox", "aria-label": "Formula suggestions", className: "max-h-64 overflow-auto py-1 m-0 list-none p-0", children: suggestions.map((s, i) => /* @__PURE__ */ jsxs6(
|
|
1334
|
-
"li",
|
|
1335
|
-
{
|
|
1336
|
-
role: "option",
|
|
1337
|
-
"aria-selected": i === selectedIndex,
|
|
1338
|
-
className: `cursor-pointer px-2 py-1 ${i === selectedIndex ? "bg-primary/10" : "hover:bg-muted/50"}`,
|
|
1339
|
-
onMouseEnter: () => setSelected(i),
|
|
1340
|
-
onMouseDown: (e) => {
|
|
1341
|
-
e.preventDefault();
|
|
1342
|
-
accept(i);
|
|
1343
|
-
},
|
|
1344
|
-
children: [
|
|
1345
|
-
/* @__PURE__ */ jsx7("div", { className: "font-mono font-semibold text-foreground", children: s.signature }),
|
|
1346
|
-
/* @__PURE__ */ jsx7("div", { className: "text-muted-foreground truncate", children: s.description })
|
|
1347
|
-
]
|
|
1348
|
-
},
|
|
1349
|
-
s.name
|
|
1350
|
-
)) })
|
|
1351
|
-
]
|
|
1352
|
-
}
|
|
1353
|
-
);
|
|
1354
|
-
};
|
|
1355
|
-
|
|
1356
|
-
// src/spreadsheet/SpreadsheetEditor.tsx
|
|
1357
|
-
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1358
|
-
var COLUMN_HEADER_HEIGHT = 28;
|
|
1359
|
-
var ROW_HEADER_WIDTH = 48;
|
|
1360
|
-
function frozenCellStyle(viewRow, viewCol) {
|
|
1361
|
-
if (!viewRow.frozen && !viewCol.frozen) return void 0;
|
|
1362
|
-
return {
|
|
1363
|
-
position: "sticky",
|
|
1364
|
-
...viewRow.frozen ? { top: COLUMN_HEADER_HEIGHT + viewRow.stickyTop } : {},
|
|
1365
|
-
...viewCol.frozen ? { left: ROW_HEADER_WIDTH + viewCol.stickyLeft } : {},
|
|
1366
|
-
zIndex: viewRow.frozen && viewCol.frozen ? 14 : viewRow.frozen ? 12 : 11,
|
|
1367
|
-
background: "var(--background, #fff)"
|
|
1368
|
-
};
|
|
1369
|
-
}
|
|
1370
|
-
var SpreadsheetEditor = ({
|
|
1371
|
-
state: externalState,
|
|
1372
|
-
readOnly = false,
|
|
1373
|
-
customCellRenderer,
|
|
1374
|
-
onAddColumnClick,
|
|
1375
|
-
onColumnHeaderClick,
|
|
1376
|
-
onColumnResize
|
|
1377
|
-
}) => {
|
|
1378
|
-
const defaultState = useSpreadsheetEditor();
|
|
1379
|
-
const state = externalState || defaultState;
|
|
1380
|
-
const gridRef = useRef11(null);
|
|
1381
|
-
const formulaBarRef = useRef11(null);
|
|
1382
|
-
const cellInputRef = useRef11(null);
|
|
1383
|
-
const [resizingColId, setResizingColId] = useState8(null);
|
|
1384
|
-
const [hoveredColId, setHoveredColId] = useState8(null);
|
|
1385
|
-
const [resizeStartX, setResizeStartX] = useState8(0);
|
|
1386
|
-
const [resizeStartWidth, setResizeStartWidth] = useState8(0);
|
|
1387
|
-
const view = state.viewModel;
|
|
1388
|
-
const { copyShortcutFallback } = useSpreadsheetClipboard(gridRef, state, readOnly);
|
|
1389
|
-
const { fillTarget, startFill, extendFill } = useFillHandle(state);
|
|
1390
|
-
const [menu, setMenu] = useState8(null);
|
|
1391
|
-
const filter = state.document.table?.filter;
|
|
1392
|
-
const filteredColIds = useMemo8(
|
|
1393
|
-
() => new Set(filter?.enabled ? filter.rules.map((r) => r.colId) : []),
|
|
1394
|
-
[filter]
|
|
1395
|
-
);
|
|
1396
|
-
const handleCellContextMenu = useCallback6((rIdx, cIdx, e) => {
|
|
1397
|
-
if (readOnly) return;
|
|
1398
|
-
e.preventDefault();
|
|
1399
|
-
if (state.editingCell) state.commitEditing();
|
|
1400
|
-
if (!state.selectedRange || !rangeContains(state.selectedRange, rIdx, cIdx)) {
|
|
1401
|
-
state.selectCell(rIdx, cIdx);
|
|
1402
|
-
}
|
|
1403
|
-
setMenu({ x: e.clientX, y: e.clientY, items: getSpreadsheetMenuItems(state.controller, { row: rIdx, col: cIdx }) });
|
|
1404
|
-
}, [readOnly, state]);
|
|
1405
|
-
const activeRawValue = useMemo8(() => {
|
|
1406
|
-
if (!state.activeCell) return "";
|
|
1407
|
-
const row = state.document.rows[state.activeCell.row];
|
|
1408
|
-
const col = state.document.columns[state.activeCell.col];
|
|
1409
|
-
if (!row || !col) return "";
|
|
1410
|
-
return state.getCell(row.id, col.id).raw;
|
|
1411
|
-
}, [state.activeCell, state.document]);
|
|
1412
|
-
const coordLabel = useMemo8(() => {
|
|
1413
|
-
if (!state.activeCell) return "--";
|
|
1414
|
-
const colName = indexToColName(state.activeCell.col);
|
|
1415
|
-
return `${colName}${state.activeCell.row + 1}`;
|
|
1416
|
-
}, [state.activeCell]);
|
|
1417
|
-
const [formulaDraft, setFormulaDraft] = useState8(null);
|
|
1418
|
-
const activeColumn = state.activeCell ? state.document.columns[state.activeCell.col] : void 0;
|
|
1419
|
-
const formulaBarReadOnly = readOnly || !state.activeCell || !!activeColumn?.readOnly;
|
|
1420
|
-
useEffect10(() => {
|
|
1421
|
-
setFormulaDraft(null);
|
|
1422
|
-
}, [state.activeCell?.row, state.activeCell?.col]);
|
|
1423
|
-
const commitFormulaBar = () => {
|
|
1424
|
-
if (state.editingCell) {
|
|
1425
|
-
state.commitEditing();
|
|
1426
|
-
return;
|
|
1427
|
-
}
|
|
1428
|
-
if (formulaDraft === null || !state.activeCell) return;
|
|
1429
|
-
const row = state.document.rows[state.activeCell.row];
|
|
1430
|
-
const col = state.document.columns[state.activeCell.col];
|
|
1431
|
-
if (row && col && !formulaBarReadOnly && formulaDraft !== activeRawValue) {
|
|
1432
|
-
state.setCellValue(row.id, col.id, formulaDraft);
|
|
1433
|
-
}
|
|
1434
|
-
setFormulaDraft(null);
|
|
1435
|
-
};
|
|
1436
|
-
const handleCellClick = useCallback6((rIdx, cIdx, e) => {
|
|
1437
|
-
if (state.editingCell && (state.editingCell.row !== rIdx || state.editingCell.col !== cIdx)) {
|
|
1438
|
-
state.commitEditing();
|
|
1439
|
-
}
|
|
1440
|
-
state.selectCell(rIdx, cIdx, e.shiftKey);
|
|
1441
|
-
}, [state]);
|
|
1442
|
-
const handleCellDblClick = useCallback6((rIdx, cIdx) => {
|
|
1443
|
-
if (readOnly) return;
|
|
1444
|
-
const col = state.document.columns[cIdx];
|
|
1445
|
-
if (col?.readOnly) return;
|
|
1446
|
-
state.startEditing(rIdx, cIdx);
|
|
1447
|
-
}, [readOnly, state]);
|
|
1448
|
-
const handleKeyDown = useCallback6((e) => {
|
|
1449
|
-
if (state.editingCell) {
|
|
1450
|
-
if (e.key === "Enter") {
|
|
1451
|
-
e.preventDefault();
|
|
1452
|
-
state.commitEditing();
|
|
1453
|
-
state.moveCursor("down");
|
|
1454
|
-
} else if (e.key === "Escape") {
|
|
1455
|
-
e.preventDefault();
|
|
1456
|
-
state.cancelEditing();
|
|
1457
|
-
} else if (e.key === "Tab") {
|
|
1458
|
-
e.preventDefault();
|
|
1459
|
-
state.commitEditing();
|
|
1460
|
-
state.moveCursor(e.shiftKey ? "left" : "right");
|
|
1461
|
-
}
|
|
1462
|
-
return;
|
|
1463
|
-
}
|
|
1464
|
-
if (!state.activeCell) return;
|
|
1465
|
-
if (e.key === "ArrowUp") {
|
|
1466
|
-
e.preventDefault();
|
|
1467
|
-
state.moveCursor("up", e.shiftKey);
|
|
1468
|
-
} else if (e.key === "ArrowDown") {
|
|
1469
|
-
e.preventDefault();
|
|
1470
|
-
state.moveCursor("down", e.shiftKey);
|
|
1471
|
-
} else if (e.key === "ArrowLeft") {
|
|
1472
|
-
e.preventDefault();
|
|
1473
|
-
state.moveCursor("left", e.shiftKey);
|
|
1474
|
-
} else if (e.key === "ArrowRight") {
|
|
1475
|
-
e.preventDefault();
|
|
1476
|
-
state.moveCursor("right", e.shiftKey);
|
|
1477
|
-
} else if (e.key === "Tab") {
|
|
1478
|
-
e.preventDefault();
|
|
1479
|
-
state.moveCursor(e.shiftKey ? "left" : "right");
|
|
1480
|
-
} else if (e.key === "Enter") {
|
|
1481
|
-
e.preventDefault();
|
|
1482
|
-
const col = state.document.columns[state.activeCell.col];
|
|
1483
|
-
if (!readOnly && !col?.readOnly) {
|
|
1484
|
-
state.startEditing();
|
|
1485
|
-
}
|
|
1486
|
-
} else if (e.key === "Backspace" || e.key === "Delete") {
|
|
1487
|
-
if (readOnly) return;
|
|
1488
|
-
const row = state.document.rows[state.activeCell.row];
|
|
1489
|
-
const col = state.document.columns[state.activeCell.col];
|
|
1490
|
-
if (row && col && !col.readOnly) {
|
|
1491
|
-
e.preventDefault();
|
|
1492
|
-
state.setCellValue(row.id, col.id, "");
|
|
1493
|
-
}
|
|
1494
|
-
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "z") {
|
|
1495
|
-
e.preventDefault();
|
|
1496
|
-
if (e.shiftKey) {
|
|
1497
|
-
state.redo();
|
|
1498
|
-
} else {
|
|
1499
|
-
state.undo();
|
|
1500
|
-
}
|
|
1501
|
-
} else if (e.ctrlKey && !e.metaKey && e.key.toLowerCase() === "y") {
|
|
1502
|
-
e.preventDefault();
|
|
1503
|
-
state.redo();
|
|
1504
|
-
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "c") {
|
|
1505
|
-
copyShortcutFallback();
|
|
1506
|
-
} else if (!readOnly && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
1507
|
-
const col = state.document.columns[state.activeCell.col];
|
|
1508
|
-
if (!col?.readOnly) {
|
|
1509
|
-
state.startEditing(state.activeCell.row, state.activeCell.col, e.key);
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
}, [readOnly, state, copyShortcutFallback]);
|
|
1513
|
-
const stateRef = useRef11(state);
|
|
1514
|
-
stateRef.current = state;
|
|
1515
|
-
const onColumnResizeRef = useRef11(onColumnResize);
|
|
1516
|
-
onColumnResizeRef.current = onColumnResize;
|
|
1517
|
-
useEffect10(() => {
|
|
1518
|
-
if (!resizingColId) return;
|
|
1519
|
-
if (typeof document !== "undefined") {
|
|
1520
|
-
document.body.style.cursor = "col-resize";
|
|
1521
|
-
document.body.style.userSelect = "none";
|
|
1522
|
-
}
|
|
1523
|
-
let rafId = null;
|
|
1524
|
-
let pendingWidth = resizeStartWidth;
|
|
1525
|
-
let hasDragged = false;
|
|
1526
|
-
const handleMouseMove = (e) => {
|
|
1527
|
-
const delta = e.clientX - resizeStartX;
|
|
1528
|
-
if (Math.abs(delta) > 2) {
|
|
1529
|
-
hasDragged = true;
|
|
1530
|
-
}
|
|
1531
|
-
pendingWidth = Math.max(30, resizeStartWidth + delta);
|
|
1532
|
-
if (rafId === null) {
|
|
1533
|
-
rafId = requestAnimationFrame(() => {
|
|
1534
|
-
rafId = null;
|
|
1535
|
-
stateRef.current.setColumnWidth(resizingColId, pendingWidth);
|
|
1536
|
-
});
|
|
1537
|
-
}
|
|
1538
|
-
};
|
|
1539
|
-
const handleMouseUp = () => {
|
|
1540
|
-
if (rafId !== null) {
|
|
1541
|
-
cancelAnimationFrame(rafId);
|
|
1542
|
-
rafId = null;
|
|
1543
|
-
}
|
|
1544
|
-
if (hasDragged) {
|
|
1545
|
-
stateRef.current.setColumnWidth(resizingColId, pendingWidth);
|
|
1546
|
-
onColumnResizeRef.current?.(resizingColId, pendingWidth);
|
|
1547
|
-
}
|
|
1548
|
-
setResizingColId(null);
|
|
1549
|
-
if (typeof document !== "undefined") {
|
|
1550
|
-
document.body.style.cursor = "";
|
|
1551
|
-
document.body.style.userSelect = "";
|
|
1552
|
-
}
|
|
1553
|
-
};
|
|
1554
|
-
window.addEventListener("mousemove", handleMouseMove);
|
|
1555
|
-
window.addEventListener("mouseup", handleMouseUp);
|
|
1556
|
-
return () => {
|
|
1557
|
-
if (rafId !== null) {
|
|
1558
|
-
cancelAnimationFrame(rafId);
|
|
1559
|
-
}
|
|
1560
|
-
if (typeof document !== "undefined") {
|
|
1561
|
-
document.body.style.cursor = "";
|
|
1562
|
-
document.body.style.userSelect = "";
|
|
1563
|
-
}
|
|
1564
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
1565
|
-
window.removeEventListener("mouseup", handleMouseUp);
|
|
1566
|
-
};
|
|
1567
|
-
}, [resizingColId, resizeStartX, resizeStartWidth]);
|
|
1568
|
-
const measureElementContentWidth = useCallback6((el, container) => {
|
|
1569
|
-
if (typeof document === "undefined") return 0;
|
|
1570
|
-
let maxW = el.scrollWidth || 0;
|
|
1571
|
-
const firstChild = el.firstElementChild;
|
|
1572
|
-
if (firstChild && firstChild.scrollWidth > maxW) {
|
|
1573
|
-
maxW = firstChild.scrollWidth;
|
|
1574
|
-
}
|
|
1575
|
-
const truncated = el.querySelectorAll(".truncate, [title], span, div");
|
|
1576
|
-
truncated.forEach((node) => {
|
|
1577
|
-
if (node.scrollWidth > maxW) {
|
|
1578
|
-
maxW = node.scrollWidth;
|
|
1579
|
-
}
|
|
1580
|
-
});
|
|
1581
|
-
try {
|
|
1582
|
-
const clone = firstChild ? firstChild.cloneNode(true) : el.cloneNode(true);
|
|
1583
|
-
clone.style.cssText = `
|
|
1584
|
-
position: absolute !important;
|
|
1585
|
-
visibility: hidden !important;
|
|
1586
|
-
width: max-content !important;
|
|
1587
|
-
min-width: max-content !important;
|
|
1588
|
-
max-width: none !important;
|
|
1589
|
-
overflow: visible !important;
|
|
1590
|
-
white-space: nowrap !important;
|
|
1591
|
-
pointer-events: none !important;
|
|
1592
|
-
top: -9999px !important;
|
|
1593
|
-
left: -9999px !important;
|
|
1594
|
-
`;
|
|
1595
|
-
const allNodes = clone.querySelectorAll("*");
|
|
1596
|
-
allNodes.forEach((node) => {
|
|
1597
|
-
node.style.maxWidth = "none";
|
|
1598
|
-
node.style.overflow = "visible";
|
|
1599
|
-
node.style.textOverflow = "clip";
|
|
1600
|
-
node.style.whiteSpace = "nowrap";
|
|
1601
|
-
if (node.classList.contains("truncate") || node.classList.contains("min-w-0") || node.classList.contains("w-full") || node.classList.contains("max-w-0") || node.classList.contains("flex-1")) {
|
|
1602
|
-
node.style.width = "max-content";
|
|
1603
|
-
node.style.minWidth = "max-content";
|
|
1604
|
-
}
|
|
1605
|
-
});
|
|
1606
|
-
container.appendChild(clone);
|
|
1607
|
-
const rect = clone.getBoundingClientRect();
|
|
1608
|
-
container.removeChild(clone);
|
|
1609
|
-
if (rect.width > maxW) {
|
|
1610
|
-
maxW = rect.width;
|
|
1611
|
-
}
|
|
1612
|
-
} catch {
|
|
1613
|
-
}
|
|
1614
|
-
return maxW;
|
|
1615
|
-
}, []);
|
|
1616
|
-
const autoFitColumn = useCallback6((colId, colIndex) => {
|
|
1617
|
-
const col = state.document.columns.find((c) => c.id === colId);
|
|
1618
|
-
if (!col) return;
|
|
1619
|
-
let calculatedWidth = 0;
|
|
1620
|
-
if (gridRef.current && typeof document !== "undefined") {
|
|
1621
|
-
const table = gridRef.current.querySelector("table");
|
|
1622
|
-
if (table) {
|
|
1623
|
-
const th = table.querySelector(`thead th[data-col="${colIndex}"]`);
|
|
1624
|
-
if (th) {
|
|
1625
|
-
const headerInner = th.firstElementChild || th;
|
|
1626
|
-
const headerW = measureElementContentWidth(headerInner, gridRef.current);
|
|
1627
|
-
calculatedWidth = Math.max(calculatedWidth, headerW + 28);
|
|
1628
|
-
}
|
|
1629
|
-
const rows = table.querySelectorAll("tbody tr");
|
|
1630
|
-
const sampleLimit = Math.min(rows.length, 150);
|
|
1631
|
-
for (let r = 0; r < sampleLimit; r++) {
|
|
1632
|
-
const tr = rows[r];
|
|
1633
|
-
const td = tr.querySelector(`td[data-col="${colIndex}"][colspan="1"]`);
|
|
1634
|
-
if (td) {
|
|
1635
|
-
const cellW = measureElementContentWidth(td, gridRef.current);
|
|
1636
|
-
calculatedWidth = Math.max(calculatedWidth, cellW + 20);
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
if (calculatedWidth > 0) {
|
|
1642
|
-
const finalWidth = Math.min(600, Math.max(50, Math.ceil(calculatedWidth)));
|
|
1643
|
-
state.setColumnWidth(colId, finalWidth);
|
|
1644
|
-
} else {
|
|
1645
|
-
state.autoFitColumnWidth?.(colId);
|
|
1646
|
-
}
|
|
1647
|
-
const updatedCol = state.document.columns.find((c) => c.id === colId);
|
|
1648
|
-
if (updatedCol && onColumnResize) {
|
|
1649
|
-
onColumnResize(updatedCol.id, updatedCol.width || 130);
|
|
1650
|
-
}
|
|
1651
|
-
}, [state, measureElementContentWidth, onColumnResize]);
|
|
1652
|
-
const handleResizeDblClick = useCallback6((colId, colIndex, e) => {
|
|
1653
|
-
e.preventDefault();
|
|
1654
|
-
e.stopPropagation();
|
|
1655
|
-
autoFitColumn(colId, colIndex);
|
|
1656
|
-
}, [autoFitColumn]);
|
|
1657
|
-
const handleHeaderDblClick = useCallback6((colId, colIndex, e) => {
|
|
1658
|
-
const target = e.currentTarget;
|
|
1659
|
-
if (target) {
|
|
1660
|
-
const rect = target.getBoundingClientRect();
|
|
1661
|
-
if (rect.right - e.clientX <= 12) {
|
|
1662
|
-
e.preventDefault();
|
|
1663
|
-
e.stopPropagation();
|
|
1664
|
-
autoFitColumn(colId, colIndex);
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
}, [autoFitColumn]);
|
|
1668
|
-
const isCellSelected = (rIdx, cIdx) => {
|
|
1669
|
-
if (!state.selectedRange) return false;
|
|
1670
|
-
const minR = Math.min(state.selectedRange.startRow, state.selectedRange.endRow);
|
|
1671
|
-
const maxR = Math.max(state.selectedRange.startRow, state.selectedRange.endRow);
|
|
1672
|
-
const minC = Math.min(state.selectedRange.startCol, state.selectedRange.endCol);
|
|
1673
|
-
const maxC = Math.max(state.selectedRange.startCol, state.selectedRange.endCol);
|
|
1674
|
-
return rIdx >= minR && rIdx <= maxR && cIdx >= minC && cIdx <= maxC;
|
|
1675
|
-
};
|
|
1676
|
-
const isActiveCell = (rIdx, cIdx) => {
|
|
1677
|
-
return state.activeCell?.row === rIdx && state.activeCell?.col === cIdx;
|
|
1678
|
-
};
|
|
1679
|
-
return /* @__PURE__ */ jsxs7(
|
|
1680
|
-
"div",
|
|
1681
|
-
{
|
|
1682
|
-
ref: gridRef,
|
|
1683
|
-
className: "flex flex-col h-full w-full overflow-hidden border border-border bg-background select-none outline-none text-foreground font-sans text-xs",
|
|
1684
|
-
tabIndex: 0,
|
|
1685
|
-
onKeyDown: handleKeyDown,
|
|
1686
|
-
role: "grid",
|
|
1687
|
-
"aria-label": "Spreadsheet",
|
|
1688
|
-
children: [
|
|
1689
|
-
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 border-b border-border bg-muted/20 px-3 py-1.5 text-xs font-mono", children: [
|
|
1690
|
-
/* @__PURE__ */ jsx8("div", { className: "flex h-6 min-w-[3rem] items-center justify-center rounded border border-border bg-background px-1.5 font-semibold text-foreground", children: coordLabel }),
|
|
1691
|
-
/* @__PURE__ */ jsx8("div", { className: "flex items-center text-muted-foreground select-none", children: /* @__PURE__ */ jsx8("span", { className: "italic font-serif font-bold text-sm text-primary", children: "fx" }) }),
|
|
1692
|
-
/* @__PURE__ */ jsx8(
|
|
1693
|
-
"input",
|
|
1694
|
-
{
|
|
1695
|
-
ref: formulaBarRef,
|
|
1696
|
-
type: "text",
|
|
1697
|
-
className: "flex-1 bg-transparent px-2 py-0.5 outline-none font-sans text-xs text-foreground placeholder:text-muted-foreground/60 focus:bg-background focus:ring-1 focus:ring-primary rounded",
|
|
1698
|
-
placeholder: "Enter a value or formula (e.g. =SUM(A1:A5))",
|
|
1699
|
-
"aria-label": "Formula bar",
|
|
1700
|
-
readOnly: formulaBarReadOnly,
|
|
1701
|
-
value: state.editingCell ? state.draftValue : formulaDraft ?? activeRawValue,
|
|
1702
|
-
onFocus: () => {
|
|
1703
|
-
if (!state.editingCell) setFormulaDraft(activeRawValue);
|
|
1704
|
-
},
|
|
1705
|
-
onChange: (e) => {
|
|
1706
|
-
if (state.editingCell) {
|
|
1707
|
-
state.setDraftValue(e.target.value);
|
|
1708
|
-
} else if (state.activeCell) {
|
|
1709
|
-
setFormulaDraft(e.target.value);
|
|
1710
|
-
}
|
|
1711
|
-
},
|
|
1712
|
-
onBlur: () => commitFormulaBar(),
|
|
1713
|
-
onKeyDown: (e) => {
|
|
1714
|
-
e.stopPropagation();
|
|
1715
|
-
if (e.key === "Enter") {
|
|
1716
|
-
e.preventDefault();
|
|
1717
|
-
commitFormulaBar();
|
|
1718
|
-
} else if (e.key === "Escape") {
|
|
1719
|
-
e.preventDefault();
|
|
1720
|
-
setFormulaDraft(null);
|
|
1721
|
-
if (state.editingCell) state.cancelEditing();
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
),
|
|
1726
|
-
!formulaBarReadOnly && /* @__PURE__ */ jsx8(
|
|
1727
|
-
FormulaHints,
|
|
1728
|
-
{
|
|
1729
|
-
inputRef: formulaBarRef,
|
|
1730
|
-
value: state.editingCell ? state.draftValue : formulaDraft ?? activeRawValue,
|
|
1731
|
-
onAccept: (next) => state.editingCell ? state.setDraftValue(next) : setFormulaDraft(next)
|
|
1732
|
-
}
|
|
1733
|
-
)
|
|
1734
|
-
] }),
|
|
1735
|
-
/* @__PURE__ */ jsx8("div", { className: "relative flex-1 overflow-auto", children: /* @__PURE__ */ jsxs7("table", { className: "border-collapse table-fixed w-max min-w-full", children: [
|
|
1736
|
-
/* @__PURE__ */ jsx8("thead", { children: /* @__PURE__ */ jsxs7("tr", { className: "h-7 bg-muted/60 sticky top-0 z-20 border-b border-border shadow-xs", children: [
|
|
1737
|
-
/* @__PURE__ */ jsx8("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: "#" }),
|
|
1738
|
-
view.columns.map(({ column: col, index: cIdx, frozen, stickyLeft }) => /* @__PURE__ */ jsxs7(
|
|
1739
|
-
"th",
|
|
1740
|
-
{
|
|
1741
|
-
"data-col": cIdx,
|
|
1742
|
-
className: `relative border-r border-border px-2 text-left font-medium text-muted-foreground hover:bg-muted/90 transition-colors group overflow-hidden max-w-0 ${onColumnHeaderClick ? "cursor-pointer" : "cursor-default"}`,
|
|
1743
|
-
style: {
|
|
1744
|
-
width: col.width || 130,
|
|
1745
|
-
minWidth: col.width || 130,
|
|
1746
|
-
maxWidth: col.width || 130,
|
|
1747
|
-
overflow: "hidden",
|
|
1748
|
-
...frozen ? { position: "sticky", left: ROW_HEADER_WIDTH + stickyLeft, zIndex: 25, background: "var(--muted, #f4f4f5)" } : {}
|
|
1749
|
-
},
|
|
1750
|
-
onClick: () => onColumnHeaderClick?.(col),
|
|
1751
|
-
onDoubleClick: (e) => handleHeaderDblClick(col.id, cIdx, e),
|
|
1752
|
-
children: [
|
|
1753
|
-
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between gap-1 min-w-0 overflow-hidden truncate", children: [
|
|
1754
|
-
/* @__PURE__ */ jsx8("span", { className: "truncate font-semibold text-foreground text-xs min-w-0", children: col.title }),
|
|
1755
|
-
filteredColIds.has(col.id) && /* @__PURE__ */ jsx8("span", { className: "text-[10px] text-primary shrink-0", title: "Filtered", "data-filtered": "true", children: "\u23F7" }),
|
|
1756
|
-
/* @__PURE__ */ jsxs7("span", { className: "text-[10px] text-muted-foreground/70 font-mono font-normal shrink-0", children: [
|
|
1757
|
-
"(",
|
|
1758
|
-
col.key,
|
|
1759
|
-
")"
|
|
1760
|
-
] })
|
|
1761
|
-
] }),
|
|
1762
|
-
/* @__PURE__ */ jsx8(
|
|
1763
|
-
"div",
|
|
1764
|
-
{
|
|
1765
|
-
className: "col-resize-handle",
|
|
1766
|
-
style: { position: "absolute", top: 0, bottom: 0, width: 12, right: -6, cursor: "col-resize", zIndex: 20, display: "flex", justifyContent: "center", touchAction: "none" },
|
|
1767
|
-
onMouseEnter: () => setHoveredColId(col.id),
|
|
1768
|
-
onMouseLeave: () => setHoveredColId((cur) => cur === col.id ? null : cur),
|
|
1769
|
-
onMouseDown: (e) => {
|
|
1770
|
-
e.preventDefault();
|
|
1771
|
-
e.stopPropagation();
|
|
1772
|
-
setResizingColId(col.id);
|
|
1773
|
-
setResizeStartX(e.clientX);
|
|
1774
|
-
setResizeStartWidth(col.width || 130);
|
|
1775
|
-
},
|
|
1776
|
-
onDoubleClick: (e) => handleResizeDblClick(col.id, cIdx, e),
|
|
1777
|
-
role: "separator",
|
|
1778
|
-
"aria-orientation": "vertical",
|
|
1779
|
-
children: /* @__PURE__ */ jsx8(
|
|
1780
|
-
"div",
|
|
1781
|
-
{
|
|
1782
|
-
className: "col-resize-handle-line",
|
|
1783
|
-
style: {
|
|
1784
|
-
width: 2,
|
|
1785
|
-
height: "100%",
|
|
1786
|
-
pointerEvents: "none",
|
|
1787
|
-
transition: "background-color 150ms ease",
|
|
1788
|
-
backgroundColor: resizingColId === col.id ? "var(--primary, #6366f1)" : hoveredColId === col.id ? "color-mix(in srgb, var(--primary, #6366f1) 75%, transparent)" : "transparent"
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
)
|
|
1792
|
-
}
|
|
1793
|
-
)
|
|
1794
|
-
]
|
|
1795
|
-
},
|
|
1796
|
-
col.id
|
|
1797
|
-
)),
|
|
1798
|
-
onAddColumnClick && /* @__PURE__ */ jsx8(
|
|
1799
|
-
"th",
|
|
1800
|
-
{
|
|
1801
|
-
className: "w-10 min-w-[2.5rem] px-1 text-center bg-muted/40 hover:bg-muted cursor-pointer transition-colors",
|
|
1802
|
-
onClick: onAddColumnClick,
|
|
1803
|
-
title: "Add Column",
|
|
1804
|
-
children: /* @__PURE__ */ jsx8("span", { className: "text-xs font-bold text-muted-foreground", children: "+" })
|
|
1805
|
-
}
|
|
1806
|
-
)
|
|
1807
|
-
] }) }),
|
|
1808
|
-
/* @__PURE__ */ jsx8("tbody", { children: view.rows.map((viewRow) => {
|
|
1809
|
-
if (viewRow.kind === "group") {
|
|
1810
|
-
return /* @__PURE__ */ jsx8(
|
|
1811
|
-
SpreadsheetGroupRow,
|
|
1812
|
-
{
|
|
1813
|
-
group: viewRow.group,
|
|
1814
|
-
columns: view.columns,
|
|
1815
|
-
groupColId: state.document.table?.grouping?.colId ?? "",
|
|
1816
|
-
onToggle: state.toggleGroupCollapsed,
|
|
1817
|
-
trailingCell: !!onAddColumnClick
|
|
1818
|
-
},
|
|
1819
|
-
`group:${viewRow.group.key}`
|
|
1820
|
-
);
|
|
1821
|
-
}
|
|
1822
|
-
const displayRowIndex = viewRow.index;
|
|
1823
|
-
const selection = state.selectedRange ? normalizeRange2(state.selectedRange) : null;
|
|
1824
|
-
return /* @__PURE__ */ jsxs7(
|
|
1825
|
-
"tr",
|
|
1826
|
-
{
|
|
1827
|
-
"data-row": displayRowIndex,
|
|
1828
|
-
className: `border-b border-border hover:bg-muted/10 transition-colors ${viewRow.section !== "body" ? "bg-muted/30 font-semibold" : ""}`,
|
|
1829
|
-
style: { height: viewRow.row.height || 32 },
|
|
1830
|
-
children: [
|
|
1831
|
-
/* @__PURE__ */ jsx8(
|
|
1832
|
-
"td",
|
|
1833
|
-
{
|
|
1834
|
-
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",
|
|
1835
|
-
style: viewRow.frozen ? { top: COLUMN_HEADER_HEIGHT + viewRow.stickyTop, zIndex: 15 } : void 0,
|
|
1836
|
-
children: displayRowIndex + 1
|
|
1837
|
-
}
|
|
1838
|
-
),
|
|
1839
|
-
view.columns.map((viewCol) => {
|
|
1840
|
-
const span = view.getCellSpan(displayRowIndex, viewCol.index);
|
|
1841
|
-
if (!span) return null;
|
|
1842
|
-
const rIdx = span.source.row;
|
|
1843
|
-
const cIdx = span.source.col;
|
|
1844
|
-
const row = state.document.rows[rIdx];
|
|
1845
|
-
const col = state.document.columns[cIdx];
|
|
1846
|
-
const cell = state.getCell(row.id, col.id);
|
|
1847
|
-
const selected = isCellSelected(rIdx, cIdx);
|
|
1848
|
-
const active = isActiveCell(rIdx, cIdx);
|
|
1849
|
-
const isEditing = state.editingCell?.row === rIdx && state.editingCell?.col === cIdx;
|
|
1850
|
-
const inFillPreview = !!fillTarget && !selected && rangeContains(fillTarget, displayRowIndex, viewCol.index);
|
|
1851
|
-
const showFillHandle = !readOnly && !isEditing && !!selection && (span.merge ? rangeContains(span.merge, selection.endRow, selection.endCol) : selection.endRow === rIdx && selection.endCol === cIdx);
|
|
1852
|
-
return /* @__PURE__ */ jsxs7(
|
|
1853
|
-
"td",
|
|
1854
|
-
{
|
|
1855
|
-
"data-row": rIdx,
|
|
1856
|
-
"data-col": cIdx,
|
|
1857
|
-
rowSpan: span.rowSpan,
|
|
1858
|
-
colSpan: span.colSpan,
|
|
1859
|
-
className: `relative border-r border-border px-2 py-1 truncate max-w-0 text-foreground transition-colors ${selected ? "bg-primary/10" : ""} ${active ? "ring-2 ring-primary ring-inset z-10" : ""} ${viewCol.header ? "font-semibold" : ""}`,
|
|
1860
|
-
style: {
|
|
1861
|
-
maxWidth: 0,
|
|
1862
|
-
overflow: "hidden",
|
|
1863
|
-
...frozenCellStyle(viewRow, viewCol),
|
|
1864
|
-
...inFillPreview ? { outline: "1px dashed var(--primary, #6366f1)", outlineOffset: -1 } : {}
|
|
1865
|
-
},
|
|
1866
|
-
onClick: (e) => handleCellClick(rIdx, cIdx, e),
|
|
1867
|
-
onDoubleClick: () => handleCellDblClick(rIdx, cIdx),
|
|
1868
|
-
onContextMenu: (e) => handleCellContextMenu(rIdx, cIdx, e),
|
|
1869
|
-
onMouseOver: () => extendFill(displayRowIndex, viewCol.index),
|
|
1870
|
-
children: [
|
|
1871
|
-
isEditing ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
1872
|
-
/* @__PURE__ */ jsx8(
|
|
1873
|
-
"input",
|
|
1874
|
-
{
|
|
1875
|
-
ref: cellInputRef,
|
|
1876
|
-
type: "text",
|
|
1877
|
-
className: "absolute inset-0 w-full h-full px-2 py-1 bg-background text-foreground outline-none font-sans text-xs border-2 border-primary z-20",
|
|
1878
|
-
value: state.draftValue,
|
|
1879
|
-
onChange: (e) => state.setDraftValue(e.target.value),
|
|
1880
|
-
onBlur: () => state.commitEditing(),
|
|
1881
|
-
autoFocus: true
|
|
1882
|
-
}
|
|
1883
|
-
),
|
|
1884
|
-
/* @__PURE__ */ jsx8(FormulaHints, { inputRef: cellInputRef, value: state.draftValue, onAccept: state.setDraftValue })
|
|
1885
|
-
] }) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ jsx8(
|
|
1886
|
-
"span",
|
|
1887
|
-
{
|
|
1888
|
-
className: `truncate block max-w-full ${cell.error ? "text-destructive font-semibold" : ""}`,
|
|
1889
|
-
title: cell.error ? cell.errorMessage ?? cell.error : void 0,
|
|
1890
|
-
children: cell.error || (cell.value ?? "")
|
|
1891
|
-
}
|
|
1892
|
-
),
|
|
1893
|
-
showFillHandle && /* @__PURE__ */ jsx8(
|
|
1894
|
-
"div",
|
|
1895
|
-
{
|
|
1896
|
-
className: "spreadsheet-fill-handle",
|
|
1897
|
-
"aria-label": "Fill handle",
|
|
1898
|
-
onMouseDown: startFill,
|
|
1899
|
-
onClick: (e) => e.stopPropagation(),
|
|
1900
|
-
style: { position: "absolute", right: 0, bottom: 0, width: 7, height: 7, background: "var(--primary, #6366f1)", cursor: "crosshair", zIndex: 25 }
|
|
1901
|
-
}
|
|
1902
|
-
)
|
|
1903
|
-
]
|
|
1904
|
-
},
|
|
1905
|
-
viewCol.column.id
|
|
1906
|
-
);
|
|
1907
|
-
}),
|
|
1908
|
-
onAddColumnClick && /* @__PURE__ */ jsx8("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
|
|
1909
|
-
]
|
|
1910
|
-
},
|
|
1911
|
-
viewRow.row.id
|
|
1912
|
-
);
|
|
1913
|
-
}) })
|
|
1914
|
-
] }) }),
|
|
1915
|
-
menu && /* @__PURE__ */ jsx8(
|
|
1916
|
-
SpreadsheetContextMenu,
|
|
1917
|
-
{
|
|
1918
|
-
items: menu.items,
|
|
1919
|
-
x: menu.x,
|
|
1920
|
-
y: menu.y,
|
|
1921
|
-
onClose: () => {
|
|
1922
|
-
setMenu(null);
|
|
1923
|
-
gridRef.current?.focus();
|
|
1924
|
-
}
|
|
1925
|
-
}
|
|
1926
|
-
),
|
|
1927
|
-
/* @__PURE__ */ jsx8("style", { children: `
|
|
1928
|
-
.col-resize-handle:hover .col-resize-handle-line {
|
|
1929
|
-
background-color: var(--primary, #6366f1);
|
|
1930
|
-
opacity: 0.75;
|
|
1931
|
-
}
|
|
1932
|
-
.col-resize-handle.active .col-resize-handle-line {
|
|
1933
|
-
background-color: var(--primary, #6366f1);
|
|
1934
|
-
opacity: 1;
|
|
1935
|
-
}
|
|
1936
|
-
table.table-fixed th {
|
|
1937
|
-
overflow: hidden;
|
|
1938
|
-
}
|
|
1939
|
-
table.table-fixed td {
|
|
1940
|
-
max-width: 0;
|
|
1941
|
-
overflow: hidden;
|
|
1942
|
-
}
|
|
1943
|
-
` })
|
|
1944
|
-
]
|
|
1945
|
-
}
|
|
1946
|
-
);
|
|
1947
|
-
};
|
|
1948
|
-
|
|
1949
865
|
// src/slots.tsx
|
|
1950
|
-
import { createContext, useContext, useMemo as
|
|
1951
|
-
import { jsx as
|
|
866
|
+
import { createContext, useContext, useMemo as useMemo6 } from "react";
|
|
867
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1952
868
|
var defaultSlots = {
|
|
1953
|
-
Button: (props) => /* @__PURE__ */
|
|
869
|
+
Button: (props) => /* @__PURE__ */ jsx5(
|
|
1954
870
|
"button",
|
|
1955
871
|
{
|
|
1956
872
|
type: "button",
|
|
@@ -1960,26 +876,26 @@ var defaultSlots = {
|
|
|
1960
876
|
children: props.children
|
|
1961
877
|
}
|
|
1962
878
|
),
|
|
1963
|
-
Tooltip: (props) => /* @__PURE__ */
|
|
1964
|
-
NodeHeader: (props) => /* @__PURE__ */
|
|
1965
|
-
/* @__PURE__ */
|
|
879
|
+
Tooltip: (props) => /* @__PURE__ */ jsx5("div", { className: `pixerate-tooltip-container ${props.className || ""}`, title: String(props.content), children: props.children }),
|
|
880
|
+
NodeHeader: (props) => /* @__PURE__ */ jsxs4("div", { className: `pixerate-node-header flex items-center justify-between ${props.className || ""}`, children: [
|
|
881
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
|
|
1966
882
|
props.icon,
|
|
1967
|
-
/* @__PURE__ */
|
|
883
|
+
/* @__PURE__ */ jsx5("span", { className: "font-semibold", children: props.title })
|
|
1968
884
|
] }),
|
|
1969
|
-
props.onClose && /* @__PURE__ */
|
|
885
|
+
props.onClose && /* @__PURE__ */ jsx5("button", { type: "button", onClick: props.onClose, className: "opacity-70 hover:opacity-100", children: "\xD7" })
|
|
1970
886
|
] })
|
|
1971
887
|
};
|
|
1972
888
|
var EditorSlotsContext = createContext(defaultSlots);
|
|
1973
889
|
function EditorSlotsProvider({ slots, children }) {
|
|
1974
|
-
const mergedSlots =
|
|
1975
|
-
return /* @__PURE__ */
|
|
890
|
+
const mergedSlots = useMemo6(() => ({ ...defaultSlots, ...slots }), [slots]);
|
|
891
|
+
return /* @__PURE__ */ jsx5(EditorSlotsContext.Provider, { value: mergedSlots, children });
|
|
1976
892
|
}
|
|
1977
893
|
function useEditorSlots() {
|
|
1978
894
|
return useContext(EditorSlotsContext);
|
|
1979
895
|
}
|
|
1980
896
|
|
|
1981
897
|
// src/review/useReview.ts
|
|
1982
|
-
import { useCallback as
|
|
898
|
+
import { useCallback as useCallback4, useMemo as useMemo7, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
1983
899
|
import {
|
|
1984
900
|
getCommentStore,
|
|
1985
901
|
getTrackChangesStore
|
|
@@ -2001,23 +917,23 @@ function isCommentStore(value) {
|
|
|
2001
917
|
}
|
|
2002
918
|
function useComments(source) {
|
|
2003
919
|
const store = isCommentStore(source) ? source : getCommentStore(source);
|
|
2004
|
-
const subscribe =
|
|
920
|
+
const subscribe = useCallback4(
|
|
2005
921
|
(onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
|
|
2006
922
|
[store]
|
|
2007
923
|
);
|
|
2008
924
|
const getSnapshot = () => store?.getSnapshot() ?? EMPTY_COMMENTS;
|
|
2009
925
|
const snapshot = useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
|
|
2010
|
-
return
|
|
926
|
+
return useMemo7(() => ({ ...snapshot, store }), [snapshot, store]);
|
|
2011
927
|
}
|
|
2012
928
|
function useTrackChanges(editor) {
|
|
2013
929
|
const store = getTrackChangesStore(editor ?? null);
|
|
2014
|
-
const subscribe =
|
|
930
|
+
const subscribe = useCallback4(
|
|
2015
931
|
(onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
|
|
2016
932
|
[store]
|
|
2017
933
|
);
|
|
2018
934
|
const getSnapshot = () => store?.getSnapshot() ?? EMPTY_TRACK_CHANGES;
|
|
2019
935
|
const snapshot = useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
|
|
2020
|
-
return
|
|
936
|
+
return useMemo7(() => {
|
|
2021
937
|
const run = (fn) => () => {
|
|
2022
938
|
if (editor && !editor.isDestroyed) fn(editor);
|
|
2023
939
|
};
|
|
@@ -2037,12 +953,12 @@ function useTrackChanges(editor) {
|
|
|
2037
953
|
}
|
|
2038
954
|
|
|
2039
955
|
// src/review/CommentsPanel.tsx
|
|
2040
|
-
import { useState as
|
|
956
|
+
import { useState as useState5 } from "react";
|
|
2041
957
|
import {
|
|
2042
958
|
formatReviewTime,
|
|
2043
959
|
getCommentAuthor
|
|
2044
960
|
} from "@pixerate/editor";
|
|
2045
|
-
import { jsx as
|
|
961
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2046
962
|
var buttonClass = "rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50";
|
|
2047
963
|
var primaryButtonClass = "rounded px-2 py-1 text-xs bg-primary/10 text-primary hover:bg-primary/20 disabled:opacity-50";
|
|
2048
964
|
var textareaClass = "w-full resize-y rounded-md border border-input bg-background p-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary";
|
|
@@ -2056,18 +972,18 @@ var CommentsPanel = ({
|
|
|
2056
972
|
emptyMessage = "No comments yet. Select text and add a comment."
|
|
2057
973
|
}) => {
|
|
2058
974
|
const { threads, activeThreadId, store } = useComments(storeProp ?? editor);
|
|
2059
|
-
const [showResolved, setShowResolved] =
|
|
975
|
+
const [showResolved, setShowResolved] = useState5(showResolvedInitial);
|
|
2060
976
|
const author = authorProp ?? getCommentAuthor(editor) ?? { id: "anonymous", name: "Anonymous" };
|
|
2061
977
|
const visible = threads.filter((t) => showResolved || !t.resolved);
|
|
2062
978
|
const resolvedCount = threads.filter((t) => t.resolved).length;
|
|
2063
|
-
return /* @__PURE__ */
|
|
2064
|
-
/* @__PURE__ */
|
|
2065
|
-
/* @__PURE__ */
|
|
979
|
+
return /* @__PURE__ */ jsxs5("aside", { className, "aria-label": "Comments", children: [
|
|
980
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between gap-2", children: [
|
|
981
|
+
/* @__PURE__ */ jsxs5("span", { className: "text-xs font-medium text-muted-foreground", children: [
|
|
2066
982
|
threads.length - resolvedCount,
|
|
2067
983
|
" open",
|
|
2068
984
|
resolvedCount > 0 ? ` \xB7 ${resolvedCount} resolved` : ""
|
|
2069
985
|
] }),
|
|
2070
|
-
resolvedCount > 0 && /* @__PURE__ */
|
|
986
|
+
resolvedCount > 0 && /* @__PURE__ */ jsx6(
|
|
2071
987
|
"button",
|
|
2072
988
|
{
|
|
2073
989
|
type: "button",
|
|
@@ -2078,7 +994,7 @@ var CommentsPanel = ({
|
|
|
2078
994
|
}
|
|
2079
995
|
)
|
|
2080
996
|
] }),
|
|
2081
|
-
visible.length === 0 ? /* @__PURE__ */
|
|
997
|
+
visible.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ jsx6("ul", { className: "flex flex-col gap-2", role: "list", children: visible.map((thread) => /* @__PURE__ */ jsx6(
|
|
2082
998
|
CommentThreadCard,
|
|
2083
999
|
{
|
|
2084
1000
|
thread,
|
|
@@ -2100,7 +1016,7 @@ var CommentThreadCard = ({
|
|
|
2100
1016
|
author,
|
|
2101
1017
|
formatTime
|
|
2102
1018
|
}) => {
|
|
2103
|
-
const [draft, setDraft] =
|
|
1019
|
+
const [draft, setDraft] = useState5("");
|
|
2104
1020
|
const isDraft = thread.body === "";
|
|
2105
1021
|
const quote = thread.anchor?.quote ?? thread.lastQuote ?? "";
|
|
2106
1022
|
const live = editor && !editor.isDestroyed ? editor : null;
|
|
@@ -2126,7 +1042,7 @@ var CommentThreadCard = ({
|
|
|
2126
1042
|
setDraft("");
|
|
2127
1043
|
};
|
|
2128
1044
|
const stop = (e) => e.stopPropagation();
|
|
2129
|
-
return /* @__PURE__ */
|
|
1045
|
+
return /* @__PURE__ */ jsxs5(
|
|
2130
1046
|
"li",
|
|
2131
1047
|
{
|
|
2132
1048
|
className: `flex flex-col gap-2 rounded-md border bg-background p-3 cursor-pointer ${active ? "border-primary ring-1 ring-primary" : "border-border"} ${thread.resolved ? "opacity-70" : ""}`,
|
|
@@ -2142,24 +1058,24 @@ var CommentThreadCard = ({
|
|
|
2142
1058
|
}
|
|
2143
1059
|
},
|
|
2144
1060
|
children: [
|
|
2145
|
-
/* @__PURE__ */
|
|
2146
|
-
/* @__PURE__ */
|
|
2147
|
-
/* @__PURE__ */
|
|
1061
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-baseline justify-between gap-2", children: [
|
|
1062
|
+
/* @__PURE__ */ jsx6("span", { className: "font-medium", children: thread.author.name }),
|
|
1063
|
+
/* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: formatTime(thread.createdAt) })
|
|
2148
1064
|
] }),
|
|
2149
|
-
quote && /* @__PURE__ */
|
|
2150
|
-
thread.orphaned && /* @__PURE__ */
|
|
2151
|
-
thread.resolved && /* @__PURE__ */
|
|
2152
|
-
!isDraft && /* @__PURE__ */
|
|
2153
|
-
thread.replies.length > 0 && /* @__PURE__ */
|
|
2154
|
-
/* @__PURE__ */
|
|
2155
|
-
/* @__PURE__ */
|
|
1065
|
+
quote && /* @__PURE__ */ jsx6("blockquote", { className: "border-l-2 border-border pl-2 text-xs text-muted-foreground line-clamp-2", children: quote }),
|
|
1066
|
+
thread.orphaned && /* @__PURE__ */ jsx6("span", { className: "text-xs text-destructive", children: "The commented text was deleted." }),
|
|
1067
|
+
thread.resolved && /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: "Resolved" }),
|
|
1068
|
+
!isDraft && /* @__PURE__ */ jsx6("p", { className: "whitespace-pre-wrap break-words", children: thread.body }),
|
|
1069
|
+
thread.replies.length > 0 && /* @__PURE__ */ jsx6("ul", { className: "flex flex-col gap-2 border-l border-border pl-3", role: "list", children: thread.replies.map((reply) => /* @__PURE__ */ jsxs5("li", { className: "flex flex-col gap-0.5", "data-reply-id": reply.id, children: [
|
|
1070
|
+
/* @__PURE__ */ jsxs5("span", { className: "text-xs", children: [
|
|
1071
|
+
/* @__PURE__ */ jsx6("span", { className: "font-medium", children: reply.author.name }),
|
|
2156
1072
|
" ",
|
|
2157
|
-
/* @__PURE__ */
|
|
1073
|
+
/* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", children: formatTime(reply.createdAt) })
|
|
2158
1074
|
] }),
|
|
2159
|
-
/* @__PURE__ */
|
|
1075
|
+
/* @__PURE__ */ jsx6("p", { className: "whitespace-pre-wrap break-words", children: reply.body })
|
|
2160
1076
|
] }, reply.id)) }),
|
|
2161
|
-
(active || isDraft) && !thread.resolved && /* @__PURE__ */
|
|
2162
|
-
/* @__PURE__ */
|
|
1077
|
+
(active || isDraft) && !thread.resolved && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", onClick: stop, children: [
|
|
1078
|
+
/* @__PURE__ */ jsx6(
|
|
2163
1079
|
"textarea",
|
|
2164
1080
|
{
|
|
2165
1081
|
className: textareaClass,
|
|
@@ -2177,9 +1093,9 @@ var CommentThreadCard = ({
|
|
|
2177
1093
|
}
|
|
2178
1094
|
}
|
|
2179
1095
|
),
|
|
2180
|
-
/* @__PURE__ */
|
|
2181
|
-
isDraft && /* @__PURE__ */
|
|
2182
|
-
/* @__PURE__ */
|
|
1096
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex justify-end gap-1", children: [
|
|
1097
|
+
isDraft && /* @__PURE__ */ jsx6("button", { type: "button", className: buttonClass, onClick: remove, children: "Cancel" }),
|
|
1098
|
+
/* @__PURE__ */ jsx6(
|
|
2183
1099
|
"button",
|
|
2184
1100
|
{
|
|
2185
1101
|
type: "button",
|
|
@@ -2191,9 +1107,9 @@ var CommentThreadCard = ({
|
|
|
2191
1107
|
)
|
|
2192
1108
|
] })
|
|
2193
1109
|
] }),
|
|
2194
|
-
!isDraft && /* @__PURE__ */
|
|
2195
|
-
/* @__PURE__ */
|
|
2196
|
-
/* @__PURE__ */
|
|
1110
|
+
!isDraft && /* @__PURE__ */ jsxs5("div", { className: "flex justify-end gap-1", onClick: stop, children: [
|
|
1111
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: buttonClass, onClick: () => setResolved(!thread.resolved), children: thread.resolved ? "Reopen" : "Resolve" }),
|
|
1112
|
+
/* @__PURE__ */ jsx6(
|
|
2197
1113
|
"button",
|
|
2198
1114
|
{
|
|
2199
1115
|
type: "button",
|
|
@@ -2209,18 +1125,21 @@ var CommentThreadCard = ({
|
|
|
2209
1125
|
};
|
|
2210
1126
|
|
|
2211
1127
|
// src/review/ReviewToolbar.tsx
|
|
2212
|
-
import { jsx as
|
|
1128
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1129
|
+
var defaultDescriptionClass = "max-w-[16rem] truncate px-1 text-xs text-muted-foreground";
|
|
2213
1130
|
var buttonClass2 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
|
|
2214
1131
|
var ReviewToolbar = ({
|
|
2215
1132
|
editor,
|
|
2216
|
-
className = "flex flex-wrap items-center gap-1 rounded-md border border-border bg-background p-1 text-sm"
|
|
1133
|
+
className = "flex flex-wrap items-center gap-1 rounded-md border border-border bg-background p-1 text-sm",
|
|
1134
|
+
showDescription = true,
|
|
1135
|
+
descriptionClassName = defaultDescriptionClass
|
|
2217
1136
|
}) => {
|
|
2218
1137
|
const review = useTrackChanges(editor);
|
|
2219
1138
|
const count = review.changes.length;
|
|
2220
1139
|
const current = review.currentChange;
|
|
2221
1140
|
const index = current ? review.changes.findIndex((c) => c.id === current.id) + 1 : 0;
|
|
2222
|
-
return /* @__PURE__ */
|
|
2223
|
-
/* @__PURE__ */
|
|
1141
|
+
return /* @__PURE__ */ jsxs6("div", { className, role: "toolbar", "aria-label": "Review", children: [
|
|
1142
|
+
/* @__PURE__ */ jsx7(
|
|
2224
1143
|
"button",
|
|
2225
1144
|
{
|
|
2226
1145
|
type: "button",
|
|
@@ -2231,8 +1150,8 @@ var ReviewToolbar = ({
|
|
|
2231
1150
|
children: review.enabled ? "Tracking on" : "Track changes"
|
|
2232
1151
|
}
|
|
2233
1152
|
),
|
|
2234
|
-
/* @__PURE__ */
|
|
2235
|
-
/* @__PURE__ */
|
|
1153
|
+
/* @__PURE__ */ jsx7("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
|
|
1154
|
+
/* @__PURE__ */ jsx7(
|
|
2236
1155
|
"button",
|
|
2237
1156
|
{
|
|
2238
1157
|
type: "button",
|
|
@@ -2243,8 +1162,8 @@ var ReviewToolbar = ({
|
|
|
2243
1162
|
children: "\u2039"
|
|
2244
1163
|
}
|
|
2245
1164
|
),
|
|
2246
|
-
/* @__PURE__ */
|
|
2247
|
-
/* @__PURE__ */
|
|
1165
|
+
/* @__PURE__ */ jsx7("span", { className: "min-w-[4rem] text-center text-xs text-muted-foreground", "aria-live": "polite", children: count === 0 ? "No changes" : current ? `${index} of ${count}` : `${count} changes` }),
|
|
1166
|
+
/* @__PURE__ */ jsx7(
|
|
2248
1167
|
"button",
|
|
2249
1168
|
{
|
|
2250
1169
|
type: "button",
|
|
@@ -2255,8 +1174,20 @@ var ReviewToolbar = ({
|
|
|
2255
1174
|
children: "\u203A"
|
|
2256
1175
|
}
|
|
2257
1176
|
),
|
|
2258
|
-
/* @__PURE__ */
|
|
2259
|
-
|
|
1177
|
+
showDescription && current && /* @__PURE__ */ jsxs6(
|
|
1178
|
+
"span",
|
|
1179
|
+
{
|
|
1180
|
+
className: descriptionClassName,
|
|
1181
|
+
"data-change-type": current.type,
|
|
1182
|
+
title: `${current.author.name}: ${current.description}`,
|
|
1183
|
+
children: [
|
|
1184
|
+
current.author.name ? `${current.author.name}: ` : "",
|
|
1185
|
+
current.description
|
|
1186
|
+
]
|
|
1187
|
+
}
|
|
1188
|
+
),
|
|
1189
|
+
/* @__PURE__ */ jsx7("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
|
|
1190
|
+
/* @__PURE__ */ jsx7(
|
|
2260
1191
|
"button",
|
|
2261
1192
|
{
|
|
2262
1193
|
type: "button",
|
|
@@ -2266,7 +1197,7 @@ var ReviewToolbar = ({
|
|
|
2266
1197
|
children: "Accept"
|
|
2267
1198
|
}
|
|
2268
1199
|
),
|
|
2269
|
-
/* @__PURE__ */
|
|
1200
|
+
/* @__PURE__ */ jsx7(
|
|
2270
1201
|
"button",
|
|
2271
1202
|
{
|
|
2272
1203
|
type: "button",
|
|
@@ -2276,18 +1207,18 @@ var ReviewToolbar = ({
|
|
|
2276
1207
|
children: "Reject"
|
|
2277
1208
|
}
|
|
2278
1209
|
),
|
|
2279
|
-
/* @__PURE__ */
|
|
2280
|
-
/* @__PURE__ */
|
|
1210
|
+
/* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.acceptAll, children: "Accept all" }),
|
|
1211
|
+
/* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.rejectAll, children: "Reject all" })
|
|
2281
1212
|
] });
|
|
2282
1213
|
};
|
|
2283
1214
|
|
|
2284
1215
|
// src/review/CommentButton.tsx
|
|
2285
|
-
import { jsx as
|
|
1216
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
2286
1217
|
var CommentButton = ({
|
|
2287
1218
|
editor,
|
|
2288
1219
|
className = "rounded px-2 py-1 text-xs text-popover-foreground hover:bg-muted",
|
|
2289
1220
|
children = "Comment"
|
|
2290
|
-
}) => /* @__PURE__ */
|
|
1221
|
+
}) => /* @__PURE__ */ jsx8(
|
|
2291
1222
|
"button",
|
|
2292
1223
|
{
|
|
2293
1224
|
type: "button",
|