@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.
@@ -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
- // Disable Alt/Option-triggered centered scaling for Frame objects.
2085
- // Alt is used for "preserve children" resize on frames, not center-scale.
2086
- // The centeredKey check happens BEFORE before:transform, so we can't prevent it.
2087
- // Instead, override the transform origin from 'center' back to the correct corner.
2088
- if (isFrameShape(transform.target) && transform.originX === 'center' && transform.originY === 'center') {
2089
- // Map corner handle to the correct opposite origin
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('Frame: overrode centered scaling origin for corner', corner, '→', origin);
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: target.left, y: target.top },
2138
- tr: { x: target.left + w, y: target.top },
2139
- bl: { x: target.left, y: target.top + h },
2140
- br: { x: target.left + w, y: target.top + h }
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
- if (onNonWidgetSelected) {
4587
- onNonWidgetSelected();
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
- <PanZoomPanel
11084
- class="absolute {getPanZoomPositionClass(panZoomPosition)} z-10"
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
- .convert-widget-btn svg {
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: fit-content;
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: fit-content;
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: inline-block;
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: fit-content;
1725
+ width: 100%;
1726
+ box-sizing: border-box;
1727
+ text-align: center;
1715
1728
  }
1716
1729
 
1717
1730
  .widget-orphan-notice {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widgetic/canvas",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "type": "module",
5
5
  "svelte": "./dist/index.js",
6
6
  "module": "./dist/index.js",