@pixerate/editor-react 0.68.0 → 0.69.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.
@@ -1,4 +1,4 @@
1
- import { PaintEditorController, PaintEditorState, PaintEditorOptions, PaintTool, SliderControl } from '@pixerate/editor/paint-editor';
1
+ import { PaintEditorController, PaintEditorState, PaintDirectorOptions, PaintPresence, PaintDirector, PaintEditorOptions, PaintTool, PaintView, SliderControl } from '@pixerate/editor/paint-editor';
2
2
  import React__default from 'react';
3
3
 
4
4
  interface UsePaintEditorReturn {
@@ -13,6 +13,15 @@ interface UsePaintEditorReturn {
13
13
  declare function usePaintEditor(options?: PaintEditorOptions | PaintEditorController): UsePaintEditorReturn;
14
14
  /** Subscribes to a controller's state. */
15
15
  declare function usePaintEditorState(controller: PaintEditorController): PaintEditorState;
16
+ /**
17
+ * A `PaintPresence` and `PaintDirector` for a paint editor: pass `presence` to `PaintEditor` (or
18
+ * `PaintCanvas`) and `director` to `createPaintEditorToolProvider`, so agent strokes are painted
19
+ * live by a cursor on the canvas. Options are read on the first render.
20
+ */
21
+ declare function usePaintDirector(controller: PaintEditorController, options?: Omit<PaintDirectorOptions, 'clock'>): {
22
+ presence: PaintPresence;
23
+ director: PaintDirector;
24
+ };
16
25
 
17
26
  interface PaintCanvasProps {
18
27
  controller: PaintEditorController;
@@ -24,6 +33,8 @@ interface PaintCanvasProps {
24
33
  fitOnMount?: boolean;
25
34
  /** Called with a refusal (hidden or locked layer). */
26
35
  onError?: (message: string | null) => void;
36
+ /** Agent cursors to draw over the canvas (see `PaintDirector` and `usePaintDirector`). */
37
+ presence?: PaintPresence;
27
38
  className?: string;
28
39
  }
29
40
  /**
@@ -34,6 +45,22 @@ interface PaintCanvasProps {
34
45
  * rotation. Guides and floating reference images are drawn on top.
35
46
  */
36
47
  declare const PaintCanvas: React__default.FC<PaintCanvasProps>;
48
+ /**
49
+ * Agent cursors and notes over the canvas. Positions are document pixels, placed through the view
50
+ * (zoom, pan, rotation, mirroring), while the cursors themselves stay upright and unscaled.
51
+ */
52
+ declare const PaintPresenceLayer: React__default.FC<{
53
+ presence: PaintPresence;
54
+ view: PaintView;
55
+ canvas: {
56
+ width: number;
57
+ height: number;
58
+ };
59
+ viewport: {
60
+ width: number;
61
+ height: number;
62
+ };
63
+ }>;
37
64
 
38
65
  interface PaintToolbarProps {
39
66
  controller: PaintEditorController;
@@ -108,6 +135,8 @@ interface PaintEditorProps {
108
135
  fileName?: string;
109
136
  /** Height of the editor (default 720 px). */
110
137
  height?: number | string;
138
+ /** Agent cursors to draw over the canvas (see `usePaintDirector`). */
139
+ presence?: PaintPresence;
111
140
  className?: string;
112
141
  }
113
142
  /**
@@ -127,4 +156,4 @@ declare const PaintSlider: React__default.FC<{
127
156
  onCommit?: (v: number) => void;
128
157
  }>;
129
158
 
130
- export { BrushPanel, type BrushPanelProps, ColorPanel, type ColorPanelProps, PaintCanvas, type PaintCanvasProps, PaintEditor, type PaintEditorProps, PaintLayersPanel, type PaintLayersPanelProps, PaintSlider, PaintToolbar, type PaintToolbarProps, PaperPanel, type PaperPanelProps, type UsePaintEditorReturn, usePaintEditor, usePaintEditorState };
159
+ export { BrushPanel, type BrushPanelProps, ColorPanel, type ColorPanelProps, PaintCanvas, type PaintCanvasProps, PaintEditor, type PaintEditorProps, PaintLayersPanel, type PaintLayersPanelProps, PaintPresenceLayer, PaintSlider, PaintToolbar, type PaintToolbarProps, PaperPanel, type PaperPanelProps, type UsePaintEditorReturn, usePaintDirector, usePaintEditor, usePaintEditorState };
@@ -1,6 +1,6 @@
1
1
  // src/paint-editor/usePaintEditor.ts
2
2
  import { useMemo, useState, useSyncExternalStore } from "react";
3
- import { PaintEditorController } from "@pixerate/editor/paint-editor";
3
+ import { PaintDirector, PaintEditorController, PaintPresence } from "@pixerate/editor/paint-editor";
4
4
  function usePaintEditor(options = {}) {
5
5
  const [controller] = useState(() => options instanceof PaintEditorController ? options : new PaintEditorController(options));
6
6
  const state = usePaintEditorState(controller);
@@ -13,19 +13,27 @@ function usePaintEditorState(controller) {
13
13
  () => controller.getState()
14
14
  );
15
15
  }
16
+ function usePaintDirector(controller, options = {}) {
17
+ const [value] = useState(() => {
18
+ const presence = new PaintPresence();
19
+ return { presence, director: new PaintDirector(controller, presence, options) };
20
+ });
21
+ return value;
22
+ }
16
23
 
17
24
  // src/paint-editor/PaintCanvas.tsx
18
- import { useEffect, useMemo as useMemo2, useRef, useState as useState2 } from "react";
25
+ import { useEffect, useMemo as useMemo2, useRef, useState as useState2, useSyncExternalStore as useSyncExternalStore2 } from "react";
19
26
  import {
20
27
  PaintCanvasPresenter,
21
28
  PaintGesture,
29
+ documentToScreen,
22
30
  fitZoom,
23
31
  guideOverlay,
24
32
  handlePaintShortcut,
25
33
  viewCssTransform
26
34
  } from "@pixerate/editor/paint-editor";
27
35
  import { jsx, jsxs } from "react/jsx-runtime";
28
- var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount = true, onError, className }) => {
36
+ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount = true, onError, presence, className }) => {
29
37
  const state = usePaintEditorState(controller);
30
38
  const viewport = useRef(null);
31
39
  const canvas = useRef(null);
@@ -140,6 +148,7 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
140
148
  predicted.length > 0 && /* @__PURE__ */ jsx("polyline", { points: predicted.map((p) => `${p.x},${p.y}`).join(" "), fill: "none", stroke: state.color, strokeOpacity: 0.5, strokeWidth: Math.max(1, state.brush.tip.size / 2), strokeLinecap: "round" })
141
149
  ] }),
142
150
  state.guides.references.filter((r) => r.visible).map((r) => /* @__PURE__ */ jsx(ReferencePanel, { controller, id: r.id }, r.id)),
151
+ presence && /* @__PURE__ */ jsx(PaintPresenceLayer, { presence, view: state.view, canvas: doc, viewport: size }),
143
152
  /* @__PURE__ */ jsxs("div", { className: "absolute bottom-1 right-2 text-[10px] text-slate-500 pointer-events-none", "data-testid": "paint-view-status", children: [
144
153
  Math.round(state.view.zoom * 100),
145
154
  "% \xB7 ",
@@ -151,6 +160,16 @@ var PaintCanvas = ({ controller, tool = "brush", showGuides = true, fitOnMount =
151
160
  }
152
161
  );
153
162
  };
163
+ var PaintPresenceLayer = ({ presence, view, canvas, viewport }) => {
164
+ const { actors } = useSyncExternalStore2(presence.subscribe, presence.getState, presence.getState);
165
+ return /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 overflow-hidden", "data-paint-presence": "", "aria-hidden": "true", children: actors.map((a) => {
166
+ const p = documentToScreen(view, canvas, viewport, a.x, a.y);
167
+ return /* @__PURE__ */ jsxs("div", { className: "absolute", style: { left: p.x, top: p.y }, "data-actor": a.id, children: [
168
+ /* @__PURE__ */ jsx("svg", { width: "18", height: "20", viewBox: "0 0 18 20", className: "drop-shadow-md", style: { transform: a.pressed ? "scale(0.85)" : void 0, transformOrigin: "0 0" }, children: /* @__PURE__ */ jsx("path", { d: "M0 0 L0 15 L4.5 10.8 L7.6 17 L10.2 15.8 L7.2 9.7 L13.5 9.4 Z", fill: a.color, stroke: "white", strokeWidth: "1.4", strokeLinejoin: "round" }) }),
169
+ /* @__PURE__ */ jsx("span", { className: "absolute left-4 top-4 w-max max-w-[240px] rounded-md px-2 py-0.5 text-[11px] font-semibold text-white shadow", style: { background: a.color }, children: a.message ?? a.name })
170
+ ] }, a.id);
171
+ }) });
172
+ };
154
173
  var ReferencePanel = ({ controller, id }) => {
155
174
  const state = usePaintEditorState(controller);
156
175
  const ref = state.guides.references.find((r) => r.id === id);
@@ -728,7 +747,7 @@ var ColorPanel = ({ controller, wheelSize = 168, mixingPad = true, className })
728
747
 
729
748
  // src/paint-editor/PaintLayersPanel.tsx
730
749
  import { useState as useState6 } from "react";
731
- import { BLEND_MODES } from "@pixerate/editor/paint-editor";
750
+ import { BLEND_MENU_GROUPS } from "@pixerate/editor/paint-editor";
732
751
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
733
752
  function siblingIndex(layers, l) {
734
753
  const sibs = layers.filter((x) => x.parentId === l.parentId).reverse();
@@ -825,7 +844,7 @@ var PaintLayersPanel = ({ controller, className }) => {
825
844
  /* @__PURE__ */ jsx7("span", { className: "w-14 text-slate-400", children: "Blend" }),
826
845
  /* @__PURE__ */ jsxs7("select", { "aria-label": "Layer blend", value: active.blend, onChange: (e) => attempt(() => controller.updateLayer(active.id, { blend: e.target.value })), className: "flex-1 rounded bg-slate-800 px-1 py-0.5", children: [
827
846
  active.type === "group" && /* @__PURE__ */ jsx7("option", { value: "pass-through", children: "Pass through" }),
828
- BLEND_MODES.map((m) => /* @__PURE__ */ jsx7("option", { value: m.id, children: m.label }, m.id))
847
+ BLEND_MENU_GROUPS.map((g) => /* @__PURE__ */ jsx7("optgroup", { label: g.label, children: g.modes.map((m) => /* @__PURE__ */ jsx7("option", { value: m.id, title: m.description, children: m.label }, m.id)) }, g.label))
829
848
  ] })
830
849
  ] }),
831
850
  /* @__PURE__ */ jsxs7("div", { className: "flex gap-1", children: [
@@ -840,7 +859,7 @@ var PaintLayersPanel = ({ controller, className }) => {
840
859
  // src/paint-editor/PaintEditor.tsx
841
860
  import { useState as useState7 } from "react";
842
861
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
843
- var PaintEditor = ({ controller, fileName, height = 720, className }) => {
862
+ var PaintEditor = ({ controller, fileName, height = 720, presence, className }) => {
844
863
  const [tool, setTool] = useState7("brush");
845
864
  const [tab, setTab] = useState7("color");
846
865
  const [error, setError] = useState7(null);
@@ -849,7 +868,7 @@ var PaintEditor = ({ controller, fileName, height = 720, className }) => {
849
868
  /* @__PURE__ */ jsxs8("div", { className: "flex min-h-0 flex-1 gap-2", children: [
850
869
  /* @__PURE__ */ jsx8(BrushPanel, { controller, className: "w-60 shrink-0 overflow-y-auto pr-1" }),
851
870
  /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-1 flex-col", children: [
852
- /* @__PURE__ */ jsx8(PaintCanvas, { controller, tool, onError: setError, className: "flex-1 rounded-lg" }),
871
+ /* @__PURE__ */ jsx8(PaintCanvas, { controller, tool, onError: setError, presence, className: "flex-1 rounded-lg" }),
853
872
  error && /* @__PURE__ */ jsx8("p", { role: "alert", className: "mt-1 text-[11px] text-rose-400", children: error })
854
873
  ] }),
855
874
  /* @__PURE__ */ jsxs8("div", { className: "flex w-64 shrink-0 flex-col gap-2 overflow-y-auto pr-1", children: [
@@ -867,9 +886,11 @@ export {
867
886
  PaintCanvas,
868
887
  PaintEditor,
869
888
  PaintLayersPanel,
889
+ PaintPresenceLayer,
870
890
  PaintSlider,
871
891
  PaintToolbar,
872
892
  PaperPanel,
893
+ usePaintDirector,
873
894
  usePaintEditor,
874
895
  usePaintEditorState
875
896
  };