@pixerate/editor-svelte 0.156.0 → 0.157.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.
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
FreeTransformTool,
|
|
20
20
|
DEFAULT_SHAPE_TOOL_OPTIONS,
|
|
21
21
|
DEFAULT_TYPE_TOOL_OPTIONS,
|
|
22
|
+
imageToolLabel,
|
|
22
23
|
type SelectionToolOptions,
|
|
23
24
|
type ShapeToolOptions,
|
|
24
25
|
type TextEditSession,
|
|
@@ -702,16 +703,16 @@
|
|
|
702
703
|
|
|
703
704
|
{#if activeTab === 'annotate'}
|
|
704
705
|
<div class="flex flex-col gap-3 text-xs">
|
|
705
|
-
<span class={imageSectionTitleClass}>Shape & Type Layers</span>
|
|
706
|
+
<span class={imageSectionTitleClass}>Shape & Type Layers, Paths</span>
|
|
706
707
|
<div class="grid grid-cols-2 gap-1.5">
|
|
707
|
-
{#each ['shape', 'type'] as const as t (t)}
|
|
708
|
+
{#each ['shape', 'type', 'path-pen', 'path-select', 'direct-select'] as const as t (t)}
|
|
708
709
|
<button
|
|
709
710
|
type="button"
|
|
710
711
|
aria-pressed={editor.state.activeTool === t}
|
|
711
712
|
onclick={() => editor.setTool(t)}
|
|
712
713
|
class="py-1.5 px-2 rounded-lg font-medium border transition-colors {editor.state.activeTool === t
|
|
713
714
|
? 'bg-primary border-primary text-primary-foreground'
|
|
714
|
-
: 'bg-muted border-border text-foreground hover:bg-accent'}">{t
|
|
715
|
+
: 'bg-muted border-border text-foreground hover:bg-accent'}">{imageToolLabel(t)}</button>
|
|
715
716
|
{/each}
|
|
716
717
|
</div>
|
|
717
718
|
{#if editor.state.activeTool === 'shape'}
|
|
@@ -742,7 +743,7 @@
|
|
|
742
743
|
? 'bg-primary border-primary text-primary-foreground'
|
|
743
744
|
: 'bg-muted border-border text-foreground hover:bg-accent'}"
|
|
744
745
|
>
|
|
745
|
-
{t}
|
|
746
|
+
{t === 'pen' ? 'freehand' : t}
|
|
746
747
|
</button>
|
|
747
748
|
{/each}
|
|
748
749
|
</div>
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
handleQuickMaskKey,
|
|
28
28
|
handleMaskViewKey,
|
|
29
29
|
ShapeDrawGesture,
|
|
30
|
+
PathEditGesture,
|
|
31
|
+
isPathEditTool,
|
|
30
32
|
TypeToolGesture,
|
|
31
33
|
DEFAULT_SHAPE_TOOL_OPTIONS,
|
|
32
34
|
DEFAULT_TYPE_TOOL_OPTIONS,
|
|
@@ -279,6 +281,8 @@
|
|
|
279
281
|
|
|
280
282
|
// Shape and type tools; type is edited on the canvas through a hidden textarea.
|
|
281
283
|
const shapeGesture = $derived(new ShapeDrawGesture(editor.controller, () => shapeOptions));
|
|
284
|
+
// Path tools (Path Selection, Direct Selection, Pen): edit the active path, shape or vector mask.
|
|
285
|
+
const pathGesture = $derived(new PathEditGesture(editor.controller));
|
|
282
286
|
const typeTool = $derived(new TypeToolGesture(editor.controller, () => typeOptions));
|
|
283
287
|
let typeInputEl = $state<HTMLTextAreaElement | null>(null);
|
|
284
288
|
$effect(() => {
|
|
@@ -521,7 +525,10 @@
|
|
|
521
525
|
const viewport = measureCanvasViewport(containerEl);
|
|
522
526
|
view = { width: viewport.width, height: viewport.height };
|
|
523
527
|
// The transform overlay's rotate handle sits a fixed distance out on screen.
|
|
524
|
-
|
|
528
|
+
const viewScale = getViewportProjection(editor.controller.getState(), view.width, view.height, mode === 'crop').scale;
|
|
529
|
+
transformTool.setView(viewScale);
|
|
530
|
+
pathGesture.setView(viewScale);
|
|
531
|
+
const activeTool = editor.controller.getState().activeTool;
|
|
525
532
|
|
|
526
533
|
await editor.controller.render(canvasEl, {
|
|
527
534
|
viewportWidth: viewport.width,
|
|
@@ -542,6 +549,7 @@
|
|
|
542
549
|
: null,
|
|
543
550
|
freeTransform: transformTool.overlay() ?? layerFrame(),
|
|
544
551
|
textEdit: typeTool.session?.alive ? typeTool.session.overlay() : null,
|
|
552
|
+
pathEdit: mode === 'annotate' && isPathEditTool(activeTool) ? pathGesture.overlay(activeTool) : null,
|
|
545
553
|
});
|
|
546
554
|
}
|
|
547
555
|
|
|
@@ -623,6 +631,24 @@
|
|
|
623
631
|
return () => window.removeEventListener('keydown', onKey);
|
|
624
632
|
});
|
|
625
633
|
|
|
634
|
+
$effect(() => {
|
|
635
|
+
if (mode !== 'annotate' || typeof window === 'undefined') return;
|
|
636
|
+
const gesture = pathGesture;
|
|
637
|
+
// The path tools: Delete removes the selected anchors, Enter / Escape end the Pen's subpath.
|
|
638
|
+
const onKey = (e: KeyboardEvent) => {
|
|
639
|
+
const target = e.target as HTMLElement | null;
|
|
640
|
+
if (target?.closest?.('input, textarea, select, [contenteditable="true"]')) return;
|
|
641
|
+
if (!isPathEditTool(editor.controller.getState().activeTool)) return;
|
|
642
|
+
if (gesture.key(e.key)) {
|
|
643
|
+
e.preventDefault();
|
|
644
|
+
report('path', gesture.error);
|
|
645
|
+
void renderCanvas();
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
window.addEventListener('keydown', onKey);
|
|
649
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
650
|
+
});
|
|
651
|
+
|
|
626
652
|
$effect(() => {
|
|
627
653
|
if (mode !== 'paint' || typeof window === 'undefined') return;
|
|
628
654
|
const c = editor.controller;
|
|
@@ -728,6 +754,13 @@
|
|
|
728
754
|
return;
|
|
729
755
|
}
|
|
730
756
|
|
|
757
|
+
if (mode === 'annotate' && isPathEditTool(editor.state.activeTool)) {
|
|
758
|
+
pathGesture.down(canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false), editor.state.activeTool, { shift: e.shiftKey, alt: e.altKey });
|
|
759
|
+
report('path', pathGesture.error);
|
|
760
|
+
void renderCanvas();
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
731
764
|
if (mode === 'annotate' && (editor.state.activeTool === 'shape' || editor.state.activeTool === 'type')) {
|
|
732
765
|
const imgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false);
|
|
733
766
|
report(editor.state.activeTool, null);
|
|
@@ -854,6 +887,16 @@
|
|
|
854
887
|
return;
|
|
855
888
|
}
|
|
856
889
|
|
|
890
|
+
if (mode === 'annotate' && isPathEditTool(editor.state.activeTool)) {
|
|
891
|
+
const imgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false);
|
|
892
|
+
if (pathGesture.active) {
|
|
893
|
+
pathGesture.move(imgPt, { shift: e.shiftKey, alt: e.altKey });
|
|
894
|
+
if (pathGesture.error) report('path', pathGesture.error);
|
|
895
|
+
} else pathGesture.hover(imgPt);
|
|
896
|
+
void renderCanvas();
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
|
|
857
900
|
if (mode === 'annotate' && editor.state.activeTool === 'shape') {
|
|
858
901
|
if (shapeGesture.active) {
|
|
859
902
|
shapeGesture.move(canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false), { shift: e.shiftKey, alt: e.altKey });
|
|
@@ -975,6 +1018,11 @@
|
|
|
975
1018
|
return;
|
|
976
1019
|
}
|
|
977
1020
|
|
|
1021
|
+
if (mode === 'annotate' && isPathEditTool(editor.state.activeTool)) {
|
|
1022
|
+
pathGesture.up();
|
|
1023
|
+
void renderCanvas();
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
978
1026
|
if (mode === 'annotate' && editor.state.activeTool === 'shape') {
|
|
979
1027
|
shapeGesture.up();
|
|
980
1028
|
return;
|
|
@@ -1080,7 +1128,7 @@
|
|
|
1080
1128
|
}}
|
|
1081
1129
|
data-testid="image-editor-canvas"
|
|
1082
1130
|
style="width: 100%; height: 100%;{panning ? ` cursor: ${isPanning ? 'grabbing' : 'grab'};` : transformActive || mode === 'transform' ? ` cursor: ${transformCursor};` : ''}"
|
|
1083
|
-
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' : ''}"
|
|
1131
|
+
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' || editor.state.activeTool === 'path-pen') ? 'cursor-crosshair' : mode === 'annotate' && isPathEditTool(editor.state.activeTool) ? 'cursor-default' : ''}"
|
|
1084
1132
|
></canvas>
|
|
1085
1133
|
{#if typeSession}
|
|
1086
1134
|
<textarea
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* thumbnails. Click selects a path, double-click renames it (renaming the Work Path saves it).
|
|
10
10
|
* Load as Selection (Shift adds, Alt subtracts), Make Work Path from the selection, Fill and
|
|
11
11
|
* Stroke on the active layer with a colour and width, Add Vector Mask to the active layer,
|
|
12
|
-
* Duplicate and Delete. Every change is one undo step on
|
|
12
|
+
* Duplicate and Delete. Clicking empty space deselects the path. Every change is one undo step on
|
|
13
|
+
* the controller.
|
|
13
14
|
*/
|
|
14
15
|
interface Props {
|
|
15
16
|
controller: ImageEditorController;
|
|
@@ -62,12 +63,15 @@
|
|
|
62
63
|
}`;
|
|
63
64
|
</script>
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
<!-- Clicking the panel's empty space deselects the path (the path tools then edit the active layer). -->
|
|
67
|
+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
|
|
68
|
+
<div data-testid="paths-panel" class="flex flex-col gap-2 text-xs {className}" onclick={(e) => e.target === e.currentTarget && attempt(() => controller.setActivePath(null))}>
|
|
66
69
|
<span class={imageSectionTitleClass}>Paths</span>
|
|
67
70
|
{#if paths.length === 0}
|
|
68
71
|
<p class="text-muted-foreground">No paths. Make a work path from a selection.</p>
|
|
69
72
|
{/if}
|
|
70
|
-
|
|
73
|
+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
|
|
74
|
+
<ul role="list" class="flex flex-col gap-0.5 min-h-4" onclick={(e) => e.target === e.currentTarget && attempt(() => controller.setActivePath(null))}>
|
|
71
75
|
{#each paths as p (p.id)}
|
|
72
76
|
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
|
|
73
77
|
<li
|
|
@@ -4,7 +4,8 @@ import type { ImageEditorController } from '@pixerate/editor/image-editor';
|
|
|
4
4
|
* thumbnails. Click selects a path, double-click renames it (renaming the Work Path saves it).
|
|
5
5
|
* Load as Selection (Shift adds, Alt subtracts), Make Work Path from the selection, Fill and
|
|
6
6
|
* Stroke on the active layer with a colour and width, Add Vector Mask to the active layer,
|
|
7
|
-
* Duplicate and Delete. Every change is one undo step on
|
|
7
|
+
* Duplicate and Delete. Clicking empty space deselects the path. Every change is one undo step on
|
|
8
|
+
* the controller.
|
|
8
9
|
*/
|
|
9
10
|
interface Props {
|
|
10
11
|
controller: ImageEditorController;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** What pointer gestures on `ImageEditorCanvas` do (and whether the crop overlay shows). */
|
|
2
2
|
export type ImageEditorCanvasMode = 'crop' | 'select' | 'paint' | 'annotate' | 'transform' | 'none';
|
|
3
3
|
/** Which gesture an `ImageEditorCanvas` `onerror` report comes from. */
|
|
4
|
-
export type ImageEditorCanvasErrorSource = 'paint' | 'select' | 'shape' | 'type' | 'transform';
|
|
4
|
+
export type ImageEditorCanvasErrorSource = 'paint' | 'select' | 'shape' | 'type' | 'transform' | 'path';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.157.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.
|
|
168
|
+
"@pixerate/editor": "0.213.0"
|
|
169
169
|
},
|
|
170
170
|
"peerDependencies": {
|
|
171
171
|
"@dagrejs/dagre": "^1.1.4",
|