@widgetic/canvas 0.5.4 → 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.
@@ -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;
@@ -36,17 +37,40 @@ declare const __propDef: {
36
37
  onWidgetShapeDeleting?: ((widgetId: string) => Promise<boolean>) | null;
37
38
  onWidgetRename?: ((widgetId: string, newName: string) => void) | null;
38
39
  onRefreshScreenshot?: ((widgetId: string) => void) | null;
40
+ /** Host persists canvases.primary_widget_id after Canvas paints the red stroke. */ onSetPrimaryWidget?: ((widgetId: string) => void) | null;
39
41
  getWidgetName?: ((widgetId: string) => string | null) | null;
40
42
  /** Returns true when widgetId still exists in the host widget list. */ isWidgetKnown?: ((widgetId: string) => boolean) | null;
41
43
  showLogs?: boolean;
42
44
  shapeFillColor?: () => string;
43
45
  deselectAll?: () => void;
46
+ duplicateSelectedObjects?: () => Promise<void>;
47
+ deleteSelectedObjects?: () => Promise<void>;
44
48
  markSelectedAsDuplicating?: () => void;
45
49
  linkObjectToWidget?: (fabricObject: any, widgetId: string, widgetName?: string | null) => void;
46
50
  linkFrameToWidget?: (objectId: string, widgetId: string) => void;
47
51
  replaceObjectWithWidgetImage?: (objectId: string, widgetId: string, screenshotDataUrl: string) => Promise<string | null>;
48
52
  updateWidgetImage?: (widgetId: string, newScreenshotDataUrl: string) => void;
49
53
  selectWidgetShape?: (widgetId: string) => boolean;
54
+ getWidgetShapeScreenRect?: (widgetId: string) => {
55
+ left: number;
56
+ top: number;
57
+ width: number;
58
+ height: number;
59
+ viewportLeft: number;
60
+ viewportTop: number;
61
+ viewportWidth: number;
62
+ viewportHeight: number;
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;
50
74
  getWidgetShapeDataUrl?: (widgetId: string) => string | null;
51
75
  addWidgetShape?: (widgetId: string, widgetName?: string, gridIndex?: number, totalCount?: number, previewImageUrl?: string | null) => string | null;
52
76
  getLinkedWidgetIds?: () => Set<string>;
@@ -64,6 +88,8 @@ declare const __propDef: {
64
88
  onSelectArrowTool?: () => void;
65
89
  onSelectShapeTool?: (shapeType: "rect" | "circle" | "circle-true" | "triangle" | "arrow") => void;
66
90
  onSelectFrameTool?: () => void;
91
+ setPrimaryWidgetShape?: (widgetId: string) => boolean;
92
+ getPrimaryWidgetId?: () => string | null;
67
93
  onSelectImageTool?: () => void;
68
94
  onSelectPanTool?: () => void;
69
95
  zoomIn?: () => void;
@@ -71,10 +97,11 @@ declare const __propDef: {
71
97
  zoomTo?: (percent: number) => void;
72
98
  zoomReset?: () => void;
73
99
  zoomFit?: () => void;
74
- onGroupSelection?: () => void;
100
+ onGroupSelection?: () => string | null;
75
101
  onUngroupSelection?: () => void;
76
102
  onSelectAllObjects?: () => void;
77
103
  onDeselectAllObjects?: () => void;
104
+ saveNow?: () => void;
78
105
  undo?: () => Promise<void>;
79
106
  redo?: () => Promise<void>;
80
107
  saveCanvas?: () => string | null;
@@ -98,12 +125,34 @@ export type CanvasSlots = typeof __propDef.slots;
98
125
  export default class Canvas extends SvelteComponentTyped<CanvasProps, CanvasEvents, CanvasSlots> {
99
126
  get shapeFillColor(): () => string;
100
127
  get deselectAll(): () => void;
128
+ get duplicateSelectedObjects(): () => Promise<void>;
129
+ get deleteSelectedObjects(): () => Promise<void>;
101
130
  get markSelectedAsDuplicating(): () => void;
102
131
  get linkObjectToWidget(): (fabricObject: any, widgetId: string, widgetName?: string | null) => void;
103
132
  get linkFrameToWidget(): (objectId: string, widgetId: string) => void;
104
133
  get replaceObjectWithWidgetImage(): (objectId: string, widgetId: string, screenshotDataUrl: string) => Promise<string | null>;
105
134
  get updateWidgetImage(): (widgetId: string, newScreenshotDataUrl: string) => void;
106
135
  get selectWidgetShape(): (widgetId: string) => boolean;
136
+ get getWidgetShapeScreenRect(): (widgetId: string) => {
137
+ left: number;
138
+ top: number;
139
+ width: number;
140
+ height: number;
141
+ viewportLeft: number;
142
+ viewportTop: number;
143
+ viewportWidth: number;
144
+ viewportHeight: number;
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;
107
156
  get getWidgetShapeDataUrl(): (widgetId: string) => string | null;
108
157
  get addWidgetShape(): (widgetId: string, widgetName?: string, gridIndex?: number, totalCount?: number, previewImageUrl?: string | null) => string | null;
109
158
  get getLinkedWidgetIds(): () => Set<string>;
@@ -121,6 +170,8 @@ export default class Canvas extends SvelteComponentTyped<CanvasProps, CanvasEven
121
170
  get onSelectArrowTool(): () => void;
122
171
  get onSelectShapeTool(): (shapeType: "rect" | "circle" | "circle-true" | "triangle" | "arrow") => void;
123
172
  get onSelectFrameTool(): () => void;
173
+ get setPrimaryWidgetShape(): (widgetId: string) => boolean;
174
+ get getPrimaryWidgetId(): () => string | null;
124
175
  get onSelectImageTool(): () => void;
125
176
  get onSelectPanTool(): () => void;
126
177
  get zoomIn(): () => void;
@@ -128,10 +179,11 @@ export default class Canvas extends SvelteComponentTyped<CanvasProps, CanvasEven
128
179
  get zoomTo(): (percent: number) => void;
129
180
  get zoomReset(): () => void;
130
181
  get zoomFit(): () => void;
131
- get onGroupSelection(): () => void;
182
+ get onGroupSelection(): () => string | null;
132
183
  get onUngroupSelection(): () => void;
133
184
  get onSelectAllObjects(): () => void;
134
185
  get onDeselectAllObjects(): () => void;
186
+ get saveNow(): () => void;
135
187
  get undo(): () => Promise<void>;
136
188
  get redo(): () => Promise<void>;
137
189
  get saveCanvas(): () => string | null;
@@ -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 -->
@@ -974,6 +981,11 @@ button:disabled {
974
981
  color: white !important;
975
982
  box-shadow: 0 2px 8px rgba(26, 58, 42, 0.25);
976
983
  }
984
+ .wdg-tool-active :global(svg),
985
+ .wdg-tool-active :global(svg *) {
986
+ color: white !important;
987
+ stroke: currentColor !important;
988
+ }
977
989
  .wdg-tool-active:hover {
978
990
  background: #244b36 !important;
979
991
  color: white !important;
@@ -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 {
@@ -241,13 +315,26 @@
241
315
 
242
316
  .panel-btn.active {
243
317
  background: var(--main-color, #314F2F);
244
- color: white;
318
+ color: white !important;
245
319
  border-color: var(--main-color, #314F2F);
246
320
  }
247
321
 
322
+ /* Host apps (widget-creator / site) may set a global svg color; force icons to follow the button. */
323
+ .panel-btn.active :global(svg),
324
+ .panel-btn.active :global(svg *) {
325
+ color: white !important;
326
+ stroke: currentColor !important;
327
+ }
328
+
248
329
  .panel-btn.active:hover {
249
330
  background: var(--main-color-hover, #263d24);
250
- color: white;
331
+ color: white !important;
332
+ }
333
+
334
+ .panel-btn.active:hover :global(svg),
335
+ .panel-btn.active:hover :global(svg *) {
336
+ color: white !important;
337
+ stroke: currentColor !important;
251
338
  }
252
339
 
253
340
  .panel-btn:disabled {
@@ -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;
@@ -27,6 +27,11 @@
27
27
  export let onStrokeTypeChange: (type: 'solid' | 'dashed' | 'dotted') => void = () => {};
28
28
  export let onOpacityChange: (opacity: number) => void = () => {};
29
29
  export let onFillOpacityChange: (opacity: number) => void = () => {};
30
+ export let onGroupFillChange: (color: string) => void = () => {};
31
+ export let onGroupFillOpacityChange: (opacity: number) => void = () => {};
32
+ export let onGroupStrokeChange: (color: string) => void = () => {};
33
+ export let onGroupStrokeWidthChange: (width: number) => void = () => {};
34
+ export let onGroupStrokeTypeChange: (type: 'solid' | 'dashed' | 'dotted') => void = () => {};
30
35
  export let onBackgroundColorChange: (color: string) => void = () => {};
31
36
  export let onBackgroundOpacityChange: (opacity: number) => void = () => {};
32
37
 
@@ -81,6 +86,7 @@
81
86
 
82
87
  // Callback: fires when the user requests a screenshot refresh for the linked widget
83
88
  export let onRefreshScreenshot: ((widgetId: string) => void) | null = null;
89
+ export let onSetAsPrimary: ((widgetId: string) => void) | null = null;
84
90
 
85
91
  // Widget name resolver: given a widgetId, returns the widget name (or null)
86
92
  export let getWidgetName: ((widgetId: string) => string | null) | null = null;
@@ -133,6 +139,7 @@
133
139
  // FrameShape objects have type === 'FrameShape', not 'group', so isGroup is always false for them.
134
140
  // isMultiSelect is only true for plain Groups and ActiveSelections (not Frames).
135
141
  $: isMultiSelect = (isGroup && !isFrame) || isActiveSelection;
142
+ $: isPlainGroup = isGroup && !isFrame;
136
143
  $: isLine = objectType === 'line';
137
144
  $: isImage = objectType === 'image' || customType === 'image';
138
145
  // Widget link: true when the selected object has already been converted to a widget
@@ -220,7 +227,6 @@
220
227
  // For Frames (Group-based), read fill/stroke from custom properties (_frameFill, _frameStroke)
221
228
  // because the actual fill/stroke is on the background Rect child, not the Group itself
222
229
  $: currentFill = isFrame ? (selectedObject?._frameFill ?? '#FFFFFF') : (selectedObject?.fill ?? '#FFFFFF');
223
- // Fill opacity is stored as custom property _fillOpacity (0-1), display as percentage
224
230
  $: currentFillOpacity = Math.round((selectedObject?._fillOpacity ?? 1) * 100);
225
231
  $: currentStroke = isFrame ? (selectedObject?._frameStroke ?? '#000000') : (selectedObject?.stroke ?? '#000000');
226
232
  $: currentStrokeWidth = isFrame ? (selectedObject?._frameStrokeWidth ?? 1) : (selectedObject?.strokeWidth ?? 1);
@@ -238,6 +244,11 @@
238
244
  ? (selectedObject as any)?._frameStrokeDashArray
239
245
  : selectedObject?.strokeDashArray
240
246
  );
247
+ $: groupFill = selectedObject?._groupFill ?? '#FFFFFF';
248
+ $: groupFillOpacity = Math.round((selectedObject?._groupFillOpacity ?? 1) * 100);
249
+ $: groupStroke = selectedObject?._groupStroke ?? '#000000';
250
+ $: groupStrokeWidth = selectedObject?._groupStrokeWidth ?? 0;
251
+ $: groupStrokeType = getStrokeType((selectedObject as any)?._groupStrokeDashArray);
241
252
 
242
253
  // Text properties - for groups, get from first textbox
243
254
  $: firstTextboxInGroup = (selectedObject?.type === 'group' || selectedObject?.type === 'activeSelection' || selectedObject?.type === 'activeselection')
@@ -538,12 +549,26 @@
538
549
  Refresh Widget Preview
539
550
  </button>
540
551
  {/if}
552
+ {#if onSetAsPrimary && widgetId}
553
+ {#if selectedObject?._isPrimaryWidget}
554
+ <span class="primary-widget-badge">Primary widget</span>
555
+ {:else}
556
+ <button
557
+ class="set-primary-btn"
558
+ type="button"
559
+ title="Use this widget as the canvas primary (red stroke)"
560
+ onclick={() => onSetAsPrimary && widgetId && onSetAsPrimary(widgetId)}
561
+ >
562
+ Set as primary
563
+ </button>
564
+ {/if}
565
+ {/if}
541
566
  {/if}
542
567
  {:else}
543
568
  <span class="prop-label">Convert to Widget</span>
544
569
  <div class="capture-section">
545
570
  <button class="convert-widget-btn" onclick={onConvertToWidget}>
546
- <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">
547
572
  <rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/>
548
573
  <line x1="7" y1="2" x2="7" y2="22"/>
549
574
  <line x1="17" y1="2" x2="17" y2="22"/>
@@ -1115,6 +1140,81 @@
1115
1140
  {/if}
1116
1141
  {/if}
1117
1142
 
1143
+ <!-- ==================== GROUP BOX (chrome around children, like Frame) ==================== -->
1144
+ {#if isPlainGroup && !isWidgetImage}
1145
+ <div class="section-divider">
1146
+ <span class="section-title">Group Box</span>
1147
+ </div>
1148
+ {#if wireframeMode || !isMinimized}
1149
+ <div class="prop-section">
1150
+ <ColorIC
1151
+ label="Group Fill Color"
1152
+ value={groupFill || '#FFFFFF'}
1153
+ onChange={onGroupFillChange}
1154
+ showPicker={!wireframeMode}
1155
+ presets={wireframeMode ? wireframeColorValues : null}
1156
+ />
1157
+ </div>
1158
+ {/if}
1159
+ <SliderUnitIC
1160
+ label="Group Fill Opacity"
1161
+ value={groupFillOpacity}
1162
+ min={0}
1163
+ max={100}
1164
+ step={1}
1165
+ unit="%"
1166
+ onChange={onGroupFillOpacityChange}
1167
+ />
1168
+ {#if wireframeMode || !isMinimized}
1169
+ <div class="prop-section">
1170
+ <ColorIC
1171
+ label="Group Stroke Color"
1172
+ value={groupStroke || '#000000'}
1173
+ onChange={onGroupStrokeChange}
1174
+ showPicker={!wireframeMode}
1175
+ presets={wireframeMode ? wireframeColorValues : null}
1176
+ />
1177
+ </div>
1178
+ {/if}
1179
+ <SliderUnitIC
1180
+ label="Group Stroke Width"
1181
+ value={groupStrokeWidth}
1182
+ min={0}
1183
+ max={20}
1184
+ step={1}
1185
+ unit="px"
1186
+ onChange={onGroupStrokeWidthChange}
1187
+ />
1188
+ {#if !isMinimized}
1189
+ <div class="prop-section">
1190
+ <span class="prop-label">Group Stroke Type</span>
1191
+ <div class="stroke-type-buttons">
1192
+ <button
1193
+ class="stroke-type-btn {groupStrokeType === 'solid' ? 'active' : ''}"
1194
+ onclick={() => onGroupStrokeTypeChange('solid')}
1195
+ >
1196
+ <span class="stroke-preview solid"></span>
1197
+ Solid
1198
+ </button>
1199
+ <button
1200
+ class="stroke-type-btn {groupStrokeType === 'dashed' ? 'active' : ''}"
1201
+ onclick={() => onGroupStrokeTypeChange('dashed')}
1202
+ >
1203
+ <span class="stroke-preview dashed"></span>
1204
+ Dashed
1205
+ </button>
1206
+ <button
1207
+ class="stroke-type-btn {groupStrokeType === 'dotted' ? 'active' : ''}"
1208
+ onclick={() => onGroupStrokeTypeChange('dotted')}
1209
+ >
1210
+ <span class="stroke-preview dotted"></span>
1211
+ Dotted
1212
+ </button>
1213
+ </div>
1214
+ </div>
1215
+ {/if}
1216
+ {/if}
1217
+
1118
1218
  <!-- ==================== TEXT PROPERTIES (for selection/group containing textboxes or frames) ==================== -->
1119
1219
  {#if !isMinimized && hasTextboxInSelection}
1120
1220
  <div class="section-divider">
@@ -1450,9 +1550,14 @@
1450
1550
  box-shadow: 0 2px 4px rgba(49, 79, 47, 0.3);
1451
1551
  }
1452
1552
 
1453
- .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 *) {
1454
1556
  width: 16px;
1455
1557
  height: 16px;
1558
+ color: #ffffff !important;
1559
+ fill: none !important;
1560
+ stroke: currentColor !important;
1456
1561
  }
1457
1562
 
1458
1563
  .widget-linked-section {
@@ -1569,7 +1674,9 @@
1569
1674
  border-radius: 4px;
1570
1675
  cursor: pointer;
1571
1676
  transition: all 0.15s;
1572
- width: fit-content;
1677
+ width: 100%;
1678
+ box-sizing: border-box;
1679
+ justify-content: center;
1573
1680
  }
1574
1681
  .refresh-screenshot-btn:hover {
1575
1682
  color: #166534;
@@ -1583,6 +1690,43 @@
1583
1690
  flex-shrink: 0;
1584
1691
  }
1585
1692
 
1693
+ .set-primary-btn {
1694
+ display: flex;
1695
+ align-items: center;
1696
+ margin-top: 4px;
1697
+ padding: 3px 8px;
1698
+ font-size: 11px;
1699
+ font-weight: 500;
1700
+ color: #991b1b;
1701
+ background: #fef2f2;
1702
+ border: 1px solid #fecaca;
1703
+ border-radius: 4px;
1704
+ cursor: pointer;
1705
+ width: 100%;
1706
+ box-sizing: border-box;
1707
+ justify-content: center;
1708
+ }
1709
+ .set-primary-btn:hover {
1710
+ background: #fee2e2;
1711
+ border-color: #f87171;
1712
+ }
1713
+ .primary-widget-badge {
1714
+ display: flex;
1715
+ align-items: center;
1716
+ justify-content: center;
1717
+ margin-top: 4px;
1718
+ padding: 3px 8px;
1719
+ font-size: 11px;
1720
+ font-weight: 600;
1721
+ color: #991b1b;
1722
+ background: #fef2f2;
1723
+ border: 1px solid #fecaca;
1724
+ border-radius: 4px;
1725
+ width: 100%;
1726
+ box-sizing: border-box;
1727
+ text-align: center;
1728
+ }
1729
+
1586
1730
  .widget-orphan-notice {
1587
1731
  font-size: 11px;
1588
1732
  line-height: 1.45;
@@ -12,6 +12,11 @@ declare const __propDef: {
12
12
  onStrokeTypeChange?: (type: "solid" | "dashed" | "dotted") => void;
13
13
  onOpacityChange?: (opacity: number) => void;
14
14
  onFillOpacityChange?: (opacity: number) => void;
15
+ onGroupFillChange?: (color: string) => void;
16
+ onGroupFillOpacityChange?: (opacity: number) => void;
17
+ onGroupStrokeChange?: (color: string) => void;
18
+ onGroupStrokeWidthChange?: (width: number) => void;
19
+ onGroupStrokeTypeChange?: (type: "solid" | "dashed" | "dotted") => void;
15
20
  onBackgroundColorChange?: (color: string) => void;
16
21
  onBackgroundOpacityChange?: (opacity: number) => void;
17
22
  onFontSizeChange?: (size: number) => void;
@@ -44,6 +49,7 @@ declare const __propDef: {
44
49
  onWidgetClick?: ((widgetId: string) => void) | null;
45
50
  onWidgetRename?: ((widgetId: string, newName: string) => void) | null;
46
51
  onRefreshScreenshot?: ((widgetId: string) => void) | null;
52
+ onSetAsPrimary?: ((widgetId: string) => void) | null;
47
53
  getWidgetName?: ((widgetId: string) => string | null) | null;
48
54
  /** Returns false when the linked widget was deleted or moved off this canvas. */ isWidgetKnown?: ((widgetId: string) => boolean) | null;
49
55
  /** Remove the selected orphaned widget shape from the canvas. */ onRemoveOrphanedShape?: (() => void) | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widgetic/canvas",
3
- "version": "0.5.4",
3
+ "version": "0.5.7",
4
4
  "type": "module",
5
5
  "svelte": "./dist/index.js",
6
6
  "module": "./dist/index.js",