@pixerate/editor-react 0.16.0 → 0.18.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.
Files changed (46) hide show
  1. package/README.md +4 -2
  2. package/dist/canvas/index.cjs +1094 -143
  3. package/dist/canvas/index.cjs.map +1 -1
  4. package/dist/canvas/index.d.cts +277 -3
  5. package/dist/canvas/index.d.ts +277 -3
  6. package/dist/canvas/index.js +976 -0
  7. package/dist/canvas/index.js.map +1 -1
  8. package/dist/chunk-34HEZX7J.js +2144 -0
  9. package/dist/chunk-34HEZX7J.js.map +1 -0
  10. package/dist/chunk-I3E4KSA2.js +515 -0
  11. package/dist/chunk-I3E4KSA2.js.map +1 -0
  12. package/dist/{chunk-XTFR3IMK.js → chunk-ZU54BMDC.js} +473 -160
  13. package/dist/chunk-ZU54BMDC.js.map +1 -0
  14. package/dist/generation/index.cjs +290 -0
  15. package/dist/generation/index.cjs.map +1 -1
  16. package/dist/generation/index.d.cts +66 -2
  17. package/dist/generation/index.d.ts +66 -2
  18. package/dist/generation/index.js +7 -1
  19. package/dist/image-editor/index.cjs +490 -187
  20. package/dist/image-editor/index.cjs.map +1 -1
  21. package/dist/image-editor/index.d.cts +34 -2
  22. package/dist/image-editor/index.d.ts +34 -2
  23. package/dist/image-editor/index.js +5 -1
  24. package/dist/index.cjs +2792 -823
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +188 -7
  27. package/dist/index.d.ts +188 -7
  28. package/dist/index.js +600 -158
  29. package/dist/index.js.map +1 -1
  30. package/dist/spreadsheet/index.cjs +1099 -142
  31. package/dist/spreadsheet/index.cjs.map +1 -1
  32. package/dist/spreadsheet/index.d.cts +156 -8
  33. package/dist/spreadsheet/index.d.ts +156 -8
  34. package/dist/spreadsheet/index.js +15 -3
  35. package/dist/tokens/index.cjs +395 -0
  36. package/dist/tokens/index.cjs.map +1 -0
  37. package/dist/tokens/index.d.cts +54 -0
  38. package/dist/tokens/index.d.ts +54 -0
  39. package/dist/tokens/index.js +375 -0
  40. package/dist/tokens/index.js.map +1 -0
  41. package/package.json +10 -2
  42. package/dist/chunk-7SPRQZ6H.js +0 -216
  43. package/dist/chunk-7SPRQZ6H.js.map +0 -1
  44. package/dist/chunk-FYIPXHAE.js +0 -1169
  45. package/dist/chunk-FYIPXHAE.js.map +0 -1
  46. package/dist/chunk-XTFR3IMK.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,21 +1,29 @@
1
1
  import {
2
2
  FormulaHints,
3
+ NameManager,
4
+ SheetTabs,
5
+ SpreadsheetConditionalFormatPanel,
3
6
  SpreadsheetContextMenu,
7
+ SpreadsheetDataValidationPanel,
4
8
  SpreadsheetEditor,
5
9
  SpreadsheetGroupRow,
10
+ SpreadsheetValidationDialog,
6
11
  useFillHandle,
7
12
  useSpreadsheetClipboard,
8
- useSpreadsheetEditor
9
- } from "./chunk-FYIPXHAE.js";
13
+ useSpreadsheetEditor,
14
+ useSpreadsheetViewport
15
+ } from "./chunk-34HEZX7J.js";
10
16
  import {
11
17
  AdjustmentLayersPanel,
12
18
  ControlSlider,
13
19
  CurvesEditor,
14
20
  Histogram,
15
21
  ImageEditor,
22
+ RefineEdgePanel,
23
+ SelectionOptionsBar,
16
24
  useImageEditor,
17
25
  useImageHistogram
18
- } from "./chunk-XTFR3IMK.js";
26
+ } from "./chunk-ZU54BMDC.js";
19
27
  import {
20
28
  CommandPalette,
21
29
  useCommandPalette,
@@ -27,11 +35,14 @@ import {
27
35
  useAutosave
28
36
  } from "./chunk-XWFU2CSG.js";
29
37
  import {
38
+ AssetMentionChips,
39
+ GenerationHistory,
30
40
  GenerationSettings,
31
41
  JobProgress,
32
42
  useGenerationPlan,
43
+ useGenerationRecords,
33
44
  useJobProgress
34
- } from "./chunk-7SPRQZ6H.js";
45
+ } from "./chunk-I3E4KSA2.js";
35
46
 
36
47
  // src/hooks/usePromptEditor.ts
37
48
  import { useMemo, useEffect, useRef } from "react";
@@ -576,7 +587,8 @@ function useHistoryShortcuts(manager, options = {}) {
576
587
  // src/hooks/useFileImport.ts
577
588
  import { useEffect as useEffect5, useMemo as useMemo3, useRef as useRef6, useSyncExternalStore as useSyncExternalStore3 } from "react";
578
589
  import {
579
- FileImportController
590
+ FileImportController,
591
+ summarizeImportStructure
580
592
  } from "@pixerate/editor";
581
593
  function useFileImport(editor, options = {}) {
582
594
  const editorRef = useRef6(editor);
@@ -592,18 +604,73 @@ function useFileImport(editor, options = {}) {
592
604
  controller.setOptions(options);
593
605
  });
594
606
  const state = useSyncExternalStore3(controller.subscribe, controller.getState, controller.getState);
607
+ const structureSummary = useMemo3(() => summarizeImportStructure(state.lastOutcomes), [state.lastOutcomes]);
595
608
  return {
596
609
  ...state,
610
+ structureSummary,
597
611
  accept: controller.accept,
598
612
  openFilePicker: controller.openFilePicker,
599
613
  importFiles: controller.importFiles
600
614
  };
601
615
  }
602
616
 
617
+ // src/hooks/useDocxExport.ts
618
+ import { useEffect as useEffect6, useMemo as useMemo4, useRef as useRef7, useSyncExternalStore as useSyncExternalStore4 } from "react";
619
+ import {
620
+ DocxExportController
621
+ } from "@pixerate/editor/export";
622
+ function useDocxExport(editor, options = {}) {
623
+ const editorRef = useRef7(editor);
624
+ editorRef.current = editor;
625
+ const controller = useMemo4(
626
+ () => new DocxExportController(() => {
627
+ const current = editorRef.current;
628
+ return current && !current.isDestroyed ? current : null;
629
+ }, options),
630
+ []
631
+ );
632
+ useEffect6(() => {
633
+ controller.setOptions(options);
634
+ });
635
+ const state = useSyncExternalStore4(controller.subscribe, controller.getState, controller.getState);
636
+ return {
637
+ ...state,
638
+ download: (fileName) => controller.download(fileName),
639
+ exportBlob: () => controller.exportBlob()
640
+ };
641
+ }
642
+
643
+ // src/components/ExportDocxButton.tsx
644
+ import { jsx } from "react/jsx-runtime";
645
+ var ExportDocxButton = ({
646
+ editor,
647
+ className = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-50",
648
+ children = "Export to Word",
649
+ onExported,
650
+ ...options
651
+ }) => {
652
+ const docx = useDocxExport(editor, options);
653
+ return /* @__PURE__ */ jsx(
654
+ "button",
655
+ {
656
+ type: "button",
657
+ className,
658
+ disabled: !editor || docx.isExporting,
659
+ "aria-busy": docx.isExporting || void 0,
660
+ title: docx.error ? `Export failed: ${docx.error.message}` : void 0,
661
+ onClick: async () => {
662
+ const report = await docx.download();
663
+ if (report) onExported?.(report);
664
+ },
665
+ children
666
+ }
667
+ );
668
+ };
669
+
603
670
  // src/hooks/useAgentToolProvider.ts
604
- import { useEffect as useEffect6 } from "react";
671
+ import { useEffect as useEffect7 } from "react";
605
672
  function useAgentToolProvider(registry, provider) {
606
- useEffect6(() => {
673
+ useEffect7(() => {
607
674
  if (!registry || !provider) return;
608
675
  return registry.register(provider);
609
676
  }, [registry, provider]);
@@ -613,17 +680,17 @@ function useAgentToolProvider(registry, provider) {
613
680
  import {
614
681
  EditorContent as TipTapEditorContent
615
682
  } from "@tiptap/react";
616
- import { jsx } from "react/jsx-runtime";
683
+ import { jsx as jsx2 } from "react/jsx-runtime";
617
684
  var EditorContent = ({
618
685
  className,
619
686
  editor,
620
687
  ...props
621
688
  }) => {
622
- return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(TipTapEditorContent, { editor, ...props }) });
689
+ return /* @__PURE__ */ jsx2("div", { className, children: /* @__PURE__ */ jsx2(TipTapEditorContent, { editor, ...props }) });
623
690
  };
624
691
 
625
692
  // src/components/BubbleMenu.tsx
626
- import { useEffect as useEffect7, useRef as useRef7, useState as useState4 } from "react";
693
+ import { useEffect as useEffect8, useRef as useRef8, useState as useState4 } from "react";
627
694
  import { createPortal } from "react-dom";
628
695
  import {
629
696
  BubbleMenuPlugin
@@ -637,12 +704,12 @@ var BubbleMenu = ({
637
704
  pluginKey = "bubbleMenu",
638
705
  updateDelay
639
706
  }) => {
640
- const menuEl = useRef7(null);
707
+ const menuEl = useRef8(null);
641
708
  const [mounted, setMounted] = useState4(false);
642
709
  if (!menuEl.current && typeof document !== "undefined") {
643
710
  menuEl.current = document.createElement("div");
644
711
  }
645
- useEffect7(() => {
712
+ useEffect8(() => {
646
713
  if (!editor || !menuEl.current || editor.isDestroyed) {
647
714
  return;
648
715
  }
@@ -673,14 +740,14 @@ var BubbleMenu = ({
673
740
  };
674
741
 
675
742
  // src/components/TemplateRenderer.tsx
676
- import { useMemo as useMemo4 } from "react";
743
+ import { useMemo as useMemo5 } from "react";
677
744
  import {
678
745
  describeConditionalTag,
679
746
  evaluatePromptTokens,
680
747
  formatConditionalTag,
681
748
  resolveTemplates
682
749
  } from "@pixerate/editor";
683
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
750
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
684
751
  var TemplateRenderer = ({
685
752
  content,
686
753
  templates = [],
@@ -692,14 +759,14 @@ var TemplateRenderer = ({
692
759
  onTemplateClick,
693
760
  onVariableClick
694
761
  }) => {
695
- const displayText = useMemo4(() => {
762
+ const displayText = useMemo5(() => {
696
763
  if (!resolve || templates.length === 0) return content;
697
764
  return resolveTemplates(content, templates, { bindings });
698
765
  }, [content, templates, resolve, bindings]);
699
- const tokens = useMemo4(() => {
766
+ const tokens = useMemo5(() => {
700
767
  return evaluatePromptTokens(displayText, values);
701
768
  }, [displayText, values]);
702
- return /* @__PURE__ */ jsx2("div", { className, children: tokens.map((token, index) => {
769
+ return /* @__PURE__ */ jsx3("div", { className, children: tokens.map((token, index) => {
703
770
  if (token.type === "template") {
704
771
  const colors = templateColorMap?.get(token.value) || {
705
772
  from: "#f43f5e",
@@ -709,7 +776,7 @@ var TemplateRenderer = ({
709
776
  "--color-from": colors.from,
710
777
  "--color-to": colors.to
711
778
  };
712
- return /* @__PURE__ */ jsx2(
779
+ return /* @__PURE__ */ jsx3(
713
780
  "span",
714
781
  {
715
782
  style,
@@ -728,7 +795,7 @@ var TemplateRenderer = ({
728
795
  token.defaultValue !== void 0 ? `Default: ${token.defaultValue || "(empty)"}` : void 0
729
796
  ].filter(Boolean).join("\n");
730
797
  if (token.resolvedValue !== void 0) {
731
- return /* @__PURE__ */ jsx2(
798
+ return /* @__PURE__ */ jsx3(
732
799
  "span",
733
800
  {
734
801
  onClick: () => onVariableClick?.(token.value),
@@ -750,7 +817,7 @@ var TemplateRenderer = ({
750
817
  title: details,
751
818
  children: [
752
819
  `{${token.value}`,
753
- token.defaultValue !== void 0 && /* @__PURE__ */ jsx2("span", { className: "opacity-60", children: `|${token.defaultValue}` }),
820
+ token.defaultValue !== void 0 && /* @__PURE__ */ jsx3("span", { className: "opacity-60", children: `|${token.defaultValue}` }),
754
821
  "}"
755
822
  ]
756
823
  },
@@ -758,7 +825,7 @@ var TemplateRenderer = ({
758
825
  );
759
826
  }
760
827
  if (token.type === "conditional" && token.conditional) {
761
- return /* @__PURE__ */ jsx2(
828
+ return /* @__PURE__ */ jsx3(
762
829
  "span",
763
830
  {
764
831
  "data-conditional": token.conditional,
@@ -770,7 +837,7 @@ var TemplateRenderer = ({
770
837
  );
771
838
  }
772
839
  if (token.type === "instruction") {
773
- return /* @__PURE__ */ jsx2(
840
+ return /* @__PURE__ */ jsx3(
774
841
  "span",
775
842
  {
776
843
  className: "inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs italic font-medium rounded-md border border-rose-400/40 bg-rose-400/15 text-rose-600 dark:text-rose-300",
@@ -780,17 +847,17 @@ var TemplateRenderer = ({
780
847
  index
781
848
  );
782
849
  }
783
- return /* @__PURE__ */ jsx2("span", { children: token.value }, index);
850
+ return /* @__PURE__ */ jsx3("span", { children: token.value }, index);
784
851
  }) });
785
852
  };
786
853
 
787
854
  // src/components/VariableForm.tsx
788
- import { useId, useMemo as useMemo5, useState as useState5 } from "react";
855
+ import { useId, useMemo as useMemo6, useState as useState5 } from "react";
789
856
  import {
790
857
  compactVariableValues,
791
858
  getVariableFormFields
792
859
  } from "@pixerate/editor";
793
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
860
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
794
861
  var VariableForm = ({
795
862
  content,
796
863
  templates,
@@ -805,7 +872,7 @@ var VariableForm = ({
805
872
  () => defaultValues ?? {}
806
873
  );
807
874
  const entered = values ?? internal;
808
- const fields = useMemo5(
875
+ const fields = useMemo6(
809
876
  () => getVariableFormFields(content, { templates, bindings }),
810
877
  [content, templates, bindings]
811
878
  );
@@ -815,7 +882,7 @@ var VariableForm = ({
815
882
  onChange?.(compactVariableValues(next), next);
816
883
  };
817
884
  if (fields.length === 0) return null;
818
- return /* @__PURE__ */ jsx3("form", { className, onSubmit: (event) => event.preventDefault(), children: fields.map((field) => {
885
+ return /* @__PURE__ */ jsx4("form", { className, onSubmit: (event) => event.preventDefault(), children: fields.map((field) => {
819
886
  const id = `${idPrefix}-${field.name}`;
820
887
  if (field.conditionOnly) {
821
888
  return /* @__PURE__ */ jsxs2(
@@ -825,7 +892,7 @@ var VariableForm = ({
825
892
  className: "flex items-center gap-2",
826
893
  "data-variable-field": field.name,
827
894
  children: [
828
- /* @__PURE__ */ jsx3(
895
+ /* @__PURE__ */ jsx4(
829
896
  "input",
830
897
  {
831
898
  id,
@@ -835,7 +902,7 @@ var VariableForm = ({
835
902
  onChange: (event) => update(field.name, event.target.checked ? "true" : "")
836
903
  }
837
904
  ),
838
- /* @__PURE__ */ jsx3("span", { children: field.label })
905
+ /* @__PURE__ */ jsx4("span", { children: field.label })
839
906
  ]
840
907
  },
841
908
  field.name
@@ -850,9 +917,9 @@ var VariableForm = ({
850
917
  children: [
851
918
  /* @__PURE__ */ jsxs2("span", { className: "font-medium", children: [
852
919
  field.label,
853
- field.required && /* @__PURE__ */ jsx3("span", { className: "text-rose-500", "aria-hidden": "true", children: " *" })
920
+ field.required && /* @__PURE__ */ jsx4("span", { className: "text-rose-500", "aria-hidden": "true", children: " *" })
854
921
  ] }),
855
- /* @__PURE__ */ jsx3(
922
+ /* @__PURE__ */ jsx4(
856
923
  "input",
857
924
  {
858
925
  id,
@@ -876,7 +943,7 @@ var VariableForm = ({
876
943
  import { useId as useId2, useState as useState7 } from "react";
877
944
 
878
945
  // src/hooks/useMergePreview.ts
879
- import { useCallback as useCallback5, useMemo as useMemo6, useState as useState6 } from "react";
946
+ import { useCallback as useCallback5, useMemo as useMemo7, useState as useState6 } from "react";
880
947
  import {
881
948
  extractVariables,
882
949
  matchFields,
@@ -891,15 +958,15 @@ function useMergePreview(options) {
891
958
  const [overrides, setOverrides] = useState6({});
892
959
  const [index, setIndexState] = useState6(0);
893
960
  const [result, setResult] = useState6(null);
894
- const source = useMemo6(
961
+ const source = useMemo7(
895
962
  () => parseDataSource(sourceText, parseOptions),
896
963
  [sourceText, parseOptions]
897
964
  );
898
- const variables = useMemo6(
965
+ const variables = useMemo7(
899
966
  () => extractVariables(template, { templates, bindings }).map((v) => v.name),
900
967
  [template, templates, bindings]
901
968
  );
902
- const mapping = useMemo6(() => {
969
+ const mapping = useMemo7(() => {
903
970
  const merged = {
904
971
  ...matchFields(variables, source.fields).mapping
905
972
  };
@@ -913,7 +980,7 @@ function useMergePreview(options) {
913
980
  const count = source.records.length;
914
981
  const current = count === 0 ? 0 : Math.min(index, count - 1);
915
982
  const record = source.records[current];
916
- const values = useMemo6(
983
+ const values = useMemo7(
917
984
  () => record ? recordToValues(record, mapping) : {},
918
985
  [record, mapping]
919
986
  );
@@ -974,7 +1041,7 @@ function useMergePreview(options) {
974
1041
  }
975
1042
 
976
1043
  // src/components/MergePreview.tsx
977
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1044
+ import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
978
1045
  function downloadText(text, fileName, type) {
979
1046
  if (typeof document === "undefined" || typeof URL.createObjectURL !== "function") {
980
1047
  return;
@@ -1023,8 +1090,8 @@ var MergePreview = ({
1023
1090
  };
1024
1091
  return /* @__PURE__ */ jsxs3("div", { className, "data-merge-preview": "", children: [
1025
1092
  /* @__PURE__ */ jsxs3("label", { htmlFor: `${id}-source`, className: "flex flex-col gap-1", children: [
1026
- /* @__PURE__ */ jsx4("span", { className: "font-medium", children: "Data source (CSV, TSV or JSON)" }),
1027
- /* @__PURE__ */ jsx4(
1093
+ /* @__PURE__ */ jsx5("span", { className: "font-medium", children: "Data source (CSV, TSV or JSON)" }),
1094
+ /* @__PURE__ */ jsx5(
1028
1095
  "textarea",
1029
1096
  {
1030
1097
  id: `${id}-source`,
@@ -1038,7 +1105,7 @@ var MergePreview = ({
1038
1105
  )
1039
1106
  ] }),
1040
1107
  /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2", children: [
1041
- /* @__PURE__ */ jsx4(
1108
+ /* @__PURE__ */ jsx5(
1042
1109
  "input",
1043
1110
  {
1044
1111
  type: "file",
@@ -1051,18 +1118,18 @@ var MergePreview = ({
1051
1118
  }
1052
1119
  }
1053
1120
  ),
1054
- /* @__PURE__ */ jsx4("span", { "data-merge-summary": "", className: "opacity-70", children: `${source.records.length} record${source.records.length === 1 ? "" : "s"} \xB7 ${source.fields.length} field${source.fields.length === 1 ? "" : "s"}` })
1121
+ /* @__PURE__ */ jsx5("span", { "data-merge-summary": "", className: "opacity-70", children: `${source.records.length} record${source.records.length === 1 ? "" : "s"} \xB7 ${source.fields.length} field${source.fields.length === 1 ? "" : "s"}` })
1055
1122
  ] }),
1056
- source.warnings.length > 0 && merge.sourceText.trim() !== "" && /* @__PURE__ */ jsx4("ul", { "data-merge-warnings": "", className: "text-amber-600 text-xs", children: source.warnings.map((warning, i) => /* @__PURE__ */ jsx4("li", { children: warning.message }, i)) }),
1123
+ source.warnings.length > 0 && merge.sourceText.trim() !== "" && /* @__PURE__ */ jsx5("ul", { "data-merge-warnings": "", className: "text-amber-600 text-xs", children: source.warnings.map((warning, i) => /* @__PURE__ */ jsx5("li", { children: warning.message }, i)) }),
1057
1124
  merge.variables.length > 0 && /* @__PURE__ */ jsxs3("fieldset", { className: "flex flex-col gap-1", children: [
1058
- /* @__PURE__ */ jsx4("legend", { className: "font-medium", children: "Field mapping" }),
1125
+ /* @__PURE__ */ jsx5("legend", { className: "font-medium", children: "Field mapping" }),
1059
1126
  merge.variables.map((variable) => /* @__PURE__ */ jsxs3(
1060
1127
  "label",
1061
1128
  {
1062
1129
  className: "flex items-center gap-2",
1063
1130
  "data-merge-mapping": variable,
1064
1131
  children: [
1065
- /* @__PURE__ */ jsx4("span", { className: "min-w-24 font-mono", children: `{${variable}}` }),
1132
+ /* @__PURE__ */ jsx5("span", { className: "min-w-24 font-mono", children: `{${variable}}` }),
1066
1133
  /* @__PURE__ */ jsxs3(
1067
1134
  "select",
1068
1135
  {
@@ -1071,8 +1138,8 @@ var MergePreview = ({
1071
1138
  onChange: (event) => merge.setFieldMapping(variable, event.target.value || null),
1072
1139
  className: "px-1 py-0.5 rounded-md border border-violet-400/40 bg-transparent",
1073
1140
  children: [
1074
- /* @__PURE__ */ jsx4("option", { value: "", children: "(none)" }),
1075
- source.fields.map((field) => /* @__PURE__ */ jsx4("option", { value: field, children: field }, field))
1141
+ /* @__PURE__ */ jsx5("option", { value: "", children: "(none)" }),
1142
+ source.fields.map((field) => /* @__PURE__ */ jsx5("option", { value: field, children: field }, field))
1076
1143
  ]
1077
1144
  }
1078
1145
  )
@@ -1082,7 +1149,7 @@ var MergePreview = ({
1082
1149
  ))
1083
1150
  ] }),
1084
1151
  /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
1085
- /* @__PURE__ */ jsx4(
1152
+ /* @__PURE__ */ jsx5(
1086
1153
  "button",
1087
1154
  {
1088
1155
  type: "button",
@@ -1092,8 +1159,8 @@ var MergePreview = ({
1092
1159
  children: "Prev"
1093
1160
  }
1094
1161
  ),
1095
- /* @__PURE__ */ jsx4("span", { "data-merge-position": "", children: merge.count === 0 ? "No records" : `Record ${merge.index + 1} of ${merge.count}` }),
1096
- /* @__PURE__ */ jsx4(
1162
+ /* @__PURE__ */ jsx5("span", { "data-merge-position": "", children: merge.count === 0 ? "No records" : `Record ${merge.index + 1} of ${merge.count}` }),
1163
+ /* @__PURE__ */ jsx5(
1097
1164
  "button",
1098
1165
  {
1099
1166
  type: "button",
@@ -1104,7 +1171,7 @@ var MergePreview = ({
1104
1171
  }
1105
1172
  )
1106
1173
  ] }),
1107
- /* @__PURE__ */ jsx4("div", { "data-merge-output": "", children: /* @__PURE__ */ jsx4(
1174
+ /* @__PURE__ */ jsx5("div", { "data-merge-output": "", children: /* @__PURE__ */ jsx5(
1108
1175
  TemplateRenderer,
1109
1176
  {
1110
1177
  content,
@@ -1116,7 +1183,7 @@ var MergePreview = ({
1116
1183
  }
1117
1184
  ) }),
1118
1185
  /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2", children: [
1119
- /* @__PURE__ */ jsx4(
1186
+ /* @__PURE__ */ jsx5(
1120
1187
  "button",
1121
1188
  {
1122
1189
  type: "button",
@@ -1131,9 +1198,9 @@ var MergePreview = ({
1131
1198
  }
1132
1199
  ),
1133
1200
  result && /* @__PURE__ */ jsxs3(Fragment, { children: [
1134
- /* @__PURE__ */ jsx4("span", { "data-merge-result": "", children: `${result.outputs.length} merged${result.skipped.length ? `, ${result.skipped.length} skipped` : ""}${result.truncated ? " (stopped early)" : ""}` }),
1135
- /* @__PURE__ */ jsx4("button", { type: "button", "data-merge-copy": "", onClick: copy, children: copied ? "Copied" : "Copy" }),
1136
- /* @__PURE__ */ jsx4(
1201
+ /* @__PURE__ */ jsx5("span", { "data-merge-result": "", children: `${result.outputs.length} merged${result.skipped.length ? `, ${result.skipped.length} skipped` : ""}${result.truncated ? " (stopped early)" : ""}` }),
1202
+ /* @__PURE__ */ jsx5("button", { type: "button", "data-merge-copy": "", onClick: copy, children: copied ? "Copied" : "Copy" }),
1203
+ /* @__PURE__ */ jsx5(
1137
1204
  "button",
1138
1205
  {
1139
1206
  type: "button",
@@ -1142,7 +1209,7 @@ var MergePreview = ({
1142
1209
  children: "Download .txt"
1143
1210
  }
1144
1211
  ),
1145
- /* @__PURE__ */ jsx4(
1212
+ /* @__PURE__ */ jsx5(
1146
1213
  "button",
1147
1214
  {
1148
1215
  type: "button",
@@ -1153,7 +1220,7 @@ var MergePreview = ({
1153
1220
  )
1154
1221
  ] })
1155
1222
  ] }),
1156
- result && /* @__PURE__ */ jsx4(
1223
+ result && /* @__PURE__ */ jsx5(
1157
1224
  "textarea",
1158
1225
  {
1159
1226
  readOnly: true,
@@ -1168,16 +1235,16 @@ var MergePreview = ({
1168
1235
  };
1169
1236
 
1170
1237
  // src/hooks/useOutline.ts
1171
- import { useMemo as useMemo7, useSyncExternalStore as useSyncExternalStore4 } from "react";
1238
+ import { useMemo as useMemo8, useSyncExternalStore as useSyncExternalStore5 } from "react";
1172
1239
  import { createOutlineStore } from "@pixerate/editor";
1173
1240
  function useOutline(editor) {
1174
- const store = useMemo7(() => createOutlineStore(editor), [editor]);
1175
- return useSyncExternalStore4(store.subscribe, store.getSnapshot, store.getSnapshot);
1241
+ const store = useMemo8(() => createOutlineStore(editor), [editor]);
1242
+ return useSyncExternalStore5(store.subscribe, store.getSnapshot, store.getSnapshot);
1176
1243
  }
1177
1244
 
1178
1245
  // src/components/OutlinePanel.tsx
1179
1246
  import { navigateToHeading } from "@pixerate/editor";
1180
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1247
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1181
1248
  var OutlinePanel = ({
1182
1249
  editor,
1183
1250
  title = "Outline",
@@ -1192,10 +1259,10 @@ var OutlinePanel = ({
1192
1259
  const { headings, activeId } = useOutline(editor);
1193
1260
  const visible = headings.filter((h) => h.level <= maxLevel);
1194
1261
  return /* @__PURE__ */ jsxs4("nav", { className, "aria-label": "Document outline", "data-outline-panel": "", children: [
1195
- title != null && /* @__PURE__ */ jsx5("div", { className: "pixerate-outline-title", children: title }),
1196
- visible.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "pixerate-outline-empty", children: emptyState }) : /* @__PURE__ */ jsx5("ul", { role: "list", style: { listStyle: "none", margin: 0, padding: 0 }, children: visible.map((heading) => {
1262
+ title != null && /* @__PURE__ */ jsx6("div", { className: "pixerate-outline-title", children: title }),
1263
+ visible.length === 0 ? /* @__PURE__ */ jsx6("div", { className: "pixerate-outline-empty", children: emptyState }) : /* @__PURE__ */ jsx6("ul", { role: "list", style: { listStyle: "none", margin: 0, padding: 0 }, children: visible.map((heading) => {
1197
1264
  const active = heading.id === activeId;
1198
- return /* @__PURE__ */ jsx5("li", { "data-level": heading.level, children: /* @__PURE__ */ jsx5(
1265
+ return /* @__PURE__ */ jsx6("li", { "data-level": heading.level, children: /* @__PURE__ */ jsx6(
1199
1266
  "button",
1200
1267
  {
1201
1268
  type: "button",
@@ -1216,6 +1283,168 @@ var OutlinePanel = ({
1216
1283
  ] });
1217
1284
  };
1218
1285
 
1286
+ // src/hooks/useParagraphStyles.ts
1287
+ import { useMemo as useMemo9, useSyncExternalStore as useSyncExternalStore6 } from "react";
1288
+ import {
1289
+ createParagraphStylesStore,
1290
+ getStyleRegistry
1291
+ } from "@pixerate/editor";
1292
+ function useParagraphStyles(editor) {
1293
+ const store = useMemo9(() => createParagraphStylesStore(editor), [editor]);
1294
+ const snapshot = useSyncExternalStore6(store.subscribe, store.getSnapshot, store.getSnapshot);
1295
+ return useMemo9(() => {
1296
+ const registry = getStyleRegistry(editor);
1297
+ const live = () => editor && !editor.isDestroyed ? editor : null;
1298
+ return {
1299
+ ...snapshot,
1300
+ registry,
1301
+ currentStyle: snapshot.currentStyleId && registry?.get(snapshot.currentStyleId) || null,
1302
+ apply: (id) => !!live()?.chain().focus().setParagraphStyle(id).run(),
1303
+ clear: () => !!live()?.chain().focus().clearParagraphStyle().run(),
1304
+ updateFromSelection: (id) => !!live()?.chain().focus().updateParagraphStyleFromSelection(id).run(),
1305
+ createFromSelection: (name, options) => {
1306
+ const e = live();
1307
+ if (!e || !registry) return null;
1308
+ const id = options?.id ?? registry.newId(name.trim());
1309
+ const ok = e.chain().focus().createParagraphStyleFromSelection(name, { ...options, id }).run();
1310
+ return ok ? id : null;
1311
+ },
1312
+ applyCharacterStyle: (id) => !!live()?.chain().focus().setCharacterStyle(id).run()
1313
+ };
1314
+ }, [snapshot, editor]);
1315
+ }
1316
+
1317
+ // src/components/StylesGallery.tsx
1318
+ import { useState as useState8 } from "react";
1319
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1320
+ var camel = (property) => property.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1321
+ var keepSelection = (event) => event.preventDefault();
1322
+ var StylesGallery = ({
1323
+ editor,
1324
+ variant = "dropdown",
1325
+ showAllStyles = false,
1326
+ showActions = true,
1327
+ preview = true,
1328
+ className = "relative inline-flex flex-wrap items-center gap-1 text-sm",
1329
+ itemClassName = "rounded px-2 py-1 text-left text-xs text-foreground hover:bg-muted",
1330
+ activeItemClassName = "bg-primary/10 text-primary",
1331
+ onChange
1332
+ }) => {
1333
+ const styles = useParagraphStyles(editor);
1334
+ const [open, setOpen] = useState8(false);
1335
+ const [creating, setCreating] = useState8(false);
1336
+ const [name, setName] = useState8("");
1337
+ const list = showAllStyles ? styles.styles : styles.gallery;
1338
+ const current = styles.currentStyle;
1339
+ const expanded = variant === "gallery" || open;
1340
+ const close = () => {
1341
+ if (variant === "dropdown") setOpen(false);
1342
+ setCreating(false);
1343
+ setName("");
1344
+ };
1345
+ const previewStyle = (id) => {
1346
+ if (!preview || !styles.registry) return void 0;
1347
+ return Object.fromEntries(
1348
+ Object.entries(styles.registry.previewDeclarations(id)).map(([k, v]) => [camel(k), v])
1349
+ );
1350
+ };
1351
+ const apply = (id) => {
1352
+ if (styles.apply(id)) onChange?.({ type: "apply", id });
1353
+ close();
1354
+ };
1355
+ const create = (event) => {
1356
+ event.preventDefault();
1357
+ const id = name.trim() ? styles.createFromSelection(name) : null;
1358
+ if (id) onChange?.({ type: "create", id });
1359
+ close();
1360
+ };
1361
+ return /* @__PURE__ */ jsxs5("div", { className, role: "group", "aria-label": "Paragraph styles", "data-styles-gallery": variant, children: [
1362
+ variant === "dropdown" && /* @__PURE__ */ jsxs5(
1363
+ "button",
1364
+ {
1365
+ type: "button",
1366
+ className: itemClassName,
1367
+ "aria-haspopup": "listbox",
1368
+ "aria-expanded": open,
1369
+ disabled: !editor || !styles.registry,
1370
+ onMouseDown: keepSelection,
1371
+ onClick: () => setOpen((value) => !value),
1372
+ children: [
1373
+ current?.name ?? "Styles",
1374
+ " \u25BE"
1375
+ ]
1376
+ }
1377
+ ),
1378
+ expanded && /* @__PURE__ */ jsxs5(
1379
+ "div",
1380
+ {
1381
+ className: variant === "dropdown" ? "absolute left-0 top-full z-20 mt-1 flex min-w-[12rem] flex-col gap-0.5 rounded-md border border-border bg-background p-1 shadow-md" : "flex flex-wrap items-center gap-1",
1382
+ children: [
1383
+ /* @__PURE__ */ jsx7("div", { role: "listbox", "aria-label": "Styles", className: variant === "dropdown" ? "flex flex-col gap-0.5" : "flex flex-wrap gap-1", children: list.map((style) => {
1384
+ const active = style.id === styles.currentStyleId;
1385
+ return /* @__PURE__ */ jsx7(
1386
+ "button",
1387
+ {
1388
+ type: "button",
1389
+ role: "option",
1390
+ "aria-selected": active,
1391
+ "data-style-id": style.id,
1392
+ className: active ? `${itemClassName} ${activeItemClassName}` : itemClassName,
1393
+ style: previewStyle(style.id),
1394
+ onMouseDown: keepSelection,
1395
+ onClick: () => apply(style.id),
1396
+ children: style.name
1397
+ },
1398
+ style.id
1399
+ );
1400
+ }) }),
1401
+ showActions && /* @__PURE__ */ jsxs5("div", { className: variant === "dropdown" ? "mt-1 flex flex-col gap-0.5 border-t border-border pt-1" : "flex items-center gap-1", children: [
1402
+ /* @__PURE__ */ jsx7(
1403
+ "button",
1404
+ {
1405
+ type: "button",
1406
+ className: itemClassName,
1407
+ disabled: !current,
1408
+ onMouseDown: keepSelection,
1409
+ onClick: () => {
1410
+ if (current && styles.updateFromSelection(current.id)) {
1411
+ onChange?.({ type: "update", id: current.id });
1412
+ }
1413
+ close();
1414
+ },
1415
+ children: current ? `Update ${current.name} to match selection` : "Update to match selection"
1416
+ }
1417
+ ),
1418
+ creating ? /* @__PURE__ */ jsxs5("form", { className: "flex items-center gap-1", onSubmit: create, children: [
1419
+ /* @__PURE__ */ jsx7(
1420
+ "input",
1421
+ {
1422
+ "aria-label": "New style name",
1423
+ className: "w-32 rounded border border-border bg-background px-1 py-0.5 text-xs",
1424
+ value: name,
1425
+ autoFocus: true,
1426
+ onChange: (event) => setName(event.target.value)
1427
+ }
1428
+ ),
1429
+ /* @__PURE__ */ jsx7("button", { type: "submit", className: itemClassName, disabled: !name.trim(), children: "Create" })
1430
+ ] }) : /* @__PURE__ */ jsx7(
1431
+ "button",
1432
+ {
1433
+ type: "button",
1434
+ className: itemClassName,
1435
+ disabled: !editor || !styles.registry,
1436
+ onMouseDown: keepSelection,
1437
+ onClick: () => setCreating(true),
1438
+ children: "New style from selection"
1439
+ }
1440
+ )
1441
+ ] })
1442
+ ]
1443
+ }
1444
+ )
1445
+ ] });
1446
+ };
1447
+
1219
1448
  // src/index.ts
1220
1449
  import {
1221
1450
  DirtyTracker as DirtyTracker2,
@@ -1228,10 +1457,10 @@ import {
1228
1457
  } from "@pixerate/editor";
1229
1458
 
1230
1459
  // src/slots.tsx
1231
- import { createContext, useContext, useMemo as useMemo8 } from "react";
1232
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1460
+ import { createContext, useContext, useMemo as useMemo10 } from "react";
1461
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1233
1462
  var defaultSlots = {
1234
- Button: (props) => /* @__PURE__ */ jsx6(
1463
+ Button: (props) => /* @__PURE__ */ jsx8(
1235
1464
  "button",
1236
1465
  {
1237
1466
  type: "button",
@@ -1241,26 +1470,26 @@ var defaultSlots = {
1241
1470
  children: props.children
1242
1471
  }
1243
1472
  ),
1244
- Tooltip: (props) => /* @__PURE__ */ jsx6("div", { className: `pixerate-tooltip-container ${props.className || ""}`, title: String(props.content), children: props.children }),
1245
- NodeHeader: (props) => /* @__PURE__ */ jsxs5("div", { className: `pixerate-node-header flex items-center justify-between ${props.className || ""}`, children: [
1246
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
1473
+ Tooltip: (props) => /* @__PURE__ */ jsx8("div", { className: `pixerate-tooltip-container ${props.className || ""}`, title: String(props.content), children: props.children }),
1474
+ NodeHeader: (props) => /* @__PURE__ */ jsxs6("div", { className: `pixerate-node-header flex items-center justify-between ${props.className || ""}`, children: [
1475
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
1247
1476
  props.icon,
1248
- /* @__PURE__ */ jsx6("span", { className: "font-semibold", children: props.title })
1477
+ /* @__PURE__ */ jsx8("span", { className: "font-semibold", children: props.title })
1249
1478
  ] }),
1250
- props.onClose && /* @__PURE__ */ jsx6("button", { type: "button", onClick: props.onClose, className: "opacity-70 hover:opacity-100", children: "\xD7" })
1479
+ props.onClose && /* @__PURE__ */ jsx8("button", { type: "button", onClick: props.onClose, className: "opacity-70 hover:opacity-100", children: "\xD7" })
1251
1480
  ] })
1252
1481
  };
1253
1482
  var EditorSlotsContext = createContext(defaultSlots);
1254
1483
  function EditorSlotsProvider({ slots, children }) {
1255
- const mergedSlots = useMemo8(() => ({ ...defaultSlots, ...slots }), [slots]);
1256
- return /* @__PURE__ */ jsx6(EditorSlotsContext.Provider, { value: mergedSlots, children });
1484
+ const mergedSlots = useMemo10(() => ({ ...defaultSlots, ...slots }), [slots]);
1485
+ return /* @__PURE__ */ jsx8(EditorSlotsContext.Provider, { value: mergedSlots, children });
1257
1486
  }
1258
1487
  function useEditorSlots() {
1259
1488
  return useContext(EditorSlotsContext);
1260
1489
  }
1261
1490
 
1262
1491
  // src/review/useReview.ts
1263
- import { useCallback as useCallback6, useMemo as useMemo9, useSyncExternalStore as useSyncExternalStore5 } from "react";
1492
+ import { useCallback as useCallback6, useMemo as useMemo11, useSyncExternalStore as useSyncExternalStore7 } from "react";
1264
1493
  import {
1265
1494
  getCommentStore,
1266
1495
  getTrackChangesStore
@@ -1287,8 +1516,8 @@ function useComments(source) {
1287
1516
  [store]
1288
1517
  );
1289
1518
  const getSnapshot = () => store?.getSnapshot() ?? EMPTY_COMMENTS;
1290
- const snapshot = useSyncExternalStore5(subscribe, getSnapshot, getSnapshot);
1291
- return useMemo9(() => ({ ...snapshot, store }), [snapshot, store]);
1519
+ const snapshot = useSyncExternalStore7(subscribe, getSnapshot, getSnapshot);
1520
+ return useMemo11(() => ({ ...snapshot, store }), [snapshot, store]);
1292
1521
  }
1293
1522
  function useTrackChanges(editor) {
1294
1523
  const store = getTrackChangesStore(editor ?? null);
@@ -1297,8 +1526,8 @@ function useTrackChanges(editor) {
1297
1526
  [store]
1298
1527
  );
1299
1528
  const getSnapshot = () => store?.getSnapshot() ?? EMPTY_TRACK_CHANGES;
1300
- const snapshot = useSyncExternalStore5(subscribe, getSnapshot, getSnapshot);
1301
- return useMemo9(() => {
1529
+ const snapshot = useSyncExternalStore7(subscribe, getSnapshot, getSnapshot);
1530
+ return useMemo11(() => {
1302
1531
  const run = (fn) => () => {
1303
1532
  if (editor && !editor.isDestroyed) fn(editor);
1304
1533
  };
@@ -1312,18 +1541,21 @@ function useTrackChanges(editor) {
1312
1541
  acceptAll: run((e) => e.commands.acceptAllChanges()),
1313
1542
  rejectAll: run((e) => e.commands.rejectAllChanges()),
1314
1543
  next: run((e) => e.chain().focus().selectNextChange().run()),
1315
- previous: run((e) => e.chain().focus().selectPreviousChange().run())
1544
+ previous: run((e) => e.chain().focus().selectPreviousChange().run()),
1545
+ select: (id) => run((e) => e.chain().focus().selectChange(id).run())(),
1546
+ acceptMove: (id) => run((e) => e.commands.acceptMove(id))(),
1547
+ rejectMove: (id) => run((e) => e.commands.rejectMove(id))()
1316
1548
  };
1317
1549
  }, [snapshot, editor]);
1318
1550
  }
1319
1551
 
1320
1552
  // src/review/CommentsPanel.tsx
1321
- import { useState as useState8 } from "react";
1553
+ import { useState as useState9 } from "react";
1322
1554
  import {
1323
1555
  formatReviewTime,
1324
1556
  getCommentAuthor
1325
1557
  } from "@pixerate/editor";
1326
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1558
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1327
1559
  var buttonClass = "rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50";
1328
1560
  var primaryButtonClass = "rounded px-2 py-1 text-xs bg-primary/10 text-primary hover:bg-primary/20 disabled:opacity-50";
1329
1561
  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";
@@ -1337,18 +1569,18 @@ var CommentsPanel = ({
1337
1569
  emptyMessage = "No comments yet. Select text and add a comment."
1338
1570
  }) => {
1339
1571
  const { threads, activeThreadId, store } = useComments(storeProp ?? editor);
1340
- const [showResolved, setShowResolved] = useState8(showResolvedInitial);
1572
+ const [showResolved, setShowResolved] = useState9(showResolvedInitial);
1341
1573
  const author = authorProp ?? getCommentAuthor(editor) ?? { id: "anonymous", name: "Anonymous" };
1342
1574
  const visible = threads.filter((t) => showResolved || !t.resolved);
1343
1575
  const resolvedCount = threads.filter((t) => t.resolved).length;
1344
- return /* @__PURE__ */ jsxs6("aside", { className, "aria-label": "Comments", children: [
1345
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between gap-2", children: [
1346
- /* @__PURE__ */ jsxs6("span", { className: "text-xs font-medium text-muted-foreground", children: [
1576
+ return /* @__PURE__ */ jsxs7("aside", { className, "aria-label": "Comments", children: [
1577
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between gap-2", children: [
1578
+ /* @__PURE__ */ jsxs7("span", { className: "text-xs font-medium text-muted-foreground", children: [
1347
1579
  threads.length - resolvedCount,
1348
1580
  " open",
1349
1581
  resolvedCount > 0 ? ` \xB7 ${resolvedCount} resolved` : ""
1350
1582
  ] }),
1351
- resolvedCount > 0 && /* @__PURE__ */ jsx7(
1583
+ resolvedCount > 0 && /* @__PURE__ */ jsx9(
1352
1584
  "button",
1353
1585
  {
1354
1586
  type: "button",
@@ -1359,7 +1591,7 @@ var CommentsPanel = ({
1359
1591
  }
1360
1592
  )
1361
1593
  ] }),
1362
- visible.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "text-xs text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ jsx7("ul", { className: "flex flex-col gap-2", role: "list", children: visible.map((thread) => /* @__PURE__ */ jsx7(
1594
+ visible.length === 0 ? /* @__PURE__ */ jsx9("p", { className: "text-xs text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ jsx9("ul", { className: "flex flex-col gap-2", role: "list", children: visible.map((thread) => /* @__PURE__ */ jsx9(
1363
1595
  CommentThreadCard,
1364
1596
  {
1365
1597
  thread,
@@ -1381,7 +1613,7 @@ var CommentThreadCard = ({
1381
1613
  author,
1382
1614
  formatTime
1383
1615
  }) => {
1384
- const [draft, setDraft] = useState8("");
1616
+ const [draft, setDraft] = useState9("");
1385
1617
  const isDraft = thread.body === "";
1386
1618
  const quote = thread.anchor?.quote ?? thread.lastQuote ?? "";
1387
1619
  const live = editor && !editor.isDestroyed ? editor : null;
@@ -1407,7 +1639,7 @@ var CommentThreadCard = ({
1407
1639
  setDraft("");
1408
1640
  };
1409
1641
  const stop = (e) => e.stopPropagation();
1410
- return /* @__PURE__ */ jsxs6(
1642
+ return /* @__PURE__ */ jsxs7(
1411
1643
  "li",
1412
1644
  {
1413
1645
  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" : ""}`,
@@ -1423,24 +1655,24 @@ var CommentThreadCard = ({
1423
1655
  }
1424
1656
  },
1425
1657
  children: [
1426
- /* @__PURE__ */ jsxs6("div", { className: "flex items-baseline justify-between gap-2", children: [
1427
- /* @__PURE__ */ jsx7("span", { className: "font-medium", children: thread.author.name }),
1428
- /* @__PURE__ */ jsx7("span", { className: "text-xs text-muted-foreground", children: formatTime(thread.createdAt) })
1658
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-baseline justify-between gap-2", children: [
1659
+ /* @__PURE__ */ jsx9("span", { className: "font-medium", children: thread.author.name }),
1660
+ /* @__PURE__ */ jsx9("span", { className: "text-xs text-muted-foreground", children: formatTime(thread.createdAt) })
1429
1661
  ] }),
1430
- quote && /* @__PURE__ */ jsx7("blockquote", { className: "border-l-2 border-border pl-2 text-xs text-muted-foreground line-clamp-2", children: quote }),
1431
- thread.orphaned && /* @__PURE__ */ jsx7("span", { className: "text-xs text-destructive", children: "The commented text was deleted." }),
1432
- thread.resolved && /* @__PURE__ */ jsx7("span", { className: "text-xs text-muted-foreground", children: "Resolved" }),
1433
- !isDraft && /* @__PURE__ */ jsx7("p", { className: "whitespace-pre-wrap break-words", children: thread.body }),
1434
- thread.replies.length > 0 && /* @__PURE__ */ jsx7("ul", { className: "flex flex-col gap-2 border-l border-border pl-3", role: "list", children: thread.replies.map((reply) => /* @__PURE__ */ jsxs6("li", { className: "flex flex-col gap-0.5", "data-reply-id": reply.id, children: [
1435
- /* @__PURE__ */ jsxs6("span", { className: "text-xs", children: [
1436
- /* @__PURE__ */ jsx7("span", { className: "font-medium", children: reply.author.name }),
1662
+ quote && /* @__PURE__ */ jsx9("blockquote", { className: "border-l-2 border-border pl-2 text-xs text-muted-foreground line-clamp-2", children: quote }),
1663
+ thread.orphaned && /* @__PURE__ */ jsx9("span", { className: "text-xs text-destructive", children: "The commented text was deleted." }),
1664
+ thread.resolved && /* @__PURE__ */ jsx9("span", { className: "text-xs text-muted-foreground", children: "Resolved" }),
1665
+ !isDraft && /* @__PURE__ */ jsx9("p", { className: "whitespace-pre-wrap break-words", children: thread.body }),
1666
+ thread.replies.length > 0 && /* @__PURE__ */ jsx9("ul", { className: "flex flex-col gap-2 border-l border-border pl-3", role: "list", children: thread.replies.map((reply) => /* @__PURE__ */ jsxs7("li", { className: "flex flex-col gap-0.5", "data-reply-id": reply.id, children: [
1667
+ /* @__PURE__ */ jsxs7("span", { className: "text-xs", children: [
1668
+ /* @__PURE__ */ jsx9("span", { className: "font-medium", children: reply.author.name }),
1437
1669
  " ",
1438
- /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: formatTime(reply.createdAt) })
1670
+ /* @__PURE__ */ jsx9("span", { className: "text-muted-foreground", children: formatTime(reply.createdAt) })
1439
1671
  ] }),
1440
- /* @__PURE__ */ jsx7("p", { className: "whitespace-pre-wrap break-words", children: reply.body })
1672
+ /* @__PURE__ */ jsx9("p", { className: "whitespace-pre-wrap break-words", children: reply.body })
1441
1673
  ] }, reply.id)) }),
1442
- (active || isDraft) && !thread.resolved && /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1", onClick: stop, children: [
1443
- /* @__PURE__ */ jsx7(
1674
+ (active || isDraft) && !thread.resolved && /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1", onClick: stop, children: [
1675
+ /* @__PURE__ */ jsx9(
1444
1676
  "textarea",
1445
1677
  {
1446
1678
  className: textareaClass,
@@ -1458,9 +1690,9 @@ var CommentThreadCard = ({
1458
1690
  }
1459
1691
  }
1460
1692
  ),
1461
- /* @__PURE__ */ jsxs6("div", { className: "flex justify-end gap-1", children: [
1462
- isDraft && /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass, onClick: remove, children: "Cancel" }),
1463
- /* @__PURE__ */ jsx7(
1693
+ /* @__PURE__ */ jsxs7("div", { className: "flex justify-end gap-1", children: [
1694
+ isDraft && /* @__PURE__ */ jsx9("button", { type: "button", className: buttonClass, onClick: remove, children: "Cancel" }),
1695
+ /* @__PURE__ */ jsx9(
1464
1696
  "button",
1465
1697
  {
1466
1698
  type: "button",
@@ -1472,9 +1704,9 @@ var CommentThreadCard = ({
1472
1704
  )
1473
1705
  ] })
1474
1706
  ] }),
1475
- !isDraft && /* @__PURE__ */ jsxs6("div", { className: "flex justify-end gap-1", onClick: stop, children: [
1476
- /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass, onClick: () => setResolved(!thread.resolved), children: thread.resolved ? "Reopen" : "Resolve" }),
1477
- /* @__PURE__ */ jsx7(
1707
+ !isDraft && /* @__PURE__ */ jsxs7("div", { className: "flex justify-end gap-1", onClick: stop, children: [
1708
+ /* @__PURE__ */ jsx9("button", { type: "button", className: buttonClass, onClick: () => setResolved(!thread.resolved), children: thread.resolved ? "Reopen" : "Resolve" }),
1709
+ /* @__PURE__ */ jsx9(
1478
1710
  "button",
1479
1711
  {
1480
1712
  type: "button",
@@ -1490,7 +1722,7 @@ var CommentThreadCard = ({
1490
1722
  };
1491
1723
 
1492
1724
  // src/review/ReviewToolbar.tsx
1493
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1725
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1494
1726
  var defaultDescriptionClass = "max-w-[16rem] truncate px-1 text-xs text-muted-foreground";
1495
1727
  var buttonClass2 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
1496
1728
  var ReviewToolbar = ({
@@ -1503,8 +1735,8 @@ var ReviewToolbar = ({
1503
1735
  const count = review.changes.length;
1504
1736
  const current = review.currentChange;
1505
1737
  const index = current ? review.changes.findIndex((c) => c.id === current.id) + 1 : 0;
1506
- return /* @__PURE__ */ jsxs7("div", { className, role: "toolbar", "aria-label": "Review", children: [
1507
- /* @__PURE__ */ jsx8(
1738
+ return /* @__PURE__ */ jsxs8("div", { className, role: "toolbar", "aria-label": "Review", children: [
1739
+ /* @__PURE__ */ jsx10(
1508
1740
  "button",
1509
1741
  {
1510
1742
  type: "button",
@@ -1515,8 +1747,8 @@ var ReviewToolbar = ({
1515
1747
  children: review.enabled ? "Tracking on" : "Track changes"
1516
1748
  }
1517
1749
  ),
1518
- /* @__PURE__ */ jsx8("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1519
- /* @__PURE__ */ jsx8(
1750
+ /* @__PURE__ */ jsx10("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1751
+ /* @__PURE__ */ jsx10(
1520
1752
  "button",
1521
1753
  {
1522
1754
  type: "button",
@@ -1527,8 +1759,8 @@ var ReviewToolbar = ({
1527
1759
  children: "\u2039"
1528
1760
  }
1529
1761
  ),
1530
- /* @__PURE__ */ jsx8("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` }),
1531
- /* @__PURE__ */ jsx8(
1762
+ /* @__PURE__ */ jsx10("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` }),
1763
+ /* @__PURE__ */ jsx10(
1532
1764
  "button",
1533
1765
  {
1534
1766
  type: "button",
@@ -1539,7 +1771,7 @@ var ReviewToolbar = ({
1539
1771
  children: "\u203A"
1540
1772
  }
1541
1773
  ),
1542
- showDescription && current && /* @__PURE__ */ jsxs7(
1774
+ showDescription && current && /* @__PURE__ */ jsxs8(
1543
1775
  "span",
1544
1776
  {
1545
1777
  className: descriptionClassName,
@@ -1551,8 +1783,8 @@ var ReviewToolbar = ({
1551
1783
  ]
1552
1784
  }
1553
1785
  ),
1554
- /* @__PURE__ */ jsx8("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1555
- /* @__PURE__ */ jsx8(
1786
+ /* @__PURE__ */ jsx10("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1787
+ /* @__PURE__ */ jsx10(
1556
1788
  "button",
1557
1789
  {
1558
1790
  type: "button",
@@ -1562,7 +1794,7 @@ var ReviewToolbar = ({
1562
1794
  children: "Accept"
1563
1795
  }
1564
1796
  ),
1565
- /* @__PURE__ */ jsx8(
1797
+ /* @__PURE__ */ jsx10(
1566
1798
  "button",
1567
1799
  {
1568
1800
  type: "button",
@@ -1572,18 +1804,18 @@ var ReviewToolbar = ({
1572
1804
  children: "Reject"
1573
1805
  }
1574
1806
  ),
1575
- /* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.acceptAll, children: "Accept all" }),
1576
- /* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.rejectAll, children: "Reject all" })
1807
+ /* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.acceptAll, children: "Accept all" }),
1808
+ /* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.rejectAll, children: "Reject all" })
1577
1809
  ] });
1578
1810
  };
1579
1811
 
1580
1812
  // src/review/CommentButton.tsx
1581
- import { jsx as jsx9 } from "react/jsx-runtime";
1813
+ import { jsx as jsx11 } from "react/jsx-runtime";
1582
1814
  var CommentButton = ({
1583
1815
  editor,
1584
1816
  className = "rounded px-2 py-1 text-xs text-popover-foreground hover:bg-muted",
1585
1817
  children = "Comment"
1586
- }) => /* @__PURE__ */ jsx9(
1818
+ }) => /* @__PURE__ */ jsx11(
1587
1819
  "button",
1588
1820
  {
1589
1821
  type: "button",
@@ -1595,13 +1827,13 @@ var CommentButton = ({
1595
1827
  );
1596
1828
 
1597
1829
  // src/review/useDocumentCompare.ts
1598
- import { useCallback as useCallback7, useMemo as useMemo10, useRef as useRef8, useState as useState9 } from "react";
1830
+ import { useCallback as useCallback7, useMemo as useMemo12, useRef as useRef9, useState as useState10 } from "react";
1599
1831
  import {
1600
1832
  applyDocumentComparison
1601
1833
  } from "@pixerate/editor";
1602
1834
  function useDocumentCompare(editor, options) {
1603
- const [summary, setSummary] = useState9(null);
1604
- const defaults = useRef8(options);
1835
+ const [summary, setSummary] = useState10(null);
1836
+ const defaults = useRef9(options);
1605
1837
  defaults.current = options;
1606
1838
  const compare = useCallback7(
1607
1839
  (original, revised, overrides) => {
@@ -1616,11 +1848,58 @@ function useDocumentCompare(editor, options) {
1616
1848
  [editor]
1617
1849
  );
1618
1850
  const clear = useCallback7(() => setSummary(null), []);
1619
- return useMemo10(() => ({ summary, compare, clear }), [summary, compare, clear]);
1851
+ return useMemo12(() => ({ summary, compare, clear }), [summary, compare, clear]);
1620
1852
  }
1621
1853
 
1854
+ // src/review/TrackedChangesList.tsx
1855
+ import { trackedChangeLabel } from "@pixerate/editor";
1856
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1857
+ var buttonClass3 = "rounded px-1.5 py-0.5 text-xs text-foreground hover:bg-muted disabled:opacity-40";
1858
+ var TrackedChangesList = ({
1859
+ editor,
1860
+ className = "flex flex-col gap-1 text-sm",
1861
+ itemClassName = "flex items-center gap-2 rounded px-2 py-1 hover:bg-muted/50",
1862
+ activeItemClassName = "bg-primary/10",
1863
+ emptyState = "No tracked changes."
1864
+ }) => {
1865
+ const review = useTrackChanges(editor);
1866
+ if (review.changes.length === 0) {
1867
+ return /* @__PURE__ */ jsx12("div", { className, "data-tracked-changes-list": "", children: /* @__PURE__ */ jsx12("div", { className: "pixerate-changes-empty text-xs text-muted-foreground", children: emptyState }) });
1868
+ }
1869
+ return /* @__PURE__ */ jsx12("ul", { className, role: "list", "aria-label": "Tracked changes", "data-tracked-changes-list": "", children: review.changes.map((change) => {
1870
+ const active = change.id === review.currentChangeId;
1871
+ return /* @__PURE__ */ jsxs9(
1872
+ "li",
1873
+ {
1874
+ className: active ? `${itemClassName} ${activeItemClassName}` : itemClassName,
1875
+ "data-change-type": change.type,
1876
+ "data-change-id": change.id,
1877
+ children: [
1878
+ /* @__PURE__ */ jsxs9(
1879
+ "button",
1880
+ {
1881
+ type: "button",
1882
+ className: "min-w-0 flex-1 truncate text-left text-xs",
1883
+ title: `${change.author.name}: ${change.description}`,
1884
+ onClick: () => review.select(change.id),
1885
+ children: [
1886
+ /* @__PURE__ */ jsx12("span", { className: "font-medium", children: trackedChangeLabel(change) }),
1887
+ change.author.name ? ` \xB7 ${change.author.name}` : "",
1888
+ /* @__PURE__ */ jsx12("span", { className: "block truncate text-muted-foreground", children: change.description })
1889
+ ]
1890
+ }
1891
+ ),
1892
+ /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass3, onClick: () => review.accept(change.id), children: "Accept" }),
1893
+ /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass3, onClick: () => review.reject(change.id), children: "Reject" })
1894
+ ]
1895
+ },
1896
+ `${change.type}:${change.id}`
1897
+ );
1898
+ }) });
1899
+ };
1900
+
1622
1901
  // src/find-replace/useFindReplace.ts
1623
- import { useCallback as useCallback8, useMemo as useMemo11, useSyncExternalStore as useSyncExternalStore6 } from "react";
1902
+ import { useCallback as useCallback8, useMemo as useMemo13, useSyncExternalStore as useSyncExternalStore8 } from "react";
1624
1903
  import {
1625
1904
  EMPTY_FIND_REPLACE_SNAPSHOT,
1626
1905
  getFindReplaceStore
@@ -1634,8 +1913,8 @@ function useFindReplace(editor) {
1634
1913
  [store]
1635
1914
  );
1636
1915
  const getSnapshot = () => store?.getSnapshot() ?? EMPTY_FIND_REPLACE_SNAPSHOT;
1637
- const snapshot = useSyncExternalStore6(subscribe, getSnapshot, getSnapshot);
1638
- return useMemo11(() => {
1916
+ const snapshot = useSyncExternalStore8(subscribe, getSnapshot, getSnapshot);
1917
+ return useMemo13(() => {
1639
1918
  const run = (fn) => {
1640
1919
  if (editor && !editor.isDestroyed && store) fn(editor);
1641
1920
  };
@@ -1659,12 +1938,12 @@ function useFindReplace(editor) {
1659
1938
  }
1660
1939
 
1661
1940
  // src/find-replace/FindReplaceBar.tsx
1662
- import { useEffect as useEffect8, useRef as useRef9, useState as useState10 } from "react";
1941
+ import { useEffect as useEffect9, useRef as useRef10, useState as useState11 } from "react";
1663
1942
  import { FIND_OPTION_TOGGLES, formatFindStatus } from "@pixerate/editor";
1664
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1943
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1665
1944
  var inputClass = "min-w-0 flex-1 rounded border border-input bg-background px-2 py-1 text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-ring";
1666
- var buttonClass3 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
1667
- var toggleClass = (on) => `${buttonClass3} font-mono ${on ? "bg-primary/10 text-primary" : "text-muted-foreground"}`;
1945
+ var buttonClass4 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
1946
+ var toggleClass = (on) => `${buttonClass4} font-mono ${on ? "bg-primary/10 text-primary" : "text-muted-foreground"}`;
1668
1947
  var isMod = (e) => e.metaKey || e.ctrlKey;
1669
1948
  var FindReplaceBar = ({
1670
1949
  editor,
@@ -1674,10 +1953,10 @@ var FindReplaceBar = ({
1674
1953
  onClose
1675
1954
  }) => {
1676
1955
  const find = useFindReplace(editor);
1677
- const findInput = useRef9(null);
1678
- const [moreOpen, setMoreOpen] = useState10(false);
1956
+ const findInput = useRef10(null);
1957
+ const [moreOpen, setMoreOpen] = useState11(false);
1679
1958
  const visible = find.available && (alwaysOpen || find.open);
1680
- useEffect8(() => {
1959
+ useEffect9(() => {
1681
1960
  if (!visible || find.focusRequest === 0) return;
1682
1961
  findInput.current?.focus();
1683
1962
  findInput.current?.select();
@@ -1715,20 +1994,20 @@ var FindReplaceBar = ({
1715
1994
  if (isMod(e)) find.replaceAll();
1716
1995
  else find.replace();
1717
1996
  };
1718
- return /* @__PURE__ */ jsxs8("div", { className, role: "search", "aria-label": "Find and replace", onKeyDown: onBarKeyDown, children: [
1719
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1", children: [
1720
- /* @__PURE__ */ jsx10(
1997
+ return /* @__PURE__ */ jsxs10("div", { className, role: "search", "aria-label": "Find and replace", onKeyDown: onBarKeyDown, children: [
1998
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1", children: [
1999
+ /* @__PURE__ */ jsx13(
1721
2000
  "button",
1722
2001
  {
1723
2002
  type: "button",
1724
- className: buttonClass3,
2003
+ className: buttonClass4,
1725
2004
  "aria-label": "Toggle replace",
1726
2005
  "aria-expanded": showReplace,
1727
2006
  onClick: () => find.show(showReplace ? "find" : "replace"),
1728
2007
  children: showReplace ? "\u25BE" : "\u25B8"
1729
2008
  }
1730
2009
  ),
1731
- /* @__PURE__ */ jsx10(
2010
+ /* @__PURE__ */ jsx13(
1732
2011
  "input",
1733
2012
  {
1734
2013
  ref: findInput,
@@ -1742,7 +2021,7 @@ var FindReplaceBar = ({
1742
2021
  onKeyDown: onFindKeyDown
1743
2022
  }
1744
2023
  ),
1745
- FIND_OPTION_TOGGLES.filter((t) => !t.advanced).map((t) => /* @__PURE__ */ jsx10(
2024
+ FIND_OPTION_TOGGLES.filter((t) => !t.advanced).map((t) => /* @__PURE__ */ jsx13(
1746
2025
  "button",
1747
2026
  {
1748
2027
  type: "button",
@@ -1755,7 +2034,7 @@ var FindReplaceBar = ({
1755
2034
  },
1756
2035
  t.key
1757
2036
  )),
1758
- /* @__PURE__ */ jsx10(
2037
+ /* @__PURE__ */ jsx13(
1759
2038
  "span",
1760
2039
  {
1761
2040
  className: `min-w-[5rem] px-1 text-center text-xs ${find.error ? "text-destructive" : "text-muted-foreground"}`,
@@ -1765,23 +2044,23 @@ var FindReplaceBar = ({
1765
2044
  children: status
1766
2045
  }
1767
2046
  ),
1768
- /* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass3, "aria-label": "Previous match", disabled: !hasMatches, onClick: find.previous, children: "\u2191" }),
1769
- /* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass3, "aria-label": "Next match", disabled: !hasMatches, onClick: find.next, children: "\u2193" }),
1770
- showMoreOptions && /* @__PURE__ */ jsx10(
2047
+ /* @__PURE__ */ jsx13("button", { type: "button", className: buttonClass4, "aria-label": "Previous match", disabled: !hasMatches, onClick: find.previous, children: "\u2191" }),
2048
+ /* @__PURE__ */ jsx13("button", { type: "button", className: buttonClass4, "aria-label": "Next match", disabled: !hasMatches, onClick: find.next, children: "\u2193" }),
2049
+ showMoreOptions && /* @__PURE__ */ jsx13(
1771
2050
  "button",
1772
2051
  {
1773
2052
  type: "button",
1774
- className: buttonClass3,
2053
+ className: buttonClass4,
1775
2054
  "aria-label": "More options",
1776
2055
  "aria-expanded": moreOpen,
1777
2056
  onClick: () => setMoreOpen((v) => !v),
1778
2057
  children: "\u2026"
1779
2058
  }
1780
2059
  ),
1781
- /* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass3, "aria-label": "Close find", onClick: close, children: "\xD7" })
2060
+ /* @__PURE__ */ jsx13("button", { type: "button", className: buttonClass4, "aria-label": "Close find", onClick: close, children: "\xD7" })
1782
2061
  ] }),
1783
- showReplace && /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1 pl-7", children: [
1784
- /* @__PURE__ */ jsx10(
2062
+ showReplace && /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1 pl-7", children: [
2063
+ /* @__PURE__ */ jsx13(
1785
2064
  "input",
1786
2065
  {
1787
2066
  type: "text",
@@ -1793,21 +2072,21 @@ var FindReplaceBar = ({
1793
2072
  onKeyDown: onReplaceKeyDown
1794
2073
  }
1795
2074
  ),
1796
- /* @__PURE__ */ jsx10(
2075
+ /* @__PURE__ */ jsx13(
1797
2076
  "button",
1798
2077
  {
1799
2078
  type: "button",
1800
- className: buttonClass3,
2079
+ className: buttonClass4,
1801
2080
  disabled: !hasMatches || find.currentIndex < 0,
1802
2081
  onClick: find.replace,
1803
2082
  children: "Replace"
1804
2083
  }
1805
2084
  ),
1806
- /* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass3, disabled: !hasMatches, onClick: find.replaceAll, children: "Replace all" }),
1807
- find.lastReplaceCount !== null && /* @__PURE__ */ jsx10("span", { className: "px-1 text-xs text-muted-foreground", "aria-live": "polite", children: find.lastReplaceCount === 1 ? "1 replaced" : `${find.lastReplaceCount} replaced` })
2085
+ /* @__PURE__ */ jsx13("button", { type: "button", className: buttonClass4, disabled: !hasMatches, onClick: find.replaceAll, children: "Replace all" }),
2086
+ find.lastReplaceCount !== null && /* @__PURE__ */ jsx13("span", { className: "px-1 text-xs text-muted-foreground", "aria-live": "polite", children: find.lastReplaceCount === 1 ? "1 replaced" : `${find.lastReplaceCount} replaced` })
1808
2087
  ] }),
1809
- showMoreOptions && moreOpen && /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 pl-7 text-xs text-muted-foreground", children: FIND_OPTION_TOGGLES.filter((t) => t.advanced).map((t) => /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-1", children: [
1810
- /* @__PURE__ */ jsx10(
2088
+ showMoreOptions && moreOpen && /* @__PURE__ */ jsx13("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 pl-7 text-xs text-muted-foreground", children: FIND_OPTION_TOGGLES.filter((t) => t.advanced).map((t) => /* @__PURE__ */ jsxs10("label", { className: "flex items-center gap-1", children: [
2089
+ /* @__PURE__ */ jsx13(
1811
2090
  "input",
1812
2091
  {
1813
2092
  type: "checkbox",
@@ -1819,8 +2098,152 @@ var FindReplaceBar = ({
1819
2098
  ] }, t.key)) })
1820
2099
  ] });
1821
2100
  };
2101
+
2102
+ // src/tables/useTableMenu.ts
2103
+ import { useMemo as useMemo14, useSyncExternalStore as useSyncExternalStore9 } from "react";
2104
+ import {
2105
+ createTableStore,
2106
+ getTableActionState,
2107
+ runTableAction
2108
+ } from "@pixerate/editor";
2109
+ function useTableMenu(editor) {
2110
+ const store = useMemo14(() => createTableStore(editor), [editor]);
2111
+ const snapshot = useSyncExternalStore9(store.subscribe, store.getSnapshot, store.getSnapshot);
2112
+ return useMemo14(
2113
+ () => ({
2114
+ ...snapshot,
2115
+ run: (action, params) => runTableAction(editor, action, params, { focus: true }),
2116
+ check: (action, params) => getTableActionState(editor, action, params)
2117
+ }),
2118
+ [snapshot, editor]
2119
+ );
2120
+ }
2121
+
2122
+ // src/tables/TableToolbar.tsx
2123
+ import React9 from "react";
2124
+ import { DEFAULT_TABLE_TOOLBAR } from "@pixerate/editor";
2125
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
2126
+ var keepSelection2 = (event) => event.preventDefault();
2127
+ var TableToolbar = ({
2128
+ editor,
2129
+ items = DEFAULT_TABLE_TOOLBAR,
2130
+ showWhenOutside = false,
2131
+ className = "flex flex-wrap items-center gap-1 text-xs",
2132
+ buttonClassName = "rounded px-2 py-1 text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent",
2133
+ activeButtonClassName = "bg-primary/10 text-primary",
2134
+ separatorClassName = "mx-1 h-4 w-px bg-border",
2135
+ onAction
2136
+ }) => {
2137
+ const table = useTableMenu(editor);
2138
+ if (!editor || !table.inTable && !showWhenOutside) return null;
2139
+ return /* @__PURE__ */ jsx14("div", { className, role: "toolbar", "aria-label": "Table", "data-table-toolbar": "", children: items.map((item, index) => {
2140
+ const status = table.check(item.action, item.params);
2141
+ const enabled = status === true;
2142
+ const active = item.active?.(table);
2143
+ const separator = index > 0 && items[index - 1].group !== item.group;
2144
+ return /* @__PURE__ */ jsxs11(React9.Fragment, { children: [
2145
+ separator && /* @__PURE__ */ jsx14("span", { className: separatorClassName, "aria-hidden": "true" }),
2146
+ /* @__PURE__ */ jsx14(
2147
+ "button",
2148
+ {
2149
+ type: "button",
2150
+ "data-action": item.id,
2151
+ "aria-label": item.title,
2152
+ title: enabled ? item.title : `${item.title}: ${status}`,
2153
+ ...item.active ? { "aria-pressed": !!active } : {},
2154
+ disabled: !enabled,
2155
+ className: active ? `${buttonClassName} ${activeButtonClassName}` : buttonClassName,
2156
+ onMouseDown: keepSelection2,
2157
+ onClick: () => {
2158
+ if (table.run(item.action, item.params)) onAction?.(item);
2159
+ },
2160
+ children: item.label
2161
+ }
2162
+ )
2163
+ ] }, item.id);
2164
+ }) });
2165
+ };
2166
+
2167
+ // src/tables/InsertTableGrid.tsx
2168
+ import { useState as useState12 } from "react";
2169
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
2170
+ var keepSelection3 = (event) => event.preventDefault();
2171
+ var InsertTableGrid = ({
2172
+ editor,
2173
+ rows = 8,
2174
+ cols = 8,
2175
+ withHeaderRow = true,
2176
+ className = "inline-flex flex-col gap-1 rounded-md border border-border bg-background p-2 text-xs shadow-md",
2177
+ cellClassName = "h-4 w-4 rounded-sm border border-border bg-background disabled:opacity-40",
2178
+ activeCellClassName = "border-primary bg-primary/20",
2179
+ onInsert
2180
+ }) => {
2181
+ const table = useTableMenu(editor);
2182
+ const [hover, setHover] = useState12(null);
2183
+ const status = table.actions.insertTable;
2184
+ const disabled = !editor || !status.enabled;
2185
+ const insert = (size) => {
2186
+ if (table.run("insertTable", { ...size, withHeaderRow })) onInsert?.(size);
2187
+ setHover(null);
2188
+ };
2189
+ return /* @__PURE__ */ jsxs12(
2190
+ "div",
2191
+ {
2192
+ className,
2193
+ role: "group",
2194
+ "aria-label": "Insert table",
2195
+ title: disabled ? status.reason : void 0,
2196
+ "data-insert-table-grid": "",
2197
+ onMouseLeave: () => setHover(null),
2198
+ children: [
2199
+ /* @__PURE__ */ jsx15(
2200
+ "div",
2201
+ {
2202
+ role: "grid",
2203
+ "aria-label": "Table size",
2204
+ style: { display: "grid", gridTemplateColumns: `repeat(${cols}, max-content)`, gap: 2 },
2205
+ children: Array.from({ length: rows * cols }, (_, i) => {
2206
+ const size = { rows: Math.floor(i / cols) + 1, cols: i % cols + 1 };
2207
+ const active = !!hover && size.rows <= hover.rows && size.cols <= hover.cols;
2208
+ return /* @__PURE__ */ jsx15(
2209
+ "button",
2210
+ {
2211
+ type: "button",
2212
+ "data-rows": size.rows,
2213
+ "data-cols": size.cols,
2214
+ "data-active": active,
2215
+ "aria-label": `${size.rows} \xD7 ${size.cols} table`,
2216
+ disabled,
2217
+ className: active ? `${cellClassName} ${activeCellClassName}` : cellClassName,
2218
+ onMouseDown: keepSelection3,
2219
+ onMouseEnter: () => setHover(size),
2220
+ onFocus: () => setHover(size),
2221
+ onClick: () => insert(size)
2222
+ },
2223
+ i
2224
+ );
2225
+ })
2226
+ }
2227
+ ),
2228
+ /* @__PURE__ */ jsx15("div", { "aria-live": "polite", "data-testid": "insert-table-size", children: disabled ? status.reason : hover ? `${hover.rows} \xD7 ${hover.cols}` : "Insert table" })
2229
+ ]
2230
+ }
2231
+ );
2232
+ };
2233
+
2234
+ // src/tables/TableBubbleMenu.tsx
2235
+ import { shouldShowTableBubbleMenu } from "@pixerate/editor";
2236
+ import { jsx as jsx16 } from "react/jsx-runtime";
2237
+ var TableBubbleMenu = ({
2238
+ editor,
2239
+ children,
2240
+ toolbarProps,
2241
+ pluginKey = "tableBubbleMenu",
2242
+ ...rest
2243
+ }) => /* @__PURE__ */ jsx16(BubbleMenu, { editor, pluginKey, shouldShow: shouldShowTableBubbleMenu, ...rest, children: children ?? /* @__PURE__ */ jsx16(TableToolbar, { editor, ...toolbarProps }) });
1822
2244
  export {
1823
2245
  AdjustmentLayersPanel,
2246
+ AssetMentionChips,
1824
2247
  BubbleMenu,
1825
2248
  CommandPalette,
1826
2249
  CommentButton,
@@ -1830,21 +2253,35 @@ export {
1830
2253
  DirtyTracker2 as DirtyTracker,
1831
2254
  EditorContent,
1832
2255
  EditorSlotsProvider,
2256
+ ExportDocxButton,
1833
2257
  FindReplaceBar,
1834
2258
  FormulaHints,
2259
+ GenerationHistory,
1835
2260
  GenerationSettings,
1836
2261
  Histogram,
1837
2262
  HistoryManager2 as HistoryManager,
1838
2263
  ImageEditor,
2264
+ InsertTableGrid,
1839
2265
  JobProgress,
1840
2266
  MergePreview,
2267
+ NameManager,
1841
2268
  OutlinePanel,
1842
2269
  RecoveryPrompt,
2270
+ RefineEdgePanel,
1843
2271
  ReviewToolbar,
2272
+ SelectionOptionsBar,
2273
+ SheetTabs,
2274
+ SpreadsheetConditionalFormatPanel,
1844
2275
  SpreadsheetContextMenu,
2276
+ SpreadsheetDataValidationPanel,
1845
2277
  SpreadsheetEditor,
1846
2278
  SpreadsheetGroupRow,
2279
+ SpreadsheetValidationDialog,
2280
+ StylesGallery,
2281
+ TableBubbleMenu,
2282
+ TableToolbar,
1847
2283
  TemplateRenderer,
2284
+ TrackedChangesList,
1848
2285
  VariableForm,
1849
2286
  bindDirtyToHistory2 as bindDirtyToHistory,
1850
2287
  createDirtyTracker,
@@ -1857,12 +2294,14 @@ export {
1857
2294
  useComments,
1858
2295
  useDismissGuard,
1859
2296
  useDocumentCompare,
2297
+ useDocxExport,
1860
2298
  useEditor2 as useEditor,
1861
2299
  useEditorSlots,
1862
2300
  useFileImport,
1863
2301
  useFillHandle,
1864
2302
  useFindReplace,
1865
2303
  useGenerationPlan,
2304
+ useGenerationRecords,
1866
2305
  useHistory,
1867
2306
  useHistoryDirty,
1868
2307
  useHistoryShortcuts,
@@ -1872,10 +2311,13 @@ export {
1872
2311
  useMergePreview,
1873
2312
  useNavigationGuard,
1874
2313
  useOutline,
2314
+ useParagraphStyles,
1875
2315
  usePromptEditor,
1876
2316
  useRegisterCommands,
1877
2317
  useSpreadsheetClipboard,
1878
2318
  useSpreadsheetEditor,
2319
+ useSpreadsheetViewport,
2320
+ useTableMenu,
1879
2321
  useTrackChanges,
1880
2322
  withoutDirty
1881
2323
  };