@pixerate/editor-react 0.14.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 (54) hide show
  1. package/README.md +6 -1
  2. package/dist/canvas/index.cjs +352 -118
  3. package/dist/canvas/index.cjs.map +1 -1
  4. package/dist/canvas/index.d.cts +112 -8
  5. package/dist/canvas/index.d.ts +112 -8
  6. package/dist/canvas/index.js +258 -19
  7. package/dist/canvas/index.js.map +1 -1
  8. package/dist/chunk-4ZC2WCUO.js +196 -0
  9. package/dist/chunk-4ZC2WCUO.js.map +1 -0
  10. package/dist/chunk-7SPRQZ6H.js +216 -0
  11. package/dist/chunk-7SPRQZ6H.js.map +1 -0
  12. package/dist/{chunk-25S5EHH4.js → chunk-FYIPXHAE.js} +22 -13
  13. package/dist/chunk-FYIPXHAE.js.map +1 -0
  14. package/dist/{chunk-TL27COKM.js → chunk-XTFR3IMK.js} +683 -286
  15. package/dist/chunk-XTFR3IMK.js.map +1 -0
  16. package/dist/chunk-XWFU2CSG.js +167 -0
  17. package/dist/chunk-XWFU2CSG.js.map +1 -0
  18. package/dist/commands/index.cjs +232 -0
  19. package/dist/commands/index.cjs.map +1 -0
  20. package/dist/commands/index.d.cts +69 -0
  21. package/dist/commands/index.d.ts +69 -0
  22. package/dist/commands/index.js +13 -0
  23. package/dist/commands/index.js.map +1 -0
  24. package/dist/generation/index.cjs +235 -0
  25. package/dist/generation/index.cjs.map +1 -0
  26. package/dist/generation/index.d.cts +60 -0
  27. package/dist/generation/index.d.ts +60 -0
  28. package/dist/generation/index.js +13 -0
  29. package/dist/generation/index.js.map +1 -0
  30. package/dist/image-editor/index.cjs +688 -299
  31. package/dist/image-editor/index.cjs.map +1 -1
  32. package/dist/image-editor/index.d.cts +65 -5
  33. package/dist/image-editor/index.d.ts +65 -5
  34. package/dist/image-editor/index.js +13 -3
  35. package/dist/index.cjs +2200 -683
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +201 -23
  38. package/dist/index.d.ts +201 -23
  39. package/dist/index.js +743 -129
  40. package/dist/index.js.map +1 -1
  41. package/dist/persistence/index.cjs +191 -0
  42. package/dist/persistence/index.cjs.map +1 -0
  43. package/dist/persistence/index.d.cts +77 -0
  44. package/dist/persistence/index.d.ts +77 -0
  45. package/dist/persistence/index.js +9 -0
  46. package/dist/persistence/index.js.map +1 -0
  47. package/dist/spreadsheet/index.cjs +17 -10
  48. package/dist/spreadsheet/index.cjs.map +1 -1
  49. package/dist/spreadsheet/index.d.cts +9 -7
  50. package/dist/spreadsheet/index.d.ts +9 -7
  51. package/dist/spreadsheet/index.js +1 -1
  52. package/package.json +26 -2
  53. package/dist/chunk-25S5EHH4.js.map +0 -1
  54. package/dist/chunk-TL27COKM.js.map +0 -1
package/dist/index.js CHANGED
@@ -6,11 +6,32 @@ import {
6
6
  useFillHandle,
7
7
  useSpreadsheetClipboard,
8
8
  useSpreadsheetEditor
9
- } from "./chunk-25S5EHH4.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-TL27COKM.js";
16
+ useImageEditor,
17
+ useImageHistogram
18
+ } from "./chunk-XTFR3IMK.js";
19
+ import {
20
+ CommandPalette,
21
+ useCommandPalette,
22
+ useCommandShortcuts,
23
+ useRegisterCommands
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";
14
35
 
15
36
  // src/hooks/usePromptEditor.ts
16
37
  import { useMemo, useEffect, useRef } from "react";
@@ -406,7 +427,7 @@ function useDismissGuard({
406
427
  }
407
428
 
408
429
  // src/hooks/useHistory.ts
409
- import { useSyncExternalStore, useRef as useRef4, useCallback as useCallback3 } from "react";
430
+ import { useSyncExternalStore, useRef as useRef4, useCallback as useCallback3, useMemo as useMemo2 } from "react";
410
431
  import {
411
432
  HistoryManager,
412
433
  createHistoryManager
@@ -428,6 +449,7 @@ function useHistory(managerOrOptions) {
428
449
  () => manager.getState(),
429
450
  () => manager.getState()
430
451
  );
452
+ const entries = useMemo2(() => manager.getEntries(), [manager, state]);
431
453
  const undo = useCallback3(() => manager.undo(), [manager]);
432
454
  const redo = useCallback3(() => manager.redo(), [manager]);
433
455
  const clear = useCallback3(() => manager.clear(), [manager]);
@@ -443,60 +465,105 @@ function useHistory(managerOrOptions) {
443
465
  (name, fn) => manager.batch(name, fn),
444
466
  [manager]
445
467
  );
468
+ const goTo = useCallback3((index) => manager.goTo(index), [manager]);
469
+ const mark = useCallback3(() => manager.mark(), [manager]);
470
+ const revertTo = useCallback3(
471
+ (m) => manager.revertTo(m),
472
+ [manager]
473
+ );
474
+ const beginGesture = useCallback3(
475
+ (key) => manager.beginGesture(key),
476
+ [manager]
477
+ );
478
+ const endGesture = useCallback3(() => manager.endGesture(), [manager]);
446
479
  return {
447
480
  ...state,
448
481
  manager,
482
+ entries,
449
483
  undo,
450
484
  redo,
451
485
  clear,
452
486
  execute,
453
487
  executeMerged,
454
- batch
488
+ batch,
489
+ goTo,
490
+ mark,
491
+ revertTo,
492
+ beginGesture,
493
+ endGesture
455
494
  };
456
495
  }
457
496
 
497
+ // src/hooks/useHistoryDirty.ts
498
+ import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef5, useState as useState3, useSyncExternalStore as useSyncExternalStore2 } from "react";
499
+ import {
500
+ DirtyTracker,
501
+ bindDirtyToHistory
502
+ } from "@pixerate/editor";
503
+ function useHistoryDirty(options) {
504
+ const getValueRef = useRef5(options.getValue);
505
+ getValueRef.current = options.getValue;
506
+ const [tracker] = useState3(
507
+ () => options.tracker ?? new DirtyTracker(options.getValue(), {
508
+ isEqual: options.isEqual,
509
+ normalize: options.normalize
510
+ })
511
+ );
512
+ const bindingRef = useRef5(null);
513
+ useEffect3(() => {
514
+ const binding = bindDirtyToHistory({
515
+ history: options.history,
516
+ getValue: () => getValueRef.current(),
517
+ tracker
518
+ });
519
+ bindingRef.current = binding;
520
+ return () => {
521
+ binding.destroy();
522
+ if (bindingRef.current === binding) bindingRef.current = null;
523
+ };
524
+ }, [options.history, tracker]);
525
+ const isDirty = useSyncExternalStore2(
526
+ useCallback4((onChange) => tracker.subscribe(onChange), [tracker]),
527
+ () => tracker.dirty,
528
+ () => tracker.dirty
529
+ );
530
+ const markClean = useCallback4(
531
+ () => tracker.markClean(getValueRef.current()),
532
+ [tracker]
533
+ );
534
+ const suppress = useCallback4(
535
+ (fn) => bindingRef.current ? bindingRef.current.suppress(fn) : tracker.suppress(fn),
536
+ [tracker]
537
+ );
538
+ const refresh = useCallback4(
539
+ () => tracker.update(getValueRef.current()),
540
+ [tracker]
541
+ );
542
+ return { isDirty, markClean, suppress, refresh, tracker };
543
+ }
544
+
458
545
  // src/hooks/useHistoryShortcuts.ts
459
- import { useEffect as useEffect3 } from "react";
546
+ import { useEffect as useEffect4 } from "react";
547
+ import { matchHistoryShortcut } from "@pixerate/editor/commands";
460
548
  function useHistoryShortcuts(manager, options = {}) {
461
549
  const {
462
550
  enabled = true,
463
551
  target,
464
552
  ignoreEditableElements = true
465
553
  } = options;
466
- useEffect3(() => {
554
+ useEffect4(() => {
467
555
  if (!enabled) return;
468
556
  const eventTarget = target ?? (typeof window !== "undefined" ? window : null);
469
557
  if (!eventTarget) return;
470
558
  const handleKeyDown = (event) => {
471
559
  const e = event;
472
- const isMac = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
473
- const modifier = isMac ? e.metaKey : e.ctrlKey;
474
- if (!modifier) return;
475
- if (ignoreEditableElements && typeof document !== "undefined") {
476
- const targetEl = e.target || document.activeElement;
477
- if (targetEl && targetEl.tagName) {
478
- const tagName = targetEl.tagName.toLowerCase();
479
- const isInput = tagName === "input" || tagName === "textarea";
480
- const isContentEditable = targetEl.isContentEditable || targetEl.getAttribute?.("contenteditable") === "true";
481
- if (isInput || isContentEditable) {
482
- return;
483
- }
484
- }
485
- }
486
- const key = e.key.toLowerCase();
487
- if (key === "z" && !e.shiftKey) {
488
- if (manager.getState().canUndo) {
489
- e.preventDefault();
490
- manager.undo();
491
- }
492
- return;
493
- }
494
- const isRedoKey = key === "z" && e.shiftKey || !isMac && key === "y" && !e.shiftKey;
495
- if (isRedoKey) {
496
- if (manager.getState().canRedo) {
497
- e.preventDefault();
498
- manager.redo();
499
- }
560
+ const action = matchHistoryShortcut(e, { allowInEditable: !ignoreEditableElements });
561
+ if (action === "undo" && manager.getState().canUndo) {
562
+ e.preventDefault();
563
+ manager.undo();
564
+ } else if (action === "redo" && manager.getState().canRedo) {
565
+ e.preventDefault();
566
+ manager.redo();
500
567
  }
501
568
  };
502
569
  eventTarget.addEventListener("keydown", handleKeyDown);
@@ -507,24 +574,24 @@ function useHistoryShortcuts(manager, options = {}) {
507
574
  }
508
575
 
509
576
  // src/hooks/useFileImport.ts
510
- import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef5, useSyncExternalStore as useSyncExternalStore2 } from "react";
577
+ import { useEffect as useEffect5, useMemo as useMemo3, useRef as useRef6, useSyncExternalStore as useSyncExternalStore3 } from "react";
511
578
  import {
512
579
  FileImportController
513
580
  } from "@pixerate/editor";
514
581
  function useFileImport(editor, options = {}) {
515
- const editorRef = useRef5(editor);
582
+ const editorRef = useRef6(editor);
516
583
  editorRef.current = editor;
517
- const controller = useMemo2(
584
+ const controller = useMemo3(
518
585
  () => new FileImportController(() => {
519
586
  const current = editorRef.current;
520
587
  return current && !current.isDestroyed ? current : null;
521
588
  }, options),
522
589
  []
523
590
  );
524
- useEffect4(() => {
591
+ useEffect5(() => {
525
592
  controller.setOptions(options);
526
593
  });
527
- const state = useSyncExternalStore2(controller.subscribe, controller.getState, controller.getState);
594
+ const state = useSyncExternalStore3(controller.subscribe, controller.getState, controller.getState);
528
595
  return {
529
596
  ...state,
530
597
  accept: controller.accept,
@@ -534,9 +601,9 @@ function useFileImport(editor, options = {}) {
534
601
  }
535
602
 
536
603
  // src/hooks/useAgentToolProvider.ts
537
- import { useEffect as useEffect5 } from "react";
604
+ import { useEffect as useEffect6 } from "react";
538
605
  function useAgentToolProvider(registry, provider) {
539
- useEffect5(() => {
606
+ useEffect6(() => {
540
607
  if (!registry || !provider) return;
541
608
  return registry.register(provider);
542
609
  }, [registry, provider]);
@@ -556,7 +623,7 @@ var EditorContent = ({
556
623
  };
557
624
 
558
625
  // src/components/BubbleMenu.tsx
559
- import { useEffect as useEffect6, useRef as useRef6, useState as useState3 } from "react";
626
+ import { useEffect as useEffect7, useRef as useRef7, useState as useState4 } from "react";
560
627
  import { createPortal } from "react-dom";
561
628
  import {
562
629
  BubbleMenuPlugin
@@ -570,12 +637,12 @@ var BubbleMenu = ({
570
637
  pluginKey = "bubbleMenu",
571
638
  updateDelay
572
639
  }) => {
573
- const menuEl = useRef6(null);
574
- const [mounted, setMounted] = useState3(false);
640
+ const menuEl = useRef7(null);
641
+ const [mounted, setMounted] = useState4(false);
575
642
  if (!menuEl.current && typeof document !== "undefined") {
576
643
  menuEl.current = document.createElement("div");
577
644
  }
578
- useEffect6(() => {
645
+ useEffect7(() => {
579
646
  if (!editor || !menuEl.current || editor.isDestroyed) {
580
647
  return;
581
648
  }
@@ -606,13 +673,14 @@ var BubbleMenu = ({
606
673
  };
607
674
 
608
675
  // src/components/TemplateRenderer.tsx
609
- import { useMemo as useMemo3 } from "react";
676
+ import { useMemo as useMemo4 } from "react";
610
677
  import {
678
+ describeConditionalTag,
611
679
  evaluatePromptTokens,
680
+ formatConditionalTag,
612
681
  resolveTemplates
613
682
  } from "@pixerate/editor";
614
683
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
615
- var CONDITIONAL_PREFIX = { if: "?", unless: "!", end: "/" };
616
684
  var TemplateRenderer = ({
617
685
  content,
618
686
  templates = [],
@@ -624,11 +692,11 @@ var TemplateRenderer = ({
624
692
  onTemplateClick,
625
693
  onVariableClick
626
694
  }) => {
627
- const displayText = useMemo3(() => {
695
+ const displayText = useMemo4(() => {
628
696
  if (!resolve || templates.length === 0) return content;
629
697
  return resolveTemplates(content, templates, { bindings });
630
698
  }, [content, templates, resolve, bindings]);
631
- const tokens = useMemo3(() => {
699
+ const tokens = useMemo4(() => {
632
700
  return evaluatePromptTokens(displayText, values);
633
701
  }, [displayText, values]);
634
702
  return /* @__PURE__ */ jsx2("div", { className, children: tokens.map((token, index) => {
@@ -695,8 +763,8 @@ var TemplateRenderer = ({
695
763
  {
696
764
  "data-conditional": token.conditional,
697
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",
698
- 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`,
699
- children: `{${CONDITIONAL_PREFIX[token.conditional]}${token.value}}`
766
+ title: describeConditionalTag(token),
767
+ children: formatConditionalTag(token)
700
768
  },
701
769
  index
702
770
  );
@@ -717,7 +785,7 @@ var TemplateRenderer = ({
717
785
  };
718
786
 
719
787
  // src/components/VariableForm.tsx
720
- import { useId, useMemo as useMemo4, useState as useState4 } from "react";
788
+ import { useId, useMemo as useMemo5, useState as useState5 } from "react";
721
789
  import {
722
790
  compactVariableValues,
723
791
  getVariableFormFields
@@ -733,11 +801,11 @@ var VariableForm = ({
733
801
  className = "flex flex-col gap-3 text-sm"
734
802
  }) => {
735
803
  const idPrefix = useId();
736
- const [internal, setInternal] = useState4(
804
+ const [internal, setInternal] = useState5(
737
805
  () => defaultValues ?? {}
738
806
  );
739
807
  const entered = values ?? internal;
740
- const fields = useMemo4(
808
+ const fields = useMemo5(
741
809
  () => getVariableFormFields(content, { templates, bindings }),
742
810
  [content, templates, bindings]
743
811
  );
@@ -804,17 +872,312 @@ var VariableForm = ({
804
872
  }) });
805
873
  };
806
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
+
807
1170
  // src/hooks/useOutline.ts
808
- import { useMemo as useMemo5, useSyncExternalStore as useSyncExternalStore3 } from "react";
1171
+ import { useMemo as useMemo7, useSyncExternalStore as useSyncExternalStore4 } from "react";
809
1172
  import { createOutlineStore } from "@pixerate/editor";
810
1173
  function useOutline(editor) {
811
- const store = useMemo5(() => createOutlineStore(editor), [editor]);
812
- return useSyncExternalStore3(store.subscribe, store.getSnapshot, store.getSnapshot);
1174
+ const store = useMemo7(() => createOutlineStore(editor), [editor]);
1175
+ return useSyncExternalStore4(store.subscribe, store.getSnapshot, store.getSnapshot);
813
1176
  }
814
1177
 
815
1178
  // src/components/OutlinePanel.tsx
816
1179
  import { navigateToHeading } from "@pixerate/editor";
817
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1180
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
818
1181
  var OutlinePanel = ({
819
1182
  editor,
820
1183
  title = "Outline",
@@ -828,11 +1191,11 @@ var OutlinePanel = ({
828
1191
  }) => {
829
1192
  const { headings, activeId } = useOutline(editor);
830
1193
  const visible = headings.filter((h) => h.level <= maxLevel);
831
- return /* @__PURE__ */ jsxs3("nav", { className, "aria-label": "Document outline", "data-outline-panel": "", children: [
832
- title != null && /* @__PURE__ */ jsx4("div", { className: "pixerate-outline-title", children: title }),
833
- 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) => {
834
1197
  const active = heading.id === activeId;
835
- return /* @__PURE__ */ jsx4("li", { "data-level": heading.level, children: /* @__PURE__ */ jsx4(
1198
+ return /* @__PURE__ */ jsx5("li", { "data-level": heading.level, children: /* @__PURE__ */ jsx5(
836
1199
  "button",
837
1200
  {
838
1201
  type: "button",
@@ -855,18 +1218,20 @@ var OutlinePanel = ({
855
1218
 
856
1219
  // src/index.ts
857
1220
  import {
858
- DirtyTracker,
1221
+ DirtyTracker as DirtyTracker2,
859
1222
  createDirtyTracker,
860
1223
  defaultIsEqual,
861
1224
  HistoryManager as HistoryManager2,
862
- createHistoryManager as createHistoryManager2
1225
+ createHistoryManager as createHistoryManager2,
1226
+ bindDirtyToHistory as bindDirtyToHistory2,
1227
+ withoutDirty
863
1228
  } from "@pixerate/editor";
864
1229
 
865
1230
  // src/slots.tsx
866
- import { createContext, useContext, useMemo as useMemo6 } from "react";
867
- 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";
868
1233
  var defaultSlots = {
869
- Button: (props) => /* @__PURE__ */ jsx5(
1234
+ Button: (props) => /* @__PURE__ */ jsx6(
870
1235
  "button",
871
1236
  {
872
1237
  type: "button",
@@ -876,26 +1241,26 @@ var defaultSlots = {
876
1241
  children: props.children
877
1242
  }
878
1243
  ),
879
- Tooltip: (props) => /* @__PURE__ */ jsx5("div", { className: `pixerate-tooltip-container ${props.className || ""}`, title: String(props.content), children: props.children }),
880
- NodeHeader: (props) => /* @__PURE__ */ jsxs4("div", { className: `pixerate-node-header flex items-center justify-between ${props.className || ""}`, children: [
881
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
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: [
882
1247
  props.icon,
883
- /* @__PURE__ */ jsx5("span", { className: "font-semibold", children: props.title })
1248
+ /* @__PURE__ */ jsx6("span", { className: "font-semibold", children: props.title })
884
1249
  ] }),
885
- 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" })
886
1251
  ] })
887
1252
  };
888
1253
  var EditorSlotsContext = createContext(defaultSlots);
889
1254
  function EditorSlotsProvider({ slots, children }) {
890
- const mergedSlots = useMemo6(() => ({ ...defaultSlots, ...slots }), [slots]);
891
- return /* @__PURE__ */ jsx5(EditorSlotsContext.Provider, { value: mergedSlots, children });
1255
+ const mergedSlots = useMemo8(() => ({ ...defaultSlots, ...slots }), [slots]);
1256
+ return /* @__PURE__ */ jsx6(EditorSlotsContext.Provider, { value: mergedSlots, children });
892
1257
  }
893
1258
  function useEditorSlots() {
894
1259
  return useContext(EditorSlotsContext);
895
1260
  }
896
1261
 
897
1262
  // src/review/useReview.ts
898
- import { useCallback as useCallback4, useMemo as useMemo7, useSyncExternalStore as useSyncExternalStore4 } from "react";
1263
+ import { useCallback as useCallback6, useMemo as useMemo9, useSyncExternalStore as useSyncExternalStore5 } from "react";
899
1264
  import {
900
1265
  getCommentStore,
901
1266
  getTrackChangesStore
@@ -917,23 +1282,23 @@ function isCommentStore(value) {
917
1282
  }
918
1283
  function useComments(source) {
919
1284
  const store = isCommentStore(source) ? source : getCommentStore(source);
920
- const subscribe = useCallback4(
1285
+ const subscribe = useCallback6(
921
1286
  (onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
922
1287
  [store]
923
1288
  );
924
1289
  const getSnapshot = () => store?.getSnapshot() ?? EMPTY_COMMENTS;
925
- const snapshot = useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
926
- return useMemo7(() => ({ ...snapshot, store }), [snapshot, store]);
1290
+ const snapshot = useSyncExternalStore5(subscribe, getSnapshot, getSnapshot);
1291
+ return useMemo9(() => ({ ...snapshot, store }), [snapshot, store]);
927
1292
  }
928
1293
  function useTrackChanges(editor) {
929
1294
  const store = getTrackChangesStore(editor ?? null);
930
- const subscribe = useCallback4(
1295
+ const subscribe = useCallback6(
931
1296
  (onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
932
1297
  [store]
933
1298
  );
934
1299
  const getSnapshot = () => store?.getSnapshot() ?? EMPTY_TRACK_CHANGES;
935
- const snapshot = useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
936
- return useMemo7(() => {
1300
+ const snapshot = useSyncExternalStore5(subscribe, getSnapshot, getSnapshot);
1301
+ return useMemo9(() => {
937
1302
  const run = (fn) => () => {
938
1303
  if (editor && !editor.isDestroyed) fn(editor);
939
1304
  };
@@ -953,12 +1318,12 @@ function useTrackChanges(editor) {
953
1318
  }
954
1319
 
955
1320
  // src/review/CommentsPanel.tsx
956
- import { useState as useState5 } from "react";
1321
+ import { useState as useState8 } from "react";
957
1322
  import {
958
1323
  formatReviewTime,
959
1324
  getCommentAuthor
960
1325
  } from "@pixerate/editor";
961
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1326
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
962
1327
  var buttonClass = "rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50";
963
1328
  var primaryButtonClass = "rounded px-2 py-1 text-xs bg-primary/10 text-primary hover:bg-primary/20 disabled:opacity-50";
964
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";
@@ -972,18 +1337,18 @@ var CommentsPanel = ({
972
1337
  emptyMessage = "No comments yet. Select text and add a comment."
973
1338
  }) => {
974
1339
  const { threads, activeThreadId, store } = useComments(storeProp ?? editor);
975
- const [showResolved, setShowResolved] = useState5(showResolvedInitial);
1340
+ const [showResolved, setShowResolved] = useState8(showResolvedInitial);
976
1341
  const author = authorProp ?? getCommentAuthor(editor) ?? { id: "anonymous", name: "Anonymous" };
977
1342
  const visible = threads.filter((t) => showResolved || !t.resolved);
978
1343
  const resolvedCount = threads.filter((t) => t.resolved).length;
979
- return /* @__PURE__ */ jsxs5("aside", { className, "aria-label": "Comments", children: [
980
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between gap-2", children: [
981
- /* @__PURE__ */ jsxs5("span", { className: "text-xs font-medium text-muted-foreground", children: [
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: [
982
1347
  threads.length - resolvedCount,
983
1348
  " open",
984
1349
  resolvedCount > 0 ? ` \xB7 ${resolvedCount} resolved` : ""
985
1350
  ] }),
986
- resolvedCount > 0 && /* @__PURE__ */ jsx6(
1351
+ resolvedCount > 0 && /* @__PURE__ */ jsx7(
987
1352
  "button",
988
1353
  {
989
1354
  type: "button",
@@ -994,7 +1359,7 @@ var CommentsPanel = ({
994
1359
  }
995
1360
  )
996
1361
  ] }),
997
- visible.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ jsx6("ul", { className: "flex flex-col gap-2", role: "list", children: visible.map((thread) => /* @__PURE__ */ jsx6(
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(
998
1363
  CommentThreadCard,
999
1364
  {
1000
1365
  thread,
@@ -1016,7 +1381,7 @@ var CommentThreadCard = ({
1016
1381
  author,
1017
1382
  formatTime
1018
1383
  }) => {
1019
- const [draft, setDraft] = useState5("");
1384
+ const [draft, setDraft] = useState8("");
1020
1385
  const isDraft = thread.body === "";
1021
1386
  const quote = thread.anchor?.quote ?? thread.lastQuote ?? "";
1022
1387
  const live = editor && !editor.isDestroyed ? editor : null;
@@ -1042,7 +1407,7 @@ var CommentThreadCard = ({
1042
1407
  setDraft("");
1043
1408
  };
1044
1409
  const stop = (e) => e.stopPropagation();
1045
- return /* @__PURE__ */ jsxs5(
1410
+ return /* @__PURE__ */ jsxs6(
1046
1411
  "li",
1047
1412
  {
1048
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" : ""}`,
@@ -1058,24 +1423,24 @@ var CommentThreadCard = ({
1058
1423
  }
1059
1424
  },
1060
1425
  children: [
1061
- /* @__PURE__ */ jsxs5("div", { className: "flex items-baseline justify-between gap-2", children: [
1062
- /* @__PURE__ */ jsx6("span", { className: "font-medium", children: thread.author.name }),
1063
- /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: formatTime(thread.createdAt) })
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) })
1064
1429
  ] }),
1065
- quote && /* @__PURE__ */ jsx6("blockquote", { className: "border-l-2 border-border pl-2 text-xs text-muted-foreground line-clamp-2", children: quote }),
1066
- thread.orphaned && /* @__PURE__ */ jsx6("span", { className: "text-xs text-destructive", children: "The commented text was deleted." }),
1067
- thread.resolved && /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: "Resolved" }),
1068
- !isDraft && /* @__PURE__ */ jsx6("p", { className: "whitespace-pre-wrap break-words", children: thread.body }),
1069
- thread.replies.length > 0 && /* @__PURE__ */ jsx6("ul", { className: "flex flex-col gap-2 border-l border-border pl-3", role: "list", children: thread.replies.map((reply) => /* @__PURE__ */ jsxs5("li", { className: "flex flex-col gap-0.5", "data-reply-id": reply.id, children: [
1070
- /* @__PURE__ */ jsxs5("span", { className: "text-xs", children: [
1071
- /* @__PURE__ */ jsx6("span", { className: "font-medium", children: reply.author.name }),
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 }),
1072
1437
  " ",
1073
- /* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", children: formatTime(reply.createdAt) })
1438
+ /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: formatTime(reply.createdAt) })
1074
1439
  ] }),
1075
- /* @__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 })
1076
1441
  ] }, reply.id)) }),
1077
- (active || isDraft) && !thread.resolved && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", onClick: stop, children: [
1078
- /* @__PURE__ */ jsx6(
1442
+ (active || isDraft) && !thread.resolved && /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1", onClick: stop, children: [
1443
+ /* @__PURE__ */ jsx7(
1079
1444
  "textarea",
1080
1445
  {
1081
1446
  className: textareaClass,
@@ -1093,9 +1458,9 @@ var CommentThreadCard = ({
1093
1458
  }
1094
1459
  }
1095
1460
  ),
1096
- /* @__PURE__ */ jsxs5("div", { className: "flex justify-end gap-1", children: [
1097
- isDraft && /* @__PURE__ */ jsx6("button", { type: "button", className: buttonClass, onClick: remove, children: "Cancel" }),
1098
- /* @__PURE__ */ jsx6(
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(
1099
1464
  "button",
1100
1465
  {
1101
1466
  type: "button",
@@ -1107,9 +1472,9 @@ var CommentThreadCard = ({
1107
1472
  )
1108
1473
  ] })
1109
1474
  ] }),
1110
- !isDraft && /* @__PURE__ */ jsxs5("div", { className: "flex justify-end gap-1", onClick: stop, children: [
1111
- /* @__PURE__ */ jsx6("button", { type: "button", className: buttonClass, onClick: () => setResolved(!thread.resolved), children: thread.resolved ? "Reopen" : "Resolve" }),
1112
- /* @__PURE__ */ jsx6(
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(
1113
1478
  "button",
1114
1479
  {
1115
1480
  type: "button",
@@ -1125,7 +1490,7 @@ var CommentThreadCard = ({
1125
1490
  };
1126
1491
 
1127
1492
  // src/review/ReviewToolbar.tsx
1128
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1493
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1129
1494
  var defaultDescriptionClass = "max-w-[16rem] truncate px-1 text-xs text-muted-foreground";
1130
1495
  var buttonClass2 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
1131
1496
  var ReviewToolbar = ({
@@ -1138,8 +1503,8 @@ var ReviewToolbar = ({
1138
1503
  const count = review.changes.length;
1139
1504
  const current = review.currentChange;
1140
1505
  const index = current ? review.changes.findIndex((c) => c.id === current.id) + 1 : 0;
1141
- return /* @__PURE__ */ jsxs6("div", { className, role: "toolbar", "aria-label": "Review", children: [
1142
- /* @__PURE__ */ jsx7(
1506
+ return /* @__PURE__ */ jsxs7("div", { className, role: "toolbar", "aria-label": "Review", children: [
1507
+ /* @__PURE__ */ jsx8(
1143
1508
  "button",
1144
1509
  {
1145
1510
  type: "button",
@@ -1150,8 +1515,8 @@ var ReviewToolbar = ({
1150
1515
  children: review.enabled ? "Tracking on" : "Track changes"
1151
1516
  }
1152
1517
  ),
1153
- /* @__PURE__ */ jsx7("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1154
- /* @__PURE__ */ jsx7(
1518
+ /* @__PURE__ */ jsx8("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1519
+ /* @__PURE__ */ jsx8(
1155
1520
  "button",
1156
1521
  {
1157
1522
  type: "button",
@@ -1162,8 +1527,8 @@ var ReviewToolbar = ({
1162
1527
  children: "\u2039"
1163
1528
  }
1164
1529
  ),
1165
- /* @__PURE__ */ jsx7("span", { className: "min-w-[4rem] text-center text-xs text-muted-foreground", "aria-live": "polite", children: count === 0 ? "No changes" : current ? `${index} of ${count}` : `${count} changes` }),
1166
- /* @__PURE__ */ jsx7(
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(
1167
1532
  "button",
1168
1533
  {
1169
1534
  type: "button",
@@ -1174,7 +1539,7 @@ var ReviewToolbar = ({
1174
1539
  children: "\u203A"
1175
1540
  }
1176
1541
  ),
1177
- showDescription && current && /* @__PURE__ */ jsxs6(
1542
+ showDescription && current && /* @__PURE__ */ jsxs7(
1178
1543
  "span",
1179
1544
  {
1180
1545
  className: descriptionClassName,
@@ -1186,8 +1551,8 @@ var ReviewToolbar = ({
1186
1551
  ]
1187
1552
  }
1188
1553
  ),
1189
- /* @__PURE__ */ jsx7("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1190
- /* @__PURE__ */ jsx7(
1554
+ /* @__PURE__ */ jsx8("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1555
+ /* @__PURE__ */ jsx8(
1191
1556
  "button",
1192
1557
  {
1193
1558
  type: "button",
@@ -1197,7 +1562,7 @@ var ReviewToolbar = ({
1197
1562
  children: "Accept"
1198
1563
  }
1199
1564
  ),
1200
- /* @__PURE__ */ jsx7(
1565
+ /* @__PURE__ */ jsx8(
1201
1566
  "button",
1202
1567
  {
1203
1568
  type: "button",
@@ -1207,18 +1572,18 @@ var ReviewToolbar = ({
1207
1572
  children: "Reject"
1208
1573
  }
1209
1574
  ),
1210
- /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.acceptAll, children: "Accept all" }),
1211
- /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.rejectAll, children: "Reject all" })
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" })
1212
1577
  ] });
1213
1578
  };
1214
1579
 
1215
1580
  // src/review/CommentButton.tsx
1216
- import { jsx as jsx8 } from "react/jsx-runtime";
1581
+ import { jsx as jsx9 } from "react/jsx-runtime";
1217
1582
  var CommentButton = ({
1218
1583
  editor,
1219
1584
  className = "rounded px-2 py-1 text-xs text-popover-foreground hover:bg-muted",
1220
1585
  children = "Comment"
1221
- }) => /* @__PURE__ */ jsx8(
1586
+ }) => /* @__PURE__ */ jsx9(
1222
1587
  "button",
1223
1588
  {
1224
1589
  type: "button",
@@ -1228,41 +1593,290 @@ var CommentButton = ({
1228
1593
  children
1229
1594
  }
1230
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
+ };
1231
1822
  export {
1823
+ AdjustmentLayersPanel,
1232
1824
  BubbleMenu,
1825
+ CommandPalette,
1233
1826
  CommentButton,
1234
1827
  CommentsPanel,
1235
- DirtyTracker,
1828
+ ControlSlider,
1829
+ CurvesEditor,
1830
+ DirtyTracker2 as DirtyTracker,
1236
1831
  EditorContent,
1237
1832
  EditorSlotsProvider,
1833
+ FindReplaceBar,
1238
1834
  FormulaHints,
1835
+ GenerationSettings,
1836
+ Histogram,
1239
1837
  HistoryManager2 as HistoryManager,
1240
1838
  ImageEditor,
1839
+ JobProgress,
1840
+ MergePreview,
1241
1841
  OutlinePanel,
1842
+ RecoveryPrompt,
1242
1843
  ReviewToolbar,
1243
1844
  SpreadsheetContextMenu,
1244
1845
  SpreadsheetEditor,
1245
1846
  SpreadsheetGroupRow,
1246
1847
  TemplateRenderer,
1247
1848
  VariableForm,
1849
+ bindDirtyToHistory2 as bindDirtyToHistory,
1248
1850
  createDirtyTracker,
1249
1851
  createHistoryManager2 as createHistoryManager,
1250
1852
  defaultIsEqual,
1251
1853
  useAgentToolProvider,
1854
+ useAutosave,
1855
+ useCommandPalette,
1856
+ useCommandShortcuts,
1252
1857
  useComments,
1253
1858
  useDismissGuard,
1859
+ useDocumentCompare,
1254
1860
  useEditor2 as useEditor,
1255
1861
  useEditorSlots,
1256
1862
  useFileImport,
1257
1863
  useFillHandle,
1864
+ useFindReplace,
1865
+ useGenerationPlan,
1258
1866
  useHistory,
1867
+ useHistoryDirty,
1259
1868
  useHistoryShortcuts,
1260
1869
  useImageEditor,
1870
+ useImageHistogram,
1871
+ useJobProgress,
1872
+ useMergePreview,
1261
1873
  useNavigationGuard,
1262
1874
  useOutline,
1263
1875
  usePromptEditor,
1876
+ useRegisterCommands,
1264
1877
  useSpreadsheetClipboard,
1265
1878
  useSpreadsheetEditor,
1266
- useTrackChanges
1879
+ useTrackChanges,
1880
+ withoutDirty
1267
1881
  };
1268
1882
  //# sourceMappingURL=index.js.map