@widgetic/canvas 0.5.6 → 0.5.9
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.
- package/dist/canvas/Canvas.svelte +353 -49
- package/dist/canvas/Canvas.svelte.d.ts +24 -0
- package/dist/canvas/CanvasToolbar.svelte +113 -26
- package/dist/canvas/CanvasToolbar.svelte.d.ts +7 -1
- package/dist/canvas/PanZoomPanel.svelte +136 -6
- package/dist/canvas/PanZoomPanel.svelte.d.ts +1 -0
- package/dist/canvas/props-panel/PropsPanel.svelte +40 -11
- package/dist/canvas/shapes/ImageShape.d.ts +9 -0
- package/dist/canvas/shapes/ImageShape.js +115 -24
- package/dist/components/Tooltip.svelte +10 -2
- package/package.json +1 -1
|
@@ -22,6 +22,8 @@ declare const __propDef: {
|
|
|
22
22
|
}) => void) | null;
|
|
23
23
|
panZoomPosition?: "TL" | "TC" | "TR" | "BL" | "BC" | "BR";
|
|
24
24
|
wireframeMode?: boolean;
|
|
25
|
+
advancedFeatures?: boolean;
|
|
26
|
+
/** When true and advanced is on, Frame/Image/Video stay on the toolbar. Default true. */ mediaBtsOnBar?: boolean;
|
|
25
27
|
onReady?: (() => void) | null;
|
|
26
28
|
isExternalDataLoading?: boolean;
|
|
27
29
|
onBeforeUnmount?: ((json: string | null) => void) | null;
|
|
@@ -60,6 +62,16 @@ declare const __propDef: {
|
|
|
60
62
|
viewportWidth: number;
|
|
61
63
|
viewportHeight: number;
|
|
62
64
|
} | null;
|
|
65
|
+
getObjectScreenRect?: (objectId: string) => {
|
|
66
|
+
left: number;
|
|
67
|
+
top: number;
|
|
68
|
+
width: number;
|
|
69
|
+
height: number;
|
|
70
|
+
viewportLeft: number;
|
|
71
|
+
viewportTop: number;
|
|
72
|
+
viewportWidth: number;
|
|
73
|
+
viewportHeight: number;
|
|
74
|
+
} | null;
|
|
63
75
|
getWidgetShapeDataUrl?: (widgetId: string) => string | null;
|
|
64
76
|
addWidgetShape?: (widgetId: string, widgetName?: string, gridIndex?: number, totalCount?: number, previewImageUrl?: string | null) => string | null;
|
|
65
77
|
getLinkedWidgetIds?: () => Set<string>;
|
|
@@ -80,6 +92,7 @@ declare const __propDef: {
|
|
|
80
92
|
setPrimaryWidgetShape?: (widgetId: string) => boolean;
|
|
81
93
|
getPrimaryWidgetId?: () => string | null;
|
|
82
94
|
onSelectImageTool?: () => void;
|
|
95
|
+
onSelectVideoTool?: () => void;
|
|
83
96
|
onSelectPanTool?: () => void;
|
|
84
97
|
zoomIn?: () => void;
|
|
85
98
|
zoomOut?: () => void;
|
|
@@ -132,6 +145,16 @@ export default class Canvas extends SvelteComponentTyped<CanvasProps, CanvasEven
|
|
|
132
145
|
viewportWidth: number;
|
|
133
146
|
viewportHeight: number;
|
|
134
147
|
} | null;
|
|
148
|
+
get getObjectScreenRect(): (objectId: string) => {
|
|
149
|
+
left: number;
|
|
150
|
+
top: number;
|
|
151
|
+
width: number;
|
|
152
|
+
height: number;
|
|
153
|
+
viewportLeft: number;
|
|
154
|
+
viewportTop: number;
|
|
155
|
+
viewportWidth: number;
|
|
156
|
+
viewportHeight: number;
|
|
157
|
+
} | null;
|
|
135
158
|
get getWidgetShapeDataUrl(): (widgetId: string) => string | null;
|
|
136
159
|
get addWidgetShape(): (widgetId: string, widgetName?: string, gridIndex?: number, totalCount?: number, previewImageUrl?: string | null) => string | null;
|
|
137
160
|
get getLinkedWidgetIds(): () => Set<string>;
|
|
@@ -152,6 +175,7 @@ export default class Canvas extends SvelteComponentTyped<CanvasProps, CanvasEven
|
|
|
152
175
|
get setPrimaryWidgetShape(): (widgetId: string) => boolean;
|
|
153
176
|
get getPrimaryWidgetId(): () => string | null;
|
|
154
177
|
get onSelectImageTool(): () => void;
|
|
178
|
+
get onSelectVideoTool(): () => void;
|
|
155
179
|
get onSelectPanTool(): () => void;
|
|
156
180
|
get zoomIn(): () => void;
|
|
157
181
|
get zoomOut(): () => void;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* COMPONENT PROPS
|
|
8
8
|
*/
|
|
9
9
|
// Tool type definition
|
|
10
|
-
type ToolName = 'select' | 'erase' | 'draw' | 'arrow' | 'text' | 'rect' | 'circle' | 'circle-true' | 'triangle' | 'frame' | 'pan' | 'image' | 'library-shape';
|
|
10
|
+
type ToolName = 'select' | 'erase' | 'draw' | 'arrow' | 'text' | 'rect' | 'circle' | 'circle-true' | 'triangle' | 'frame' | 'pan' | 'image' | 'video' | 'library-shape';
|
|
11
11
|
// Shape tool sub-type for the shapes dropdown (Arrow is a dedicated button, not in dropdown)
|
|
12
12
|
type ShapeToolName = 'rect' | 'circle' | 'circle-true' | 'triangle';
|
|
13
13
|
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
export let onSelectTextTool: () => void = () => {};
|
|
32
32
|
export let onSelectPanTool: () => void = () => {};
|
|
33
33
|
export let onSelectImageTool: () => void = () => {};
|
|
34
|
+
export let onSelectVideoTool: () => void = () => {};
|
|
34
35
|
// Zoom callbacks — optional, used for Ctrl+/Ctrl- shortcuts
|
|
35
36
|
export let onZoomIn: () => void = () => {};
|
|
36
37
|
export let onZoomOut: () => void = () => {};
|
|
@@ -63,6 +64,17 @@
|
|
|
63
64
|
/** Draw-to-place: activates library shape tool so user clicks-and-drags to size it. */
|
|
64
65
|
export let onSelectLibraryShapeTool: (shapeKey: string, pathData: string) => void = () => {};
|
|
65
66
|
|
|
67
|
+
/** Extra tools (undo/redo/lock). Code stays; UI is gated. */
|
|
68
|
+
export let advancedFeatures: boolean = false;
|
|
69
|
+
/**
|
|
70
|
+
* When true (default) and advancedFeatures is on, Frame / Image / Video sit on the toolbar.
|
|
71
|
+
* When false, they leave the bar and appear under More Shapes → Media Shapes.
|
|
72
|
+
*/
|
|
73
|
+
export let mediaBtsOnBar: boolean = true;
|
|
74
|
+
|
|
75
|
+
$: showMediaOnBar = advancedFeatures && mediaBtsOnBar;
|
|
76
|
+
$: showMediaInDropdown = !showMediaOnBar;
|
|
77
|
+
|
|
66
78
|
// Pre-group shapes for the dropdown
|
|
67
79
|
const libraryShapesByCategory = getShapesByCategory();
|
|
68
80
|
|
|
@@ -76,7 +88,7 @@
|
|
|
76
88
|
|
|
77
89
|
// ─── Shapes + Image dropdown state ───
|
|
78
90
|
// The last-selected tool within the dropdown (shapes, image, OR library shape).
|
|
79
|
-
type DropdownToolName = ShapeToolName | 'image' | 'library-shape';
|
|
91
|
+
type DropdownToolName = ShapeToolName | 'image' | 'video' | 'frame' | 'library-shape';
|
|
80
92
|
let lastSelectedDropdownTool: DropdownToolName = 'rect';
|
|
81
93
|
// When a library shape was last selected, store its info for the trigger button
|
|
82
94
|
let lastSelectedLibraryShape: LibraryShape | null = null;
|
|
@@ -134,12 +146,29 @@
|
|
|
134
146
|
};
|
|
135
147
|
|
|
136
148
|
// The currently displayed tool in the dropdown button (active tool if it's a dropdown tool, else last selected)
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
149
|
+
const mediaToolInfo: Record<'frame' | 'image' | 'video', { iconSvg: string; label: string; key: string }> = {
|
|
150
|
+
frame: {
|
|
151
|
+
iconSvg: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 3v6"/></svg>`,
|
|
152
|
+
label: 'Frame',
|
|
153
|
+
key: 'F',
|
|
154
|
+
},
|
|
155
|
+
image: {
|
|
156
|
+
iconSvg: imageDropdownInfo.iconSvg,
|
|
157
|
+
label: 'Image',
|
|
158
|
+
key: 'I',
|
|
159
|
+
},
|
|
160
|
+
video: {
|
|
161
|
+
iconSvg: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>`,
|
|
162
|
+
label: 'Video',
|
|
163
|
+
key: '',
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const dropdownTools: DropdownToolName[] = ['rect', 'circle', 'circle-true', 'triangle', 'image', 'video', 'frame', 'library-shape'];
|
|
168
|
+
$: activeDropdownTool = dropdownTools.includes(activeTool as DropdownToolName)
|
|
140
169
|
? (activeTool as DropdownToolName)
|
|
141
170
|
: lastSelectedDropdownTool;
|
|
142
|
-
$: activeDropdownIsActive =
|
|
171
|
+
$: activeDropdownIsActive = dropdownTools.includes(activeTool as DropdownToolName);
|
|
143
172
|
|
|
144
173
|
// Backwards-compat aliases used in template
|
|
145
174
|
$: activeShapeInDropdown = activeDropdownTool as ShapeToolName;
|
|
@@ -152,10 +181,23 @@
|
|
|
152
181
|
}
|
|
153
182
|
|
|
154
183
|
function selectImageFromDropdown() {
|
|
184
|
+
lastSelectedDropdownTool = 'image';
|
|
155
185
|
if (!KEEP_SHAPES_DROPDOWN_OPEN) shapesDropdownOpen = false;
|
|
156
186
|
onSelectImageTool();
|
|
157
187
|
}
|
|
158
188
|
|
|
189
|
+
function selectFrameFromDropdown() {
|
|
190
|
+
lastSelectedDropdownTool = 'frame';
|
|
191
|
+
if (!KEEP_SHAPES_DROPDOWN_OPEN) shapesDropdownOpen = false;
|
|
192
|
+
onSelectFrameTool();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function selectVideoFromDropdown() {
|
|
196
|
+
lastSelectedDropdownTool = 'video';
|
|
197
|
+
if (!KEEP_SHAPES_DROPDOWN_OPEN) shapesDropdownOpen = false;
|
|
198
|
+
onSelectVideoTool();
|
|
199
|
+
}
|
|
200
|
+
|
|
159
201
|
function selectLibraryShape(shape: LibraryShape) {
|
|
160
202
|
lastSelectedDropdownTool = 'library-shape';
|
|
161
203
|
lastSelectedLibraryShape = shape;
|
|
@@ -167,14 +209,26 @@
|
|
|
167
209
|
function activateDropdownTool() {
|
|
168
210
|
if (activeDropdownTool === 'library-shape' && lastSelectedLibraryShape) {
|
|
169
211
|
selectLibraryShape(lastSelectedLibraryShape);
|
|
212
|
+
} else if (activeDropdownTool === 'image') {
|
|
213
|
+
selectImageFromDropdown();
|
|
214
|
+
} else if (activeDropdownTool === 'video') {
|
|
215
|
+
selectVideoFromDropdown();
|
|
216
|
+
} else if (activeDropdownTool === 'frame') {
|
|
217
|
+
selectFrameFromDropdown();
|
|
170
218
|
} else {
|
|
171
219
|
selectShape(activeDropdownTool as ShapeToolName);
|
|
172
220
|
}
|
|
173
221
|
}
|
|
174
222
|
|
|
223
|
+
$: mediaTrigger = (activeDropdownTool === 'frame' || activeDropdownTool === 'image' || activeDropdownTool === 'video')
|
|
224
|
+
? mediaToolInfo[activeDropdownTool]
|
|
225
|
+
: null;
|
|
226
|
+
|
|
175
227
|
// Tooltip text for the main dropdown button
|
|
176
228
|
$: dropdownButtonTooltip = activeDropdownTool === 'library-shape' && lastSelectedLibraryShape
|
|
177
229
|
? lastSelectedLibraryShape.label
|
|
230
|
+
: mediaTrigger
|
|
231
|
+
? (mediaTrigger.key ? `${mediaTrigger.label} (${mediaTrigger.key})` : mediaTrigger.label)
|
|
178
232
|
: shapeInfo[activeDropdownTool as ShapeToolName]?.label
|
|
179
233
|
? (shapeInfo[activeDropdownTool as ShapeToolName].key
|
|
180
234
|
? `${shapeInfo[activeDropdownTool as ShapeToolName].label} (${shapeInfo[activeDropdownTool as ShapeToolName].key})`
|
|
@@ -464,6 +518,7 @@
|
|
|
464
518
|
<!-- Scrollable container for tools — overflow-x-auto clips absolute children,
|
|
465
519
|
so dropdowns and tooltips are rendered as siblings OUTSIDE this div -->
|
|
466
520
|
<div class="tools-ct flex flex-row">
|
|
521
|
+
{#if advancedFeatures}
|
|
467
522
|
<!-- ── Utility cluster: Undo, Redo, Hand, Lock | Divider ── -->
|
|
468
523
|
<Tooltip text="Undo (Ctrl+Z)">
|
|
469
524
|
<div class="disabled-tooltip-wrap">
|
|
@@ -528,6 +583,7 @@
|
|
|
528
583
|
</Tooltip>
|
|
529
584
|
|
|
530
585
|
<div class="toolbar-divider"></div>
|
|
586
|
+
{/if}
|
|
531
587
|
|
|
532
588
|
<!-- ══ Illustrated tools: Pencil & Eraser — "pulled from sheath" on select ══ -->
|
|
533
589
|
<!-- Each tool is wrapped in a mask container that clips its body at rest.
|
|
@@ -623,6 +679,7 @@
|
|
|
623
679
|
</button>
|
|
624
680
|
</Tooltip>
|
|
625
681
|
|
|
682
|
+
{#if showMediaOnBar}
|
|
626
683
|
<div class="toolbar-divider"></div>
|
|
627
684
|
|
|
628
685
|
<Tooltip text="Frame (F)">
|
|
@@ -649,7 +706,19 @@
|
|
|
649
706
|
</button>
|
|
650
707
|
</Tooltip>
|
|
651
708
|
|
|
652
|
-
|
|
709
|
+
<Tooltip text="Video">
|
|
710
|
+
<button class="video-bt {buttonClass} {activeTool === 'video' ? activeClass : inactiveClass}"
|
|
711
|
+
onclick={onSelectVideoTool}
|
|
712
|
+
aria-label="Video">
|
|
713
|
+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
714
|
+
<rect x="2" y="6" width="14" height="12" rx="2"/>
|
|
715
|
+
<path d="M16 10l6-3v10l-6-3z"/>
|
|
716
|
+
</svg>
|
|
717
|
+
</button>
|
|
718
|
+
</Tooltip>
|
|
719
|
+
{/if}
|
|
720
|
+
|
|
721
|
+
<!-- Shapes: tldraw-style split — left activates tool, right opens dropdown. Always available. -->
|
|
653
722
|
<div class="shapes-split-ct" bind:this={dropdownTriggerEl}>
|
|
654
723
|
<Tooltip text={dropdownButtonTooltip}>
|
|
655
724
|
<button
|
|
@@ -659,6 +728,8 @@
|
|
|
659
728
|
>
|
|
660
729
|
{#if activeDropdownTool === 'library-shape' && lastSelectedLibraryShape}
|
|
661
730
|
{@html lastSelectedLibraryShape.iconSvg}
|
|
731
|
+
{:else if mediaTrigger}
|
|
732
|
+
{@html mediaTrigger.iconSvg}
|
|
662
733
|
{:else if shapeInfo[activeDropdownTool as ShapeToolName]}
|
|
663
734
|
{@html shapeInfo[activeDropdownTool as ShapeToolName].iconSvg}
|
|
664
735
|
{:else}
|
|
@@ -722,6 +793,29 @@
|
|
|
722
793
|
use:overlayScrollbar
|
|
723
794
|
style={shapesDropdownNeedsScroll ? 'margin-right: -6px' : ''}
|
|
724
795
|
>
|
|
796
|
+
<!-- Media Shapes — first so Frame / Image / Video are easy to reach when not on the bar -->
|
|
797
|
+
{#if showMediaInDropdown}
|
|
798
|
+
<div class="shapes-category-label">Media Shapes</div>
|
|
799
|
+
<div class="shapes-library-grid">
|
|
800
|
+
<Tooltip text="Frame (F)" position="above">
|
|
801
|
+
<button class="shape-library-bt {activeTool === 'frame' ? 'active' : ''}" onclick={selectFrameFromDropdown} aria-label="Frame">
|
|
802
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 3v6"/></svg>
|
|
803
|
+
</button>
|
|
804
|
+
</Tooltip>
|
|
805
|
+
<Tooltip text="Image (I)" position="above">
|
|
806
|
+
<button class="shape-library-bt {activeTool === 'image' ? 'active' : ''}" onclick={selectImageFromDropdown} aria-label="Image">
|
|
807
|
+
{@html imageDropdownInfo.iconSvg}
|
|
808
|
+
</button>
|
|
809
|
+
</Tooltip>
|
|
810
|
+
<Tooltip text="Video" position="above">
|
|
811
|
+
<button class="shape-library-bt {activeTool === 'video' ? 'active' : ''}" onclick={selectVideoFromDropdown} aria-label="Video">
|
|
812
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-3v10l-6-3z"/></svg>
|
|
813
|
+
</button>
|
|
814
|
+
</Tooltip>
|
|
815
|
+
</div>
|
|
816
|
+
<div class="shapes-dropdown-divider"></div>
|
|
817
|
+
{/if}
|
|
818
|
+
|
|
725
819
|
<!-- Geometric Shapes — 3×2 grid -->
|
|
726
820
|
<div class="shapes-category-label">Geometric Shapes</div>
|
|
727
821
|
<div class="shapes-library-grid">
|
|
@@ -761,21 +855,6 @@
|
|
|
761
855
|
{/each}
|
|
762
856
|
</div>
|
|
763
857
|
|
|
764
|
-
<!-- Media (hidden — Image tool moved to main toolbar) -->
|
|
765
|
-
{#if false}
|
|
766
|
-
<div class="shapes-dropdown-divider"></div>
|
|
767
|
-
<div class="shapes-category-label">Media</div>
|
|
768
|
-
<Tooltip text="Image (I)" position="above">
|
|
769
|
-
<button
|
|
770
|
-
class="shape-option-bt {activeTool === 'image' ? 'active' : ''}"
|
|
771
|
-
onclick={selectImageFromDropdown}
|
|
772
|
-
>
|
|
773
|
-
{@html imageDropdownInfo.iconSvg}
|
|
774
|
-
{imageDropdownInfo.label}
|
|
775
|
-
</button>
|
|
776
|
-
</Tooltip>
|
|
777
|
-
{/if}
|
|
778
|
-
|
|
779
858
|
<!-- Library shape categories (skip Geometric, already rendered above) -->
|
|
780
859
|
{#each [...libraryShapesByCategory.entries()].filter(([cat]) => cat !== 'Geometric Shapes') as [category, shapes]}
|
|
781
860
|
<div class="shapes-dropdown-divider"></div>
|
|
@@ -894,6 +973,13 @@
|
|
|
894
973
|
background: transparent !important;
|
|
895
974
|
box-shadow: none !important;
|
|
896
975
|
}
|
|
976
|
+
.canvas-toolbar-el :global(button:focus),
|
|
977
|
+
.canvas-toolbar-el :global(button:focus-visible),
|
|
978
|
+
.canvas-toolbar-el :global(a:focus),
|
|
979
|
+
.canvas-toolbar-el :global(a:focus-visible) {
|
|
980
|
+
outline: none !important;
|
|
981
|
+
box-shadow: none !important;
|
|
982
|
+
}
|
|
897
983
|
|
|
898
984
|
/* ── "Sheath" — the white container holding all buttons ── */
|
|
899
985
|
.tools-ct {
|
|
@@ -901,7 +987,7 @@
|
|
|
901
987
|
border-radius: 1.25rem;
|
|
902
988
|
box-shadow: 0 4px 24px rgba(0,0,0,0.08), 0 1px 4px rgba(0,0,0,0.04);
|
|
903
989
|
height: 80px;
|
|
904
|
-
padding: 0
|
|
990
|
+
padding: 0 24px;
|
|
905
991
|
gap: 4px;
|
|
906
992
|
align-items: center;
|
|
907
993
|
overflow: visible;
|
|
@@ -919,9 +1005,10 @@ button {
|
|
|
919
1005
|
background: transparent;
|
|
920
1006
|
}
|
|
921
1007
|
|
|
922
|
-
button:focus
|
|
923
|
-
|
|
924
|
-
|
|
1008
|
+
button:focus,
|
|
1009
|
+
button:focus-visible {
|
|
1010
|
+
outline: none !important;
|
|
1011
|
+
box-shadow: none !important;
|
|
925
1012
|
}
|
|
926
1013
|
|
|
927
1014
|
button:disabled {
|
|
@@ -3,7 +3,7 @@ declare const __propDef: {
|
|
|
3
3
|
props: {
|
|
4
4
|
class?: string;
|
|
5
5
|
style?: string;
|
|
6
|
-
activeTool?: "select" | "rect" | "erase" | "draw" | "arrow" | "text" | "circle" | "circle-true" | "triangle" | "frame" | "pan" | "image" | "library-shape";
|
|
6
|
+
activeTool?: "select" | "video" | "rect" | "erase" | "draw" | "arrow" | "text" | "circle" | "circle-true" | "triangle" | "frame" | "pan" | "image" | "library-shape";
|
|
7
7
|
activeClass?: string;
|
|
8
8
|
inactiveClass?: string;
|
|
9
9
|
buttonClass?: string;
|
|
@@ -15,6 +15,7 @@ declare const __propDef: {
|
|
|
15
15
|
onSelectTextTool?: () => void;
|
|
16
16
|
onSelectPanTool?: () => void;
|
|
17
17
|
onSelectImageTool?: () => void;
|
|
18
|
+
onSelectVideoTool?: () => void;
|
|
18
19
|
onZoomIn?: () => void;
|
|
19
20
|
onZoomOut?: () => void;
|
|
20
21
|
onZoomReset?: () => void;
|
|
@@ -32,6 +33,11 @@ declare const __propDef: {
|
|
|
32
33
|
onToggleStickyTool?: () => void;
|
|
33
34
|
/** Called when user clicks a library shape. Canvas inserts it as a Path. */ onInsertLibraryShape?: (shapeKey: string, pathData: string) => void;
|
|
34
35
|
/** Draw-to-place: activates library shape tool so user clicks-and-drags to size it. */ onSelectLibraryShapeTool?: (shapeKey: string, pathData: string) => void;
|
|
36
|
+
/** Extra tools (undo/redo/lock). Code stays; UI is gated. */ advancedFeatures?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* When true (default) and advancedFeatures is on, Frame / Image / Video sit on the toolbar.
|
|
39
|
+
* When false, they leave the bar and appear under More Shapes → Media Shapes.
|
|
40
|
+
*/ mediaBtsOnBar?: boolean;
|
|
35
41
|
addText?: () => void;
|
|
36
42
|
addRect?: () => void;
|
|
37
43
|
addTriangle?: () => void;
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
// Reflects open state so the button can show an active style
|
|
28
28
|
export let shortcutsOpen: boolean = false;
|
|
29
29
|
|
|
30
|
+
/** Extra view controls (grid, pan, 1:1, fit, shortcuts). Zoom ± / % and save stay available. */
|
|
31
|
+
export let advancedFeatures: boolean = false;
|
|
32
|
+
|
|
30
33
|
// Save button: visible when showSaveButton is true
|
|
31
34
|
export let showSaveButton: boolean = false;
|
|
32
35
|
export let hasUnsavedChanges: boolean = false;
|
|
@@ -56,9 +59,29 @@
|
|
|
56
59
|
(e.target as HTMLInputElement).blur();
|
|
57
60
|
}
|
|
58
61
|
}
|
|
62
|
+
|
|
63
|
+
let isMinimized = true;
|
|
64
|
+
|
|
65
|
+
function toggleMinimized() {
|
|
66
|
+
isMinimized = !isMinimized;
|
|
67
|
+
console.log('[PanZoomPanel] minimized:', isMinimized);
|
|
68
|
+
}
|
|
59
69
|
</script>
|
|
60
70
|
|
|
61
|
-
<div class="{className ? className + ' ' : ''}pan-zoom-panel" style={style}>
|
|
71
|
+
<div class="pan-zoom-panel-ct {className ? className + ' ' : ''}pan-zoom-panel {isMinimized ? 'pan-zoom-panel-minimized' : ''}" style={style}>
|
|
72
|
+
{#if isMinimized}
|
|
73
|
+
<Tooltip text="Zoom {zoomLevel}% — click to expand" position="above">
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
class="panel-btn pan-zoom-expand-btn"
|
|
77
|
+
onclick={toggleMinimized}
|
|
78
|
+
aria-label="Expand zoom controls"
|
|
79
|
+
>
|
|
80
|
+
<span class="text-btn">{zoomLevel}%</span>
|
|
81
|
+
</button>
|
|
82
|
+
</Tooltip>
|
|
83
|
+
{:else}
|
|
84
|
+
{#if advancedFeatures}
|
|
62
85
|
<!-- Grid Toggle -->
|
|
63
86
|
<Tooltip text="Toggle Grid [G]" position="above">
|
|
64
87
|
<button
|
|
@@ -92,6 +115,7 @@
|
|
|
92
115
|
</Tooltip>
|
|
93
116
|
|
|
94
117
|
<div class="divider"></div>
|
|
118
|
+
{/if}
|
|
95
119
|
|
|
96
120
|
<!-- Zoom Out -->
|
|
97
121
|
<Tooltip text="Zoom Out [Ctrl/Cmd] + [−]" position="above">
|
|
@@ -110,6 +134,19 @@
|
|
|
110
134
|
|
|
111
135
|
<!-- Zoom Level Input -->
|
|
112
136
|
<div class="zoom-level">
|
|
137
|
+
<Tooltip text="Reset zoom to 100% [Ctrl/Cmd] + [0]" position="above">
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
class="zoom-reset-in-input"
|
|
141
|
+
onclick={onZoomReset}
|
|
142
|
+
aria-label="Reset zoom to 100%"
|
|
143
|
+
>
|
|
144
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
145
|
+
<polyline points="1 4 1 10 7 10"/>
|
|
146
|
+
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
|
|
147
|
+
</svg>
|
|
148
|
+
</button>
|
|
149
|
+
</Tooltip>
|
|
113
150
|
<input
|
|
114
151
|
type="text"
|
|
115
152
|
class="zoom-input"
|
|
@@ -138,6 +175,7 @@
|
|
|
138
175
|
</button>
|
|
139
176
|
</Tooltip>
|
|
140
177
|
|
|
178
|
+
{#if advancedFeatures}
|
|
141
179
|
<div class="divider"></div>
|
|
142
180
|
|
|
143
181
|
<!-- Reset Zoom (100%) -->
|
|
@@ -181,8 +219,9 @@
|
|
|
181
219
|
</svg>
|
|
182
220
|
</button>
|
|
183
221
|
</Tooltip>
|
|
222
|
+
{/if}
|
|
184
223
|
|
|
185
|
-
|
|
224
|
+
{#if showSaveButton && advancedFeatures}
|
|
186
225
|
<div class="divider"></div>
|
|
187
226
|
<Tooltip text={hasUnsavedChanges ? 'Unsaved changes — click to save [Cmd+S]' : 'All changes saved [Cmd+S]'} position="above">
|
|
188
227
|
<button
|
|
@@ -201,18 +240,77 @@
|
|
|
201
240
|
</button>
|
|
202
241
|
</Tooltip>
|
|
203
242
|
{/if}
|
|
243
|
+
|
|
244
|
+
<div class="pan-zoom-minimize-anchor">
|
|
245
|
+
<Tooltip text="Minimize Zoom Panel" position="above">
|
|
246
|
+
<button
|
|
247
|
+
type="button"
|
|
248
|
+
class="pan-zoom-minimize-btn"
|
|
249
|
+
onclick={toggleMinimized}
|
|
250
|
+
aria-label="Minimize Zoom Panel"
|
|
251
|
+
>
|
|
252
|
+
<svg width="8" height="8" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
253
|
+
<rect x="2" y="5.25" width="8" height="1.5" rx="0.25" fill="currentColor"/>
|
|
254
|
+
</svg>
|
|
255
|
+
</button>
|
|
256
|
+
</Tooltip>
|
|
257
|
+
</div>
|
|
258
|
+
{/if}
|
|
204
259
|
</div>
|
|
205
260
|
|
|
206
261
|
<style>
|
|
207
262
|
.pan-zoom-panel {
|
|
263
|
+
position: relative;
|
|
208
264
|
display: flex;
|
|
209
265
|
align-items: center;
|
|
210
266
|
gap: 4px;
|
|
267
|
+
width: max-content;
|
|
268
|
+
max-width: 100%;
|
|
211
269
|
padding: 6px 10px;
|
|
212
270
|
background: white;
|
|
213
271
|
border: 2px solid #e5e7eb;
|
|
214
272
|
border-radius: 8px;
|
|
215
273
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
274
|
+
overflow: visible;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.pan-zoom-minimize-anchor {
|
|
278
|
+
position: absolute;
|
|
279
|
+
top: -8px;
|
|
280
|
+
right: -8px;
|
|
281
|
+
z-index: 2;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.pan-zoom-minimize-btn {
|
|
285
|
+
display: flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
justify-content: center;
|
|
288
|
+
width: 14px;
|
|
289
|
+
height: 14px;
|
|
290
|
+
padding: 0;
|
|
291
|
+
border-radius: 9999px;
|
|
292
|
+
background: #ffffff;
|
|
293
|
+
border: 1px solid #9ca3af;
|
|
294
|
+
color: #9ca3af;
|
|
295
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
|
|
296
|
+
cursor: pointer;
|
|
297
|
+
transition: color 0.15s ease, border-color 0.15s ease;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.pan-zoom-minimize-btn:hover:not(:disabled) {
|
|
301
|
+
background: #ffffff;
|
|
302
|
+
border-color: #111827;
|
|
303
|
+
color: #111827;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.pan-zoom-panel-minimized {
|
|
307
|
+
padding: 4px;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.pan-zoom-expand-btn {
|
|
311
|
+
min-width: 44px;
|
|
312
|
+
width: auto;
|
|
313
|
+
padding: 0 8px;
|
|
216
314
|
}
|
|
217
315
|
|
|
218
316
|
.panel-btn {
|
|
@@ -268,6 +366,14 @@
|
|
|
268
366
|
cursor: not-allowed;
|
|
269
367
|
}
|
|
270
368
|
|
|
369
|
+
.pan-zoom-panel button:focus,
|
|
370
|
+
.pan-zoom-panel button:focus-visible,
|
|
371
|
+
.pan-zoom-panel input:focus,
|
|
372
|
+
.pan-zoom-panel input:focus-visible {
|
|
373
|
+
outline: none;
|
|
374
|
+
box-shadow: none;
|
|
375
|
+
}
|
|
376
|
+
|
|
271
377
|
/* Keyboard shortcuts button — slightly bigger to ensure the icon is clearly readable */
|
|
272
378
|
.shortcuts-btn {
|
|
273
379
|
width: 36px;
|
|
@@ -296,14 +402,34 @@
|
|
|
296
402
|
background: #f9fafb;
|
|
297
403
|
}
|
|
298
404
|
|
|
405
|
+
.zoom-reset-in-input {
|
|
406
|
+
display: flex;
|
|
407
|
+
align-items: center;
|
|
408
|
+
justify-content: center;
|
|
409
|
+
width: 18px;
|
|
410
|
+
height: 24px;
|
|
411
|
+
padding: 0;
|
|
412
|
+
margin: 0 0 0 2px;
|
|
413
|
+
border: none;
|
|
414
|
+
background: transparent;
|
|
415
|
+
color: #6b7280;
|
|
416
|
+
cursor: pointer;
|
|
417
|
+
flex-shrink: 0;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.zoom-reset-in-input:hover {
|
|
421
|
+
color: #111827;
|
|
422
|
+
background: #f3f4f6;
|
|
423
|
+
}
|
|
424
|
+
|
|
299
425
|
.zoom-input {
|
|
300
|
-
width:
|
|
301
|
-
padding: 4px
|
|
426
|
+
width: 28px;
|
|
427
|
+
padding: 4px 0;
|
|
302
428
|
border: none;
|
|
303
429
|
background: transparent;
|
|
304
430
|
font-size: 12px;
|
|
305
431
|
font-weight: 500;
|
|
306
|
-
text-align:
|
|
432
|
+
text-align: center;
|
|
307
433
|
outline: none;
|
|
308
434
|
}
|
|
309
435
|
|
|
@@ -312,9 +438,13 @@
|
|
|
312
438
|
}
|
|
313
439
|
|
|
314
440
|
.zoom-unit {
|
|
315
|
-
|
|
441
|
+
width: 18px;
|
|
442
|
+
padding: 4px 0;
|
|
443
|
+
margin: 0 2px 0 0;
|
|
316
444
|
font-size: 11px;
|
|
317
445
|
color: #6b7280;
|
|
446
|
+
text-align: center;
|
|
447
|
+
flex-shrink: 0;
|
|
318
448
|
}
|
|
319
449
|
|
|
320
450
|
.save-btn--dirty {
|
|
@@ -15,6 +15,7 @@ declare const __propDef: {
|
|
|
15
15
|
onGridToggle?: () => void;
|
|
16
16
|
onShortcutsToggle?: () => void;
|
|
17
17
|
shortcutsOpen?: boolean;
|
|
18
|
+
/** Extra view controls (grid, pan, 1:1, fit, shortcuts). Zoom ± / % and save stay available. */ advancedFeatures?: boolean;
|
|
18
19
|
showSaveButton?: boolean;
|
|
19
20
|
hasUnsavedChanges?: boolean;
|
|
20
21
|
onSave?: (() => void) | null;
|