@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.cjs CHANGED
@@ -30,41 +30,64 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ AdjustmentLayersPanel: () => AdjustmentLayersPanel,
33
34
  BubbleMenu: () => BubbleMenu,
35
+ CommandPalette: () => CommandPalette,
34
36
  CommentButton: () => CommentButton,
35
37
  CommentsPanel: () => CommentsPanel,
36
- DirtyTracker: () => import_editor11.DirtyTracker,
38
+ ControlSlider: () => ControlSlider,
39
+ CurvesEditor: () => CurvesEditor,
40
+ DirtyTracker: () => import_editor16.DirtyTracker,
37
41
  EditorContent: () => EditorContent,
38
42
  EditorSlotsProvider: () => EditorSlotsProvider,
43
+ FindReplaceBar: () => FindReplaceBar,
39
44
  FormulaHints: () => FormulaHints,
40
- HistoryManager: () => import_editor11.HistoryManager,
45
+ GenerationSettings: () => GenerationSettings,
46
+ Histogram: () => Histogram,
47
+ HistoryManager: () => import_editor16.HistoryManager,
41
48
  ImageEditor: () => ImageEditor,
49
+ JobProgress: () => JobProgress,
50
+ MergePreview: () => MergePreview,
42
51
  OutlinePanel: () => OutlinePanel,
52
+ RecoveryPrompt: () => RecoveryPrompt,
43
53
  ReviewToolbar: () => ReviewToolbar,
44
54
  SpreadsheetContextMenu: () => SpreadsheetContextMenu,
45
55
  SpreadsheetEditor: () => SpreadsheetEditor,
46
56
  SpreadsheetGroupRow: () => SpreadsheetGroupRow,
47
57
  TemplateRenderer: () => TemplateRenderer,
48
58
  VariableForm: () => VariableForm,
49
- createDirtyTracker: () => import_editor11.createDirtyTracker,
50
- createHistoryManager: () => import_editor11.createHistoryManager,
51
- defaultIsEqual: () => import_editor11.defaultIsEqual,
59
+ bindDirtyToHistory: () => import_editor16.bindDirtyToHistory,
60
+ createDirtyTracker: () => import_editor16.createDirtyTracker,
61
+ createHistoryManager: () => import_editor16.createHistoryManager,
62
+ defaultIsEqual: () => import_editor16.defaultIsEqual,
52
63
  useAgentToolProvider: () => useAgentToolProvider,
64
+ useAutosave: () => useAutosave,
65
+ useCommandPalette: () => useCommandPalette,
66
+ useCommandShortcuts: () => useCommandShortcuts,
53
67
  useComments: () => useComments,
54
68
  useDismissGuard: () => useDismissGuard,
69
+ useDocumentCompare: () => useDocumentCompare,
55
70
  useEditor: () => useEditor2,
56
71
  useEditorSlots: () => useEditorSlots,
57
72
  useFileImport: () => useFileImport,
58
73
  useFillHandle: () => useFillHandle,
74
+ useFindReplace: () => useFindReplace,
75
+ useGenerationPlan: () => useGenerationPlan,
59
76
  useHistory: () => useHistory,
77
+ useHistoryDirty: () => useHistoryDirty,
60
78
  useHistoryShortcuts: () => useHistoryShortcuts,
61
79
  useImageEditor: () => useImageEditor,
80
+ useImageHistogram: () => useImageHistogram,
81
+ useJobProgress: () => useJobProgress,
82
+ useMergePreview: () => useMergePreview,
62
83
  useNavigationGuard: () => useNavigationGuard,
63
84
  useOutline: () => useOutline,
64
85
  usePromptEditor: () => usePromptEditor,
86
+ useRegisterCommands: () => useRegisterCommands,
65
87
  useSpreadsheetClipboard: () => useSpreadsheetClipboard,
66
88
  useSpreadsheetEditor: () => useSpreadsheetEditor,
67
- useTrackChanges: () => useTrackChanges
89
+ useTrackChanges: () => useTrackChanges,
90
+ withoutDirty: () => import_editor16.withoutDirty
68
91
  });
69
92
  module.exports = __toCommonJS(src_exports);
70
93
 
@@ -470,6 +493,7 @@ function useHistory(managerOrOptions) {
470
493
  () => manager.getState(),
471
494
  () => manager.getState()
472
495
  );
496
+ const entries = (0, import_react6.useMemo)(() => manager.getEntries(), [manager, state]);
473
497
  const undo = (0, import_react6.useCallback)(() => manager.undo(), [manager]);
474
498
  const redo = (0, import_react6.useCallback)(() => manager.redo(), [manager]);
475
499
  const clear = (0, import_react6.useCallback)(() => manager.clear(), [manager]);
@@ -485,60 +509,102 @@ function useHistory(managerOrOptions) {
485
509
  (name, fn) => manager.batch(name, fn),
486
510
  [manager]
487
511
  );
512
+ const goTo = (0, import_react6.useCallback)((index) => manager.goTo(index), [manager]);
513
+ const mark = (0, import_react6.useCallback)(() => manager.mark(), [manager]);
514
+ const revertTo = (0, import_react6.useCallback)(
515
+ (m) => manager.revertTo(m),
516
+ [manager]
517
+ );
518
+ const beginGesture = (0, import_react6.useCallback)(
519
+ (key) => manager.beginGesture(key),
520
+ [manager]
521
+ );
522
+ const endGesture = (0, import_react6.useCallback)(() => manager.endGesture(), [manager]);
488
523
  return {
489
524
  ...state,
490
525
  manager,
526
+ entries,
491
527
  undo,
492
528
  redo,
493
529
  clear,
494
530
  execute,
495
531
  executeMerged,
496
- batch
532
+ batch,
533
+ goTo,
534
+ mark,
535
+ revertTo,
536
+ beginGesture,
537
+ endGesture
497
538
  };
498
539
  }
499
540
 
500
- // src/hooks/useHistoryShortcuts.ts
541
+ // src/hooks/useHistoryDirty.ts
501
542
  var import_react7 = require("react");
543
+ var import_editor4 = require("@pixerate/editor");
544
+ function useHistoryDirty(options) {
545
+ const getValueRef = (0, import_react7.useRef)(options.getValue);
546
+ getValueRef.current = options.getValue;
547
+ const [tracker] = (0, import_react7.useState)(
548
+ () => options.tracker ?? new import_editor4.DirtyTracker(options.getValue(), {
549
+ isEqual: options.isEqual,
550
+ normalize: options.normalize
551
+ })
552
+ );
553
+ const bindingRef = (0, import_react7.useRef)(null);
554
+ (0, import_react7.useEffect)(() => {
555
+ const binding = (0, import_editor4.bindDirtyToHistory)({
556
+ history: options.history,
557
+ getValue: () => getValueRef.current(),
558
+ tracker
559
+ });
560
+ bindingRef.current = binding;
561
+ return () => {
562
+ binding.destroy();
563
+ if (bindingRef.current === binding) bindingRef.current = null;
564
+ };
565
+ }, [options.history, tracker]);
566
+ const isDirty = (0, import_react7.useSyncExternalStore)(
567
+ (0, import_react7.useCallback)((onChange) => tracker.subscribe(onChange), [tracker]),
568
+ () => tracker.dirty,
569
+ () => tracker.dirty
570
+ );
571
+ const markClean = (0, import_react7.useCallback)(
572
+ () => tracker.markClean(getValueRef.current()),
573
+ [tracker]
574
+ );
575
+ const suppress = (0, import_react7.useCallback)(
576
+ (fn) => bindingRef.current ? bindingRef.current.suppress(fn) : tracker.suppress(fn),
577
+ [tracker]
578
+ );
579
+ const refresh = (0, import_react7.useCallback)(
580
+ () => tracker.update(getValueRef.current()),
581
+ [tracker]
582
+ );
583
+ return { isDirty, markClean, suppress, refresh, tracker };
584
+ }
585
+
586
+ // src/hooks/useHistoryShortcuts.ts
587
+ var import_react8 = require("react");
588
+ var import_commands = require("@pixerate/editor/commands");
502
589
  function useHistoryShortcuts(manager, options = {}) {
503
590
  const {
504
591
  enabled = true,
505
592
  target,
506
593
  ignoreEditableElements = true
507
594
  } = options;
508
- (0, import_react7.useEffect)(() => {
595
+ (0, import_react8.useEffect)(() => {
509
596
  if (!enabled) return;
510
597
  const eventTarget = target ?? (typeof window !== "undefined" ? window : null);
511
598
  if (!eventTarget) return;
512
599
  const handleKeyDown = (event) => {
513
600
  const e = event;
514
- const isMac = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
515
- const modifier = isMac ? e.metaKey : e.ctrlKey;
516
- if (!modifier) return;
517
- if (ignoreEditableElements && typeof document !== "undefined") {
518
- const targetEl = e.target || document.activeElement;
519
- if (targetEl && targetEl.tagName) {
520
- const tagName = targetEl.tagName.toLowerCase();
521
- const isInput = tagName === "input" || tagName === "textarea";
522
- const isContentEditable = targetEl.isContentEditable || targetEl.getAttribute?.("contenteditable") === "true";
523
- if (isInput || isContentEditable) {
524
- return;
525
- }
526
- }
527
- }
528
- const key = e.key.toLowerCase();
529
- if (key === "z" && !e.shiftKey) {
530
- if (manager.getState().canUndo) {
531
- e.preventDefault();
532
- manager.undo();
533
- }
534
- return;
535
- }
536
- const isRedoKey = key === "z" && e.shiftKey || !isMac && key === "y" && !e.shiftKey;
537
- if (isRedoKey) {
538
- if (manager.getState().canRedo) {
539
- e.preventDefault();
540
- manager.redo();
541
- }
601
+ const action = (0, import_commands.matchHistoryShortcut)(e, { allowInEditable: !ignoreEditableElements });
602
+ if (action === "undo" && manager.getState().canUndo) {
603
+ e.preventDefault();
604
+ manager.undo();
605
+ } else if (action === "redo" && manager.getState().canRedo) {
606
+ e.preventDefault();
607
+ manager.redo();
542
608
  }
543
609
  };
544
610
  eventTarget.addEventListener("keydown", handleKeyDown);
@@ -549,22 +615,22 @@ function useHistoryShortcuts(manager, options = {}) {
549
615
  }
550
616
 
551
617
  // src/hooks/useFileImport.ts
552
- var import_react8 = require("react");
553
- var import_editor4 = require("@pixerate/editor");
618
+ var import_react9 = require("react");
619
+ var import_editor5 = require("@pixerate/editor");
554
620
  function useFileImport(editor, options = {}) {
555
- const editorRef = (0, import_react8.useRef)(editor);
621
+ const editorRef = (0, import_react9.useRef)(editor);
556
622
  editorRef.current = editor;
557
- const controller = (0, import_react8.useMemo)(
558
- () => new import_editor4.FileImportController(() => {
623
+ const controller = (0, import_react9.useMemo)(
624
+ () => new import_editor5.FileImportController(() => {
559
625
  const current = editorRef.current;
560
626
  return current && !current.isDestroyed ? current : null;
561
627
  }, options),
562
628
  []
563
629
  );
564
- (0, import_react8.useEffect)(() => {
630
+ (0, import_react9.useEffect)(() => {
565
631
  controller.setOptions(options);
566
632
  });
567
- const state = (0, import_react8.useSyncExternalStore)(controller.subscribe, controller.getState, controller.getState);
633
+ const state = (0, import_react9.useSyncExternalStore)(controller.subscribe, controller.getState, controller.getState);
568
634
  return {
569
635
  ...state,
570
636
  accept: controller.accept,
@@ -574,27 +640,27 @@ function useFileImport(editor, options = {}) {
574
640
  }
575
641
 
576
642
  // src/hooks/useAgentToolProvider.ts
577
- var import_react9 = require("react");
643
+ var import_react10 = require("react");
578
644
  function useAgentToolProvider(registry, provider) {
579
- (0, import_react9.useEffect)(() => {
645
+ (0, import_react10.useEffect)(() => {
580
646
  if (!registry || !provider) return;
581
647
  return registry.register(provider);
582
648
  }, [registry, provider]);
583
649
  }
584
650
 
585
651
  // src/components/EditorContent.tsx
586
- var import_react10 = require("@tiptap/react");
652
+ var import_react11 = require("@tiptap/react");
587
653
  var import_jsx_runtime = require("react/jsx-runtime");
588
654
  var EditorContent = ({
589
655
  className,
590
656
  editor,
591
657
  ...props
592
658
  }) => {
593
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react10.EditorContent, { editor, ...props }) });
659
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react11.EditorContent, { editor, ...props }) });
594
660
  };
595
661
 
596
662
  // src/components/BubbleMenu.tsx
597
- var import_react11 = require("react");
663
+ var import_react12 = require("react");
598
664
  var import_react_dom = require("react-dom");
599
665
  var import_extension_bubble_menu = require("@tiptap/extension-bubble-menu");
600
666
  var BubbleMenu = ({
@@ -606,12 +672,12 @@ var BubbleMenu = ({
606
672
  pluginKey = "bubbleMenu",
607
673
  updateDelay
608
674
  }) => {
609
- const menuEl = (0, import_react11.useRef)(null);
610
- const [mounted, setMounted] = (0, import_react11.useState)(false);
675
+ const menuEl = (0, import_react12.useRef)(null);
676
+ const [mounted, setMounted] = (0, import_react12.useState)(false);
611
677
  if (!menuEl.current && typeof document !== "undefined") {
612
678
  menuEl.current = document.createElement("div");
613
679
  }
614
- (0, import_react11.useEffect)(() => {
680
+ (0, import_react12.useEffect)(() => {
615
681
  if (!editor || !menuEl.current || editor.isDestroyed) {
616
682
  return;
617
683
  }
@@ -642,10 +708,9 @@ var BubbleMenu = ({
642
708
  };
643
709
 
644
710
  // src/components/TemplateRenderer.tsx
645
- var import_react12 = require("react");
646
- var import_editor5 = require("@pixerate/editor");
711
+ var import_react13 = require("react");
712
+ var import_editor6 = require("@pixerate/editor");
647
713
  var import_jsx_runtime2 = require("react/jsx-runtime");
648
- var CONDITIONAL_PREFIX = { if: "?", unless: "!", end: "/" };
649
714
  var TemplateRenderer = ({
650
715
  content,
651
716
  templates = [],
@@ -657,12 +722,12 @@ var TemplateRenderer = ({
657
722
  onTemplateClick,
658
723
  onVariableClick
659
724
  }) => {
660
- const displayText = (0, import_react12.useMemo)(() => {
725
+ const displayText = (0, import_react13.useMemo)(() => {
661
726
  if (!resolve || templates.length === 0) return content;
662
- return (0, import_editor5.resolveTemplates)(content, templates, { bindings });
727
+ return (0, import_editor6.resolveTemplates)(content, templates, { bindings });
663
728
  }, [content, templates, resolve, bindings]);
664
- const tokens = (0, import_react12.useMemo)(() => {
665
- return (0, import_editor5.evaluatePromptTokens)(displayText, values);
729
+ const tokens = (0, import_react13.useMemo)(() => {
730
+ return (0, import_editor6.evaluatePromptTokens)(displayText, values);
666
731
  }, [displayText, values]);
667
732
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, children: tokens.map((token, index) => {
668
733
  if (token.type === "template") {
@@ -728,8 +793,8 @@ var TemplateRenderer = ({
728
793
  {
729
794
  "data-conditional": token.conditional,
730
795
  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",
731
- 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`,
732
- children: `{${CONDITIONAL_PREFIX[token.conditional]}${token.value}}`
796
+ title: (0, import_editor6.describeConditionalTag)(token),
797
+ children: (0, import_editor6.formatConditionalTag)(token)
733
798
  },
734
799
  index
735
800
  );
@@ -750,8 +815,8 @@ var TemplateRenderer = ({
750
815
  };
751
816
 
752
817
  // src/components/VariableForm.tsx
753
- var import_react13 = require("react");
754
- var import_editor6 = require("@pixerate/editor");
818
+ var import_react14 = require("react");
819
+ var import_editor7 = require("@pixerate/editor");
755
820
  var import_jsx_runtime3 = require("react/jsx-runtime");
756
821
  var VariableForm = ({
757
822
  content,
@@ -762,19 +827,19 @@ var VariableForm = ({
762
827
  onChange,
763
828
  className = "flex flex-col gap-3 text-sm"
764
829
  }) => {
765
- const idPrefix = (0, import_react13.useId)();
766
- const [internal, setInternal] = (0, import_react13.useState)(
830
+ const idPrefix = (0, import_react14.useId)();
831
+ const [internal, setInternal] = (0, import_react14.useState)(
767
832
  () => defaultValues ?? {}
768
833
  );
769
834
  const entered = values ?? internal;
770
- const fields = (0, import_react13.useMemo)(
771
- () => (0, import_editor6.getVariableFormFields)(content, { templates, bindings }),
835
+ const fields = (0, import_react14.useMemo)(
836
+ () => (0, import_editor7.getVariableFormFields)(content, { templates, bindings }),
772
837
  [content, templates, bindings]
773
838
  );
774
839
  const update = (name, value) => {
775
840
  const next = { ...entered, [name]: value };
776
841
  if (values === void 0) setInternal(next);
777
- onChange?.((0, import_editor6.compactVariableValues)(next), next);
842
+ onChange?.((0, import_editor7.compactVariableValues)(next), next);
778
843
  };
779
844
  if (fields.length === 0) return null;
780
845
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("form", { className, onSubmit: (event) => event.preventDefault(), children: fields.map((field) => {
@@ -834,17 +899,305 @@ var VariableForm = ({
834
899
  }) });
835
900
  };
836
901
 
902
+ // src/components/MergePreview.tsx
903
+ var import_react16 = require("react");
904
+
905
+ // src/hooks/useMergePreview.ts
906
+ var import_react15 = require("react");
907
+ var import_editor8 = require("@pixerate/editor");
908
+ function useMergePreview(options) {
909
+ const { template, templates, bindings, parseOptions } = options;
910
+ const [sourceText, setSourceTextState] = (0, import_react15.useState)(options.initialSource ?? "");
911
+ const [overrides, setOverrides] = (0, import_react15.useState)({});
912
+ const [index, setIndexState] = (0, import_react15.useState)(0);
913
+ const [result, setResult] = (0, import_react15.useState)(null);
914
+ const source = (0, import_react15.useMemo)(
915
+ () => (0, import_editor8.parseDataSource)(sourceText, parseOptions),
916
+ [sourceText, parseOptions]
917
+ );
918
+ const variables = (0, import_react15.useMemo)(
919
+ () => (0, import_editor8.extractVariables)(template, { templates, bindings }).map((v) => v.name),
920
+ [template, templates, bindings]
921
+ );
922
+ const mapping = (0, import_react15.useMemo)(() => {
923
+ const merged = {
924
+ ...(0, import_editor8.matchFields)(variables, source.fields).mapping
925
+ };
926
+ for (const [variable, field] of Object.entries(overrides)) {
927
+ if (variables.includes(variable) && (field === null || source.fields.includes(field))) {
928
+ merged[variable] = field;
929
+ }
930
+ }
931
+ return merged;
932
+ }, [variables, source.fields, overrides]);
933
+ const count = source.records.length;
934
+ const current = count === 0 ? 0 : Math.min(index, count - 1);
935
+ const record = source.records[current];
936
+ const values = (0, import_react15.useMemo)(
937
+ () => record ? (0, import_editor8.recordToValues)(record, mapping) : {},
938
+ [record, mapping]
939
+ );
940
+ const setSourceText = (0, import_react15.useCallback)((text) => {
941
+ setSourceTextState(text);
942
+ setIndexState(0);
943
+ setResult(null);
944
+ }, []);
945
+ const loadFile = (0, import_react15.useCallback)(
946
+ async (file) => setSourceText(await file.text()),
947
+ [setSourceText]
948
+ );
949
+ const setIndex = (0, import_react15.useCallback)(
950
+ (next) => setIndexState(Math.max(0, Math.min(next, count - 1))),
951
+ [count]
952
+ );
953
+ const setFieldMapping = (0, import_react15.useCallback)((variable, field) => {
954
+ setOverrides((previous) => ({ ...previous, [variable]: field }));
955
+ setResult(null);
956
+ }, []);
957
+ const resetMapping = (0, import_react15.useCallback)(() => setOverrides({}), []);
958
+ const merge = (0, import_react15.useCallback)(() => {
959
+ const merged = (0, import_editor8.mergeRecords)(template, source, {
960
+ templates,
961
+ bindings,
962
+ mapping,
963
+ skipIf: options.skipIf,
964
+ separator: options.separator,
965
+ maxOutputs: options.maxOutputs
966
+ });
967
+ setResult(merged);
968
+ return merged;
969
+ }, [template, source, templates, bindings, mapping, options.skipIf, options.separator, options.maxOutputs]);
970
+ const serialize = (0, import_react15.useCallback)(
971
+ (format) => result ? (0, import_editor8.serializeMergeOutputs)(result.outputs, format, options.separator) : "",
972
+ [result, options.separator]
973
+ );
974
+ return {
975
+ sourceText,
976
+ setSourceText,
977
+ loadFile,
978
+ source,
979
+ variables,
980
+ mapping,
981
+ setFieldMapping,
982
+ resetMapping,
983
+ index: current,
984
+ count,
985
+ setIndex,
986
+ next: () => setIndex(current + 1),
987
+ prev: () => setIndex(current - 1),
988
+ record,
989
+ values,
990
+ merge,
991
+ result,
992
+ serialize
993
+ };
994
+ }
995
+
996
+ // src/components/MergePreview.tsx
997
+ var import_jsx_runtime4 = require("react/jsx-runtime");
998
+ function downloadText(text, fileName, type) {
999
+ if (typeof document === "undefined" || typeof URL.createObjectURL !== "function") {
1000
+ return;
1001
+ }
1002
+ const url = URL.createObjectURL(new Blob([text], { type }));
1003
+ const link = document.createElement("a");
1004
+ link.href = url;
1005
+ link.download = fileName;
1006
+ link.click();
1007
+ setTimeout(() => URL.revokeObjectURL(url), 0);
1008
+ }
1009
+ var MergePreview = ({
1010
+ content,
1011
+ templates,
1012
+ bindings,
1013
+ templateColorMap,
1014
+ defaultSource,
1015
+ parseOptions,
1016
+ skipIf,
1017
+ separator,
1018
+ maxOutputs,
1019
+ onMerge,
1020
+ downloadName = "merged",
1021
+ className = "flex flex-col gap-3 text-sm"
1022
+ }) => {
1023
+ const id = (0, import_react16.useId)();
1024
+ const merge = useMergePreview({
1025
+ template: content,
1026
+ templates,
1027
+ bindings,
1028
+ initialSource: defaultSource,
1029
+ parseOptions,
1030
+ skipIf,
1031
+ separator,
1032
+ maxOutputs
1033
+ });
1034
+ const [copied, setCopied] = (0, import_react16.useState)(false);
1035
+ const { source, result } = merge;
1036
+ const copy = async () => {
1037
+ try {
1038
+ await navigator.clipboard.writeText(merge.serialize("text"));
1039
+ setCopied(true);
1040
+ } catch {
1041
+ setCopied(false);
1042
+ }
1043
+ };
1044
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className, "data-merge-preview": "", children: [
1045
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { htmlFor: `${id}-source`, className: "flex flex-col gap-1", children: [
1046
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "font-medium", children: "Data source (CSV, TSV or JSON)" }),
1047
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1048
+ "textarea",
1049
+ {
1050
+ id: `${id}-source`,
1051
+ "data-merge-source": "",
1052
+ value: merge.sourceText,
1053
+ rows: 5,
1054
+ spellCheck: false,
1055
+ onChange: (event) => merge.setSourceText(event.target.value),
1056
+ className: "px-2 py-1 font-mono text-xs rounded-md border border-violet-400/40 bg-transparent"
1057
+ }
1058
+ )
1059
+ ] }),
1060
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
1061
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1062
+ "input",
1063
+ {
1064
+ type: "file",
1065
+ accept: ".csv,.tsv,.txt,.json,text/csv,application/json",
1066
+ "aria-label": "Load data source file",
1067
+ "data-merge-file": "",
1068
+ onChange: (event) => {
1069
+ const file = event.target.files?.[0];
1070
+ if (file) void merge.loadFile(file);
1071
+ }
1072
+ }
1073
+ ),
1074
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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"}` })
1075
+ ] }),
1076
+ source.warnings.length > 0 && merge.sourceText.trim() !== "" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { "data-merge-warnings": "", className: "text-amber-600 text-xs", children: source.warnings.map((warning, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: warning.message }, i)) }),
1077
+ merge.variables.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("fieldset", { className: "flex flex-col gap-1", children: [
1078
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("legend", { className: "font-medium", children: "Field mapping" }),
1079
+ merge.variables.map((variable) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1080
+ "label",
1081
+ {
1082
+ className: "flex items-center gap-2",
1083
+ "data-merge-mapping": variable,
1084
+ children: [
1085
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "min-w-24 font-mono", children: `{${variable}}` }),
1086
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1087
+ "select",
1088
+ {
1089
+ value: merge.mapping[variable] ?? "",
1090
+ "aria-label": `Field for ${variable}`,
1091
+ onChange: (event) => merge.setFieldMapping(variable, event.target.value || null),
1092
+ className: "px-1 py-0.5 rounded-md border border-violet-400/40 bg-transparent",
1093
+ children: [
1094
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "", children: "(none)" }),
1095
+ source.fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: field, children: field }, field))
1096
+ ]
1097
+ }
1098
+ )
1099
+ ]
1100
+ },
1101
+ variable
1102
+ ))
1103
+ ] }),
1104
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", children: [
1105
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1106
+ "button",
1107
+ {
1108
+ type: "button",
1109
+ "data-merge-prev": "",
1110
+ onClick: merge.prev,
1111
+ disabled: merge.index <= 0,
1112
+ children: "Prev"
1113
+ }
1114
+ ),
1115
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "data-merge-position": "", children: merge.count === 0 ? "No records" : `Record ${merge.index + 1} of ${merge.count}` }),
1116
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1117
+ "button",
1118
+ {
1119
+ type: "button",
1120
+ "data-merge-next": "",
1121
+ onClick: merge.next,
1122
+ disabled: merge.index >= merge.count - 1,
1123
+ children: "Next"
1124
+ }
1125
+ )
1126
+ ] }),
1127
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-merge-output": "", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1128
+ TemplateRenderer,
1129
+ {
1130
+ content,
1131
+ templates,
1132
+ bindings,
1133
+ templateColorMap,
1134
+ resolve: !!templates && templates.length > 0,
1135
+ values: merge.values
1136
+ }
1137
+ ) }),
1138
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
1139
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1140
+ "button",
1141
+ {
1142
+ type: "button",
1143
+ "data-merge-all": "",
1144
+ disabled: merge.count === 0,
1145
+ onClick: () => {
1146
+ const merged = merge.merge();
1147
+ setCopied(false);
1148
+ onMerge?.(merged);
1149
+ },
1150
+ children: "Merge all"
1151
+ }
1152
+ ),
1153
+ result && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1154
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "data-merge-result": "", children: `${result.outputs.length} merged${result.skipped.length ? `, ${result.skipped.length} skipped` : ""}${result.truncated ? " (stopped early)" : ""}` }),
1155
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "data-merge-copy": "", onClick: copy, children: copied ? "Copied" : "Copy" }),
1156
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1157
+ "button",
1158
+ {
1159
+ type: "button",
1160
+ "data-merge-download": "text",
1161
+ onClick: () => downloadText(merge.serialize("text"), `${downloadName}.txt`, "text/plain"),
1162
+ children: "Download .txt"
1163
+ }
1164
+ ),
1165
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1166
+ "button",
1167
+ {
1168
+ type: "button",
1169
+ "data-merge-download": "json",
1170
+ onClick: () => downloadText(merge.serialize("json"), `${downloadName}.json`, "application/json"),
1171
+ children: "Download .json"
1172
+ }
1173
+ )
1174
+ ] })
1175
+ ] }),
1176
+ result && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1177
+ "textarea",
1178
+ {
1179
+ readOnly: true,
1180
+ "aria-label": "Merged output",
1181
+ "data-merge-text": "",
1182
+ rows: 6,
1183
+ value: merge.serialize("text"),
1184
+ className: "px-2 py-1 font-mono text-xs rounded-md border border-violet-400/40 bg-transparent"
1185
+ }
1186
+ )
1187
+ ] });
1188
+ };
1189
+
837
1190
  // src/hooks/useOutline.ts
838
- var import_react14 = require("react");
839
- var import_editor7 = require("@pixerate/editor");
1191
+ var import_react17 = require("react");
1192
+ var import_editor9 = require("@pixerate/editor");
840
1193
  function useOutline(editor) {
841
- const store = (0, import_react14.useMemo)(() => (0, import_editor7.createOutlineStore)(editor), [editor]);
842
- return (0, import_react14.useSyncExternalStore)(store.subscribe, store.getSnapshot, store.getSnapshot);
1194
+ const store = (0, import_react17.useMemo)(() => (0, import_editor9.createOutlineStore)(editor), [editor]);
1195
+ return (0, import_react17.useSyncExternalStore)(store.subscribe, store.getSnapshot, store.getSnapshot);
843
1196
  }
844
1197
 
845
1198
  // src/components/OutlinePanel.tsx
846
- var import_editor8 = require("@pixerate/editor");
847
- var import_jsx_runtime4 = require("react/jsx-runtime");
1199
+ var import_editor10 = require("@pixerate/editor");
1200
+ var import_jsx_runtime5 = require("react/jsx-runtime");
848
1201
  var OutlinePanel = ({
849
1202
  editor,
850
1203
  title = "Outline",
@@ -858,11 +1211,11 @@ var OutlinePanel = ({
858
1211
  }) => {
859
1212
  const { headings, activeId } = useOutline(editor);
860
1213
  const visible = headings.filter((h) => h.level <= maxLevel);
861
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("nav", { className, "aria-label": "Document outline", "data-outline-panel": "", children: [
862
- title != null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "pixerate-outline-title", children: title }),
863
- visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "pixerate-outline-empty", children: emptyState }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { role: "list", style: { listStyle: "none", margin: 0, padding: 0 }, children: visible.map((heading) => {
1214
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("nav", { className, "aria-label": "Document outline", "data-outline-panel": "", children: [
1215
+ title != null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "pixerate-outline-title", children: title }),
1216
+ visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "pixerate-outline-empty", children: emptyState }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { role: "list", style: { listStyle: "none", margin: 0, padding: 0 }, children: visible.map((heading) => {
864
1217
  const active = heading.id === activeId;
865
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { "data-level": heading.level, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1218
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { "data-level": heading.level, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
866
1219
  "button",
867
1220
  {
868
1221
  type: "button",
@@ -872,7 +1225,7 @@ var OutlinePanel = ({
872
1225
  "data-heading-id": heading.id,
873
1226
  onClick: () => {
874
1227
  if (!editor) return;
875
- if ((0, import_editor8.navigateToHeading)(editor, heading, { focus: focusOnNavigate })) {
1228
+ if ((0, import_editor10.navigateToHeading)(editor, heading, { focus: focusOnNavigate })) {
876
1229
  onNavigate?.(heading);
877
1230
  }
878
1231
  },
@@ -884,16 +1237,16 @@ var OutlinePanel = ({
884
1237
  };
885
1238
 
886
1239
  // src/index.ts
887
- var import_editor11 = require("@pixerate/editor");
1240
+ var import_editor16 = require("@pixerate/editor");
888
1241
 
889
1242
  // src/spreadsheet/useSpreadsheetEditor.ts
890
- var import_react15 = require("react");
1243
+ var import_react18 = require("react");
891
1244
  var import_spreadsheet = require("@pixerate/editor/spreadsheet");
892
1245
  function useSpreadsheetEditor(options = {}) {
893
- const optionsRef = (0, import_react15.useRef)(options);
1246
+ const optionsRef = (0, import_react18.useRef)(options);
894
1247
  optionsRef.current = options;
895
- const settersRef = (0, import_react15.useRef)({});
896
- const [controller] = (0, import_react15.useState)(
1248
+ const settersRef = (0, import_react18.useRef)({});
1249
+ const [controller] = (0, import_react18.useState)(
897
1250
  () => new import_spreadsheet.SpreadsheetController({
898
1251
  ...options,
899
1252
  onDocumentChange: (updated) => {
@@ -910,97 +1263,97 @@ function useSpreadsheetEditor(options = {}) {
910
1263
  }
911
1264
  })
912
1265
  );
913
- const [doc, setDoc] = (0, import_react15.useState)(() => ({ ...controller.document }));
914
- const [activeCell, setActiveCell] = (0, import_react15.useState)(controller.activeCell);
915
- const [selectedRange, setSelectedRange] = (0, import_react15.useState)(controller.selectedRange);
916
- const [editingCell, setEditingCell] = (0, import_react15.useState)(null);
917
- const [draftValue, setDraftState] = (0, import_react15.useState)("");
1266
+ const [doc, setDoc] = (0, import_react18.useState)(() => ({ ...controller.document }));
1267
+ const [activeCell, setActiveCell] = (0, import_react18.useState)(controller.activeCell);
1268
+ const [selectedRange, setSelectedRange] = (0, import_react18.useState)(controller.selectedRange);
1269
+ const [editingCell, setEditingCell] = (0, import_react18.useState)(null);
1270
+ const [draftValue, setDraftState] = (0, import_react18.useState)("");
918
1271
  settersRef.current = { setDoc, setActiveCell, setSelectedRange };
919
- const setDraftValue = (0, import_react15.useCallback)((value) => {
1272
+ const setDraftValue = (0, import_react18.useCallback)((value) => {
920
1273
  controller.draftValue = value;
921
1274
  setDraftState(value);
922
1275
  }, [controller]);
923
- const setCellValue = (0, import_react15.useCallback)((rowId, colId, raw) => {
1276
+ const setCellValue = (0, import_react18.useCallback)((rowId, colId, raw) => {
924
1277
  controller.setCellValue(rowId, colId, raw);
925
1278
  setDoc({ ...controller.document });
926
1279
  }, [controller]);
927
- const selectCell = (0, import_react15.useCallback)((row, col, extendSelection = false) => {
1280
+ const selectCell = (0, import_react18.useCallback)((row, col, extendSelection = false) => {
928
1281
  controller.selectCell(row, col, extendSelection);
929
1282
  setActiveCell(controller.activeCell);
930
1283
  setSelectedRange(controller.selectedRange);
931
1284
  }, [controller]);
932
- const selectRange = (0, import_react15.useCallback)((range) => {
1285
+ const selectRange = (0, import_react18.useCallback)((range) => {
933
1286
  controller.selectRange(range);
934
1287
  setSelectedRange(controller.selectedRange);
935
1288
  }, [controller]);
936
- const startEditing = (0, import_react15.useCallback)((row, col, initialVal) => {
1289
+ const startEditing = (0, import_react18.useCallback)((row, col, initialVal) => {
937
1290
  controller.startEditing(row, col, initialVal);
938
1291
  setActiveCell(controller.activeCell);
939
1292
  setEditingCell(controller.editingCell);
940
1293
  setDraftValue(controller.draftValue);
941
1294
  }, [controller]);
942
- const commitEditing = (0, import_react15.useCallback)((val) => {
1295
+ const commitEditing = (0, import_react18.useCallback)((val) => {
943
1296
  controller.commitEditing(val);
944
1297
  setEditingCell(null);
945
1298
  setDraftValue("");
946
1299
  setDoc({ ...controller.document });
947
1300
  }, [controller]);
948
- const cancelEditing = (0, import_react15.useCallback)(() => {
1301
+ const cancelEditing = (0, import_react18.useCallback)(() => {
949
1302
  controller.cancelEditing();
950
1303
  setEditingCell(null);
951
1304
  setDraftValue("");
952
1305
  }, [controller]);
953
- const moveCursor = (0, import_react15.useCallback)((dir, extend = false) => {
1306
+ const moveCursor = (0, import_react18.useCallback)((dir, extend = false) => {
954
1307
  controller.moveCursor(dir, extend);
955
1308
  setActiveCell(controller.activeCell);
956
1309
  setSelectedRange(controller.selectedRange);
957
1310
  }, [controller]);
958
- const insertColumn = (0, import_react15.useCallback)((index, config) => {
1311
+ const insertColumn = (0, import_react18.useCallback)((index, config) => {
959
1312
  controller.insertColumn(index, config);
960
1313
  setDoc({ ...controller.document });
961
1314
  }, [controller]);
962
- const deleteColumn = (0, import_react15.useCallback)((colId) => {
1315
+ const deleteColumn = (0, import_react18.useCallback)((colId) => {
963
1316
  controller.deleteColumn(colId);
964
1317
  setDoc({ ...controller.document });
965
1318
  }, [controller]);
966
- const insertRow = (0, import_react15.useCallback)((index, config) => {
1319
+ const insertRow = (0, import_react18.useCallback)((index, config) => {
967
1320
  controller.insertRow(index, config);
968
1321
  setDoc({ ...controller.document });
969
1322
  }, [controller]);
970
- const deleteRow = (0, import_react15.useCallback)((rowId) => {
1323
+ const deleteRow = (0, import_react18.useCallback)((rowId) => {
971
1324
  controller.deleteRow(rowId);
972
1325
  setDoc({ ...controller.document });
973
1326
  }, [controller]);
974
- const setColumnWidth = (0, import_react15.useCallback)((colId, width) => {
1327
+ const setColumnWidth = (0, import_react18.useCallback)((colId, width) => {
975
1328
  controller.setColumnWidth(colId, width);
976
1329
  setDoc({ ...controller.document });
977
1330
  }, [controller]);
978
- const autoFitColumnWidth = (0, import_react15.useCallback)((colId, customWidth) => {
1331
+ const autoFitColumnWidth = (0, import_react18.useCallback)((colId, customWidth) => {
979
1332
  controller.autoFitColumnWidth(colId, customWidth);
980
1333
  setDoc({ ...controller.document });
981
1334
  }, [controller]);
982
- const registerDataSource = (0, import_react15.useCallback)((ds) => {
1335
+ const registerDataSource = (0, import_react18.useCallback)((ds) => {
983
1336
  controller.registerDataSource(ds);
984
1337
  setDoc({ ...controller.document });
985
1338
  }, [controller]);
986
- const setPrimaryDataSource = (0, import_react15.useCallback)((id) => {
1339
+ const setPrimaryDataSource = (0, import_react18.useCallback)((id) => {
987
1340
  controller.setPrimaryDataSource(id);
988
1341
  setDoc({ ...controller.document });
989
1342
  }, [controller]);
990
- const recalculateAll = (0, import_react15.useCallback)(() => {
1343
+ const recalculateAll = (0, import_react18.useCallback)(() => {
991
1344
  controller.recalculateAll();
992
1345
  setDoc({ ...controller.document });
993
1346
  }, [controller]);
994
- const undo = (0, import_react15.useCallback)(() => {
1347
+ const undo = (0, import_react18.useCallback)(() => {
995
1348
  controller.undo();
996
1349
  setDoc({ ...controller.document });
997
1350
  }, [controller]);
998
- const redo = (0, import_react15.useCallback)(() => {
1351
+ const redo = (0, import_react18.useCallback)(() => {
999
1352
  controller.redo();
1000
1353
  setDoc({ ...controller.document });
1001
1354
  }, [controller]);
1002
- const tableActions = (0, import_react15.useMemo)(() => (0, import_spreadsheet.bindSpreadsheetTableActions)(controller), [controller]);
1003
- const viewModel = (0, import_react15.useMemo)(() => controller.getViewModel(), [controller, doc]);
1355
+ const tableActions = (0, import_react18.useMemo)(() => (0, import_spreadsheet.bindSpreadsheetTableActions)(controller), [controller]);
1356
+ const viewModel = (0, import_react18.useMemo)(() => controller.getViewModel(), [controller, doc]);
1004
1357
  return {
1005
1358
  document: doc,
1006
1359
  activeCell,
@@ -1052,20 +1405,20 @@ function useSpreadsheetEditor(options = {}) {
1052
1405
  }
1053
1406
 
1054
1407
  // src/spreadsheet/SpreadsheetEditor.tsx
1055
- var import_react19 = require("react");
1408
+ var import_react22 = require("react");
1056
1409
  var import_spreadsheet5 = require("@pixerate/editor/spreadsheet");
1057
1410
 
1058
1411
  // src/spreadsheet/SpreadsheetContextMenu.tsx
1059
- var import_react16 = __toESM(require("react"), 1);
1412
+ var import_react19 = __toESM(require("react"), 1);
1060
1413
  var import_spreadsheet2 = require("@pixerate/editor/spreadsheet");
1061
- var import_jsx_runtime5 = require("react/jsx-runtime");
1414
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1062
1415
  var VIEWPORT_MARGIN = 4;
1063
1416
  var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
1064
- const menuRef = (0, import_react16.useRef)(null);
1065
- const onCloseRef = (0, import_react16.useRef)(onClose);
1417
+ const menuRef = (0, import_react19.useRef)(null);
1418
+ const onCloseRef = (0, import_react19.useRef)(onClose);
1066
1419
  onCloseRef.current = onClose;
1067
- const [position, setPosition] = (0, import_react16.useState)({ x, y });
1068
- (0, import_react16.useLayoutEffect)(() => {
1420
+ const [position, setPosition] = (0, import_react19.useState)({ x, y });
1421
+ (0, import_react19.useLayoutEffect)(() => {
1069
1422
  const menu = menuRef.current;
1070
1423
  if (!menu || typeof window === "undefined") return;
1071
1424
  const { width, height } = menu.getBoundingClientRect();
@@ -1077,7 +1430,7 @@ var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
1077
1430
  );
1078
1431
  setPosition((prev) => prev.x === next.x && prev.y === next.y ? prev : next);
1079
1432
  }, [x, y, items]);
1080
- (0, import_react16.useEffect)(() => {
1433
+ (0, import_react19.useEffect)(() => {
1081
1434
  const handlePointer = (e) => {
1082
1435
  if (!menuRef.current?.contains(e.target)) onCloseRef.current();
1083
1436
  };
@@ -1091,7 +1444,7 @@ var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
1091
1444
  window.removeEventListener("keydown", handleKey);
1092
1445
  };
1093
1446
  }, []);
1094
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1447
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1095
1448
  "div",
1096
1449
  {
1097
1450
  ref: menuRef,
@@ -1107,19 +1460,20 @@ var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
1107
1460
  },
1108
1461
  onContextMenu: (e) => e.preventDefault(),
1109
1462
  onKeyDown: (e) => e.stopPropagation(),
1110
- children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react16.default.Fragment, { children: [
1111
- item.separatorBefore && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { role: "separator", className: "my-1 h-px bg-border" }),
1112
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1463
+ children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react19.default.Fragment, { children: [
1464
+ item.separatorBefore && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { role: "separator", className: "my-1 h-px bg-border" }),
1465
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1113
1466
  "button",
1114
1467
  {
1115
1468
  type: "button",
1116
1469
  role: "menuitem",
1117
1470
  "data-action": item.id,
1471
+ "data-checked": item.checked ? "" : void 0,
1118
1472
  disabled: item.disabled,
1119
- className: "block w-full px-3 py-1 text-left hover:bg-muted disabled:cursor-default disabled:opacity-40 disabled:hover:bg-transparent",
1473
+ className: `block w-full px-3 py-1 text-left hover:bg-muted disabled:cursor-default disabled:opacity-40 disabled:hover:bg-transparent ${item.checked ? "font-semibold" : ""}`,
1120
1474
  onClick: () => {
1121
1475
  onClose();
1122
- item.run();
1476
+ (0, import_spreadsheet2.runSpreadsheetMenuItem)(item);
1123
1477
  },
1124
1478
  children: item.label
1125
1479
  }
@@ -1130,7 +1484,7 @@ var SpreadsheetContextMenu = ({ items, x, y, onClose }) => {
1130
1484
  };
1131
1485
 
1132
1486
  // src/spreadsheet/SpreadsheetGroupRow.tsx
1133
- var import_jsx_runtime6 = require("react/jsx-runtime");
1487
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1134
1488
  var formatSummary = (value) => value === null ? "\u2013" : String(value);
1135
1489
  var SpreadsheetGroupRow = ({
1136
1490
  group,
@@ -1140,8 +1494,8 @@ var SpreadsheetGroupRow = ({
1140
1494
  trailingCell
1141
1495
  }) => {
1142
1496
  const labelColId = columns.some((c) => c.column.id === groupColId) ? groupColId : columns[0]?.column.id;
1143
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("tr", { className: "border-b border-border bg-muted/40 font-semibold", "data-group-key": group.key, style: { height: 28 }, children: [
1144
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { className: "w-12 min-w-[3rem] sticky left-0 z-10 bg-muted border-r border-border text-center", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1497
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { className: "border-b border-border bg-muted/40 font-semibold", "data-group-key": group.key, style: { height: 28 }, children: [
1498
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { className: "w-12 min-w-[3rem] sticky left-0 z-10 bg-muted border-r border-border text-center", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1145
1499
  "button",
1146
1500
  {
1147
1501
  type: "button",
@@ -1157,22 +1511,22 @@ var SpreadsheetGroupRow = ({
1157
1511
  ) }),
1158
1512
  columns.map(({ column }) => {
1159
1513
  const summary = group.summaries[column.id];
1160
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { className: "border-r border-border px-2 py-1 truncate max-w-0 text-foreground", style: { maxWidth: 0, overflow: "hidden" }, children: column.id === labelColId ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "truncate", children: [
1514
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { className: "border-r border-border px-2 py-1 truncate max-w-0 text-foreground", style: { maxWidth: 0, overflow: "hidden" }, children: column.id === labelColId ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "truncate", children: [
1161
1515
  group.label,
1162
1516
  " ",
1163
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "font-normal text-muted-foreground", children: [
1517
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "font-normal text-muted-foreground", children: [
1164
1518
  "(",
1165
1519
  group.rowIndices.length,
1166
1520
  ")"
1167
1521
  ] })
1168
- ] }) : summary !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "truncate", "data-summary": column.id, children: formatSummary(summary) }) : null }, column.id);
1522
+ ] }) : summary !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate", "data-summary": column.id, children: formatSummary(summary) }) : null }, column.id);
1169
1523
  }),
1170
- trailingCell && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
1524
+ trailingCell && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
1171
1525
  ] });
1172
1526
  };
1173
1527
 
1174
1528
  // src/spreadsheet/useSpreadsheetTableInteractions.ts
1175
- var import_react17 = require("react");
1529
+ var import_react20 = require("react");
1176
1530
  var import_spreadsheet3 = require("@pixerate/editor/spreadsheet");
1177
1531
  function gridHasFocus(grid) {
1178
1532
  if (!grid || typeof document === "undefined") return false;
@@ -1181,12 +1535,12 @@ function gridHasFocus(grid) {
1181
1535
  return !(active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement);
1182
1536
  }
1183
1537
  function useSpreadsheetClipboard(gridRef, state, readOnly) {
1184
- const stateRef = (0, import_react17.useRef)(state);
1538
+ const stateRef = (0, import_react20.useRef)(state);
1185
1539
  stateRef.current = state;
1186
- const readOnlyRef = (0, import_react17.useRef)(readOnly);
1540
+ const readOnlyRef = (0, import_react20.useRef)(readOnly);
1187
1541
  readOnlyRef.current = readOnly;
1188
- const copiedRef = (0, import_react17.useRef)(false);
1189
- (0, import_react17.useEffect)(() => {
1542
+ const copiedRef = (0, import_react20.useRef)(false);
1543
+ (0, import_react20.useEffect)(() => {
1190
1544
  if (typeof document === "undefined") return;
1191
1545
  const handleCopy = (e) => {
1192
1546
  const s = stateRef.current;
@@ -1209,7 +1563,7 @@ function useSpreadsheetClipboard(gridRef, state, readOnly) {
1209
1563
  document.removeEventListener("paste", handlePaste);
1210
1564
  };
1211
1565
  }, [gridRef]);
1212
- const copyShortcutFallback = (0, import_react17.useCallback)(() => {
1566
+ const copyShortcutFallback = (0, import_react20.useCallback)(() => {
1213
1567
  copiedRef.current = false;
1214
1568
  setTimeout(() => {
1215
1569
  if (copiedRef.current) return;
@@ -1221,13 +1575,13 @@ function useSpreadsheetClipboard(gridRef, state, readOnly) {
1221
1575
  return { copyShortcutFallback };
1222
1576
  }
1223
1577
  function useFillHandle(state) {
1224
- const [drag, setDrag] = (0, import_react17.useState)(null);
1225
- const dragRef = (0, import_react17.useRef)(drag);
1578
+ const [drag, setDrag] = (0, import_react20.useState)(null);
1579
+ const dragRef = (0, import_react20.useRef)(drag);
1226
1580
  dragRef.current = drag;
1227
- const stateRef = (0, import_react17.useRef)(state);
1581
+ const stateRef = (0, import_react20.useRef)(state);
1228
1582
  stateRef.current = state;
1229
1583
  const dragging = drag !== null;
1230
- (0, import_react17.useEffect)(() => {
1584
+ (0, import_react20.useEffect)(() => {
1231
1585
  if (!dragging) return;
1232
1586
  const handleMouseUp = () => {
1233
1587
  const current = dragRef.current;
@@ -1237,13 +1591,13 @@ function useFillHandle(state) {
1237
1591
  window.addEventListener("mouseup", handleMouseUp);
1238
1592
  return () => window.removeEventListener("mouseup", handleMouseUp);
1239
1593
  }, [dragging]);
1240
- const startFill = (0, import_react17.useCallback)((e) => {
1594
+ const startFill = (0, import_react20.useCallback)((e) => {
1241
1595
  e.preventDefault();
1242
1596
  e.stopPropagation();
1243
1597
  const range = stateRef.current.selectedRange;
1244
1598
  if (range) setDrag({ source: (0, import_spreadsheet3.normalizeRange)(range), target: null });
1245
1599
  }, []);
1246
- const extendFill = (0, import_react17.useCallback)((row, col) => {
1600
+ const extendFill = (0, import_react20.useCallback)((row, col) => {
1247
1601
  const current = dragRef.current;
1248
1602
  if (!current) return;
1249
1603
  setDrag({ source: current.source, target: (0, import_spreadsheet3.computeFillTarget)(current.source, { row, col }) });
@@ -1252,24 +1606,24 @@ function useFillHandle(state) {
1252
1606
  }
1253
1607
 
1254
1608
  // src/spreadsheet/FormulaHints.tsx
1255
- var import_react18 = require("react");
1609
+ var import_react21 = require("react");
1256
1610
  var import_spreadsheet4 = require("@pixerate/editor/spreadsheet");
1257
- var import_jsx_runtime7 = require("react/jsx-runtime");
1611
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1258
1612
  var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1259
- const [caret, setCaret] = (0, import_react18.useState)(null);
1260
- const [focused, setFocused] = (0, import_react18.useState)(false);
1261
- const [selected, setSelected] = (0, import_react18.useState)(0);
1262
- const [dismissed, setDismissed] = (0, import_react18.useState)(false);
1263
- const [position, setPosition] = (0, import_react18.useState)(null);
1264
- const pendingCaret = (0, import_react18.useRef)(null);
1265
- const hints = (0, import_react18.useMemo)(
1613
+ const [caret, setCaret] = (0, import_react21.useState)(null);
1614
+ const [focused, setFocused] = (0, import_react21.useState)(false);
1615
+ const [selected, setSelected] = (0, import_react21.useState)(0);
1616
+ const [dismissed, setDismissed] = (0, import_react21.useState)(false);
1617
+ const [position, setPosition] = (0, import_react21.useState)(null);
1618
+ const pendingCaret = (0, import_react21.useRef)(null);
1619
+ const hints = (0, import_react21.useMemo)(
1266
1620
  () => (0, import_spreadsheet4.getFormulaHints)(value, caret ?? value.length, { limit }),
1267
1621
  [value, caret, limit]
1268
1622
  );
1269
1623
  const suggestions = dismissed ? [] : hints.suggestions;
1270
1624
  const selectedIndex = Math.max(0, Math.min(selected, suggestions.length - 1));
1271
1625
  const visible = focused && value.trimStart().startsWith("=") && (suggestions.length > 0 || !!hints.activeCall);
1272
- const latest = (0, import_react18.useRef)({ suggestions, selected: selectedIndex, value, caret, onAccept });
1626
+ const latest = (0, import_react21.useRef)({ suggestions, selected: selectedIndex, value, caret, onAccept });
1273
1627
  latest.current = { suggestions, selected: selectedIndex, value, caret, onAccept };
1274
1628
  const accept = (index) => {
1275
1629
  const { suggestions: list, value: current, caret: at, onAccept: commit } = latest.current;
@@ -1279,7 +1633,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1279
1633
  pendingCaret.current = next.caret;
1280
1634
  commit(next.value);
1281
1635
  };
1282
- (0, import_react18.useEffect)(() => {
1636
+ (0, import_react21.useEffect)(() => {
1283
1637
  const input = inputRef.current;
1284
1638
  if (!input) return;
1285
1639
  const syncCaret = () => setCaret(input.selectionStart ?? input.value.length);
@@ -1329,7 +1683,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1329
1683
  input.removeEventListener("keydown", onKeyDown);
1330
1684
  };
1331
1685
  }, [inputRef]);
1332
- (0, import_react18.useLayoutEffect)(() => {
1686
+ (0, import_react21.useLayoutEffect)(() => {
1333
1687
  const input = inputRef.current;
1334
1688
  const at = pendingCaret.current;
1335
1689
  if (!input || at === null || input.value !== value) return;
@@ -1338,7 +1692,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1338
1692
  setCaret(at);
1339
1693
  setSelected(0);
1340
1694
  }, [value, inputRef]);
1341
- (0, import_react18.useLayoutEffect)(() => {
1695
+ (0, import_react21.useLayoutEffect)(() => {
1342
1696
  const input = inputRef.current;
1343
1697
  if (!visible || !input) return;
1344
1698
  const rect = input.getBoundingClientRect();
@@ -1346,7 +1700,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1346
1700
  }, [visible, value, inputRef]);
1347
1701
  if (!visible) return null;
1348
1702
  const { activeCall } = hints;
1349
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1703
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1350
1704
  "div",
1351
1705
  {
1352
1706
  "data-formula-hints": "",
@@ -1362,8 +1716,8 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1362
1716
  },
1363
1717
  onMouseDown: (e) => e.preventDefault(),
1364
1718
  children: [
1365
- activeCall && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-formula-signature": "", className: "px-2 py-1.5 border-b border-border bg-muted/30", children: [
1366
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "font-mono", children: activeCall.parts.map((part, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1719
+ activeCall && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { "data-formula-signature": "", className: "px-2 py-1.5 border-b border-border bg-muted/30", children: [
1720
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "font-mono", children: activeCall.parts.map((part, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1367
1721
  "span",
1368
1722
  {
1369
1723
  "data-active": part.active ? "" : void 0,
@@ -1372,13 +1726,13 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1372
1726
  },
1373
1727
  i
1374
1728
  )) }),
1375
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mt-0.5 text-muted-foreground", children: activeCall.argument ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1376
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-semibold", children: activeCall.argument.name }),
1729
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mt-0.5 text-muted-foreground", children: activeCall.argument ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1730
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-semibold", children: activeCall.argument.name }),
1377
1731
  ": ",
1378
1732
  activeCall.argument.description
1379
1733
  ] }) : activeCall.description })
1380
1734
  ] }),
1381
- suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { role: "listbox", "aria-label": "Formula suggestions", className: "max-h-64 overflow-auto py-1 m-0 list-none p-0", children: suggestions.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1735
+ suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { role: "listbox", "aria-label": "Formula suggestions", className: "max-h-64 overflow-auto py-1 m-0 list-none p-0", children: suggestions.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1382
1736
  "li",
1383
1737
  {
1384
1738
  role: "option",
@@ -1390,8 +1744,8 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1390
1744
  accept(i);
1391
1745
  },
1392
1746
  children: [
1393
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "font-mono font-semibold text-foreground", children: s.signature }),
1394
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-muted-foreground truncate", children: s.description })
1747
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "font-mono font-semibold text-foreground", children: s.signature }),
1748
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-muted-foreground truncate", children: s.description })
1395
1749
  ]
1396
1750
  },
1397
1751
  s.name
@@ -1402,7 +1756,7 @@ var FormulaHints = ({ inputRef, value, onAccept, limit = 8 }) => {
1402
1756
  };
1403
1757
 
1404
1758
  // src/spreadsheet/SpreadsheetEditor.tsx
1405
- var import_jsx_runtime8 = require("react/jsx-runtime");
1759
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1406
1760
  var COLUMN_HEADER_HEIGHT = 28;
1407
1761
  var ROW_HEADER_WIDTH = 48;
1408
1762
  function frozenCellStyle(viewRow, viewCol) {
@@ -1429,6 +1783,19 @@ function spillBorderStyle(range, row, col) {
1429
1783
  borderRight: col === range.endCol ? line : void 0
1430
1784
  };
1431
1785
  }
1786
+ function CellText({ cell }) {
1787
+ const shown = (0, import_spreadsheet5.formatCellForDisplay)(cell);
1788
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1789
+ "span",
1790
+ {
1791
+ className: `truncate block max-w-full ${cell.error ? "text-destructive font-semibold" : ""}`,
1792
+ title: cell.error ? cell.errorMessage ?? cell.error : void 0,
1793
+ "data-numeric": shown.numeric ? "" : void 0,
1794
+ style: shown.numeric || shown.color ? { ...shown.numeric ? { textAlign: "right" } : {}, ...shown.color ? { color: (0, import_spreadsheet5.numberFormatColorToCss)(shown.color) } : {} } : void 0,
1795
+ children: shown.text
1796
+ }
1797
+ );
1798
+ }
1432
1799
  var SpreadsheetEditor = ({
1433
1800
  state: externalState,
1434
1801
  readOnly = false,
@@ -1439,23 +1806,23 @@ var SpreadsheetEditor = ({
1439
1806
  }) => {
1440
1807
  const defaultState = useSpreadsheetEditor();
1441
1808
  const state = externalState || defaultState;
1442
- const gridRef = (0, import_react19.useRef)(null);
1443
- const formulaBarRef = (0, import_react19.useRef)(null);
1444
- const cellInputRef = (0, import_react19.useRef)(null);
1445
- const [resizingColId, setResizingColId] = (0, import_react19.useState)(null);
1446
- const [hoveredColId, setHoveredColId] = (0, import_react19.useState)(null);
1447
- const [resizeStartX, setResizeStartX] = (0, import_react19.useState)(0);
1448
- const [resizeStartWidth, setResizeStartWidth] = (0, import_react19.useState)(0);
1809
+ const gridRef = (0, import_react22.useRef)(null);
1810
+ const formulaBarRef = (0, import_react22.useRef)(null);
1811
+ const cellInputRef = (0, import_react22.useRef)(null);
1812
+ const [resizingColId, setResizingColId] = (0, import_react22.useState)(null);
1813
+ const [hoveredColId, setHoveredColId] = (0, import_react22.useState)(null);
1814
+ const [resizeStartX, setResizeStartX] = (0, import_react22.useState)(0);
1815
+ const [resizeStartWidth, setResizeStartWidth] = (0, import_react22.useState)(0);
1449
1816
  const view = state.viewModel;
1450
1817
  const { copyShortcutFallback } = useSpreadsheetClipboard(gridRef, state, readOnly);
1451
1818
  const { fillTarget, startFill, extendFill } = useFillHandle(state);
1452
- const [menu, setMenu] = (0, import_react19.useState)(null);
1819
+ const [menu, setMenu] = (0, import_react22.useState)(null);
1453
1820
  const filter = state.document.table?.filter;
1454
- const filteredColIds = (0, import_react19.useMemo)(
1821
+ const filteredColIds = (0, import_react22.useMemo)(
1455
1822
  () => new Set(filter?.enabled ? filter.rules.map((r) => r.colId) : []),
1456
1823
  [filter]
1457
1824
  );
1458
- const handleCellContextMenu = (0, import_react19.useCallback)((rIdx, cIdx, e) => {
1825
+ const handleCellContextMenu = (0, import_react22.useCallback)((rIdx, cIdx, e) => {
1459
1826
  if (readOnly) return;
1460
1827
  e.preventDefault();
1461
1828
  if (state.editingCell) state.commitEditing();
@@ -1464,28 +1831,28 @@ var SpreadsheetEditor = ({
1464
1831
  }
1465
1832
  setMenu({ x: e.clientX, y: e.clientY, items: (0, import_spreadsheet5.getSpreadsheetMenuItems)(state.controller, { row: rIdx, col: cIdx }) });
1466
1833
  }, [readOnly, state]);
1467
- const activeRawValue = (0, import_react19.useMemo)(() => {
1834
+ const activeRawValue = (0, import_react22.useMemo)(() => {
1468
1835
  if (!state.activeCell) return "";
1469
1836
  const row = state.document.rows[state.activeCell.row];
1470
1837
  const col = state.document.columns[state.activeCell.col];
1471
1838
  if (!row || !col) return "";
1472
1839
  return state.getCell(row.id, col.id).raw;
1473
1840
  }, [state.activeCell, state.document]);
1474
- const activeSpill = (0, import_react19.useMemo)(
1841
+ const activeSpill = (0, import_react22.useMemo)(
1475
1842
  () => state.activeCell ? state.controller.getSpillAt(state.activeCell.row, state.activeCell.col) : null,
1476
1843
  // eslint-disable-next-line react-hooks/exhaustive-deps
1477
1844
  [state.controller, state.activeCell, state.document]
1478
1845
  );
1479
1846
  const spillFormula = activeSpill && !activeRawValue && activeSpill.key !== coordKey(state.activeCell) ? activeSpill.formula : "";
1480
- const coordLabel = (0, import_react19.useMemo)(() => {
1847
+ const coordLabel = (0, import_react22.useMemo)(() => {
1481
1848
  if (!state.activeCell) return "--";
1482
1849
  const colName = (0, import_spreadsheet5.indexToColName)(state.activeCell.col);
1483
1850
  return `${colName}${state.activeCell.row + 1}`;
1484
1851
  }, [state.activeCell]);
1485
- const [formulaDraft, setFormulaDraft] = (0, import_react19.useState)(null);
1852
+ const [formulaDraft, setFormulaDraft] = (0, import_react22.useState)(null);
1486
1853
  const activeColumn = state.activeCell ? state.document.columns[state.activeCell.col] : void 0;
1487
1854
  const formulaBarReadOnly = readOnly || !state.activeCell || !!activeColumn?.readOnly;
1488
- (0, import_react19.useEffect)(() => {
1855
+ (0, import_react22.useEffect)(() => {
1489
1856
  setFormulaDraft(null);
1490
1857
  }, [state.activeCell?.row, state.activeCell?.col]);
1491
1858
  const commitFormulaBar = () => {
@@ -1501,19 +1868,19 @@ var SpreadsheetEditor = ({
1501
1868
  }
1502
1869
  setFormulaDraft(null);
1503
1870
  };
1504
- const handleCellClick = (0, import_react19.useCallback)((rIdx, cIdx, e) => {
1871
+ const handleCellClick = (0, import_react22.useCallback)((rIdx, cIdx, e) => {
1505
1872
  if (state.editingCell && (state.editingCell.row !== rIdx || state.editingCell.col !== cIdx)) {
1506
1873
  state.commitEditing();
1507
1874
  }
1508
1875
  state.selectCell(rIdx, cIdx, e.shiftKey);
1509
1876
  }, [state]);
1510
- const handleCellDblClick = (0, import_react19.useCallback)((rIdx, cIdx) => {
1877
+ const handleCellDblClick = (0, import_react22.useCallback)((rIdx, cIdx) => {
1511
1878
  if (readOnly) return;
1512
1879
  const col = state.document.columns[cIdx];
1513
1880
  if (col?.readOnly) return;
1514
1881
  state.startEditing(rIdx, cIdx);
1515
1882
  }, [readOnly, state]);
1516
- const handleKeyDown = (0, import_react19.useCallback)((e) => {
1883
+ const handleKeyDown = (0, import_react22.useCallback)((e) => {
1517
1884
  if (state.editingCell) {
1518
1885
  if (e.key === "Enter") {
1519
1886
  e.preventDefault();
@@ -1582,11 +1949,11 @@ var SpreadsheetEditor = ({
1582
1949
  }
1583
1950
  }
1584
1951
  }, [readOnly, state, copyShortcutFallback]);
1585
- const stateRef = (0, import_react19.useRef)(state);
1952
+ const stateRef = (0, import_react22.useRef)(state);
1586
1953
  stateRef.current = state;
1587
- const onColumnResizeRef = (0, import_react19.useRef)(onColumnResize);
1954
+ const onColumnResizeRef = (0, import_react22.useRef)(onColumnResize);
1588
1955
  onColumnResizeRef.current = onColumnResize;
1589
- (0, import_react19.useEffect)(() => {
1956
+ (0, import_react22.useEffect)(() => {
1590
1957
  if (!resizingColId) return;
1591
1958
  if (typeof document !== "undefined") {
1592
1959
  document.body.style.cursor = "col-resize";
@@ -1637,7 +2004,7 @@ var SpreadsheetEditor = ({
1637
2004
  window.removeEventListener("mouseup", handleMouseUp);
1638
2005
  };
1639
2006
  }, [resizingColId, resizeStartX, resizeStartWidth]);
1640
- const measureElementContentWidth = (0, import_react19.useCallback)((el, container) => {
2007
+ const measureElementContentWidth = (0, import_react22.useCallback)((el, container) => {
1641
2008
  if (typeof document === "undefined") return 0;
1642
2009
  let maxW = el.scrollWidth || 0;
1643
2010
  const firstChild = el.firstElementChild;
@@ -1685,7 +2052,7 @@ var SpreadsheetEditor = ({
1685
2052
  }
1686
2053
  return maxW;
1687
2054
  }, []);
1688
- const autoFitColumn = (0, import_react19.useCallback)((colId, colIndex) => {
2055
+ const autoFitColumn = (0, import_react22.useCallback)((colId, colIndex) => {
1689
2056
  const col = state.document.columns.find((c) => c.id === colId);
1690
2057
  if (!col) return;
1691
2058
  let calculatedWidth = 0;
@@ -1721,12 +2088,12 @@ var SpreadsheetEditor = ({
1721
2088
  onColumnResize(updatedCol.id, updatedCol.width || 130);
1722
2089
  }
1723
2090
  }, [state, measureElementContentWidth, onColumnResize]);
1724
- const handleResizeDblClick = (0, import_react19.useCallback)((colId, colIndex, e) => {
2091
+ const handleResizeDblClick = (0, import_react22.useCallback)((colId, colIndex, e) => {
1725
2092
  e.preventDefault();
1726
2093
  e.stopPropagation();
1727
2094
  autoFitColumn(colId, colIndex);
1728
2095
  }, [autoFitColumn]);
1729
- const handleHeaderDblClick = (0, import_react19.useCallback)((colId, colIndex, e) => {
2096
+ const handleHeaderDblClick = (0, import_react22.useCallback)((colId, colIndex, e) => {
1730
2097
  const target = e.currentTarget;
1731
2098
  if (target) {
1732
2099
  const rect = target.getBoundingClientRect();
@@ -1748,7 +2115,7 @@ var SpreadsheetEditor = ({
1748
2115
  const isActiveCell = (rIdx, cIdx) => {
1749
2116
  return state.activeCell?.row === rIdx && state.activeCell?.col === cIdx;
1750
2117
  };
1751
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2118
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1752
2119
  "div",
1753
2120
  {
1754
2121
  ref: gridRef,
@@ -1758,10 +2125,10 @@ var SpreadsheetEditor = ({
1758
2125
  role: "grid",
1759
2126
  "aria-label": "Spreadsheet",
1760
2127
  children: [
1761
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2 border-b border-border bg-muted/20 px-3 py-1.5 text-xs font-mono", children: [
1762
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex h-6 min-w-[3rem] items-center justify-center rounded border border-border bg-background px-1.5 font-semibold text-foreground", children: coordLabel }),
1763
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex items-center text-muted-foreground select-none", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "italic font-serif font-bold text-sm text-primary", children: "fx" }) }),
1764
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2128
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2 border-b border-border bg-muted/20 px-3 py-1.5 text-xs font-mono", children: [
2129
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex h-6 min-w-[3rem] items-center justify-center rounded border border-border bg-background px-1.5 font-semibold text-foreground", children: coordLabel }),
2130
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex items-center text-muted-foreground select-none", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "italic font-serif font-bold text-sm text-primary", children: "fx" }) }),
2131
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1765
2132
  "input",
1766
2133
  {
1767
2134
  ref: formulaBarRef,
@@ -1797,7 +2164,7 @@ var SpreadsheetEditor = ({
1797
2164
  }
1798
2165
  }
1799
2166
  ),
1800
- !formulaBarReadOnly && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2167
+ !formulaBarReadOnly && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1801
2168
  FormulaHints,
1802
2169
  {
1803
2170
  inputRef: formulaBarRef,
@@ -1806,10 +2173,10 @@ var SpreadsheetEditor = ({
1806
2173
  }
1807
2174
  )
1808
2175
  ] }),
1809
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "relative flex-1 overflow-auto", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("table", { className: "border-collapse table-fixed w-max min-w-full", children: [
1810
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { className: "h-7 bg-muted sticky top-0 z-20 border-b border-border shadow-xs", children: [
1811
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { className: "w-12 min-w-[3rem] sticky left-0 z-30 bg-muted border-r border-border text-center text-[10px] text-muted-foreground font-medium", children: "#" }),
1812
- view.columns.map(({ column: col, index: cIdx, frozen, stickyLeft }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2176
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "relative flex-1 overflow-auto", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { className: "border-collapse table-fixed w-max min-w-full", children: [
2177
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { className: "h-7 bg-muted sticky top-0 z-20 border-b border-border shadow-xs", children: [
2178
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { className: "w-12 min-w-[3rem] sticky left-0 z-30 bg-muted border-r border-border text-center text-[10px] text-muted-foreground font-medium", children: "#" }),
2179
+ view.columns.map(({ column: col, index: cIdx, frozen, stickyLeft }) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1813
2180
  "th",
1814
2181
  {
1815
2182
  "data-col": cIdx,
@@ -1824,16 +2191,16 @@ var SpreadsheetEditor = ({
1824
2191
  onClick: () => onColumnHeaderClick?.(col),
1825
2192
  onDoubleClick: (e) => handleHeaderDblClick(col.id, cIdx, e),
1826
2193
  children: [
1827
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between gap-1 min-w-0 overflow-hidden truncate", children: [
1828
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "truncate font-semibold text-foreground text-xs min-w-0", children: col.title }),
1829
- filteredColIds.has(col.id) && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-[10px] text-primary shrink-0", title: "Filtered", "data-filtered": "true", children: "\u23F7" }),
1830
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[10px] text-muted-foreground/70 font-mono font-normal shrink-0", children: [
2194
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between gap-1 min-w-0 overflow-hidden truncate", children: [
2195
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "truncate font-semibold text-foreground text-xs min-w-0", children: col.title }),
2196
+ filteredColIds.has(col.id) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[10px] text-primary shrink-0", title: "Filtered", "data-filtered": "true", children: "\u23F7" }),
2197
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-[10px] text-muted-foreground/70 font-mono font-normal shrink-0", children: [
1831
2198
  "(",
1832
2199
  col.key,
1833
2200
  ")"
1834
2201
  ] })
1835
2202
  ] }),
1836
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2203
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1837
2204
  "div",
1838
2205
  {
1839
2206
  className: "col-resize-handle",
@@ -1850,7 +2217,7 @@ var SpreadsheetEditor = ({
1850
2217
  onDoubleClick: (e) => handleResizeDblClick(col.id, cIdx, e),
1851
2218
  role: "separator",
1852
2219
  "aria-orientation": "vertical",
1853
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2220
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1854
2221
  "div",
1855
2222
  {
1856
2223
  className: "col-resize-handle-line",
@@ -1869,19 +2236,19 @@ var SpreadsheetEditor = ({
1869
2236
  },
1870
2237
  col.id
1871
2238
  )),
1872
- onAddColumnClick && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2239
+ onAddColumnClick && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1873
2240
  "th",
1874
2241
  {
1875
2242
  className: "w-10 min-w-[2.5rem] px-1 text-center bg-muted/40 hover:bg-muted cursor-pointer transition-colors",
1876
2243
  onClick: onAddColumnClick,
1877
2244
  title: "Add Column",
1878
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-xs font-bold text-muted-foreground", children: "+" })
2245
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs font-bold text-muted-foreground", children: "+" })
1879
2246
  }
1880
2247
  )
1881
2248
  ] }) }),
1882
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tbody", { children: view.rows.map((viewRow) => {
2249
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tbody", { children: view.rows.map((viewRow) => {
1883
2250
  if (viewRow.kind === "group") {
1884
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2251
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1885
2252
  SpreadsheetGroupRow,
1886
2253
  {
1887
2254
  group: viewRow.group,
@@ -1895,14 +2262,14 @@ var SpreadsheetEditor = ({
1895
2262
  }
1896
2263
  const displayRowIndex = viewRow.index;
1897
2264
  const selection = state.selectedRange ? (0, import_spreadsheet5.normalizeRange)(state.selectedRange) : null;
1898
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2265
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1899
2266
  "tr",
1900
2267
  {
1901
2268
  "data-row": displayRowIndex,
1902
2269
  className: `border-b border-border hover:bg-muted/10 transition-colors ${viewRow.section !== "body" ? "bg-muted/30 font-semibold" : ""}`,
1903
2270
  style: { height: viewRow.row.height || 32 },
1904
2271
  children: [
1905
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2272
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1906
2273
  "td",
1907
2274
  {
1908
2275
  className: "w-12 min-w-[3rem] sticky left-0 z-10 bg-muted border-r border-border text-center text-[10px] text-muted-foreground font-mono select-none",
@@ -1923,7 +2290,7 @@ var SpreadsheetEditor = ({
1923
2290
  const isEditing = state.editingCell?.row === rIdx && state.editingCell?.col === cIdx;
1924
2291
  const inFillPreview = !!fillTarget && !selected && (0, import_spreadsheet5.rangeContains)(fillTarget, displayRowIndex, viewCol.index);
1925
2292
  const showFillHandle = !readOnly && !isEditing && !!selection && (span.merge ? (0, import_spreadsheet5.rangeContains)(span.merge, selection.endRow, selection.endCol) : selection.endRow === rIdx && selection.endCol === cIdx);
1926
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2293
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1927
2294
  "td",
1928
2295
  {
1929
2296
  "data-row": rIdx,
@@ -1942,8 +2309,8 @@ var SpreadsheetEditor = ({
1942
2309
  onContextMenu: (e) => handleCellContextMenu(rIdx, cIdx, e),
1943
2310
  onMouseOver: () => extendFill(displayRowIndex, viewCol.index),
1944
2311
  children: [
1945
- isEditing ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1946
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2312
+ isEditing ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2313
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1947
2314
  "input",
1948
2315
  {
1949
2316
  ref: cellInputRef,
@@ -1959,17 +2326,10 @@ var SpreadsheetEditor = ({
1959
2326
  autoFocus: true
1960
2327
  }
1961
2328
  ),
1962
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FormulaHints, { inputRef: cellInputRef, value: state.draftValue, onAccept: state.setDraftValue })
1963
- ] }) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1964
- "span",
1965
- {
1966
- className: `truncate block max-w-full ${cell.error ? "text-destructive font-semibold" : ""}`,
1967
- title: cell.error ? cell.errorMessage ?? cell.error : void 0,
1968
- children: cell.error || (cell.value ?? "")
1969
- }
1970
- ),
1971
- activeSpill && (0, import_spreadsheet5.rangeContains)(activeSpill.range, rIdx, cIdx) && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "aria-hidden": "true", className: "spreadsheet-spill-border", style: spillBorderStyle(activeSpill.range, rIdx, cIdx) }),
1972
- showFillHandle && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2329
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FormulaHints, { inputRef: cellInputRef, value: state.draftValue, onAccept: state.setDraftValue })
2330
+ ] }) : customCellRenderer ? customCellRenderer({ row, col, cell }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CellText, { cell }),
2331
+ activeSpill && (0, import_spreadsheet5.rangeContains)(activeSpill.range, rIdx, cIdx) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "aria-hidden": "true", className: "spreadsheet-spill-border", style: spillBorderStyle(activeSpill.range, rIdx, cIdx) }),
2332
+ showFillHandle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1973
2333
  "div",
1974
2334
  {
1975
2335
  className: "spreadsheet-fill-handle",
@@ -1984,14 +2344,14 @@ var SpreadsheetEditor = ({
1984
2344
  viewCol.column.id
1985
2345
  );
1986
2346
  }),
1987
- onAddColumnClick && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
2347
+ onAddColumnClick && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { className: "w-10 min-w-[2.5rem] bg-muted/10 border-r border-border" })
1988
2348
  ]
1989
2349
  },
1990
2350
  viewRow.row.id
1991
2351
  );
1992
2352
  }) })
1993
2353
  ] }) }),
1994
- menu && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2354
+ menu && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1995
2355
  SpreadsheetContextMenu,
1996
2356
  {
1997
2357
  items: menu.items,
@@ -2003,7 +2363,7 @@ var SpreadsheetEditor = ({
2003
2363
  }
2004
2364
  }
2005
2365
  ),
2006
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("style", { children: `
2366
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("style", { children: `
2007
2367
  .col-resize-handle:hover .col-resize-handle-line {
2008
2368
  background-color: var(--primary, #6366f1);
2009
2369
  opacity: 0.75;
@@ -2026,10 +2386,10 @@ var SpreadsheetEditor = ({
2026
2386
  };
2027
2387
 
2028
2388
  // src/slots.tsx
2029
- var import_react20 = require("react");
2030
- var import_jsx_runtime9 = require("react/jsx-runtime");
2389
+ var import_react23 = require("react");
2390
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2031
2391
  var defaultSlots = {
2032
- Button: (props) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2392
+ Button: (props) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2033
2393
  "button",
2034
2394
  {
2035
2395
  type: "button",
@@ -2039,111 +2399,128 @@ var defaultSlots = {
2039
2399
  children: props.children
2040
2400
  }
2041
2401
  ),
2042
- Tooltip: (props) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `pixerate-tooltip-container ${props.className || ""}`, title: String(props.content), children: props.children }),
2043
- NodeHeader: (props) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `pixerate-node-header flex items-center justify-between ${props.className || ""}`, children: [
2044
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2", children: [
2402
+ Tooltip: (props) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: `pixerate-tooltip-container ${props.className || ""}`, title: String(props.content), children: props.children }),
2403
+ NodeHeader: (props) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `pixerate-node-header flex items-center justify-between ${props.className || ""}`, children: [
2404
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
2045
2405
  props.icon,
2046
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-semibold", children: props.title })
2406
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-semibold", children: props.title })
2047
2407
  ] }),
2048
- props.onClose && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", onClick: props.onClose, className: "opacity-70 hover:opacity-100", children: "\xD7" })
2408
+ props.onClose && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: props.onClose, className: "opacity-70 hover:opacity-100", children: "\xD7" })
2049
2409
  ] })
2050
2410
  };
2051
- var EditorSlotsContext = (0, import_react20.createContext)(defaultSlots);
2411
+ var EditorSlotsContext = (0, import_react23.createContext)(defaultSlots);
2052
2412
  function EditorSlotsProvider({ slots, children }) {
2053
- const mergedSlots = (0, import_react20.useMemo)(() => ({ ...defaultSlots, ...slots }), [slots]);
2054
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EditorSlotsContext.Provider, { value: mergedSlots, children });
2413
+ const mergedSlots = (0, import_react23.useMemo)(() => ({ ...defaultSlots, ...slots }), [slots]);
2414
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(EditorSlotsContext.Provider, { value: mergedSlots, children });
2055
2415
  }
2056
2416
  function useEditorSlots() {
2057
- return (0, import_react20.useContext)(EditorSlotsContext);
2417
+ return (0, import_react23.useContext)(EditorSlotsContext);
2058
2418
  }
2059
2419
 
2060
2420
  // src/image-editor/useImageEditor.ts
2061
- var import_react21 = require("react");
2421
+ var import_react24 = require("react");
2062
2422
  var import_image_editor = require("@pixerate/editor/image-editor");
2063
2423
  function useImageEditor(options = {}) {
2064
- const controllerRef = (0, import_react21.useRef)(null);
2424
+ const controllerRef = (0, import_react24.useRef)(null);
2065
2425
  if (!controllerRef.current) {
2066
2426
  controllerRef.current = new import_image_editor.ImageEditorController(options);
2067
2427
  }
2068
2428
  const controller = controllerRef.current;
2069
- const [state, setState] = (0, import_react21.useState)(() => controller.getState());
2070
- const [historyTicks, setHistoryTicks] = (0, import_react21.useState)(0);
2071
- (0, import_react21.useEffect)(() => {
2429
+ const [state, setState] = (0, import_react24.useState)(() => controller.getState());
2430
+ const [historyTicks, setHistoryTicks] = (0, import_react24.useState)(0);
2431
+ const [histogram, setHistogram] = (0, import_react24.useState)(() => controller.getHistogram());
2432
+ (0, import_react24.useEffect)(() => controller.subscribeHistogram(setHistogram), [controller]);
2433
+ (0, import_react24.useEffect)(() => {
2072
2434
  const unsub = controller.subscribe((next) => {
2073
2435
  setState(next);
2074
2436
  setHistoryTicks((t) => t + 1);
2075
2437
  });
2076
2438
  return unsub;
2077
2439
  }, [controller]);
2078
- const loadedImageRef = (0, import_react21.useRef)(options.image);
2079
- (0, import_react21.useEffect)(() => {
2440
+ const loadedImageRef = (0, import_react24.useRef)(options.image);
2441
+ (0, import_react24.useEffect)(() => {
2080
2442
  if (options.image === loadedImageRef.current) return;
2081
2443
  loadedImageRef.current = options.image;
2082
2444
  if (options.image) {
2083
2445
  controller.loadImage(options.image);
2084
2446
  }
2085
2447
  }, [options.image, controller]);
2086
- const setTool = (0, import_react21.useCallback)((tool) => controller.setTool(tool), [controller]);
2087
- const setCrop = (0, import_react21.useCallback)(
2448
+ const setTool = (0, import_react24.useCallback)((tool) => controller.setTool(tool), [controller]);
2449
+ const setCrop = (0, import_react24.useCallback)(
2088
2450
  (crop, addToHistory = true) => controller.setCrop(crop, addToHistory),
2089
2451
  [controller]
2090
2452
  );
2091
- const applyCropPreset = (0, import_react21.useCallback)((ratio) => controller.applyCropPreset(ratio), [controller]);
2092
- const resetCrop = (0, import_react21.useCallback)(() => controller.resetCrop(), [controller]);
2093
- const rotate = (0, import_react21.useCallback)((deg) => controller.rotate(deg), [controller]);
2094
- const setRotation = (0, import_react21.useCallback)((deg) => controller.setRotation(deg), [controller]);
2095
- const flipHorizontal = (0, import_react21.useCallback)(() => controller.flipHorizontal(), [controller]);
2096
- const flipVertical = (0, import_react21.useCallback)(() => controller.flipVertical(), [controller]);
2097
- const setAdjustments = (0, import_react21.useCallback)(
2453
+ const applyCropPreset = (0, import_react24.useCallback)((ratio) => controller.applyCropPreset(ratio), [controller]);
2454
+ const resetCrop = (0, import_react24.useCallback)(() => controller.resetCrop(), [controller]);
2455
+ const rotate = (0, import_react24.useCallback)((deg) => controller.rotate(deg), [controller]);
2456
+ const setRotation = (0, import_react24.useCallback)((deg) => controller.setRotation(deg), [controller]);
2457
+ const flipHorizontal = (0, import_react24.useCallback)(() => controller.flipHorizontal(), [controller]);
2458
+ const flipVertical = (0, import_react24.useCallback)(() => controller.flipVertical(), [controller]);
2459
+ const setAdjustments = (0, import_react24.useCallback)(
2098
2460
  (partial, commitToHistory = true) => controller.setAdjustments(partial, commitToHistory),
2099
2461
  [controller]
2100
2462
  );
2101
- const resetAdjustments = (0, import_react21.useCallback)(() => controller.resetAdjustments(), [controller]);
2102
- const setDepthMask = (0, import_react21.useCallback)((partial) => controller.setDepthMask(partial), [controller]);
2103
- const resetDepthMask = (0, import_react21.useCallback)(() => controller.resetDepthMask(), [controller]);
2104
- const addAnnotation = (0, import_react21.useCallback)((ann) => controller.addAnnotation(ann), [controller]);
2105
- const updateAnnotation = (0, import_react21.useCallback)(
2463
+ const resetAdjustments = (0, import_react24.useCallback)(() => controller.resetAdjustments(), [controller]);
2464
+ const upgradeProcess = (0, import_react24.useCallback)(() => controller.upgradeProcess(), [controller]);
2465
+ const addAdjustmentLayer = (0, import_react24.useCallback)(
2466
+ (init, index) => controller.addAdjustmentLayer(init, index),
2467
+ [controller]
2468
+ );
2469
+ const updateAdjustmentLayer = (0, import_react24.useCallback)(
2470
+ (id, update, commitToHistory = true) => controller.updateAdjustmentLayer(id, update, commitToHistory),
2471
+ [controller]
2472
+ );
2473
+ const removeAdjustmentLayer = (0, import_react24.useCallback)((id) => controller.removeAdjustmentLayer(id), [controller]);
2474
+ const moveAdjustmentLayer = (0, import_react24.useCallback)((id, to) => controller.moveAdjustmentLayer(id, to), [controller]);
2475
+ const toggleAdjustmentLayer = (0, import_react24.useCallback)(
2476
+ (id, enabled) => controller.toggleAdjustmentLayer(id, enabled),
2477
+ [controller]
2478
+ );
2479
+ const setDepthMask = (0, import_react24.useCallback)((partial) => controller.setDepthMask(partial), [controller]);
2480
+ const resetDepthMask = (0, import_react24.useCallback)(() => controller.resetDepthMask(), [controller]);
2481
+ const addAnnotation = (0, import_react24.useCallback)((ann) => controller.addAnnotation(ann), [controller]);
2482
+ const updateAnnotation = (0, import_react24.useCallback)(
2106
2483
  (id, partial, commitToHistory = true) => controller.updateAnnotation(id, partial, commitToHistory),
2107
2484
  [controller]
2108
2485
  );
2109
- const removeAnnotation = (0, import_react21.useCallback)((id) => controller.removeAnnotation(id), [controller]);
2110
- const clearAnnotations = (0, import_react21.useCallback)(() => controller.clearAnnotations(), [controller]);
2111
- const selectAnnotation = (0, import_react21.useCallback)((id) => controller.selectAnnotation(id), [controller]);
2112
- const undo = (0, import_react21.useCallback)(() => controller.undo(), [controller]);
2113
- const redo = (0, import_react21.useCallback)(() => controller.redo(), [controller]);
2114
- const reset = (0, import_react21.useCallback)(() => controller.reset(), [controller]);
2115
- const zoomIn = (0, import_react21.useCallback)((factor) => controller.zoomIn(factor), [controller]);
2116
- const zoomOut = (0, import_react21.useCallback)((factor) => controller.zoomOut(factor), [controller]);
2117
- const resetZoom = (0, import_react21.useCallback)(() => controller.resetZoom(), [controller]);
2118
- const setZoom = (0, import_react21.useCallback)((zoom) => controller.setZoom(zoom), [controller]);
2119
- const setPan = (0, import_react21.useCallback)((pan) => controller.setPan(pan), [controller]);
2120
- const setComparing = (0, import_react21.useCallback)((comp) => controller.setComparing(comp), [controller]);
2121
- const loadImage = (0, import_react21.useCallback)((src) => controller.loadImage(src), [controller]);
2122
- const toDataURL = (0, import_react21.useCallback)((opts) => controller.toDataURL(opts), [controller]);
2123
- const toBlob = (0, import_react21.useCallback)((opts) => controller.toBlob(opts), [controller]);
2124
- const toJSON = (0, import_react21.useCallback)(() => controller.toJSON(), [controller]);
2125
- const loadJSON = (0, import_react21.useCallback)((data) => controller.loadJSON(data), [controller]);
2126
- const renderMask = (0, import_react21.useCallback)(
2486
+ const removeAnnotation = (0, import_react24.useCallback)((id) => controller.removeAnnotation(id), [controller]);
2487
+ const clearAnnotations = (0, import_react24.useCallback)(() => controller.clearAnnotations(), [controller]);
2488
+ const selectAnnotation = (0, import_react24.useCallback)((id) => controller.selectAnnotation(id), [controller]);
2489
+ const undo = (0, import_react24.useCallback)(() => controller.undo(), [controller]);
2490
+ const redo = (0, import_react24.useCallback)(() => controller.redo(), [controller]);
2491
+ const reset = (0, import_react24.useCallback)(() => controller.reset(), [controller]);
2492
+ const zoomIn = (0, import_react24.useCallback)((factor) => controller.zoomIn(factor), [controller]);
2493
+ const zoomOut = (0, import_react24.useCallback)((factor) => controller.zoomOut(factor), [controller]);
2494
+ const resetZoom = (0, import_react24.useCallback)(() => controller.resetZoom(), [controller]);
2495
+ const setZoom = (0, import_react24.useCallback)((zoom) => controller.setZoom(zoom), [controller]);
2496
+ const setPan = (0, import_react24.useCallback)((pan) => controller.setPan(pan), [controller]);
2497
+ const setComparing = (0, import_react24.useCallback)((comp) => controller.setComparing(comp), [controller]);
2498
+ const loadImage = (0, import_react24.useCallback)((src) => controller.loadImage(src), [controller]);
2499
+ const toDataURL = (0, import_react24.useCallback)((opts) => controller.toDataURL(opts), [controller]);
2500
+ const toBlob = (0, import_react24.useCallback)((opts) => controller.toBlob(opts), [controller]);
2501
+ const toJSON = (0, import_react24.useCallback)(() => controller.toJSON(), [controller]);
2502
+ const loadJSON = (0, import_react24.useCallback)((data) => controller.loadJSON(data), [controller]);
2503
+ const renderMask = (0, import_react24.useCallback)(
2127
2504
  (opts) => controller.renderMask(opts),
2128
2505
  [controller]
2129
2506
  );
2130
- const toMaskDataURL = (0, import_react21.useCallback)(
2507
+ const toMaskDataURL = (0, import_react24.useCallback)(
2131
2508
  (opts) => controller.toMaskDataURL(opts),
2132
2509
  [controller]
2133
2510
  );
2134
- const toSourceDataURL = (0, import_react21.useCallback)(
2511
+ const toSourceDataURL = (0, import_react24.useCallback)(
2135
2512
  (opts) => controller.toSourceDataURL(opts),
2136
2513
  [controller]
2137
2514
  );
2138
- const applyInpaintedImage = (0, import_react21.useCallback)(
2515
+ const applyInpaintedImage = (0, import_react24.useCallback)(
2139
2516
  (newDataUrl) => controller.applyInpaintedImage(newDataUrl),
2140
2517
  [controller]
2141
2518
  );
2142
- const applyBackgroundRemovedImage = (0, import_react21.useCallback)(
2519
+ const applyBackgroundRemovedImage = (0, import_react24.useCallback)(
2143
2520
  (newDataUrl) => controller.applyBackgroundRemovedImage(newDataUrl),
2144
2521
  [controller]
2145
2522
  );
2146
- return (0, import_react21.useMemo)(
2523
+ return (0, import_react24.useMemo)(
2147
2524
  () => ({
2148
2525
  controller,
2149
2526
  state,
@@ -2159,6 +2536,13 @@ function useImageEditor(options = {}) {
2159
2536
  flipVertical,
2160
2537
  setAdjustments,
2161
2538
  resetAdjustments,
2539
+ upgradeProcess,
2540
+ addAdjustmentLayer,
2541
+ updateAdjustmentLayer,
2542
+ removeAdjustmentLayer,
2543
+ moveAdjustmentLayer,
2544
+ toggleAdjustmentLayer,
2545
+ histogram,
2162
2546
  setDepthMask,
2163
2547
  resetDepthMask,
2164
2548
  addAnnotation,
@@ -2187,69 +2571,501 @@ function useImageEditor(options = {}) {
2187
2571
  applyBackgroundRemovedImage
2188
2572
  }),
2189
2573
  // eslint-disable-next-line react-hooks/exhaustive-deps
2190
- [controller, state, historyTicks]
2574
+ [controller, state, historyTicks, histogram]
2191
2575
  );
2192
2576
  }
2193
2577
 
2194
2578
  // src/image-editor/ImageEditor.tsx
2195
- var import_react22 = require("react");
2579
+ var import_react28 = __toESM(require("react"), 1);
2580
+ var import_image_editor6 = require("@pixerate/editor/image-editor");
2581
+
2582
+ // src/image-editor/ControlSlider.tsx
2196
2583
  var import_image_editor2 = require("@pixerate/editor/image-editor");
2197
- var import_jsx_runtime10 = require("react/jsx-runtime");
2198
- var ImageEditor = ({
2199
- editor: externalEditor,
2200
- src,
2201
- className = "",
2202
- style,
2203
- onSave,
2204
- exportOptions,
2205
- onInpaint,
2206
- onRemoveBackground,
2207
- aiHooks
2208
- }) => {
2209
- const internalEditor = useImageEditor({ image: src });
2210
- const editor = externalEditor || internalEditor;
2211
- const { state, controller } = editor;
2212
- const canvasRef = (0, import_react22.useRef)(null);
2213
- const containerRef = (0, import_react22.useRef)(null);
2214
- const [activeTab, setActiveTab] = (0, import_react22.useState)("crop");
2215
- const [activeCropRatio, setActiveCropRatio] = (0, import_react22.useState)(null);
2216
- const [inpaintPrompt, setInpaintPrompt] = (0, import_react22.useState)("");
2217
- const [inpaintMaskMode, setInpaintMaskMode] = (0, import_react22.useState)("annotations");
2218
- const [isRemovingBg, setIsRemovingBg] = (0, import_react22.useState)(false);
2219
- const [isInpainting, setIsInpainting] = (0, import_react22.useState)(false);
2220
- const [aiError, setAiError] = (0, import_react22.useState)(null);
2221
- const [aiSuccess, setAiSuccess] = (0, import_react22.useState)(null);
2222
- const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
2223
- const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
2224
- const handleRemoveBackground = async () => {
2225
- if (!effectiveOnRemoveBg) {
2226
- setAiError("No background removal hook is configured.");
2227
- return;
2228
- }
2229
- setAiError(null);
2230
- setAiSuccess(null);
2231
- setIsRemovingBg(true);
2232
- try {
2233
- const imageDataUrl = await editor.toSourceDataURL();
2234
- const result = await effectiveOnRemoveBg({ imageDataUrl });
2235
- if (typeof result === "string") {
2236
- await editor.applyBackgroundRemovedImage(result);
2237
- setAiSuccess("Background removed successfully!");
2238
- } else {
2239
- setAiSuccess("Background removal complete.");
2584
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2585
+ var ControlSlider = ({ spec, value, onChange, disabled, label }) => {
2586
+ const name = label ?? spec.label;
2587
+ const track = (0, import_image_editor2.controlTrackBackground)(spec);
2588
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "data-control-id": spec.id, children: [
2589
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2590
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: name }),
2591
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-mono", children: (0, import_image_editor2.formatControlValue)(spec, value) })
2592
+ ] }),
2593
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2594
+ "input",
2595
+ {
2596
+ type: "range",
2597
+ min: spec.min,
2598
+ max: spec.max,
2599
+ step: spec.step,
2600
+ "aria-label": name,
2601
+ disabled,
2602
+ value,
2603
+ onChange: (e) => onChange(Number(e.target.value)),
2604
+ className: "w-full accent-indigo-500 disabled:opacity-40"
2240
2605
  }
2241
- } catch (err) {
2242
- setAiError(err?.message || "Failed to remove background.");
2243
- } finally {
2244
- setIsRemovingBg(false);
2606
+ ),
2607
+ track && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "h-0.5 rounded-full opacity-70", style: { background: track } })
2608
+ ] });
2609
+ };
2610
+
2611
+ // src/image-editor/Histogram.tsx
2612
+ var import_react25 = require("react");
2613
+ var import_image_editor3 = require("@pixerate/editor/image-editor");
2614
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2615
+ var FILL = {
2616
+ luminance: "rgba(148, 163, 184, 0.45)",
2617
+ red: "rgba(239, 68, 68, 0.45)",
2618
+ green: "rgba(34, 197, 94, 0.45)",
2619
+ blue: "rgba(59, 130, 246, 0.45)"
2620
+ };
2621
+ var W = 256;
2622
+ var Histogram = ({ histogram, channel = "rgb", height = 64, className = "", style }) => {
2623
+ const channels = channel === "rgb" ? ["luminance", "red", "green", "blue"] : [channel];
2624
+ const paths = (0, import_react25.useMemo)(
2625
+ () => histogram && histogram.samples > 0 ? channels.map((c) => ({ c, d: (0, import_image_editor3.histogramPath)(histogram, c, W, height) })) : [],
2626
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2627
+ [histogram, channel, height]
2628
+ );
2629
+ const clipLow = histogram && histogram.samples ? Math.max(...histogram.shadows) / histogram.samples : 0;
2630
+ const clipHigh = histogram && histogram.samples ? Math.max(...histogram.highlights) / histogram.samples : 0;
2631
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2632
+ "svg",
2633
+ {
2634
+ viewBox: `0 0 ${W} ${height}`,
2635
+ preserveAspectRatio: "none",
2636
+ role: "img",
2637
+ "aria-label": histogram ? `Histogram of ${histogram.samples} pixels` : "Histogram (no data)",
2638
+ "data-testid": "image-editor-histogram",
2639
+ className: `w-full rounded bg-slate-950/60 ${className}`,
2640
+ style: { height, ...style },
2641
+ children: [
2642
+ paths.map(({ c, d }) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d, fill: FILL[c], stroke: "none" }, c)),
2643
+ clipLow > 5e-3 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("rect", { x: 0, y: 0, width: 3, height, fill: "#3b82f6", "data-testid": "histogram-clip-shadows" }),
2644
+ clipHigh > 5e-3 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("rect", { x: W - 3, y: 0, width: 3, height, fill: "#ef4444", "data-testid": "histogram-clip-highlights" })
2645
+ ]
2245
2646
  }
2647
+ );
2648
+ };
2649
+ function useImageHistogram(controller) {
2650
+ const [histogram, setHistogram] = (0, import_react25.useState)(() => controller.getHistogram());
2651
+ (0, import_react25.useEffect)(() => controller.subscribeHistogram(setHistogram), [controller]);
2652
+ return histogram;
2653
+ }
2654
+
2655
+ // src/image-editor/AdjustmentLayersPanel.tsx
2656
+ var import_react27 = require("react");
2657
+ var import_image_editor5 = require("@pixerate/editor/image-editor");
2658
+
2659
+ // src/image-editor/CurvesEditor.tsx
2660
+ var import_react26 = require("react");
2661
+ var import_image_editor4 = require("@pixerate/editor/image-editor");
2662
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2663
+ var BOX = 256;
2664
+ var STROKE = { master: "#e2e8f0", red: "#ef4444", green: "#22c55e", blue: "#3b82f6" };
2665
+ var CurvesEditor = ({ points, onChange, histogram, channel = "master", size = 200 }) => {
2666
+ const svgRef = (0, import_react26.useRef)(null);
2667
+ const [drag, setDrag] = (0, import_react26.useState)(null);
2668
+ const toBox = (e) => {
2669
+ const rect = svgRef.current?.getBoundingClientRect();
2670
+ if (!rect || !rect.width || !rect.height) return { x: 0, y: 0 };
2671
+ return { x: (e.clientX - rect.left) / rect.width * BOX, y: (e.clientY - rect.top) / rect.height * BOX };
2246
2672
  };
2247
- const handleInpaint = async () => {
2248
- if (!effectiveOnInpaint) {
2249
- setAiError("No inpainting hook is configured.");
2250
- return;
2673
+ const onPointerDown = (e) => {
2674
+ const { x, y } = toBox(e);
2675
+ let index = (0, import_image_editor4.hitTestCurvePoint)(points, x, y, BOX, BOX, 10);
2676
+ if (index === -1) {
2677
+ const inserted = (0, import_image_editor4.insertCurvePoint)(points, (0, import_image_editor4.boxToCurvePoint)(x, y, BOX, BOX));
2678
+ onChange(inserted.points, true);
2679
+ index = inserted.index;
2680
+ } else {
2681
+ onChange(points, true);
2251
2682
  }
2252
- if (!inpaintPrompt.trim()) {
2683
+ setDrag(index);
2684
+ e.target.setPointerCapture?.(e.pointerId);
2685
+ };
2686
+ const onPointerMove = (e) => {
2687
+ if (drag === null) return;
2688
+ onChange((0, import_image_editor4.moveCurvePoint)(points, drag, (0, import_image_editor4.boxToCurvePoint)(toBox(e).x, toBox(e).y, BOX, BOX)), false);
2689
+ };
2690
+ const onDoubleClick = (e) => {
2691
+ const { x, y } = toBox(e);
2692
+ const index = (0, import_image_editor4.hitTestCurvePoint)(points, x, y, BOX, BOX, 10);
2693
+ if (index !== -1 && points.length > 2) onChange((0, import_image_editor4.removeCurvePoint)(points, index), true);
2694
+ };
2695
+ const histChannel = channel === "master" ? "luminance" : channel;
2696
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2697
+ "svg",
2698
+ {
2699
+ ref: svgRef,
2700
+ viewBox: `0 0 ${BOX} ${BOX}`,
2701
+ width: size,
2702
+ height: size,
2703
+ role: "application",
2704
+ "aria-label": `${channel} curve`,
2705
+ "data-testid": "curves-editor",
2706
+ className: "rounded bg-slate-950/70 touch-none select-none cursor-crosshair",
2707
+ onPointerDown,
2708
+ onPointerMove,
2709
+ onPointerUp: () => setDrag(null),
2710
+ onPointerCancel: () => setDrag(null),
2711
+ onDoubleClick,
2712
+ children: [
2713
+ histogram && histogram.samples > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: (0, import_image_editor4.histogramPath)(histogram, histChannel, BOX, BOX), fill: "rgba(148, 163, 184, 0.25)" }),
2714
+ [64, 128, 192].map((v) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("g", { stroke: "rgba(148, 163, 184, 0.2)", strokeWidth: 1, children: [
2715
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("line", { x1: v, y1: 0, x2: v, y2: BOX }),
2716
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("line", { x1: 0, y1: v, x2: BOX, y2: v })
2717
+ ] }, v)),
2718
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("line", { x1: 0, y1: BOX, x2: BOX, y2: 0, stroke: "rgba(148, 163, 184, 0.35)", strokeDasharray: "4 4" }),
2719
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: (0, import_image_editor4.curveSvgPath)(points, BOX, BOX), fill: "none", stroke: STROKE[channel], strokeWidth: 2 }),
2720
+ points.map((p, i) => {
2721
+ const b = (0, import_image_editor4.curvePointToBox)(p, BOX, BOX);
2722
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2723
+ "circle",
2724
+ {
2725
+ cx: b.x,
2726
+ cy: b.y,
2727
+ r: 5,
2728
+ fill: drag === i ? STROKE[channel] : "#0f172a",
2729
+ stroke: STROKE[channel],
2730
+ strokeWidth: 2,
2731
+ "aria-label": `Point ${p.input} to ${p.output}`
2732
+ },
2733
+ i
2734
+ );
2735
+ })
2736
+ ]
2737
+ }
2738
+ );
2739
+ };
2740
+
2741
+ // src/image-editor/AdjustmentLayersPanel.tsx
2742
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2743
+ var CHANNELS = [
2744
+ { id: "master", label: "RGB" },
2745
+ { id: "red", label: "Red" },
2746
+ { id: "green", label: "Green" },
2747
+ { id: "blue", label: "Blue" }
2748
+ ];
2749
+ var TONES = [
2750
+ { id: "shadows", label: "Shadows" },
2751
+ { id: "midtones", label: "Midtones" },
2752
+ { id: "highlights", label: "Highlights" }
2753
+ ];
2754
+ var selectClass = "bg-slate-800 border border-slate-700 rounded px-1.5 py-1 text-slate-200";
2755
+ var chipClass = (on) => `px-2 py-0.5 rounded text-[10px] ${on ? "bg-indigo-600 text-white" : "bg-slate-800 text-slate-400 hover:text-slate-200"}`;
2756
+ var HueRangeStrip = ({ bounds }) => {
2757
+ const pts = Array.from({ length: 73 }, (_, i) => `${i / 72 * 360},${20 - (0, import_image_editor5.hueRangeWeight)({ bounds }, i * 5) * 18}`).join(" ");
2758
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("svg", { viewBox: "0 0 360 20", preserveAspectRatio: "none", className: "w-full h-4 rounded", "data-testid": "hue-range-strip", children: [
2759
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("linearGradient", { id: "pixerate-hue-strip", children: [0, 60, 120, 180, 240, 300, 360].map((h) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("stop", { offset: `${h / 360 * 100}%`, stopColor: `hsl(${h}, 90%, 50%)` }, h)) }) }),
2760
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("rect", { width: 360, height: 20, fill: "url(#pixerate-hue-strip)", opacity: 0.6 }),
2761
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("polyline", { points: pts, fill: "none", stroke: "#fff", strokeWidth: 1.5 })
2762
+ ] });
2763
+ };
2764
+ var AdjustmentLayersPanel = ({ controller, layers: layersProp, histogram }) => {
2765
+ const follow = layersProp === void 0;
2766
+ const [, setTick] = (0, import_react27.useState)(0);
2767
+ (0, import_react27.useEffect)(() => follow ? controller.subscribe(() => setTick((n) => n + 1)) : void 0, [controller, follow]);
2768
+ const layers = layersProp ?? controller.getAdjustmentLayers();
2769
+ const [newKind, setNewKind] = (0, import_react27.useState)("curves");
2770
+ const [selectedId, setSelectedId] = (0, import_react27.useState)(null);
2771
+ const [channel, setChannel] = (0, import_react27.useState)("master");
2772
+ const [range, setRange] = (0, import_react27.useState)(-1);
2773
+ const [tone, setTone] = (0, import_react27.useState)("midtones");
2774
+ const selected = layers.find((l) => l.id === selectedId) ?? null;
2775
+ const topFirst = [...layers].reverse();
2776
+ const setParam = (layer, path, value, commit = true) => controller.updateAdjustmentLayer(layer.id, { params: (0, import_image_editor5.setControlPath)(layer.params, path, value) }, commit);
2777
+ const slider = (layer, spec, label) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2778
+ ControlSlider,
2779
+ {
2780
+ spec,
2781
+ label,
2782
+ value: Number((0, import_image_editor5.getControlPath)(layer.params, spec.id) ?? spec.default),
2783
+ onChange: (v) => setParam(layer, spec.id, v)
2784
+ },
2785
+ spec.id
2786
+ );
2787
+ const controlsOf = (layer, filter = () => true) => import_image_editor5.ADJUSTMENT_LAYER_CONTROLS[layer.kind].filter(filter);
2788
+ const checkbox = (layer, path, label) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
2789
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2790
+ "input",
2791
+ {
2792
+ type: "checkbox",
2793
+ checked: Boolean((0, import_image_editor5.getControlPath)(layer.params, path)),
2794
+ onChange: (e) => setParam(layer, path, e.target.checked),
2795
+ className: "accent-indigo-500"
2796
+ }
2797
+ ),
2798
+ label
2799
+ ] });
2800
+ const editor = (layer) => {
2801
+ switch (layer.kind) {
2802
+ case "curves":
2803
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-2", children: [
2804
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex gap-1", role: "group", "aria-label": "Curve channel", children: CHANNELS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: chipClass(channel === c.id), onClick: () => setChannel(c.id), children: c.label }, c.id)) }),
2805
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2806
+ CurvesEditor,
2807
+ {
2808
+ points: layer.params[channel],
2809
+ channel,
2810
+ histogram,
2811
+ onChange: (points, commit) => setParam(layer, channel, points, commit)
2812
+ }
2813
+ )
2814
+ ] });
2815
+ case "levels":
2816
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-2", children: [
2817
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex gap-1", role: "group", "aria-label": "Levels channel", children: CHANNELS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: chipClass(channel === c.id), onClick: () => setChannel(c.id), children: c.label }, c.id)) }),
2818
+ controlsOf(layer, (s) => s.id.startsWith(`${channel}.`)).map((s) => slider(layer, s))
2819
+ ] });
2820
+ case "hueSaturation":
2821
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-2", children: [
2822
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-wrap gap-1", role: "group", "aria-label": "Hue range", children: [
2823
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: chipClass(range === -1), onClick: () => setRange(-1), children: "Master" }),
2824
+ import_image_editor5.HUE_RANGE_LABELS.map((name, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: chipClass(range === i), onClick: () => setRange(i), disabled: layer.params.colorize, children: name }, name))
2825
+ ] }),
2826
+ range >= 0 && !layer.params.colorize && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(HueRangeStrip, { bounds: layer.params.ranges[range].bounds }),
2827
+ controlsOf(layer, (s) => range === -1 || layer.params.colorize ? !s.id.startsWith("ranges.") : s.id.startsWith(`ranges.${range}.`)).map(
2828
+ (s) => slider(layer, s)
2829
+ ),
2830
+ range === -1 && checkbox(layer, "colorize", "Colorize")
2831
+ ] });
2832
+ case "colorBalance":
2833
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-2", children: [
2834
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex gap-1", role: "group", "aria-label": "Tone", children: TONES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: chipClass(tone === t.id), onClick: () => setTone(t.id), children: t.label }, t.id)) }),
2835
+ controlsOf(layer, (s) => s.id.startsWith(`${tone}.`)).map((s) => slider(layer, s)),
2836
+ checkbox(layer, "preserveLuminosity", "Preserve luminosity")
2837
+ ] });
2838
+ case "blackWhite":
2839
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-2", children: [
2840
+ controlsOf(layer).map((s) => slider(layer, s)),
2841
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
2842
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2843
+ "input",
2844
+ {
2845
+ type: "checkbox",
2846
+ checked: layer.params.tint !== null,
2847
+ onChange: (e) => setParam(layer, "tint", e.target.checked ? "#e1d3b3" : null),
2848
+ className: "accent-indigo-500"
2849
+ }
2850
+ ),
2851
+ "Tint",
2852
+ layer.params.tint && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2853
+ "input",
2854
+ {
2855
+ type: "color",
2856
+ "aria-label": "Tint colour",
2857
+ value: layer.params.tint,
2858
+ onChange: (e) => setParam(layer, "tint", e.target.value),
2859
+ className: "h-5 w-8 bg-transparent"
2860
+ }
2861
+ )
2862
+ ] })
2863
+ ] });
2864
+ case "photoFilter":
2865
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-2", children: [
2866
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-2 text-slate-300", children: [
2867
+ "Filter colour",
2868
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2869
+ "input",
2870
+ {
2871
+ type: "color",
2872
+ "aria-label": "Filter colour",
2873
+ value: layer.params.color,
2874
+ onChange: (e) => setParam(layer, "color", e.target.value),
2875
+ className: "h-5 w-8 bg-transparent"
2876
+ }
2877
+ )
2878
+ ] }),
2879
+ controlsOf(layer).map((s) => slider(layer, s)),
2880
+ checkbox(layer, "preserveLuminosity", "Preserve luminosity")
2881
+ ] });
2882
+ case "invert":
2883
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-slate-500 text-[11px]", children: "Invert has no settings." });
2884
+ default:
2885
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex flex-col gap-2", children: controlsOf(layer).map((s) => slider(layer, s)) });
2886
+ }
2887
+ };
2888
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-2 text-xs", "data-testid": "adjustment-layers-panel", children: [
2889
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Adjustment Layers" }) }),
2890
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex gap-1.5", children: [
2891
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2892
+ "select",
2893
+ {
2894
+ "aria-label": "New adjustment layer kind",
2895
+ className: `${selectClass} flex-1`,
2896
+ value: newKind,
2897
+ onChange: (e) => setNewKind(e.target.value),
2898
+ children: import_image_editor5.ADJUSTMENT_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: k.kind, children: k.label }, k.kind))
2899
+ }
2900
+ ),
2901
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2902
+ "button",
2903
+ {
2904
+ type: "button",
2905
+ className: "px-2 py-1 rounded bg-indigo-600 text-white hover:bg-indigo-500",
2906
+ onClick: () => {
2907
+ setSelectedId(controller.addAdjustmentLayer(newKind));
2908
+ setChannel("master");
2909
+ setRange(-1);
2910
+ },
2911
+ children: "Add Layer"
2912
+ }
2913
+ )
2914
+ ] }),
2915
+ topFirst.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("ul", { className: "flex flex-col gap-1", "aria-label": "Adjustment layers", children: topFirst.map((layer) => {
2916
+ const index = layers.indexOf(layer);
2917
+ const label = layer.name || (0, import_image_editor5.adjustmentKindLabel)(layer.kind);
2918
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2919
+ "li",
2920
+ {
2921
+ className: `flex items-center gap-1.5 rounded px-1.5 py-1 ${layer.id === selectedId ? "bg-indigo-600/20 ring-1 ring-indigo-500/50" : "bg-slate-800/60"}`,
2922
+ children: [
2923
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2924
+ "input",
2925
+ {
2926
+ type: "checkbox",
2927
+ "aria-label": `Show ${label}`,
2928
+ checked: layer.enabled,
2929
+ onChange: () => controller.toggleAdjustmentLayer(layer.id),
2930
+ className: "accent-indigo-500"
2931
+ }
2932
+ ),
2933
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2934
+ "button",
2935
+ {
2936
+ type: "button",
2937
+ className: `flex-1 text-left truncate ${layer.enabled ? "text-slate-200" : "text-slate-500 line-through"}`,
2938
+ onClick: () => setSelectedId(layer.id === selectedId ? null : layer.id),
2939
+ "aria-pressed": layer.id === selectedId,
2940
+ children: label
2941
+ }
2942
+ ),
2943
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2944
+ "button",
2945
+ {
2946
+ type: "button",
2947
+ "aria-label": `Move ${label} up`,
2948
+ disabled: index === layers.length - 1,
2949
+ onClick: () => controller.moveAdjustmentLayer(layer.id, index + 1),
2950
+ className: "px-1 text-slate-400 hover:text-slate-200 disabled:opacity-30",
2951
+ children: "\u2191"
2952
+ }
2953
+ ),
2954
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2955
+ "button",
2956
+ {
2957
+ type: "button",
2958
+ "aria-label": `Move ${label} down`,
2959
+ disabled: index === 0,
2960
+ onClick: () => controller.moveAdjustmentLayer(layer.id, index - 1),
2961
+ className: "px-1 text-slate-400 hover:text-slate-200 disabled:opacity-30",
2962
+ children: "\u2193"
2963
+ }
2964
+ ),
2965
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2966
+ "button",
2967
+ {
2968
+ type: "button",
2969
+ "aria-label": `Remove ${label}`,
2970
+ onClick: () => {
2971
+ controller.removeAdjustmentLayer(layer.id);
2972
+ if (selectedId === layer.id) setSelectedId(null);
2973
+ },
2974
+ className: "px-1 text-rose-400 hover:text-rose-300",
2975
+ children: "\u2715"
2976
+ }
2977
+ )
2978
+ ]
2979
+ },
2980
+ layer.id
2981
+ );
2982
+ }) }),
2983
+ selected && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-2 rounded border border-slate-700/60 p-2", "data-testid": "adjustment-layer-editor", children: [
2984
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2", children: [
2985
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-slate-400", children: "Blend" }),
2986
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2987
+ "select",
2988
+ {
2989
+ "aria-label": "Blend mode",
2990
+ className: `${selectClass} flex-1`,
2991
+ value: selected.blend,
2992
+ onChange: (e) => controller.updateAdjustmentLayer(selected.id, { blend: e.target.value }),
2993
+ children: import_image_editor5.BLEND_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: m.id, children: m.label }, m.id))
2994
+ }
2995
+ )
2996
+ ] }),
2997
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2998
+ ControlSlider,
2999
+ {
3000
+ spec: import_image_editor5.ADJUSTMENT_LAYER_OPACITY_CONTROL,
3001
+ label: "Layer opacity",
3002
+ value: selected.opacity,
3003
+ onChange: (v) => controller.updateAdjustmentLayer(selected.id, { opacity: v })
3004
+ }
3005
+ ),
3006
+ editor(selected)
3007
+ ] })
3008
+ ] });
3009
+ };
3010
+
3011
+ // src/image-editor/ImageEditor.tsx
3012
+ var import_jsx_runtime15 = require("react/jsx-runtime");
3013
+ var ImageEditor = ({
3014
+ editor: externalEditor,
3015
+ src,
3016
+ className = "",
3017
+ style,
3018
+ onSave,
3019
+ exportOptions,
3020
+ onInpaint,
3021
+ onRemoveBackground,
3022
+ aiHooks
3023
+ }) => {
3024
+ const internalEditor = useImageEditor({ image: src });
3025
+ const editor = externalEditor || internalEditor;
3026
+ const { state, controller } = editor;
3027
+ const legacyProcess = (0, import_image_editor6.normalizeProcessVersion)(state.adjustments.process) < import_image_editor6.LATEST_PROCESS_VERSION;
3028
+ const canvasRef = (0, import_react28.useRef)(null);
3029
+ const containerRef = (0, import_react28.useRef)(null);
3030
+ const [activeTab, setActiveTab] = (0, import_react28.useState)("crop");
3031
+ const [activeCropRatio, setActiveCropRatio] = (0, import_react28.useState)(null);
3032
+ const [inpaintPrompt, setInpaintPrompt] = (0, import_react28.useState)("");
3033
+ const [inpaintMaskMode, setInpaintMaskMode] = (0, import_react28.useState)("annotations");
3034
+ const [isRemovingBg, setIsRemovingBg] = (0, import_react28.useState)(false);
3035
+ const [isInpainting, setIsInpainting] = (0, import_react28.useState)(false);
3036
+ const [aiError, setAiError] = (0, import_react28.useState)(null);
3037
+ const [aiSuccess, setAiSuccess] = (0, import_react28.useState)(null);
3038
+ const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
3039
+ const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
3040
+ const handleRemoveBackground = async () => {
3041
+ if (!effectiveOnRemoveBg) {
3042
+ setAiError("No background removal hook is configured.");
3043
+ return;
3044
+ }
3045
+ setAiError(null);
3046
+ setAiSuccess(null);
3047
+ setIsRemovingBg(true);
3048
+ try {
3049
+ const imageDataUrl = await editor.toSourceDataURL();
3050
+ const result = await effectiveOnRemoveBg({ imageDataUrl });
3051
+ if (typeof result === "string") {
3052
+ await editor.applyBackgroundRemovedImage(result);
3053
+ setAiSuccess("Background removed successfully!");
3054
+ } else {
3055
+ setAiSuccess("Background removal complete.");
3056
+ }
3057
+ } catch (err) {
3058
+ setAiError(err?.message || "Failed to remove background.");
3059
+ } finally {
3060
+ setIsRemovingBg(false);
3061
+ }
3062
+ };
3063
+ const handleInpaint = async () => {
3064
+ if (!effectiveOnInpaint) {
3065
+ setAiError("No inpainting hook is configured.");
3066
+ return;
3067
+ }
3068
+ if (!inpaintPrompt.trim()) {
2253
3069
  setAiError("Please enter an inpainting prompt.");
2254
3070
  return;
2255
3071
  }
@@ -2280,18 +3096,18 @@ var ImageEditor = ({
2280
3096
  setIsInpainting(false);
2281
3097
  }
2282
3098
  };
2283
- const [activeColor, setActiveColor] = (0, import_react22.useState)("#ef4444");
2284
- const [strokeWidth, setStrokeWidth] = (0, import_react22.useState)(3);
2285
- const [fontSize, setFontSize] = (0, import_react22.useState)(24);
2286
- const [textInput, setTextInput] = (0, import_react22.useState)("");
2287
- const draftAnnotationRef = (0, import_react22.useRef)(null);
2288
- const draftCropRef = (0, import_react22.useRef)(null);
2289
- const cropDragInfoRef = (0, import_react22.useRef)(null);
2290
- const annotationDragInfoRef = (0, import_react22.useRef)(null);
2291
- const isDrawingRef = (0, import_react22.useRef)(false);
2292
- const currentPathRef = (0, import_react22.useRef)([]);
2293
- const dragStartPointRef = (0, import_react22.useRef)(null);
2294
- const renderCanvas = (0, import_react22.useCallback)(
3099
+ const [activeColor, setActiveColor] = (0, import_react28.useState)("#ef4444");
3100
+ const [strokeWidth, setStrokeWidth] = (0, import_react28.useState)(3);
3101
+ const [fontSize, setFontSize] = (0, import_react28.useState)(24);
3102
+ const [textInput, setTextInput] = (0, import_react28.useState)("");
3103
+ const draftAnnotationRef = (0, import_react28.useRef)(null);
3104
+ const draftCropRef = (0, import_react28.useRef)(null);
3105
+ const cropDragInfoRef = (0, import_react28.useRef)(null);
3106
+ const annotationDragInfoRef = (0, import_react28.useRef)(null);
3107
+ const isDrawingRef = (0, import_react28.useRef)(false);
3108
+ const currentPathRef = (0, import_react28.useRef)([]);
3109
+ const dragStartPointRef = (0, import_react28.useRef)(null);
3110
+ const renderCanvas = (0, import_react28.useCallback)(
2295
3111
  async (draftAnnotation, draftCrop) => {
2296
3112
  const canvas = canvasRef.current;
2297
3113
  const container = containerRef.current;
@@ -2310,7 +3126,7 @@ var ImageEditor = ({
2310
3126
  },
2311
3127
  [controller, activeTab]
2312
3128
  );
2313
- (0, import_react22.useEffect)(() => {
3129
+ (0, import_react28.useEffect)(() => {
2314
3130
  const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : (cb) => setTimeout(cb, 0);
2315
3131
  const caf = typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame : (id) => clearTimeout(id);
2316
3132
  const animId = raf(() => {
@@ -2318,12 +3134,12 @@ var ImageEditor = ({
2318
3134
  });
2319
3135
  return () => caf(animId);
2320
3136
  }, [renderCanvas, state]);
2321
- (0, import_react22.useEffect)(() => {
3137
+ (0, import_react28.useEffect)(() => {
2322
3138
  if (activeTab === "crop" && editor.state.activeTool !== "crop") {
2323
3139
  editor.setTool("crop");
2324
3140
  }
2325
3141
  }, [activeTab, editor]);
2326
- (0, import_react22.useEffect)(() => {
3142
+ (0, import_react28.useEffect)(() => {
2327
3143
  const container = containerRef.current;
2328
3144
  if (!container) return;
2329
3145
  if (typeof ResizeObserver !== "undefined") {
@@ -2360,8 +3176,8 @@ var ImageEditor = ({
2360
3176
  width: state.imageDimensions.width,
2361
3177
  height: state.imageDimensions.height
2362
3178
  };
2363
- const handle = (0, import_image_editor2.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
2364
- const startImgPt = (0, import_image_editor2.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
3179
+ const handle = (0, import_image_editor6.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
3180
+ const startImgPt = (0, import_image_editor6.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
2365
3181
  if (handle) {
2366
3182
  cropDragInfoRef.current = {
2367
3183
  handle,
@@ -2380,12 +3196,12 @@ var ImageEditor = ({
2380
3196
  return;
2381
3197
  }
2382
3198
  if (activeTab === "annotate") {
2383
- const imgPt = (0, import_image_editor2.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
3199
+ const imgPt = (0, import_image_editor6.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
2384
3200
  dragStartPointRef.current = imgPt;
2385
3201
  if (state.activeTool === "select") {
2386
3202
  for (let i = state.annotations.length - 1; i >= 0; i--) {
2387
3203
  const ann = state.annotations[i];
2388
- if ((0, import_image_editor2.hitTestAnnotation)(imgPt, ann)) {
3204
+ if ((0, import_image_editor6.hitTestAnnotation)(imgPt, ann)) {
2389
3205
  editor.selectAnnotation(ann.id);
2390
3206
  annotationDragInfoRef.current = {
2391
3207
  annId: ann.id,
@@ -2422,7 +3238,7 @@ var ImageEditor = ({
2422
3238
  if (activeTab === "crop") {
2423
3239
  const drag = cropDragInfoRef.current;
2424
3240
  if (drag) {
2425
- const currentImgPt = (0, import_image_editor2.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
3241
+ const currentImgPt = (0, import_image_editor6.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, true);
2426
3242
  let newCrop;
2427
3243
  if (drag.handle === "new") {
2428
3244
  const minX = Math.max(0, Math.min(drag.startImgPt.x, currentImgPt.x));
@@ -2436,7 +3252,7 @@ var ImageEditor = ({
2436
3252
  height: Math.round(Math.max(20, maxY - minY))
2437
3253
  };
2438
3254
  } else {
2439
- newCrop = (0, import_image_editor2.calculateCropDrag)(
3255
+ newCrop = (0, import_image_editor6.calculateCropDrag)(
2440
3256
  drag.startCrop,
2441
3257
  drag.handle,
2442
3258
  drag.startImgPt,
@@ -2456,15 +3272,15 @@ var ImageEditor = ({
2456
3272
  width: state.imageDimensions.width,
2457
3273
  height: state.imageDimensions.height
2458
3274
  };
2459
- const handle = (0, import_image_editor2.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
2460
- canvas.style.cursor = (0, import_image_editor2.getCropCursor)(handle, state.transform);
3275
+ const handle = (0, import_image_editor6.hitTestCrop)(canvasPt, state, canvas.width, canvas.height, 14, activeCrop);
3276
+ canvas.style.cursor = (0, import_image_editor6.getCropCursor)(handle, state.transform);
2461
3277
  }
2462
3278
  return;
2463
3279
  }
2464
3280
  if (activeTab === "annotate") {
2465
3281
  if (annotationDragInfoRef.current) {
2466
3282
  const drag = annotationDragInfoRef.current;
2467
- const currentImgPt2 = (0, import_image_editor2.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
3283
+ const currentImgPt2 = (0, import_image_editor6.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
2468
3284
  const dx = currentImgPt2.x - drag.startImgPt.x;
2469
3285
  const dy = currentImgPt2.y - drag.startImgPt.y;
2470
3286
  editor.updateAnnotation(drag.annId, {
@@ -2476,10 +3292,10 @@ var ImageEditor = ({
2476
3292
  const startPt = dragStartPointRef.current;
2477
3293
  if (!startPt) {
2478
3294
  if (state.activeTool === "select") {
2479
- const imgPt = (0, import_image_editor2.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
3295
+ const imgPt = (0, import_image_editor6.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
2480
3296
  let hovered = false;
2481
3297
  for (let i = state.annotations.length - 1; i >= 0; i--) {
2482
- if ((0, import_image_editor2.hitTestAnnotation)(imgPt, state.annotations[i])) {
3298
+ if ((0, import_image_editor6.hitTestAnnotation)(imgPt, state.annotations[i])) {
2483
3299
  hovered = true;
2484
3300
  break;
2485
3301
  }
@@ -2490,7 +3306,7 @@ var ImageEditor = ({
2490
3306
  }
2491
3307
  return;
2492
3308
  }
2493
- const currentImgPt = (0, import_image_editor2.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
3309
+ const currentImgPt = (0, import_image_editor6.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
2494
3310
  if (state.activeTool === "pen" && isDrawingRef.current) {
2495
3311
  currentPathRef.current.push(currentImgPt);
2496
3312
  const draft = {
@@ -2585,7 +3401,7 @@ var ImageEditor = ({
2585
3401
  }
2586
3402
  const startPt = dragStartPointRef.current;
2587
3403
  if (startPt) {
2588
- const currentImgPt = (0, import_image_editor2.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
3404
+ const currentImgPt = (0, import_image_editor6.canvasToImagePoint)(canvasPt, state, canvas.width, canvas.height, false);
2589
3405
  if (state.activeTool === "pen" && isDrawingRef.current) {
2590
3406
  if (currentPathRef.current.length > 1) {
2591
3407
  editor.addAnnotation({
@@ -2681,15 +3497,15 @@ var ImageEditor = ({
2681
3497
  link.click();
2682
3498
  }
2683
3499
  };
2684
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3500
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2685
3501
  "div",
2686
3502
  {
2687
3503
  className: `flex flex-col bg-slate-900 border border-slate-800 rounded-xl overflow-hidden shadow-2xl text-slate-100 ${className}`,
2688
3504
  style,
2689
3505
  children: [
2690
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-wrap items-center justify-between px-4 py-3 bg-slate-950/80 border-b border-slate-800 gap-3", children: [
2691
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-1 bg-slate-900 p-1 rounded-lg border border-slate-800 text-xs", children: [
2692
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3506
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap items-center justify-between px-4 py-3 bg-slate-950/80 border-b border-slate-800 gap-3", children: [
3507
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-1 bg-slate-900 p-1 rounded-lg border border-slate-800 text-xs", children: [
3508
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2693
3509
  "button",
2694
3510
  {
2695
3511
  type: "button",
@@ -2701,7 +3517,7 @@ var ImageEditor = ({
2701
3517
  children: "Transform & Crop"
2702
3518
  }
2703
3519
  ),
2704
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3520
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2705
3521
  "button",
2706
3522
  {
2707
3523
  type: "button",
@@ -2713,7 +3529,7 @@ var ImageEditor = ({
2713
3529
  children: "Adjustments"
2714
3530
  }
2715
3531
  ),
2716
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3532
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2717
3533
  "button",
2718
3534
  {
2719
3535
  type: "button",
@@ -2725,7 +3541,7 @@ var ImageEditor = ({
2725
3541
  children: "Annotations"
2726
3542
  }
2727
3543
  ),
2728
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3544
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2729
3545
  "button",
2730
3546
  {
2731
3547
  type: "button",
@@ -2737,7 +3553,7 @@ var ImageEditor = ({
2737
3553
  children: "Depth Mask"
2738
3554
  }
2739
3555
  ),
2740
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3556
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2741
3557
  "button",
2742
3558
  {
2743
3559
  type: "button",
@@ -2747,14 +3563,14 @@ var ImageEditor = ({
2747
3563
  },
2748
3564
  className: `px-3 py-1.5 rounded-md font-medium transition-colors flex items-center gap-1.5 ${activeTab === "ai" ? "bg-indigo-600 text-white shadow" : "text-slate-400 hover:text-slate-200"}`,
2749
3565
  children: [
2750
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
3566
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
2751
3567
  "AI Tools"
2752
3568
  ]
2753
3569
  }
2754
3570
  )
2755
3571
  ] }),
2756
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
2757
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3572
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
3573
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2758
3574
  "button",
2759
3575
  {
2760
3576
  type: "button",
@@ -2765,7 +3581,7 @@ var ImageEditor = ({
2765
3581
  children: "\u21A9"
2766
3582
  }
2767
3583
  ),
2768
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3584
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2769
3585
  "button",
2770
3586
  {
2771
3587
  type: "button",
@@ -2776,7 +3592,7 @@ var ImageEditor = ({
2776
3592
  children: "\u21AA"
2777
3593
  }
2778
3594
  ),
2779
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3595
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2780
3596
  "button",
2781
3597
  {
2782
3598
  type: "button",
@@ -2785,8 +3601,8 @@ var ImageEditor = ({
2785
3601
  children: "Reset"
2786
3602
  }
2787
3603
  ),
2788
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2789
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3604
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
3605
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2790
3606
  "button",
2791
3607
  {
2792
3608
  type: "button",
@@ -2796,11 +3612,11 @@ var ImageEditor = ({
2796
3612
  children: "-"
2797
3613
  }
2798
3614
  ),
2799
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
3615
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "font-mono text-slate-400 w-10 text-center", children: [
2800
3616
  Math.round(state.zoom * 100),
2801
3617
  "%"
2802
3618
  ] }),
2803
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3619
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2804
3620
  "button",
2805
3621
  {
2806
3622
  type: "button",
@@ -2810,8 +3626,8 @@ var ImageEditor = ({
2810
3626
  children: "+"
2811
3627
  }
2812
3628
  ),
2813
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
2814
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3629
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "h-4 w-px bg-slate-800 mx-1" }),
3630
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2815
3631
  "button",
2816
3632
  {
2817
3633
  type: "button",
@@ -2822,7 +3638,7 @@ var ImageEditor = ({
2822
3638
  children: "Hold Compare"
2823
3639
  }
2824
3640
  ),
2825
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3641
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2826
3642
  "button",
2827
3643
  {
2828
3644
  type: "button",
@@ -2833,13 +3649,13 @@ var ImageEditor = ({
2833
3649
  )
2834
3650
  ] })
2835
3651
  ] }),
2836
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
2837
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3652
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col md:flex-row flex-1 min-h-[520px] md:h-[560px]", children: [
3653
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2838
3654
  "div",
2839
3655
  {
2840
3656
  ref: containerRef,
2841
3657
  className: "relative flex-1 h-[360px] md:h-full min-h-[320px] bg-slate-950 overflow-hidden select-none",
2842
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3658
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2843
3659
  "canvas",
2844
3660
  {
2845
3661
  ref: canvasRef,
@@ -2852,11 +3668,11 @@ var ImageEditor = ({
2852
3668
  )
2853
3669
  }
2854
3670
  ),
2855
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "w-full md:w-80 bg-slate-900/90 border-t md:border-t-0 md:border-l border-slate-800 p-4 overflow-y-auto h-auto md:h-full md:max-h-[560px]", children: [
2856
- activeTab === "crop" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
2857
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
2858
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [
2859
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3671
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "w-full md:w-80 bg-slate-900/90 border-t md:border-t-0 md:border-l border-slate-800 p-4 overflow-y-auto h-auto md:h-full md:max-h-[560px]", children: [
3672
+ activeTab === "crop" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
3673
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Aspect Ratio Presets" }),
3674
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [
3675
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2860
3676
  "button",
2861
3677
  {
2862
3678
  type: "button",
@@ -2868,7 +3684,7 @@ var ImageEditor = ({
2868
3684
  children: "Freeform"
2869
3685
  }
2870
3686
  ),
2871
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3687
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2872
3688
  "button",
2873
3689
  {
2874
3690
  type: "button",
@@ -2880,7 +3696,7 @@ var ImageEditor = ({
2880
3696
  children: "1:1 Square"
2881
3697
  }
2882
3698
  ),
2883
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3699
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2884
3700
  "button",
2885
3701
  {
2886
3702
  type: "button",
@@ -2892,7 +3708,7 @@ var ImageEditor = ({
2892
3708
  children: "4:3 Standard"
2893
3709
  }
2894
3710
  ),
2895
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3711
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2896
3712
  "button",
2897
3713
  {
2898
3714
  type: "button",
@@ -2904,7 +3720,7 @@ var ImageEditor = ({
2904
3720
  children: "16:9 Wide"
2905
3721
  }
2906
3722
  ),
2907
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3723
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2908
3724
  "button",
2909
3725
  {
2910
3726
  type: "button",
@@ -2916,7 +3732,7 @@ var ImageEditor = ({
2916
3732
  children: "3:2 Photo"
2917
3733
  }
2918
3734
  ),
2919
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3735
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2920
3736
  "button",
2921
3737
  {
2922
3738
  type: "button",
@@ -2929,10 +3745,10 @@ var ImageEditor = ({
2929
3745
  }
2930
3746
  )
2931
3747
  ] }),
2932
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
2933
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
2934
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
2935
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3748
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
3749
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Orientation & Flips" }),
3750
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
3751
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2936
3752
  "button",
2937
3753
  {
2938
3754
  type: "button",
@@ -2941,7 +3757,7 @@ var ImageEditor = ({
2941
3757
  children: "Rotate -90\xB0"
2942
3758
  }
2943
3759
  ),
2944
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3760
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2945
3761
  "button",
2946
3762
  {
2947
3763
  type: "button",
@@ -2950,7 +3766,7 @@ var ImageEditor = ({
2950
3766
  children: "Rotate +90\xB0"
2951
3767
  }
2952
3768
  ),
2953
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3769
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2954
3770
  "button",
2955
3771
  {
2956
3772
  type: "button",
@@ -2959,7 +3775,7 @@ var ImageEditor = ({
2959
3775
  children: "Flip Horizontal"
2960
3776
  }
2961
3777
  ),
2962
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3778
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2963
3779
  "button",
2964
3780
  {
2965
3781
  type: "button",
@@ -2970,10 +3786,10 @@ var ImageEditor = ({
2970
3786
  )
2971
3787
  ] })
2972
3788
  ] }),
2973
- activeTab === "adjust" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
2974
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between", children: [
2975
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
2976
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3789
+ activeTab === "adjust" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
3790
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
3791
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Color Adjustments" }),
3792
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2977
3793
  "button",
2978
3794
  {
2979
3795
  type: "button",
@@ -2983,136 +3799,49 @@ var ImageEditor = ({
2983
3799
  }
2984
3800
  )
2985
3801
  ] }),
2986
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
2987
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
2988
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Brightness" }),
2989
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono", children: state.adjustments.brightness })
2990
- ] }),
2991
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2992
- "input",
2993
- {
2994
- type: "range",
2995
- min: "-100",
2996
- max: "100",
2997
- value: state.adjustments.brightness,
2998
- onChange: (e) => editor.setAdjustments({ brightness: Number(e.target.value) }),
2999
- className: "w-full accent-indigo-500"
3000
- }
3001
- )
3002
- ] }),
3003
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3004
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
3005
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Contrast" }),
3006
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono", children: state.adjustments.contrast })
3007
- ] }),
3008
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3009
- "input",
3802
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Histogram, { histogram: editor.histogram }),
3803
+ import_image_editor6.IMAGE_ADJUSTMENT_CONTROLS.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react28.default.Fragment, { children: [
3804
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3805
+ ControlSlider,
3010
3806
  {
3011
- type: "range",
3012
- min: "-100",
3013
- max: "100",
3014
- value: state.adjustments.contrast,
3015
- onChange: (e) => editor.setAdjustments({ contrast: Number(e.target.value) }),
3016
- className: "w-full accent-indigo-500"
3807
+ spec,
3808
+ value: (0, import_image_editor6.readAdjustmentControls)(state.adjustments)[spec.id],
3809
+ disabled: spec.requiresProcess !== void 0 && legacyProcess,
3810
+ onChange: (v) => editor.setAdjustments({ [spec.id]: v })
3017
3811
  }
3018
- )
3019
- ] }),
3020
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3021
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
3022
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Saturation" }),
3023
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono", children: state.adjustments.saturation })
3024
- ] }),
3025
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3026
- "input",
3027
- {
3028
- type: "range",
3029
- min: "-100",
3030
- max: "100",
3031
- value: state.adjustments.saturation,
3032
- onChange: (e) => editor.setAdjustments({ saturation: Number(e.target.value) }),
3033
- className: "w-full accent-indigo-500"
3034
- }
3035
- )
3036
- ] }),
3037
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3038
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
3039
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Exposure" }),
3040
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono", children: state.adjustments.exposure })
3041
- ] }),
3042
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3043
- "input",
3044
- {
3045
- type: "range",
3046
- min: "-100",
3047
- max: "100",
3048
- value: state.adjustments.exposure,
3049
- onChange: (e) => editor.setAdjustments({ exposure: Number(e.target.value) }),
3050
- className: "w-full accent-indigo-500"
3051
- }
3052
- )
3053
- ] }),
3054
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3055
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
3056
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Warmth / Temperature" }),
3057
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono", children: state.adjustments.temperature })
3058
- ] }),
3059
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3060
- "input",
3061
- {
3062
- type: "range",
3063
- min: "-100",
3064
- max: "100",
3065
- value: state.adjustments.temperature,
3066
- onChange: (e) => editor.setAdjustments({ temperature: Number(e.target.value) }),
3067
- className: "w-full accent-indigo-500"
3068
- }
3069
- )
3070
- ] }),
3071
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3072
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
3073
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Blur" }),
3074
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "font-mono", children: [
3075
- state.adjustments.blur,
3076
- "px"
3077
- ] })
3078
- ] }),
3079
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3080
- "input",
3081
- {
3082
- type: "range",
3083
- min: "0",
3084
- max: "30",
3085
- value: state.adjustments.blur,
3086
- onChange: (e) => editor.setAdjustments({ blur: Number(e.target.value) }),
3087
- className: "w-full accent-indigo-500"
3088
- }
3089
- )
3090
- ] }),
3091
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3092
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
3093
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Opacity" }),
3094
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "font-mono", children: [
3095
- Math.round(state.adjustments.opacity * 100),
3096
- "%"
3097
- ] })
3098
- ] }),
3099
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3100
- "input",
3812
+ ),
3813
+ spec.id === "tint" && legacyProcess && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3814
+ "div",
3101
3815
  {
3102
- type: "range",
3103
- min: "0",
3104
- max: "1",
3105
- step: "0.05",
3106
- value: state.adjustments.opacity,
3107
- onChange: (e) => editor.setAdjustments({ opacity: Number(e.target.value) }),
3108
- className: "w-full accent-indigo-500"
3816
+ className: "flex items-center justify-between gap-2 rounded border border-amber-500/30 bg-amber-500/10 px-2 py-1.5 text-[10px] text-amber-200",
3817
+ "data-testid": "image-editor-process-notice",
3818
+ children: [
3819
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Legacy rendering (process 1). Tint needs the current process." }),
3820
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3821
+ "button",
3822
+ {
3823
+ type: "button",
3824
+ onClick: () => editor.upgradeProcess(),
3825
+ className: "shrink-0 rounded bg-amber-500/20 px-2 py-0.5 hover:bg-amber-500/30",
3826
+ children: "Update"
3827
+ }
3828
+ )
3829
+ ]
3109
3830
  }
3110
3831
  )
3111
- ] })
3832
+ ] }, spec.id)),
3833
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "border-t border-slate-700/60 pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3834
+ AdjustmentLayersPanel,
3835
+ {
3836
+ controller,
3837
+ layers: state.adjustments.layers,
3838
+ histogram: editor.histogram
3839
+ }
3840
+ ) })
3112
3841
  ] }),
3113
- activeTab === "annotate" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
3114
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
3115
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "grid grid-cols-3 gap-1.5", children: ["pen", "rect", "circle", "arrow", "line"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3842
+ activeTab === "annotate" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
3843
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Draw & Annotate" }),
3844
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "grid grid-cols-3 gap-1.5", children: ["pen", "rect", "circle", "arrow", "line"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3116
3845
  "button",
3117
3846
  {
3118
3847
  type: "button",
@@ -3122,9 +3851,9 @@ var ImageEditor = ({
3122
3851
  },
3123
3852
  t
3124
3853
  )) }),
3125
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2 mt-2", children: [
3126
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("label", { className: "text-slate-400", children: "Color:" }),
3127
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3854
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2 mt-2", children: [
3855
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { className: "text-slate-400", children: "Color:" }),
3856
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3128
3857
  "input",
3129
3858
  {
3130
3859
  type: "color",
@@ -3133,15 +3862,15 @@ var ImageEditor = ({
3133
3862
  className: "w-8 h-8 rounded border border-slate-700 cursor-pointer bg-transparent"
3134
3863
  }
3135
3864
  ),
3136
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex-1 ml-2", children: [
3137
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
3138
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Width:" }),
3139
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "font-mono", children: [
3865
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex-1 ml-2", children: [
3866
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex justify-between text-slate-400 mb-0.5", children: [
3867
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Width:" }),
3868
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "font-mono", children: [
3140
3869
  strokeWidth,
3141
3870
  "px"
3142
3871
  ] })
3143
3872
  ] }),
3144
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3873
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3145
3874
  "input",
3146
3875
  {
3147
3876
  type: "range",
@@ -3154,10 +3883,10 @@ var ImageEditor = ({
3154
3883
  )
3155
3884
  ] })
3156
3885
  ] }),
3157
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
3158
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
3159
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-2", children: [
3160
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3886
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
3887
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Add Text" }),
3888
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex gap-2", children: [
3889
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3161
3890
  "input",
3162
3891
  {
3163
3892
  type: "text",
@@ -3168,7 +3897,7 @@ var ImageEditor = ({
3168
3897
  className: "flex-1 bg-slate-950 border border-slate-700 rounded-lg px-2.5 py-1.5 text-xs text-slate-100 placeholder-slate-500 focus:outline-none focus:border-indigo-500"
3169
3898
  }
3170
3899
  ),
3171
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3900
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3172
3901
  "button",
3173
3902
  {
3174
3903
  type: "button",
@@ -3178,14 +3907,14 @@ var ImageEditor = ({
3178
3907
  }
3179
3908
  )
3180
3909
  ] }),
3181
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between text-slate-400", children: [
3182
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Font Size:" }),
3183
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "font-mono", children: [
3910
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between text-slate-400", children: [
3911
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Font Size:" }),
3912
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "font-mono", children: [
3184
3913
  fontSize,
3185
3914
  "px"
3186
3915
  ] })
3187
3916
  ] }),
3188
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3917
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3189
3918
  "input",
3190
3919
  {
3191
3920
  type: "range",
@@ -3196,14 +3925,14 @@ var ImageEditor = ({
3196
3925
  className: "w-full accent-indigo-500"
3197
3926
  }
3198
3927
  ),
3199
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
3200
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between", children: [
3201
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-slate-400", children: [
3928
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "h-px bg-slate-800 my-1" }),
3929
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
3930
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "text-slate-400", children: [
3202
3931
  "Layers (",
3203
3932
  state.annotations.length,
3204
3933
  ")"
3205
3934
  ] }),
3206
- state.selectedAnnotationId && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3935
+ state.selectedAnnotationId && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3207
3936
  "button",
3208
3937
  {
3209
3938
  type: "button",
@@ -3212,7 +3941,7 @@ var ImageEditor = ({
3212
3941
  children: "Delete Selected"
3213
3942
  }
3214
3943
  ),
3215
- state.annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3944
+ state.annotations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3216
3945
  "button",
3217
3946
  {
3218
3947
  type: "button",
@@ -3223,10 +3952,10 @@ var ImageEditor = ({
3223
3952
  )
3224
3953
  ] })
3225
3954
  ] }),
3226
- activeTab === "depth" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
3227
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between", children: [
3228
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
3229
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3955
+ activeTab === "depth" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col gap-3 text-xs", children: [
3956
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
3957
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px]", children: "Depth Masking" }),
3958
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3230
3959
  "button",
3231
3960
  {
3232
3961
  type: "button",
@@ -3236,8 +3965,8 @@ var ImageEditor = ({
3236
3965
  }
3237
3966
  )
3238
3967
  ] }),
3239
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
3240
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3968
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300", children: [
3969
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3241
3970
  "input",
3242
3971
  {
3243
3972
  type: "checkbox",
@@ -3246,12 +3975,12 @@ var ImageEditor = ({
3246
3975
  className: "rounded accent-indigo-500"
3247
3976
  }
3248
3977
  ),
3249
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Enable Depth / Alpha Mask" })
3978
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Enable Depth / Alpha Mask" })
3250
3979
  ] }),
3251
- state.depthMask.enabled && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3252
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3253
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
3254
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3980
+ state.depthMask.enabled && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
3981
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
3982
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { className: "text-slate-400 block mb-1", children: "Mask Image URL:" }),
3983
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3255
3984
  "input",
3256
3985
  {
3257
3986
  type: "text",
@@ -3262,8 +3991,8 @@ var ImageEditor = ({
3262
3991
  }
3263
3992
  )
3264
3993
  ] }),
3265
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-2", children: [
3266
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3994
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex gap-2", children: [
3995
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3267
3996
  "button",
3268
3997
  {
3269
3998
  type: "button",
@@ -3272,7 +4001,7 @@ var ImageEditor = ({
3272
4001
  children: "Threshold Mode"
3273
4002
  }
3274
4003
  ),
3275
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4004
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3276
4005
  "button",
3277
4006
  {
3278
4007
  type: "button",
@@ -3282,12 +4011,12 @@ var ImageEditor = ({
3282
4011
  }
3283
4012
  )
3284
4013
  ] }),
3285
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3286
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
3287
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Depth Start (Lower):" }),
3288
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[0] })
4014
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
4015
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
4016
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Depth Start (Lower):" }),
4017
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[0] })
3289
4018
  ] }),
3290
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4019
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3291
4020
  "input",
3292
4021
  {
3293
4022
  type: "range",
@@ -3301,12 +4030,12 @@ var ImageEditor = ({
3301
4030
  }
3302
4031
  )
3303
4032
  ] }),
3304
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3305
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
3306
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Depth Stop (Upper):" }),
3307
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[1] })
4033
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
4034
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
4035
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Depth Stop (Upper):" }),
4036
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "font-mono", children: state.depthMask.depthRange[1] })
3308
4037
  ] }),
3309
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4038
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3310
4039
  "input",
3311
4040
  {
3312
4041
  type: "range",
@@ -3320,12 +4049,12 @@ var ImageEditor = ({
3320
4049
  }
3321
4050
  )
3322
4051
  ] }),
3323
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
3324
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
3325
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Softness:" }),
3326
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "font-mono", children: state.depthMask.softness })
4052
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
4053
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex justify-between text-slate-400 mb-1", children: [
4054
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Softness:" }),
4055
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "font-mono", children: state.depthMask.softness })
3327
4056
  ] }),
3328
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4057
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3329
4058
  "input",
3330
4059
  {
3331
4060
  type: "range",
@@ -3337,8 +4066,8 @@ var ImageEditor = ({
3337
4066
  }
3338
4067
  )
3339
4068
  ] }),
3340
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
3341
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4069
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer text-slate-300 mt-1", children: [
4070
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3342
4071
  "input",
3343
4072
  {
3344
4073
  type: "checkbox",
@@ -3347,52 +4076,52 @@ var ImageEditor = ({
3347
4076
  className: "rounded accent-indigo-500"
3348
4077
  }
3349
4078
  ),
3350
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Invert Mask" })
4079
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Invert Mask" })
3351
4080
  ] })
3352
4081
  ] })
3353
4082
  ] }),
3354
- activeTab === "ai" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
3355
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
3356
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between", children: [
3357
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
3358
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { className: "w-3.5 h-3.5 text-indigo-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M6 3v12M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" }) }),
4083
+ activeTab === "ai" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
4084
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
4085
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
4086
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
4087
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("svg", { className: "w-3.5 h-3.5 text-indigo-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M6 3v12M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" }) }),
3359
4088
  "Background Removal"
3360
4089
  ] }),
3361
- isRemovingBg && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-indigo-900/60 text-indigo-300 animate-pulse", children: "Removing..." })
4090
+ isRemovingBg && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-indigo-900/60 text-indigo-300 animate-pulse", children: "Removing..." })
3362
4091
  ] }),
3363
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
3364
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4092
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
4093
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3365
4094
  "button",
3366
4095
  {
3367
4096
  type: "button",
3368
4097
  onClick: handleRemoveBackground,
3369
4098
  disabled: isRemovingBg || isInpainting,
3370
4099
  className: "mt-1 px-3 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white font-medium shadow transition-colors flex items-center justify-center gap-2",
3371
- children: isRemovingBg ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3372
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
4100
+ children: isRemovingBg ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
4101
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
3373
4102
  "Removing Background..."
3374
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3375
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
4103
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
4104
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
3376
4105
  "Remove Background"
3377
4106
  ] })
3378
4107
  }
3379
4108
  )
3380
4109
  ] }),
3381
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
3382
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between", children: [
3383
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
3384
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { className: "w-3.5 h-3.5 text-purple-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
3385
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "m21.64 3.64-1.28-1.28a1.2 1.2 0 0 0-1.7 0l-9.72 9.72-1.94 4.86 4.86-1.94 9.72-9.72a1.2 1.2 0 0 0 .06-1.64z" }),
3386
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "m14 7 3 3" })
4110
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
4111
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between", children: [
4112
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
4113
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("svg", { className: "w-3.5 h-3.5 text-purple-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
4114
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "m21.64 3.64-1.28-1.28a1.2 1.2 0 0 0-1.7 0l-9.72 9.72-1.94 4.86 4.86-1.94 9.72-9.72a1.2 1.2 0 0 0 .06-1.64z" }),
4115
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "m14 7 3 3" })
3387
4116
  ] }),
3388
4117
  "Generative Inpainting"
3389
4118
  ] }),
3390
- isInpainting && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-purple-900/60 text-purple-300 animate-pulse", children: "Inpainting..." })
4119
+ isInpainting && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-purple-900/60 text-purple-300 animate-pulse", children: "Inpainting..." })
3391
4120
  ] }),
3392
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
3393
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-1", children: [
3394
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
3395
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4121
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
4122
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col gap-1", children: [
4123
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
4124
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3396
4125
  "textarea",
3397
4126
  {
3398
4127
  id: "ai-inpaint-prompt-react",
@@ -3404,10 +4133,10 @@ var ImageEditor = ({
3404
4133
  }
3405
4134
  )
3406
4135
  ] }),
3407
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-1", children: [
3408
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
3409
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "grid grid-cols-2 gap-1.5", children: [
3410
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4136
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-col gap-1", children: [
4137
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
4138
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "grid grid-cols-2 gap-1.5", children: [
4139
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3411
4140
  "button",
3412
4141
  {
3413
4142
  type: "button",
@@ -3420,7 +4149,7 @@ var ImageEditor = ({
3420
4149
  ]
3421
4150
  }
3422
4151
  ),
3423
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4152
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3424
4153
  "button",
3425
4154
  {
3426
4155
  type: "button",
@@ -3435,25 +4164,25 @@ var ImageEditor = ({
3435
4164
  )
3436
4165
  ] })
3437
4166
  ] }),
3438
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4167
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3439
4168
  "button",
3440
4169
  {
3441
4170
  type: "button",
3442
4171
  onClick: handleInpaint,
3443
4172
  disabled: isInpainting || isRemovingBg || !inpaintPrompt.trim(),
3444
4173
  className: "mt-1 px-3 py-2 rounded-lg bg-purple-600 hover:bg-purple-500 disabled:opacity-50 text-white font-medium shadow transition-colors flex items-center justify-center gap-2",
3445
- children: isInpainting ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3446
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
4174
+ children: isInpainting ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
4175
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
3447
4176
  "Generating Inpaint..."
3448
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3449
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
4177
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
4178
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
3450
4179
  "Apply Inpainting"
3451
4180
  ] })
3452
4181
  }
3453
4182
  )
3454
4183
  ] }),
3455
- aiError && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "p-2.5 rounded-lg bg-rose-950/60 border border-rose-800 text-rose-300 text-[11px]", children: aiError }),
3456
- aiSuccess && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "p-2.5 rounded-lg bg-emerald-950/60 border border-emerald-800 text-emerald-300 text-[11px]", children: aiSuccess })
4184
+ aiError && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "p-2.5 rounded-lg bg-rose-950/60 border border-rose-800 text-rose-300 text-[11px]", children: aiError }),
4185
+ aiSuccess && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "p-2.5 rounded-lg bg-emerald-950/60 border border-emerald-800 text-emerald-300 text-[11px]", children: aiSuccess })
3457
4186
  ] })
3458
4187
  ] })
3459
4188
  ] })
@@ -3463,8 +4192,8 @@ var ImageEditor = ({
3463
4192
  };
3464
4193
 
3465
4194
  // src/review/useReview.ts
3466
- var import_react23 = require("react");
3467
- var import_editor9 = require("@pixerate/editor");
4195
+ var import_react29 = require("react");
4196
+ var import_editor11 = require("@pixerate/editor");
3468
4197
  var EMPTY_COMMENTS = Object.freeze({
3469
4198
  threads: [],
3470
4199
  activeThreadId: null
@@ -3481,24 +4210,24 @@ function isCommentStore(value) {
3481
4210
  return !!value && typeof value.getSnapshot === "function" && typeof value.createThread === "function";
3482
4211
  }
3483
4212
  function useComments(source) {
3484
- const store = isCommentStore(source) ? source : (0, import_editor9.getCommentStore)(source);
3485
- const subscribe = (0, import_react23.useCallback)(
4213
+ const store = isCommentStore(source) ? source : (0, import_editor11.getCommentStore)(source);
4214
+ const subscribe = (0, import_react29.useCallback)(
3486
4215
  (onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
3487
4216
  [store]
3488
4217
  );
3489
4218
  const getSnapshot = () => store?.getSnapshot() ?? EMPTY_COMMENTS;
3490
- const snapshot = (0, import_react23.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
3491
- return (0, import_react23.useMemo)(() => ({ ...snapshot, store }), [snapshot, store]);
4219
+ const snapshot = (0, import_react29.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
4220
+ return (0, import_react29.useMemo)(() => ({ ...snapshot, store }), [snapshot, store]);
3492
4221
  }
3493
4222
  function useTrackChanges(editor) {
3494
- const store = (0, import_editor9.getTrackChangesStore)(editor ?? null);
3495
- const subscribe = (0, import_react23.useCallback)(
4223
+ const store = (0, import_editor11.getTrackChangesStore)(editor ?? null);
4224
+ const subscribe = (0, import_react29.useCallback)(
3496
4225
  (onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe,
3497
4226
  [store]
3498
4227
  );
3499
4228
  const getSnapshot = () => store?.getSnapshot() ?? EMPTY_TRACK_CHANGES;
3500
- const snapshot = (0, import_react23.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
3501
- return (0, import_react23.useMemo)(() => {
4229
+ const snapshot = (0, import_react29.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
4230
+ return (0, import_react29.useMemo)(() => {
3502
4231
  const run = (fn) => () => {
3503
4232
  if (editor && !editor.isDestroyed) fn(editor);
3504
4233
  };
@@ -3518,9 +4247,9 @@ function useTrackChanges(editor) {
3518
4247
  }
3519
4248
 
3520
4249
  // src/review/CommentsPanel.tsx
3521
- var import_react24 = require("react");
3522
- var import_editor10 = require("@pixerate/editor");
3523
- var import_jsx_runtime11 = require("react/jsx-runtime");
4250
+ var import_react30 = require("react");
4251
+ var import_editor12 = require("@pixerate/editor");
4252
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3524
4253
  var buttonClass = "rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50";
3525
4254
  var primaryButtonClass = "rounded px-2 py-1 text-xs bg-primary/10 text-primary hover:bg-primary/20 disabled:opacity-50";
3526
4255
  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";
@@ -3530,22 +4259,22 @@ var CommentsPanel = ({
3530
4259
  author: authorProp,
3531
4260
  showResolved: showResolvedInitial = false,
3532
4261
  className = "flex flex-col gap-2 text-sm text-foreground",
3533
- formatTime = (t) => (0, import_editor10.formatReviewTime)(t),
4262
+ formatTime = (t) => (0, import_editor12.formatReviewTime)(t),
3534
4263
  emptyMessage = "No comments yet. Select text and add a comment."
3535
4264
  }) => {
3536
4265
  const { threads, activeThreadId, store } = useComments(storeProp ?? editor);
3537
- const [showResolved, setShowResolved] = (0, import_react24.useState)(showResolvedInitial);
3538
- const author = authorProp ?? (0, import_editor10.getCommentAuthor)(editor) ?? { id: "anonymous", name: "Anonymous" };
4266
+ const [showResolved, setShowResolved] = (0, import_react30.useState)(showResolvedInitial);
4267
+ const author = authorProp ?? (0, import_editor12.getCommentAuthor)(editor) ?? { id: "anonymous", name: "Anonymous" };
3539
4268
  const visible = threads.filter((t) => showResolved || !t.resolved);
3540
4269
  const resolvedCount = threads.filter((t) => t.resolved).length;
3541
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("aside", { className, "aria-label": "Comments", children: [
3542
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
3543
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "text-xs font-medium text-muted-foreground", children: [
4270
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("aside", { className, "aria-label": "Comments", children: [
4271
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
4272
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-xs font-medium text-muted-foreground", children: [
3544
4273
  threads.length - resolvedCount,
3545
4274
  " open",
3546
4275
  resolvedCount > 0 ? ` \xB7 ${resolvedCount} resolved` : ""
3547
4276
  ] }),
3548
- resolvedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4277
+ resolvedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3549
4278
  "button",
3550
4279
  {
3551
4280
  type: "button",
@@ -3556,7 +4285,7 @@ var CommentsPanel = ({
3556
4285
  }
3557
4286
  )
3558
4287
  ] }),
3559
- visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-xs text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "flex flex-col gap-2", role: "list", children: visible.map((thread) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4288
+ visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-xs text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "flex flex-col gap-2", role: "list", children: visible.map((thread) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3560
4289
  CommentThreadCard,
3561
4290
  {
3562
4291
  thread,
@@ -3578,7 +4307,7 @@ var CommentThreadCard = ({
3578
4307
  author,
3579
4308
  formatTime
3580
4309
  }) => {
3581
- const [draft, setDraft] = (0, import_react24.useState)("");
4310
+ const [draft, setDraft] = (0, import_react30.useState)("");
3582
4311
  const isDraft = thread.body === "";
3583
4312
  const quote = thread.anchor?.quote ?? thread.lastQuote ?? "";
3584
4313
  const live = editor && !editor.isDestroyed ? editor : null;
@@ -3604,7 +4333,7 @@ var CommentThreadCard = ({
3604
4333
  setDraft("");
3605
4334
  };
3606
4335
  const stop = (e) => e.stopPropagation();
3607
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4336
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3608
4337
  "li",
3609
4338
  {
3610
4339
  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" : ""}`,
@@ -3620,24 +4349,24 @@ var CommentThreadCard = ({
3620
4349
  }
3621
4350
  },
3622
4351
  children: [
3623
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-baseline justify-between gap-2", children: [
3624
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-medium", children: thread.author.name }),
3625
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-xs text-muted-foreground", children: formatTime(thread.createdAt) })
4352
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-baseline justify-between gap-2", children: [
4353
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: thread.author.name }),
4354
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs text-muted-foreground", children: formatTime(thread.createdAt) })
3626
4355
  ] }),
3627
- quote && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("blockquote", { className: "border-l-2 border-border pl-2 text-xs text-muted-foreground line-clamp-2", children: quote }),
3628
- thread.orphaned && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-xs text-destructive", children: "The commented text was deleted." }),
3629
- thread.resolved && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-xs text-muted-foreground", children: "Resolved" }),
3630
- !isDraft && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "whitespace-pre-wrap break-words", children: thread.body }),
3631
- thread.replies.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "flex flex-col gap-2 border-l border-border pl-3", role: "list", children: thread.replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { className: "flex flex-col gap-0.5", "data-reply-id": reply.id, children: [
3632
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "text-xs", children: [
3633
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-medium", children: reply.author.name }),
4356
+ quote && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("blockquote", { className: "border-l-2 border-border pl-2 text-xs text-muted-foreground line-clamp-2", children: quote }),
4357
+ thread.orphaned && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs text-destructive", children: "The commented text was deleted." }),
4358
+ thread.resolved && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-xs text-muted-foreground", children: "Resolved" }),
4359
+ !isDraft && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "whitespace-pre-wrap break-words", children: thread.body }),
4360
+ thread.replies.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "flex flex-col gap-2 border-l border-border pl-3", role: "list", children: thread.replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("li", { className: "flex flex-col gap-0.5", "data-reply-id": reply.id, children: [
4361
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-xs", children: [
4362
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: reply.author.name }),
3634
4363
  " ",
3635
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-muted-foreground", children: formatTime(reply.createdAt) })
4364
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-muted-foreground", children: formatTime(reply.createdAt) })
3636
4365
  ] }),
3637
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "whitespace-pre-wrap break-words", children: reply.body })
4366
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "whitespace-pre-wrap break-words", children: reply.body })
3638
4367
  ] }, reply.id)) }),
3639
- (active || isDraft) && !thread.resolved && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-1", onClick: stop, children: [
3640
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4368
+ (active || isDraft) && !thread.resolved && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-col gap-1", onClick: stop, children: [
4369
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3641
4370
  "textarea",
3642
4371
  {
3643
4372
  className: textareaClass,
@@ -3655,9 +4384,9 @@ var CommentThreadCard = ({
3655
4384
  }
3656
4385
  }
3657
4386
  ),
3658
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex justify-end gap-1", children: [
3659
- isDraft && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: buttonClass, onClick: remove, children: "Cancel" }),
3660
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4387
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex justify-end gap-1", children: [
4388
+ isDraft && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: buttonClass, onClick: remove, children: "Cancel" }),
4389
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3661
4390
  "button",
3662
4391
  {
3663
4392
  type: "button",
@@ -3669,9 +4398,9 @@ var CommentThreadCard = ({
3669
4398
  )
3670
4399
  ] })
3671
4400
  ] }),
3672
- !isDraft && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex justify-end gap-1", onClick: stop, children: [
3673
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: buttonClass, onClick: () => setResolved(!thread.resolved), children: thread.resolved ? "Reopen" : "Resolve" }),
3674
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4401
+ !isDraft && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex justify-end gap-1", onClick: stop, children: [
4402
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: buttonClass, onClick: () => setResolved(!thread.resolved), children: thread.resolved ? "Reopen" : "Resolve" }),
4403
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3675
4404
  "button",
3676
4405
  {
3677
4406
  type: "button",
@@ -3687,7 +4416,7 @@ var CommentThreadCard = ({
3687
4416
  };
3688
4417
 
3689
4418
  // src/review/ReviewToolbar.tsx
3690
- var import_jsx_runtime12 = require("react/jsx-runtime");
4419
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3691
4420
  var defaultDescriptionClass = "max-w-[16rem] truncate px-1 text-xs text-muted-foreground";
3692
4421
  var buttonClass2 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
3693
4422
  var ReviewToolbar = ({
@@ -3700,8 +4429,8 @@ var ReviewToolbar = ({
3700
4429
  const count = review.changes.length;
3701
4430
  const current = review.currentChange;
3702
4431
  const index = current ? review.changes.findIndex((c) => c.id === current.id) + 1 : 0;
3703
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className, role: "toolbar", "aria-label": "Review", children: [
3704
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4432
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className, role: "toolbar", "aria-label": "Review", children: [
4433
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3705
4434
  "button",
3706
4435
  {
3707
4436
  type: "button",
@@ -3712,8 +4441,8 @@ var ReviewToolbar = ({
3712
4441
  children: review.enabled ? "Tracking on" : "Track changes"
3713
4442
  }
3714
4443
  ),
3715
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
3716
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4444
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
4445
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3717
4446
  "button",
3718
4447
  {
3719
4448
  type: "button",
@@ -3724,8 +4453,8 @@ var ReviewToolbar = ({
3724
4453
  children: "\u2039"
3725
4454
  }
3726
4455
  ),
3727
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("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` }),
3728
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4456
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("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` }),
4457
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3729
4458
  "button",
3730
4459
  {
3731
4460
  type: "button",
@@ -3736,7 +4465,7 @@ var ReviewToolbar = ({
3736
4465
  children: "\u203A"
3737
4466
  }
3738
4467
  ),
3739
- showDescription && current && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
4468
+ showDescription && current && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3740
4469
  "span",
3741
4470
  {
3742
4471
  className: descriptionClassName,
@@ -3748,8 +4477,8 @@ var ReviewToolbar = ({
3748
4477
  ]
3749
4478
  }
3750
4479
  ),
3751
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
3752
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4480
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
4481
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3753
4482
  "button",
3754
4483
  {
3755
4484
  type: "button",
@@ -3759,7 +4488,7 @@ var ReviewToolbar = ({
3759
4488
  children: "Accept"
3760
4489
  }
3761
4490
  ),
3762
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4491
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3763
4492
  "button",
3764
4493
  {
3765
4494
  type: "button",
@@ -3769,18 +4498,18 @@ var ReviewToolbar = ({
3769
4498
  children: "Reject"
3770
4499
  }
3771
4500
  ),
3772
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.acceptAll, children: "Accept all" }),
3773
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.rejectAll, children: "Reject all" })
4501
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.acceptAll, children: "Accept all" }),
4502
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: buttonClass2, disabled: count === 0, onClick: review.rejectAll, children: "Reject all" })
3774
4503
  ] });
3775
4504
  };
3776
4505
 
3777
4506
  // src/review/CommentButton.tsx
3778
- var import_jsx_runtime13 = require("react/jsx-runtime");
4507
+ var import_jsx_runtime18 = require("react/jsx-runtime");
3779
4508
  var CommentButton = ({
3780
4509
  editor,
3781
4510
  className = "rounded px-2 py-1 text-xs text-popover-foreground hover:bg-muted",
3782
4511
  children = "Comment"
3783
- }) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4512
+ }) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3784
4513
  "button",
3785
4514
  {
3786
4515
  type: "button",
@@ -3790,42 +4519,830 @@ var CommentButton = ({
3790
4519
  children
3791
4520
  }
3792
4521
  );
4522
+
4523
+ // src/review/useDocumentCompare.ts
4524
+ var import_react31 = require("react");
4525
+ var import_editor13 = require("@pixerate/editor");
4526
+ function useDocumentCompare(editor, options) {
4527
+ const [summary, setSummary] = (0, import_react31.useState)(null);
4528
+ const defaults = (0, import_react31.useRef)(options);
4529
+ defaults.current = options;
4530
+ const compare = (0, import_react31.useCallback)(
4531
+ (original, revised, overrides) => {
4532
+ if (!editor || editor.isDestroyed) return null;
4533
+ const result = (0, import_editor13.applyDocumentComparison)(editor, original, revised, {
4534
+ ...defaults.current,
4535
+ ...overrides
4536
+ });
4537
+ setSummary(result.summary);
4538
+ return result.summary;
4539
+ },
4540
+ [editor]
4541
+ );
4542
+ const clear = (0, import_react31.useCallback)(() => setSummary(null), []);
4543
+ return (0, import_react31.useMemo)(() => ({ summary, compare, clear }), [summary, compare, clear]);
4544
+ }
4545
+
4546
+ // src/commands/useCommandPalette.ts
4547
+ var import_react32 = require("react");
4548
+ var import_commands2 = require("@pixerate/editor/commands");
4549
+ function useCommandPalette(registry, options = {}) {
4550
+ const optionsRef = (0, import_react32.useRef)(options);
4551
+ optionsRef.current = options;
4552
+ const controller = (0, import_react32.useMemo)(
4553
+ () => (0, import_commands2.createCommandPaletteController)(registry, {
4554
+ context: () => {
4555
+ const c = optionsRef.current.context;
4556
+ if (c === void 0) return registry.context;
4557
+ return typeof c === "function" ? c() : c;
4558
+ },
4559
+ scope: () => {
4560
+ const s = optionsRef.current.scope;
4561
+ return typeof s === "function" ? s() : s;
4562
+ },
4563
+ recents: options.recents,
4564
+ recentLimit: options.recentLimit,
4565
+ limit: options.limit,
4566
+ platform: options.platform,
4567
+ onRecentsChange: (recents) => optionsRef.current.onRecentsChange?.(recents),
4568
+ onRun: (result) => optionsRef.current.onRun?.(result)
4569
+ }),
4570
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4571
+ [registry]
4572
+ );
4573
+ const state = (0, import_react32.useSyncExternalStore)(controller.subscribe, controller.getState, controller.getState);
4574
+ const { hotkey, hotkeyTarget } = options;
4575
+ (0, import_react32.useEffect)(() => {
4576
+ if (!hotkey) return;
4577
+ const target = hotkeyTarget ?? (typeof window !== "undefined" ? window : null);
4578
+ if (!target) return;
4579
+ const onKeyDown = (event) => {
4580
+ const e = event;
4581
+ if (e.defaultPrevented || !(0, import_commands2.matchPaletteHotkey)(e, hotkey, optionsRef.current.platform)) return;
4582
+ e.preventDefault();
4583
+ controller.toggle();
4584
+ };
4585
+ target.addEventListener("keydown", onKeyDown);
4586
+ return () => target.removeEventListener("keydown", onKeyDown);
4587
+ }, [controller, hotkey, hotkeyTarget]);
4588
+ return {
4589
+ ...state,
4590
+ controller,
4591
+ setOpen: controller.setOpen,
4592
+ toggle: controller.toggle,
4593
+ setQuery: controller.setQuery,
4594
+ setActiveIndex: controller.setActiveIndex,
4595
+ move: controller.move,
4596
+ run: controller.run,
4597
+ runActive: controller.runActive,
4598
+ handleKeyDown: controller.handleKeyDown
4599
+ };
4600
+ }
4601
+
4602
+ // src/commands/CommandPalette.tsx
4603
+ var import_react33 = __toESM(require("react"), 1);
4604
+ var import_jsx_runtime19 = require("react/jsx-runtime");
4605
+ function HighlightedLabel({ item }) {
4606
+ if (item.labelIndices.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, { children: item.label });
4607
+ const marked = new Set(item.labelIndices);
4608
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, { children: Array.from(item.label).map(
4609
+ (ch, i) => marked.has(i) ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "font-semibold text-foreground", "data-match": "", children: ch }, i) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react33.default.Fragment, { children: ch }, i)
4610
+ ) });
4611
+ }
4612
+ var CommandPalette = ({
4613
+ palette,
4614
+ placeholder = "Type a command\u2026",
4615
+ emptyMessage = "No matching commands",
4616
+ className = ""
4617
+ }) => {
4618
+ const inputRef = (0, import_react33.useRef)(null);
4619
+ const listRef = (0, import_react33.useRef)(null);
4620
+ const baseId = (0, import_react33.useId)();
4621
+ const listId = `${baseId}-list`;
4622
+ const optionId = (index) => `${baseId}-option-${index}`;
4623
+ const { open, query, items, activeIndex } = palette;
4624
+ (0, import_react33.useEffect)(() => {
4625
+ if (open) inputRef.current?.focus();
4626
+ }, [open]);
4627
+ (0, import_react33.useEffect)(() => {
4628
+ if (!open) return;
4629
+ const active = listRef.current?.querySelector('[aria-selected="true"]');
4630
+ active?.scrollIntoView?.({ block: "nearest" });
4631
+ }, [open, activeIndex]);
4632
+ if (!open) return null;
4633
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4634
+ "div",
4635
+ {
4636
+ className: "fixed inset-0 z-50 flex items-start justify-center bg-black/30 px-4 pt-[15vh]",
4637
+ "data-command-palette-backdrop": "",
4638
+ onMouseDown: (e) => {
4639
+ if (e.target === e.currentTarget) palette.setOpen(false);
4640
+ },
4641
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
4642
+ "div",
4643
+ {
4644
+ role: "dialog",
4645
+ "aria-modal": "true",
4646
+ "aria-label": "Command palette",
4647
+ className: `w-full max-w-lg overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-xl ${className}`,
4648
+ style: { background: "var(--popover, var(--background, #fff))" },
4649
+ children: [
4650
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4651
+ "input",
4652
+ {
4653
+ ref: inputRef,
4654
+ type: "text",
4655
+ role: "combobox",
4656
+ "aria-expanded": "true",
4657
+ "aria-controls": listId,
4658
+ "aria-autocomplete": "list",
4659
+ "aria-activedescendant": items.length ? optionId(activeIndex) : void 0,
4660
+ "aria-label": "Search commands",
4661
+ placeholder,
4662
+ value: query,
4663
+ onChange: (e) => palette.setQuery(e.target.value),
4664
+ onKeyDown: (e) => {
4665
+ if (palette.handleKeyDown(e.nativeEvent)) e.stopPropagation();
4666
+ },
4667
+ className: "w-full border-b border-border bg-transparent px-3 py-2 text-sm outline-none placeholder:text-muted-foreground"
4668
+ }
4669
+ ),
4670
+ items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "px-3 py-6 text-center text-sm text-muted-foreground", children: emptyMessage }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ul", { ref: listRef, id: listId, role: "listbox", "aria-label": "Commands", className: "max-h-80 overflow-y-auto py-1", children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
4671
+ "li",
4672
+ {
4673
+ id: optionId(index),
4674
+ role: "option",
4675
+ "aria-selected": index === activeIndex,
4676
+ "aria-disabled": !item.enabled || void 0,
4677
+ "data-command-id": item.id,
4678
+ "data-recent": item.recent ? "" : void 0,
4679
+ title: item.disabledReason,
4680
+ onMouseMove: () => index !== activeIndex && palette.setActiveIndex(index),
4681
+ onMouseDown: (e) => e.preventDefault(),
4682
+ onClick: () => palette.run(item.id),
4683
+ className: `flex cursor-pointer items-center gap-2 px-3 py-1.5 text-sm ${index === activeIndex ? "bg-muted" : ""} ${item.enabled ? "" : "cursor-default opacity-50"}`,
4684
+ children: [
4685
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "min-w-0 flex-1 truncate", children: [
4686
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(HighlightedLabel, { item }),
4687
+ item.disabledReason && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "ml-2 text-xs text-muted-foreground", "data-disabled-reason": "", children: item.disabledReason })
4688
+ ] }),
4689
+ item.group && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "shrink-0 text-xs text-muted-foreground", children: item.group }),
4690
+ item.shortcut && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("kbd", { className: "shrink-0 rounded border border-border px-1.5 font-mono text-[10px] text-muted-foreground", children: item.shortcut })
4691
+ ]
4692
+ },
4693
+ item.id
4694
+ )) })
4695
+ ]
4696
+ }
4697
+ )
4698
+ }
4699
+ );
4700
+ };
4701
+
4702
+ // src/commands/useCommandShortcuts.ts
4703
+ var import_react34 = require("react");
4704
+ function useCommandShortcuts(registry, options = {}) {
4705
+ const optionsRef = (0, import_react34.useRef)(options);
4706
+ optionsRef.current = options;
4707
+ const { enabled = true, target } = options;
4708
+ (0, import_react34.useEffect)(() => {
4709
+ if (!registry || !enabled) return;
4710
+ const eventTarget = target ?? (typeof window !== "undefined" ? window : null);
4711
+ if (!eventTarget) return;
4712
+ const onKeyDown = (event) => {
4713
+ const { scopes, context, allowInEditable, onRun } = optionsRef.current;
4714
+ const result = registry.handleKeyDown(event, {
4715
+ scopes,
4716
+ ...allowInEditable !== void 0 ? { allowInEditable } : {},
4717
+ ...context !== void 0 ? { context } : {}
4718
+ });
4719
+ if (result) onRun?.(result);
4720
+ };
4721
+ eventTarget.addEventListener("keydown", onKeyDown);
4722
+ return () => eventTarget.removeEventListener("keydown", onKeyDown);
4723
+ }, [registry, enabled, target]);
4724
+ }
4725
+ function useRegisterCommands(registry, specs) {
4726
+ (0, import_react34.useEffect)(() => {
4727
+ if (!registry || !specs || specs.length === 0) return;
4728
+ return registry.register(specs);
4729
+ }, [registry, specs]);
4730
+ }
4731
+
4732
+ // src/persistence/useAutosave.ts
4733
+ var import_react35 = require("react");
4734
+ var import_persistence = require("@pixerate/editor/persistence");
4735
+ var IDLE = {
4736
+ status: "idle",
4737
+ error: null,
4738
+ failures: 0,
4739
+ nextAttemptAt: null,
4740
+ lastSavedAt: null,
4741
+ ready: false
4742
+ };
4743
+ function useAutosave(options) {
4744
+ const optionsRef = (0, import_react35.useRef)(options);
4745
+ optionsRef.current = options;
4746
+ const { storage, key, history, dirty } = options;
4747
+ const recover = options.recover !== false;
4748
+ const flushOnHide = options.flushOnHide !== false;
4749
+ const ready = options.ready !== false;
4750
+ const [controller, setController] = (0, import_react35.useState)(null);
4751
+ const [status, setStatus] = (0, import_react35.useState)(IDLE);
4752
+ const [recovery, setRecovery] = (0, import_react35.useState)(null);
4753
+ const [pendingRecovery, setPendingRecovery] = (0, import_react35.useState)(recover);
4754
+ const revisionsRef = (0, import_react35.useRef)(null);
4755
+ (0, import_react35.useEffect)(() => {
4756
+ const revisions = (0, import_persistence.createRevisionTracker)({ history, dirty });
4757
+ const o = optionsRef.current;
4758
+ const instance = (0, import_persistence.createAutosaveController)({
4759
+ storage,
4760
+ key,
4761
+ getSnapshot: () => optionsRef.current.getSnapshot(),
4762
+ serialize: o.serialize,
4763
+ deserialize: o.deserialize,
4764
+ name: () => optionsRef.current.name,
4765
+ revision: revisions.revision,
4766
+ savedRevision: revisions.savedRevision,
4767
+ interval: o.interval,
4768
+ debounceMs: o.debounceMs,
4769
+ retryBaseMs: o.retryBaseMs,
4770
+ retryMaxMs: o.retryMaxMs
4771
+ });
4772
+ const unsubscribeStatus = instance.subscribe(setStatus);
4773
+ const unsubscribeRevisions = revisions.subscribe(() => instance.notify());
4774
+ revisionsRef.current = revisions;
4775
+ setController(instance);
4776
+ setStatus(instance.getStatus());
4777
+ let active = true;
4778
+ if (recover) {
4779
+ setPendingRecovery(true);
4780
+ instance.checkRecovery().catch(() => null).then((found) => {
4781
+ if (!active) return;
4782
+ setRecovery(found);
4783
+ setPendingRecovery(found !== null);
4784
+ });
4785
+ } else {
4786
+ setRecovery(null);
4787
+ setPendingRecovery(false);
4788
+ }
4789
+ return () => {
4790
+ active = false;
4791
+ unsubscribeStatus();
4792
+ unsubscribeRevisions();
4793
+ revisions.destroy();
4794
+ instance.destroy();
4795
+ if (revisionsRef.current === revisions) revisionsRef.current = null;
4796
+ };
4797
+ }, [storage, key, history, dirty, recover]);
4798
+ (0, import_react35.useEffect)(() => {
4799
+ if (controller && ready && !pendingRecovery) controller.ready();
4800
+ }, [controller, ready, pendingRecovery]);
4801
+ (0, import_react35.useEffect)(() => {
4802
+ if (!controller || !flushOnHide || typeof window === "undefined") return;
4803
+ const flush = () => void controller.flushNow();
4804
+ const onVisibility = () => {
4805
+ if (document.visibilityState === "hidden") flush();
4806
+ };
4807
+ window.addEventListener("pagehide", flush);
4808
+ document.addEventListener("visibilitychange", onVisibility);
4809
+ return () => {
4810
+ window.removeEventListener("pagehide", flush);
4811
+ document.removeEventListener("visibilitychange", onVisibility);
4812
+ };
4813
+ }, [controller, flushOnHide]);
4814
+ const restore = (0, import_react35.useCallback)(() => {
4815
+ const snapshot = recovery?.snapshot;
4816
+ setRecovery(null);
4817
+ setPendingRecovery(false);
4818
+ return snapshot;
4819
+ }, [recovery]);
4820
+ const discard = (0, import_react35.useCallback)(async () => {
4821
+ if (controller) await controller.discardRecovery();
4822
+ setRecovery(null);
4823
+ setPendingRecovery(false);
4824
+ }, [controller]);
4825
+ const flushNow = (0, import_react35.useCallback)(async () => {
4826
+ await controller?.flushNow();
4827
+ }, [controller]);
4828
+ const markClean = (0, import_react35.useCallback)(async () => {
4829
+ revisionsRef.current?.markSaved();
4830
+ await controller?.markClean();
4831
+ }, [controller]);
4832
+ const notifyChange = (0, import_react35.useCallback)(() => revisionsRef.current?.bump(), []);
4833
+ return { status, recovery, pendingRecovery, restore, discard, flushNow, markClean, notifyChange, controller };
4834
+ }
4835
+
4836
+ // src/persistence/RecoveryPrompt.tsx
4837
+ var import_persistence2 = require("@pixerate/editor/persistence");
4838
+ var import_jsx_runtime20 = require("react/jsx-runtime");
4839
+ var RecoveryPrompt = ({
4840
+ recovery,
4841
+ onRestore,
4842
+ onDiscard,
4843
+ formatTime,
4844
+ restoreLabel = "Restore",
4845
+ discardLabel = "Discard",
4846
+ className = ""
4847
+ }) => {
4848
+ if (!recovery) return null;
4849
+ const { name } = recovery.meta;
4850
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
4851
+ "div",
4852
+ {
4853
+ role: "alertdialog",
4854
+ "aria-label": "Unsaved changes found",
4855
+ "data-recovery-prompt": "",
4856
+ className: `flex flex-wrap items-center gap-3 rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground shadow-sm ${className}`,
4857
+ children: [
4858
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "min-w-0 flex-1", children: [
4859
+ (0, import_persistence2.formatRecoveryMessage)(recovery.meta, formatTime),
4860
+ name ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "ml-1 text-xs text-muted-foreground", children: [
4861
+ "(",
4862
+ name,
4863
+ ")"
4864
+ ] }) : null
4865
+ ] }),
4866
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4867
+ "button",
4868
+ {
4869
+ type: "button",
4870
+ "data-recovery-restore": "",
4871
+ onClick: onRestore,
4872
+ className: "rounded-md bg-primary px-3 py-1 text-xs font-medium text-primary-foreground hover:opacity-90",
4873
+ children: restoreLabel
4874
+ }
4875
+ ),
4876
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4877
+ "button",
4878
+ {
4879
+ type: "button",
4880
+ "data-recovery-discard": "",
4881
+ onClick: onDiscard,
4882
+ className: "rounded-md border border-border px-3 py-1 text-xs font-medium hover:bg-muted",
4883
+ children: discardLabel
4884
+ }
4885
+ )
4886
+ ]
4887
+ }
4888
+ );
4889
+ };
4890
+
4891
+ // src/generation/useGenerationPlan.ts
4892
+ var import_react36 = require("react");
4893
+ var import_generation = require("@pixerate/editor/generation");
4894
+ function useGenerationPlan(request, capabilities, options = {}) {
4895
+ const key = JSON.stringify([request, capabilities, options]);
4896
+ return (0, import_react36.useMemo)(() => (0, import_generation.planGenerationRequest)(request, capabilities, options), [key]);
4897
+ }
4898
+ function useJobProgress(options) {
4899
+ const { startedAt, expectedMs, done, now, intervalMs = 1e3 } = options;
4900
+ const [tick, setTick] = (0, import_react36.useState)(() => Date.now());
4901
+ const ticking = !done && now === void 0;
4902
+ (0, import_react36.useEffect)(() => {
4903
+ if (!ticking) return;
4904
+ setTick(Date.now());
4905
+ const id = setInterval(() => setTick(Date.now()), Math.max(16, intervalMs));
4906
+ return () => clearInterval(id);
4907
+ }, [ticking, intervalMs]);
4908
+ return (0, import_generation.estimateJobProgress)({ startedAt, expectedMs, done, now: now ?? tick });
4909
+ }
4910
+
4911
+ // src/generation/GenerationSettings.tsx
4912
+ var import_react37 = require("react");
4913
+ var import_generation2 = require("@pixerate/editor/generation");
4914
+ var import_jsx_runtime21 = require("react/jsx-runtime");
4915
+ var inputClass = "w-full rounded-md border border-border bg-background px-2 py-1 text-sm outline-none focus:ring-1 focus:ring-ring";
4916
+ function Control({
4917
+ control,
4918
+ value,
4919
+ id,
4920
+ onValue
4921
+ }) {
4922
+ const shown = value ?? control.defaultValue;
4923
+ if (control.kind === "checkbox") {
4924
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4925
+ "input",
4926
+ {
4927
+ id,
4928
+ type: "checkbox",
4929
+ "data-field": control.key,
4930
+ checked: Boolean(shown),
4931
+ onChange: (e) => onValue((0, import_generation2.parseControlValue)(control, e.target.checked))
4932
+ }
4933
+ );
4934
+ }
4935
+ if (control.kind === "select") {
4936
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4937
+ "select",
4938
+ {
4939
+ id,
4940
+ "data-field": control.key,
4941
+ className: inputClass,
4942
+ value: shown === void 0 ? "" : String(shown),
4943
+ onChange: (e) => onValue((0, import_generation2.parseControlValue)(control, e.target.value)),
4944
+ children: [
4945
+ shown === void 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: "", children: "Default" }),
4946
+ control.options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: String(option.value), children: option.label }, String(option.value)))
4947
+ ]
4948
+ }
4949
+ );
4950
+ }
4951
+ if (control.kind === "textarea") {
4952
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4953
+ "textarea",
4954
+ {
4955
+ id,
4956
+ "data-field": control.key,
4957
+ className: inputClass,
4958
+ rows: 2,
4959
+ maxLength: control.maxLength,
4960
+ value: typeof value === "string" ? value : "",
4961
+ onChange: (e) => onValue((0, import_generation2.parseControlValue)(control, e.target.value))
4962
+ }
4963
+ );
4964
+ }
4965
+ const numeric = typeof shown === "number" ? shown : void 0;
4966
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-2", children: [
4967
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4968
+ "input",
4969
+ {
4970
+ id,
4971
+ type: control.kind === "range" ? "range" : "number",
4972
+ "data-field": control.key,
4973
+ className: control.kind === "range" ? "flex-1" : inputClass,
4974
+ min: control.min,
4975
+ max: control.max,
4976
+ step: control.step ?? "any",
4977
+ value: numeric ?? (control.kind === "range" ? control.min ?? 0 : ""),
4978
+ onChange: (e) => onValue((0, import_generation2.parseControlValue)(control, e.target.value))
4979
+ }
4980
+ ),
4981
+ control.kind === "range" && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "w-10 text-right tabular-nums text-muted-foreground", "data-field-value": "", children: numeric ?? "\u2013" })
4982
+ ] });
4983
+ }
4984
+ var GenerationSettings = ({
4985
+ capabilities,
4986
+ value,
4987
+ onChange,
4988
+ plan: planProp,
4989
+ strategy,
4990
+ showAdjustments = true,
4991
+ onAddReference,
4992
+ renderReference,
4993
+ className = ""
4994
+ }) => {
4995
+ const baseId = (0, import_react37.useId)();
4996
+ const controls = (0, import_react37.useMemo)(() => (0, import_generation2.getGenerationControls)(capabilities), [capabilities]);
4997
+ const computed = useGenerationPlan(value, capabilities, { strategy });
4998
+ const plan = planProp ?? computed;
4999
+ const slots = (0, import_generation2.getReferenceSlots)(capabilities, value);
5000
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: `flex flex-col gap-3 text-sm ${className}`, "data-generation-settings": "", "data-model": capabilities.id, children: [
5001
+ controls.map((control) => {
5002
+ const id = `${baseId}-${control.key}`;
5003
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-col gap-1", "data-control": control.key, children: [
5004
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("label", { htmlFor: id, className: "text-xs font-medium text-muted-foreground", title: control.description, children: control.label }),
5005
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
5006
+ Control,
5007
+ {
5008
+ control,
5009
+ id,
5010
+ value: (0, import_generation2.getRequestValue)(value, control.key),
5011
+ onValue: (next) => onChange((0, import_generation2.setRequestValue)(value, control.key, next))
5012
+ }
5013
+ )
5014
+ ] }, control.key);
5015
+ }),
5016
+ slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("fieldset", { className: "flex flex-col gap-1", "data-reference-role": slot.role, children: [
5017
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("legend", { className: "text-xs font-medium text-muted-foreground", title: slot.description, children: [
5018
+ slot.label,
5019
+ " ",
5020
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "tabular-nums", "data-reference-count": "", children: [
5021
+ "(",
5022
+ slot.references.length,
5023
+ "/",
5024
+ slot.max,
5025
+ ")"
5026
+ ] }),
5027
+ slot.min > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-destructive", children: " *" })
5028
+ ] }),
5029
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("ul", { className: "flex flex-wrap gap-1", children: [
5030
+ slot.references.map((ref) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex items-center gap-1 rounded border border-border px-1.5 py-0.5", "data-reference-id": ref.id, children: [
5031
+ renderReference ? renderReference(ref) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "max-w-[10rem] truncate", children: ref.id }),
5032
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
5033
+ "button",
5034
+ {
5035
+ type: "button",
5036
+ "aria-label": `Remove ${slot.label} ${ref.id}`,
5037
+ className: "text-muted-foreground hover:text-foreground",
5038
+ onClick: () => onChange((0, import_generation2.removeReference)(value, slot.role, ref.id)),
5039
+ children: "\xD7"
5040
+ }
5041
+ )
5042
+ ] }, ref.id)),
5043
+ onAddReference && slot.canAdd && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
5044
+ "button",
5045
+ {
5046
+ type: "button",
5047
+ className: "rounded border border-dashed border-border px-1.5 py-0.5 text-muted-foreground hover:text-foreground",
5048
+ onClick: () => onAddReference(slot.role),
5049
+ children: "Add"
5050
+ }
5051
+ ) })
5052
+ ] })
5053
+ ] }, slot.role)),
5054
+ showAdjustments && plan.adjustments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { role: "status", className: "flex flex-col gap-0.5 text-xs text-muted-foreground", "data-generation-adjustments": "", children: plan.adjustments.map((adjustment, index) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { "data-adjustment-field": adjustment.field, "data-adjustment-kind": adjustment.kind, children: adjustment.reason }, index)) }),
5055
+ showAdjustments && plan.errors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { role: "alert", className: "flex flex-col gap-0.5 text-xs text-destructive", "data-generation-errors": "", children: plan.errors.map((error, index) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { "data-error-field": error.field, children: error.message }, index)) })
5056
+ ] });
5057
+ };
5058
+
5059
+ // src/generation/JobProgress.tsx
5060
+ var import_jsx_runtime22 = require("react/jsx-runtime");
5061
+ var JobProgress = ({ title, className = "", ...options }) => {
5062
+ const progress = useJobProgress(options);
5063
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: `flex flex-col gap-1 text-sm ${className}`, "data-job-progress": "", "data-done": options.done ? "" : void 0, children: [
5064
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center justify-between gap-2 text-muted-foreground", children: [
5065
+ title && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "truncate text-foreground", children: title }),
5066
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "data-job-progress-label": "", "aria-live": "polite", children: progress.label })
5067
+ ] }),
5068
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5069
+ "div",
5070
+ {
5071
+ role: "progressbar",
5072
+ "aria-label": title ?? "Generation progress",
5073
+ "aria-valuemin": 0,
5074
+ "aria-valuemax": 100,
5075
+ "aria-valuenow": progress.percent,
5076
+ "aria-valuetext": `${progress.percent}%, ${progress.label}`,
5077
+ className: "h-1.5 w-full overflow-hidden rounded-full bg-muted",
5078
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5079
+ "div",
5080
+ {
5081
+ className: "h-full rounded-full bg-primary transition-[width] duration-500",
5082
+ style: { width: `${progress.percent}%`, background: "var(--primary, currentColor)" }
5083
+ }
5084
+ )
5085
+ }
5086
+ )
5087
+ ] });
5088
+ };
5089
+
5090
+ // src/find-replace/useFindReplace.ts
5091
+ var import_react38 = require("react");
5092
+ var import_editor14 = require("@pixerate/editor");
5093
+ var noopUnsubscribe2 = () => {
5094
+ };
5095
+ function useFindReplace(editor) {
5096
+ const store = (0, import_editor14.getFindReplaceStore)(editor ?? null);
5097
+ const subscribe = (0, import_react38.useCallback)(
5098
+ (onChange) => store ? store.subscribe(() => onChange()) : noopUnsubscribe2,
5099
+ [store]
5100
+ );
5101
+ const getSnapshot = () => store?.getSnapshot() ?? import_editor14.EMPTY_FIND_REPLACE_SNAPSHOT;
5102
+ const snapshot = (0, import_react38.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
5103
+ return (0, import_react38.useMemo)(() => {
5104
+ const run = (fn) => {
5105
+ if (editor && !editor.isDestroyed && store) fn(editor);
5106
+ };
5107
+ return {
5108
+ ...snapshot,
5109
+ available: !!store,
5110
+ matchCount: snapshot.matches.length,
5111
+ show: (mode = "find") => run((e) => e.commands.openFindReplace({ mode })),
5112
+ close: () => run((e) => e.commands.closeFindReplace()),
5113
+ setQuery: (query) => run((e) => e.commands.setSearch({ query })),
5114
+ setReplacement: (replacement) => run((e) => e.commands.setSearch({ replacement })),
5115
+ setOptions: (options) => run((e) => e.commands.setSearch({ options })),
5116
+ toggleOption: (option) => run((e) => e.commands.setSearch({ options: { [option]: !snapshot.options[option] } })),
5117
+ next: () => run((e) => e.commands.findNext()),
5118
+ previous: () => run((e) => e.commands.findPrevious()),
5119
+ replace: () => run((e) => e.commands.replaceCurrent()),
5120
+ replaceAll: () => run((e) => e.commands.replaceAll()),
5121
+ clear: () => run((e) => e.commands.clearSearch())
5122
+ };
5123
+ }, [snapshot, editor, store]);
5124
+ }
5125
+
5126
+ // src/find-replace/FindReplaceBar.tsx
5127
+ var import_react39 = require("react");
5128
+ var import_editor15 = require("@pixerate/editor");
5129
+ var import_jsx_runtime23 = require("react/jsx-runtime");
5130
+ var inputClass2 = "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";
5131
+ var buttonClass3 = "rounded px-2 py-1 text-xs text-foreground hover:bg-muted disabled:opacity-40 disabled:hover:bg-transparent";
5132
+ var toggleClass = (on) => `${buttonClass3} font-mono ${on ? "bg-primary/10 text-primary" : "text-muted-foreground"}`;
5133
+ var isMod = (e) => e.metaKey || e.ctrlKey;
5134
+ var FindReplaceBar = ({
5135
+ editor,
5136
+ className = "flex flex-col gap-1 rounded-md border border-border bg-background p-1 text-sm shadow-sm",
5137
+ alwaysOpen = false,
5138
+ showMoreOptions = true,
5139
+ onClose
5140
+ }) => {
5141
+ const find = useFindReplace(editor);
5142
+ const findInput = (0, import_react39.useRef)(null);
5143
+ const [moreOpen, setMoreOpen] = (0, import_react39.useState)(false);
5144
+ const visible = find.available && (alwaysOpen || find.open);
5145
+ (0, import_react39.useEffect)(() => {
5146
+ if (!visible || find.focusRequest === 0) return;
5147
+ findInput.current?.focus();
5148
+ findInput.current?.select();
5149
+ }, [visible, find.focusRequest]);
5150
+ if (!visible) return null;
5151
+ const showReplace = find.mode === "replace";
5152
+ const status = (0, import_editor15.formatFindStatus)(find);
5153
+ const hasMatches = find.matchCount > 0;
5154
+ const close = () => {
5155
+ find.close();
5156
+ editor?.commands.focus();
5157
+ onClose?.();
5158
+ };
5159
+ const onBarKeyDown = (e) => {
5160
+ if (e.key === "Escape") {
5161
+ e.preventDefault();
5162
+ close();
5163
+ } else if (isMod(e) && (e.key === "f" || e.key === "F")) {
5164
+ e.preventDefault();
5165
+ find.show("find");
5166
+ } else if (isMod(e) && (e.key === "h" || e.key === "H")) {
5167
+ e.preventDefault();
5168
+ find.show("replace");
5169
+ }
5170
+ };
5171
+ const onFindKeyDown = (e) => {
5172
+ if (e.key !== "Enter") return;
5173
+ e.preventDefault();
5174
+ if (e.shiftKey) find.previous();
5175
+ else find.next();
5176
+ };
5177
+ const onReplaceKeyDown = (e) => {
5178
+ if (e.key !== "Enter") return;
5179
+ e.preventDefault();
5180
+ if (isMod(e)) find.replaceAll();
5181
+ else find.replace();
5182
+ };
5183
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className, role: "search", "aria-label": "Find and replace", onKeyDown: onBarKeyDown, children: [
5184
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center gap-1", children: [
5185
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5186
+ "button",
5187
+ {
5188
+ type: "button",
5189
+ className: buttonClass3,
5190
+ "aria-label": "Toggle replace",
5191
+ "aria-expanded": showReplace,
5192
+ onClick: () => find.show(showReplace ? "find" : "replace"),
5193
+ children: showReplace ? "\u25BE" : "\u25B8"
5194
+ }
5195
+ ),
5196
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5197
+ "input",
5198
+ {
5199
+ ref: findInput,
5200
+ type: "text",
5201
+ className: inputClass2,
5202
+ placeholder: "Find",
5203
+ "aria-label": "Find",
5204
+ "aria-invalid": !!find.error,
5205
+ value: find.query,
5206
+ onChange: (e) => find.setQuery(e.target.value),
5207
+ onKeyDown: onFindKeyDown
5208
+ }
5209
+ ),
5210
+ import_editor15.FIND_OPTION_TOGGLES.filter((t) => !t.advanced).map((t) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5211
+ "button",
5212
+ {
5213
+ type: "button",
5214
+ className: toggleClass(!!find.options[t.key]),
5215
+ "aria-label": t.label,
5216
+ title: t.label,
5217
+ "aria-pressed": !!find.options[t.key],
5218
+ onClick: () => find.toggleOption(t.key),
5219
+ children: t.short
5220
+ },
5221
+ t.key
5222
+ )),
5223
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5224
+ "span",
5225
+ {
5226
+ className: `min-w-[5rem] px-1 text-center text-xs ${find.error ? "text-destructive" : "text-muted-foreground"}`,
5227
+ "aria-live": "polite",
5228
+ role: find.error ? "alert" : "status",
5229
+ "data-testid": "find-status",
5230
+ children: status
5231
+ }
5232
+ ),
5233
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: buttonClass3, "aria-label": "Previous match", disabled: !hasMatches, onClick: find.previous, children: "\u2191" }),
5234
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: buttonClass3, "aria-label": "Next match", disabled: !hasMatches, onClick: find.next, children: "\u2193" }),
5235
+ showMoreOptions && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5236
+ "button",
5237
+ {
5238
+ type: "button",
5239
+ className: buttonClass3,
5240
+ "aria-label": "More options",
5241
+ "aria-expanded": moreOpen,
5242
+ onClick: () => setMoreOpen((v) => !v),
5243
+ children: "\u2026"
5244
+ }
5245
+ ),
5246
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: buttonClass3, "aria-label": "Close find", onClick: close, children: "\xD7" })
5247
+ ] }),
5248
+ showReplace && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center gap-1 pl-7", children: [
5249
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5250
+ "input",
5251
+ {
5252
+ type: "text",
5253
+ className: inputClass2,
5254
+ placeholder: "Replace",
5255
+ "aria-label": "Replace",
5256
+ value: find.replacement,
5257
+ onChange: (e) => find.setReplacement(e.target.value),
5258
+ onKeyDown: onReplaceKeyDown
5259
+ }
5260
+ ),
5261
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5262
+ "button",
5263
+ {
5264
+ type: "button",
5265
+ className: buttonClass3,
5266
+ disabled: !hasMatches || find.currentIndex < 0,
5267
+ onClick: find.replace,
5268
+ children: "Replace"
5269
+ }
5270
+ ),
5271
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: buttonClass3, disabled: !hasMatches, onClick: find.replaceAll, children: "Replace all" }),
5272
+ find.lastReplaceCount !== null && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "px-1 text-xs text-muted-foreground", "aria-live": "polite", children: find.lastReplaceCount === 1 ? "1 replaced" : `${find.lastReplaceCount} replaced` })
5273
+ ] }),
5274
+ showMoreOptions && moreOpen && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 pl-7 text-xs text-muted-foreground", children: import_editor15.FIND_OPTION_TOGGLES.filter((t) => t.advanced).map((t) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { className: "flex items-center gap-1", children: [
5275
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5276
+ "input",
5277
+ {
5278
+ type: "checkbox",
5279
+ checked: !!find.options[t.key],
5280
+ onChange: () => find.toggleOption(t.key)
5281
+ }
5282
+ ),
5283
+ t.label
5284
+ ] }, t.key)) })
5285
+ ] });
5286
+ };
3793
5287
  // Annotate the CommonJS export names for ESM import in node:
3794
5288
  0 && (module.exports = {
5289
+ AdjustmentLayersPanel,
3795
5290
  BubbleMenu,
5291
+ CommandPalette,
3796
5292
  CommentButton,
3797
5293
  CommentsPanel,
5294
+ ControlSlider,
5295
+ CurvesEditor,
3798
5296
  DirtyTracker,
3799
5297
  EditorContent,
3800
5298
  EditorSlotsProvider,
5299
+ FindReplaceBar,
3801
5300
  FormulaHints,
5301
+ GenerationSettings,
5302
+ Histogram,
3802
5303
  HistoryManager,
3803
5304
  ImageEditor,
5305
+ JobProgress,
5306
+ MergePreview,
3804
5307
  OutlinePanel,
5308
+ RecoveryPrompt,
3805
5309
  ReviewToolbar,
3806
5310
  SpreadsheetContextMenu,
3807
5311
  SpreadsheetEditor,
3808
5312
  SpreadsheetGroupRow,
3809
5313
  TemplateRenderer,
3810
5314
  VariableForm,
5315
+ bindDirtyToHistory,
3811
5316
  createDirtyTracker,
3812
5317
  createHistoryManager,
3813
5318
  defaultIsEqual,
3814
5319
  useAgentToolProvider,
5320
+ useAutosave,
5321
+ useCommandPalette,
5322
+ useCommandShortcuts,
3815
5323
  useComments,
3816
5324
  useDismissGuard,
5325
+ useDocumentCompare,
3817
5326
  useEditor,
3818
5327
  useEditorSlots,
3819
5328
  useFileImport,
3820
5329
  useFillHandle,
5330
+ useFindReplace,
5331
+ useGenerationPlan,
3821
5332
  useHistory,
5333
+ useHistoryDirty,
3822
5334
  useHistoryShortcuts,
3823
5335
  useImageEditor,
5336
+ useImageHistogram,
5337
+ useJobProgress,
5338
+ useMergePreview,
3824
5339
  useNavigationGuard,
3825
5340
  useOutline,
3826
5341
  usePromptEditor,
5342
+ useRegisterCommands,
3827
5343
  useSpreadsheetClipboard,
3828
5344
  useSpreadsheetEditor,
3829
- useTrackChanges
5345
+ useTrackChanges,
5346
+ withoutDirty
3830
5347
  });
3831
5348
  //# sourceMappingURL=index.cjs.map