@pixerate/editor-svelte 0.131.0 → 0.133.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.
@@ -18,9 +18,11 @@
18
18
  FreeTransformTool,
19
19
  boxFrameHit,
20
20
  quadFrameOverlay,
21
+ TRANSFORM_ROTATE_HANDLE_OFFSET,
21
22
  getViewportProjection,
22
23
  measureCanvasViewport,
23
24
  watchCanvasViewport,
25
+ startMarchingAnts,
24
26
  ShapeDrawGesture,
25
27
  TypeToolGesture,
26
28
  DEFAULT_SHAPE_TOOL_OPTIONS,
@@ -371,7 +373,8 @@
371
373
  if (!layer || layer.visible === false) return null;
372
374
  const corners = c.getLayerFrame(layer.id);
373
375
  if (!corners) return null;
374
- return quadFrameOverlay(corners, !(layer.locks?.all || layer.locks?.position));
376
+ const { scale } = getViewportProjection(c.getState(), view.width, view.height, false);
377
+ return quadFrameOverlay(corners, !(layer.locks?.all || layer.locks?.position), TRANSFORM_ROTATE_HANDLE_OFFSET / (scale || 1));
375
378
  }
376
379
 
377
380
  /**
@@ -499,10 +502,23 @@
499
502
  );
500
503
  });
501
504
 
505
+ // Marching ants: while a selection shows as ants, step the dash offset and redraw.
506
+ let antsOffset = 0;
507
+ const marching = $derived(!!editor.state.selection?.bounds && !editor.state.quickMask);
508
+ $effect(() => {
509
+ if (!marching) return;
510
+ return startMarchingAnts((offset) => {
511
+ antsOffset = offset;
512
+ void renderCanvas();
513
+ });
514
+ });
515
+
502
516
  async function renderCanvas(overrideAnnotation?: Annotation | null, overrideCrop?: CropBox | null) {
503
517
  if (!canvasEl || !containerEl) return;
504
518
  const viewport = measureCanvasViewport(containerEl);
505
519
  view = { width: viewport.width, height: viewport.height };
520
+ // The transform overlay's rotate handle sits a fixed distance out on screen.
521
+ transformTool.setView(getViewportProjection(editor.controller.getState(), view.width, view.height, mode === 'crop').scale);
506
522
 
507
523
  await editor.controller.render(canvasEl, {
508
524
  viewportWidth: viewport.width,
@@ -512,6 +528,7 @@
512
528
  fit,
513
529
  showCropOverlay: mode === 'crop',
514
530
  showSelectionOverlay: true,
531
+ antsOffset,
515
532
  interactive: true,
516
533
  draftAnnotation: overrideAnnotation !== undefined ? overrideAnnotation : draftAnnotation,
517
534
  draftCrop: overrideCrop !== undefined ? overrideCrop : draftCrop,
@@ -45,6 +45,29 @@
45
45
 
46
46
  let radius = $state(2);
47
47
 
48
+ // Select › Subject (only with a segmentation provider).
49
+ const canSegment = $derived.by(() => {
50
+ void tick;
51
+ return Boolean(controller.segmentationProvider);
52
+ });
53
+ let subject = $state('');
54
+ let segmenting = $state(false);
55
+ let subjectStatus = $state<string | null>(null);
56
+ async function selectSubject() {
57
+ const target = subject.trim();
58
+ if (!target || segmenting) return;
59
+ segmenting = true;
60
+ subjectStatus = null;
61
+ try {
62
+ const { segments } = await controller.selectSubject(target, { mode: options.mode });
63
+ subjectStatus = segments.length ? null : `Nothing matching “${target}” was found.`;
64
+ } catch (err) {
65
+ subjectStatus = err instanceof Error ? err.message : String(err);
66
+ } finally {
67
+ segmenting = false;
68
+ }
69
+ }
70
+
48
71
  const MODIFY_OPS: { op: SelectionModifyOp; label: string }[] = [
49
72
  { op: 'grow', label: 'Grow' },
50
73
  { op: 'shrink', label: 'Shrink' },
@@ -109,6 +132,33 @@
109
132
  </label>
110
133
  </div>
111
134
  {/if}
135
+ {#if canSegment}
136
+ <form
137
+ data-testid="select-subject"
138
+ class="flex flex-col gap-1"
139
+ onsubmit={(e) => {
140
+ e.preventDefault();
141
+ void selectSubject();
142
+ }}
143
+ >
144
+ <div class="flex gap-1">
145
+ <input
146
+ type="text"
147
+ aria-label="Subject to select"
148
+ placeholder="Subject, e.g. the dog"
149
+ bind:value={subject}
150
+ disabled={segmenting}
151
+ class="min-w-0 flex-1 bg-background border border-border rounded px-2 py-1 text-foreground"
152
+ />
153
+ <button type="submit" class={btn(false)} disabled={segmenting || !subject.trim()} aria-busy={segmenting}>
154
+ {segmenting ? 'Selecting…' : 'Select Subject'}
155
+ </button>
156
+ </div>
157
+ {#if subjectStatus}
158
+ <p role="status" class="text-muted-foreground">{subjectStatus}</p>
159
+ {/if}
160
+ </form>
161
+ {/if}
112
162
  <label class="flex items-center gap-2 text-foreground">
113
163
  <ImageCheckbox label="Anti-alias" checked={options.antiAlias} onchange={(on) => set({ antiAlias: on })} />
114
164
  Anti-alias
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-svelte",
3
- "version": "0.131.0",
3
+ "version": "0.133.0",
4
4
  "description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -165,7 +165,7 @@
165
165
  "**/*.css"
166
166
  ],
167
167
  "dependencies": {
168
- "@pixerate/editor": "0.178.0"
168
+ "@pixerate/editor": "0.180.0"
169
169
  },
170
170
  "peerDependencies": {
171
171
  "@dagrejs/dagre": "^1.1.4",