@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.
- package/dist/CreatorApp.svelte +1876 -518
- package/dist/CreatorApp.svelte.d.ts +1 -0
- package/dist/components/EditableName.svelte +2 -2
- package/dist/components/WidgetDetails.svelte +431 -71
- package/dist/components/WidgetDetails.svelte.d.ts +22 -1
- package/dist/creator-types.d.ts +6 -0
- package/dist/utils/operationStream.js +20 -1
- package/package.json +15 -15
|
@@ -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 =
|
|
855
|
-
const
|
|
856
|
-
const
|
|
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
|
|
1111
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
2047
|
-
|
|
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 -
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2214
|
-
const
|
|
2215
|
-
|
|
2216
|
-
|
|
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
|
|
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
|
-
<
|
|
2698
|
-
|
|
2699
|
-
{
|
|
2700
|
-
|
|
2701
|
-
{
|
|
2702
|
-
</
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
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={
|
|
3046
|
+
onclick={() => previewColumnCollapsed ? expandPreviewColumn() : collapsePreviewColumn()}
|
|
2762
3047
|
onmousedown={(e) => e.stopPropagation()}
|
|
2763
3048
|
onpointerdown={(e) => e.stopPropagation()}
|
|
2764
|
-
class="widget-details-
|
|
2765
|
-
|
|
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
|
|
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
|
|
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 === '
|
|
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-
|
|
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
|
|
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>
|