@pixerate/editor-svelte 0.188.0 → 0.189.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.
@@ -10,11 +10,13 @@
10
10
  viewCssTransform,
11
11
  type PaintEditorController,
12
12
  type PaintPresence,
13
+ type PaintProposals,
13
14
  type PaintTool,
14
15
  } from '@pixerate/editor/paint-editor';
15
16
  import { untrack } from 'svelte';
16
17
  import { paintEditorState } from './paintEditorRune.svelte.js';
17
18
  import PaintPresenceLayer from './PaintPresenceLayer.svelte';
19
+ import PaintProposalGhosts from './PaintProposalGhosts.svelte';
18
20
  import ReferencePanel from './ReferencePanel.svelte';
19
21
 
20
22
  interface Props {
@@ -29,9 +31,11 @@
29
31
  onerror?: (message: string | null) => void;
30
32
  /** Agent cursors to draw over the canvas (see `PaintDirector` and `createPaintDirector`). */
31
33
  presence?: PaintPresence;
34
+ /** Paint an agent suggests, drawn over the canvas as ghosts (see `createPaintProposalState`). */
35
+ proposals?: PaintProposals;
32
36
  class?: string;
33
37
  }
34
- let { controller, tool = 'brush', showGuides = true, fitOnMount = true, onerror, presence, class: className = '' }: Props = $props();
38
+ let { controller, tool = 'brush', showGuides = true, fitOnMount = true, onerror, presence, proposals, class: className = '' }: Props = $props();
35
39
 
36
40
  const editor = paintEditorState(() => controller);
37
41
  const snap = $derived(editor.current);
@@ -201,6 +205,7 @@
201
205
  {#each snap.guides.references.filter((r) => r.visible) as r (r.id)}
202
206
  <ReferencePanel {controller} reference={r} />
203
207
  {/each}
208
+ {#if proposals}<PaintProposalGhosts {proposals} {transform} zoom={snap.view.zoom} />{/if}
204
209
  {#if presence}<PaintPresenceLayer {presence} view={snap.view} canvas={doc} viewport={size} />{/if}
205
210
  <div class="absolute bottom-1 right-2 text-[10px] text-slate-500 pointer-events-none" data-testid="paint-view-status">
206
211
  {Math.round(snap.view.zoom * 100)}% · {Math.round(snap.view.rotation)}°{snap.view.mirror ? ' · mirrored' : ''}
@@ -1,4 +1,4 @@
1
- import { type PaintEditorController, type PaintPresence, type PaintTool } from '@pixerate/editor/paint-editor';
1
+ import { type PaintEditorController, type PaintPresence, type PaintProposals, type PaintTool } from '@pixerate/editor/paint-editor';
2
2
  interface Props {
3
3
  controller: PaintEditorController;
4
4
  /** The active tool (default brush). */
@@ -11,6 +11,8 @@ interface Props {
11
11
  onerror?: (message: string | null) => void;
12
12
  /** Agent cursors to draw over the canvas (see `PaintDirector` and `createPaintDirector`). */
13
13
  presence?: PaintPresence;
14
+ /** Paint an agent suggests, drawn over the canvas as ghosts (see `createPaintProposalState`). */
15
+ proposals?: PaintProposals;
14
16
  class?: string;
15
17
  }
16
18
  declare const PaintCanvas: import("svelte").Component<Props, {}, "">;
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { PaintPresence, documentToScreen, screenToDocument, type PaintEditorController, type PaintTool } from '@pixerate/editor/paint-editor';
2
+ import { PaintPresence, documentToScreen, screenToDocument, type PaintEditorController, type PaintProposals, type PaintTool } from '@pixerate/editor/paint-editor';
3
3
  import type { AgentPlayback, PlaybackPoint, PlaybackRect, PlaybackRef } from '@pixerate/editor/tools';
4
4
  import { createPlaybackSurface, getAgentPlayback } from '../playback/playback.svelte.js';
5
5
  import PresenceLayer from '../playback/PresenceLayer.svelte';
@@ -23,6 +23,8 @@
23
23
  height?: number | string;
24
24
  /** Agent cursors to draw over the canvas (see `createPaintDirector`). */
25
25
  presence?: PaintPresence;
26
+ /** Paint an agent suggests, drawn over the canvas as ghosts (see `createPaintProposalState`). */
27
+ proposals?: PaintProposals;
26
28
  class?: string;
27
29
  /**
28
30
  * Agent playback: act out the paint tools' calls here with the agent's cursor, opening the
@@ -35,7 +37,7 @@
35
37
  /** Bring the editor on screen (switch to its tab) before the agent acts. */
36
38
  onplaybackreveal?: () => void | Promise<void>;
37
39
  }
38
- let { controller, fileName, height = 720, presence, class: className = '', playback, playbackId = 'paint', onplaybackreveal }: Props = $props();
40
+ let { controller, fileName, height = 720, presence, proposals, class: className = '', playback, playbackId = 'paint', onplaybackreveal }: Props = $props();
39
41
 
40
42
  /** Controls a paint plan can point at, by `{ kind: 'control', id }`. */
41
43
  const PAINT_CONTROLS: Record<string, string> = {
@@ -116,7 +118,7 @@
116
118
  <BrushPanel {controller} class="w-60 shrink-0 overflow-y-auto pr-1" />
117
119
  <div class="flex min-w-0 flex-1 flex-col">
118
120
  <!-- With playback the editor draws the cursor (over the panels too), so the canvas doesn't. -->
119
- <PaintCanvas {controller} {tool} presence={playing ? undefined : presence} onerror={(e) => (error = e)} class="flex-1 rounded-lg" />
121
+ <PaintCanvas {controller} {tool} presence={playing ? undefined : presence} {proposals} onerror={(e) => (error = e)} class="flex-1 rounded-lg" />
120
122
  {#if error}<p role="alert" class="mt-1 text-[11px] text-rose-400">{error}</p>{/if}
121
123
  </div>
122
124
  <div class="flex w-64 shrink-0 flex-col gap-2 overflow-y-auto pr-1">
@@ -1,4 +1,4 @@
1
- import { PaintPresence, type PaintEditorController } from '@pixerate/editor/paint-editor';
1
+ import { PaintPresence, type PaintEditorController, type PaintProposals } from '@pixerate/editor/paint-editor';
2
2
  import type { AgentPlayback } from '@pixerate/editor/tools';
3
3
  interface Props {
4
4
  controller: PaintEditorController;
@@ -8,6 +8,8 @@ interface Props {
8
8
  height?: number | string;
9
9
  /** Agent cursors to draw over the canvas (see `createPaintDirector`). */
10
10
  presence?: PaintPresence;
11
+ /** Paint an agent suggests, drawn over the canvas as ghosts (see `createPaintProposalState`). */
12
+ proposals?: PaintProposals;
11
13
  class?: string;
12
14
  /**
13
15
  * Agent playback: act out the paint tools' calls here with the agent's cursor, opening the
@@ -0,0 +1,46 @@
1
+ <script lang="ts">
2
+ import { paintProposalGhost, type PaintProposals, type PaintProposalsState } from '@pixerate/editor/paint-editor';
3
+
4
+ interface Props {
5
+ proposals: PaintProposals;
6
+ /** The view's CSS transform (`viewCssTransform`). */
7
+ transform: string;
8
+ zoom: number;
9
+ }
10
+ let { proposals, transform, zoom }: Props = $props();
11
+
12
+ let pending = $state.raw<PaintProposalsState>({ proposals: [] });
13
+ $effect(() => {
14
+ pending = proposals.getState();
15
+ return proposals.subscribe((next) => {
16
+ pending = next;
17
+ });
18
+ });
19
+
20
+ /** Draws a ghost's pixels into its canvas. */
21
+ function ghost(node: HTMLCanvasElement, data: Uint8ClampedArray) {
22
+ const draw = (d: Uint8ClampedArray) => {
23
+ const ctx = node.getContext('2d');
24
+ if (ctx && typeof ImageData !== 'undefined') ctx.putImageData(new ImageData(new Uint8ClampedArray(d), node.width, node.height), 0, 0);
25
+ };
26
+ draw(data);
27
+ return { update: draw };
28
+ }
29
+ </script>
30
+
31
+ <!-- Pending paint proposals drawn over the canvas: each ghost where its paint would go, translucent with a dashed edge. -->
32
+ {#each pending.proposals as p (p.id)}
33
+ {@const g = paintProposalGhost(p)}
34
+ {#if g}
35
+ <canvas
36
+ use:ghost={g.data}
37
+ width={g.width}
38
+ height={g.height}
39
+ data-paint-proposal={p.id}
40
+ title={p.rationale ? `${p.title}: ${p.rationale}` : p.title}
41
+ aria-label={`Suggested: ${p.title}`}
42
+ class="absolute left-0 top-0 pointer-events-none"
43
+ style={`transform: ${transform} translate(${g.x}px, ${g.y}px); transform-origin: 0 0; opacity: 0.7; outline: ${1.5 / zoom}px dashed #a78bfa; outline-offset: ${2 / zoom}px`}
44
+ ></canvas>
45
+ {/if}
46
+ {/each}
@@ -0,0 +1,10 @@
1
+ import { type PaintProposals } from '@pixerate/editor/paint-editor';
2
+ interface Props {
3
+ proposals: PaintProposals;
4
+ /** The view's CSS transform (`viewCssTransform`). */
5
+ transform: string;
6
+ zoom: number;
7
+ }
8
+ declare const PaintProposalGhosts: import("svelte").Component<Props, {}, "">;
9
+ type PaintProposalGhosts = ReturnType<typeof PaintProposalGhosts>;
10
+ export default PaintProposalGhosts;
@@ -31,6 +31,8 @@ export { default as PaintEffectsPanel } from './PaintEffectsPanel.svelte';
31
31
  export { default as PaintGradientEditor } from './PaintGradientEditor.svelte';
32
32
  export { default as PaintSlider } from './PaintSlider.svelte';
33
33
  export { default as PaintPresenceLayer } from './PaintPresenceLayer.svelte';
34
+ export { default as PaintProposalGhosts } from './PaintProposalGhosts.svelte';
35
+ export { createPaintChangeWatcher, createPaintProposalState, type PaintChangeWatcherRune, type PaintProposalStateRune } from './paintProposalRune.svelte.js';
34
36
  export { createPaintDirector, createReactivePaintEditor, paintEditorState, paintEffectsProvider, usePaintEffectsTools } from './paintEditorRune.svelte.js';
35
37
  export type PaintEditorProps = ComponentProps<typeof PaintEditorComponent>;
36
38
  export type PaintCanvasProps = ComponentProps<typeof PaintCanvasComponent>;
@@ -16,4 +16,6 @@ export { default as PaintEffectsPanel } from './PaintEffectsPanel.svelte';
16
16
  export { default as PaintGradientEditor } from './PaintGradientEditor.svelte';
17
17
  export { default as PaintSlider } from './PaintSlider.svelte';
18
18
  export { default as PaintPresenceLayer } from './PaintPresenceLayer.svelte';
19
+ export { default as PaintProposalGhosts } from './PaintProposalGhosts.svelte';
20
+ export { createPaintChangeWatcher, createPaintProposalState } from './paintProposalRune.svelte.js';
19
21
  export { createPaintDirector, createReactivePaintEditor, paintEditorState, paintEffectsProvider, usePaintEffectsTools } from './paintEditorRune.svelte.js';
@@ -0,0 +1,32 @@
1
+ import { type PaintChange, type PaintChangeFeed, type PaintEditorController, type PaintProposal, type PaintProposals, type PaintProposalsOptions } from '@pixerate/editor/paint-editor';
2
+ export interface PaintProposalStateRune {
3
+ /** The framework-free store (for `createPaintProposalToolProvider` and agents). */
4
+ readonly store: PaintProposals;
5
+ /** Pending proposals (reactive); pass `store` to `PaintCanvas` (`proposals`) to draw them as ghosts. */
6
+ readonly proposals: readonly PaintProposal[];
7
+ propose: PaintProposals['propose'];
8
+ accept: PaintProposals['accept'];
9
+ reject: PaintProposals['reject'];
10
+ clear: PaintProposals['clear'];
11
+ }
12
+ /**
13
+ * Paint an agent suggests without painting it, as a Svelte 5 rune: ghosts the user accepts
14
+ * (painted on a layer of their own as one undo step, made by the proposer) or dismisses. Pass
15
+ * `store` to `PaintCanvas` as `proposals` to draw the ghosts, and to
16
+ * `createPaintProposalToolProvider` for agents. The same API as React's `usePaintProposals`.
17
+ */
18
+ export declare function createPaintProposalState(controller: PaintEditorController, options?: Omit<PaintProposalsOptions, 'controller'>): PaintProposalStateRune;
19
+ export interface PaintChangeWatcherRune {
20
+ readonly feed: PaintChangeFeed;
21
+ /** The latest change (reactive). */
22
+ readonly last: PaintChange | null;
23
+ }
24
+ /**
25
+ * What changed in the painting, as a Svelte 5 rune: strokes, fills and layers added, removed or
26
+ * changed, and the start and end of each stroke, with who made it. For agents that paint
27
+ * alongside the user. Call during component initialisation; the feed stops on unmount. The same
28
+ * API as React's `usePaintChangeFeed`.
29
+ */
30
+ export declare function createPaintChangeWatcher(controller: PaintEditorController, options?: {
31
+ onChange?: (change: PaintChange) => void;
32
+ }): PaintChangeWatcherRune;
@@ -0,0 +1,45 @@
1
+ import { createPaintChangeFeed, createPaintProposals, } from '@pixerate/editor/paint-editor';
2
+ /**
3
+ * Paint an agent suggests without painting it, as a Svelte 5 rune: ghosts the user accepts
4
+ * (painted on a layer of their own as one undo step, made by the proposer) or dismisses. Pass
5
+ * `store` to `PaintCanvas` as `proposals` to draw the ghosts, and to
6
+ * `createPaintProposalToolProvider` for agents. The same API as React's `usePaintProposals`.
7
+ */
8
+ export function createPaintProposalState(controller, options = {}) {
9
+ const store = createPaintProposals({ ...options, controller });
10
+ let state = $state.raw(store.getState());
11
+ store.subscribe((next) => {
12
+ state = next;
13
+ });
14
+ return {
15
+ store,
16
+ get proposals() {
17
+ return state.proposals;
18
+ },
19
+ propose: store.propose,
20
+ accept: store.accept,
21
+ reject: store.reject,
22
+ clear: store.clear,
23
+ };
24
+ }
25
+ /**
26
+ * What changed in the painting, as a Svelte 5 rune: strokes, fills and layers added, removed or
27
+ * changed, and the start and end of each stroke, with who made it. For agents that paint
28
+ * alongside the user. Call during component initialisation; the feed stops on unmount. The same
29
+ * API as React's `usePaintChangeFeed`.
30
+ */
31
+ export function createPaintChangeWatcher(controller, options = {}) {
32
+ const feed = createPaintChangeFeed(controller);
33
+ let last = $state.raw(null);
34
+ feed.subscribe((change) => {
35
+ last = change;
36
+ options.onChange?.(change);
37
+ });
38
+ $effect(() => () => feed.dispose());
39
+ return {
40
+ feed,
41
+ get last() {
42
+ return last;
43
+ },
44
+ };
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-svelte",
3
- "version": "0.188.0",
3
+ "version": "0.189.0",
4
4
  "description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -175,7 +175,7 @@
175
175
  "**/*.css"
176
176
  ],
177
177
  "dependencies": {
178
- "@pixerate/editor": "0.255.0"
178
+ "@pixerate/editor": "0.256.0"
179
179
  },
180
180
  "peerDependencies": {
181
181
  "@dagrejs/dagre": "^1.1.4",