@widgetic/canvas 0.5.4

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.
Files changed (51) hide show
  1. package/README.md +45 -0
  2. package/dist/canvas/Canvas.svelte +10678 -0
  3. package/dist/canvas/Canvas.svelte.d.ts +147 -0
  4. package/dist/canvas/CanvasToolbar.svelte +1422 -0
  5. package/dist/canvas/CanvasToolbar.svelte.d.ts +54 -0
  6. package/dist/canvas/ContextMenu.svelte +279 -0
  7. package/dist/canvas/ContextMenu.svelte.d.ts +36 -0
  8. package/dist/canvas/PanZoomPanel.svelte +315 -0
  9. package/dist/canvas/PanZoomPanel.svelte.d.ts +32 -0
  10. package/dist/canvas/canvasLogger.d.ts +5 -0
  11. package/dist/canvas/canvasLogger.js +19 -0
  12. package/dist/canvas/index.d.ts +13 -0
  13. package/dist/canvas/index.js +12 -0
  14. package/dist/canvas/props-panel/PropsPanel.svelte +1902 -0
  15. package/dist/canvas/props-panel/PropsPanel.svelte.d.ts +73 -0
  16. package/dist/canvas/props-panel/TextPropsICSection.svelte +238 -0
  17. package/dist/canvas/props-panel/TextPropsICSection.svelte.d.ts +36 -0
  18. package/dist/canvas/shapes/FrameShape.d.ts +97 -0
  19. package/dist/canvas/shapes/FrameShape.js +951 -0
  20. package/dist/canvas/shapes/ImageShape.d.ts +38 -0
  21. package/dist/canvas/shapes/ImageShape.js +245 -0
  22. package/dist/canvas/shapes/ShapeLibrary.d.ts +64 -0
  23. package/dist/canvas/shapes/ShapeLibrary.js +526 -0
  24. package/dist/canvas/shapes/WidgetShape.d.ts +21 -0
  25. package/dist/canvas/shapes/WidgetShape.js +132 -0
  26. package/dist/canvas/types.d.ts +26 -0
  27. package/dist/canvas/types.js +5 -0
  28. package/dist/components/Tooltip.svelte +179 -0
  29. package/dist/components/Tooltip.svelte.d.ts +21 -0
  30. package/dist/components/index.d.ts +11 -0
  31. package/dist/components/index.js +13 -0
  32. package/dist/components/input-controls/ColorIC.svelte +388 -0
  33. package/dist/components/input-controls/ColorIC.svelte.d.ts +22 -0
  34. package/dist/components/input-controls/FontSelectorIC.svelte +69 -0
  35. package/dist/components/input-controls/FontSelectorIC.svelte.d.ts +17 -0
  36. package/dist/components/input-controls/SliderUnitIC.svelte +217 -0
  37. package/dist/components/input-controls/SliderUnitIC.svelte.d.ts +24 -0
  38. package/dist/components/input-controls/TextAlignIC.svelte +84 -0
  39. package/dist/components/input-controls/TextAlignIC.svelte.d.ts +17 -0
  40. package/dist/components/input-controls/TextStyleIC.svelte +85 -0
  41. package/dist/components/input-controls/TextStyleIC.svelte.d.ts +21 -0
  42. package/dist/icons/arrow.svg +3 -0
  43. package/dist/icons/checkmark.svg +3 -0
  44. package/dist/icons/draw.svg +22 -0
  45. package/dist/icons/eraser.svg +23 -0
  46. package/dist/icons/hand.svg +6 -0
  47. package/dist/icons/select.svg +3 -0
  48. package/dist/icons/text.svg +5 -0
  49. package/dist/index.d.ts +11 -0
  50. package/dist/index.js +12 -0
  51. package/package.json +101 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * WidgetShape — extends ImageShape with widget-specific properties.
3
+ *
4
+ * Adds _isWidgetImage and _widgetId to link an image placeholder to a
5
+ * generated widget in the Widgetic platform.
6
+ *
7
+ * When an ImageShape is "converted to widget", its prototype is swapped
8
+ * to WidgetShape via upgradeToWidgetShape(), and the serialized type
9
+ * becomes 'WidgetShape'.
10
+ *
11
+ * Backward-compat: old saves that stored widget links directly on ImageShape
12
+ * (with type:'ImageShape' + _isWidgetImage:true) are handled by the
13
+ * classRegistry alias and the reviver in Canvas.svelte.
14
+ */
15
+ import { Rect, classRegistry } from 'fabric';
16
+ import { ImageShape, IMAGE_SHAPE_CUSTOM_PROPS } from './ImageShape';
17
+ import { canvasLog, canvasWarn } from '../canvasLogger';
18
+ // Widget-specific custom properties (on top of IMAGE_SHAPE_CUSTOM_PROPS)
19
+ export const WIDGET_SHAPE_EXTRA_PROPS = [
20
+ '_isWidgetImage',
21
+ '_widgetId',
22
+ '_widgetName',
23
+ '_widgetOrphaned',
24
+ ];
25
+ // Full list: base image props + widget props
26
+ export const WIDGET_SHAPE_CUSTOM_PROPS = [
27
+ ...IMAGE_SHAPE_CUSTOM_PROPS,
28
+ ...WIDGET_SHAPE_EXTRA_PROPS,
29
+ ];
30
+ export class WidgetShape extends ImageShape {
31
+ static type = 'WidgetShape';
32
+ _isWidgetImage = false;
33
+ _widgetId = null;
34
+ _widgetName = null;
35
+ /** True when _widgetId no longer exists in the host widget list (deleted or moved). */
36
+ _widgetOrphaned = false;
37
+ /** Custom placeholder overlay: replace generic "Drop image" text with the widget name. */
38
+ _render(ctx) {
39
+ const hasImage = this._hasImage && this._imageElement;
40
+ if (hasImage) {
41
+ super._render(ctx);
42
+ return;
43
+ }
44
+ // Draw the underlying rect background (dashed border stays via stroke).
45
+ Rect.prototype._render.call(this, ctx);
46
+ this._renderWidgetPlaceholder(ctx);
47
+ }
48
+ _renderWidgetPlaceholder(ctx) {
49
+ const availableW = this.width || 100;
50
+ const availableH = this.height || 100;
51
+ ctx.save();
52
+ ctx.fillStyle = '#475569';
53
+ ctx.textAlign = 'center';
54
+ ctx.textBaseline = 'middle';
55
+ const isOrphaned = !!this._widgetOrphaned;
56
+ // Widget icon
57
+ const iconSize = Math.min(48, availableH * 0.28);
58
+ ctx.font = `${iconSize}px Arial`;
59
+ ctx.fillText(isOrphaned ? '⚠️' : '🧩', 0, -availableH * 0.18);
60
+ // Widget name
61
+ const name = isOrphaned
62
+ ? 'Unlinked widget'
63
+ : (this._widgetName || 'Widget');
64
+ const maxTextWidth = availableW - 24;
65
+ let fontSize = Math.max(11, Math.min(18, availableW * 0.06));
66
+ ctx.font = `600 ${fontSize}px system-ui, -apple-system, sans-serif`;
67
+ let displayName = name;
68
+ // Truncate with ellipsis if too wide
69
+ while (ctx.measureText(displayName).width > maxTextWidth && displayName.length > 4) {
70
+ displayName = displayName.slice(0, -1);
71
+ }
72
+ if (displayName.length < name.length)
73
+ displayName = displayName.slice(0, -1) + '…';
74
+ ctx.fillStyle = '#1e293b';
75
+ ctx.fillText(displayName, 0, availableH * 0.06);
76
+ // Subtitle
77
+ ctx.font = `400 ${Math.max(10, fontSize - 3)}px system-ui, -apple-system, sans-serif`;
78
+ ctx.fillStyle = '#64748b';
79
+ ctx.fillText(isOrphaned ? 'Remove from canvas' : 'Widget preview', 0, availableH * 0.06 + fontSize + 6);
80
+ ctx.restore();
81
+ }
82
+ // ── Serialisation ─────────────────────────────────────────────────────────
83
+ toObject(propertiesToInclude) {
84
+ const extras = WIDGET_SHAPE_CUSTOM_PROPS.concat(propertiesToInclude ?? []);
85
+ const base = super.toObject(extras);
86
+ return {
87
+ ...base,
88
+ type: WidgetShape.type,
89
+ _imageElement: undefined,
90
+ };
91
+ }
92
+ // ── Deserialisation ───────────────────────────────────────────────────────
93
+ static fromObject(object, options) {
94
+ return Rect.fromObject({ ...object, type: 'rect' }, options).then((rect) => {
95
+ Object.setPrototypeOf(rect, WidgetShape.prototype);
96
+ const shape = rect;
97
+ for (const prop of WIDGET_SHAPE_CUSTOM_PROPS) {
98
+ if (prop in object && shape[prop] === undefined || shape[prop] !== object[prop]) {
99
+ shape[prop] = object[prop];
100
+ }
101
+ }
102
+ if (shape._imageUrl) {
103
+ ImageShape.reloadImage(shape);
104
+ }
105
+ canvasLog('WidgetShape: fromObject — restored', shape._objectId || 'shape', 'hasImage:', shape._hasImage, 'isWidget:', shape._isWidgetImage, 'widgetId:', shape._widgetId);
106
+ return shape;
107
+ });
108
+ }
109
+ }
110
+ // Register so loadFromJSON can resolve type:'WidgetShape'
111
+ classRegistry.setClass(WidgetShape);
112
+ /**
113
+ * Upgrades an ImageShape to a WidgetShape by swapping the prototype chain.
114
+ * Called when user clicks "Convert to Widget".
115
+ */
116
+ export function upgradeToWidgetShape(obj) {
117
+ if (!obj)
118
+ return null;
119
+ if (obj instanceof WidgetShape)
120
+ return obj;
121
+ try {
122
+ Object.setPrototypeOf(obj, WidgetShape.prototype);
123
+ const shape = obj;
124
+ shape._isWidgetImage = true;
125
+ canvasLog('WidgetShape: upgraded ImageShape → WidgetShape for', shape._objectId || 'shape');
126
+ return shape;
127
+ }
128
+ catch (err) {
129
+ canvasWarn('WidgetShape: upgradeToWidgetShape failed', err);
130
+ return null;
131
+ }
132
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Canvas component shared types.
3
+ * Importable by both the canvas project and consuming apps (e.g. widget-creator).
4
+ */
5
+ /** Payload emitted when a frame is converted to a widget via onConvertToWidget callback. */
6
+ export interface WidgetConversionPayload {
7
+ frameName: string;
8
+ frameId: string;
9
+ screenshot: string;
10
+ canvasData: {
11
+ frame: Record<string, any>;
12
+ children: Record<string, any>[];
13
+ bounds: {
14
+ left: number;
15
+ top: number;
16
+ width: number;
17
+ height: number;
18
+ };
19
+ };
20
+ metadata: {
21
+ childCount: number;
22
+ shapeTypes: string[];
23
+ hasImages: boolean;
24
+ hasText: boolean;
25
+ };
26
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Canvas component shared types.
3
+ * Importable by both the canvas project and consuming apps (e.g. widget-creator).
4
+ */
5
+ export {};
@@ -0,0 +1,179 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Tooltip — reusable canvas tooltip component
4
+ *
5
+ * Usage:
6
+ * <Tooltip text="Draw D">
7
+ * <button>Draw</button>
8
+ * </Tooltip>
9
+ *
10
+ * The tooltip element is appended directly to <body> so it always appears
11
+ * above overflow containers and CSS transform ancestors (transformed
12
+ * ancestors would otherwise create a new containing-block for position:fixed,
13
+ * causing mis-positioning).
14
+ */
15
+ import { onDestroy } from 'svelte';
16
+
17
+ // Tooltip label
18
+ export let text: string = '';
19
+ // Preferred position: 'above' appears above the trigger, 'below' below it
20
+ export let position: 'above' | 'below' = 'above';
21
+ // Set to true to suppress the tooltip
22
+ export let disabled: boolean = false;
23
+ // Extra vertical offset (px) — positive moves tooltip further from trigger
24
+ export let offsetY: number = 0;
25
+
26
+ let triggerEl: HTMLElement | null = null;
27
+
28
+ // Lazily-created body-level elements
29
+ let tooltipBodyEl: HTMLElement | null = null;
30
+ let arrowBodyEl: HTMLElement | null = null;
31
+
32
+ function ensureBodyEl() {
33
+ if (tooltipBodyEl) return;
34
+
35
+ tooltipBodyEl = document.createElement('div');
36
+ arrowBodyEl = document.createElement('div');
37
+ document.body.appendChild(tooltipBodyEl);
38
+
39
+ // Base tooltip styles
40
+ Object.assign(tooltipBodyEl.style, {
41
+ position: 'fixed',
42
+ zIndex: '99999',
43
+ pointerEvents: 'none',
44
+ display: 'none',
45
+ background: '#1f2937',
46
+ color: '#f9fafb',
47
+ fontSize: '12px',
48
+ lineHeight: '1.4',
49
+ padding: '5px 10px',
50
+ borderRadius: '6px',
51
+ whiteSpace: 'normal',
52
+ maxWidth: '200px',
53
+ textAlign: 'center',
54
+ boxShadow: '0 2px 8px rgba(0,0,0,0.25)',
55
+ userSelect: 'none',
56
+ WebkitUserSelect: 'none',
57
+ });
58
+
59
+ // Arrow base styles — final border values set in show()
60
+ Object.assign(arrowBodyEl.style, {
61
+ position: 'absolute',
62
+ left: '50%',
63
+ transform: 'translateX(-50%)',
64
+ width: '0',
65
+ height: '0',
66
+ borderLeft: '5px solid transparent',
67
+ borderRight: '5px solid transparent',
68
+ });
69
+ }
70
+
71
+ const VIEWPORT_PADDING = 6;
72
+ const GAP = 8;
73
+ const ARROW_SIZE = 5;
74
+
75
+ function show() {
76
+ if (disabled || !text || !triggerEl) return;
77
+ ensureBodyEl();
78
+
79
+ // For display:contents wrappers, getBoundingClientRect() returns a zero rect;
80
+ // fall back to the first visual child in that case.
81
+ const triggerRect = triggerEl.getBoundingClientRect();
82
+ const anchor = (triggerRect.width === 0 && triggerRect.height === 0)
83
+ ? ((triggerEl.firstElementChild as HTMLElement) ?? triggerEl)
84
+ : triggerEl;
85
+ const rect = anchor.getBoundingClientRect();
86
+
87
+ tooltipBodyEl!.textContent = text;
88
+ tooltipBodyEl!.appendChild(arrowBodyEl!);
89
+
90
+ // Temporarily show off-screen to measure natural size
91
+ Object.assign(tooltipBodyEl!.style, {
92
+ display: 'block',
93
+ left: '-9999px',
94
+ top: '-9999px',
95
+ transform: 'none',
96
+ });
97
+ const tipW = tooltipBodyEl!.offsetWidth;
98
+ const tipH = tooltipBodyEl!.offsetHeight;
99
+
100
+ const vw = window.innerWidth;
101
+ const vh = window.innerHeight;
102
+ const cx = rect.left + rect.width / 2;
103
+
104
+ // Decide vertical placement: prefer the requested position, flip if no room
105
+ let placeAbove = position === 'above';
106
+ const spaceAbove = rect.top - GAP - offsetY;
107
+ const spaceBelow = vh - rect.bottom - GAP - offsetY;
108
+
109
+ if (placeAbove && tipH > spaceAbove && spaceBelow > spaceAbove) {
110
+ placeAbove = false;
111
+ } else if (!placeAbove && tipH > spaceBelow && spaceAbove > spaceBelow) {
112
+ placeAbove = true;
113
+ }
114
+
115
+ // Vertical position
116
+ let top: number;
117
+ if (placeAbove) {
118
+ top = rect.top - GAP - offsetY - tipH;
119
+ } else {
120
+ top = rect.bottom + GAP + offsetY;
121
+ }
122
+
123
+ // Horizontal position: center on trigger, then clamp to viewport
124
+ let left = cx - tipW / 2;
125
+ left = Math.max(VIEWPORT_PADDING, Math.min(left, vw - tipW - VIEWPORT_PADDING));
126
+
127
+ Object.assign(tooltipBodyEl!.style, {
128
+ left: `${left}px`,
129
+ top: `${top}px`,
130
+ transform: 'none',
131
+ });
132
+
133
+ // Position arrow relative to the trigger center
134
+ const arrowLeft = Math.max(ARROW_SIZE + 2, Math.min(cx - left, tipW - ARROW_SIZE - 2));
135
+ if (placeAbove) {
136
+ Object.assign(arrowBodyEl!.style, {
137
+ bottom: `${-ARROW_SIZE}px`,
138
+ top: 'auto',
139
+ left: `${arrowLeft}px`,
140
+ transform: 'translateX(-50%)',
141
+ borderTop: `${ARROW_SIZE}px solid #1f2937`,
142
+ borderBottom: 'none',
143
+ });
144
+ } else {
145
+ Object.assign(arrowBodyEl!.style, {
146
+ top: `${-ARROW_SIZE}px`,
147
+ bottom: 'auto',
148
+ left: `${arrowLeft}px`,
149
+ transform: 'translateX(-50%)',
150
+ borderBottom: `${ARROW_SIZE}px solid #1f2937`,
151
+ borderTop: 'none',
152
+ });
153
+ }
154
+ }
155
+
156
+ function hide() {
157
+ if (tooltipBodyEl) tooltipBodyEl.style.display = 'none';
158
+ }
159
+
160
+ // Clean up body element when component is destroyed
161
+ onDestroy(() => {
162
+ hide();
163
+ if (tooltipBodyEl) {
164
+ tooltipBodyEl.remove();
165
+ tooltipBodyEl = null;
166
+ }
167
+ });
168
+ </script>
169
+
170
+ <!-- display:contents makes this wrapper invisible to layout while still receiving events -->
171
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
172
+ <div
173
+ bind:this={triggerEl}
174
+ onmouseenter={show}
175
+ onmouseleave={hide}
176
+ style="display:contents"
177
+ >
178
+ <slot />
179
+ </div>
@@ -0,0 +1,21 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ text?: string;
5
+ position?: "above" | "below";
6
+ disabled?: boolean;
7
+ offsetY?: number;
8
+ };
9
+ events: {
10
+ [evt: string]: CustomEvent<any>;
11
+ };
12
+ slots: {
13
+ default: {};
14
+ };
15
+ };
16
+ export type TooltipProps = typeof __propDef.props;
17
+ export type TooltipEvents = typeof __propDef.events;
18
+ export type TooltipSlots = typeof __propDef.slots;
19
+ export default class Tooltip extends SvelteComponentTyped<TooltipProps, TooltipEvents, TooltipSlots> {
20
+ }
21
+ export {};
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Lightweight components barrel — general-purpose, safe for static import.
3
+ *
4
+ * Import: import { Tooltip, ColorIC } from '@widgetic/canvas/components';
5
+ */
6
+ export { default as Tooltip } from './Tooltip.svelte';
7
+ export { default as ColorIC } from './input-controls/ColorIC.svelte';
8
+ export { default as FontSelectorIC } from './input-controls/FontSelectorIC.svelte';
9
+ export { default as SliderUnitIC } from './input-controls/SliderUnitIC.svelte';
10
+ export { default as TextAlignIC } from './input-controls/TextAlignIC.svelte';
11
+ export { default as TextStyleIC } from './input-controls/TextStyleIC.svelte';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Lightweight components barrel — general-purpose, safe for static import.
3
+ *
4
+ * Import: import { Tooltip, ColorIC } from '@widgetic/canvas/components';
5
+ */
6
+ // General Components
7
+ export { default as Tooltip } from './Tooltip.svelte';
8
+ // Input Controllers
9
+ export { default as ColorIC } from './input-controls/ColorIC.svelte';
10
+ export { default as FontSelectorIC } from './input-controls/FontSelectorIC.svelte';
11
+ export { default as SliderUnitIC } from './input-controls/SliderUnitIC.svelte';
12
+ export { default as TextAlignIC } from './input-controls/TextAlignIC.svelte';
13
+ export { default as TextStyleIC } from './input-controls/TextStyleIC.svelte';