@widgetic/creator 0.3.49 → 0.3.51

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.
@@ -31,7 +31,6 @@
31
31
  import { userSession as userSessionStore } from '../stores/userSession.js';
32
32
 
33
33
  import { WidgetPreview } from '@widgetic/editor';
34
- import EditableName from './EditableName.svelte';
35
34
 
36
35
  const dispatch = createEventDispatcher();
37
36
 
@@ -272,6 +271,13 @@
272
271
 
273
272
  $: hasWidgetCode = !!lastCommitId || lastPublishedVersion !== null;
274
273
 
274
+ /** Convert UX v2: draft panels (synthetic ids) have no widget/repo yet —
275
+ * show a friendly draft hint instead of the repo-setup loader. */
276
+ $: isDraftConvertPanel = !!selectedWidgetId?.startsWith('draft_convert_');
277
+ $: if (isDraftConvertPanel && previewError === 'NO_CODE_YET') {
278
+ previewError = 'DRAFT_PANEL';
279
+ }
280
+
275
281
  /** Published widgets must never show the draft "No Code Yet" empty state. */
276
282
  $: if (hasWidgetCode && previewError === 'NO_CODE_YET') {
277
283
  previewError = lastPublishedVersion !== null
@@ -851,14 +857,87 @@
851
857
  $: detailsPanelTopOffset = panelTopOffset ?? legacyTopOffset;
852
858
  $: detailsPanelBottomMargin = panelBottomMargin ?? legacyBottomMargin;
853
859
 
854
- const DETAILS_PANEL_DEFAULT_WIDTH = 1100;
855
- const DETAILS_PANEL_MIN_WIDTH = 720;
856
- const DETAILS_PANEL_MIN_HEIGHT = 500;
860
+ const DETAILS_PANEL_DEFAULT_WIDTH = 600;
861
+ const DETAILS_PANEL_MAX_WIDTH = 600;
862
+ const DETAILS_PANEL_MAX_HEIGHT = 800;
863
+ const DETAILS_PANEL_MIN_WIDTH = 320;
864
+ const DETAILS_PANEL_MIN_HEIGHT = 400;
857
865
  const DETAILS_PANEL_FILL_INSET = 16;
866
+ const DETAILS_PANEL_SHAPE_GAP = 12;
858
867
  /** Left column share of the two-col layout (chat / PropsEditor). */
859
868
  const LEFT_COL_DEFAULT_PERCENT = 58;
860
869
  const LEFT_COL_MIN_PERCENT = 32;
861
870
  const LEFT_COL_MAX_PERCENT = 72;
871
+ const COL_RESIZER_PX = 10;
872
+ const PREVIEW_COL_MIN_PX = 240;
873
+ /** Chat-first layout: preview column starts collapsed and opens on generate. */
874
+ let previewColumnCollapsed = true;
875
+ let lastAutoExpandForGenerating = false;
876
+ let widthBeforePreviewExpand = 0;
877
+ $: if (isGeneratingCode && !lastAutoExpandForGenerating) {
878
+ lastAutoExpandForGenerating = true;
879
+ if (previewColumnCollapsed) expandPreviewColumn();
880
+ }
881
+ $: if (!isGeneratingCode) {
882
+ lastAutoExpandForGenerating = false;
883
+ }
884
+ /** Open the live preview column once when this widget already has code (or a loaded iframe). */
885
+ $: if (
886
+ showWidgetDetails
887
+ && !isDraftConvertPanel
888
+ && previewColumnCollapsed
889
+ && !hasAutoOpenedPreview
890
+ && (hasWidgetCode || !!iframeSrc)
891
+ ) {
892
+ hasAutoOpenedPreview = true;
893
+ expandPreviewColumn();
894
+ }
895
+
896
+ function maxLeftColPercent(): number {
897
+ const twoCol =
898
+ typeof document !== 'undefined'
899
+ ? (document.querySelector('.widget-details-two-col') as HTMLElement | null)
900
+ : null;
901
+ const width = twoCol?.clientWidth || detailsPanelWidth || DETAILS_PANEL_DEFAULT_WIDTH;
902
+ if (width <= 0) return LEFT_COL_MAX_PERCENT;
903
+ const maxLeftPx = Math.max(160, width - COL_RESIZER_PX - PREVIEW_COL_MIN_PX);
904
+ return Math.min(LEFT_COL_MAX_PERCENT, (maxLeftPx / width) * 100);
905
+ }
906
+
907
+ function clampLeftColPercent(percent: number): number {
908
+ const maxP = maxLeftColPercent();
909
+ const minP = Math.min(LEFT_COL_MIN_PERCENT, maxP);
910
+ return Math.min(maxP, Math.max(minP, percent));
911
+ }
912
+
913
+ function collapsePreviewColumn() {
914
+ previewColumnCollapsed = true;
915
+ if (widthBeforePreviewExpand > 0) {
916
+ detailsPanelWidth = widthBeforePreviewExpand;
917
+ widthBeforePreviewExpand = 0;
918
+ leftColPercent = LEFT_COL_DEFAULT_PERCENT;
919
+ }
920
+ console.log('[WidgetDetails] Preview column minimized, restored width', detailsPanelWidth);
921
+ }
922
+
923
+ function expandPreviewColumn() {
924
+ if (!previewColumnCollapsed) return;
925
+ widthBeforePreviewExpand = detailsPanelWidth;
926
+ previewColumnCollapsed = false;
927
+ const extraWidth = 420;
928
+ const maxRight = typeof window !== 'undefined' ? window.innerWidth - 8 : detailsPanelWidth;
929
+ const expandedWidth = widthBeforePreviewExpand + extraWidth;
930
+ const fittedWidth = Math.min(expandedWidth, Math.max(widthBeforePreviewExpand, maxRight - detailsPanelX));
931
+ detailsPanelWidth = Math.max(widthBeforePreviewExpand, fittedWidth);
932
+ const usable = Math.max(1, detailsPanelWidth);
933
+ leftColPercent = clampLeftColPercent((widthBeforePreviewExpand / usable) * 100);
934
+ console.log('[WidgetDetails] Preview column expanded', {
935
+ from: widthBeforePreviewExpand,
936
+ to: detailsPanelWidth,
937
+ leftColPercent,
938
+ });
939
+ tick().then(() => requestAnimationFrame(() => fitPreviewToArea()));
940
+ }
862
941
 
863
942
  let detailsPanelX = -1;
864
943
  let detailsPanelY = -1;
@@ -959,6 +1038,12 @@
959
1038
 
960
1039
  /** Probe Worker / build-from-repo pipeline without resetting panel state. */
961
1040
  async function runPreviewLoadPipeline(widgetId: string): Promise<void> {
1041
+ if (iframeSrc && previewLoadedForWidgetId === widgetId) {
1042
+ previewBuildDeferred = false;
1043
+ previewLoading = false;
1044
+ console.log('[WidgetDetails] Preview already loaded, skipping pipeline:', widgetId.substring(0, 8));
1045
+ return;
1046
+ }
962
1047
  if (shouldDeferPreviewNetworkWork()) {
963
1048
  console.log('[WidgetDetails] Deferring preview load (background panel):', widgetId.substring(0, 8));
964
1049
  previewBuildDeferred = true;
@@ -1026,6 +1111,11 @@
1026
1111
  /** Resume preview after a background panel receives focus. */
1027
1112
  export async function resumeDeferredPreviewLoad(): Promise<void> {
1028
1113
  if (!previewBuildDeferred || !selectedWidgetId) return;
1114
+ if (iframeSrc && previewLoadedForWidgetId === selectedWidgetId) {
1115
+ previewBuildDeferred = false;
1116
+ console.log('[WidgetDetails] Skip deferred preview — already loaded for', selectedWidgetId.substring(0, 8));
1117
+ return;
1118
+ }
1029
1119
  previewBuildDeferred = false;
1030
1120
  await runPreviewLoadPipeline(selectedWidgetId);
1031
1121
  }
@@ -1107,10 +1197,11 @@
1107
1197
  return;
1108
1198
  }
1109
1199
 
1110
- // Brand-new widget (no commit, not published): skip Worker HEAD / build-from-repo.
1111
- // Probing localhost:5173/__widget_builder__/preview/... only produces expected 404 noise.
1200
+ // Brand-new widget (no repo): skip Worker HEAD / build-from-repo.
1201
+ // If the widget already has a GitLab repo, compile even without a cached commit SHA
1202
+ // (localStorage miss, failed retries, unpublished lastCpgCommitSha).
1112
1203
  const hasRepo = !!(normalizeRepositoryId(selectedWidget) || currentRepositoryId);
1113
- if (!lastCommitId && lastPublishedVersion === null) {
1204
+ if (!lastCommitId && lastPublishedVersion === null && !hasRepo) {
1114
1205
  console.log('[WidgetDetails] No code yet — skipping Worker probe/build-from-repo', {
1115
1206
  widgetId: widgetId?.substring(0, 8),
1116
1207
  hasRepo,
@@ -1386,7 +1477,9 @@
1386
1477
  }
1387
1478
 
1388
1479
  export function hasValidPreview(): boolean {
1389
- return !!iframeSrc && !previewError && !previewLoading;
1480
+ // Iframe can already show a compiled preview while `previewLoading`/`previewError`
1481
+ // still lag (e.g. iteration rebuild). Screenshot only needs a live contentWindow.
1482
+ return !!previewIframe?.contentWindow;
1390
1483
  }
1391
1484
 
1392
1485
  export function getPreviewIframe(): HTMLIFrameElement | null {
@@ -1515,19 +1608,65 @@
1515
1608
  notifyPreviewCompileErrorChanged();
1516
1609
  }
1517
1610
  flushPendingPreviewMessages();
1518
- if (currentStep === 'edit' || currentStep === 'embed') {
1519
- dispatch('previewLoaded', { step: currentStep });
1520
- }
1611
+ dispatch('previewLoaded', { step: currentStep });
1521
1612
  // Emit previewReady so the parent app can clear the "…loading preview…" status.
1522
1613
  dispatch('previewReady', { widgetId: selectedWidgetId, step: currentStep });
1523
1614
  // Fill available preview area once the widget confirms it is ready.
1524
1615
  scheduleFitPreviewToArea();
1525
1616
  }
1526
1617
 
1618
+ function looksLikeHtmlMarkup(value: unknown): value is string {
1619
+ return typeof value === 'string' && /<[a-z][\s\S]*>/i.test(value);
1620
+ }
1621
+
1622
+ /** Same-origin preview iframes (Vite /__widget_builder__ proxy): restore bold/italic after widgets set textContent. */
1623
+ function applyRichTextInPreviewIframe(message: Record<string, unknown>): void {
1624
+ if (message.type !== 'widgetic:update') return;
1625
+ const doc = previewIframe?.contentDocument;
1626
+ if (!doc) return;
1627
+ const apply = () => {
1628
+ try {
1629
+ const content = message.content as Record<string, unknown> | undefined;
1630
+ if (content) {
1631
+ for (const [key, value] of Object.entries(content)) {
1632
+ if (!looksLikeHtmlMarkup(value)) continue;
1633
+ doc.querySelectorAll(`[data-content="${CSS.escape(key)}"]`).forEach((el) => {
1634
+ if (el instanceof HTMLImageElement || el instanceof HTMLInputElement) return;
1635
+ el.innerHTML = value;
1636
+ });
1637
+ }
1638
+ }
1639
+ const items = message.contentItems as Array<Record<string, unknown>> | undefined;
1640
+ if (Array.isArray(items)) {
1641
+ for (const item of items) {
1642
+ const itemEl = item?.id
1643
+ ? doc.querySelector(`[data-content-item="${CSS.escape(String(item.id))}"]`)
1644
+ : null;
1645
+ const scope = itemEl || doc;
1646
+ for (const [key, value] of Object.entries(item || {})) {
1647
+ if (key === 'id' || !looksLikeHtmlMarkup(value)) continue;
1648
+ scope.querySelectorAll(`[data-content="${CSS.escape(key)}"]`).forEach((el) => {
1649
+ if (el instanceof HTMLImageElement || el instanceof HTMLInputElement) return;
1650
+ el.innerHTML = value;
1651
+ });
1652
+ }
1653
+ }
1654
+ }
1655
+ } catch (error) {
1656
+ console.warn('[WidgetDetails] Rich-text preview overlay skipped:', error);
1657
+ }
1658
+ };
1659
+ requestAnimationFrame(() => {
1660
+ apply();
1661
+ setTimeout(apply, 50);
1662
+ });
1663
+ }
1664
+
1527
1665
  /** Send a postMessage to the widget preview iframe (for live property editing) */
1528
1666
  export function sendMessageToPreview(message: Record<string, unknown>): void {
1529
1667
  if (previewWidgetReady && previewIframe?.contentWindow) {
1530
1668
  previewIframe.contentWindow.postMessage(message, '*');
1669
+ applyRichTextInPreviewIframe(message);
1531
1670
  return;
1532
1671
  }
1533
1672
  pendingPreviewMessages.push(message);
@@ -1869,9 +2008,15 @@
1869
2008
  }
1870
2009
  } else {
1871
2010
  previewHydrating = false;
1872
- previewWidgetReady = true;
1873
2011
  dispatch('previewReady', { widgetId: selectedWidgetId, step: currentStep });
1874
- // Create step: iframe load does not go through markPreviewWidgetReady still fit-to-area.
2012
+ // Create: wait for widgetic:ready so contentItems overlay is not sent before the widget listens.
2013
+ if (!previewWidgetReady) {
2014
+ if (previewReadyFallbackTimeout) clearTimeout(previewReadyFallbackTimeout);
2015
+ previewReadyFallbackTimeout = setTimeout(() => {
2016
+ console.warn('[WidgetDetails] Create step widgetic:ready fallback — applying overlay');
2017
+ markPreviewWidgetReady();
2018
+ }, 1500);
2019
+ }
1875
2020
  scheduleFitPreviewToArea();
1876
2021
  }
1877
2022
  }
@@ -2043,20 +2188,30 @@
2043
2188
  // ═══════════════════════════════════════════════════════════════════════
2044
2189
 
2045
2190
  function getDefaultDetailsPanelHeight(): number {
2046
- if (typeof window === 'undefined') return 600;
2047
- return window.innerHeight - detailsPanelTopOffset - detailsPanelBottomMargin;
2191
+ if (typeof window === 'undefined') return DETAILS_PANEL_MAX_HEIGHT;
2192
+ const available = window.innerHeight - detailsPanelTopOffset - detailsPanelBottomMargin;
2193
+ return Math.max(DETAILS_PANEL_MIN_HEIGHT, Math.min(DETAILS_PANEL_MAX_HEIGHT, available));
2048
2194
  }
2049
2195
 
2050
2196
  function clampPanelWidth(w: number): number {
2051
- if (typeof window === 'undefined') return w;
2052
- const maxWidth = window.innerWidth - 40;
2197
+ if (typeof window === 'undefined') return Math.min(DETAILS_PANEL_MAX_WIDTH, w);
2198
+ const maxWidth = Math.min(DETAILS_PANEL_MAX_WIDTH, window.innerWidth - 24);
2053
2199
  return Math.max(DETAILS_PANEL_MIN_WIDTH, Math.min(w, maxWidth));
2054
2200
  }
2055
2201
 
2202
+ function clampPanelHeight(h: number): number {
2203
+ if (typeof window === 'undefined') return Math.min(DETAILS_PANEL_MAX_HEIGHT, h);
2204
+ const available = window.innerHeight - detailsPanelTopOffset - 16;
2205
+ return Math.max(
2206
+ DETAILS_PANEL_MIN_HEIGHT,
2207
+ Math.min(h, Math.min(DETAILS_PANEL_MAX_HEIGHT, available)),
2208
+ );
2209
+ }
2210
+
2056
2211
  function resetDetailsPanelSize() {
2057
2212
  if (typeof window === 'undefined') {
2058
2213
  detailsPanelWidth = DETAILS_PANEL_DEFAULT_WIDTH;
2059
- detailsPanelHeight = 0;
2214
+ detailsPanelHeight = DETAILS_PANEL_MAX_HEIGHT;
2060
2215
  return;
2061
2216
  }
2062
2217
  if (fillViewport) {
@@ -2071,7 +2226,7 @@
2071
2226
  return;
2072
2227
  }
2073
2228
  detailsPanelWidth = clampPanelWidth(DETAILS_PANEL_DEFAULT_WIDTH);
2074
- detailsPanelHeight = 0;
2229
+ detailsPanelHeight = getDefaultDetailsPanelHeight();
2075
2230
  }
2076
2231
 
2077
2232
  export function centerDetailsPanel() {
@@ -2083,10 +2238,93 @@
2083
2238
  return;
2084
2239
  }
2085
2240
  const cascade = panelCascadeIndex * 32;
2086
- detailsPanelX = Math.max(0, Math.round((window.innerWidth - detailsPanelWidth) / 2) + cascade);
2241
+ detailsPanelX = Math.max(8, Math.round((window.innerWidth - detailsPanelWidth) / 2) + cascade);
2087
2242
  detailsPanelY = detailsPanelTopOffset + cascade;
2088
2243
  }
2089
2244
 
2245
+ /** Keep the panel on-screen after a browser resize. Does not reset to default 600×800. */
2246
+ export function keepPanelInViewport() {
2247
+ if (typeof window === 'undefined') return;
2248
+ if (fillViewport) {
2249
+ resetDetailsPanelSize();
2250
+ detailsPanelX = DETAILS_PANEL_FILL_INSET;
2251
+ detailsPanelY = detailsPanelTopOffset;
2252
+ return;
2253
+ }
2254
+ const maxW = Math.max(DETAILS_PANEL_MIN_WIDTH, window.innerWidth - 16);
2255
+ const maxH = Math.max(
2256
+ DETAILS_PANEL_MIN_HEIGHT,
2257
+ window.innerHeight - detailsPanelTopOffset - 16,
2258
+ );
2259
+ if (detailsPanelWidth > maxW) detailsPanelWidth = maxW;
2260
+ const currentH = detailsPanelHeight || getDefaultDetailsPanelHeight();
2261
+ if (currentH > maxH) detailsPanelHeight = maxH;
2262
+ const pos = clampDetailsPanelPosition(detailsPanelX, detailsPanelY);
2263
+ detailsPanelX = pos.x;
2264
+ detailsPanelY = pos.y;
2265
+ }
2266
+
2267
+ /**
2268
+ * Park the panel beside a canvas widget shape: right if the shape is left of
2269
+ * the canvas viewport center, otherwise left. Caps at 600×800 (or smaller).
2270
+ */
2271
+ export function positionBesideShape(shapeRect: {
2272
+ left: number;
2273
+ top: number;
2274
+ width: number;
2275
+ height: number;
2276
+ viewportLeft: number;
2277
+ viewportTop: number;
2278
+ viewportWidth: number;
2279
+ viewportHeight: number;
2280
+ } | null): void {
2281
+ if (typeof window === 'undefined') return;
2282
+ resetDetailsPanelSize();
2283
+ _hasBeenOpenedOnce = true;
2284
+ if (!shapeRect) {
2285
+ centerDetailsPanel();
2286
+ console.log('[WidgetDetails] No shape rect — centered floating panel', detailsPanelWidth, detailsPanelHeight);
2287
+ return;
2288
+ }
2289
+
2290
+ const pad = 8;
2291
+ const viewLeft = shapeRect.viewportLeft;
2292
+ const viewRight = shapeRect.viewportLeft + shapeRect.viewportWidth;
2293
+ const availableWidth = Math.max(DETAILS_PANEL_MIN_WIDTH, shapeRect.viewportWidth - pad * 2);
2294
+ detailsPanelWidth = clampPanelWidth(Math.min(DETAILS_PANEL_MAX_WIDTH, availableWidth));
2295
+ detailsPanelHeight = clampPanelHeight(
2296
+ Math.min(DETAILS_PANEL_MAX_HEIGHT, shapeRect.viewportHeight - pad * 2),
2297
+ );
2298
+
2299
+ const shapeCenterX = shapeRect.left + shapeRect.width / 2;
2300
+ const viewCenterX = shapeRect.viewportLeft + shapeRect.viewportWidth / 2;
2301
+ const openOnRight = shapeCenterX < viewCenterX;
2302
+ if (openOnRight) {
2303
+ detailsPanelX = shapeRect.left + shapeRect.width + DETAILS_PANEL_SHAPE_GAP;
2304
+ if (detailsPanelX + detailsPanelWidth > viewRight - pad) {
2305
+ detailsPanelX = Math.max(viewLeft + pad, viewRight - pad - detailsPanelWidth);
2306
+ }
2307
+ } else {
2308
+ detailsPanelX = shapeRect.left - DETAILS_PANEL_SHAPE_GAP - detailsPanelWidth;
2309
+ if (detailsPanelX < viewLeft + pad) {
2310
+ detailsPanelX = viewLeft + pad;
2311
+ }
2312
+ }
2313
+
2314
+ const maxY = window.innerHeight - detailsPanelHeight - pad;
2315
+ detailsPanelY = Math.min(
2316
+ maxY,
2317
+ Math.max(detailsPanelTopOffset, shapeRect.top),
2318
+ );
2319
+ console.log('[WidgetDetails] Positioned beside shape', {
2320
+ openOnRight,
2321
+ x: Math.round(detailsPanelX),
2322
+ y: Math.round(detailsPanelY),
2323
+ w: detailsPanelWidth,
2324
+ h: detailsPanelHeight,
2325
+ });
2326
+ }
2327
+
2090
2328
  function startColumnResize(e: MouseEvent) {
2091
2329
  e.preventDefault();
2092
2330
  e.stopPropagation();
@@ -2105,10 +2343,7 @@
2105
2343
  const width = twoCol?.clientWidth || detailsPanelWidth || DETAILS_PANEL_DEFAULT_WIDTH;
2106
2344
  if (width <= 0) return;
2107
2345
  const deltaPercent = ((e.clientX - columnResizeStartX) / width) * 100;
2108
- leftColPercent = Math.min(
2109
- LEFT_COL_MAX_PERCENT,
2110
- Math.max(LEFT_COL_MIN_PERCENT, columnResizeStartPercent + deltaPercent)
2111
- );
2346
+ leftColPercent = clampLeftColPercent(columnResizeStartPercent + deltaPercent);
2112
2347
  // Keep preview iframe inside the shrinking right column (avoid clipped "cut off" widgets).
2113
2348
  requestAnimationFrame(() => {
2114
2349
  clampPreviewToArea();
@@ -2134,7 +2369,7 @@
2134
2369
  export function openWidgetDetails() {
2135
2370
  if (showWidgetDetails) return;
2136
2371
  resetDetailsPanelSize();
2137
- if (fillViewport || !_hasBeenOpenedOnce || detailsPanelX < 0 || detailsPanelY < 0) {
2372
+ if (fillViewport || (!_hasBeenOpenedOnce && (detailsPanelX < 0 || detailsPanelY < 0))) {
2138
2373
  centerDetailsPanel();
2139
2374
  _hasBeenOpenedOnce = true;
2140
2375
  }
@@ -2152,7 +2387,6 @@
2152
2387
  if (selectedWidgetId) {
2153
2388
  if (iframeSrc && previewLoadedForWidgetId === selectedWidgetId) {
2154
2389
  console.log('[WidgetDetails] Reopened with existing preview, no reload needed');
2155
- dispatch('buildFromRepoSuccess', { widgetId: selectedWidgetId, source: 'cached-preview' });
2156
2390
  return;
2157
2391
  }
2158
2392
  loadWidget(selectedWidgetId);
@@ -2162,6 +2396,10 @@
2162
2396
  export function closeWidgetDetails(options?: { silent?: boolean }) {
2163
2397
  showWidgetDetails = false;
2164
2398
  showPreviewModal = false;
2399
+ // Next open should auto-expand preview again if the widget already has code.
2400
+ hasAutoOpenedPreview = false;
2401
+ previewColumnCollapsed = true;
2402
+ widthBeforePreviewExpand = 0;
2165
2403
  if (!options?.silent) {
2166
2404
  dispatch('widgetDetailsToggle', { open: false, showWidgetDetails: false });
2167
2405
  }
@@ -2192,15 +2430,27 @@
2192
2430
  window.removeEventListener('blur', stopFn);
2193
2431
  }
2194
2432
 
2195
- function startDetailsResize(e: MouseEvent) {
2433
+ let detailsResizeEdge: 'se' | 'sw' | 'e' | 'w' | 's' = 'se';
2434
+ let detailsResizeStartPanelX = 0;
2435
+ let detailsResizeStartPanelY = 0;
2436
+
2437
+ function startDetailsResize(e: MouseEvent, edge: 'se' | 'sw' | 'e' | 'w' | 's' = 'se') {
2196
2438
  e.preventDefault();
2197
2439
  e.stopPropagation();
2198
2440
  isResizingDetails = true;
2441
+ detailsResizeEdge = edge;
2199
2442
  detailsResizeStartX = e.clientX;
2200
2443
  detailsResizeStartY = e.clientY;
2201
2444
  detailsResizeStartWidth = detailsPanelWidth;
2202
2445
  detailsResizeStartHeight = detailsPanelHeight || getDefaultDetailsPanelHeight();
2203
- document.body.style.cursor = 'nwse-resize';
2446
+ detailsResizeStartPanelX = detailsPanelX;
2447
+ detailsResizeStartPanelY = detailsPanelY;
2448
+ const cursor =
2449
+ edge === 'e' || edge === 'w' ? 'ew-resize'
2450
+ : edge === 's' ? 'ns-resize'
2451
+ : edge === 'sw' ? 'nesw-resize'
2452
+ : 'nwse-resize';
2453
+ document.body.style.cursor = cursor;
2204
2454
  document.body.style.userSelect = 'none';
2205
2455
  window.addEventListener('mousemove', onDetailsResize);
2206
2456
  addGlobalStopListeners(stopDetailsResize);
@@ -2210,10 +2460,46 @@
2210
2460
  if (!isResizingDetails) return;
2211
2461
  const deltaX = e.clientX - detailsResizeStartX;
2212
2462
  const deltaY = e.clientY - detailsResizeStartY;
2213
- const maxWidth = window.innerWidth - detailsPanelX - 20;
2214
- const maxHeight = window.innerHeight - detailsPanelY - 20;
2215
- detailsPanelWidth = Math.min(maxWidth, Math.max(DETAILS_PANEL_MIN_WIDTH, detailsResizeStartWidth + deltaX));
2216
- detailsPanelHeight = Math.min(maxHeight, Math.max(DETAILS_PANEL_MIN_HEIGHT, detailsResizeStartHeight + deltaY));
2463
+ const minW = DETAILS_PANEL_MIN_WIDTH;
2464
+ const minH = DETAILS_PANEL_MIN_HEIGHT;
2465
+ const maxRight = window.innerWidth - 8;
2466
+ const maxBottom = window.innerHeight - 8;
2467
+
2468
+ let nextW = detailsResizeStartWidth;
2469
+ let nextH = detailsResizeStartHeight;
2470
+ let nextX = detailsResizeStartPanelX;
2471
+ let nextY = detailsResizeStartPanelY;
2472
+
2473
+ if (detailsResizeEdge === 'e' || detailsResizeEdge === 'se') {
2474
+ nextW = detailsResizeStartWidth + deltaX;
2475
+ }
2476
+ if (detailsResizeEdge === 'w' || detailsResizeEdge === 'sw') {
2477
+ nextW = detailsResizeStartWidth - deltaX;
2478
+ nextX = detailsResizeStartPanelX + deltaX;
2479
+ }
2480
+ if (detailsResizeEdge === 's' || detailsResizeEdge === 'se' || detailsResizeEdge === 'sw') {
2481
+ nextH = detailsResizeStartHeight + deltaY;
2482
+ }
2483
+
2484
+ nextW = Math.max(minW, nextW);
2485
+ nextH = Math.max(minH, nextH);
2486
+ if (detailsResizeEdge === 'w' || detailsResizeEdge === 'sw') {
2487
+ nextX = detailsResizeStartPanelX + (detailsResizeStartWidth - nextW);
2488
+ if (nextX < 8) {
2489
+ nextW -= (8 - nextX);
2490
+ nextX = 8;
2491
+ }
2492
+ }
2493
+ if (nextX + nextW > maxRight) nextW = maxRight - nextX;
2494
+ if (nextY + nextH > maxBottom) nextH = maxBottom - nextY;
2495
+
2496
+ detailsPanelWidth = nextW;
2497
+ detailsPanelHeight = nextH;
2498
+ detailsPanelX = nextX;
2499
+ detailsPanelY = nextY;
2500
+ if (!previewColumnCollapsed) {
2501
+ leftColPercent = clampLeftColPercent(leftColPercent);
2502
+ }
2217
2503
  }
2218
2504
 
2219
2505
  function stopDetailsResize() {
@@ -2676,11 +2962,11 @@
2676
2962
  {#if selectedWidgetId}
2677
2963
  <!-- svelte-ignore a11y-no-static-element-interactions -->
2678
2964
  <div
2679
- class="widget-details-floating-panel pointer-events-auto fixed overflow-hidden
2965
+ class="widget-details-floating-panel pointer-events-auto fixed
2680
2966
  {showWidgetDetails ? 'flex flex-col' : 'hidden'}
2681
2967
  {fillViewport
2682
- ? 'bg-white rounded-none border-x border-b border-t-0 ring-0 shadow-none'
2683
- : 'bg-white/95 backdrop-blur-sm shadow-2xl rounded-xl border'}
2968
+ ? 'bg-white rounded-none border-x border-b border-t-0 ring-0 shadow-none overflow-hidden'
2969
+ : 'bg-white/95 backdrop-blur-sm shadow-2xl rounded-xl border overflow-visible'}
2684
2970
  {isFocusedPanel
2685
2971
  ? (fillViewport ? 'border-blue-400' : 'border-blue-400 ring-2 ring-blue-200/80')
2686
2972
  : 'border-gray-300 opacity-[0.97]'}"
@@ -2691,19 +2977,18 @@
2691
2977
  <!-- Widget Details Top Bar — title, steps, and close on one row -->
2692
2978
  <div class="widget-details-header-ct flex flex-col shrink-0 bg-gray-100 border-b border-gray-200">
2693
2979
  <div
2694
- class="widget-details-drag-bar relative flex items-center gap-2 px-3 py-1.5 cursor-grab active:cursor-grabbing select-none min-h-[40px] touch-none"
2980
+ class="widget-details-drag-bar relative flex items-center justify-end gap-2 px-3 py-1.5 cursor-grab active:cursor-grabbing select-none min-h-[40px] touch-none flex-nowrap overflow-hidden"
2695
2981
  onpointerdown={startDetailsDrag}
2696
2982
  >
2697
- <h3 class="widget-details-title text-sm font-semibold text-gray-700 truncate min-w-0 max-w-[28%] shrink" title={selectedWidget?.name || 'Widget Details'}>
2698
- {selectedWidget?.name || 'Widget Details'}
2699
- {#if !isFocusedPanel}
2700
- <span class="text-gray-400 font-normal"> · bg</span>
2701
- {/if}
2702
- </h3>
2703
-
2704
- <!-- Step Indicator (inline same row as title) -->
2705
- <div class="widget-details-step-indicator-row flex flex-1 justify-center min-w-0 px-1">
2706
- <div class="widget-details-step-indicator flex items-center gap-1 flex-wrap justify-center">
2983
+ <span
2984
+ class="widget-details-name pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 max-w-[28%] truncate text-sm font-bold text-gray-800"
2985
+ title={selectedWidget?.name || ''}
2986
+ >
2987
+ {selectedWidget?.name || (isDraftConvertPanel ? 'Widget Draft' : 'Widget')}
2988
+ </span>
2989
+ <!-- Step Indicator — centered; name is compact on the left (read-only, no rename) -->
2990
+ <div class="widget-details-step-indicator-row pointer-events-none absolute left-1/2 -translate-x-1/2 flex justify-center px-1">
2991
+ <div class="widget-details-step-indicator pointer-events-auto flex items-center gap-1 flex-nowrap justify-center whitespace-nowrap">
2707
2992
  <!-- svelte-ignore a11y_consider_explicit_label -->
2708
2993
  <button
2709
2994
  type="button"
@@ -2713,7 +2998,7 @@
2713
2998
  ? 'bg-blue-100 text-blue-700 border border-blue-300 cursor-pointer'
2714
2999
  : 'text-gray-600 hover:bg-gray-200 border border-transparent cursor-pointer'}"
2715
3000
  >
2716
- <span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold {currentStep === 'create' ? 'bg-blue-600 text-white' : 'bg-gray-300 text-gray-700'}">1</span>
3001
+ <span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold leading-none {currentStep === 'create' ? 'bg-blue-600 text-white' : 'bg-gray-300 text-gray-700'}">1</span>
2717
3002
  <span class="step-label">Create</span>
2718
3003
  </button>
2719
3004
  <div class="step-connector w-4 h-px {canEditStep ? 'bg-gray-400' : 'bg-gray-200'}"></div>
@@ -2731,7 +3016,7 @@
2731
3016
  ? 'text-gray-600 hover:bg-gray-200 border border-transparent cursor-pointer'
2732
3017
  : 'text-gray-300 border border-transparent cursor-default pointer-events-none'}"
2733
3018
  >
2734
- <span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold {currentStep === 'edit' ? 'bg-blue-600 text-white' : canEditStep ? 'bg-gray-300 text-gray-700' : 'bg-gray-200 text-gray-400'}">2</span>
3019
+ <span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold leading-none {currentStep === 'edit' ? 'bg-blue-600 text-white' : canEditStep ? 'bg-gray-300 text-gray-700' : 'bg-gray-200 text-gray-400'}">2</span>
2735
3020
  <span class="step-label">Edit</span>
2736
3021
  </button>
2737
3022
  <div class="step-connector w-4 h-px {canEmbedStep ? 'bg-gray-400' : 'bg-gray-200'}"></div>
@@ -2749,35 +3034,49 @@
2749
3034
  ? 'text-gray-600 hover:bg-gray-200 border border-transparent cursor-pointer'
2750
3035
  : 'text-gray-300 border border-transparent cursor-default pointer-events-none'}"
2751
3036
  >
2752
- <span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold {currentStep === 'embed' ? 'bg-blue-600 text-white' : canEmbedStep ? 'bg-gray-300 text-gray-700' : 'bg-gray-200 text-gray-400'}">3</span>
3037
+ <span class="step-number inline-flex items-center justify-center w-5 h-5 rounded-full text-[11px] font-bold leading-none {currentStep === 'embed' ? 'bg-blue-600 text-white' : canEmbedStep ? 'bg-gray-300 text-gray-700' : 'bg-gray-200 text-gray-400'}">3</span>
2753
3038
  <span class="step-label">Embed</span>
2754
3039
  </button>
2755
3040
  </div>
2756
3041
  </div>
2757
3042
 
2758
- <div class="widget-details-debug-buttons flex items-center gap-1.5 shrink-0">
3043
+ <div class="widget-details-debug-buttons flex items-center gap-1.5 shrink-0 pr-1">
2759
3044
  <button
2760
3045
  type="button"
2761
- onclick={handleCloseButtonClick}
3046
+ onclick={() => previewColumnCollapsed ? expandPreviewColumn() : collapsePreviewColumn()}
2762
3047
  onmousedown={(e) => e.stopPropagation()}
2763
3048
  onpointerdown={(e) => e.stopPropagation()}
2764
- class="widget-details-close-bt w-6 h-6 flex items-center justify-center rounded-lg hover:bg-gray-200 text-gray-500 hover:text-gray-700 cursor-pointer transition-colors text-sm"
2765
- title="Close widget details"
3049
+ class="widget-details-preview-toggle-bt h-6 min-w-[6.75rem] px-2 flex items-center justify-center rounded-lg text-[11px] font-medium cursor-pointer transition-colors whitespace-nowrap
3050
+ {previewColumnCollapsed
3051
+ ? 'bg-white border border-gray-300 text-gray-600 hover:bg-gray-100'
3052
+ : 'bg-blue-50 border border-blue-300 text-blue-700 hover:bg-blue-100'}"
3053
+ title={previewColumnCollapsed ? 'Open live preview column' : 'Close live preview column'}
2766
3054
  >
2767
-
3055
+ {previewColumnCollapsed ? 'Open Preview' : 'Close Preview'}
2768
3056
  </button>
2769
3057
  </div>
2770
3058
  </div>
2771
3059
  </div>
2772
3060
 
3061
+ <button
3062
+ type="button"
3063
+ onclick={handleCloseButtonClick}
3064
+ onmousedown={(e) => e.stopPropagation()}
3065
+ onpointerdown={(e) => e.stopPropagation()}
3066
+ class="widget-details-close-bt absolute -top-2.5 -right-2.5 z-30 w-7 h-7 flex items-center justify-center rounded-full bg-white border border-gray-300 shadow-md hover:bg-red-50 hover:border-red-300 hover:text-red-600 text-gray-500 cursor-pointer transition-colors text-sm"
3067
+ title="Close widget details"
3068
+ >
3069
+
3070
+ </button>
3071
+
2773
3072
  <!-- Widget Details Content: Two-column layout -->
2774
- <div class="widget-details-content-wrapper relative m-2.5 min-w-0 min-h-0 flex-1">
2775
- <div class="widget-details-two-col flex h-full min-h-0 gap-0">
3073
+ <div class="widget-details-content-wrapper relative m-2.5 min-w-0 min-h-0 flex-1 overflow-x-hidden">
3074
+ <div class="widget-details-two-col flex h-full min-h-0 min-w-0 gap-0 overflow-x-hidden">
2776
3075
 
2777
3076
  <!-- ═══ LEFT COLUMN: Step Content (scrollable) ═══ -->
2778
3077
  <div
2779
3078
  class="widget-details-left-col flex flex-col min-w-0 min-h-0 relative"
2780
- style="width: {leftColPercent}%; flex: 0 0 {leftColPercent}%;"
3079
+ style="width: {previewColumnCollapsed ? 100 : leftColPercent}%; flex: 0 0 {previewColumnCollapsed ? 100 : leftColPercent}%;"
2781
3080
  >
2782
3081
  <div class="widget-details-left-border absolute inset-y-0 left-0 right-0 border border-gray-300 rounded-lg pointer-events-none z-10"></div>
2783
3082
 
@@ -2785,16 +3084,13 @@
2785
3084
  <!-- Sticky Widget Info Bar (always visible above scrollable content) -->
2786
3085
  <div class="widget-info-sticky-bar flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-gray-50 rounded-t-lg shrink-0 gap-2 min-w-0 z-5">
2787
3086
  <div class="widget-info-name flex items-center min-w-0 gap-1 text-sm text-gray-700">
2788
- <EditableName
2789
- name={selectedWidget?.name || ''}
2790
- placeholder="Unnamed"
2791
- tooltipText="Double-click to rename"
2792
- class="flex-1 min-w-0"
2793
- on:rename={({ detail }) => dispatch('widgetRename', { widgetId: selectedWidgetId, newName: detail.newName })}
2794
- />
2795
3087
  <!-- svelte-ignore a11y-click-events-have-key-events -->
2796
3088
  <!-- svelte-ignore a11y-no-static-element-interactions -->
2797
- <span class="ml-1 text-[10px] font-mono text-gray-400 cursor-pointer hover:text-gray-600 shrink-0" title="Click to copy widget ID" onclick={() => { navigator.clipboard.writeText(selectedWidgetId); showToast('success', 'Widget ID copied', { duration: 2000 }); }}>(id: {selectedWidgetId.substring(0, 8)}…)</span>
3089
+ <span
3090
+ class="widget-id-label text-[11px] font-mono text-gray-500 cursor-pointer hover:text-gray-700 truncate"
3091
+ title="Click to copy widget ID"
3092
+ onclick={() => { navigator.clipboard.writeText(selectedWidgetId); showToast('success', 'Widget ID copied', { duration: 2000 }); }}
3093
+ >Widget id: {selectedWidgetId}</span>
2798
3094
  </div>
2799
3095
  <div class="widget-info-meta flex items-center gap-2 shrink-0">
2800
3096
  {#if saveStatus === 'saving'}
@@ -2815,17 +3111,17 @@
2815
3111
 
2816
3112
  <!-- Scrollable step content -->
2817
3113
  <div
2818
- class="widget-details-left-content flex flex-col flex-1 min-h-0 relative rounded-b-lg {currentStep === 'edit' ? 'overflow-hidden' : 'overflow-y-auto'}"
3114
+ class="widget-details-left-content flex flex-col flex-1 min-h-0 relative rounded-b-lg overflow-hidden overflow-x-hidden"
2819
3115
  bind:this={contentContainerEl}
2820
3116
  >
2821
3117
  <div class="actions-panel widget-details-actions-panel relative flex flex-col flex-1 min-h-0">
2822
3118
 
2823
3119
  <!-- Keep all steps mounted — switching tabs must not destroy chat / PropsEditor state -->
2824
- <div class="widget-details-create-step flex flex-col gap-2 p-2 {currentStep === 'create' ? '' : 'hidden'}">
3120
+ <div class="widget-details-create-step flex flex-col flex-1 min-h-0 justify-start overflow-y-auto overflow-x-hidden scrollbar-thin gap-2 p-2 {currentStep === 'create' ? '' : 'hidden'}">
2825
3121
  <slot name="code-generation" />
2826
3122
  <slot name="publish-widget" />
2827
3123
  </div>
2828
- <div class="widget-details-edit-step flex flex-col h-full min-h-0 overflow-hidden {currentStep === 'edit' ? '' : 'hidden'}">
3124
+ <div class="widget-details-edit-step flex flex-col h-full min-h-0 overflow-y-auto overflow-x-hidden scrollbar-thin {currentStep === 'edit' ? '' : 'hidden'}">
2829
3125
  <slot name="composition-editor" />
2830
3126
  </div>
2831
3127
  <div class="widget-details-embed-step flex flex-col gap-2 p-2 {currentStep === 'embed' ? '' : 'hidden'}">
@@ -2838,6 +3134,18 @@
2838
3134
 
2839
3135
  </div> <!-- end of widget-details-left-col -->
2840
3136
 
3137
+ {#if previewColumnCollapsed}
3138
+ <div class="widget-details-preview-expand-strip flex items-center justify-center w-8 shrink-0 border-l border-gray-200 bg-gray-50">
3139
+ <button
3140
+ type="button"
3141
+ onclick={expandPreviewColumn}
3142
+ class="widget-details-preview-expand-bt h-full w-full text-[10px] font-semibold text-gray-500 hover:text-forest-green hover:bg-gray-100 cursor-pointer"
3143
+ title="Show live preview"
3144
+ >
3145
+ <span class="widget-details-preview-expand-label inline-block rotate-180" style="writing-mode: vertical-rl;">Preview</span>
3146
+ </button>
3147
+ </div>
3148
+ {:else}
2841
3149
  <!-- svelte-ignore a11y-no-static-element-interactions -->
2842
3150
  <div
2843
3151
  class="widget-details-col-resizer w-2.5 shrink-0 cursor-col-resize relative z-20 group"
@@ -2947,9 +3255,17 @@
2947
3255
  {#if isGeneratingCode || previewLoading}
2948
3256
  <GenerateLoader
2949
3257
  title={isGeneratingCode ? 'Generating code…' : 'Building preview…'}
3258
+ subtitle=""
2950
3259
  size="md"
2951
3260
  />
2952
- {:else if previewError === 'NO_CODE_YET' && !hasWidgetCode}
3261
+ {:else if previewError === 'DRAFT_PANEL'}
3262
+ <div class="widget-preview-draft-hint flex flex-col items-center justify-center text-center p-4">
3263
+ <div class="text-3xl mb-2">🎨</div>
3264
+ <div class="font-medium text-sm text-gray-700 mb-1">Sketch draft</div>
3265
+ <div class="text-xs max-w-[240px] text-gray-500 mb-1">Your sketch is still on the canvas — keep editing it freely.</div>
3266
+ <div class="text-xs max-w-[240px] text-gray-500">Send the prompt in chat to create the widget and generate code.</div>
3267
+ </div>
3268
+ {:else if previewError === 'NO_CODE_YET' && !hasWidgetCode}
2953
3269
  <div class="widget-preview-no-code flex flex-col items-center justify-center text-center p-4">
2954
3270
  {#if !normalizeRepositoryId(selectedWidget) && repositorySetupStatus !== 'failed'}
2955
3271
  <GenerateLoader
@@ -2991,6 +3307,17 @@
2991
3307
  </div>
2992
3308
  {/if}
2993
3309
  </div>
3310
+ {:else if lastGenerationFailed}
3311
+ <div class="widget-preview-generation-error flex flex-col items-center justify-center text-center p-4">
3312
+ <div class="text-3xl mb-2">⚠️</div>
3313
+ <div class="font-medium text-sm text-red-600 mb-1">Code generation failed</div>
3314
+ <div class="text-xs max-w-[220px] text-gray-600 mb-3">{lastGenerationError || 'Fix the prompt or attachments, then retry.'}</div>
3315
+ <button
3316
+ type="button"
3317
+ onclick={() => { dispatch('generateNow', { widgetId: selectedWidgetId }); }}
3318
+ class="generate-now-btn px-3 py-1.5 text-xs rounded border border-green-500 text-green-700 bg-white hover:bg-green-50 cursor-pointer font-medium"
3319
+ >▶ Retry</button>
3320
+ </div>
2994
3321
  {:else if isLoadingDirectPreviewUrl || previewLoading}
2995
3322
  <GenerateLoader title="Building Preview…" subtitle="Compiling widget code…" size="md" />
2996
3323
  {:else}
@@ -3031,6 +3358,7 @@
3031
3358
 
3032
3359
  </div> <!-- end of widget-details-right-content -->
3033
3360
  </div> <!-- end of widget-details-right-col -->
3361
+ {/if}
3034
3362
 
3035
3363
  </div> <!-- end of widget-details-two-col -->
3036
3364
 
@@ -3052,7 +3380,7 @@
3052
3380
  </div> <!-- end of widget-details-content-wrapper -->
3053
3381
 
3054
3382
  <!-- svelte-ignore a11y-no-static-element-interactions -->
3055
- <div class="absolute bottom-0 right-0 w-4 h-4 cursor-nwse-resize z-10 group" onmousedown={startDetailsResize} title="Resize panel">
3383
+ <div class="widget-details-resize-se absolute bottom-0 right-0 w-4 h-4 cursor-nwse-resize z-20 group" onmousedown={(e) => startDetailsResize(e, 'se')} title="Resize panel">
3056
3384
  <svg class="w-3 h-3 absolute bottom-0.5 right-0.5 text-gray-400 group-hover:text-gray-600 transition-colors" viewBox="0 0 6 6" fill="currentColor">
3057
3385
  <circle cx="5" cy="1" r="0.7"/>
3058
3386
  <circle cx="3" cy="3" r="0.7"/>
@@ -3062,12 +3390,31 @@
3062
3390
  <circle cx="5" cy="5" r="0.7"/>
3063
3391
  </svg>
3064
3392
  </div>
3393
+ {#if !fillViewport}
3394
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
3395
+ <div class="widget-details-resize-sw absolute bottom-0 left-0 w-4 h-4 cursor-nesw-resize z-20 group" onmousedown={(e) => startDetailsResize(e, 'sw')} title="Resize panel">
3396
+ <svg class="w-3 h-3 absolute bottom-0.5 left-0.5 text-gray-400 group-hover:text-gray-600 transition-colors rotate-90" viewBox="0 0 6 6" fill="currentColor">
3397
+ <circle cx="5" cy="1" r="0.7"/>
3398
+ <circle cx="3" cy="3" r="0.7"/>
3399
+ <circle cx="5" cy="3" r="0.7"/>
3400
+ <circle cx="1" cy="5" r="0.7"/>
3401
+ <circle cx="3" cy="5" r="0.7"/>
3402
+ <circle cx="5" cy="5" r="0.7"/>
3403
+ </svg>
3404
+ </div>
3405
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
3406
+ <div class="widget-details-resize-e absolute top-8 bottom-4 right-0 w-1.5 cursor-ew-resize z-20 hover:bg-blue-400/30" onmousedown={(e) => startDetailsResize(e, 'e')} title="Resize width"></div>
3407
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
3408
+ <div class="widget-details-resize-w absolute top-8 bottom-4 left-0 w-1.5 cursor-ew-resize z-20 hover:bg-blue-400/30" onmousedown={(e) => startDetailsResize(e, 'w')} title="Resize width"></div>
3409
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
3410
+ <div class="widget-details-resize-s absolute bottom-0 left-4 right-4 h-1.5 cursor-ns-resize z-20 hover:bg-blue-400/30" onmousedown={(e) => startDetailsResize(e, 's')} title="Resize height"></div>
3411
+ {/if}
3065
3412
  </div>
3066
3413
  {/if}
3067
3414
 
3068
3415
  <style>
3069
3416
  .widget-details-left-content {
3070
- overflow-y: auto;
3417
+ overflow: hidden;
3071
3418
  overflow-x: hidden;
3072
3419
  scrollbar-width: thin;
3073
3420
  scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
@@ -3124,4 +3471,17 @@
3124
3471
  :global(.widget-details-right-content .widget-preview-wrapper) {
3125
3472
  overflow: visible !important;
3126
3473
  }
3474
+
3475
+ .step-number {
3476
+ line-height: 1;
3477
+ }
3478
+
3479
+ /* Tab focus: hide DS/site orange ring inside Widget Details (buttons + links). */
3480
+ .widget-details-floating-panel :global(button:focus),
3481
+ .widget-details-floating-panel :global(button:focus-visible),
3482
+ .widget-details-floating-panel :global(a:focus),
3483
+ .widget-details-floating-panel :global(a:focus-visible) {
3484
+ outline: none !important;
3485
+ box-shadow: none !important;
3486
+ }
3127
3487
  </style>