@pixerate/editor-react 0.15.0 → 0.16.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 -0
  2. package/dist/canvas/index.cjs +219 -118
  3. package/dist/canvas/index.cjs.map +1 -1
  4. package/dist/canvas/index.d.cts +59 -3
  5. package/dist/canvas/index.d.ts +59 -3
  6. package/dist/canvas/index.js +103 -0
  7. package/dist/canvas/index.js.map +1 -1
  8. package/dist/chunk-7SPRQZ6H.js +216 -0
  9. package/dist/chunk-7SPRQZ6H.js.map +1 -0
  10. package/dist/{chunk-QVQYYHJ5.js → chunk-FYIPXHAE.js} +21 -13
  11. package/dist/chunk-FYIPXHAE.js.map +1 -0
  12. package/dist/{chunk-LPFZMZ3L.js → chunk-XTFR3IMK.js} +676 -322
  13. package/dist/chunk-XTFR3IMK.js.map +1 -0
  14. package/dist/chunk-XWFU2CSG.js +167 -0
  15. package/dist/chunk-XWFU2CSG.js.map +1 -0
  16. package/dist/generation/index.cjs +235 -0
  17. package/dist/generation/index.cjs.map +1 -0
  18. package/dist/generation/index.d.cts +60 -0
  19. package/dist/generation/index.d.ts +60 -0
  20. package/dist/generation/index.js +13 -0
  21. package/dist/generation/index.js.map +1 -0
  22. package/dist/image-editor/index.cjs +684 -336
  23. package/dist/image-editor/index.cjs.map +1 -1
  24. package/dist/image-editor/index.d.cts +60 -2
  25. package/dist/image-editor/index.d.ts +60 -2
  26. package/dist/image-editor/index.js +13 -3
  27. package/dist/index.cjs +1881 -648
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +143 -4
  30. package/dist/index.d.ts +143 -4
  31. package/dist/index.js +626 -73
  32. package/dist/index.js.map +1 -1
  33. package/dist/persistence/index.cjs +191 -0
  34. package/dist/persistence/index.cjs.map +1 -0
  35. package/dist/persistence/index.d.cts +77 -0
  36. package/dist/persistence/index.d.ts +77 -0
  37. package/dist/persistence/index.js +9 -0
  38. package/dist/persistence/index.js.map +1 -0
  39. package/dist/spreadsheet/index.cjs +17 -10
  40. package/dist/spreadsheet/index.cjs.map +1 -1
  41. package/dist/spreadsheet/index.d.cts +2 -0
  42. package/dist/spreadsheet/index.d.ts +2 -0
  43. package/dist/spreadsheet/index.js +1 -1
  44. package/package.json +18 -2
  45. package/dist/chunk-LPFZMZ3L.js.map +0 -1
  46. package/dist/chunk-QVQYYHJ5.js.map +0 -1
package/dist/index.js CHANGED
@@ -6,17 +6,32 @@ import {
6
6
  useFillHandle,
7
7
  useSpreadsheetClipboard,
8
8
  useSpreadsheetEditor
9
- } from "./chunk-QVQYYHJ5.js";
9
+ } from "./chunk-FYIPXHAE.js";
10
10
  import {
11
+ AdjustmentLayersPanel,
12
+ ControlSlider,
13
+ CurvesEditor,
14
+ Histogram,
11
15
  ImageEditor,
12
- useImageEditor
13
- } from "./chunk-LPFZMZ3L.js";
16
+ useImageEditor,
17
+ useImageHistogram
18
+ } from "./chunk-XTFR3IMK.js";
14
19
  import {
15
20
  CommandPalette,
16
21
  useCommandPalette,
17
22
  useCommandShortcuts,
18
23
  useRegisterCommands
19
24
  } from "./chunk-4ZC2WCUO.js";
25
+ import {
26
+ RecoveryPrompt,
27
+ useAutosave
28
+ } from "./chunk-XWFU2CSG.js";
29
+ import {
30
+ GenerationSettings,
31
+ JobProgress,
32
+ useGenerationPlan,
33
+ useJobProgress
34
+ } from "./chunk-7SPRQZ6H.js";
20
35
 
21
36
  // src/hooks/usePromptEditor.ts
22
37
  import { useMemo, useEffect, useRef } from "react";
@@ -660,11 +675,12 @@ var BubbleMenu = ({
660
675
  // src/components/TemplateRenderer.tsx
661
676
  import { useMemo as useMemo4 } from "react";
662
677
  import {
678
+ describeConditionalTag,
663
679
  evaluatePromptTokens,
680
+ formatConditionalTag,
664
681
  resolveTemplates
665
682
  } from "@pixerate/editor";
666
683
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
667
- var CONDITIONAL_PREFIX = { if: "?", unless: "!", end: "/" };
668
684
  var TemplateRenderer = ({
669
685
  content,
670
686
  templates = [],
@@ -747,8 +763,8 @@ var TemplateRenderer = ({
747
763
  {
748
764
  "data-conditional": token.conditional,
749
765
  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",
750
- 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`,
751
- children: `{${CONDITIONAL_PREFIX[token.conditional]}${token.value}}`
766
+ title: describeConditionalTag(token),
767
+ children: formatConditionalTag(token)
752
768
  },
753
769
  index
754
770
  );
@@ -856,17 +872,312 @@ var VariableForm = ({
856
872
  }) });
857
873
  };
858
874
 
875
+ // src/components/MergePreview.tsx
876
+ import { useId as useId2, useState as useState7 } from "react";
877
+
878
+ // src/hooks/useMergePreview.ts
879
+ import { useCallback as useCallback5, useMemo as useMemo6, useState as useState6 } from "react";
880
+ import {
881
+ extractVariables,
882
+ matchFields,
883
+ mergeRecords,
884
+ parseDataSource,
885
+ recordToValues,
886
+ serializeMergeOutputs
887
+ } from "@pixerate/editor";
888
+ function useMergePreview(options) {
889
+ const { template, templates, bindings, parseOptions } = options;
890
+ const [sourceText, setSourceTextState] = useState6(options.initialSource ?? "");
891
+ const [overrides, setOverrides] = useState6({});
892
+ const [index, setIndexState] = useState6(0);
893
+ const [result, setResult] = useState6(null);
894
+ const source = useMemo6(
895
+ () => parseDataSource(sourceText, parseOptions),
896
+ [sourceText, parseOptions]
897
+ );
898
+ const variables = useMemo6(
899
+ () => extractVariables(template, { templates, bindings }).map((v) => v.name),
900
+ [template, templates, bindings]
901
+ );
902
+ const mapping = useMemo6(() => {
903
+ const merged = {
904
+ ...matchFields(variables, source.fields).mapping
905
+ };
906
+ for (const [variable, field] of Object.entries(overrides)) {
907
+ if (variables.includes(variable) && (field === null || source.fields.includes(field))) {
908
+ merged[variable] = field;
909
+ }
910
+ }
911
+ return merged;
912
+ }, [variables, source.fields, overrides]);
913
+ const count = source.records.length;
914
+ const current = count === 0 ? 0 : Math.min(index, count - 1);
915
+ const record = source.records[current];
916
+ const values = useMemo6(
917
+ () => record ? recordToValues(record, mapping) : {},
918
+ [record, mapping]
919
+ );
920
+ const setSourceText = useCallback5((text) => {
921
+ setSourceTextState(text);
922
+ setIndexState(0);
923
+ setResult(null);
924
+ }, []);
925
+ const loadFile = useCallback5(
926
+ async (file) => setSourceText(await file.text()),
927
+ [setSourceText]
928
+ );
929
+ const setIndex = useCallback5(
930
+ (next) => setIndexState(Math.max(0, Math.min(next, count - 1))),
931
+ [count]
932
+ );
933
+ const setFieldMapping = useCallback5((variable, field) => {
934
+ setOverrides((previous) => ({ ...previous, [variable]: field }));
935
+ setResult(null);
936
+ }, []);
937
+ const resetMapping = useCallback5(() => setOverrides({}), []);
938
+ const merge = useCallback5(() => {
939
+ const merged = mergeRecords(template, source, {
940
+ templates,
941
+ bindings,
942
+ mapping,
943
+ skipIf: options.skipIf,
944
+ separator: options.separator,
945
+ maxOutputs: options.maxOutputs
946
+ });
947
+ setResult(merged);
948
+ return merged;
949
+ }, [template, source, templates, bindings, mapping, options.skipIf, options.separator, options.maxOutputs]);
950
+ const serialize = useCallback5(
951
+ (format) => result ? serializeMergeOutputs(result.outputs, format, options.separator) : "",
952
+ [result, options.separator]
953
+ );
954
+ return {
955
+ sourceText,
956
+ setSourceText,
957
+ loadFile,
958
+ source,
959
+ variables,
960
+ mapping,
961
+ setFieldMapping,
962
+ resetMapping,
963
+ index: current,
964
+ count,
965
+ setIndex,
966
+ next: () => setIndex(current + 1),
967
+ prev: () => setIndex(current - 1),
968
+ record,
969
+ values,
970
+ merge,
971
+ result,
972
+ serialize
973
+ };
974
+ }
975
+
976
+ // src/components/MergePreview.tsx
977
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
978
+ function downloadText(text, fileName, type) {
979
+ if (typeof document === "undefined" || typeof URL.createObjectURL !== "function") {
980
+ return;
981
+ }
982
+ const url = URL.createObjectURL(new Blob([text], { type }));
983
+ const link = document.createElement("a");
984
+ link.href = url;
985
+ link.download = fileName;
986
+ link.click();
987
+ setTimeout(() => URL.revokeObjectURL(url), 0);
988
+ }
989
+ var MergePreview = ({
990
+ content,
991
+ templates,
992
+ bindings,
993
+ templateColorMap,
994
+ defaultSource,
995
+ parseOptions,
996
+ skipIf,
997
+ separator,
998
+ maxOutputs,
999
+ onMerge,
1000
+ downloadName = "merged",
1001
+ className = "flex flex-col gap-3 text-sm"
1002
+ }) => {
1003
+ const id = useId2();
1004
+ const merge = useMergePreview({
1005
+ template: content,
1006
+ templates,
1007
+ bindings,
1008
+ initialSource: defaultSource,
1009
+ parseOptions,
1010
+ skipIf,
1011
+ separator,
1012
+ maxOutputs
1013
+ });
1014
+ const [copied, setCopied] = useState7(false);
1015
+ const { source, result } = merge;
1016
+ const copy = async () => {
1017
+ try {
1018
+ await navigator.clipboard.writeText(merge.serialize("text"));
1019
+ setCopied(true);
1020
+ } catch {
1021
+ setCopied(false);
1022
+ }
1023
+ };
1024
+ return /* @__PURE__ */ jsxs3("div", { className, "data-merge-preview": "", children: [
1025
+ /* @__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(
1028
+ "textarea",
1029
+ {
1030
+ id: `${id}-source`,
1031
+ "data-merge-source": "",
1032
+ value: merge.sourceText,
1033
+ rows: 5,
1034
+ spellCheck: false,
1035
+ onChange: (event) => merge.setSourceText(event.target.value),
1036
+ className: "px-2 py-1 font-mono text-xs rounded-md border border-violet-400/40 bg-transparent"
1037
+ }
1038
+ )
1039
+ ] }),
1040
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2", children: [
1041
+ /* @__PURE__ */ jsx4(
1042
+ "input",
1043
+ {
1044
+ type: "file",
1045
+ accept: ".csv,.tsv,.txt,.json,text/csv,application/json",
1046
+ "aria-label": "Load data source file",
1047
+ "data-merge-file": "",
1048
+ onChange: (event) => {
1049
+ const file = event.target.files?.[0];
1050
+ if (file) void merge.loadFile(file);
1051
+ }
1052
+ }
1053
+ ),
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"}` })
1055
+ ] }),
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)) }),
1057
+ merge.variables.length > 0 && /* @__PURE__ */ jsxs3("fieldset", { className: "flex flex-col gap-1", children: [
1058
+ /* @__PURE__ */ jsx4("legend", { className: "font-medium", children: "Field mapping" }),
1059
+ merge.variables.map((variable) => /* @__PURE__ */ jsxs3(
1060
+ "label",
1061
+ {
1062
+ className: "flex items-center gap-2",
1063
+ "data-merge-mapping": variable,
1064
+ children: [
1065
+ /* @__PURE__ */ jsx4("span", { className: "min-w-24 font-mono", children: `{${variable}}` }),
1066
+ /* @__PURE__ */ jsxs3(
1067
+ "select",
1068
+ {
1069
+ value: merge.mapping[variable] ?? "",
1070
+ "aria-label": `Field for ${variable}`,
1071
+ onChange: (event) => merge.setFieldMapping(variable, event.target.value || null),
1072
+ className: "px-1 py-0.5 rounded-md border border-violet-400/40 bg-transparent",
1073
+ children: [
1074
+ /* @__PURE__ */ jsx4("option", { value: "", children: "(none)" }),
1075
+ source.fields.map((field) => /* @__PURE__ */ jsx4("option", { value: field, children: field }, field))
1076
+ ]
1077
+ }
1078
+ )
1079
+ ]
1080
+ },
1081
+ variable
1082
+ ))
1083
+ ] }),
1084
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
1085
+ /* @__PURE__ */ jsx4(
1086
+ "button",
1087
+ {
1088
+ type: "button",
1089
+ "data-merge-prev": "",
1090
+ onClick: merge.prev,
1091
+ disabled: merge.index <= 0,
1092
+ children: "Prev"
1093
+ }
1094
+ ),
1095
+ /* @__PURE__ */ jsx4("span", { "data-merge-position": "", children: merge.count === 0 ? "No records" : `Record ${merge.index + 1} of ${merge.count}` }),
1096
+ /* @__PURE__ */ jsx4(
1097
+ "button",
1098
+ {
1099
+ type: "button",
1100
+ "data-merge-next": "",
1101
+ onClick: merge.next,
1102
+ disabled: merge.index >= merge.count - 1,
1103
+ children: "Next"
1104
+ }
1105
+ )
1106
+ ] }),
1107
+ /* @__PURE__ */ jsx4("div", { "data-merge-output": "", children: /* @__PURE__ */ jsx4(
1108
+ TemplateRenderer,
1109
+ {
1110
+ content,
1111
+ templates,
1112
+ bindings,
1113
+ templateColorMap,
1114
+ resolve: !!templates && templates.length > 0,
1115
+ values: merge.values
1116
+ }
1117
+ ) }),
1118
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2", children: [
1119
+ /* @__PURE__ */ jsx4(
1120
+ "button",
1121
+ {
1122
+ type: "button",
1123
+ "data-merge-all": "",
1124
+ disabled: merge.count === 0,
1125
+ onClick: () => {
1126
+ const merged = merge.merge();
1127
+ setCopied(false);
1128
+ onMerge?.(merged);
1129
+ },
1130
+ children: "Merge all"
1131
+ }
1132
+ ),
1133
+ 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(
1137
+ "button",
1138
+ {
1139
+ type: "button",
1140
+ "data-merge-download": "text",
1141
+ onClick: () => downloadText(merge.serialize("text"), `${downloadName}.txt`, "text/plain"),
1142
+ children: "Download .txt"
1143
+ }
1144
+ ),
1145
+ /* @__PURE__ */ jsx4(
1146
+ "button",
1147
+ {
1148
+ type: "button",
1149
+ "data-merge-download": "json",
1150
+ onClick: () => downloadText(merge.serialize("json"), `${downloadName}.json`, "application/json"),
1151
+ children: "Download .json"
1152
+ }
1153
+ )
1154
+ ] })
1155
+ ] }),
1156
+ result && /* @__PURE__ */ jsx4(
1157
+ "textarea",
1158
+ {
1159
+ readOnly: true,
1160
+ "aria-label": "Merged output",
1161
+ "data-merge-text": "",
1162
+ rows: 6,
1163
+ value: merge.serialize("text"),
1164
+ className: "px-2 py-1 font-mono text-xs rounded-md border border-violet-400/40 bg-transparent"
1165
+ }
1166
+ )
1167
+ ] });
1168
+ };
1169
+
859
1170
  // src/hooks/useOutline.ts
860
- import { useMemo as useMemo6, useSyncExternalStore as useSyncExternalStore4 } from "react";
1171
+ import { useMemo as useMemo7, useSyncExternalStore as useSyncExternalStore4 } from "react";
861
1172
  import { createOutlineStore } from "@pixerate/editor";
862
1173
  function useOutline(editor) {
863
- const store = useMemo6(() => createOutlineStore(editor), [editor]);
1174
+ const store = useMemo7(() => createOutlineStore(editor), [editor]);
864
1175
  return useSyncExternalStore4(store.subscribe, store.getSnapshot, store.getSnapshot);
865
1176
  }
866
1177
 
867
1178
  // src/components/OutlinePanel.tsx
868
1179
  import { navigateToHeading } from "@pixerate/editor";
869
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1180
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
870
1181
  var OutlinePanel = ({
871
1182
  editor,
872
1183
  title = "Outline",
@@ -880,11 +1191,11 @@ var OutlinePanel = ({
880
1191
  }) => {
881
1192
  const { headings, activeId } = useOutline(editor);
882
1193
  const visible = headings.filter((h) => h.level <= maxLevel);
883
- return /* @__PURE__ */ jsxs3("nav", { className, "aria-label": "Document outline", "data-outline-panel": "", children: [
884
- title != null && /* @__PURE__ */ jsx4("div", { className: "pixerate-outline-title", children: title }),
885
- 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) => {
1194
+ 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) => {
886
1197
  const active = heading.id === activeId;
887
- return /* @__PURE__ */ jsx4("li", { "data-level": heading.level, children: /* @__PURE__ */ jsx4(
1198
+ return /* @__PURE__ */ jsx5("li", { "data-level": heading.level, children: /* @__PURE__ */ jsx5(
888
1199
  "button",
889
1200
  {
890
1201
  type: "button",
@@ -917,10 +1228,10 @@ import {
917
1228
  } from "@pixerate/editor";
918
1229
 
919
1230
  // src/slots.tsx
920
- import { createContext, useContext, useMemo as useMemo7 } from "react";
921
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1231
+ import { createContext, useContext, useMemo as useMemo8 } from "react";
1232
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
922
1233
  var defaultSlots = {
923
- Button: (props) => /* @__PURE__ */ jsx5(
1234
+ Button: (props) => /* @__PURE__ */ jsx6(
924
1235
  "button",
925
1236
  {
926
1237
  type: "button",
@@ -930,26 +1241,26 @@ var defaultSlots = {
930
1241
  children: props.children
931
1242
  }
932
1243
  ),
933
- Tooltip: (props) => /* @__PURE__ */ jsx5("div", { className: `pixerate-tooltip-container ${props.className || ""}`, title: String(props.content), children: props.children }),
934
- NodeHeader: (props) => /* @__PURE__ */ jsxs4("div", { className: `pixerate-node-header flex items-center justify-between ${props.className || ""}`, children: [
935
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
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: [
936
1247
  props.icon,
937
- /* @__PURE__ */ jsx5("span", { className: "font-semibold", children: props.title })
1248
+ /* @__PURE__ */ jsx6("span", { className: "font-semibold", children: props.title })
938
1249
  ] }),
939
- props.onClose && /* @__PURE__ */ jsx5("button", { type: "button", onClick: props.onClose, className: "opacity-70 hover:opacity-100", children: "\xD7" })
1250
+ props.onClose && /* @__PURE__ */ jsx6("button", { type: "button", onClick: props.onClose, className: "opacity-70 hover:opacity-100", children: "\xD7" })
940
1251
  ] })
941
1252
  };
942
1253
  var EditorSlotsContext = createContext(defaultSlots);
943
1254
  function EditorSlotsProvider({ slots, children }) {
944
- const mergedSlots = useMemo7(() => ({ ...defaultSlots, ...slots }), [slots]);
945
- return /* @__PURE__ */ jsx5(EditorSlotsContext.Provider, { value: mergedSlots, children });
1255
+ const mergedSlots = useMemo8(() => ({ ...defaultSlots, ...slots }), [slots]);
1256
+ return /* @__PURE__ */ jsx6(EditorSlotsContext.Provider, { value: mergedSlots, children });
946
1257
  }
947
1258
  function useEditorSlots() {
948
1259
  return useContext(EditorSlotsContext);
949
1260
  }
950
1261
 
951
1262
  // src/review/useReview.ts
952
- import { useCallback as useCallback5, useMemo as useMemo8, useSyncExternalStore as useSyncExternalStore5 } from "react";
1263
+ import { useCallback as useCallback6, useMemo as useMemo9, useSyncExternalStore as useSyncExternalStore5 } from "react";
953
1264
  import {
954
1265
  getCommentStore,
955
1266
  getTrackChangesStore
@@ -971,23 +1282,23 @@ function isCommentStore(value) {
971
1282
  }
972
1283
  function useComments(source) {
973
1284
  const store = isCommentStore(source) ? source : getCommentStore(source);
974
- const subscribe = useCallback5(
1285
+ const subscribe = useCallback6(
975
1286
  (onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
976
1287
  [store]
977
1288
  );
978
1289
  const getSnapshot = () => store?.getSnapshot() ?? EMPTY_COMMENTS;
979
1290
  const snapshot = useSyncExternalStore5(subscribe, getSnapshot, getSnapshot);
980
- return useMemo8(() => ({ ...snapshot, store }), [snapshot, store]);
1291
+ return useMemo9(() => ({ ...snapshot, store }), [snapshot, store]);
981
1292
  }
982
1293
  function useTrackChanges(editor) {
983
1294
  const store = getTrackChangesStore(editor ?? null);
984
- const subscribe = useCallback5(
1295
+ const subscribe = useCallback6(
985
1296
  (onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
986
1297
  [store]
987
1298
  );
988
1299
  const getSnapshot = () => store?.getSnapshot() ?? EMPTY_TRACK_CHANGES;
989
1300
  const snapshot = useSyncExternalStore5(subscribe, getSnapshot, getSnapshot);
990
- return useMemo8(() => {
1301
+ return useMemo9(() => {
991
1302
  const run = (fn) => () => {
992
1303
  if (editor && !editor.isDestroyed) fn(editor);
993
1304
  };
@@ -1007,12 +1318,12 @@ function useTrackChanges(editor) {
1007
1318
  }
1008
1319
 
1009
1320
  // src/review/CommentsPanel.tsx
1010
- import { useState as useState6 } from "react";
1321
+ import { useState as useState8 } from "react";
1011
1322
  import {
1012
1323
  formatReviewTime,
1013
1324
  getCommentAuthor
1014
1325
  } from "@pixerate/editor";
1015
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1326
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1016
1327
  var buttonClass = "rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50";
1017
1328
  var primaryButtonClass = "rounded px-2 py-1 text-xs bg-primary/10 text-primary hover:bg-primary/20 disabled:opacity-50";
1018
1329
  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";
@@ -1026,18 +1337,18 @@ var CommentsPanel = ({
1026
1337
  emptyMessage = "No comments yet. Select text and add a comment."
1027
1338
  }) => {
1028
1339
  const { threads, activeThreadId, store } = useComments(storeProp ?? editor);
1029
- const [showResolved, setShowResolved] = useState6(showResolvedInitial);
1340
+ const [showResolved, setShowResolved] = useState8(showResolvedInitial);
1030
1341
  const author = authorProp ?? getCommentAuthor(editor) ?? { id: "anonymous", name: "Anonymous" };
1031
1342
  const visible = threads.filter((t) => showResolved || !t.resolved);
1032
1343
  const resolvedCount = threads.filter((t) => t.resolved).length;
1033
- return /* @__PURE__ */ jsxs5("aside", { className, "aria-label": "Comments", children: [
1034
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between gap-2", children: [
1035
- /* @__PURE__ */ jsxs5("span", { className: "text-xs font-medium text-muted-foreground", children: [
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: [
1036
1347
  threads.length - resolvedCount,
1037
1348
  " open",
1038
1349
  resolvedCount > 0 ? ` \xB7 ${resolvedCount} resolved` : ""
1039
1350
  ] }),
1040
- resolvedCount > 0 && /* @__PURE__ */ jsx6(
1351
+ resolvedCount > 0 && /* @__PURE__ */ jsx7(
1041
1352
  "button",
1042
1353
  {
1043
1354
  type: "button",
@@ -1048,7 +1359,7 @@ var CommentsPanel = ({
1048
1359
  }
1049
1360
  )
1050
1361
  ] }),
1051
- visible.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ jsx6("ul", { className: "flex flex-col gap-2", role: "list", children: visible.map((thread) => /* @__PURE__ */ jsx6(
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(
1052
1363
  CommentThreadCard,
1053
1364
  {
1054
1365
  thread,
@@ -1070,7 +1381,7 @@ var CommentThreadCard = ({
1070
1381
  author,
1071
1382
  formatTime
1072
1383
  }) => {
1073
- const [draft, setDraft] = useState6("");
1384
+ const [draft, setDraft] = useState8("");
1074
1385
  const isDraft = thread.body === "";
1075
1386
  const quote = thread.anchor?.quote ?? thread.lastQuote ?? "";
1076
1387
  const live = editor && !editor.isDestroyed ? editor : null;
@@ -1096,7 +1407,7 @@ var CommentThreadCard = ({
1096
1407
  setDraft("");
1097
1408
  };
1098
1409
  const stop = (e) => e.stopPropagation();
1099
- return /* @__PURE__ */ jsxs5(
1410
+ return /* @__PURE__ */ jsxs6(
1100
1411
  "li",
1101
1412
  {
1102
1413
  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" : ""}`,
@@ -1112,24 +1423,24 @@ var CommentThreadCard = ({
1112
1423
  }
1113
1424
  },
1114
1425
  children: [
1115
- /* @__PURE__ */ jsxs5("div", { className: "flex items-baseline justify-between gap-2", children: [
1116
- /* @__PURE__ */ jsx6("span", { className: "font-medium", children: thread.author.name }),
1117
- /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: formatTime(thread.createdAt) })
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) })
1118
1429
  ] }),
1119
- quote && /* @__PURE__ */ jsx6("blockquote", { className: "border-l-2 border-border pl-2 text-xs text-muted-foreground line-clamp-2", children: quote }),
1120
- thread.orphaned && /* @__PURE__ */ jsx6("span", { className: "text-xs text-destructive", children: "The commented text was deleted." }),
1121
- thread.resolved && /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: "Resolved" }),
1122
- !isDraft && /* @__PURE__ */ jsx6("p", { className: "whitespace-pre-wrap break-words", children: thread.body }),
1123
- thread.replies.length > 0 && /* @__PURE__ */ jsx6("ul", { className: "flex flex-col gap-2 border-l border-border pl-3", role: "list", children: thread.replies.map((reply) => /* @__PURE__ */ jsxs5("li", { className: "flex flex-col gap-0.5", "data-reply-id": reply.id, children: [
1124
- /* @__PURE__ */ jsxs5("span", { className: "text-xs", children: [
1125
- /* @__PURE__ */ jsx6("span", { className: "font-medium", children: reply.author.name }),
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 }),
1126
1437
  " ",
1127
- /* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", children: formatTime(reply.createdAt) })
1438
+ /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: formatTime(reply.createdAt) })
1128
1439
  ] }),
1129
- /* @__PURE__ */ jsx6("p", { className: "whitespace-pre-wrap break-words", children: reply.body })
1440
+ /* @__PURE__ */ jsx7("p", { className: "whitespace-pre-wrap break-words", children: reply.body })
1130
1441
  ] }, reply.id)) }),
1131
- (active || isDraft) && !thread.resolved && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", onClick: stop, children: [
1132
- /* @__PURE__ */ jsx6(
1442
+ (active || isDraft) && !thread.resolved && /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1", onClick: stop, children: [
1443
+ /* @__PURE__ */ jsx7(
1133
1444
  "textarea",
1134
1445
  {
1135
1446
  className: textareaClass,
@@ -1147,9 +1458,9 @@ var CommentThreadCard = ({
1147
1458
  }
1148
1459
  }
1149
1460
  ),
1150
- /* @__PURE__ */ jsxs5("div", { className: "flex justify-end gap-1", children: [
1151
- isDraft && /* @__PURE__ */ jsx6("button", { type: "button", className: buttonClass, onClick: remove, children: "Cancel" }),
1152
- /* @__PURE__ */ jsx6(
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(
1153
1464
  "button",
1154
1465
  {
1155
1466
  type: "button",
@@ -1161,9 +1472,9 @@ var CommentThreadCard = ({
1161
1472
  )
1162
1473
  ] })
1163
1474
  ] }),
1164
- !isDraft && /* @__PURE__ */ jsxs5("div", { className: "flex justify-end gap-1", onClick: stop, children: [
1165
- /* @__PURE__ */ jsx6("button", { type: "button", className: buttonClass, onClick: () => setResolved(!thread.resolved), children: thread.resolved ? "Reopen" : "Resolve" }),
1166
- /* @__PURE__ */ jsx6(
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(
1167
1478
  "button",
1168
1479
  {
1169
1480
  type: "button",
@@ -1179,7 +1490,7 @@ var CommentThreadCard = ({
1179
1490
  };
1180
1491
 
1181
1492
  // src/review/ReviewToolbar.tsx
1182
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1493
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1183
1494
  var defaultDescriptionClass = "max-w-[16rem] truncate px-1 text-xs text-muted-foreground";
1184
1495
  var buttonClass2 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
1185
1496
  var ReviewToolbar = ({
@@ -1192,8 +1503,8 @@ var ReviewToolbar = ({
1192
1503
  const count = review.changes.length;
1193
1504
  const current = review.currentChange;
1194
1505
  const index = current ? review.changes.findIndex((c) => c.id === current.id) + 1 : 0;
1195
- return /* @__PURE__ */ jsxs6("div", { className, role: "toolbar", "aria-label": "Review", children: [
1196
- /* @__PURE__ */ jsx7(
1506
+ return /* @__PURE__ */ jsxs7("div", { className, role: "toolbar", "aria-label": "Review", children: [
1507
+ /* @__PURE__ */ jsx8(
1197
1508
  "button",
1198
1509
  {
1199
1510
  type: "button",
@@ -1204,8 +1515,8 @@ var ReviewToolbar = ({
1204
1515
  children: review.enabled ? "Tracking on" : "Track changes"
1205
1516
  }
1206
1517
  ),
1207
- /* @__PURE__ */ jsx7("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1208
- /* @__PURE__ */ jsx7(
1518
+ /* @__PURE__ */ jsx8("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1519
+ /* @__PURE__ */ jsx8(
1209
1520
  "button",
1210
1521
  {
1211
1522
  type: "button",
@@ -1216,8 +1527,8 @@ var ReviewToolbar = ({
1216
1527
  children: "\u2039"
1217
1528
  }
1218
1529
  ),
1219
- /* @__PURE__ */ jsx7("span", { className: "min-w-[4rem] text-center text-xs text-muted-foreground", "aria-live": "polite", children: count === 0 ? "No changes" : current ? `${index} of ${count}` : `${count} changes` }),
1220
- /* @__PURE__ */ jsx7(
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(
1221
1532
  "button",
1222
1533
  {
1223
1534
  type: "button",
@@ -1228,7 +1539,7 @@ var ReviewToolbar = ({
1228
1539
  children: "\u203A"
1229
1540
  }
1230
1541
  ),
1231
- showDescription && current && /* @__PURE__ */ jsxs6(
1542
+ showDescription && current && /* @__PURE__ */ jsxs7(
1232
1543
  "span",
1233
1544
  {
1234
1545
  className: descriptionClassName,
@@ -1240,8 +1551,8 @@ var ReviewToolbar = ({
1240
1551
  ]
1241
1552
  }
1242
1553
  ),
1243
- /* @__PURE__ */ jsx7("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1244
- /* @__PURE__ */ jsx7(
1554
+ /* @__PURE__ */ jsx8("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1555
+ /* @__PURE__ */ jsx8(
1245
1556
  "button",
1246
1557
  {
1247
1558
  type: "button",
@@ -1251,7 +1562,7 @@ var ReviewToolbar = ({
1251
1562
  children: "Accept"
1252
1563
  }
1253
1564
  ),
1254
- /* @__PURE__ */ jsx7(
1565
+ /* @__PURE__ */ jsx8(
1255
1566
  "button",
1256
1567
  {
1257
1568
  type: "button",
@@ -1261,18 +1572,18 @@ var ReviewToolbar = ({
1261
1572
  children: "Reject"
1262
1573
  }
1263
1574
  ),
1264
- /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.acceptAll, children: "Accept all" }),
1265
- /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.rejectAll, children: "Reject all" })
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" })
1266
1577
  ] });
1267
1578
  };
1268
1579
 
1269
1580
  // src/review/CommentButton.tsx
1270
- import { jsx as jsx8 } from "react/jsx-runtime";
1581
+ import { jsx as jsx9 } from "react/jsx-runtime";
1271
1582
  var CommentButton = ({
1272
1583
  editor,
1273
1584
  className = "rounded px-2 py-1 text-xs text-popover-foreground hover:bg-muted",
1274
1585
  children = "Comment"
1275
- }) => /* @__PURE__ */ jsx8(
1586
+ }) => /* @__PURE__ */ jsx9(
1276
1587
  "button",
1277
1588
  {
1278
1589
  type: "button",
@@ -1282,18 +1593,253 @@ var CommentButton = ({
1282
1593
  children
1283
1594
  }
1284
1595
  );
1596
+
1597
+ // src/review/useDocumentCompare.ts
1598
+ import { useCallback as useCallback7, useMemo as useMemo10, useRef as useRef8, useState as useState9 } from "react";
1599
+ import {
1600
+ applyDocumentComparison
1601
+ } from "@pixerate/editor";
1602
+ function useDocumentCompare(editor, options) {
1603
+ const [summary, setSummary] = useState9(null);
1604
+ const defaults = useRef8(options);
1605
+ defaults.current = options;
1606
+ const compare = useCallback7(
1607
+ (original, revised, overrides) => {
1608
+ if (!editor || editor.isDestroyed) return null;
1609
+ const result = applyDocumentComparison(editor, original, revised, {
1610
+ ...defaults.current,
1611
+ ...overrides
1612
+ });
1613
+ setSummary(result.summary);
1614
+ return result.summary;
1615
+ },
1616
+ [editor]
1617
+ );
1618
+ const clear = useCallback7(() => setSummary(null), []);
1619
+ return useMemo10(() => ({ summary, compare, clear }), [summary, compare, clear]);
1620
+ }
1621
+
1622
+ // src/find-replace/useFindReplace.ts
1623
+ import { useCallback as useCallback8, useMemo as useMemo11, useSyncExternalStore as useSyncExternalStore6 } from "react";
1624
+ import {
1625
+ EMPTY_FIND_REPLACE_SNAPSHOT,
1626
+ getFindReplaceStore
1627
+ } from "@pixerate/editor";
1628
+ var noopUnsubscribe2 = () => {
1629
+ };
1630
+ function useFindReplace(editor) {
1631
+ const store = getFindReplaceStore(editor ?? null);
1632
+ const subscribe = useCallback8(
1633
+ (onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe2,
1634
+ [store]
1635
+ );
1636
+ const getSnapshot = () => store?.getSnapshot() ?? EMPTY_FIND_REPLACE_SNAPSHOT;
1637
+ const snapshot = useSyncExternalStore6(subscribe, getSnapshot, getSnapshot);
1638
+ return useMemo11(() => {
1639
+ const run = (fn) => {
1640
+ if (editor && !editor.isDestroyed && store) fn(editor);
1641
+ };
1642
+ return {
1643
+ ...snapshot,
1644
+ available: !!store,
1645
+ matchCount: snapshot.matches.length,
1646
+ show: (mode = "find") => run((e) => e.commands.openFindReplace({ mode })),
1647
+ close: () => run((e) => e.commands.closeFindReplace()),
1648
+ setQuery: (query) => run((e) => e.commands.setSearch({ query })),
1649
+ setReplacement: (replacement) => run((e) => e.commands.setSearch({ replacement })),
1650
+ setOptions: (options) => run((e) => e.commands.setSearch({ options })),
1651
+ toggleOption: (option) => run((e) => e.commands.setSearch({ options: { [option]: !snapshot.options[option] } })),
1652
+ next: () => run((e) => e.commands.findNext()),
1653
+ previous: () => run((e) => e.commands.findPrevious()),
1654
+ replace: () => run((e) => e.commands.replaceCurrent()),
1655
+ replaceAll: () => run((e) => e.commands.replaceAll()),
1656
+ clear: () => run((e) => e.commands.clearSearch())
1657
+ };
1658
+ }, [snapshot, editor, store]);
1659
+ }
1660
+
1661
+ // src/find-replace/FindReplaceBar.tsx
1662
+ import { useEffect as useEffect8, useRef as useRef9, useState as useState10 } from "react";
1663
+ import { FIND_OPTION_TOGGLES, formatFindStatus } from "@pixerate/editor";
1664
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1665
+ 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"}`;
1668
+ var isMod = (e) => e.metaKey || e.ctrlKey;
1669
+ var FindReplaceBar = ({
1670
+ editor,
1671
+ className = "flex flex-col gap-1 rounded-md border border-border bg-background p-1 text-sm shadow-sm",
1672
+ alwaysOpen = false,
1673
+ showMoreOptions = true,
1674
+ onClose
1675
+ }) => {
1676
+ const find = useFindReplace(editor);
1677
+ const findInput = useRef9(null);
1678
+ const [moreOpen, setMoreOpen] = useState10(false);
1679
+ const visible = find.available && (alwaysOpen || find.open);
1680
+ useEffect8(() => {
1681
+ if (!visible || find.focusRequest === 0) return;
1682
+ findInput.current?.focus();
1683
+ findInput.current?.select();
1684
+ }, [visible, find.focusRequest]);
1685
+ if (!visible) return null;
1686
+ const showReplace = find.mode === "replace";
1687
+ const status = formatFindStatus(find);
1688
+ const hasMatches = find.matchCount > 0;
1689
+ const close = () => {
1690
+ find.close();
1691
+ editor?.commands.focus();
1692
+ onClose?.();
1693
+ };
1694
+ const onBarKeyDown = (e) => {
1695
+ if (e.key === "Escape") {
1696
+ e.preventDefault();
1697
+ close();
1698
+ } else if (isMod(e) && (e.key === "f" || e.key === "F")) {
1699
+ e.preventDefault();
1700
+ find.show("find");
1701
+ } else if (isMod(e) && (e.key === "h" || e.key === "H")) {
1702
+ e.preventDefault();
1703
+ find.show("replace");
1704
+ }
1705
+ };
1706
+ const onFindKeyDown = (e) => {
1707
+ if (e.key !== "Enter") return;
1708
+ e.preventDefault();
1709
+ if (e.shiftKey) find.previous();
1710
+ else find.next();
1711
+ };
1712
+ const onReplaceKeyDown = (e) => {
1713
+ if (e.key !== "Enter") return;
1714
+ e.preventDefault();
1715
+ if (isMod(e)) find.replaceAll();
1716
+ else find.replace();
1717
+ };
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(
1721
+ "button",
1722
+ {
1723
+ type: "button",
1724
+ className: buttonClass3,
1725
+ "aria-label": "Toggle replace",
1726
+ "aria-expanded": showReplace,
1727
+ onClick: () => find.show(showReplace ? "find" : "replace"),
1728
+ children: showReplace ? "\u25BE" : "\u25B8"
1729
+ }
1730
+ ),
1731
+ /* @__PURE__ */ jsx10(
1732
+ "input",
1733
+ {
1734
+ ref: findInput,
1735
+ type: "text",
1736
+ className: inputClass,
1737
+ placeholder: "Find",
1738
+ "aria-label": "Find",
1739
+ "aria-invalid": !!find.error,
1740
+ value: find.query,
1741
+ onChange: (e) => find.setQuery(e.target.value),
1742
+ onKeyDown: onFindKeyDown
1743
+ }
1744
+ ),
1745
+ FIND_OPTION_TOGGLES.filter((t) => !t.advanced).map((t) => /* @__PURE__ */ jsx10(
1746
+ "button",
1747
+ {
1748
+ type: "button",
1749
+ className: toggleClass(!!find.options[t.key]),
1750
+ "aria-label": t.label,
1751
+ title: t.label,
1752
+ "aria-pressed": !!find.options[t.key],
1753
+ onClick: () => find.toggleOption(t.key),
1754
+ children: t.short
1755
+ },
1756
+ t.key
1757
+ )),
1758
+ /* @__PURE__ */ jsx10(
1759
+ "span",
1760
+ {
1761
+ className: `min-w-[5rem] px-1 text-center text-xs ${find.error ? "text-destructive" : "text-muted-foreground"}`,
1762
+ "aria-live": "polite",
1763
+ role: find.error ? "alert" : "status",
1764
+ "data-testid": "find-status",
1765
+ children: status
1766
+ }
1767
+ ),
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(
1771
+ "button",
1772
+ {
1773
+ type: "button",
1774
+ className: buttonClass3,
1775
+ "aria-label": "More options",
1776
+ "aria-expanded": moreOpen,
1777
+ onClick: () => setMoreOpen((v) => !v),
1778
+ children: "\u2026"
1779
+ }
1780
+ ),
1781
+ /* @__PURE__ */ jsx10("button", { type: "button", className: buttonClass3, "aria-label": "Close find", onClick: close, children: "\xD7" })
1782
+ ] }),
1783
+ showReplace && /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1 pl-7", children: [
1784
+ /* @__PURE__ */ jsx10(
1785
+ "input",
1786
+ {
1787
+ type: "text",
1788
+ className: inputClass,
1789
+ placeholder: "Replace",
1790
+ "aria-label": "Replace",
1791
+ value: find.replacement,
1792
+ onChange: (e) => find.setReplacement(e.target.value),
1793
+ onKeyDown: onReplaceKeyDown
1794
+ }
1795
+ ),
1796
+ /* @__PURE__ */ jsx10(
1797
+ "button",
1798
+ {
1799
+ type: "button",
1800
+ className: buttonClass3,
1801
+ disabled: !hasMatches || find.currentIndex < 0,
1802
+ onClick: find.replace,
1803
+ children: "Replace"
1804
+ }
1805
+ ),
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` })
1808
+ ] }),
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(
1811
+ "input",
1812
+ {
1813
+ type: "checkbox",
1814
+ checked: !!find.options[t.key],
1815
+ onChange: () => find.toggleOption(t.key)
1816
+ }
1817
+ ),
1818
+ t.label
1819
+ ] }, t.key)) })
1820
+ ] });
1821
+ };
1285
1822
  export {
1823
+ AdjustmentLayersPanel,
1286
1824
  BubbleMenu,
1287
1825
  CommandPalette,
1288
1826
  CommentButton,
1289
1827
  CommentsPanel,
1828
+ ControlSlider,
1829
+ CurvesEditor,
1290
1830
  DirtyTracker2 as DirtyTracker,
1291
1831
  EditorContent,
1292
1832
  EditorSlotsProvider,
1833
+ FindReplaceBar,
1293
1834
  FormulaHints,
1835
+ GenerationSettings,
1836
+ Histogram,
1294
1837
  HistoryManager2 as HistoryManager,
1295
1838
  ImageEditor,
1839
+ JobProgress,
1840
+ MergePreview,
1296
1841
  OutlinePanel,
1842
+ RecoveryPrompt,
1297
1843
  ReviewToolbar,
1298
1844
  SpreadsheetContextMenu,
1299
1845
  SpreadsheetEditor,
@@ -1305,18 +1851,25 @@ export {
1305
1851
  createHistoryManager2 as createHistoryManager,
1306
1852
  defaultIsEqual,
1307
1853
  useAgentToolProvider,
1854
+ useAutosave,
1308
1855
  useCommandPalette,
1309
1856
  useCommandShortcuts,
1310
1857
  useComments,
1311
1858
  useDismissGuard,
1859
+ useDocumentCompare,
1312
1860
  useEditor2 as useEditor,
1313
1861
  useEditorSlots,
1314
1862
  useFileImport,
1315
1863
  useFillHandle,
1864
+ useFindReplace,
1865
+ useGenerationPlan,
1316
1866
  useHistory,
1317
1867
  useHistoryDirty,
1318
1868
  useHistoryShortcuts,
1319
1869
  useImageEditor,
1870
+ useImageHistogram,
1871
+ useJobProgress,
1872
+ useMergePreview,
1320
1873
  useNavigationGuard,
1321
1874
  useOutline,
1322
1875
  usePromptEditor,