@pixerate/editor-svelte 0.116.0 → 0.117.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.
@@ -16,6 +16,8 @@
16
16
  handlePaintShortcut,
17
17
  pointerPressure,
18
18
  FreeTransformTool,
19
+ boxFrameHit,
20
+ boxFrameOverlay,
19
21
  getViewportProjection,
20
22
  measureCanvasViewport,
21
23
  watchCanvasViewport,
@@ -26,6 +28,7 @@
26
28
  type SelectionToolOptions,
27
29
  type ShapeToolOptions,
28
30
  type TextEditSession,
31
+ type TransformOverlay,
29
32
  type TypeToolOptions,
30
33
  } from '@pixerate/editor/image-editor';
31
34
  import { untrack } from 'svelte';
@@ -343,6 +346,63 @@
343
346
  return () => window.removeEventListener('keydown', onKey);
344
347
  });
345
348
 
349
+ /** How far outside a frame (in hit tolerances) a drag rotates instead of picking a layer. */
350
+ const ROTATE_BAND = 4;
351
+
352
+ /**
353
+ * Transform mode, with no transform open: the active layer's frame, drawn as a transform's.
354
+ * A layer locked in place gets the outline without handles.
355
+ */
356
+ function layerFrame(): TransformOverlay | null {
357
+ if (mode !== 'transform' || transformTool.active) return null;
358
+ const c = editor.controller;
359
+ const id = c.getState().activeLayerId;
360
+ const layer = id ? c.getLayer(id) : undefined;
361
+ if (!layer || layer.visible === false) return null;
362
+ const bounds = c.getLayerBounds(layer.id);
363
+ if (!bounds) return null;
364
+ return boxFrameOverlay(bounds, !(layer.locks?.all || layer.locks?.position));
365
+ }
366
+
367
+ /**
368
+ * Transform mode: a drag on the active layer's frame transforms it, and a press elsewhere picks
369
+ * the layer there (or none) and drags that. Each drag opens a Free Transform and closes it on
370
+ * release, one undo step when it changed something, so other edits never meet an open one.
371
+ */
372
+ let frameDrag = false;
373
+ function handleFramePointerDown(e: PointerEvent): boolean {
374
+ if (mode !== 'transform' || transformTool.active || !canvasEl) return false;
375
+ // Middle-button and Space drags pan (see handleMouseDown).
376
+ if (e.button !== 0 || (viewportGestures && isSpacePressed)) return false;
377
+ const c = editor.controller;
378
+ const { pt, tolerance } = transformPoint(e);
379
+ const frame = layerFrame();
380
+ let target = frame && boxFrameHit(frame, pt, tolerance, tolerance * ROTATE_BAND) ? c.getState().activeLayerId : null;
381
+ if (!target) {
382
+ target = c.layerAt(pt);
383
+ c.setActiveLayer(target);
384
+ }
385
+ const layer = target ? c.getLayer(target) : undefined;
386
+ if (!layer || layer.locks?.all || layer.locks?.position) return true;
387
+ e.preventDefault();
388
+ if (transformTool.begin({ layerId: layer.id })) return true;
389
+ try {
390
+ canvasEl.setPointerCapture?.(e.pointerId);
391
+ } catch {
392
+ // Synthetic events have no active pointer to capture.
393
+ }
394
+ transformTool.down(pt, tolerance);
395
+ frameDrag = true;
396
+ return true;
397
+ }
398
+ function endFrameDrag(cancelled = false) {
399
+ if (!frameDrag) return;
400
+ frameDrag = false;
401
+ if (!transformTool.active) return;
402
+ if (!cancelled && transformTool.modified) transformTool.commit();
403
+ else transformTool.cancel();
404
+ }
405
+
346
406
  /** The document point of a pointer event and the hit tolerance (6 screen px). */
347
407
  function transformPoint(e: PointerEvent) {
348
408
  const rect = canvasEl!.getBoundingClientRect();
@@ -448,7 +508,7 @@
448
508
  mode === 'paint' && brushCursor && editor.controller.getState().activeTool !== 'move' && !transformTool.active
449
509
  ? { ...brushCursor, diameter: editor.controller.getState().paint.brush.size }
450
510
  : null,
451
- freeTransform: transformTool.overlay(),
511
+ freeTransform: transformTool.overlay() ?? layerFrame(),
452
512
  textEdit: typeTool.session?.alive ? typeTool.session.overlay() : null,
453
513
  });
454
514
  }
@@ -468,6 +528,7 @@
468
528
  }
469
529
  function handlePaintPointerDown(e: PointerEvent) {
470
530
  if (handleTransformPointerDown(e)) return;
531
+ if (handleFramePointerDown(e)) return;
471
532
  if (mode !== 'paint' || e.button !== 0 || !canvasEl) return;
472
533
  e.preventDefault();
473
534
  try {
@@ -479,6 +540,12 @@
479
540
  }
480
541
  function handlePaintPointerMove(e: PointerEvent) {
481
542
  if (handleTransformPointerMove(e)) return;
543
+ if (mode === 'transform' && canvasEl) {
544
+ const { pt, tolerance } = transformPoint(e);
545
+ const frame = layerFrame();
546
+ transformCursor = frame ? transformTool.cursor(boxFrameHit(frame, pt, tolerance, tolerance * ROTATE_BAND)) : 'default';
547
+ return;
548
+ }
482
549
  if (mode !== 'paint' || !canvasEl) return;
483
550
  const samples = paintSamples(e);
484
551
  brushCursor = samples[samples.length - 1] ?? null;
@@ -486,7 +553,10 @@
486
553
  else void renderCanvas();
487
554
  }
488
555
  function handlePaintPointerUp() {
489
- if (handleTransformPointerUp()) return;
556
+ if (handleTransformPointerUp()) {
557
+ endFrameDrag();
558
+ return;
559
+ }
490
560
  if (mode !== 'paint' || !paintGesture.active) return;
491
561
  paintGesture.up();
492
562
  if (paintGesture.error) report('paint', paintGesture.error);
@@ -552,23 +622,8 @@
552
622
  return;
553
623
  }
554
624
 
555
- if (mode === 'transform') {
556
- const canvasPt = getCanvasEventPoint(e);
557
- const imgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false);
558
- const hitLayerId = editor.controller.layerAt(imgPt);
559
- if (hitLayerId) {
560
- editor.controller.setActiveLayer(hitLayerId);
561
- if (!transformTool.active) {
562
- transformTool.begin();
563
- }
564
- } else {
565
- editor.controller.setActiveLayer(null);
566
- if (transformTool.active) {
567
- transformTool.cancel();
568
- }
569
- }
570
- return;
571
- }
625
+ // Transform mode picks and drags layers on pointerdown (handleFramePointerDown).
626
+ if (mode === 'transform') return;
572
627
 
573
628
  if (transformTool.active) return;
574
629
  const canvasPt = getCanvasEventPoint(e);
@@ -952,10 +1007,13 @@
952
1007
  onpointerdown={handlePaintPointerDown}
953
1008
  onpointermove={handlePaintPointerMove}
954
1009
  onpointerup={handlePaintPointerUp}
955
- onpointercancel={() => paintGesture.cancel()}
1010
+ onpointercancel={() => {
1011
+ paintGesture.cancel();
1012
+ endFrameDrag(true);
1013
+ }}
956
1014
  data-testid="image-editor-canvas"
957
- style="width: 100%; height: 100%;{transformActive ? ` cursor: ${transformCursor};` : ''}"
958
- class="absolute inset-0 w-full h-full block {transformActive ? 'touch-none' : mode === 'paint' ? 'touch-none cursor-crosshair' : mode === 'annotate' && editor.state.activeTool === 'type' ? 'cursor-text' : mode === 'annotate' && editor.state.activeTool === 'shape' ? 'cursor-crosshair' : ''}"
1015
+ style="width: 100%; height: 100%;{transformActive || mode === 'transform' ? ` cursor: ${transformCursor};` : ''}"
1016
+ class="absolute inset-0 w-full h-full block {transformActive || mode === 'transform' ? 'touch-none' : mode === 'paint' ? 'touch-none cursor-crosshair' : mode === 'annotate' && editor.state.activeTool === 'type' ? 'cursor-text' : mode === 'annotate' && editor.state.activeTool === 'shape' ? 'cursor-crosshair' : ''}"
959
1017
  ></canvas>
960
1018
  {#if typeSession}
961
1019
  <textarea
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-svelte",
3
- "version": "0.116.0",
3
+ "version": "0.117.0",
4
4
  "description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -160,7 +160,7 @@
160
160
  "@tiptap/extension-bubble-menu": "^2.27.3",
161
161
  "@tiptap/extension-placeholder": "^2.27.3",
162
162
  "@tiptap/starter-kit": "^2.27.3",
163
- "@pixerate/editor": "0.158.0"
163
+ "@pixerate/editor": "0.159.0"
164
164
  },
165
165
  "peerDependencies": {
166
166
  "@dagrejs/dagre": "^1.1.4",