@widgetic/canvas 0.5.4 → 0.5.6
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 +763 -143
- package/dist/canvas/Canvas.svelte.d.ts +33 -2
- package/dist/canvas/CanvasToolbar.svelte +5 -0
- package/dist/canvas/PanZoomPanel.svelte +15 -2
- package/dist/canvas/props-panel/PropsPanel.svelte +132 -1
- package/dist/canvas/props-panel/PropsPanel.svelte.d.ts +6 -0
- package/package.json +1 -1
|
@@ -36,17 +36,30 @@ declare const __propDef: {
|
|
|
36
36
|
onWidgetShapeDeleting?: ((widgetId: string) => Promise<boolean>) | null;
|
|
37
37
|
onWidgetRename?: ((widgetId: string, newName: string) => void) | null;
|
|
38
38
|
onRefreshScreenshot?: ((widgetId: string) => void) | null;
|
|
39
|
+
/** Host persists canvases.primary_widget_id after Canvas paints the red stroke. */ onSetPrimaryWidget?: ((widgetId: string) => void) | null;
|
|
39
40
|
getWidgetName?: ((widgetId: string) => string | null) | null;
|
|
40
41
|
/** Returns true when widgetId still exists in the host widget list. */ isWidgetKnown?: ((widgetId: string) => boolean) | null;
|
|
41
42
|
showLogs?: boolean;
|
|
42
43
|
shapeFillColor?: () => string;
|
|
43
44
|
deselectAll?: () => void;
|
|
45
|
+
duplicateSelectedObjects?: () => Promise<void>;
|
|
46
|
+
deleteSelectedObjects?: () => Promise<void>;
|
|
44
47
|
markSelectedAsDuplicating?: () => void;
|
|
45
48
|
linkObjectToWidget?: (fabricObject: any, widgetId: string, widgetName?: string | null) => void;
|
|
46
49
|
linkFrameToWidget?: (objectId: string, widgetId: string) => void;
|
|
47
50
|
replaceObjectWithWidgetImage?: (objectId: string, widgetId: string, screenshotDataUrl: string) => Promise<string | null>;
|
|
48
51
|
updateWidgetImage?: (widgetId: string, newScreenshotDataUrl: string) => void;
|
|
49
52
|
selectWidgetShape?: (widgetId: string) => boolean;
|
|
53
|
+
getWidgetShapeScreenRect?: (widgetId: string) => {
|
|
54
|
+
left: number;
|
|
55
|
+
top: number;
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
viewportLeft: number;
|
|
59
|
+
viewportTop: number;
|
|
60
|
+
viewportWidth: number;
|
|
61
|
+
viewportHeight: number;
|
|
62
|
+
} | null;
|
|
50
63
|
getWidgetShapeDataUrl?: (widgetId: string) => string | null;
|
|
51
64
|
addWidgetShape?: (widgetId: string, widgetName?: string, gridIndex?: number, totalCount?: number, previewImageUrl?: string | null) => string | null;
|
|
52
65
|
getLinkedWidgetIds?: () => Set<string>;
|
|
@@ -64,6 +77,8 @@ declare const __propDef: {
|
|
|
64
77
|
onSelectArrowTool?: () => void;
|
|
65
78
|
onSelectShapeTool?: (shapeType: "rect" | "circle" | "circle-true" | "triangle" | "arrow") => void;
|
|
66
79
|
onSelectFrameTool?: () => void;
|
|
80
|
+
setPrimaryWidgetShape?: (widgetId: string) => boolean;
|
|
81
|
+
getPrimaryWidgetId?: () => string | null;
|
|
67
82
|
onSelectImageTool?: () => void;
|
|
68
83
|
onSelectPanTool?: () => void;
|
|
69
84
|
zoomIn?: () => void;
|
|
@@ -71,10 +86,11 @@ declare const __propDef: {
|
|
|
71
86
|
zoomTo?: (percent: number) => void;
|
|
72
87
|
zoomReset?: () => void;
|
|
73
88
|
zoomFit?: () => void;
|
|
74
|
-
onGroupSelection?: () =>
|
|
89
|
+
onGroupSelection?: () => string | null;
|
|
75
90
|
onUngroupSelection?: () => void;
|
|
76
91
|
onSelectAllObjects?: () => void;
|
|
77
92
|
onDeselectAllObjects?: () => void;
|
|
93
|
+
saveNow?: () => void;
|
|
78
94
|
undo?: () => Promise<void>;
|
|
79
95
|
redo?: () => Promise<void>;
|
|
80
96
|
saveCanvas?: () => string | null;
|
|
@@ -98,12 +114,24 @@ export type CanvasSlots = typeof __propDef.slots;
|
|
|
98
114
|
export default class Canvas extends SvelteComponentTyped<CanvasProps, CanvasEvents, CanvasSlots> {
|
|
99
115
|
get shapeFillColor(): () => string;
|
|
100
116
|
get deselectAll(): () => void;
|
|
117
|
+
get duplicateSelectedObjects(): () => Promise<void>;
|
|
118
|
+
get deleteSelectedObjects(): () => Promise<void>;
|
|
101
119
|
get markSelectedAsDuplicating(): () => void;
|
|
102
120
|
get linkObjectToWidget(): (fabricObject: any, widgetId: string, widgetName?: string | null) => void;
|
|
103
121
|
get linkFrameToWidget(): (objectId: string, widgetId: string) => void;
|
|
104
122
|
get replaceObjectWithWidgetImage(): (objectId: string, widgetId: string, screenshotDataUrl: string) => Promise<string | null>;
|
|
105
123
|
get updateWidgetImage(): (widgetId: string, newScreenshotDataUrl: string) => void;
|
|
106
124
|
get selectWidgetShape(): (widgetId: string) => boolean;
|
|
125
|
+
get getWidgetShapeScreenRect(): (widgetId: string) => {
|
|
126
|
+
left: number;
|
|
127
|
+
top: number;
|
|
128
|
+
width: number;
|
|
129
|
+
height: number;
|
|
130
|
+
viewportLeft: number;
|
|
131
|
+
viewportTop: number;
|
|
132
|
+
viewportWidth: number;
|
|
133
|
+
viewportHeight: number;
|
|
134
|
+
} | null;
|
|
107
135
|
get getWidgetShapeDataUrl(): (widgetId: string) => string | null;
|
|
108
136
|
get addWidgetShape(): (widgetId: string, widgetName?: string, gridIndex?: number, totalCount?: number, previewImageUrl?: string | null) => string | null;
|
|
109
137
|
get getLinkedWidgetIds(): () => Set<string>;
|
|
@@ -121,6 +149,8 @@ export default class Canvas extends SvelteComponentTyped<CanvasProps, CanvasEven
|
|
|
121
149
|
get onSelectArrowTool(): () => void;
|
|
122
150
|
get onSelectShapeTool(): (shapeType: "rect" | "circle" | "circle-true" | "triangle" | "arrow") => void;
|
|
123
151
|
get onSelectFrameTool(): () => void;
|
|
152
|
+
get setPrimaryWidgetShape(): (widgetId: string) => boolean;
|
|
153
|
+
get getPrimaryWidgetId(): () => string | null;
|
|
124
154
|
get onSelectImageTool(): () => void;
|
|
125
155
|
get onSelectPanTool(): () => void;
|
|
126
156
|
get zoomIn(): () => void;
|
|
@@ -128,10 +158,11 @@ export default class Canvas extends SvelteComponentTyped<CanvasProps, CanvasEven
|
|
|
128
158
|
get zoomTo(): (percent: number) => void;
|
|
129
159
|
get zoomReset(): () => void;
|
|
130
160
|
get zoomFit(): () => void;
|
|
131
|
-
get onGroupSelection(): () =>
|
|
161
|
+
get onGroupSelection(): () => string | null;
|
|
132
162
|
get onUngroupSelection(): () => void;
|
|
133
163
|
get onSelectAllObjects(): () => void;
|
|
134
164
|
get onDeselectAllObjects(): () => void;
|
|
165
|
+
get saveNow(): () => void;
|
|
135
166
|
get undo(): () => Promise<void>;
|
|
136
167
|
get redo(): () => Promise<void>;
|
|
137
168
|
get saveCanvas(): () => string | null;
|
|
@@ -974,6 +974,11 @@ button:disabled {
|
|
|
974
974
|
color: white !important;
|
|
975
975
|
box-shadow: 0 2px 8px rgba(26, 58, 42, 0.25);
|
|
976
976
|
}
|
|
977
|
+
.wdg-tool-active :global(svg),
|
|
978
|
+
.wdg-tool-active :global(svg *) {
|
|
979
|
+
color: white !important;
|
|
980
|
+
stroke: currentColor !important;
|
|
981
|
+
}
|
|
977
982
|
.wdg-tool-active:hover {
|
|
978
983
|
background: #244b36 !important;
|
|
979
984
|
color: white !important;
|
|
@@ -241,13 +241,26 @@
|
|
|
241
241
|
|
|
242
242
|
.panel-btn.active {
|
|
243
243
|
background: var(--main-color, #314F2F);
|
|
244
|
-
color: white;
|
|
244
|
+
color: white !important;
|
|
245
245
|
border-color: var(--main-color, #314F2F);
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
/* Host apps (widget-creator / site) may set a global svg color; force icons to follow the button. */
|
|
249
|
+
.panel-btn.active :global(svg),
|
|
250
|
+
.panel-btn.active :global(svg *) {
|
|
251
|
+
color: white !important;
|
|
252
|
+
stroke: currentColor !important;
|
|
253
|
+
}
|
|
254
|
+
|
|
248
255
|
.panel-btn.active:hover {
|
|
249
256
|
background: var(--main-color-hover, #263d24);
|
|
250
|
-
color: white;
|
|
257
|
+
color: white !important;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.panel-btn.active:hover :global(svg),
|
|
261
|
+
.panel-btn.active:hover :global(svg *) {
|
|
262
|
+
color: white !important;
|
|
263
|
+
stroke: currentColor !important;
|
|
251
264
|
}
|
|
252
265
|
|
|
253
266
|
.panel-btn:disabled {
|
|
@@ -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,6 +549,20 @@
|
|
|
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>
|
|
@@ -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">
|
|
@@ -1583,6 +1683,37 @@
|
|
|
1583
1683
|
flex-shrink: 0;
|
|
1584
1684
|
}
|
|
1585
1685
|
|
|
1686
|
+
.set-primary-btn {
|
|
1687
|
+
display: flex;
|
|
1688
|
+
align-items: center;
|
|
1689
|
+
margin-top: 4px;
|
|
1690
|
+
padding: 3px 8px;
|
|
1691
|
+
font-size: 11px;
|
|
1692
|
+
font-weight: 500;
|
|
1693
|
+
color: #991b1b;
|
|
1694
|
+
background: #fef2f2;
|
|
1695
|
+
border: 1px solid #fecaca;
|
|
1696
|
+
border-radius: 4px;
|
|
1697
|
+
cursor: pointer;
|
|
1698
|
+
width: fit-content;
|
|
1699
|
+
}
|
|
1700
|
+
.set-primary-btn:hover {
|
|
1701
|
+
background: #fee2e2;
|
|
1702
|
+
border-color: #f87171;
|
|
1703
|
+
}
|
|
1704
|
+
.primary-widget-badge {
|
|
1705
|
+
display: inline-block;
|
|
1706
|
+
margin-top: 4px;
|
|
1707
|
+
padding: 3px 8px;
|
|
1708
|
+
font-size: 11px;
|
|
1709
|
+
font-weight: 600;
|
|
1710
|
+
color: #991b1b;
|
|
1711
|
+
background: #fef2f2;
|
|
1712
|
+
border: 1px solid #fecaca;
|
|
1713
|
+
border-radius: 4px;
|
|
1714
|
+
width: fit-content;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1586
1717
|
.widget-orphan-notice {
|
|
1587
1718
|
font-size: 11px;
|
|
1588
1719
|
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;
|