@widgetic/editor 4.0.2 → 4.0.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.
- package/dist/components/MockWidget.svelte +1 -1
- package/dist/components/WidgetPreview.svelte +347 -97
- package/dist/components/WidgetPreview.svelte.d.ts +5 -0
- package/dist/components/editors/EmbedEditor.svelte +1 -1
- package/dist/components/editors/PropsEditor.svelte +173 -148
- package/dist/components/ic/BrowserInputController.svelte +14 -14
- package/dist/components/ic/CheckboxInputController.svelte +1 -1
- package/dist/components/ic/ColorPickerInputController.svelte +37 -37
- package/dist/components/ic/DatePickerInputController.svelte +6 -6
- package/dist/components/ic/DropdownInputController.svelte +1 -1
- package/dist/components/ic/FontInputController.svelte +136 -63
- package/dist/components/ic/FontInputController.svelte.d.ts +3 -1
- package/dist/components/ic/NumberInputController.svelte +37 -37
- package/dist/components/ic/RadioGroupInputController.svelte +1 -1
- package/dist/components/ic/SliderRangeInputController.svelte +1 -1
- package/dist/components/ic/SwitchInputController.svelte +1 -1
- package/dist/components/ic/TextAreaInputController.svelte +23 -23
- package/dist/components/ic/TextAreaPreviewInputController.svelte +2 -2
- package/dist/components/ic/VectorInputController.svelte +34 -34
- package/dist/components/ic/shared/CategoryTabs.svelte +1 -0
- package/dist/components/ic/shared/RecentItemsSection.svelte +21 -6
- package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +1 -0
- package/dist/stores/userSession.d.ts +1 -1
- package/dist/stores/userSession.js +17 -2
- package/dist/utils/FontManager.d.ts +1 -1
- package/dist/utils/FontManager.js +75 -102
- package/dist/utils/VectorManager.d.ts +3 -1
- package/dist/utils/VectorManager.js +30 -70
- package/dist/utils/colorTransparency.js +3 -3
- package/dist/utils/fontUtils.js +1 -1
- package/dist/utils/passiveEvents.d.ts +5 -0
- package/dist/utils/passiveEvents.js +9 -18
- package/dist/utils/recentColors.js +15 -15
- package/dist/utils/vectorUtils.js +1 -1
- package/package.json +1 -1
|
@@ -59,6 +59,12 @@
|
|
|
59
59
|
export let mobileViewportWidth: number = 390;
|
|
60
60
|
export let mobileViewportHeight: number = 667;
|
|
61
61
|
export let enableResize: boolean = false;
|
|
62
|
+
/** Drag only via the preview chrome header (keeps iframe interactive). */
|
|
63
|
+
export let enableDrag: boolean = false;
|
|
64
|
+
/** W×H number inputs in the chrome header, live-synced with resize. */
|
|
65
|
+
export let showDimensionInputs: boolean = false;
|
|
66
|
+
/** Initial / reset placement inside the stage. */
|
|
67
|
+
export let defaultStageAlign: 'center' | 'right-half' = 'center';
|
|
62
68
|
/** Observe stage size and fill it (desktop modal) */
|
|
63
69
|
export let autoFitStage: boolean = false;
|
|
64
70
|
/** No padding/border on preview-content-container — matches widget-creator Edit panel */
|
|
@@ -87,32 +93,163 @@
|
|
|
87
93
|
const MIN_WIDTH = 120;
|
|
88
94
|
const MIN_HEIGHT = 80;
|
|
89
95
|
const HANDLE_MARGIN = 6;
|
|
90
|
-
/** Inset padding so resize handles
|
|
91
|
-
const RESIZE_PAD_X =
|
|
92
|
-
const RESIZE_PAD_TOP =
|
|
93
|
-
const RESIZE_PAD_BOTTOM =
|
|
96
|
+
/** Inset padding so resize handles stay inside bounds */
|
|
97
|
+
const RESIZE_PAD_X = 12;
|
|
98
|
+
const RESIZE_PAD_TOP = 12;
|
|
99
|
+
const RESIZE_PAD_BOTTOM = 12;
|
|
100
|
+
const PREVIEW_CHROME_HEADER_H = 36;
|
|
101
|
+
const PREVIEW_STROKE = '#3b82f6';
|
|
102
|
+
const PREVIEW_STROKE_ALT = '#f97316';
|
|
94
103
|
let isResizing = false;
|
|
104
|
+
let isDraggingPreview = false;
|
|
105
|
+
let isSymmetricalResize = false;
|
|
106
|
+
let previewShellLeft = 0;
|
|
107
|
+
let previewShellTop = 0;
|
|
108
|
+
let previewPositionReady = false;
|
|
109
|
+
let widthInputValue = '';
|
|
110
|
+
let heightInputValue = '';
|
|
95
111
|
let contentContainerEl: HTMLElement | null = null;
|
|
96
112
|
let resizeStageEl: HTMLElement | null = null;
|
|
97
113
|
let stageResizeObserver: ResizeObserver | null = null;
|
|
98
114
|
|
|
115
|
+
$: showPreviewChrome = enableDrag || showDimensionInputs;
|
|
116
|
+
$: chromeHeaderHeight = showPreviewChrome ? PREVIEW_CHROME_HEADER_H : 0;
|
|
117
|
+
$: if (resizeWidth != null) widthInputValue = String(Math.round(resizeWidth));
|
|
118
|
+
$: if (resizeHeight != null) heightInputValue = String(Math.round(resizeHeight));
|
|
119
|
+
$: previewStrokeColor = isSymmetricalResize ? PREVIEW_STROKE_ALT : PREVIEW_STROKE;
|
|
120
|
+
|
|
99
121
|
export function getResizeLimits(): { maxWidth: number; maxHeight: number } {
|
|
100
122
|
const stageEl = resizeStageEl ?? contentContainerEl;
|
|
123
|
+
const chromeH = showPreviewChrome ? PREVIEW_CHROME_HEADER_H : 0;
|
|
101
124
|
if (stageEl) {
|
|
102
125
|
return {
|
|
103
126
|
maxWidth: Math.max(MIN_WIDTH, stageEl.clientWidth),
|
|
104
|
-
maxHeight: Math.max(MIN_HEIGHT, stageEl.clientHeight),
|
|
127
|
+
maxHeight: Math.max(MIN_HEIGHT, stageEl.clientHeight - chromeH),
|
|
105
128
|
};
|
|
106
129
|
}
|
|
107
130
|
if (hasFixedBounds && boundsWidth != null && boundsHeight != null) {
|
|
108
131
|
return {
|
|
109
132
|
maxWidth: Math.max(MIN_WIDTH, boundsWidth - RESIZE_PAD_X * 2),
|
|
110
|
-
maxHeight: Math.max(MIN_HEIGHT, boundsHeight - RESIZE_PAD_TOP - RESIZE_PAD_BOTTOM),
|
|
133
|
+
maxHeight: Math.max(MIN_HEIGHT, boundsHeight - RESIZE_PAD_TOP - RESIZE_PAD_BOTTOM - chromeH),
|
|
111
134
|
};
|
|
112
135
|
}
|
|
113
136
|
return { maxWidth: 720, maxHeight: 420 };
|
|
114
137
|
}
|
|
115
138
|
|
|
139
|
+
function clampDimension(value: number, min: number, max: number): number {
|
|
140
|
+
return Math.max(min, Math.min(max, value));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function computeDefaultShellPosition(
|
|
144
|
+
stageWidth: number,
|
|
145
|
+
stageHeight: number,
|
|
146
|
+
shellWidth: number,
|
|
147
|
+
shellHeight: number
|
|
148
|
+
): { left: number; top: number } {
|
|
149
|
+
const maxLeft = Math.max(RESIZE_PAD_X, stageWidth - shellWidth - RESIZE_PAD_X);
|
|
150
|
+
const maxTop = Math.max(RESIZE_PAD_TOP, stageHeight - shellHeight - RESIZE_PAD_BOTTOM);
|
|
151
|
+
if (defaultStageAlign === 'right-half') {
|
|
152
|
+
const rightHalfCenterX = stageWidth * 0.75;
|
|
153
|
+
const left = clampDimension(rightHalfCenterX - shellWidth / 2, RESIZE_PAD_X, maxLeft);
|
|
154
|
+
const top = clampDimension((stageHeight - shellHeight) / 2, RESIZE_PAD_TOP, maxTop);
|
|
155
|
+
return { left, top };
|
|
156
|
+
}
|
|
157
|
+
const left = clampDimension((stageWidth - shellWidth) / 2, RESIZE_PAD_X, maxLeft);
|
|
158
|
+
const top = clampDimension((stageHeight - shellHeight) / 2, RESIZE_PAD_TOP, maxTop);
|
|
159
|
+
return { left, top };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Dock / reset preview shell to defaultStageAlign. */
|
|
163
|
+
export function resetPreviewPosition(): void {
|
|
164
|
+
const stageEl = resizeStageEl ?? contentContainerEl;
|
|
165
|
+
if (!stageEl || resizeWidth == null || resizeHeight == null) return;
|
|
166
|
+
if (stageEl.clientWidth < 40 || stageEl.clientHeight < 40) {
|
|
167
|
+
requestAnimationFrame(() => resetPreviewPosition());
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const shellWidth = resizeWidth;
|
|
171
|
+
const shellHeight = resizeHeight + chromeHeaderHeight;
|
|
172
|
+
const next = computeDefaultShellPosition(
|
|
173
|
+
stageEl.clientWidth,
|
|
174
|
+
stageEl.clientHeight,
|
|
175
|
+
shellWidth,
|
|
176
|
+
shellHeight
|
|
177
|
+
);
|
|
178
|
+
previewShellLeft = next.left;
|
|
179
|
+
previewShellTop = next.top;
|
|
180
|
+
previewPositionReady = true;
|
|
181
|
+
console.log('[WidgetPreview] Reset preview position', {
|
|
182
|
+
align: defaultStageAlign,
|
|
183
|
+
left: previewShellLeft,
|
|
184
|
+
top: previewShellTop,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function ensurePreviewPosition(): void {
|
|
189
|
+
if (previewPositionReady) return;
|
|
190
|
+
resetPreviewPosition();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function applyDimensionFromInput(dimension: 'width' | 'height', raw: string) {
|
|
194
|
+
const { maxWidth, maxHeight } = getResizeLimits();
|
|
195
|
+
const parsed = Number.parseInt(raw, 10);
|
|
196
|
+
if (!Number.isFinite(parsed)) return;
|
|
197
|
+
if (dimension === 'width') {
|
|
198
|
+
resizeWidth = clampDimension(parsed, MIN_WIDTH, maxWidth);
|
|
199
|
+
widthInputValue = String(Math.round(resizeWidth));
|
|
200
|
+
} else {
|
|
201
|
+
resizeHeight = clampDimension(parsed, MIN_HEIGHT, maxHeight);
|
|
202
|
+
heightInputValue = String(Math.round(resizeHeight));
|
|
203
|
+
}
|
|
204
|
+
if (resizeWidth != null && resizeHeight != null) {
|
|
205
|
+
dispatch('resize', { width: resizeWidth, height: resizeHeight });
|
|
206
|
+
}
|
|
207
|
+
console.log('[WidgetPreview] Dimension input', { dimension, value: parsed });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function startPreviewDrag(event: PointerEvent) {
|
|
211
|
+
if (!enableDrag || isResizing) return;
|
|
212
|
+
const target = event.target as HTMLElement | null;
|
|
213
|
+
if (target?.closest('input, button, a, label')) return;
|
|
214
|
+
event.preventDefault();
|
|
215
|
+
event.stopPropagation();
|
|
216
|
+
isDraggingPreview = true;
|
|
217
|
+
const startX = event.clientX;
|
|
218
|
+
const startY = event.clientY;
|
|
219
|
+
const startLeft = previewShellLeft;
|
|
220
|
+
const startTop = previewShellTop;
|
|
221
|
+
const stageEl = resizeStageEl ?? contentContainerEl;
|
|
222
|
+
const shellWidth = resizeWidth ?? 0;
|
|
223
|
+
const shellHeight = (resizeHeight ?? 0) + chromeHeaderHeight;
|
|
224
|
+
|
|
225
|
+
function onPointerMove(e: PointerEvent) {
|
|
226
|
+
if (!stageEl) return;
|
|
227
|
+
const maxLeft = Math.max(RESIZE_PAD_X, stageEl.clientWidth - shellWidth - RESIZE_PAD_X);
|
|
228
|
+
const maxTop = Math.max(RESIZE_PAD_TOP, stageEl.clientHeight - shellHeight - RESIZE_PAD_BOTTOM);
|
|
229
|
+
previewShellLeft = clampDimension(startLeft + (e.clientX - startX), RESIZE_PAD_X, maxLeft);
|
|
230
|
+
previewShellTop = clampDimension(startTop + (e.clientY - startY), RESIZE_PAD_TOP, maxTop);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function endDrag() {
|
|
234
|
+
isDraggingPreview = false;
|
|
235
|
+
document.removeEventListener('pointermove', onPointerMove);
|
|
236
|
+
document.removeEventListener('pointerup', endDrag);
|
|
237
|
+
document.removeEventListener('pointercancel', endDrag);
|
|
238
|
+
document.body.style.removeProperty('user-select');
|
|
239
|
+
document.body.style.removeProperty('cursor');
|
|
240
|
+
console.log('[WidgetPreview] Preview drag end', {
|
|
241
|
+
left: previewShellLeft,
|
|
242
|
+
top: previewShellTop,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
document.body.style.userSelect = 'none';
|
|
247
|
+
document.body.style.cursor = 'move';
|
|
248
|
+
document.addEventListener('pointermove', onPointerMove);
|
|
249
|
+
document.addEventListener('pointerup', endDrag);
|
|
250
|
+
document.addEventListener('pointercancel', endDrag);
|
|
251
|
+
}
|
|
252
|
+
|
|
116
253
|
/** Firefox/Chrome skip painting a cross-origin iframe that started at opacity 0 or inside a CSS filter until a reflow (opening DevTools). */
|
|
117
254
|
let iframePaintNudgeGeneration = 0;
|
|
118
255
|
export function nudgeIframePaint(): void {
|
|
@@ -205,7 +342,7 @@
|
|
|
205
342
|
const artifactPath = jsPath.match(/\/widgets\/[^/]+\/v\d+\/.+$/i);
|
|
206
343
|
if (artifactPath) {
|
|
207
344
|
const proxyUrl = `${apiBaseUrl}${artifactPath[0]}`;
|
|
208
|
-
console.log('[WidgetPreview] Using API proxy URL (R2):', proxyUrl);
|
|
345
|
+
// console.log('[WidgetPreview] Using API proxy URL (R2):', proxyUrl);
|
|
209
346
|
return proxyUrl;
|
|
210
347
|
}
|
|
211
348
|
}
|
|
@@ -215,11 +352,11 @@
|
|
|
215
352
|
|
|
216
353
|
if (widgetBuilderUrl && w.id) {
|
|
217
354
|
const previewUrl = `${widgetBuilderUrl.replace(/\/+$/, '')}/preview/${w.id}`;
|
|
218
|
-
console.log('[WidgetPreview] Using Worker preview URL (unpublished):', previewUrl);
|
|
355
|
+
// console.log('[WidgetPreview] Using Worker preview URL (unpublished):', previewUrl);
|
|
219
356
|
return previewUrl;
|
|
220
357
|
}
|
|
221
358
|
|
|
222
|
-
console.log('[WidgetPreview] Widget not published and no Worker URL:', w.name, w.id);
|
|
359
|
+
// console.log('[WidgetPreview] Widget not published and no Worker URL:', w.name, w.id);
|
|
223
360
|
return null;
|
|
224
361
|
}
|
|
225
362
|
|
|
@@ -228,32 +365,6 @@
|
|
|
228
365
|
return `${widgetBuilderUrl.replace(/\/+$/, '')}/preview/${w.id}`;
|
|
229
366
|
}
|
|
230
367
|
|
|
231
|
-
async function resolveWidgetPreviewUrl(w: any, primaryUrl: string): Promise<string> {
|
|
232
|
-
try {
|
|
233
|
-
const res = await fetch(primaryUrl, { method: 'GET', mode: 'cors' });
|
|
234
|
-
const contentType = res.headers.get('content-type') || '';
|
|
235
|
-
const looksLikeErrorPayload =
|
|
236
|
-
!res.ok || (contentType.includes('application/json') && !contentType.includes('html'));
|
|
237
|
-
if (looksLikeErrorPayload) {
|
|
238
|
-
const fallbackUrl = tryFallbackUrl(w);
|
|
239
|
-
if (fallbackUrl && !triedFallback) {
|
|
240
|
-
triedFallback = true;
|
|
241
|
-
console.log('[WidgetPreview] Primary URL failed, trying Worker fallback:', fallbackUrl);
|
|
242
|
-
return fallbackUrl;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
} catch (err) {
|
|
246
|
-
const fallbackUrl = tryFallbackUrl(w);
|
|
247
|
-
if (fallbackUrl && !triedFallback) {
|
|
248
|
-
triedFallback = true;
|
|
249
|
-
console.log('[WidgetPreview] Primary URL unreachable, trying Worker fallback:', fallbackUrl);
|
|
250
|
-
return fallbackUrl;
|
|
251
|
-
}
|
|
252
|
-
console.warn('[WidgetPreview] URL probe failed:', primaryUrl, err);
|
|
253
|
-
}
|
|
254
|
-
return primaryUrl;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
368
|
function beginWidgetLoad(w: any, url: string | null) {
|
|
258
369
|
pendingWidget = w;
|
|
259
370
|
triedFallback = false;
|
|
@@ -262,20 +373,16 @@
|
|
|
262
373
|
widgetReady = false;
|
|
263
374
|
errorMessage = '';
|
|
264
375
|
pendingMessages = [];
|
|
376
|
+
isResolvingUrl = false;
|
|
265
377
|
|
|
266
378
|
if (!url) {
|
|
267
379
|
internalIframeSrc = null;
|
|
268
|
-
isResolvingUrl = false;
|
|
269
380
|
return;
|
|
270
381
|
}
|
|
271
382
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
void
|
|
275
|
-
if (pendingWidget !== w) return;
|
|
276
|
-
isResolvingUrl = false;
|
|
277
|
-
internalIframeSrc = resolvedUrl;
|
|
278
|
-
await tick();
|
|
383
|
+
// console.log('[WidgetPreview] Setting widget URL:', url);
|
|
384
|
+
internalIframeSrc = url;
|
|
385
|
+
void tick().then(() => {
|
|
279
386
|
requestAnimationFrame(() => {
|
|
280
387
|
ensureIframeLoadDetected();
|
|
281
388
|
});
|
|
@@ -299,7 +406,7 @@
|
|
|
299
406
|
|
|
300
407
|
window.setTimeout(() => {
|
|
301
408
|
if (mode === 'standalone' && !iframeLoaded && !iframeError && iframeEl && effectiveSrc) {
|
|
302
|
-
console.log('[WidgetPreview] Fallback: marking iframe loaded after ping timeout');
|
|
409
|
+
// console.log('[WidgetPreview] Fallback: marking iframe loaded after ping timeout');
|
|
303
410
|
void handleIframeLoad();
|
|
304
411
|
}
|
|
305
412
|
}, 400);
|
|
@@ -353,12 +460,12 @@
|
|
|
353
460
|
contentItems
|
|
354
461
|
};
|
|
355
462
|
iframeEl.contentWindow.postMessage(message, '*');
|
|
356
|
-
console.log('[WidgetPreview] Sent property update to widget iframe', {
|
|
357
|
-
designKeys: design ? Object.keys(design) : [],
|
|
358
|
-
genericContentKeys: Object.keys(genericContent),
|
|
359
|
-
contentItemsCount: contentItems?.length,
|
|
360
|
-
iframeSrc: effectiveSrc?.substring(0, 80)
|
|
361
|
-
});
|
|
463
|
+
// console.log('[WidgetPreview] Sent property update to widget iframe', {
|
|
464
|
+
// designKeys: design ? Object.keys(design) : [],
|
|
465
|
+
// genericContentKeys: Object.keys(genericContent),
|
|
466
|
+
// contentItemsCount: contentItems?.length,
|
|
467
|
+
// iframeSrc: effectiveSrc?.substring(0, 80)
|
|
468
|
+
// });
|
|
362
469
|
} catch (err) {
|
|
363
470
|
console.warn('[WidgetPreview] Failed to send postMessage:', err);
|
|
364
471
|
}
|
|
@@ -406,7 +513,7 @@
|
|
|
406
513
|
iframeLoaded = true;
|
|
407
514
|
iframeError = false;
|
|
408
515
|
widgetReady = true;
|
|
409
|
-
console.log('[WidgetPreview] iframe onload:', effectiveSrc);
|
|
516
|
+
// console.log('[WidgetPreview] iframe onload:', effectiveSrc);
|
|
410
517
|
|
|
411
518
|
if (iframeEl?.contentWindow) {
|
|
412
519
|
iframeEl.contentWindow.postMessage({ type: 'widgetic:ping' }, '*');
|
|
@@ -426,6 +533,15 @@
|
|
|
426
533
|
dispatch('iframeError', event || new Event('error'));
|
|
427
534
|
return;
|
|
428
535
|
}
|
|
536
|
+
if (!triedFallback && pendingWidget) {
|
|
537
|
+
const fallbackUrl = tryFallbackUrl(pendingWidget);
|
|
538
|
+
if (fallbackUrl && fallbackUrl !== internalIframeSrc) {
|
|
539
|
+
triedFallback = true;
|
|
540
|
+
// console.log('[WidgetPreview] Primary iframe load failed, trying Worker fallback:', fallbackUrl);
|
|
541
|
+
internalIframeSrc = fallbackUrl;
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
429
545
|
iframeError = true;
|
|
430
546
|
iframeLoaded = false;
|
|
431
547
|
widgetReady = false;
|
|
@@ -441,7 +557,7 @@
|
|
|
441
557
|
iframeLoaded = true;
|
|
442
558
|
iframeError = false;
|
|
443
559
|
widgetReady = true;
|
|
444
|
-
console.log('[WidgetPreview] Widget confirmed ready via postMessage');
|
|
560
|
+
// console.log('[WidgetPreview] Widget confirmed ready via postMessage');
|
|
445
561
|
sendPropertiesToWidget(designValues, contentValues);
|
|
446
562
|
flushPendingMessages();
|
|
447
563
|
}
|
|
@@ -496,22 +612,64 @@
|
|
|
496
612
|
event.preventDefault();
|
|
497
613
|
event.stopPropagation();
|
|
498
614
|
isResizing = true;
|
|
615
|
+
isSymmetricalResize = event.altKey;
|
|
499
616
|
const startX = event.clientX;
|
|
500
617
|
const startY = event.clientY;
|
|
501
|
-
|
|
618
|
+
const startLeft = previewShellLeft;
|
|
619
|
+
const startTop = previewShellTop;
|
|
502
620
|
const { maxWidth, maxHeight } = getResizeLimits();
|
|
503
621
|
const startWidth = resizeWidth ?? maxWidth;
|
|
504
622
|
const startHeight = resizeHeight ?? maxHeight;
|
|
505
623
|
|
|
506
624
|
function onPointerMove(e: PointerEvent) {
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
625
|
+
const useAlt = e.altKey;
|
|
626
|
+
isSymmetricalResize = useAlt;
|
|
627
|
+
const dx = e.clientX - startX;
|
|
628
|
+
const dy = e.clientY - startY;
|
|
629
|
+
|
|
630
|
+
if (useAlt) {
|
|
631
|
+
const deltaW = signX !== 0 ? dx * signX * 2 : 0;
|
|
632
|
+
const deltaH = signY !== 0 ? dy * signY * 2 : 0;
|
|
633
|
+
const nextW = clampDimension(startWidth + deltaW, MIN_WIDTH, maxWidth);
|
|
634
|
+
const nextH = clampDimension(startHeight + deltaH, MIN_HEIGHT, maxHeight);
|
|
635
|
+
resizeWidth = nextW;
|
|
636
|
+
resizeHeight = nextH;
|
|
637
|
+
previewShellLeft = startLeft + (startWidth - nextW) / 2;
|
|
638
|
+
previewShellTop = startTop + (startHeight - nextH) / 2;
|
|
639
|
+
} else {
|
|
640
|
+
let nextW = startWidth;
|
|
641
|
+
let nextH = startHeight;
|
|
642
|
+
let nextLeft = startLeft;
|
|
643
|
+
let nextTop = startTop;
|
|
644
|
+
|
|
645
|
+
if (signX === -1) {
|
|
646
|
+
nextW = clampDimension(startWidth - dx, MIN_WIDTH, maxWidth);
|
|
647
|
+
nextLeft = startLeft + (startWidth - nextW);
|
|
648
|
+
} else if (signX === 1) {
|
|
649
|
+
nextW = clampDimension(startWidth + dx, MIN_WIDTH, maxWidth);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (signY === -1) {
|
|
653
|
+
nextH = clampDimension(startHeight - dy, MIN_HEIGHT, maxHeight);
|
|
654
|
+
nextTop = startTop + (startHeight - nextH);
|
|
655
|
+
} else if (signY === 1) {
|
|
656
|
+
nextH = clampDimension(startHeight + dy, MIN_HEIGHT, maxHeight);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
resizeWidth = nextW;
|
|
660
|
+
resizeHeight = nextH;
|
|
661
|
+
previewShellLeft = nextLeft;
|
|
662
|
+
previewShellTop = nextTop;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (resizeWidth != null && resizeHeight != null) {
|
|
666
|
+
dispatch('resize', { width: resizeWidth, height: resizeHeight });
|
|
667
|
+
}
|
|
511
668
|
}
|
|
512
669
|
|
|
513
670
|
function endResize() {
|
|
514
671
|
isResizing = false;
|
|
672
|
+
isSymmetricalResize = false;
|
|
515
673
|
document.removeEventListener('pointermove', onPointerMove);
|
|
516
674
|
document.removeEventListener('pointerup', endResize);
|
|
517
675
|
document.removeEventListener('pointercancel', endResize);
|
|
@@ -523,7 +681,14 @@
|
|
|
523
681
|
}
|
|
524
682
|
|
|
525
683
|
document.body.style.userSelect = 'none';
|
|
526
|
-
document.body.style.cursor =
|
|
684
|
+
document.body.style.cursor =
|
|
685
|
+
signX !== 0 && signY !== 0
|
|
686
|
+
? signX * signY > 0
|
|
687
|
+
? 'nwse-resize'
|
|
688
|
+
: 'nesw-resize'
|
|
689
|
+
: signX !== 0
|
|
690
|
+
? 'ew-resize'
|
|
691
|
+
: 'ns-resize';
|
|
527
692
|
document.addEventListener('pointermove', onPointerMove);
|
|
528
693
|
document.addEventListener('pointerup', endResize);
|
|
529
694
|
document.addEventListener('pointercancel', endResize);
|
|
@@ -531,6 +696,7 @@
|
|
|
531
696
|
|
|
532
697
|
$: if (enableResize && contentContainerEl && (resizeWidth != null || resizeHeight != null)) {
|
|
533
698
|
clampResizeToLimits();
|
|
699
|
+
ensurePreviewPosition();
|
|
534
700
|
}
|
|
535
701
|
|
|
536
702
|
$: if (autoFitStage && resizeStageEl) {
|
|
@@ -544,6 +710,18 @@
|
|
|
544
710
|
|
|
545
711
|
onMount(() => {
|
|
546
712
|
window.addEventListener('message', handleMessage);
|
|
713
|
+
const onAltDown = (e: KeyboardEvent) => {
|
|
714
|
+
if (e.key === 'Alt') isSymmetricalResize = true;
|
|
715
|
+
};
|
|
716
|
+
const onAltUp = (e: KeyboardEvent) => {
|
|
717
|
+
if (e.key === 'Alt' && !isResizing) isSymmetricalResize = false;
|
|
718
|
+
};
|
|
719
|
+
window.addEventListener('keydown', onAltDown);
|
|
720
|
+
window.addEventListener('keyup', onAltUp);
|
|
721
|
+
return () => {
|
|
722
|
+
window.removeEventListener('keydown', onAltDown);
|
|
723
|
+
window.removeEventListener('keyup', onAltUp);
|
|
724
|
+
};
|
|
547
725
|
});
|
|
548
726
|
|
|
549
727
|
onDestroy(() => {
|
|
@@ -657,12 +835,66 @@
|
|
|
657
835
|
{:else if effectiveSrc || (mode === 'standalone' && isResolvingUrl)}
|
|
658
836
|
<div
|
|
659
837
|
bind:this={resizeStageEl}
|
|
660
|
-
class="{enableResize ? 'preview-resize-stage-ct absolute inset-0 overflow-hidden
|
|
838
|
+
class="{enableResize ? 'preview-resize-stage-ct absolute inset-0 overflow-hidden' : 'preview-resize-stage-ct relative flex items-center justify-center w-full h-full'}"
|
|
661
839
|
>
|
|
662
840
|
<div
|
|
663
|
-
class="preview-iframe-
|
|
664
|
-
|
|
841
|
+
class="preview-iframe-shell relative flex flex-col {enableResize ? 'preview-iframe-shell-positioned' : ''}"
|
|
842
|
+
class:preview-iframe-shell-symmetrical={isSymmetricalResize}
|
|
843
|
+
style="{enableResize
|
|
844
|
+
? `position: absolute; left: ${previewShellLeft}px; top: ${previewShellTop}px; width: ${resizeWidth ? resizeWidth + 'px' : '100%'};`
|
|
845
|
+
: `width: ${resizeWidth ? resizeWidth + 'px' : '100%'}; height: ${resizeHeight ? resizeHeight + 'px' : '100%'};`} transition: {isResizing || isDraggingPreview ? 'none' : 'width 0.2s, height 0.2s, left 0.15s, top 0.15s'};"
|
|
665
846
|
>
|
|
847
|
+
{#if showPreviewChrome}
|
|
848
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
849
|
+
<div
|
|
850
|
+
class="preview-chrome-header flex items-center gap-2 h-9 px-2 border border-b-0 rounded-t-md bg-white select-none {enableDrag ? 'cursor-move' : ''}"
|
|
851
|
+
style="border-color: {previewStrokeColor};"
|
|
852
|
+
onpointerdown={startPreviewDrag}
|
|
853
|
+
>
|
|
854
|
+
{#if enableDrag}
|
|
855
|
+
<span class="preview-chrome-drag-grip shrink-0 text-gray-400 text-xs leading-none" title="Drag preview" aria-hidden="true">⠿</span>
|
|
856
|
+
{/if}
|
|
857
|
+
<span class="preview-chrome-title text-[11px] font-semibold uppercase tracking-wide text-gray-500 shrink-0">Preview</span>
|
|
858
|
+
{#if showDimensionInputs}
|
|
859
|
+
<div class="preview-chrome-dimensions ml-auto flex items-center gap-1.5">
|
|
860
|
+
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
861
|
+
<span>W</span>
|
|
862
|
+
<input
|
|
863
|
+
type="number"
|
|
864
|
+
min={MIN_WIDTH}
|
|
865
|
+
class="preview-dim-input w-14 h-6 rounded border border-gray-300 bg-white px-1 text-[11px] text-gray-800 focus:outline-none focus:border-blue-400 focus-visible:outline-none"
|
|
866
|
+
value={widthInputValue}
|
|
867
|
+
oninput={(e) => {
|
|
868
|
+
widthInputValue = e.currentTarget.value;
|
|
869
|
+
applyDimensionFromInput('width', e.currentTarget.value);
|
|
870
|
+
}}
|
|
871
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
872
|
+
/>
|
|
873
|
+
</label>
|
|
874
|
+
<span class="text-[10px] text-gray-400">×</span>
|
|
875
|
+
<label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
|
|
876
|
+
<span>H</span>
|
|
877
|
+
<input
|
|
878
|
+
type="number"
|
|
879
|
+
min={MIN_HEIGHT}
|
|
880
|
+
class="preview-dim-input w-14 h-6 rounded border border-gray-300 bg-white px-1 text-[11px] text-gray-800 focus:outline-none focus:border-blue-400 focus-visible:outline-none"
|
|
881
|
+
value={heightInputValue}
|
|
882
|
+
oninput={(e) => {
|
|
883
|
+
heightInputValue = e.currentTarget.value;
|
|
884
|
+
applyDimensionFromInput('height', e.currentTarget.value);
|
|
885
|
+
}}
|
|
886
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
887
|
+
/>
|
|
888
|
+
</label>
|
|
889
|
+
</div>
|
|
890
|
+
{/if}
|
|
891
|
+
</div>
|
|
892
|
+
{/if}
|
|
893
|
+
|
|
894
|
+
<div
|
|
895
|
+
class="preview-iframe-wrapper relative"
|
|
896
|
+
style="width: 100%; height: {resizeHeight ? resizeHeight + 'px' : '100%'};"
|
|
897
|
+
>
|
|
666
898
|
{#if showResizeHandles}
|
|
667
899
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
668
900
|
<div class="preview-resize-handle preview-resize-handle-tl" onpointerdown={(e) => startResize(e, -1, -1)}></div>
|
|
@@ -672,9 +904,17 @@
|
|
|
672
904
|
<div class="preview-resize-handle preview-resize-handle-bl" onpointerdown={(e) => startResize(e, -1, 1)}></div>
|
|
673
905
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
674
906
|
<div class="preview-resize-handle preview-resize-handle-br" onpointerdown={(e) => startResize(e, 1, 1)}></div>
|
|
907
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
908
|
+
<div class="preview-resize-handle preview-resize-handle-t" onpointerdown={(e) => startResize(e, 0, -1)}></div>
|
|
909
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
910
|
+
<div class="preview-resize-handle preview-resize-handle-r" onpointerdown={(e) => startResize(e, 1, 0)}></div>
|
|
911
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
912
|
+
<div class="preview-resize-handle preview-resize-handle-b" onpointerdown={(e) => startResize(e, 0, 1)}></div>
|
|
913
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
914
|
+
<div class="preview-resize-handle preview-resize-handle-l" onpointerdown={(e) => startResize(e, -1, 0)}></div>
|
|
675
915
|
{/if}
|
|
676
916
|
|
|
677
|
-
{#if resizeWidth || resizeHeight}
|
|
917
|
+
{#if (resizeWidth || resizeHeight) && !showDimensionInputs}
|
|
678
918
|
<div
|
|
679
919
|
class="widget-preview-dimension-badge absolute left-1/2 -translate-x-1/2 text-[10px] bg-black/70 px-1.5 py-px rounded text-gray-300 select-none pointer-events-none z-20 whitespace-nowrap"
|
|
680
920
|
style="top: calc(100% + 4px);"
|
|
@@ -683,7 +923,10 @@
|
|
|
683
923
|
</div>
|
|
684
924
|
{/if}
|
|
685
925
|
|
|
686
|
-
<div
|
|
926
|
+
<div
|
|
927
|
+
class="preview-iframe-container relative w-full h-full overflow-hidden {compactChrome && !enableResize ? '' : showUrlBar || mode === 'controlled' || enableResize ? 'border' : ''}"
|
|
928
|
+
style={enableResize ? `border-color: ${previewStrokeColor}; border-radius: ${showPreviewChrome ? '0 0 6px 6px' : '6px'};` : ''}
|
|
929
|
+
>
|
|
687
930
|
{#if mode === 'standalone' && iframeError}
|
|
688
931
|
<div class="widget-preview-error-ct absolute inset-0 flex items-center justify-center bg-white z-10 rounded">
|
|
689
932
|
<div class="widget-preview-error-content-ct flex flex-col items-center gap-2 text-center px-4">
|
|
@@ -734,9 +977,14 @@
|
|
|
734
977
|
{#if mode === 'controlled' && previewHasRuntimeError && !isGeneratingCode}
|
|
735
978
|
<div class="compilation-error-overlay absolute inset-0 z-20 flex flex-col items-center justify-center bg-red-50 text-red-700 p-6">
|
|
736
979
|
<div class="text-5xl mb-4">⚠️</div>
|
|
737
|
-
<div class="font-semibold text-xl mb-2">
|
|
738
|
-
|
|
739
|
-
|
|
980
|
+
<div class="font-semibold text-xl mb-2">
|
|
981
|
+
{previewError && /TypeError|Uncaught|is not a function|is not defined/i.test(previewError)
|
|
982
|
+
? 'Widget runtime error'
|
|
983
|
+
: 'Code Has Compilation Errors'}
|
|
984
|
+
</div>
|
|
985
|
+
<div class="text-sm text-center text-red-600 mb-4 max-w-md whitespace-pre-line">
|
|
986
|
+
{previewError
|
|
987
|
+
|| `The generated code has syntax or compilation errors (HTTP ${previewRuntimeErrorStatus}).`}
|
|
740
988
|
</div>
|
|
741
989
|
<div class="flex gap-3 flex-wrap justify-center">
|
|
742
990
|
<button type="button" onclick={() => dispatch('fixErrors')} class="px-4 py-2 text-xs rounded bg-red-600 hover:bg-red-700 text-white cursor-pointer border-0">🔧 Fix Errors</button>
|
|
@@ -755,7 +1003,7 @@
|
|
|
755
1003
|
src={mountedPreviewSrc}
|
|
756
1004
|
class="widget-preview-iframe h-full w-full border-0"
|
|
757
1005
|
tabindex="-1"
|
|
758
|
-
sandbox={mode === 'controlled' ? undefined : 'allow-scripts allow-popups allow-forms'}
|
|
1006
|
+
sandbox={mode === 'controlled' ? undefined : 'allow-scripts allow-popups allow-forms allow-same-origin'}
|
|
759
1007
|
loading="eager"
|
|
760
1008
|
allow="clipboard-read; clipboard-write"
|
|
761
1009
|
onload={handleIframeLoad}
|
|
@@ -772,6 +1020,7 @@
|
|
|
772
1020
|
<GenerateLoader overlay title="Generating code…" subtitle="" size="lg" />
|
|
773
1021
|
{/if}
|
|
774
1022
|
</div>
|
|
1023
|
+
</div>
|
|
775
1024
|
</div>
|
|
776
1025
|
</div>
|
|
777
1026
|
|
|
@@ -800,10 +1049,10 @@
|
|
|
800
1049
|
{/if}
|
|
801
1050
|
</div>
|
|
802
1051
|
|
|
803
|
-
{#if isResizing}
|
|
1052
|
+
{#if isResizing || isDraggingPreview}
|
|
804
1053
|
<div
|
|
805
|
-
class="widget-preview-resize-guard-ct fixed inset-0 z-999999
|
|
806
|
-
style="background: transparent;"
|
|
1054
|
+
class="widget-preview-resize-guard-ct fixed inset-0 z-999999"
|
|
1055
|
+
style="background: transparent; cursor: {isDraggingPreview ? 'move' : 'nwse-resize'};"
|
|
807
1056
|
aria-hidden="true"
|
|
808
1057
|
></div>
|
|
809
1058
|
{/if}
|
|
@@ -815,38 +1064,39 @@
|
|
|
815
1064
|
|
|
816
1065
|
.preview-resize-handle {
|
|
817
1066
|
position: absolute;
|
|
818
|
-
width:
|
|
819
|
-
height:
|
|
1067
|
+
width: 10px;
|
|
1068
|
+
height: 10px;
|
|
820
1069
|
z-index: 30;
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
.preview-resize-handle::before,
|
|
825
|
-
.preview-resize-handle::after {
|
|
826
|
-
content: '';
|
|
827
|
-
position: absolute;
|
|
828
|
-
background: #94a3b8;
|
|
1070
|
+
box-sizing: border-box;
|
|
1071
|
+
background: #ffffff;
|
|
1072
|
+
border: 1.5px solid #3b82f6;
|
|
829
1073
|
border-radius: 1px;
|
|
830
1074
|
}
|
|
831
1075
|
|
|
832
|
-
.preview-
|
|
833
|
-
.preview-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
.preview-resize-handle-tr { top: -4px; right: -4px; cursor: nesw-resize; }
|
|
837
|
-
.preview-resize-handle-tr::before { top: 0; right: 0; width: 10px; height: 2px; }
|
|
838
|
-
.preview-resize-handle-tr::after { top: 0; right: 0; width: 2px; height: 10px; }
|
|
839
|
-
|
|
840
|
-
.preview-resize-handle-bl { bottom: -4px; left: -4px; cursor: nesw-resize; }
|
|
841
|
-
.preview-resize-handle-bl::before { bottom: 0; left: 0; width: 10px; height: 2px; }
|
|
842
|
-
.preview-resize-handle-bl::after { bottom: 0; left: 0; width: 2px; height: 10px; }
|
|
1076
|
+
.preview-iframe-shell-symmetrical .preview-resize-handle,
|
|
1077
|
+
.preview-iframe-shell-symmetrical .preview-iframe-container {
|
|
1078
|
+
border-color: #f97316 !important;
|
|
1079
|
+
}
|
|
843
1080
|
|
|
844
|
-
.preview-
|
|
845
|
-
.preview-
|
|
846
|
-
.preview-
|
|
1081
|
+
.preview-chrome-header button:focus,
|
|
1082
|
+
.preview-chrome-header button:focus-visible,
|
|
1083
|
+
.preview-chrome-header input:focus,
|
|
1084
|
+
.preview-chrome-header input:focus-visible {
|
|
1085
|
+
outline: none;
|
|
1086
|
+
box-shadow: none;
|
|
1087
|
+
}
|
|
847
1088
|
|
|
848
|
-
.preview-resize-handle:
|
|
849
|
-
.preview-resize-handle:
|
|
850
|
-
|
|
1089
|
+
.preview-resize-handle-tl { top: -5px; left: -5px; cursor: nwse-resize; }
|
|
1090
|
+
.preview-resize-handle-tr { top: -5px; right: -5px; cursor: nesw-resize; }
|
|
1091
|
+
.preview-resize-handle-bl { bottom: -5px; left: -5px; cursor: nesw-resize; }
|
|
1092
|
+
.preview-resize-handle-br { bottom: -5px; right: -5px; cursor: nwse-resize; }
|
|
1093
|
+
.preview-resize-handle-t { top: -5px; left: 50%; margin-left: -5px; cursor: ns-resize; }
|
|
1094
|
+
.preview-resize-handle-b { bottom: -5px; left: 50%; margin-left: -5px; cursor: ns-resize; }
|
|
1095
|
+
.preview-resize-handle-l { left: -5px; top: 50%; margin-top: -5px; cursor: ew-resize; }
|
|
1096
|
+
.preview-resize-handle-r { right: -5px; top: 50%; margin-top: -5px; cursor: ew-resize; }
|
|
1097
|
+
|
|
1098
|
+
.preview-resize-handle:hover {
|
|
1099
|
+
background: #eff6ff;
|
|
1100
|
+
border-color: #2563eb;
|
|
851
1101
|
}
|
|
852
1102
|
</style>
|