@widgetic/creator 0.3.58 → 0.3.59

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.
@@ -447,6 +447,7 @@
447
447
  */
448
448
  import SelectorDropdown from './components/SelectorDropdown.svelte';
449
449
  import type { DropdownItem } from './components/SelectorDropdown.svelte';
450
+ import WidgetShapeLivePreviews from './components/WidgetShapeLivePreviews.svelte';
450
451
 
451
452
  // WIDGET DETAILS COMPONENT — lazy-loaded to reduce peak build memory
452
453
  let WidgetDetails: any;
@@ -473,6 +474,10 @@
473
474
 
474
475
  let WidgeticCanvas: any;
475
476
  let canvasRef: any;
477
+ /** HTML iframe overlays over Fabric WidgetShapes (W3). */
478
+ let widgetShapeLivePreviewsRef: { sync?: () => void } | null = null;
479
+ /** Selected WidgetShape id for chrome highlight (from Fabric selection). */
480
+ let canvasSelectedWidgetShapeId: string | null = null;
476
481
  let widgetDetailsModulePromise: Promise<any> | null = null;
477
482
 
478
483
  function loadWidgetDetailsModule(): Promise<any> {
@@ -8996,6 +9001,48 @@ Requirements:
8996
9001
  return resolvePublishedWidgetUrl(widgetId, version, jsPath);
8997
9002
  }
8998
9003
 
9004
+ /** Widget Builder origin for canvas shape iframes (mirrors WidgetDetails). */
9005
+ function widgetBuilderPreviewBaseUrl(): string {
9006
+ const publicBase = (
9007
+ widgetBuilderUrl ||
9008
+ import.meta.env.VITE_WIDGET_BUILDER_URL ||
9009
+ import.meta.env.PUBLIC_WIDGET_BUILDER_URL ||
9010
+ 'https://wbuilder.widgetic.com'
9011
+ ).replace(/\/+$/, '');
9012
+ if (import.meta.env.DEV && publicBase.includes('wbuilder.widgetic.com')) {
9013
+ return '/__widget_builder__';
9014
+ }
9015
+ return publicBase;
9016
+ }
9017
+
9018
+ /**
9019
+ * Live preview URL for a canvas WidgetShape overlay.
9020
+ * Prefer published CDN when available; else Worker /preview when code exists.
9021
+ * Returns null → screenshot-only fallback on the Fabric shape.
9022
+ */
9023
+ function resolveWidgetShapeLivePreviewUrl(widgetId: string): string | null {
9024
+ if (!widgetId) return null;
9025
+ const published = getPanelEditPreviewUrl(widgetId);
9026
+ if (published) return published;
9027
+
9028
+ const widget = getWidgetById(widgetId);
9029
+ if (!widget) return null;
9030
+ const ps = getPanelDisplayState(widgetId);
9031
+ const hasCode = !!(ps.lastCommitId || loadCommitIdForWidget(widgetId));
9032
+ if (!hasCode) return null;
9033
+
9034
+ return `${widgetBuilderPreviewBaseUrl()}/preview/${widgetId}`;
9035
+ }
9036
+
9037
+ function syncWidgetShapeLivePreviews(): void {
9038
+ try {
9039
+ canvasSelectedWidgetShapeId = canvasRef?.getSelectedWidgetShapeId?.() ?? null;
9040
+ widgetShapeLivePreviewsRef?.sync?.();
9041
+ } catch (err) {
9042
+ console.warn('[CreatorApp] WidgetShape live preview sync failed', err);
9043
+ }
9044
+ }
9045
+
8999
9046
  function getPanelEmbedPreviewUrl(widgetId: string): string | null {
9000
9047
  const base = getPanelEditPreviewUrl(widgetId);
9001
9048
  if (!base) return null;
@@ -11298,6 +11345,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
11298
11345
  if (selectedWidget?.id && !didForceCreateNewWidget) {
11299
11346
  placeSelectedWidgetShapeOnCanvas();
11300
11347
  }
11348
+ syncWidgetShapeLivePreviews();
11301
11349
  return;
11302
11350
  }
11303
11351
  const { loaded, widgetIds: widgetIdsOnCanvas } = await tryLoadCanvasState();
@@ -11306,6 +11354,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
11306
11354
  if (!isCanvasStateLoaded) {
11307
11355
  scheduleCanvasHydrateRetry('canvas ready before canvasId');
11308
11356
  }
11357
+ syncWidgetShapeLivePreviews();
11309
11358
  return;
11310
11359
  }
11311
11360
  await syncWidgetsWithCanvas(widgetIdsOnCanvas);
@@ -11318,6 +11367,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
11318
11367
  } else if (isConvertOrDraftPanelOpen()) {
11319
11368
  // console.log('[CreatorApp] Skip primary shape select — convert/draft panel is open');
11320
11369
  }
11370
+ syncWidgetShapeLivePreviews();
11321
11371
  }
11322
11372
 
11323
11373
  /**
@@ -12550,6 +12600,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
12550
12600
  onWidgetRename={handleWidgetRename}
12551
12601
  onRefreshScreenshot={handleRefreshScreenshot}
12552
12602
  onSetPrimaryWidget={handleSetPrimaryWidget}
12603
+ onAfterRender={syncWidgetShapeLivePreviews}
12553
12604
  getWidgetName={getWidgetNameById}
12554
12605
  isWidgetKnown={isWidgetKnownById}
12555
12606
  onNonWidgetSelected={() => {
@@ -12574,6 +12625,24 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
12574
12625
  <!-- FLOATING PANELS OVERLAY (pointer-events-none container, each panel is pointer-events-auto) -->
12575
12626
  <div class="floating-panels-overlay absolute inset-0 z-2003 pointer-events-none">
12576
12627
 
12628
+ <!-- W3: live iframe + drag bar on WidgetShapes (iframe interactive; body drag locked) -->
12629
+ <WidgetShapeLivePreviews
12630
+ bind:this={widgetShapeLivePreviewsRef}
12631
+ getRects={() => canvasRef?.listWidgetShapeScreenRects?.() ?? []}
12632
+ resolveUrl={resolveWidgetShapeLivePreviewUrl}
12633
+ selectedWidgetId={canvasSelectedWidgetShapeId}
12634
+ onTranslate={(widgetId, dx, dy) => {
12635
+ canvasRef?.translateWidgetShapeByScreen?.(widgetId, dx, dy);
12636
+ }}
12637
+ onSelectShape={(widgetId) => {
12638
+ canvasRef?.selectWidgetShapeById?.(widgetId);
12639
+ canvasSelectedWidgetShapeId = widgetId;
12640
+ }}
12641
+ onOpenChat={(widgetId) => {
12642
+ void openWidgetDetailsPanel(widgetId);
12643
+ }}
12644
+ />
12645
+
12577
12646
  <!-- ═══ PANEL 2: Widgets Panel — debug + showWidgetsList (site passes false) ═══ -->
12578
12647
  {#if debugMode && showWidgetsList}
12579
12648
  <!-- svelte-ignore a11y-no-static-element-interactions -->
@@ -763,7 +763,11 @@
763
763
  }
764
764
  let showPreviewModal = false;
765
765
  let previewModalRef: HTMLDivElement | null = null;
766
- let widgetPreviewComponentRef: { getResizeLimits?: () => { maxWidth: number; maxHeight: number }; fitToContainer?: () => void } | undefined;
766
+ let widgetPreviewComponentRef: {
767
+ getResizeLimits?: () => { maxWidth: number; maxHeight: number };
768
+ fitToContainer?: () => void;
769
+ resetPreviewPosition?: () => void;
770
+ } | undefined;
767
771
  let previewIframe: HTMLIFrameElement | null = null;
768
772
  let isLoadingDirectPreviewUrl = false;
769
773
  let previewFetchRetryCount = 0;
@@ -925,27 +929,18 @@
925
929
  const LEFT_COL_MAX_PERCENT = 62;
926
930
  const COL_RESIZER_PX = 10;
927
931
  const PREVIEW_COL_MIN_PX = 420;
928
- /** Chat-first layout: preview column starts collapsed and opens on generate. */
932
+ /** Chat-first: panel preview column is hidden (W3 canvas live preview is primary). */
933
+ const SHOW_PANEL_PREVIEW_COLUMN = false;
934
+ /** Chat-first layout: preview column stays collapsed until the user opens it (W3 canvas shows live preview). */
929
935
  let previewColumnCollapsed = true;
930
936
  let lastAutoExpandForGenerating = false;
931
937
  let widthBeforePreviewExpand = 0;
932
- $: if (isGeneratingCode && !lastAutoExpandForGenerating) {
933
- lastAutoExpandForGenerating = true;
934
- if (previewColumnCollapsed) expandPreviewColumn();
935
- }
938
+ // Do not auto-expand the right preview column — canvas WidgetShape iframes are the live preview.
936
939
  $: if (!isGeneratingCode) {
937
940
  lastAutoExpandForGenerating = false;
938
941
  }
939
- /** Open the live preview column once when this widget already has code (or a loaded iframe). */
940
- $: if (
941
- showWidgetDetails
942
- && !isDraftConvertPanel
943
- && previewColumnCollapsed
944
- && !hasAutoOpenedPreview
945
- && (hasWidgetCode || !!iframeSrc)
946
- ) {
947
- hasAutoOpenedPreview = true;
948
- expandPreviewColumn();
942
+ $: if (!SHOW_PANEL_PREVIEW_COLUMN) {
943
+ previewColumnCollapsed = true;
949
944
  }
950
945
 
951
946
  function maxLeftColPercent(): number {
@@ -974,6 +969,7 @@
974
969
  }
975
970
 
976
971
  function expandPreviewColumn() {
972
+ if (!SHOW_PANEL_PREVIEW_COLUMN) return;
977
973
  if (!previewColumnCollapsed) return;
978
974
  previewColumnCollapsed = false;
979
975
  leftColPercent = clampLeftColPercent(LEFT_COL_DEFAULT_PERCENT);
@@ -982,7 +978,25 @@
982
978
  detailsPanelX = Math.max(8, window.innerWidth - 8 - detailsPanelWidth);
983
979
  }
984
980
  console.log('[WidgetDetails] Open Preview — panel width', detailsPanelWidth);
985
- tick().then(() => requestAnimationFrame(() => fitPreviewToArea()));
981
+ tick().then(() => requestAnimationFrame(async () => {
982
+ // Load panel preview only when the user opens the column (avoid wasted Worker/CDN requests).
983
+ if (selectedWidgetId && !iframeSrc && !showPreviewModal) {
984
+ try {
985
+ if (await loadPublishedPreviewForCurrentStep()) {
986
+ fitPreviewToArea();
987
+ return;
988
+ }
989
+ if (lastCommitId || lastPublishedVersion !== null || normalizeRepositoryId(selectedWidget) || currentRepositoryId) {
990
+ previewLoading = true;
991
+ await runPreviewLoadPipeline(selectedWidgetId);
992
+ previewLoadedForWidgetId = selectedWidgetId;
993
+ }
994
+ } catch (err) {
995
+ console.warn('[WidgetDetails] Deferred preview load on expand failed', err);
996
+ }
997
+ }
998
+ fitPreviewToArea();
999
+ }));
986
1000
  }
987
1001
 
988
1002
  let detailsPanelX = -1;
@@ -1256,6 +1270,14 @@
1256
1270
  return;
1257
1271
  }
1258
1272
 
1273
+ // Chat-first: skip panel Worker/CDN preview while the right column is closed (W3 canvas overlays handle live view).
1274
+ if (previewColumnCollapsed && !showPreviewModal) {
1275
+ previewLoading = false;
1276
+ previewError = null;
1277
+ console.log('[WidgetDetails] Preview column collapsed — skipping panel preview network');
1278
+ return;
1279
+ }
1280
+
1259
1281
  if (await loadPublishedPreviewForCurrentStep()) {
1260
1282
  return;
1261
1283
  }
@@ -2019,7 +2041,8 @@
2019
2041
  stopPreviewTokenAutoRefresh();
2020
2042
  // console.log('[TokenAutoRefresh] Started - checking every', TOKEN_CHECK_INTERVAL_MS / 1000, 'seconds');
2021
2043
  previewTokenRefreshInterval = setInterval(async () => {
2022
- const isPreviewVisible = showPreviewModal || (showWidgetDetails && inlinePanelView === 'preview');
2044
+ const isPreviewVisible = showPreviewModal
2045
+ || (showWidgetDetails && !previewColumnCollapsed && inlinePanelView === 'preview');
2023
2046
  if (!isPreviewVisible || !lastPreviewUrlFetchTime || !directPreviewUrl) return;
2024
2047
  const tokenAge = Date.now() - lastPreviewUrlFetchTime;
2025
2048
  if (tokenAge >= PREVIEW_TOKEN_REFRESH_MS) {
@@ -2536,7 +2559,9 @@
2536
2559
  );
2537
2560
  return;
2538
2561
  }
2539
- detailsPanelWidth = clampPanelWidth(DETAILS_PANEL_DEFAULT_WIDTH);
2562
+ detailsPanelWidth = clampPanelWidth(
2563
+ previewColumnCollapsed ? DETAILS_PANEL_MIN_WIDTH : DETAILS_PANEL_DEFAULT_WIDTH
2564
+ );
2540
2565
  detailsPanelHeight = getDefaultDetailsPanelHeight();
2541
2566
  }
2542
2567
 
@@ -2583,7 +2608,8 @@
2583
2608
  }
2584
2609
 
2585
2610
  /**
2586
- * Center Widget Details in the canvas viewport (X and Y), clamped on-screen.
2611
+ * Park the panel beside a canvas widget shape: right if the shape is left of
2612
+ * viewport center, otherwise left. Chat-first width when preview is collapsed.
2587
2613
  */
2588
2614
  export function positionBesideShape(shapeRect: {
2589
2615
  left: number;
@@ -2596,32 +2622,55 @@
2596
2622
  viewportHeight: number;
2597
2623
  } | null): void {
2598
2624
  if (typeof window === 'undefined') return;
2625
+ previewColumnCollapsed = true;
2599
2626
  resetDetailsPanelSize();
2600
2627
  _hasBeenOpenedOnce = true;
2601
2628
  if (!shapeRect) {
2602
2629
  centerDetailsPanel();
2603
- // console.log('[WidgetDetails] No shape rect — centered floating panel', detailsPanelWidth, detailsPanelHeight);
2630
+ console.log('[WidgetDetails] No shape rect — centered floating panel', detailsPanelWidth, detailsPanelHeight);
2604
2631
  return;
2605
2632
  }
2606
2633
 
2607
- detailsPanelWidth = clampPanelWidth(DETAILS_PANEL_DEFAULT_WIDTH);
2608
- detailsPanelHeight = clampPanelHeight(DETAILS_PANEL_MAX_HEIGHT);
2634
+ const pad = 8;
2635
+ const viewLeft = shapeRect.viewportLeft;
2636
+ const viewRight = shapeRect.viewportLeft + shapeRect.viewportWidth;
2637
+ const availableWidth = Math.max(DETAILS_PANEL_MIN_WIDTH, shapeRect.viewportWidth - pad * 2);
2638
+ detailsPanelWidth = clampPanelWidth(
2639
+ previewColumnCollapsed
2640
+ ? DETAILS_PANEL_MIN_WIDTH
2641
+ : Math.min(DETAILS_PANEL_MAX_WIDTH, availableWidth)
2642
+ );
2643
+ detailsPanelHeight = clampPanelHeight(
2644
+ Math.min(DETAILS_PANEL_MAX_HEIGHT, shapeRect.viewportHeight - pad * 2),
2645
+ );
2609
2646
 
2610
- const viewportLeft = shapeRect.viewportLeft ?? 0;
2611
- const viewportWidth = shapeRect.viewportWidth || window.innerWidth;
2612
- const viewportTop = shapeRect.viewportTop ?? detailsPanelTopOffset;
2613
- const viewportHeight = shapeRect.viewportHeight || (window.innerHeight - viewportTop);
2647
+ const shapeCenterX = shapeRect.left + shapeRect.width / 2;
2648
+ const viewCenterX = shapeRect.viewportLeft + shapeRect.viewportWidth / 2;
2649
+ const openOnRight = shapeCenterX < viewCenterX;
2650
+ if (openOnRight) {
2651
+ detailsPanelX = shapeRect.left + shapeRect.width + DETAILS_PANEL_SHAPE_GAP;
2652
+ if (detailsPanelX + detailsPanelWidth > viewRight - pad) {
2653
+ detailsPanelX = Math.max(viewLeft + pad, viewRight - pad - detailsPanelWidth);
2654
+ }
2655
+ } else {
2656
+ detailsPanelX = shapeRect.left - DETAILS_PANEL_SHAPE_GAP - detailsPanelWidth;
2657
+ if (detailsPanelX < viewLeft + pad) {
2658
+ detailsPanelX = viewLeft + pad;
2659
+ }
2660
+ }
2614
2661
 
2615
- const x = Math.round(viewportLeft + (viewportWidth - detailsPanelWidth) / 2);
2616
- const y = Math.round(viewportTop + Math.max(0, (viewportHeight - detailsPanelHeight) / 2));
2617
- const pos = clampDetailsPanelPosition(x, y);
2618
- detailsPanelX = pos.x;
2619
- detailsPanelY = pos.y;
2620
- console.log('[WidgetDetails] Centered in canvas viewport', {
2662
+ const maxY = window.innerHeight - detailsPanelHeight - pad;
2663
+ detailsPanelY = Math.min(
2664
+ maxY,
2665
+ Math.max(detailsPanelTopOffset, shapeRect.top),
2666
+ );
2667
+ console.log('[WidgetDetails] Positioned beside shape', {
2668
+ openOnRight,
2621
2669
  x: Math.round(detailsPanelX),
2622
2670
  y: Math.round(detailsPanelY),
2623
2671
  w: detailsPanelWidth,
2624
2672
  h: detailsPanelHeight,
2673
+ previewCollapsed: previewColumnCollapsed,
2625
2674
  });
2626
2675
  }
2627
2676
 
@@ -2668,6 +2717,8 @@
2668
2717
  let _hasBeenOpenedOnce = false;
2669
2718
  export function openWidgetDetails() {
2670
2719
  if (showWidgetDetails) return;
2720
+ previewColumnCollapsed = true;
2721
+ hasAutoOpenedPreview = false;
2671
2722
  resetDetailsPanelSize();
2672
2723
  if (fillViewport || (!_hasBeenOpenedOnce && (detailsPanelX < 0 || detailsPanelY < 0))) {
2673
2724
  centerDetailsPanel();
@@ -2676,7 +2727,7 @@
2676
2727
  showWidgetDetails = true;
2677
2728
  dispatch('widgetDetailsToggle', { open: true });
2678
2729
 
2679
- // Clamp preview after DOM is laid out
2730
+ // Clamp preview after DOM is laid out (no-op while column collapsed)
2680
2731
  tick().then(() => {
2681
2732
  requestAnimationFrame(() => {
2682
2733
  fitPreviewToArea();
@@ -2959,6 +3010,7 @@
2959
3010
  /** Expand preview to fill the available panel (fit-to-area). */
2960
3011
  function fitPreviewToArea() {
2961
3012
  widgetPreviewComponentRef?.fitToContainer?.();
3013
+ widgetPreviewComponentRef?.resetPreviewPosition?.();
2962
3014
  }
2963
3015
 
2964
3016
  // Clamp preview to available stage inside WidgetPreview (handles + dimension badge padding)
@@ -3064,8 +3116,8 @@
3064
3116
  previewLoading = false;
3065
3117
  }
3066
3118
 
3067
- // Stop token auto-refresh when no preview is visible
3068
- $: if (!showPreviewModal && !(showWidgetDetails && inlinePanelView === 'preview')) {
3119
+ // Stop token auto-refresh when no preview is visible (collapsed column counts as hidden)
3120
+ $: if (!showPreviewModal && !(showWidgetDetails && !previewColumnCollapsed && inlinePanelView === 'preview')) {
3069
3121
  stopPreviewTokenAutoRefresh();
3070
3122
  }
3071
3123
 
@@ -3447,6 +3499,7 @@
3447
3499
  class="widget-details-debug-buttons relative z-[1] ml-auto flex items-center shrink-0"
3448
3500
  bind:this={widgetDetailsDebugButtonsEl}
3449
3501
  >
3502
+ {#if SHOW_PANEL_PREVIEW_COLUMN}
3450
3503
  <HoverTooltip text={previewColumnCollapsed ? 'Open live preview' : 'Close live preview'}>
3451
3504
  <button
3452
3505
  type="button"
@@ -3474,6 +3527,7 @@
3474
3527
  {/if}
3475
3528
  </button>
3476
3529
  </HoverTooltip>
3530
+ {/if}
3477
3531
  </div>
3478
3532
  </div>
3479
3533
  </div>
@@ -3561,7 +3615,7 @@
3561
3615
 
3562
3616
  </div> <!-- end of widget-details-left-col -->
3563
3617
 
3564
- {#if previewColumnCollapsed}
3618
+ {#if SHOW_PANEL_PREVIEW_COLUMN && previewColumnCollapsed}
3565
3619
  <div class="widget-details-preview-expand-strip flex items-center justify-center w-8 shrink-0 border-l border-gray-200 bg-gray-50">
3566
3620
  <button
3567
3621
  type="button"
@@ -3572,7 +3626,7 @@
3572
3626
  <span class="widget-details-preview-expand-label inline-block rotate-180" style="writing-mode: vertical-rl;">Preview</span>
3573
3627
  </button>
3574
3628
  </div>
3575
- {:else}
3629
+ {:else if SHOW_PANEL_PREVIEW_COLUMN}
3576
3630
  <!-- svelte-ignore a11y-no-static-element-interactions -->
3577
3631
  <div
3578
3632
  class="widget-details-col-resizer w-2.5 shrink-0 cursor-col-resize relative z-20 group"
@@ -3645,6 +3699,7 @@
3645
3699
  mode="controlled"
3646
3700
  enableResize={true}
3647
3701
  resizeMode="concentric"
3702
+ defaultStageAlign="center"
3648
3703
  autoFitStage={showPreviewModal}
3649
3704
  src={iframeSrc || getFallbackPreviewUrl()}
3650
3705
  displayUrl={directPreviewUrl || iframeSrc}
@@ -0,0 +1,170 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Live iframe + chrome over Fabric WidgetShapes (W3).
4
+ * Body drag is locked on the shape; move via the top drag bar so the iframe stays interactive.
5
+ */
6
+ let className = '';
7
+ export { className as class };
8
+ export let style = '';
9
+
10
+ export let enabled = true;
11
+ /** Screen-space boxes for widget shapes currently on the canvas. */
12
+ export let getRects:
13
+ | (() => Array<{ widgetId: string; left: number; top: number; width: number; height: number }>)
14
+ | null = null;
15
+ /** Resolve a live preview URL for a widget (CDN published or Worker preview). */
16
+ export let resolveUrl: ((widgetId: string) => string | null) | null = null;
17
+ /** Currently selected WidgetShape id (highlights chrome). */
18
+ export let selectedWidgetId: string | null = null;
19
+ /** Translate shape by screen-pixel delta. */
20
+ export let onTranslate:
21
+ | ((widgetId: string, dxScreen: number, dyScreen: number) => void)
22
+ | null = null;
23
+ /** Select the Fabric WidgetShape (e.g. when pressing the drag bar). */
24
+ export let onSelectShape: ((widgetId: string) => void) | null = null;
25
+ /** Open Widget Details / chat for this widget. */
26
+ export let onOpenChat: ((widgetId: string) => void) | null = null;
27
+
28
+ const DRAG_BAR_H = 28;
29
+ const BAR_SHAPE_GAP = 4;
30
+
31
+ type ShapeRect = {
32
+ widgetId: string;
33
+ left: number;
34
+ top: number;
35
+ width: number;
36
+ height: number;
37
+ src: string;
38
+ };
39
+
40
+ let overlays: ShapeRect[] = [];
41
+ let draggingWidgetId: string | null = null;
42
+
43
+ export function sync(): void {
44
+ if (!enabled || !getRects || !resolveUrl) {
45
+ overlays = [];
46
+ return;
47
+ }
48
+ const next: ShapeRect[] = [];
49
+ for (const rect of getRects() || []) {
50
+ if (!rect?.widgetId) continue;
51
+ const src = resolveUrl(rect.widgetId);
52
+ if (!src) continue;
53
+ next.push({
54
+ widgetId: rect.widgetId,
55
+ left: rect.left,
56
+ top: rect.top,
57
+ width: rect.width,
58
+ height: rect.height,
59
+ src,
60
+ });
61
+ }
62
+ overlays = next;
63
+ }
64
+
65
+ function startShapeDrag(event: PointerEvent, widgetId: string) {
66
+ const target = event.target as HTMLElement | null;
67
+ if (target?.closest('button, a, input')) return;
68
+ event.preventDefault();
69
+ event.stopPropagation();
70
+ onSelectShape?.(widgetId);
71
+ draggingWidgetId = widgetId;
72
+ const startX = event.clientX;
73
+ const startY = event.clientY;
74
+ let lastX = startX;
75
+ let lastY = startY;
76
+
77
+ function onMove(e: PointerEvent) {
78
+ const dx = e.clientX - lastX;
79
+ const dy = e.clientY - lastY;
80
+ lastX = e.clientX;
81
+ lastY = e.clientY;
82
+ if (dx === 0 && dy === 0) return;
83
+ onTranslate?.(widgetId, dx, dy);
84
+ }
85
+
86
+ function endDrag() {
87
+ draggingWidgetId = null;
88
+ document.removeEventListener('pointermove', onMove);
89
+ document.removeEventListener('pointerup', endDrag);
90
+ document.removeEventListener('pointercancel', endDrag);
91
+ document.body.style.removeProperty('user-select');
92
+ document.body.style.removeProperty('cursor');
93
+ console.log('[WidgetShapeLivePreviews] Drag end', widgetId.substring(0, 8));
94
+ }
95
+
96
+ document.body.style.userSelect = 'none';
97
+ document.body.style.cursor = 'grabbing';
98
+ document.addEventListener('pointermove', onMove);
99
+ document.addEventListener('pointerup', endDrag);
100
+ document.addEventListener('pointercancel', endDrag);
101
+ console.log('[WidgetShapeLivePreviews] Drag start', widgetId.substring(0, 8));
102
+ }
103
+
104
+ function handleOpenChat(event: MouseEvent, widgetId: string) {
105
+ event.preventDefault();
106
+ event.stopPropagation();
107
+ onSelectShape?.(widgetId);
108
+ onOpenChat?.(widgetId);
109
+ }
110
+ </script>
111
+
112
+ {#if enabled && overlays.length > 0}
113
+ <div class="widget-shape-live-previews-ct pointer-events-none {className}" {style}>
114
+ {#each overlays as overlay (overlay.widgetId)}
115
+ {@const isSelected = selectedWidgetId === overlay.widgetId}
116
+ {@const isDragging = draggingWidgetId === overlay.widgetId}
117
+ {@const chromeTop = overlay.top - DRAG_BAR_H - BAR_SHAPE_GAP}
118
+ <div
119
+ class="widget-shape-live-chrome-ct fixed"
120
+ style="left: {overlay.left}px; top: {chromeTop}px; width: {overlay.width}px; height: {DRAG_BAR_H + BAR_SHAPE_GAP + overlay.height}px; z-index: 1;"
121
+ >
122
+ <!-- Drag bar sits between rotation control and the shape top stroke -->
123
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
124
+ <div
125
+ class="widget-shape-drag-bar pointer-events-auto absolute left-0 right-0 top-0 flex h-7 items-center gap-1 rounded-t-md border border-b-0 bg-white/95 px-1.5 shadow-sm select-none
126
+ {isSelected ? 'border-blue-400' : 'border-gray-300'}"
127
+ style="height: {DRAG_BAR_H}px; cursor: grab;"
128
+ title="Drag widget"
129
+ onpointerdown={(e) => startShapeDrag(e, overlay.widgetId)}
130
+ >
131
+ <span class="widget-shape-drag-grip shrink-0 text-xs leading-none text-gray-400" aria-hidden="true">⠿</span>
132
+ {#if isSelected}
133
+ <span
134
+ class="widget-shape-edit-mode-badge shrink-0 rounded-full bg-amber-200 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-amber-900"
135
+ >Edit mode</span>
136
+ {/if}
137
+ <div class="widget-shape-drag-bar-actions ml-auto flex items-center gap-0.5">
138
+ <button
139
+ type="button"
140
+ class="widget-shape-open-chat-bt flex h-6 items-center gap-1 rounded px-1.5 text-[10px] font-medium text-gray-600 hover:bg-gray-100 hover:text-forest-green cursor-pointer"
141
+ title="Open chat"
142
+ onclick={(e) => handleOpenChat(e, overlay.widgetId)}
143
+ onpointerdown={(e) => e.stopPropagation()}
144
+ >
145
+ Chat
146
+ </button>
147
+ </div>
148
+ </div>
149
+
150
+ <div
151
+ class="widget-shape-live-preview-frame pointer-events-none absolute left-0 right-0 overflow-visible
152
+ {isSelected ? 'ring-2 ring-blue-400/80 rounded-sm' : ''}"
153
+ style="top: {DRAG_BAR_H + BAR_SHAPE_GAP}px; height: {overlay.height}px;"
154
+ >
155
+ <!-- Inset so Fabric corner/rotate handles stay clickable around the edges -->
156
+ <iframe
157
+ class="widget-shape-live-preview-iframe pointer-events-auto absolute border-0 bg-white"
158
+ class:pointer-events-none={isDragging}
159
+ style="left: 8px; top: 8px; right: 8px; bottom: 8px; width: calc(100% - 16px); height: calc(100% - 16px);"
160
+ title="Live widget preview"
161
+ src={overlay.src}
162
+ tabindex="-1"
163
+ loading="lazy"
164
+ sandbox="allow-scripts allow-same-origin allow-forms"
165
+ ></iframe>
166
+ </div>
167
+ </div>
168
+ {/each}
169
+ </div>
170
+ {/if}
@@ -0,0 +1,40 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: Props & {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const WidgetShapeLivePreviews: $$__sveltets_2_IsomorphicComponent<{
15
+ /**
16
+ * Live iframe + chrome over Fabric WidgetShapes (W3).
17
+ * Body drag is locked on the shape; move via the top drag bar so the iframe stays interactive.
18
+ */ class?: string;
19
+ style?: string;
20
+ enabled?: boolean;
21
+ /** Screen-space boxes for widget shapes currently on the canvas. */ getRects?: (() => Array<{
22
+ widgetId: string;
23
+ left: number;
24
+ top: number;
25
+ width: number;
26
+ height: number;
27
+ }>) | null;
28
+ /** Resolve a live preview URL for a widget (CDN published or Worker preview). */ resolveUrl?: ((widgetId: string) => string | null) | null;
29
+ /** Currently selected WidgetShape id (highlights chrome). */ selectedWidgetId?: string | null;
30
+ /** Translate shape by screen-pixel delta. */ onTranslate?: ((widgetId: string, dxScreen: number, dyScreen: number) => void) | null;
31
+ /** Select the Fabric WidgetShape (e.g. when pressing the drag bar). */ onSelectShape?: ((widgetId: string) => void) | null;
32
+ /** Open Widget Details / chat for this widget. */ onOpenChat?: ((widgetId: string) => void) | null;
33
+ sync?: () => void;
34
+ }, {
35
+ [evt: string]: CustomEvent<any>;
36
+ }, {}, {
37
+ sync: () => void;
38
+ }, string>;
39
+ type WidgetShapeLivePreviews = InstanceType<typeof WidgetShapeLivePreviews>;
40
+ export default WidgetShapeLivePreviews;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widgetic/creator",
3
- "version": "0.3.58",
3
+ "version": "0.3.59",
4
4
  "type": "module",
5
5
  "peerDependencies": {
6
6
  "@sveltejs/kit": "^2.20.0",