@pixerate/editor-react 0.11.0 → 0.12.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 +25 -1
- package/dist/canvas/index.cjs +283 -36
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +135 -3
- package/dist/canvas/index.d.ts +135 -3
- package/dist/canvas/index.js +251 -1
- package/dist/canvas/index.js.map +1 -1
- package/dist/index.cjs +1424 -389
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +292 -7
- package/dist/index.d.ts +292 -7
- package/dist/index.js +1159 -118
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
plainTextToTipTapHtml,
|
|
17
17
|
plainTextToSlice,
|
|
18
18
|
getEditorText,
|
|
19
|
-
tokenizePrompt
|
|
19
|
+
tokenizePrompt,
|
|
20
|
+
FileImport
|
|
20
21
|
} from "@pixerate/editor";
|
|
21
22
|
var DEFAULT_TEMPLATES = [];
|
|
22
23
|
var DEFAULT_COLOR_MAP = /* @__PURE__ */ new Map([
|
|
@@ -41,8 +42,12 @@ function usePromptEditor({
|
|
|
41
42
|
onMilestone,
|
|
42
43
|
containerId = "editor-container",
|
|
43
44
|
jsonVariables,
|
|
44
|
-
speed = 2
|
|
45
|
+
speed = 2,
|
|
46
|
+
fileImport
|
|
45
47
|
}) {
|
|
48
|
+
const fileImportRef = useRef(fileImport);
|
|
49
|
+
fileImportRef.current = fileImport;
|
|
50
|
+
const fileImportEnabled = !!fileImport;
|
|
46
51
|
const slashCommandActiveRef = useRef(slashCommandActive);
|
|
47
52
|
slashCommandActiveRef.current = slashCommandActive;
|
|
48
53
|
const clearSlashCommandRef = useRef(clearSlashCommand);
|
|
@@ -97,7 +102,14 @@ function usePromptEditor({
|
|
|
97
102
|
TemplateSuggestions.configure({
|
|
98
103
|
templates
|
|
99
104
|
}),
|
|
100
|
-
LoadingNode
|
|
105
|
+
LoadingNode,
|
|
106
|
+
...fileImportEnabled ? [
|
|
107
|
+
FileImport.configure({
|
|
108
|
+
...typeof fileImportRef.current === "object" ? fileImportRef.current : {},
|
|
109
|
+
// Prompt editors only ever receive plain text.
|
|
110
|
+
asPlainText: true
|
|
111
|
+
})
|
|
112
|
+
] : []
|
|
101
113
|
],
|
|
102
114
|
content: plainTextToTipTapHtml(content),
|
|
103
115
|
onUpdate: ({ editor: ed }) => {
|
|
@@ -194,7 +206,7 @@ function usePromptEditor({
|
|
|
194
206
|
},
|
|
195
207
|
// Only options baked into extensions recreate the editor; editable state,
|
|
196
208
|
// placeholder, class names and content are applied to the live instance.
|
|
197
|
-
[resolvedColorMap, templateKey, speed, jsonVariablesKey]
|
|
209
|
+
[resolvedColorMap, templateKey, speed, jsonVariablesKey, fileImportEnabled]
|
|
198
210
|
);
|
|
199
211
|
useEffect(() => {
|
|
200
212
|
if (editor && !editor.isDestroyed) {
|
|
@@ -240,10 +252,22 @@ function usePromptEditor({
|
|
|
240
252
|
|
|
241
253
|
// src/hooks/useEditor.ts
|
|
242
254
|
import { useEditor as useTipTapEditor } from "@tiptap/react";
|
|
243
|
-
import {
|
|
255
|
+
import {
|
|
256
|
+
createRichTextPreset
|
|
257
|
+
} from "@pixerate/editor";
|
|
244
258
|
function useEditor2(options = {}) {
|
|
245
|
-
const {
|
|
246
|
-
|
|
259
|
+
const {
|
|
260
|
+
richTextOptions,
|
|
261
|
+
fileImport,
|
|
262
|
+
richClipboard,
|
|
263
|
+
extensions = [],
|
|
264
|
+
...editorOptions
|
|
265
|
+
} = options;
|
|
266
|
+
const preset = createRichTextPreset({
|
|
267
|
+
...fileImport !== void 0 ? { fileImport } : {},
|
|
268
|
+
...richClipboard !== void 0 ? { richClipboard } : {},
|
|
269
|
+
...richTextOptions
|
|
270
|
+
});
|
|
247
271
|
const combinedExtensions = [...preset, ...extensions];
|
|
248
272
|
return useTipTapEditor({
|
|
249
273
|
extensions: combinedExtensions,
|
|
@@ -473,6 +497,42 @@ function useHistoryShortcuts(manager, options = {}) {
|
|
|
473
497
|
}, [manager, enabled, target, ignoreEditableElements]);
|
|
474
498
|
}
|
|
475
499
|
|
|
500
|
+
// src/hooks/useFileImport.ts
|
|
501
|
+
import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef5, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
502
|
+
import {
|
|
503
|
+
FileImportController
|
|
504
|
+
} from "@pixerate/editor";
|
|
505
|
+
function useFileImport(editor, options = {}) {
|
|
506
|
+
const editorRef = useRef5(editor);
|
|
507
|
+
editorRef.current = editor;
|
|
508
|
+
const controller = useMemo2(
|
|
509
|
+
() => new FileImportController(() => {
|
|
510
|
+
const current = editorRef.current;
|
|
511
|
+
return current && !current.isDestroyed ? current : null;
|
|
512
|
+
}, options),
|
|
513
|
+
[]
|
|
514
|
+
);
|
|
515
|
+
useEffect4(() => {
|
|
516
|
+
controller.setOptions(options);
|
|
517
|
+
});
|
|
518
|
+
const state = useSyncExternalStore2(controller.subscribe, controller.getState, controller.getState);
|
|
519
|
+
return {
|
|
520
|
+
...state,
|
|
521
|
+
accept: controller.accept,
|
|
522
|
+
openFilePicker: controller.openFilePicker,
|
|
523
|
+
importFiles: controller.importFiles
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/hooks/useAgentToolProvider.ts
|
|
528
|
+
import { useEffect as useEffect5 } from "react";
|
|
529
|
+
function useAgentToolProvider(registry, provider) {
|
|
530
|
+
useEffect5(() => {
|
|
531
|
+
if (!registry || !provider) return;
|
|
532
|
+
return registry.register(provider);
|
|
533
|
+
}, [registry, provider]);
|
|
534
|
+
}
|
|
535
|
+
|
|
476
536
|
// src/components/EditorContent.tsx
|
|
477
537
|
import {
|
|
478
538
|
EditorContent as TipTapEditorContent
|
|
@@ -487,7 +547,7 @@ var EditorContent = ({
|
|
|
487
547
|
};
|
|
488
548
|
|
|
489
549
|
// src/components/BubbleMenu.tsx
|
|
490
|
-
import { useEffect as
|
|
550
|
+
import { useEffect as useEffect6, useRef as useRef6, useState as useState3 } from "react";
|
|
491
551
|
import { createPortal } from "react-dom";
|
|
492
552
|
import {
|
|
493
553
|
BubbleMenuPlugin
|
|
@@ -501,12 +561,12 @@ var BubbleMenu = ({
|
|
|
501
561
|
pluginKey = "bubbleMenu",
|
|
502
562
|
updateDelay
|
|
503
563
|
}) => {
|
|
504
|
-
const menuEl =
|
|
564
|
+
const menuEl = useRef6(null);
|
|
505
565
|
const [mounted, setMounted] = useState3(false);
|
|
506
566
|
if (!menuEl.current && typeof document !== "undefined") {
|
|
507
567
|
menuEl.current = document.createElement("div");
|
|
508
568
|
}
|
|
509
|
-
|
|
569
|
+
useEffect6(() => {
|
|
510
570
|
if (!editor || !menuEl.current || editor.isDestroyed) {
|
|
511
571
|
return;
|
|
512
572
|
}
|
|
@@ -537,28 +597,31 @@ var BubbleMenu = ({
|
|
|
537
597
|
};
|
|
538
598
|
|
|
539
599
|
// src/components/TemplateRenderer.tsx
|
|
540
|
-
import { useMemo as
|
|
600
|
+
import { useMemo as useMemo3 } from "react";
|
|
541
601
|
import {
|
|
542
|
-
|
|
602
|
+
evaluatePromptTokens,
|
|
543
603
|
resolveTemplates
|
|
544
604
|
} from "@pixerate/editor";
|
|
545
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
605
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
606
|
+
var CONDITIONAL_PREFIX = { if: "?", unless: "!", end: "/" };
|
|
546
607
|
var TemplateRenderer = ({
|
|
547
608
|
content,
|
|
548
609
|
templates = [],
|
|
549
610
|
templateColorMap,
|
|
550
611
|
resolve = false,
|
|
612
|
+
bindings,
|
|
613
|
+
values,
|
|
551
614
|
className = "font-mono text-sm leading-relaxed whitespace-pre-wrap break-words",
|
|
552
615
|
onTemplateClick,
|
|
553
616
|
onVariableClick
|
|
554
617
|
}) => {
|
|
555
|
-
const displayText =
|
|
618
|
+
const displayText = useMemo3(() => {
|
|
556
619
|
if (!resolve || templates.length === 0) return content;
|
|
557
|
-
return resolveTemplates(content, templates);
|
|
558
|
-
}, [content, templates, resolve]);
|
|
559
|
-
const tokens =
|
|
560
|
-
return
|
|
561
|
-
}, [displayText]);
|
|
620
|
+
return resolveTemplates(content, templates, { bindings });
|
|
621
|
+
}, [content, templates, resolve, bindings]);
|
|
622
|
+
const tokens = useMemo3(() => {
|
|
623
|
+
return evaluatePromptTokens(displayText, values);
|
|
624
|
+
}, [displayText, values]);
|
|
562
625
|
return /* @__PURE__ */ jsx2("div", { className, children: tokens.map((token, index) => {
|
|
563
626
|
if (token.type === "template") {
|
|
564
627
|
const colors = templateColorMap?.get(token.value) || {
|
|
@@ -582,13 +645,49 @@ var TemplateRenderer = ({
|
|
|
582
645
|
);
|
|
583
646
|
}
|
|
584
647
|
if (token.type === "variable") {
|
|
585
|
-
|
|
648
|
+
const details = [
|
|
649
|
+
`Variable: ${token.value}`,
|
|
650
|
+
token.description,
|
|
651
|
+
token.defaultValue !== void 0 ? `Default: ${token.defaultValue || "(empty)"}` : void 0
|
|
652
|
+
].filter(Boolean).join("\n");
|
|
653
|
+
if (token.resolvedValue !== void 0) {
|
|
654
|
+
return /* @__PURE__ */ jsx2(
|
|
655
|
+
"span",
|
|
656
|
+
{
|
|
657
|
+
onClick: () => onVariableClick?.(token.value),
|
|
658
|
+
"data-variable": token.value,
|
|
659
|
+
"data-value-source": token.valueSource,
|
|
660
|
+
className: token.valueSource === "value" ? "inline px-1 mx-0.5 rounded-md bg-emerald-400/15 text-emerald-700 dark:text-emerald-300 border-b border-emerald-400/60 cursor-pointer" : "inline px-1 mx-0.5 rounded-md bg-violet-400/10 text-violet-600 dark:text-violet-300 italic border-b border-dashed border-violet-400/60 cursor-pointer",
|
|
661
|
+
title: details,
|
|
662
|
+
children: token.resolvedValue
|
|
663
|
+
},
|
|
664
|
+
index
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
return /* @__PURE__ */ jsxs(
|
|
586
668
|
"span",
|
|
587
669
|
{
|
|
588
670
|
onClick: () => onVariableClick?.(token.value),
|
|
671
|
+
"data-variable": token.value,
|
|
589
672
|
className: "inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs font-medium rounded-md border border-violet-400/40 bg-violet-400/15 text-violet-600 dark:text-violet-300 hover:brightness-110 cursor-pointer transition-all",
|
|
590
|
-
title:
|
|
591
|
-
children:
|
|
673
|
+
title: details,
|
|
674
|
+
children: [
|
|
675
|
+
`{${token.value}`,
|
|
676
|
+
token.defaultValue !== void 0 && /* @__PURE__ */ jsx2("span", { className: "opacity-60", children: `|${token.defaultValue}` }),
|
|
677
|
+
"}"
|
|
678
|
+
]
|
|
679
|
+
},
|
|
680
|
+
index
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
if (token.type === "conditional" && token.conditional) {
|
|
684
|
+
return /* @__PURE__ */ jsx2(
|
|
685
|
+
"span",
|
|
686
|
+
{
|
|
687
|
+
"data-conditional": token.conditional,
|
|
688
|
+
className: "inline-flex items-center px-1 py-0.5 mx-0.5 text-xs font-medium rounded-md border border-amber-400/40 bg-amber-400/15 text-amber-700 dark:text-amber-300",
|
|
689
|
+
title: token.conditional === "end" ? `End of section: ${token.value}` : token.conditional === "if" ? `Shown when "${token.value}" has a value` : `Shown when "${token.value}" is empty`,
|
|
690
|
+
children: `{${CONDITIONAL_PREFIX[token.conditional]}${token.value}}`
|
|
592
691
|
},
|
|
593
692
|
index
|
|
594
693
|
);
|
|
@@ -608,6 +707,143 @@ var TemplateRenderer = ({
|
|
|
608
707
|
}) });
|
|
609
708
|
};
|
|
610
709
|
|
|
710
|
+
// src/components/VariableForm.tsx
|
|
711
|
+
import { useId, useMemo as useMemo4, useState as useState4 } from "react";
|
|
712
|
+
import {
|
|
713
|
+
compactVariableValues,
|
|
714
|
+
getVariableFormFields
|
|
715
|
+
} from "@pixerate/editor";
|
|
716
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
717
|
+
var VariableForm = ({
|
|
718
|
+
content,
|
|
719
|
+
templates,
|
|
720
|
+
bindings,
|
|
721
|
+
values,
|
|
722
|
+
defaultValues,
|
|
723
|
+
onChange,
|
|
724
|
+
className = "flex flex-col gap-3 text-sm"
|
|
725
|
+
}) => {
|
|
726
|
+
const idPrefix = useId();
|
|
727
|
+
const [internal, setInternal] = useState4(
|
|
728
|
+
() => defaultValues ?? {}
|
|
729
|
+
);
|
|
730
|
+
const entered = values ?? internal;
|
|
731
|
+
const fields = useMemo4(
|
|
732
|
+
() => getVariableFormFields(content, { templates, bindings }),
|
|
733
|
+
[content, templates, bindings]
|
|
734
|
+
);
|
|
735
|
+
const update = (name, value) => {
|
|
736
|
+
const next = { ...entered, [name]: value };
|
|
737
|
+
if (values === void 0) setInternal(next);
|
|
738
|
+
onChange?.(compactVariableValues(next), next);
|
|
739
|
+
};
|
|
740
|
+
if (fields.length === 0) return null;
|
|
741
|
+
return /* @__PURE__ */ jsx3("form", { className, onSubmit: (event) => event.preventDefault(), children: fields.map((field) => {
|
|
742
|
+
const id = `${idPrefix}-${field.name}`;
|
|
743
|
+
if (field.conditionOnly) {
|
|
744
|
+
return /* @__PURE__ */ jsxs2(
|
|
745
|
+
"label",
|
|
746
|
+
{
|
|
747
|
+
htmlFor: id,
|
|
748
|
+
className: "flex items-center gap-2",
|
|
749
|
+
"data-variable-field": field.name,
|
|
750
|
+
children: [
|
|
751
|
+
/* @__PURE__ */ jsx3(
|
|
752
|
+
"input",
|
|
753
|
+
{
|
|
754
|
+
id,
|
|
755
|
+
name: field.name,
|
|
756
|
+
type: "checkbox",
|
|
757
|
+
checked: (entered[field.name] ?? "") !== "",
|
|
758
|
+
onChange: (event) => update(field.name, event.target.checked ? "true" : "")
|
|
759
|
+
}
|
|
760
|
+
),
|
|
761
|
+
/* @__PURE__ */ jsx3("span", { children: field.label })
|
|
762
|
+
]
|
|
763
|
+
},
|
|
764
|
+
field.name
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
return /* @__PURE__ */ jsxs2(
|
|
768
|
+
"label",
|
|
769
|
+
{
|
|
770
|
+
htmlFor: id,
|
|
771
|
+
className: "flex flex-col gap-1",
|
|
772
|
+
"data-variable-field": field.name,
|
|
773
|
+
children: [
|
|
774
|
+
/* @__PURE__ */ jsxs2("span", { className: "font-medium", children: [
|
|
775
|
+
field.label,
|
|
776
|
+
field.required && /* @__PURE__ */ jsx3("span", { className: "text-rose-500", "aria-hidden": "true", children: " *" })
|
|
777
|
+
] }),
|
|
778
|
+
/* @__PURE__ */ jsx3(
|
|
779
|
+
"input",
|
|
780
|
+
{
|
|
781
|
+
id,
|
|
782
|
+
name: field.name,
|
|
783
|
+
type: "text",
|
|
784
|
+
value: entered[field.name] ?? "",
|
|
785
|
+
placeholder: field.placeholder,
|
|
786
|
+
"aria-required": field.required || void 0,
|
|
787
|
+
onChange: (event) => update(field.name, event.target.value),
|
|
788
|
+
className: "px-2 py-1 rounded-md border border-violet-400/40 bg-transparent"
|
|
789
|
+
}
|
|
790
|
+
)
|
|
791
|
+
]
|
|
792
|
+
},
|
|
793
|
+
field.name
|
|
794
|
+
);
|
|
795
|
+
}) });
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
// src/hooks/useOutline.ts
|
|
799
|
+
import { useMemo as useMemo5, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
800
|
+
import { createOutlineStore } from "@pixerate/editor";
|
|
801
|
+
function useOutline(editor) {
|
|
802
|
+
const store = useMemo5(() => createOutlineStore(editor), [editor]);
|
|
803
|
+
return useSyncExternalStore3(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// src/components/OutlinePanel.tsx
|
|
807
|
+
import { navigateToHeading } from "@pixerate/editor";
|
|
808
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
809
|
+
var OutlinePanel = ({
|
|
810
|
+
editor,
|
|
811
|
+
title = "Outline",
|
|
812
|
+
emptyState = "Add headings to see the outline.",
|
|
813
|
+
maxLevel = 6,
|
|
814
|
+
focusOnNavigate = true,
|
|
815
|
+
className = "flex flex-col gap-1 text-sm",
|
|
816
|
+
itemClassName = "w-full truncate rounded px-2 py-1 text-left hover:bg-black/5",
|
|
817
|
+
activeItemClassName = "font-semibold bg-black/5",
|
|
818
|
+
onNavigate
|
|
819
|
+
}) => {
|
|
820
|
+
const { headings, activeId } = useOutline(editor);
|
|
821
|
+
const visible = headings.filter((h) => h.level <= maxLevel);
|
|
822
|
+
return /* @__PURE__ */ jsxs3("nav", { className, "aria-label": "Document outline", "data-outline-panel": "", children: [
|
|
823
|
+
title != null && /* @__PURE__ */ jsx4("div", { className: "pixerate-outline-title", children: title }),
|
|
824
|
+
visible.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "pixerate-outline-empty", children: emptyState }) : /* @__PURE__ */ jsx4("ul", { role: "list", style: { listStyle: "none", margin: 0, padding: 0 }, children: visible.map((heading) => {
|
|
825
|
+
const active = heading.id === activeId;
|
|
826
|
+
return /* @__PURE__ */ jsx4("li", { "data-level": heading.level, children: /* @__PURE__ */ jsx4(
|
|
827
|
+
"button",
|
|
828
|
+
{
|
|
829
|
+
type: "button",
|
|
830
|
+
className: active ? `${itemClassName} ${activeItemClassName}` : itemClassName,
|
|
831
|
+
style: { paddingLeft: `${0.5 + heading.depth * 0.75}rem` },
|
|
832
|
+
"aria-current": active ? "location" : void 0,
|
|
833
|
+
"data-heading-id": heading.id,
|
|
834
|
+
onClick: () => {
|
|
835
|
+
if (!editor) return;
|
|
836
|
+
if (navigateToHeading(editor, heading, { focus: focusOnNavigate })) {
|
|
837
|
+
onNavigate?.(heading);
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
children: heading.text || "Untitled"
|
|
841
|
+
}
|
|
842
|
+
) }, heading.id);
|
|
843
|
+
}) })
|
|
844
|
+
] });
|
|
845
|
+
};
|
|
846
|
+
|
|
611
847
|
// src/index.ts
|
|
612
848
|
import {
|
|
613
849
|
DirtyTracker,
|
|
@@ -618,15 +854,16 @@ import {
|
|
|
618
854
|
} from "@pixerate/editor";
|
|
619
855
|
|
|
620
856
|
// src/spreadsheet/useSpreadsheetEditor.ts
|
|
621
|
-
import { useState as
|
|
857
|
+
import { useState as useState5, useRef as useRef7, useCallback as useCallback4, useMemo as useMemo6 } from "react";
|
|
622
858
|
import {
|
|
623
|
-
SpreadsheetController
|
|
859
|
+
SpreadsheetController,
|
|
860
|
+
bindSpreadsheetTableActions
|
|
624
861
|
} from "@pixerate/editor";
|
|
625
862
|
function useSpreadsheetEditor(options = {}) {
|
|
626
|
-
const optionsRef =
|
|
863
|
+
const optionsRef = useRef7(options);
|
|
627
864
|
optionsRef.current = options;
|
|
628
|
-
const settersRef =
|
|
629
|
-
const [controller] =
|
|
865
|
+
const settersRef = useRef7({});
|
|
866
|
+
const [controller] = useState5(
|
|
630
867
|
() => new SpreadsheetController({
|
|
631
868
|
...options,
|
|
632
869
|
onDocumentChange: (updated) => {
|
|
@@ -643,11 +880,11 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
643
880
|
}
|
|
644
881
|
})
|
|
645
882
|
);
|
|
646
|
-
const [doc, setDoc] =
|
|
647
|
-
const [activeCell, setActiveCell] =
|
|
648
|
-
const [selectedRange, setSelectedRange] =
|
|
649
|
-
const [editingCell, setEditingCell] =
|
|
650
|
-
const [draftValue, setDraftValue] =
|
|
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("");
|
|
651
888
|
settersRef.current = { setDoc, setActiveCell, setSelectedRange };
|
|
652
889
|
const setCellValue = useCallback4((rowId, colId, raw) => {
|
|
653
890
|
controller.setCellValue(rowId, colId, raw);
|
|
@@ -728,6 +965,8 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
728
965
|
controller.redo();
|
|
729
966
|
setDoc({ ...controller.document });
|
|
730
967
|
}, [controller]);
|
|
968
|
+
const tableActions = useMemo6(() => bindSpreadsheetTableActions(controller), [controller]);
|
|
969
|
+
const viewModel = useMemo6(() => controller.getViewModel(), [controller, doc]);
|
|
731
970
|
return {
|
|
732
971
|
document: doc,
|
|
733
972
|
activeCell,
|
|
@@ -771,17 +1010,363 @@ function useSpreadsheetEditor(options = {}) {
|
|
|
771
1010
|
controller.importFromTsv(data);
|
|
772
1011
|
setDoc({ ...controller.document });
|
|
773
1012
|
},
|
|
1013
|
+
...tableActions,
|
|
1014
|
+
viewModel,
|
|
774
1015
|
undo,
|
|
775
1016
|
redo
|
|
776
1017
|
};
|
|
777
1018
|
}
|
|
778
1019
|
|
|
779
1020
|
// src/spreadsheet/SpreadsheetEditor.tsx
|
|
780
|
-
import { useRef as
|
|
1021
|
+
import { useRef as useRef11, useState as useState8, useMemo as useMemo8, useEffect as useEffect10, useCallback as useCallback6 } from "react";
|
|
781
1022
|
import {
|
|
782
|
-
indexToColName
|
|
1023
|
+
indexToColName,
|
|
1024
|
+
getSpreadsheetMenuItems,
|
|
1025
|
+
normalizeRange as normalizeRange2,
|
|
1026
|
+
rangeContains
|
|
783
1027
|
} from "@pixerate/editor";
|
|
784
|
-
|
|
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
|
+
}
|
|
785
1370
|
var SpreadsheetEditor = ({
|
|
786
1371
|
state: externalState,
|
|
787
1372
|
readOnly = false,
|
|
@@ -792,27 +1377,47 @@ var SpreadsheetEditor = ({
|
|
|
792
1377
|
}) => {
|
|
793
1378
|
const defaultState = useSpreadsheetEditor();
|
|
794
1379
|
const state = externalState || defaultState;
|
|
795
|
-
const gridRef =
|
|
796
|
-
const
|
|
797
|
-
const
|
|
798
|
-
const [
|
|
799
|
-
const [
|
|
800
|
-
const
|
|
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(() => {
|
|
801
1406
|
if (!state.activeCell) return "";
|
|
802
1407
|
const row = state.document.rows[state.activeCell.row];
|
|
803
1408
|
const col = state.document.columns[state.activeCell.col];
|
|
804
1409
|
if (!row || !col) return "";
|
|
805
1410
|
return state.getCell(row.id, col.id).raw;
|
|
806
1411
|
}, [state.activeCell, state.document]);
|
|
807
|
-
const coordLabel =
|
|
1412
|
+
const coordLabel = useMemo8(() => {
|
|
808
1413
|
if (!state.activeCell) return "--";
|
|
809
1414
|
const colName = indexToColName(state.activeCell.col);
|
|
810
1415
|
return `${colName}${state.activeCell.row + 1}`;
|
|
811
1416
|
}, [state.activeCell]);
|
|
812
|
-
const [formulaDraft, setFormulaDraft] =
|
|
1417
|
+
const [formulaDraft, setFormulaDraft] = useState8(null);
|
|
813
1418
|
const activeColumn = state.activeCell ? state.document.columns[state.activeCell.col] : void 0;
|
|
814
1419
|
const formulaBarReadOnly = readOnly || !state.activeCell || !!activeColumn?.readOnly;
|
|
815
|
-
|
|
1420
|
+
useEffect10(() => {
|
|
816
1421
|
setFormulaDraft(null);
|
|
817
1422
|
}, [state.activeCell?.row, state.activeCell?.col]);
|
|
818
1423
|
const commitFormulaBar = () => {
|
|
@@ -828,19 +1433,19 @@ var SpreadsheetEditor = ({
|
|
|
828
1433
|
}
|
|
829
1434
|
setFormulaDraft(null);
|
|
830
1435
|
};
|
|
831
|
-
const handleCellClick =
|
|
1436
|
+
const handleCellClick = useCallback6((rIdx, cIdx, e) => {
|
|
832
1437
|
if (state.editingCell && (state.editingCell.row !== rIdx || state.editingCell.col !== cIdx)) {
|
|
833
1438
|
state.commitEditing();
|
|
834
1439
|
}
|
|
835
1440
|
state.selectCell(rIdx, cIdx, e.shiftKey);
|
|
836
1441
|
}, [state]);
|
|
837
|
-
const handleCellDblClick =
|
|
1442
|
+
const handleCellDblClick = useCallback6((rIdx, cIdx) => {
|
|
838
1443
|
if (readOnly) return;
|
|
839
1444
|
const col = state.document.columns[cIdx];
|
|
840
1445
|
if (col?.readOnly) return;
|
|
841
1446
|
state.startEditing(rIdx, cIdx);
|
|
842
1447
|
}, [readOnly, state]);
|
|
843
|
-
const handleKeyDown =
|
|
1448
|
+
const handleKeyDown = useCallback6((e) => {
|
|
844
1449
|
if (state.editingCell) {
|
|
845
1450
|
if (e.key === "Enter") {
|
|
846
1451
|
e.preventDefault();
|
|
@@ -897,21 +1502,19 @@ var SpreadsheetEditor = ({
|
|
|
897
1502
|
e.preventDefault();
|
|
898
1503
|
state.redo();
|
|
899
1504
|
} else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "c") {
|
|
900
|
-
|
|
901
|
-
const tsv = state.exportToTsv(state.selectedRange);
|
|
902
|
-
navigator.clipboard?.writeText(tsv);
|
|
1505
|
+
copyShortcutFallback();
|
|
903
1506
|
} else if (!readOnly && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
904
1507
|
const col = state.document.columns[state.activeCell.col];
|
|
905
1508
|
if (!col?.readOnly) {
|
|
906
1509
|
state.startEditing(state.activeCell.row, state.activeCell.col, e.key);
|
|
907
1510
|
}
|
|
908
1511
|
}
|
|
909
|
-
}, [readOnly, state]);
|
|
910
|
-
const stateRef =
|
|
1512
|
+
}, [readOnly, state, copyShortcutFallback]);
|
|
1513
|
+
const stateRef = useRef11(state);
|
|
911
1514
|
stateRef.current = state;
|
|
912
|
-
const onColumnResizeRef =
|
|
1515
|
+
const onColumnResizeRef = useRef11(onColumnResize);
|
|
913
1516
|
onColumnResizeRef.current = onColumnResize;
|
|
914
|
-
|
|
1517
|
+
useEffect10(() => {
|
|
915
1518
|
if (!resizingColId) return;
|
|
916
1519
|
if (typeof document !== "undefined") {
|
|
917
1520
|
document.body.style.cursor = "col-resize";
|
|
@@ -962,7 +1565,7 @@ var SpreadsheetEditor = ({
|
|
|
962
1565
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
963
1566
|
};
|
|
964
1567
|
}, [resizingColId, resizeStartX, resizeStartWidth]);
|
|
965
|
-
const measureElementContentWidth =
|
|
1568
|
+
const measureElementContentWidth = useCallback6((el, container) => {
|
|
966
1569
|
if (typeof document === "undefined") return 0;
|
|
967
1570
|
let maxW = el.scrollWidth || 0;
|
|
968
1571
|
const firstChild = el.firstElementChild;
|
|
@@ -1010,15 +1613,14 @@ var SpreadsheetEditor = ({
|
|
|
1010
1613
|
}
|
|
1011
1614
|
return maxW;
|
|
1012
1615
|
}, []);
|
|
1013
|
-
const autoFitColumn =
|
|
1616
|
+
const autoFitColumn = useCallback6((colId, colIndex) => {
|
|
1014
1617
|
const col = state.document.columns.find((c) => c.id === colId);
|
|
1015
1618
|
if (!col) return;
|
|
1016
1619
|
let calculatedWidth = 0;
|
|
1017
1620
|
if (gridRef.current && typeof document !== "undefined") {
|
|
1018
1621
|
const table = gridRef.current.querySelector("table");
|
|
1019
1622
|
if (table) {
|
|
1020
|
-
const
|
|
1021
|
-
const th = thElements[colIndex + 1];
|
|
1623
|
+
const th = table.querySelector(`thead th[data-col="${colIndex}"]`);
|
|
1022
1624
|
if (th) {
|
|
1023
1625
|
const headerInner = th.firstElementChild || th;
|
|
1024
1626
|
const headerW = measureElementContentWidth(headerInner, gridRef.current);
|
|
@@ -1028,7 +1630,7 @@ var SpreadsheetEditor = ({
|
|
|
1028
1630
|
const sampleLimit = Math.min(rows.length, 150);
|
|
1029
1631
|
for (let r = 0; r < sampleLimit; r++) {
|
|
1030
1632
|
const tr = rows[r];
|
|
1031
|
-
const td = tr.
|
|
1633
|
+
const td = tr.querySelector(`td[data-col="${colIndex}"][colspan="1"]`);
|
|
1032
1634
|
if (td) {
|
|
1033
1635
|
const cellW = measureElementContentWidth(td, gridRef.current);
|
|
1034
1636
|
calculatedWidth = Math.max(calculatedWidth, cellW + 20);
|
|
@@ -1047,12 +1649,12 @@ var SpreadsheetEditor = ({
|
|
|
1047
1649
|
onColumnResize(updatedCol.id, updatedCol.width || 130);
|
|
1048
1650
|
}
|
|
1049
1651
|
}, [state, measureElementContentWidth, onColumnResize]);
|
|
1050
|
-
const handleResizeDblClick =
|
|
1652
|
+
const handleResizeDblClick = useCallback6((colId, colIndex, e) => {
|
|
1051
1653
|
e.preventDefault();
|
|
1052
1654
|
e.stopPropagation();
|
|
1053
1655
|
autoFitColumn(colId, colIndex);
|
|
1054
1656
|
}, [autoFitColumn]);
|
|
1055
|
-
const handleHeaderDblClick =
|
|
1657
|
+
const handleHeaderDblClick = useCallback6((colId, colIndex, e) => {
|
|
1056
1658
|
const target = e.currentTarget;
|
|
1057
1659
|
if (target) {
|
|
1058
1660
|
const rect = target.getBoundingClientRect();
|
|
@@ -1074,7 +1676,7 @@ var SpreadsheetEditor = ({
|
|
|
1074
1676
|
const isActiveCell = (rIdx, cIdx) => {
|
|
1075
1677
|
return state.activeCell?.row === rIdx && state.activeCell?.col === cIdx;
|
|
1076
1678
|
};
|
|
1077
|
-
return /* @__PURE__ */
|
|
1679
|
+
return /* @__PURE__ */ jsxs7(
|
|
1078
1680
|
"div",
|
|
1079
1681
|
{
|
|
1080
1682
|
ref: gridRef,
|
|
@@ -1084,12 +1686,13 @@ var SpreadsheetEditor = ({
|
|
|
1084
1686
|
role: "grid",
|
|
1085
1687
|
"aria-label": "Spreadsheet",
|
|
1086
1688
|
children: [
|
|
1087
|
-
/* @__PURE__ */
|
|
1088
|
-
/* @__PURE__ */
|
|
1089
|
-
/* @__PURE__ */
|
|
1090
|
-
/* @__PURE__ */
|
|
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(
|
|
1091
1693
|
"input",
|
|
1092
1694
|
{
|
|
1695
|
+
ref: formulaBarRef,
|
|
1093
1696
|
type: "text",
|
|
1094
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",
|
|
1095
1698
|
placeholder: "Enter a value or formula (e.g. =SUM(A1:A5))",
|
|
@@ -1119,28 +1722,44 @@ var SpreadsheetEditor = ({
|
|
|
1119
1722
|
}
|
|
1120
1723
|
}
|
|
1121
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
|
+
}
|
|
1122
1733
|
)
|
|
1123
1734
|
] }),
|
|
1124
|
-
/* @__PURE__ */
|
|
1125
|
-
/* @__PURE__ */
|
|
1126
|
-
/* @__PURE__ */
|
|
1127
|
-
|
|
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(
|
|
1128
1739
|
"th",
|
|
1129
1740
|
{
|
|
1741
|
+
"data-col": cIdx,
|
|
1130
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"}`,
|
|
1131
|
-
style: {
|
|
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
|
+
},
|
|
1132
1750
|
onClick: () => onColumnHeaderClick?.(col),
|
|
1133
1751
|
onDoubleClick: (e) => handleHeaderDblClick(col.id, cIdx, e),
|
|
1134
1752
|
children: [
|
|
1135
|
-
/* @__PURE__ */
|
|
1136
|
-
/* @__PURE__ */
|
|
1137
|
-
/* @__PURE__ */
|
|
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: [
|
|
1138
1757
|
"(",
|
|
1139
1758
|
col.key,
|
|
1140
1759
|
")"
|
|
1141
1760
|
] })
|
|
1142
1761
|
] }),
|
|
1143
|
-
/* @__PURE__ */
|
|
1762
|
+
/* @__PURE__ */ jsx8(
|
|
1144
1763
|
"div",
|
|
1145
1764
|
{
|
|
1146
1765
|
className: "col-resize-handle",
|
|
@@ -1157,7 +1776,7 @@ var SpreadsheetEditor = ({
|
|
|
1157
1776
|
onDoubleClick: (e) => handleResizeDblClick(col.id, cIdx, e),
|
|
1158
1777
|
role: "separator",
|
|
1159
1778
|
"aria-orientation": "vertical",
|
|
1160
|
-
children: /* @__PURE__ */
|
|
1779
|
+
children: /* @__PURE__ */ jsx8(
|
|
1161
1780
|
"div",
|
|
1162
1781
|
{
|
|
1163
1782
|
className: "col-resize-handle-line",
|
|
@@ -1176,49 +1795,136 @@ var SpreadsheetEditor = ({
|
|
|
1176
1795
|
},
|
|
1177
1796
|
col.id
|
|
1178
1797
|
)),
|
|
1179
|
-
onAddColumnClick && /* @__PURE__ */
|
|
1798
|
+
onAddColumnClick && /* @__PURE__ */ jsx8(
|
|
1180
1799
|
"th",
|
|
1181
1800
|
{
|
|
1182
1801
|
className: "w-10 min-w-[2.5rem] px-1 text-center bg-muted/40 hover:bg-muted cursor-pointer transition-colors",
|
|
1183
1802
|
onClick: onAddColumnClick,
|
|
1184
1803
|
title: "Add Column",
|
|
1185
|
-
children: /* @__PURE__ */
|
|
1804
|
+
children: /* @__PURE__ */ jsx8("span", { className: "text-xs font-bold text-muted-foreground", children: "+" })
|
|
1186
1805
|
}
|
|
1187
1806
|
)
|
|
1188
1807
|
] }) }),
|
|
1189
|
-
/* @__PURE__ */
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
const selected = isCellSelected(rIdx, cIdx);
|
|
1194
|
-
const active = isActiveCell(rIdx, cIdx);
|
|
1195
|
-
const isEditing = state.editingCell?.row === rIdx && state.editingCell?.col === cIdx;
|
|
1196
|
-
return /* @__PURE__ */ jsx3(
|
|
1197
|
-
"td",
|
|
1808
|
+
/* @__PURE__ */ jsx8("tbody", { children: view.rows.map((viewRow) => {
|
|
1809
|
+
if (viewRow.kind === "group") {
|
|
1810
|
+
return /* @__PURE__ */ jsx8(
|
|
1811
|
+
SpreadsheetGroupRow,
|
|
1198
1812
|
{
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
"input",
|
|
1205
|
-
{
|
|
1206
|
-
type: "text",
|
|
1207
|
-
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",
|
|
1208
|
-
value: state.draftValue,
|
|
1209
|
-
onChange: (e) => state.setDraftValue(e.target.value),
|
|
1210
|
-
onBlur: () => state.commitEditing(),
|
|
1211
|
-
autoFocus: true
|
|
1212
|
-
}
|
|
1213
|
-
) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ jsx3("span", { className: `truncate block max-w-full ${cell.error ? "text-destructive font-semibold" : ""}`, children: cell.error || (cell.value ?? "") })
|
|
1813
|
+
group: viewRow.group,
|
|
1814
|
+
columns: view.columns,
|
|
1815
|
+
groupColId: state.document.table?.grouping?.colId ?? "",
|
|
1816
|
+
onToggle: state.toggleGroupCollapsed,
|
|
1817
|
+
trailingCell: !!onAddColumnClick
|
|
1214
1818
|
},
|
|
1215
|
-
|
|
1819
|
+
`group:${viewRow.group.key}`
|
|
1216
1820
|
);
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
|
|
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
|
+
}) })
|
|
1220
1914
|
] }) }),
|
|
1221
|
-
/* @__PURE__ */
|
|
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: `
|
|
1222
1928
|
.col-resize-handle:hover .col-resize-handle-line {
|
|
1223
1929
|
background-color: var(--primary, #6366f1);
|
|
1224
1930
|
opacity: 0.75;
|
|
@@ -1241,10 +1947,10 @@ var SpreadsheetEditor = ({
|
|
|
1241
1947
|
};
|
|
1242
1948
|
|
|
1243
1949
|
// src/slots.tsx
|
|
1244
|
-
import { createContext, useContext, useMemo as
|
|
1245
|
-
import { jsx as
|
|
1950
|
+
import { createContext, useContext, useMemo as useMemo9 } from "react";
|
|
1951
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1246
1952
|
var defaultSlots = {
|
|
1247
|
-
Button: (props) => /* @__PURE__ */
|
|
1953
|
+
Button: (props) => /* @__PURE__ */ jsx9(
|
|
1248
1954
|
"button",
|
|
1249
1955
|
{
|
|
1250
1956
|
type: "button",
|
|
@@ -1254,43 +1960,378 @@ var defaultSlots = {
|
|
|
1254
1960
|
children: props.children
|
|
1255
1961
|
}
|
|
1256
1962
|
),
|
|
1257
|
-
Tooltip: (props) => /* @__PURE__ */
|
|
1258
|
-
NodeHeader: (props) => /* @__PURE__ */
|
|
1259
|
-
/* @__PURE__ */
|
|
1963
|
+
Tooltip: (props) => /* @__PURE__ */ jsx9("div", { className: `pixerate-tooltip-container ${props.className || ""}`, title: String(props.content), children: props.children }),
|
|
1964
|
+
NodeHeader: (props) => /* @__PURE__ */ jsxs8("div", { className: `pixerate-node-header flex items-center justify-between ${props.className || ""}`, children: [
|
|
1965
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
|
|
1260
1966
|
props.icon,
|
|
1261
|
-
/* @__PURE__ */
|
|
1967
|
+
/* @__PURE__ */ jsx9("span", { className: "font-semibold", children: props.title })
|
|
1262
1968
|
] }),
|
|
1263
|
-
props.onClose && /* @__PURE__ */
|
|
1969
|
+
props.onClose && /* @__PURE__ */ jsx9("button", { type: "button", onClick: props.onClose, className: "opacity-70 hover:opacity-100", children: "\xD7" })
|
|
1264
1970
|
] })
|
|
1265
1971
|
};
|
|
1266
1972
|
var EditorSlotsContext = createContext(defaultSlots);
|
|
1267
1973
|
function EditorSlotsProvider({ slots, children }) {
|
|
1268
|
-
const mergedSlots =
|
|
1269
|
-
return /* @__PURE__ */
|
|
1974
|
+
const mergedSlots = useMemo9(() => ({ ...defaultSlots, ...slots }), [slots]);
|
|
1975
|
+
return /* @__PURE__ */ jsx9(EditorSlotsContext.Provider, { value: mergedSlots, children });
|
|
1270
1976
|
}
|
|
1271
1977
|
function useEditorSlots() {
|
|
1272
1978
|
return useContext(EditorSlotsContext);
|
|
1273
1979
|
}
|
|
1980
|
+
|
|
1981
|
+
// src/review/useReview.ts
|
|
1982
|
+
import { useCallback as useCallback7, useMemo as useMemo10, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
1983
|
+
import {
|
|
1984
|
+
getCommentStore,
|
|
1985
|
+
getTrackChangesStore
|
|
1986
|
+
} from "@pixerate/editor";
|
|
1987
|
+
var EMPTY_COMMENTS = Object.freeze({
|
|
1988
|
+
threads: [],
|
|
1989
|
+
activeThreadId: null
|
|
1990
|
+
});
|
|
1991
|
+
var EMPTY_TRACK_CHANGES = Object.freeze({
|
|
1992
|
+
enabled: false,
|
|
1993
|
+
author: { id: "anonymous", name: "Anonymous" },
|
|
1994
|
+
changes: [],
|
|
1995
|
+
currentChangeId: null
|
|
1996
|
+
});
|
|
1997
|
+
var noopUnsubscribe = () => {
|
|
1998
|
+
};
|
|
1999
|
+
function isCommentStore(value) {
|
|
2000
|
+
return !!value && typeof value.getSnapshot === "function" && typeof value.createThread === "function";
|
|
2001
|
+
}
|
|
2002
|
+
function useComments(source) {
|
|
2003
|
+
const store = isCommentStore(source) ? source : getCommentStore(source);
|
|
2004
|
+
const subscribe = useCallback7(
|
|
2005
|
+
(onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
|
|
2006
|
+
[store]
|
|
2007
|
+
);
|
|
2008
|
+
const getSnapshot = () => store?.getSnapshot() ?? EMPTY_COMMENTS;
|
|
2009
|
+
const snapshot = useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
|
|
2010
|
+
return useMemo10(() => ({ ...snapshot, store }), [snapshot, store]);
|
|
2011
|
+
}
|
|
2012
|
+
function useTrackChanges(editor) {
|
|
2013
|
+
const store = getTrackChangesStore(editor ?? null);
|
|
2014
|
+
const subscribe = useCallback7(
|
|
2015
|
+
(onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
|
|
2016
|
+
[store]
|
|
2017
|
+
);
|
|
2018
|
+
const getSnapshot = () => store?.getSnapshot() ?? EMPTY_TRACK_CHANGES;
|
|
2019
|
+
const snapshot = useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
|
|
2020
|
+
return useMemo10(() => {
|
|
2021
|
+
const run = (fn) => () => {
|
|
2022
|
+
if (editor && !editor.isDestroyed) fn(editor);
|
|
2023
|
+
};
|
|
2024
|
+
return {
|
|
2025
|
+
...snapshot,
|
|
2026
|
+
currentChange: snapshot.changes.find((c) => c.id === snapshot.currentChangeId) ?? null,
|
|
2027
|
+
setEnabled: (enabled) => run((e) => e.commands.setTrackChanges(enabled))(),
|
|
2028
|
+
toggle: run((e) => e.commands.toggleTrackChanges()),
|
|
2029
|
+
accept: (id) => run((e) => e.commands.acceptChange(id))(),
|
|
2030
|
+
reject: (id) => run((e) => e.commands.rejectChange(id))(),
|
|
2031
|
+
acceptAll: run((e) => e.commands.acceptAllChanges()),
|
|
2032
|
+
rejectAll: run((e) => e.commands.rejectAllChanges()),
|
|
2033
|
+
next: run((e) => e.chain().focus().selectNextChange().run()),
|
|
2034
|
+
previous: run((e) => e.chain().focus().selectPreviousChange().run())
|
|
2035
|
+
};
|
|
2036
|
+
}, [snapshot, editor]);
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
// src/review/CommentsPanel.tsx
|
|
2040
|
+
import { useState as useState9 } from "react";
|
|
2041
|
+
import {
|
|
2042
|
+
formatReviewTime,
|
|
2043
|
+
getCommentAuthor
|
|
2044
|
+
} from "@pixerate/editor";
|
|
2045
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2046
|
+
var buttonClass = "rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50";
|
|
2047
|
+
var primaryButtonClass = "rounded px-2 py-1 text-xs bg-primary/10 text-primary hover:bg-primary/20 disabled:opacity-50";
|
|
2048
|
+
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";
|
|
2049
|
+
var CommentsPanel = ({
|
|
2050
|
+
editor,
|
|
2051
|
+
store: storeProp,
|
|
2052
|
+
author: authorProp,
|
|
2053
|
+
showResolved: showResolvedInitial = false,
|
|
2054
|
+
className = "flex flex-col gap-2 text-sm text-foreground",
|
|
2055
|
+
formatTime = (t) => formatReviewTime(t),
|
|
2056
|
+
emptyMessage = "No comments yet. Select text and add a comment."
|
|
2057
|
+
}) => {
|
|
2058
|
+
const { threads, activeThreadId, store } = useComments(storeProp ?? editor);
|
|
2059
|
+
const [showResolved, setShowResolved] = useState9(showResolvedInitial);
|
|
2060
|
+
const author = authorProp ?? getCommentAuthor(editor) ?? { id: "anonymous", name: "Anonymous" };
|
|
2061
|
+
const visible = threads.filter((t) => showResolved || !t.resolved);
|
|
2062
|
+
const resolvedCount = threads.filter((t) => t.resolved).length;
|
|
2063
|
+
return /* @__PURE__ */ jsxs9("aside", { className, "aria-label": "Comments", children: [
|
|
2064
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between gap-2", children: [
|
|
2065
|
+
/* @__PURE__ */ jsxs9("span", { className: "text-xs font-medium text-muted-foreground", children: [
|
|
2066
|
+
threads.length - resolvedCount,
|
|
2067
|
+
" open",
|
|
2068
|
+
resolvedCount > 0 ? ` \xB7 ${resolvedCount} resolved` : ""
|
|
2069
|
+
] }),
|
|
2070
|
+
resolvedCount > 0 && /* @__PURE__ */ jsx10(
|
|
2071
|
+
"button",
|
|
2072
|
+
{
|
|
2073
|
+
type: "button",
|
|
2074
|
+
className: buttonClass,
|
|
2075
|
+
"aria-pressed": showResolved,
|
|
2076
|
+
onClick: () => setShowResolved((v) => !v),
|
|
2077
|
+
children: showResolved ? "Hide resolved" : "Show resolved"
|
|
2078
|
+
}
|
|
2079
|
+
)
|
|
2080
|
+
] }),
|
|
2081
|
+
visible.length === 0 ? /* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ jsx10("ul", { className: "flex flex-col gap-2", role: "list", children: visible.map((thread) => /* @__PURE__ */ jsx10(
|
|
2082
|
+
CommentThreadCard,
|
|
2083
|
+
{
|
|
2084
|
+
thread,
|
|
2085
|
+
active: thread.id === activeThreadId,
|
|
2086
|
+
editor,
|
|
2087
|
+
store,
|
|
2088
|
+
author,
|
|
2089
|
+
formatTime
|
|
2090
|
+
},
|
|
2091
|
+
thread.id
|
|
2092
|
+
)) })
|
|
2093
|
+
] });
|
|
2094
|
+
};
|
|
2095
|
+
var CommentThreadCard = ({
|
|
2096
|
+
thread,
|
|
2097
|
+
active,
|
|
2098
|
+
editor,
|
|
2099
|
+
store,
|
|
2100
|
+
author,
|
|
2101
|
+
formatTime
|
|
2102
|
+
}) => {
|
|
2103
|
+
const [draft, setDraft] = useState9("");
|
|
2104
|
+
const isDraft = thread.body === "";
|
|
2105
|
+
const quote = thread.anchor?.quote ?? thread.lastQuote ?? "";
|
|
2106
|
+
const live = editor && !editor.isDestroyed ? editor : null;
|
|
2107
|
+
const focus = () => {
|
|
2108
|
+
if (!active) {
|
|
2109
|
+
if (live && !thread.orphaned) live.commands.focusComment(thread.id);
|
|
2110
|
+
else store?.setActiveThread(thread.id);
|
|
2111
|
+
}
|
|
2112
|
+
};
|
|
2113
|
+
const remove = () => {
|
|
2114
|
+
if (live) live.commands.deleteComment(thread.id);
|
|
2115
|
+
else store?.deleteThread(thread.id);
|
|
2116
|
+
};
|
|
2117
|
+
const setResolved = (resolved) => {
|
|
2118
|
+
if (live) live.commands.resolveComment(thread.id, resolved);
|
|
2119
|
+
else store?.resolveThread(thread.id, resolved, author);
|
|
2120
|
+
};
|
|
2121
|
+
const submit = () => {
|
|
2122
|
+
const body = draft.trim();
|
|
2123
|
+
if (!body || !store) return;
|
|
2124
|
+
if (isDraft) store.editComment(thread.id, body);
|
|
2125
|
+
else store.addReply(thread.id, { body, author });
|
|
2126
|
+
setDraft("");
|
|
2127
|
+
};
|
|
2128
|
+
const stop = (e) => e.stopPropagation();
|
|
2129
|
+
return /* @__PURE__ */ jsxs9(
|
|
2130
|
+
"li",
|
|
2131
|
+
{
|
|
2132
|
+
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" : ""}`,
|
|
2133
|
+
"data-thread-id": thread.id,
|
|
2134
|
+
"data-active": active || void 0,
|
|
2135
|
+
"aria-current": active || void 0,
|
|
2136
|
+
tabIndex: 0,
|
|
2137
|
+
onClick: focus,
|
|
2138
|
+
onKeyDown: (e) => {
|
|
2139
|
+
if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) {
|
|
2140
|
+
e.preventDefault();
|
|
2141
|
+
focus();
|
|
2142
|
+
}
|
|
2143
|
+
},
|
|
2144
|
+
children: [
|
|
2145
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-baseline justify-between gap-2", children: [
|
|
2146
|
+
/* @__PURE__ */ jsx10("span", { className: "font-medium", children: thread.author.name }),
|
|
2147
|
+
/* @__PURE__ */ jsx10("span", { className: "text-xs text-muted-foreground", children: formatTime(thread.createdAt) })
|
|
2148
|
+
] }),
|
|
2149
|
+
quote && /* @__PURE__ */ jsx10("blockquote", { className: "border-l-2 border-border pl-2 text-xs text-muted-foreground line-clamp-2", children: quote }),
|
|
2150
|
+
thread.orphaned && /* @__PURE__ */ jsx10("span", { className: "text-xs text-destructive", children: "The commented text was deleted." }),
|
|
2151
|
+
thread.resolved && /* @__PURE__ */ jsx10("span", { className: "text-xs text-muted-foreground", children: "Resolved" }),
|
|
2152
|
+
!isDraft && /* @__PURE__ */ jsx10("p", { className: "whitespace-pre-wrap break-words", children: thread.body }),
|
|
2153
|
+
thread.replies.length > 0 && /* @__PURE__ */ jsx10("ul", { className: "flex flex-col gap-2 border-l border-border pl-3", role: "list", children: thread.replies.map((reply) => /* @__PURE__ */ jsxs9("li", { className: "flex flex-col gap-0.5", "data-reply-id": reply.id, children: [
|
|
2154
|
+
/* @__PURE__ */ jsxs9("span", { className: "text-xs", children: [
|
|
2155
|
+
/* @__PURE__ */ jsx10("span", { className: "font-medium", children: reply.author.name }),
|
|
2156
|
+
" ",
|
|
2157
|
+
/* @__PURE__ */ jsx10("span", { className: "text-muted-foreground", children: formatTime(reply.createdAt) })
|
|
2158
|
+
] }),
|
|
2159
|
+
/* @__PURE__ */ jsx10("p", { className: "whitespace-pre-wrap break-words", children: reply.body })
|
|
2160
|
+
] }, reply.id)) }),
|
|
2161
|
+
(active || isDraft) && !thread.resolved && /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-1", onClick: stop, children: [
|
|
2162
|
+
/* @__PURE__ */ jsx10(
|
|
2163
|
+
"textarea",
|
|
2164
|
+
{
|
|
2165
|
+
className: textareaClass,
|
|
2166
|
+
rows: 2,
|
|
2167
|
+
value: draft,
|
|
2168
|
+
"aria-label": isDraft ? "Comment" : "Reply",
|
|
2169
|
+
placeholder: isDraft ? "Add a comment\u2026" : "Reply\u2026",
|
|
2170
|
+
autoFocus: isDraft,
|
|
2171
|
+
onChange: (e) => setDraft(e.target.value),
|
|
2172
|
+
onKeyDown: (e) => {
|
|
2173
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
2174
|
+
e.preventDefault();
|
|
2175
|
+
submit();
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
),
|
|
2180
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex justify-end gap-1", children: [
|
|
2181
|
+
isDraft && /* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass, onClick: remove, children: "Cancel" }),
|
|
2182
|
+
/* @__PURE__ */ jsx10(
|
|
2183
|
+
"button",
|
|
2184
|
+
{
|
|
2185
|
+
type: "button",
|
|
2186
|
+
className: primaryButtonClass,
|
|
2187
|
+
disabled: !draft.trim(),
|
|
2188
|
+
onClick: submit,
|
|
2189
|
+
children: isDraft ? "Comment" : "Reply"
|
|
2190
|
+
}
|
|
2191
|
+
)
|
|
2192
|
+
] })
|
|
2193
|
+
] }),
|
|
2194
|
+
!isDraft && /* @__PURE__ */ jsxs9("div", { className: "flex justify-end gap-1", onClick: stop, children: [
|
|
2195
|
+
/* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass, onClick: () => setResolved(!thread.resolved), children: thread.resolved ? "Reopen" : "Resolve" }),
|
|
2196
|
+
/* @__PURE__ */ jsx10(
|
|
2197
|
+
"button",
|
|
2198
|
+
{
|
|
2199
|
+
type: "button",
|
|
2200
|
+
className: `${buttonClass} hover:text-destructive`,
|
|
2201
|
+
onClick: remove,
|
|
2202
|
+
children: "Delete"
|
|
2203
|
+
}
|
|
2204
|
+
)
|
|
2205
|
+
] })
|
|
2206
|
+
]
|
|
2207
|
+
}
|
|
2208
|
+
);
|
|
2209
|
+
};
|
|
2210
|
+
|
|
2211
|
+
// src/review/ReviewToolbar.tsx
|
|
2212
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2213
|
+
var buttonClass2 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
|
|
2214
|
+
var ReviewToolbar = ({
|
|
2215
|
+
editor,
|
|
2216
|
+
className = "flex flex-wrap items-center gap-1 rounded-md border border-border bg-background p-1 text-sm"
|
|
2217
|
+
}) => {
|
|
2218
|
+
const review = useTrackChanges(editor);
|
|
2219
|
+
const count = review.changes.length;
|
|
2220
|
+
const current = review.currentChange;
|
|
2221
|
+
const index = current ? review.changes.findIndex((c) => c.id === current.id) + 1 : 0;
|
|
2222
|
+
return /* @__PURE__ */ jsxs10("div", { className, role: "toolbar", "aria-label": "Review", children: [
|
|
2223
|
+
/* @__PURE__ */ jsx11(
|
|
2224
|
+
"button",
|
|
2225
|
+
{
|
|
2226
|
+
type: "button",
|
|
2227
|
+
className: `${buttonClass2} ${review.enabled ? "bg-primary/10 text-primary" : ""}`,
|
|
2228
|
+
"aria-pressed": review.enabled,
|
|
2229
|
+
disabled: !editor,
|
|
2230
|
+
onClick: review.toggle,
|
|
2231
|
+
children: review.enabled ? "Tracking on" : "Track changes"
|
|
2232
|
+
}
|
|
2233
|
+
),
|
|
2234
|
+
/* @__PURE__ */ jsx11("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
|
|
2235
|
+
/* @__PURE__ */ jsx11(
|
|
2236
|
+
"button",
|
|
2237
|
+
{
|
|
2238
|
+
type: "button",
|
|
2239
|
+
className: buttonClass2,
|
|
2240
|
+
"aria-label": "Previous change",
|
|
2241
|
+
disabled: count === 0,
|
|
2242
|
+
onClick: review.previous,
|
|
2243
|
+
children: "\u2039"
|
|
2244
|
+
}
|
|
2245
|
+
),
|
|
2246
|
+
/* @__PURE__ */ jsx11("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` }),
|
|
2247
|
+
/* @__PURE__ */ jsx11(
|
|
2248
|
+
"button",
|
|
2249
|
+
{
|
|
2250
|
+
type: "button",
|
|
2251
|
+
className: buttonClass2,
|
|
2252
|
+
"aria-label": "Next change",
|
|
2253
|
+
disabled: count === 0,
|
|
2254
|
+
onClick: review.next,
|
|
2255
|
+
children: "\u203A"
|
|
2256
|
+
}
|
|
2257
|
+
),
|
|
2258
|
+
/* @__PURE__ */ jsx11("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
|
|
2259
|
+
/* @__PURE__ */ jsx11(
|
|
2260
|
+
"button",
|
|
2261
|
+
{
|
|
2262
|
+
type: "button",
|
|
2263
|
+
className: buttonClass2,
|
|
2264
|
+
disabled: !current,
|
|
2265
|
+
onClick: () => current && review.accept(current.id),
|
|
2266
|
+
children: "Accept"
|
|
2267
|
+
}
|
|
2268
|
+
),
|
|
2269
|
+
/* @__PURE__ */ jsx11(
|
|
2270
|
+
"button",
|
|
2271
|
+
{
|
|
2272
|
+
type: "button",
|
|
2273
|
+
className: buttonClass2,
|
|
2274
|
+
disabled: !current,
|
|
2275
|
+
onClick: () => current && review.reject(current.id),
|
|
2276
|
+
children: "Reject"
|
|
2277
|
+
}
|
|
2278
|
+
),
|
|
2279
|
+
/* @__PURE__ */ jsx11("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.acceptAll, children: "Accept all" }),
|
|
2280
|
+
/* @__PURE__ */ jsx11("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.rejectAll, children: "Reject all" })
|
|
2281
|
+
] });
|
|
2282
|
+
};
|
|
2283
|
+
|
|
2284
|
+
// src/review/CommentButton.tsx
|
|
2285
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
2286
|
+
var CommentButton = ({
|
|
2287
|
+
editor,
|
|
2288
|
+
className = "rounded px-2 py-1 text-xs text-popover-foreground hover:bg-muted",
|
|
2289
|
+
children = "Comment"
|
|
2290
|
+
}) => /* @__PURE__ */ jsx12(
|
|
2291
|
+
"button",
|
|
2292
|
+
{
|
|
2293
|
+
type: "button",
|
|
2294
|
+
className,
|
|
2295
|
+
disabled: !editor,
|
|
2296
|
+
onClick: () => editor?.chain().focus().addComment().run(),
|
|
2297
|
+
children
|
|
2298
|
+
}
|
|
2299
|
+
);
|
|
1274
2300
|
export {
|
|
1275
2301
|
BubbleMenu,
|
|
2302
|
+
CommentButton,
|
|
2303
|
+
CommentsPanel,
|
|
1276
2304
|
DirtyTracker,
|
|
1277
2305
|
EditorContent,
|
|
1278
2306
|
EditorSlotsProvider,
|
|
2307
|
+
FormulaHints,
|
|
1279
2308
|
HistoryManager2 as HistoryManager,
|
|
1280
2309
|
ImageEditor,
|
|
2310
|
+
OutlinePanel,
|
|
2311
|
+
ReviewToolbar,
|
|
2312
|
+
SpreadsheetContextMenu,
|
|
1281
2313
|
SpreadsheetEditor,
|
|
2314
|
+
SpreadsheetGroupRow,
|
|
1282
2315
|
TemplateRenderer,
|
|
2316
|
+
VariableForm,
|
|
1283
2317
|
createDirtyTracker,
|
|
1284
2318
|
createHistoryManager2 as createHistoryManager,
|
|
1285
2319
|
defaultIsEqual,
|
|
2320
|
+
useAgentToolProvider,
|
|
2321
|
+
useComments,
|
|
1286
2322
|
useDismissGuard,
|
|
1287
2323
|
useEditor2 as useEditor,
|
|
1288
2324
|
useEditorSlots,
|
|
2325
|
+
useFileImport,
|
|
2326
|
+
useFillHandle,
|
|
1289
2327
|
useHistory,
|
|
1290
2328
|
useHistoryShortcuts,
|
|
1291
2329
|
useImageEditor,
|
|
1292
2330
|
useNavigationGuard,
|
|
2331
|
+
useOutline,
|
|
1293
2332
|
usePromptEditor,
|
|
1294
|
-
|
|
2333
|
+
useSpreadsheetClipboard,
|
|
2334
|
+
useSpreadsheetEditor,
|
|
2335
|
+
useTrackChanges
|
|
1295
2336
|
};
|
|
1296
2337
|
//# sourceMappingURL=index.js.map
|