@widgetic/canvas 0.5.6 → 0.5.7
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 +91 -25
- package/dist/canvas/Canvas.svelte.d.ts +21 -0
- package/dist/canvas/CanvasToolbar.svelte +13 -6
- package/dist/canvas/CanvasToolbar.svelte.d.ts +1 -0
- package/dist/canvas/PanZoomPanel.svelte +75 -1
- package/dist/canvas/PanZoomPanel.svelte.d.ts +1 -0
- package/dist/canvas/props-panel/PropsPanel.svelte +19 -6
- package/package.json +1 -1
|
@@ -153,6 +153,10 @@
|
|
|
153
153
|
// (white fill, dark stroke) on all newly created objects
|
|
154
154
|
export let wireframeMode: boolean = true;
|
|
155
155
|
|
|
156
|
+
// Extra canvas chrome (undo/redo/lock, frame, image, shape library).
|
|
157
|
+
// Default off for the sketch MVP. Same pattern as wireframeMode: keep the code, hide the UI.
|
|
158
|
+
export let advancedFeatures: boolean = false;
|
|
159
|
+
|
|
156
160
|
// Callback fired once when Fabric canvas is fully initialized and ready
|
|
157
161
|
export let onReady: (() => void) | null = null;
|
|
158
162
|
|
|
@@ -1149,6 +1153,37 @@
|
|
|
1149
1153
|
};
|
|
1150
1154
|
}
|
|
1151
1155
|
|
|
1156
|
+
/** Screen-space box for any canvas object id (convert drafts use the grouped sketch). */
|
|
1157
|
+
export function getObjectScreenRect(objectId: string): {
|
|
1158
|
+
left: number;
|
|
1159
|
+
top: number;
|
|
1160
|
+
width: number;
|
|
1161
|
+
height: number;
|
|
1162
|
+
viewportLeft: number;
|
|
1163
|
+
viewportTop: number;
|
|
1164
|
+
viewportWidth: number;
|
|
1165
|
+
viewportHeight: number;
|
|
1166
|
+
} | null {
|
|
1167
|
+
if (!canvas || !canvasEl || !objectId) return null;
|
|
1168
|
+
const obj = canvas.getObjects().find((o: any) => o._objectId === objectId);
|
|
1169
|
+
if (!obj) {
|
|
1170
|
+
canvasLog('Canvas: getObjectScreenRect — no object for', objectId);
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
const bbox = obj.getBoundingRect();
|
|
1174
|
+
const canvasRect = canvasEl.getBoundingClientRect();
|
|
1175
|
+
return {
|
|
1176
|
+
left: canvasRect.left + bbox.left,
|
|
1177
|
+
top: canvasRect.top + bbox.top,
|
|
1178
|
+
width: bbox.width,
|
|
1179
|
+
height: bbox.height,
|
|
1180
|
+
viewportLeft: canvasRect.left,
|
|
1181
|
+
viewportTop: canvasRect.top,
|
|
1182
|
+
viewportWidth: canvasRect.width,
|
|
1183
|
+
viewportHeight: canvasRect.height,
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1152
1187
|
/**
|
|
1153
1188
|
* Get the current image data URL of a widget shape on the canvas.
|
|
1154
1189
|
* Returns the base64 data URL or null if the shape is not found or has no image.
|
|
@@ -1406,12 +1441,12 @@
|
|
|
1406
1441
|
function getPanZoomPositionClass(position: string): string {
|
|
1407
1442
|
canvasLog('getPanZoomPositionClass: ', position);
|
|
1408
1443
|
const positions: Record<string, string> = {
|
|
1409
|
-
'TL': 'top-4 left-4',
|
|
1410
|
-
'TC': 'top-4 left-1/2 -translate-x-1/2',
|
|
1411
|
-
'TR': 'top-4 right-4',
|
|
1412
|
-
'BL': 'bottom-20 left-4',
|
|
1413
|
-
'BC': 'bottom-20 left-1/2 -translate-x-1/2',
|
|
1414
|
-
'BR': 'bottom-20 right-4'
|
|
1444
|
+
'TL': 'absolute z-10 top-4 left-4',
|
|
1445
|
+
'TC': 'absolute z-10 top-4 left-1/2 -translate-x-1/2',
|
|
1446
|
+
'TR': 'absolute z-10 top-4 right-4',
|
|
1447
|
+
'BL': 'absolute z-10 bottom-20 left-4',
|
|
1448
|
+
'BC': 'absolute z-10 bottom-20 left-1/2 -translate-x-1/2',
|
|
1449
|
+
'BR': 'absolute z-10 bottom-20 right-4'
|
|
1415
1450
|
};
|
|
1416
1451
|
return positions[position] || positions['TL'];
|
|
1417
1452
|
}
|
|
@@ -1447,6 +1482,10 @@
|
|
|
1447
1482
|
// Let Fabric handle Shift+corner = proportional resize natively.
|
|
1448
1483
|
// For _lockAspectRatio shapes, before:transform sets uniformScaling=true.
|
|
1449
1484
|
(canvas as any).uniScaleKey = 'shiftKey';
|
|
1485
|
+
// Free corner resize (not from center). Keep Alt off Fabric's centeredKey —
|
|
1486
|
+
// Option is used for frame children, and a stuck altKey used to explode scales.
|
|
1487
|
+
canvas.centeredScaling = false;
|
|
1488
|
+
(canvas as any).centeredKey = null;
|
|
1450
1489
|
|
|
1451
1490
|
// Set 'move' cursor as default hover cursor on ALL objects.
|
|
1452
1491
|
// Set on both canvas level and prototype level for full coverage.
|
|
@@ -2056,8 +2095,10 @@
|
|
|
2056
2095
|
let altKeyCurrentlyHeld = false;
|
|
2057
2096
|
const trackAltKeyDown = (e: KeyboardEvent) => { if (e.altKey) altKeyCurrentlyHeld = true; };
|
|
2058
2097
|
const trackAltKeyUp = (e: KeyboardEvent) => { if (!e.altKey) altKeyCurrentlyHeld = false; };
|
|
2098
|
+
const trackAltKeyBlur = () => { altKeyCurrentlyHeld = false; };
|
|
2059
2099
|
window.addEventListener('keydown', trackAltKeyDown);
|
|
2060
2100
|
window.addEventListener('keyup', trackAltKeyUp);
|
|
2101
|
+
window.addEventListener('blur', trackAltKeyBlur);
|
|
2061
2102
|
|
|
2062
2103
|
// ── Custom right-click context menu ───────────────────────────────────
|
|
2063
2104
|
// Prevent the browser default context menu on the canvas and show ours.
|
|
@@ -2081,12 +2122,17 @@
|
|
|
2081
2122
|
const transform = opt.transform;
|
|
2082
2123
|
if (!transform) return;
|
|
2083
2124
|
|
|
2084
|
-
//
|
|
2085
|
-
//
|
|
2086
|
-
//
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2125
|
+
// Only remap when Fabric forced origin=center on an object that is NOT
|
|
2126
|
+
// natively center-origin (library icons / groups already use origin center).
|
|
2127
|
+
// Remapping those made corner resize look like Alt-deflate.
|
|
2128
|
+
const targetObj = transform.target as any;
|
|
2129
|
+
const nativeCenterOrigin =
|
|
2130
|
+
targetObj?.originX === 'center' && targetObj?.originY === 'center';
|
|
2131
|
+
if (
|
|
2132
|
+
!nativeCenterOrigin &&
|
|
2133
|
+
transform.originX === 'center' &&
|
|
2134
|
+
transform.originY === 'center'
|
|
2135
|
+
) {
|
|
2090
2136
|
const cornerOriginMap: Record<string, { x: string; y: string }> = {
|
|
2091
2137
|
tl: { x: 'right', y: 'bottom' },
|
|
2092
2138
|
tr: { x: 'left', y: 'bottom' },
|
|
@@ -2102,7 +2148,7 @@
|
|
|
2102
2148
|
if (origin) {
|
|
2103
2149
|
transform.originX = origin.x;
|
|
2104
2150
|
transform.originY = origin.y;
|
|
2105
|
-
canvasLog('
|
|
2151
|
+
canvasLog('Canvas: overrode accidental centered origin for corner', corner, '→', origin);
|
|
2106
2152
|
}
|
|
2107
2153
|
}
|
|
2108
2154
|
|
|
@@ -2131,13 +2177,19 @@
|
|
|
2131
2177
|
// Store the original corners BEFORE any transformation
|
|
2132
2178
|
// This ensures we have accurate positions to work with
|
|
2133
2179
|
const target = scalingTarget as any;
|
|
2134
|
-
const w = target.width * target.scaleX;
|
|
2135
|
-
const h = target.height * target.scaleY;
|
|
2180
|
+
const w = (target.width ?? 0) * (target.scaleX ?? 1);
|
|
2181
|
+
const h = (target.height ?? 0) * (target.scaleY ?? 1);
|
|
2182
|
+
const originX = target.originX ?? 'left';
|
|
2183
|
+
const originY = target.originY ?? 'top';
|
|
2184
|
+
const left = target.left ?? 0;
|
|
2185
|
+
const top = target.top ?? 0;
|
|
2186
|
+
const tlX = originX === 'center' ? left - w / 2 : originX === 'right' ? left - w : left;
|
|
2187
|
+
const tlY = originY === 'center' ? top - h / 2 : originY === 'bottom' ? top - h : top;
|
|
2136
2188
|
target._originalCorners = {
|
|
2137
|
-
tl: { x:
|
|
2138
|
-
tr: { x:
|
|
2139
|
-
bl: { x:
|
|
2140
|
-
br: { x:
|
|
2189
|
+
tl: { x: tlX, y: tlY },
|
|
2190
|
+
tr: { x: tlX + w, y: tlY },
|
|
2191
|
+
bl: { x: tlX, y: tlY + h },
|
|
2192
|
+
br: { x: tlX + w, y: tlY + h }
|
|
2141
2193
|
};
|
|
2142
2194
|
|
|
2143
2195
|
// For frames: store original children metrics for Alt/Option key resize.
|
|
@@ -3073,6 +3125,10 @@
|
|
|
3073
3125
|
isAltHeld = false;
|
|
3074
3126
|
}
|
|
3075
3127
|
};
|
|
3128
|
+
const handleGlobalAltBlur = () => {
|
|
3129
|
+
isAltHeld = false;
|
|
3130
|
+
};
|
|
3131
|
+
window.addEventListener('blur', handleGlobalAltBlur);
|
|
3076
3132
|
|
|
3077
3133
|
// Shared helper: delete whatever is currently active on the canvas.
|
|
3078
3134
|
// Used by both the Delete key handler and the Cut (Ctrl+X) handler.
|
|
@@ -4369,6 +4425,8 @@
|
|
|
4369
4425
|
(canvas as any)._globalShiftKeyupHandler = handleGlobalShiftKeyup;
|
|
4370
4426
|
(canvas as any)._globalAltKeydownHandler = handleGlobalAltKeydown;
|
|
4371
4427
|
(canvas as any)._globalAltKeyupHandler = handleGlobalAltKeyup;
|
|
4428
|
+
(canvas as any)._globalAltBlurHandler = handleGlobalAltBlur;
|
|
4429
|
+
(canvas as any)._altTrackBlur = trackAltKeyBlur;
|
|
4372
4430
|
(canvas as any)._deleteKeyHandler = handleDeleteKeydown;
|
|
4373
4431
|
(canvas as any)._arrowKeyHandler = handleArrowKeydown;
|
|
4374
4432
|
(canvas as any)._selectAllHandler = handleSelectAllKeydown;
|
|
@@ -4583,9 +4641,8 @@
|
|
|
4583
4641
|
exitGroupEdit(false);
|
|
4584
4642
|
}
|
|
4585
4643
|
canvasLog('Canvas: Selection cleared');
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
}
|
|
4644
|
+
// Do not close Widget Details here — Fabric also clears selection when
|
|
4645
|
+
// switching from widget A to widget B, which reopened the previous panel.
|
|
4589
4646
|
};
|
|
4590
4647
|
|
|
4591
4648
|
canvas.on('selection:created', handleSelectionCreated);
|
|
@@ -5461,6 +5518,12 @@
|
|
|
5461
5518
|
if (canvas && (canvas as any)._altTrackUp) {
|
|
5462
5519
|
window.removeEventListener('keyup', (canvas as any)._altTrackUp);
|
|
5463
5520
|
}
|
|
5521
|
+
if (canvas && (canvas as any)._altTrackBlur) {
|
|
5522
|
+
window.removeEventListener('blur', (canvas as any)._altTrackBlur);
|
|
5523
|
+
}
|
|
5524
|
+
if (canvas && (canvas as any)._globalAltBlurHandler) {
|
|
5525
|
+
window.removeEventListener('blur', (canvas as any)._globalAltBlurHandler);
|
|
5526
|
+
}
|
|
5464
5527
|
if (canvas && (canvas as any)._contextMenuHandler && (canvas as any)._contextMenuTarget) {
|
|
5465
5528
|
(canvas as any)._contextMenuTarget.removeEventListener('contextmenu', (canvas as any)._contextMenuHandler);
|
|
5466
5529
|
}
|
|
@@ -11080,11 +11143,12 @@ function checkAndAddNewShapeToFrame(shape: any) {
|
|
|
11080
11143
|
|
|
11081
11144
|
<!-- Pan/Zoom Panel: Position configurable via panZoomPosition prop -->
|
|
11082
11145
|
{#if !isCanvasLoading}
|
|
11083
|
-
<
|
|
11084
|
-
|
|
11146
|
+
<div class="canvas-pan-zoom-overlay {getPanZoomPositionClass(panZoomPosition)}">
|
|
11147
|
+
<PanZoomPanel
|
|
11085
11148
|
{zoomLevel}
|
|
11086
11149
|
isPanActive={activeTool === 'pan'}
|
|
11087
11150
|
{showGrid}
|
|
11151
|
+
{advancedFeatures}
|
|
11088
11152
|
onPanToggle={() => activeTool === 'pan' ? onSelectSelectTool() : onSelectPanTool()}
|
|
11089
11153
|
onZoomIn={zoomIn}
|
|
11090
11154
|
onZoomOut={zoomOut}
|
|
@@ -11098,6 +11162,7 @@ function checkAndAddNewShapeToFrame(shape: any) {
|
|
|
11098
11162
|
hasUnsavedChanges={_hasUnsavedChanges}
|
|
11099
11163
|
onSave={saveNow}
|
|
11100
11164
|
/>
|
|
11165
|
+
</div>
|
|
11101
11166
|
{/if}
|
|
11102
11167
|
|
|
11103
11168
|
<!-- Canvas -->
|
|
@@ -11156,6 +11221,7 @@ function checkAndAddNewShapeToFrame(shape: any) {
|
|
|
11156
11221
|
onToggleStickyTool={() => { stickyToolEnabled = !stickyToolEnabled; }}
|
|
11157
11222
|
{onInsertLibraryShape}
|
|
11158
11223
|
{onSelectLibraryShapeTool}
|
|
11224
|
+
{advancedFeatures}
|
|
11159
11225
|
/>
|
|
11160
11226
|
{/if}
|
|
11161
11227
|
|
|
@@ -11294,5 +11360,5 @@ function checkAndAddNewShapeToFrame(shape: any) {
|
|
|
11294
11360
|
-->
|
|
11295
11361
|
|
|
11296
11362
|
<style>/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
|
11297
|
-
button{cursor:pointer}.canvas-container{-webkit-user-select:none;user-select:none}.canvas-container input,.canvas-container textarea{-webkit-user-select:text;user-select:text}.canvas-loader-spinner{border:3px solid #e5e7eb;border-top-color:#374151;border-radius:50%;width:36px;height:36px;animation:.8s linear infinite canvas-spin}@keyframes canvas-spin{to{transform:rotate(360deg)}}
|
|
11363
|
+
button{cursor:pointer}.canvas-container{-webkit-user-select:none;user-select:none}.canvas-container input,.canvas-container textarea{-webkit-user-select:text;user-select:text}.canvas-loader-spinner{border:3px solid #e5e7eb;border-top-color:#374151;border-radius:50%;width:36px;height:36px;animation:.8s linear infinite canvas-spin}@keyframes canvas-spin{to{transform:rotate(360deg)}}.canvas-pan-zoom-overlay{width:max-content;max-width:calc(100% - 2rem);overflow:visible}
|
|
11298
11364
|
</style>
|
|
@@ -22,6 +22,7 @@ declare const __propDef: {
|
|
|
22
22
|
}) => void) | null;
|
|
23
23
|
panZoomPosition?: "TL" | "TC" | "TR" | "BL" | "BC" | "BR";
|
|
24
24
|
wireframeMode?: boolean;
|
|
25
|
+
advancedFeatures?: boolean;
|
|
25
26
|
onReady?: (() => void) | null;
|
|
26
27
|
isExternalDataLoading?: boolean;
|
|
27
28
|
onBeforeUnmount?: ((json: string | null) => void) | null;
|
|
@@ -60,6 +61,16 @@ declare const __propDef: {
|
|
|
60
61
|
viewportWidth: number;
|
|
61
62
|
viewportHeight: number;
|
|
62
63
|
} | null;
|
|
64
|
+
getObjectScreenRect?: (objectId: string) => {
|
|
65
|
+
left: number;
|
|
66
|
+
top: number;
|
|
67
|
+
width: number;
|
|
68
|
+
height: number;
|
|
69
|
+
viewportLeft: number;
|
|
70
|
+
viewportTop: number;
|
|
71
|
+
viewportWidth: number;
|
|
72
|
+
viewportHeight: number;
|
|
73
|
+
} | null;
|
|
63
74
|
getWidgetShapeDataUrl?: (widgetId: string) => string | null;
|
|
64
75
|
addWidgetShape?: (widgetId: string, widgetName?: string, gridIndex?: number, totalCount?: number, previewImageUrl?: string | null) => string | null;
|
|
65
76
|
getLinkedWidgetIds?: () => Set<string>;
|
|
@@ -132,6 +143,16 @@ export default class Canvas extends SvelteComponentTyped<CanvasProps, CanvasEven
|
|
|
132
143
|
viewportWidth: number;
|
|
133
144
|
viewportHeight: number;
|
|
134
145
|
} | null;
|
|
146
|
+
get getObjectScreenRect(): (objectId: string) => {
|
|
147
|
+
left: number;
|
|
148
|
+
top: number;
|
|
149
|
+
width: number;
|
|
150
|
+
height: number;
|
|
151
|
+
viewportLeft: number;
|
|
152
|
+
viewportTop: number;
|
|
153
|
+
viewportWidth: number;
|
|
154
|
+
viewportHeight: number;
|
|
155
|
+
} | null;
|
|
135
156
|
get getWidgetShapeDataUrl(): (widgetId: string) => string | null;
|
|
136
157
|
get addWidgetShape(): (widgetId: string, widgetName?: string, gridIndex?: number, totalCount?: number, previewImageUrl?: string | null) => string | null;
|
|
137
158
|
get getLinkedWidgetIds(): () => Set<string>;
|
|
@@ -63,6 +63,9 @@
|
|
|
63
63
|
/** Draw-to-place: activates library shape tool so user clicks-and-drags to size it. */
|
|
64
64
|
export let onSelectLibraryShapeTool: (shapeKey: string, pathData: string) => void = () => {};
|
|
65
65
|
|
|
66
|
+
/** Extra tools (undo/redo/lock, frame, image, shape library). Code stays; UI is gated. */
|
|
67
|
+
export let advancedFeatures: boolean = false;
|
|
68
|
+
|
|
66
69
|
// Pre-group shapes for the dropdown
|
|
67
70
|
const libraryShapesByCategory = getShapesByCategory();
|
|
68
71
|
|
|
@@ -385,12 +388,12 @@
|
|
|
385
388
|
case 'D': event.preventDefault(); onSelectDrawTool(); break;
|
|
386
389
|
case 'H': event.preventDefault(); onSelectPanTool(); break;
|
|
387
390
|
case 'A': event.preventDefault(); selectShape('arrow'); break;
|
|
388
|
-
case 'R': event.preventDefault(); selectShape('rect'); break;
|
|
389
|
-
case 'G': event.preventDefault(); selectShape('triangle'); break;
|
|
390
|
-
case 'O': event.preventDefault(); selectShape('circle'); break;
|
|
391
|
-
case 'F': event.preventDefault(); onSelectFrameTool(); break;
|
|
391
|
+
case 'R': if (advancedFeatures) { event.preventDefault(); selectShape('rect'); } break;
|
|
392
|
+
case 'G': if (advancedFeatures) { event.preventDefault(); selectShape('triangle'); } break;
|
|
393
|
+
case 'O': if (advancedFeatures) { event.preventDefault(); selectShape('circle'); } break;
|
|
394
|
+
case 'F': if (advancedFeatures) { event.preventDefault(); onSelectFrameTool(); } break;
|
|
392
395
|
case 'T': event.preventDefault(); onSelectTextTool(); break;
|
|
393
|
-
case 'I': event.preventDefault(); onSelectImageTool(); break;
|
|
396
|
+
case 'I': if (advancedFeatures) { event.preventDefault(); onSelectImageTool(); } break;
|
|
394
397
|
}
|
|
395
398
|
}
|
|
396
399
|
|
|
@@ -464,6 +467,7 @@
|
|
|
464
467
|
<!-- Scrollable container for tools — overflow-x-auto clips absolute children,
|
|
465
468
|
so dropdowns and tooltips are rendered as siblings OUTSIDE this div -->
|
|
466
469
|
<div class="tools-ct flex flex-row">
|
|
470
|
+
{#if advancedFeatures}
|
|
467
471
|
<!-- ── Utility cluster: Undo, Redo, Hand, Lock | Divider ── -->
|
|
468
472
|
<Tooltip text="Undo (Ctrl+Z)">
|
|
469
473
|
<div class="disabled-tooltip-wrap">
|
|
@@ -528,6 +532,7 @@
|
|
|
528
532
|
</Tooltip>
|
|
529
533
|
|
|
530
534
|
<div class="toolbar-divider"></div>
|
|
535
|
+
{/if}
|
|
531
536
|
|
|
532
537
|
<!-- ══ Illustrated tools: Pencil & Eraser — "pulled from sheath" on select ══ -->
|
|
533
538
|
<!-- Each tool is wrapped in a mask container that clips its body at rest.
|
|
@@ -623,6 +628,7 @@
|
|
|
623
628
|
</button>
|
|
624
629
|
</Tooltip>
|
|
625
630
|
|
|
631
|
+
{#if advancedFeatures}
|
|
626
632
|
<div class="toolbar-divider"></div>
|
|
627
633
|
|
|
628
634
|
<Tooltip text="Frame (F)">
|
|
@@ -678,6 +684,7 @@
|
|
|
678
684
|
</button>
|
|
679
685
|
</Tooltip>
|
|
680
686
|
</div>
|
|
687
|
+
{/if}
|
|
681
688
|
</div>
|
|
682
689
|
</div>
|
|
683
690
|
|
|
@@ -708,7 +715,7 @@
|
|
|
708
715
|
dropdownOpenAbove=true: appears above the trigger (toolbar at bottom).
|
|
709
716
|
dropdownOpenAbove=false: appears below (toolbar at top).
|
|
710
717
|
═══════════════════════════════════════════════════════════════ -->
|
|
711
|
-
{#if shapesDropdownOpen}
|
|
718
|
+
{#if shapesDropdownOpen && advancedFeatures}
|
|
712
719
|
<!-- position:absolute within .canvas-container (position:relative) so the dropdown
|
|
713
720
|
is correctly positioned regardless of any CSS transform on ancestors. -->
|
|
714
721
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
@@ -32,6 +32,7 @@ declare const __propDef: {
|
|
|
32
32
|
onToggleStickyTool?: () => void;
|
|
33
33
|
/** Called when user clicks a library shape. Canvas inserts it as a Path. */ onInsertLibraryShape?: (shapeKey: string, pathData: string) => void;
|
|
34
34
|
/** Draw-to-place: activates library shape tool so user clicks-and-drags to size it. */ onSelectLibraryShapeTool?: (shapeKey: string, pathData: string) => void;
|
|
35
|
+
/** Extra tools (undo/redo/lock, frame, image, shape library). Code stays; UI is gated. */ advancedFeatures?: boolean;
|
|
35
36
|
addText?: () => void;
|
|
36
37
|
addRect?: () => void;
|
|
37
38
|
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">
|
|
@@ -138,6 +162,7 @@
|
|
|
138
162
|
</button>
|
|
139
163
|
</Tooltip>
|
|
140
164
|
|
|
165
|
+
{#if advancedFeatures}
|
|
141
166
|
<div class="divider"></div>
|
|
142
167
|
|
|
143
168
|
<!-- Reset Zoom (100%) -->
|
|
@@ -181,6 +206,7 @@
|
|
|
181
206
|
</svg>
|
|
182
207
|
</button>
|
|
183
208
|
</Tooltip>
|
|
209
|
+
{/if}
|
|
184
210
|
|
|
185
211
|
{#if showSaveButton}
|
|
186
212
|
<div class="divider"></div>
|
|
@@ -201,18 +227,66 @@
|
|
|
201
227
|
</button>
|
|
202
228
|
</Tooltip>
|
|
203
229
|
{/if}
|
|
230
|
+
|
|
231
|
+
<div class="pan-zoom-minimize-anchor">
|
|
232
|
+
<Tooltip text="Minimize zoom" position="above">
|
|
233
|
+
<button
|
|
234
|
+
type="button"
|
|
235
|
+
class="panel-btn pan-zoom-minimize-btn"
|
|
236
|
+
onclick={toggleMinimized}
|
|
237
|
+
aria-label="Minimize zoom controls"
|
|
238
|
+
>
|
|
239
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
|
|
240
|
+
<polyline points="4 14 10 14 10 20"/>
|
|
241
|
+
<polyline points="20 10 14 10 14 4"/>
|
|
242
|
+
<line x1="14" y1="10" x2="21" y2="3"/>
|
|
243
|
+
<line x1="3" y1="21" x2="10" y2="14"/>
|
|
244
|
+
</svg>
|
|
245
|
+
</button>
|
|
246
|
+
</Tooltip>
|
|
247
|
+
</div>
|
|
248
|
+
{/if}
|
|
204
249
|
</div>
|
|
205
250
|
|
|
206
251
|
<style>
|
|
207
252
|
.pan-zoom-panel {
|
|
253
|
+
position: relative;
|
|
208
254
|
display: flex;
|
|
209
255
|
align-items: center;
|
|
210
256
|
gap: 4px;
|
|
257
|
+
width: max-content;
|
|
258
|
+
max-width: 100%;
|
|
211
259
|
padding: 6px 10px;
|
|
212
260
|
background: white;
|
|
213
261
|
border: 2px solid #e5e7eb;
|
|
214
262
|
border-radius: 8px;
|
|
215
263
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
264
|
+
overflow: visible;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.pan-zoom-minimize-anchor {
|
|
268
|
+
position: absolute;
|
|
269
|
+
top: -8px;
|
|
270
|
+
right: -8px;
|
|
271
|
+
z-index: 2;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.pan-zoom-minimize-btn {
|
|
275
|
+
width: 18px;
|
|
276
|
+
height: 18px;
|
|
277
|
+
background: white;
|
|
278
|
+
border: 1px solid #e5e7eb;
|
|
279
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.pan-zoom-panel-minimized {
|
|
283
|
+
padding: 4px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.pan-zoom-expand-btn {
|
|
287
|
+
min-width: 44px;
|
|
288
|
+
width: auto;
|
|
289
|
+
padding: 0 8px;
|
|
216
290
|
}
|
|
217
291
|
|
|
218
292
|
.panel-btn {
|
|
@@ -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;
|
|
@@ -568,7 +568,7 @@
|
|
|
568
568
|
<span class="prop-label">Convert to Widget</span>
|
|
569
569
|
<div class="capture-section">
|
|
570
570
|
<button class="convert-widget-btn" onclick={onConvertToWidget}>
|
|
571
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
571
|
+
<svg class="convert-widget-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
572
572
|
<rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/>
|
|
573
573
|
<line x1="7" y1="2" x2="7" y2="22"/>
|
|
574
574
|
<line x1="17" y1="2" x2="17" y2="22"/>
|
|
@@ -1550,9 +1550,14 @@
|
|
|
1550
1550
|
box-shadow: 0 2px 4px rgba(49, 79, 47, 0.3);
|
|
1551
1551
|
}
|
|
1552
1552
|
|
|
1553
|
-
|
|
1553
|
+
/* Host apps (site / DS) leak global svg fill/stroke; keep this icon white on the green button. */
|
|
1554
|
+
.convert-widget-btn :global(svg),
|
|
1555
|
+
.convert-widget-btn :global(svg *) {
|
|
1554
1556
|
width: 16px;
|
|
1555
1557
|
height: 16px;
|
|
1558
|
+
color: #ffffff !important;
|
|
1559
|
+
fill: none !important;
|
|
1560
|
+
stroke: currentColor !important;
|
|
1556
1561
|
}
|
|
1557
1562
|
|
|
1558
1563
|
.widget-linked-section {
|
|
@@ -1669,7 +1674,9 @@
|
|
|
1669
1674
|
border-radius: 4px;
|
|
1670
1675
|
cursor: pointer;
|
|
1671
1676
|
transition: all 0.15s;
|
|
1672
|
-
width:
|
|
1677
|
+
width: 100%;
|
|
1678
|
+
box-sizing: border-box;
|
|
1679
|
+
justify-content: center;
|
|
1673
1680
|
}
|
|
1674
1681
|
.refresh-screenshot-btn:hover {
|
|
1675
1682
|
color: #166534;
|
|
@@ -1695,14 +1702,18 @@
|
|
|
1695
1702
|
border: 1px solid #fecaca;
|
|
1696
1703
|
border-radius: 4px;
|
|
1697
1704
|
cursor: pointer;
|
|
1698
|
-
width:
|
|
1705
|
+
width: 100%;
|
|
1706
|
+
box-sizing: border-box;
|
|
1707
|
+
justify-content: center;
|
|
1699
1708
|
}
|
|
1700
1709
|
.set-primary-btn:hover {
|
|
1701
1710
|
background: #fee2e2;
|
|
1702
1711
|
border-color: #f87171;
|
|
1703
1712
|
}
|
|
1704
1713
|
.primary-widget-badge {
|
|
1705
|
-
display:
|
|
1714
|
+
display: flex;
|
|
1715
|
+
align-items: center;
|
|
1716
|
+
justify-content: center;
|
|
1706
1717
|
margin-top: 4px;
|
|
1707
1718
|
padding: 3px 8px;
|
|
1708
1719
|
font-size: 11px;
|
|
@@ -1711,7 +1722,9 @@
|
|
|
1711
1722
|
background: #fef2f2;
|
|
1712
1723
|
border: 1px solid #fecaca;
|
|
1713
1724
|
border-radius: 4px;
|
|
1714
|
-
width:
|
|
1725
|
+
width: 100%;
|
|
1726
|
+
box-sizing: border-box;
|
|
1727
|
+
text-align: center;
|
|
1715
1728
|
}
|
|
1716
1729
|
|
|
1717
1730
|
.widget-orphan-notice {
|