@amplifyup/sdk 0.1.12 → 0.1.14

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.
@@ -7,7 +7,9 @@ interface AmplifyRendererProps {
7
7
  * Component renderer function that maps componentId to React component
8
8
  * This is site-specific and must be provided by the developer
9
9
  */
10
- renderComponent: (componentId: string, props: Record<string, any>, slots?: Record<string, ReactNode>) => ReactNode;
10
+ renderComponent: (componentId: string, props: Record<string, any>, slots?: Record<string, ReactNode>, context?: {
11
+ layoutNodeId: string;
12
+ }) => ReactNode;
11
13
  /**
12
14
  * Optional page config for server-side rendering (SSR/SSG)
13
15
  * If provided, the component will use this instead of fetching client-side
@@ -7,7 +7,9 @@ interface AmplifyRendererProps {
7
7
  * Component renderer function that maps componentId to React component
8
8
  * This is site-specific and must be provided by the developer
9
9
  */
10
- renderComponent: (componentId: string, props: Record<string, any>, slots?: Record<string, ReactNode>) => ReactNode;
10
+ renderComponent: (componentId: string, props: Record<string, any>, slots?: Record<string, ReactNode>, context?: {
11
+ layoutNodeId: string;
12
+ }) => ReactNode;
11
13
  /**
12
14
  * Optional page config for server-side rendering (SSR/SSG)
13
15
  * If provided, the component will use this instead of fetching client-side
@@ -285,6 +285,32 @@ function resolveAmplifyUpUrls(target) {
285
285
  };
286
286
  }
287
287
 
288
+ // src/preview.ts
289
+ function isPreviewRequest(searchParams) {
290
+ if (searchParams instanceof URLSearchParams) {
291
+ return searchParams.get("preview") === "true" || searchParams.get("visualEditor") === "true";
292
+ }
293
+ const preview = searchParams.preview;
294
+ const visualEditor = searchParams.visualEditor;
295
+ return preview === "true" || visualEditor === "true";
296
+ }
297
+ function isComposerPreview() {
298
+ if (typeof window === "undefined") return false;
299
+ if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) return true;
300
+ const params = new URLSearchParams(window.location.search);
301
+ if (isPreviewRequest(params)) return true;
302
+ if (window.self !== window.top && document.referrer) {
303
+ try {
304
+ const refUrl = new URL(document.referrer);
305
+ if (refUrl.hostname === "amplifyup.ai" || refUrl.hostname.endsWith(".amplifyup.ai") || refUrl.hostname === "localhost" && refUrl.port === "5001" || refUrl.pathname.includes("/dashboard/composer") || refUrl.pathname.includes("/dashboard/routes/")) {
306
+ return true;
307
+ }
308
+ } catch {
309
+ }
310
+ }
311
+ return false;
312
+ }
313
+
288
314
  // src/index.ts
289
315
  function readPreviewQueryOverrides() {
290
316
  if (typeof window === "undefined") return {};
@@ -326,6 +352,15 @@ var AmplifyUpSDK = class {
326
352
  this.messageListener = null;
327
353
  this.mutationObserver = null;
328
354
  this.composerDragHandlers = null;
355
+ /** Last slot highlighted during Composer drag — avoid clear/re-add flicker. */
356
+ this.lastComposerDragSlot = null;
357
+ /** Consecutive miss frames before clearing highlight (anti-jitter). */
358
+ this.composerDragMissFrames = 0;
359
+ /**
360
+ * Parent overlay is driving drag via postMessage. Native iframe dragleave must
361
+ * not clear highlight — overlay takeover fires a spurious document dragleave.
362
+ */
363
+ this.composerOverlayDragActive = false;
329
364
  this.isVisualEditorMode = false;
330
365
  this.eventSelectionMode = false;
331
366
  this.eventSelectionClickHandler = null;
@@ -856,6 +891,12 @@ Available Console Commands:
856
891
  }
857
892
  throw new Error(`Failed to fetch page: ${response.statusText}`);
858
893
  }
894
+ const contentType = response.headers.get("content-type") || "";
895
+ if (!contentType.includes("application/json")) {
896
+ throw new Error(
897
+ `Layout provider returned non-JSON (${contentType || "unknown"}). Check Composer preview routing.`
898
+ );
899
+ }
859
900
  const data = await response.json();
860
901
  const pageConfig = data.page;
861
902
  if (pageConfig?.eventTracking) {
@@ -1140,6 +1181,13 @@ Available Console Commands:
1140
1181
  }
1141
1182
  return;
1142
1183
  }
1184
+ if (event.data?.type === "COMPOSER_DRAG_START") {
1185
+ this.composerOverlayDragActive = true;
1186
+ if (typeof document !== "undefined") {
1187
+ document.body.classList.add("amplifyup-composer-dragging");
1188
+ }
1189
+ return;
1190
+ }
1143
1191
  if (event.data?.type === "COMPOSER_DRAG_AT_POINT") {
1144
1192
  const { x, y } = event.data;
1145
1193
  if (typeof x === "number" && typeof y === "number") {
@@ -1155,36 +1203,72 @@ Available Console Commands:
1155
1203
  return;
1156
1204
  }
1157
1205
  if (event.data?.type === "COMPOSER_DRAG_END") {
1206
+ this.composerOverlayDragActive = false;
1158
1207
  this.clearComposerDragHighlight();
1159
1208
  return;
1160
1209
  }
1161
1210
  if (event.data?.type === "GET_ELEMENT_POSITION") {
1162
- const { componentId, slotName } = event.data;
1163
- const selector = slotName ? `[data-amplifyup-slot="${slotName}"], [data-slot-id="${slotName}"]` : `[data-amplifyup-component="${componentId}"], [data-component-id="${componentId}"]`;
1164
- console.log("[AmplifyUp SDK] Finding element position:", { selector, componentId, slotName });
1165
- const element = document.querySelector(selector);
1211
+ const { componentId, slotName, nodeId, isPageSlot } = event.data;
1212
+ let element = null;
1213
+ if (isPageSlot || slotName === "main" && !nodeId && !componentId) {
1214
+ element = document.querySelector(
1215
+ '[data-slot-source="page"]'
1216
+ );
1217
+ }
1218
+ if (!element && nodeId && slotName) {
1219
+ element = document.querySelector(
1220
+ `[data-parent-node-id="${nodeId}"][data-slot-id="${slotName}"], [data-parent-node-id="${nodeId}"][data-amplifyup-slot="${slotName}"]`
1221
+ );
1222
+ }
1223
+ if (!element && nodeId && !slotName) {
1224
+ element = document.querySelector(
1225
+ `[data-node-id="${nodeId}"]`
1226
+ );
1227
+ }
1228
+ if (!element && slotName) {
1229
+ element = document.querySelector(
1230
+ `[data-amplifyup-slot="${slotName}"], [data-slot-id="${slotName}"]`
1231
+ );
1232
+ }
1233
+ if (!element && componentId) {
1234
+ element = document.querySelector(
1235
+ `[data-amplifyup-component="${componentId}"], [data-component-id="${componentId}"]`
1236
+ );
1237
+ }
1238
+ console.log("[AmplifyUp SDK] Finding element position:", {
1239
+ componentId,
1240
+ slotName,
1241
+ nodeId,
1242
+ isPageSlot,
1243
+ found: !!element
1244
+ });
1166
1245
  if (element) {
1167
1246
  const rect = element.getBoundingClientRect();
1168
1247
  const position = {
1169
1248
  x: rect.left + rect.width / 2,
1170
- // Center of element
1171
1249
  y: rect.top - 50,
1172
- // Position above element
1173
1250
  width: rect.width,
1174
1251
  height: rect.height
1175
1252
  };
1176
- console.log("[AmplifyUp SDK] Element position calculated:", { rect, position });
1253
+ const bounds = {
1254
+ top: rect.top,
1255
+ left: rect.left,
1256
+ width: rect.width,
1257
+ height: rect.height
1258
+ };
1177
1259
  if (window.parent && window.parent !== window) {
1178
1260
  window.parent.postMessage({
1179
1261
  type: "ELEMENT_POSITION",
1180
- componentId,
1181
- slotName,
1182
- position
1262
+ componentId: componentId || null,
1263
+ slotName: slotName || null,
1264
+ nodeId: nodeId || null,
1265
+ isPageSlot: !!isPageSlot,
1266
+ position,
1267
+ bounds
1183
1268
  }, "*");
1184
- console.log("[AmplifyUp SDK] Sent ELEMENT_POSITION message");
1185
1269
  }
1186
1270
  } else {
1187
- console.warn("[AmplifyUp SDK] Element not found for selector:", selector);
1271
+ console.warn("[AmplifyUp SDK] Element not found for position request");
1188
1272
  }
1189
1273
  return;
1190
1274
  }
@@ -1299,37 +1383,148 @@ Available Console Commands:
1299
1383
  el.classList.remove("amplifyup-drag-over-slot");
1300
1384
  });
1301
1385
  document.body.classList.remove("amplifyup-drag-no-drop");
1386
+ document.body.classList.remove("amplifyup-composer-dragging");
1387
+ this.lastComposerDragSlot = null;
1388
+ this.composerDragMissFrames = 0;
1389
+ }
1390
+ /** Re-apply drag-over class only if missing (avoids classList churn / flicker). */
1391
+ ensureDragOverClass(slot) {
1392
+ if (!slot.classList.contains("amplifyup-drag-over-slot")) {
1393
+ slot.classList.add("amplifyup-drag-over-slot");
1394
+ }
1395
+ if (document.body.classList.contains("amplifyup-drag-no-drop")) {
1396
+ document.body.classList.remove("amplifyup-drag-no-drop");
1397
+ }
1302
1398
  }
1303
1399
  /**
1304
- * Hit-test a slot at iframe-local coordinates (Composer overlay drag path).
1305
- * Required for cross-origin drags: the browser blocks native drop inside the iframe.
1400
+ * Composer drag targets the innermost (most specific) slot under the cursor.
1401
+ * e.g. FullWidthGrid's main wins over a page-level main that wraps it.
1306
1402
  */
1307
- handleComposerDragAtPoint(x, y) {
1308
- if (typeof document === "undefined") return;
1403
+ resolveSlotAtPoint(x, y) {
1404
+ if (typeof document === "undefined") return null;
1309
1405
  const slotSelector = "[data-slot-id], [data-amplifyup-slot]";
1310
- this.clearComposerDragHighlight();
1311
- const el = document.elementFromPoint(x, y)?.closest?.(slotSelector);
1312
- if (el) {
1313
- el.classList.add("amplifyup-drag-over-slot");
1314
- } else {
1406
+ const containing = [];
1407
+ document.querySelectorAll(slotSelector).forEach((node) => {
1408
+ const el = node;
1409
+ const r = el.getBoundingClientRect();
1410
+ if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) {
1411
+ containing.push(el);
1412
+ }
1413
+ });
1414
+ if (containing.length === 0) return null;
1415
+ const innermost = containing.filter(
1416
+ (el) => !containing.some((other) => other !== el && el.contains(other))
1417
+ );
1418
+ if (innermost.length === 1) return innermost[0];
1419
+ let best = innermost[0];
1420
+ let bestArea = Infinity;
1421
+ for (const el of innermost) {
1422
+ const r = el.getBoundingClientRect();
1423
+ const area = Math.max(1, r.width) * Math.max(1, r.height);
1424
+ if (area < bestArea) {
1425
+ bestArea = area;
1426
+ best = el;
1427
+ }
1428
+ }
1429
+ return best;
1430
+ }
1431
+ /** Parent layout node that owns this slot (null for page-level code main). */
1432
+ parentComponentForSlot(slotEl) {
1433
+ let componentId = slotEl.getAttribute("data-parent-component-id");
1434
+ let nodeId = slotEl.getAttribute("data-parent-node-id");
1435
+ let el = slotEl.parentElement;
1436
+ while (el && (!nodeId || !componentId)) {
1437
+ if (!nodeId && el.hasAttribute("data-node-id")) {
1438
+ nodeId = el.getAttribute("data-node-id");
1439
+ }
1440
+ if (!componentId) {
1441
+ componentId = el.getAttribute("data-amplifyup-component") || el.getAttribute("data-component-id");
1442
+ }
1443
+ if (nodeId) break;
1444
+ el = el.parentElement;
1445
+ }
1446
+ return { componentId, nodeId };
1447
+ }
1448
+ pointInRect(x, y, el, pad = 8) {
1449
+ const r = el.getBoundingClientRect();
1450
+ return x >= r.left - pad && x <= r.right + pad && y >= r.top - pad && y <= r.bottom + pad;
1451
+ }
1452
+ setComposerDragHighlight(slot, x, y) {
1453
+ if (slot && slot === this.lastComposerDragSlot) {
1454
+ this.composerDragMissFrames = 0;
1455
+ this.ensureDragOverClass(slot);
1456
+ return;
1457
+ }
1458
+ if (slot && this.lastComposerDragSlot && this.lastComposerDragSlot.contains(slot) && slot !== this.lastComposerDragSlot) {
1459
+ this.composerDragMissFrames = 0;
1460
+ this.lastComposerDragSlot.classList.remove("amplifyup-drag-over-slot");
1461
+ this.lastComposerDragSlot = slot;
1462
+ this.ensureDragOverClass(slot);
1463
+ return;
1464
+ }
1465
+ if (this.lastComposerDragSlot && typeof x === "number" && typeof y === "number" && this.pointInRect(x, y, this.lastComposerDragSlot)) {
1466
+ if (!slot || slot.contains(this.lastComposerDragSlot) || slot === this.lastComposerDragSlot) {
1467
+ this.composerDragMissFrames = 0;
1468
+ this.ensureDragOverClass(this.lastComposerDragSlot);
1469
+ return;
1470
+ }
1471
+ }
1472
+ if (slot) {
1473
+ this.composerDragMissFrames = 0;
1474
+ if (this.lastComposerDragSlot && this.lastComposerDragSlot !== slot) {
1475
+ this.lastComposerDragSlot.classList.remove("amplifyup-drag-over-slot");
1476
+ }
1477
+ this.lastComposerDragSlot = slot;
1478
+ this.ensureDragOverClass(slot);
1479
+ return;
1480
+ }
1481
+ this.composerDragMissFrames += 1;
1482
+ if (this.lastComposerDragSlot && this.composerDragMissFrames < 8) {
1483
+ this.ensureDragOverClass(this.lastComposerDragSlot);
1484
+ return;
1485
+ }
1486
+ if (this.lastComposerDragSlot) {
1487
+ this.lastComposerDragSlot.classList.remove("amplifyup-drag-over-slot");
1488
+ this.lastComposerDragSlot = null;
1489
+ }
1490
+ if (!document.body.classList.contains("amplifyup-drag-no-drop")) {
1315
1491
  document.body.classList.add("amplifyup-drag-no-drop");
1316
1492
  }
1317
1493
  }
1494
+ handleComposerDragAtPoint(x, y) {
1495
+ this.composerOverlayDragActive = true;
1496
+ if (!document.body.classList.contains("amplifyup-composer-dragging")) {
1497
+ document.body.classList.add("amplifyup-composer-dragging");
1498
+ }
1499
+ this.setComposerDragHighlight(this.resolveSlotAtPoint(x, y), x, y);
1500
+ }
1501
+ postDropOnSlot(slotEl) {
1502
+ const win = window;
1503
+ if (!win.parent || win.parent === win) return;
1504
+ const slotName = slotEl.getAttribute("data-amplifyup-slot") || slotEl.getAttribute("data-slot-id");
1505
+ if (!slotName) return;
1506
+ const { componentId, nodeId } = this.parentComponentForSlot(slotEl);
1507
+ const isEmpty = slotEl.getAttribute("data-slot-empty") === "true";
1508
+ win.parent.postMessage(
1509
+ {
1510
+ type: "DROP_ON_SLOT",
1511
+ slotName,
1512
+ slotElement: {
1513
+ path: slotEl.getAttribute("data-slot-path"),
1514
+ isEmpty,
1515
+ parentComponentId: componentId,
1516
+ parentNodeId: nodeId
1517
+ }
1518
+ },
1519
+ "*"
1520
+ );
1521
+ }
1318
1522
  handleComposerDropAtPoint(x, y) {
1319
1523
  if (typeof document === "undefined") return;
1320
- const win = window;
1321
- const slotSelector = "[data-slot-id], [data-amplifyup-slot]";
1524
+ const htmlEl = this.resolveSlotAtPoint(x, y);
1322
1525
  this.clearComposerDragHighlight();
1323
- const htmlEl = document.elementFromPoint(x, y)?.closest?.(slotSelector);
1324
- if (!htmlEl || !win.parent || win.parent === win) return;
1325
- const slotName = htmlEl.getAttribute("data-amplifyup-slot") || htmlEl.getAttribute("data-slot-id");
1326
- if (!slotName) return;
1327
- const isEmpty = htmlEl.getAttribute("data-slot-empty") === "true";
1328
- win.parent.postMessage({
1329
- type: "DROP_ON_SLOT",
1330
- slotName,
1331
- slotElement: { path: htmlEl.getAttribute("data-slot-path"), isEmpty }
1332
- }, "*");
1526
+ if (!htmlEl) return;
1527
+ this.postDropOnSlot(htmlEl);
1333
1528
  }
1334
1529
  /**
1335
1530
  * Enable component highlighting
@@ -1357,35 +1552,25 @@ Available Console Commands:
1357
1552
  this.teardownComposerDragDrop();
1358
1553
  const slotSelector = "[data-slot-id], [data-amplifyup-slot]";
1359
1554
  const clearDragOverState = () => {
1360
- document.querySelectorAll(`${slotSelector}.amplifyup-drag-over-slot`).forEach((el) => {
1361
- el.classList.remove("amplifyup-drag-over-slot");
1362
- });
1363
- document.body.classList.remove("amplifyup-drag-no-drop");
1555
+ this.clearComposerDragHighlight();
1364
1556
  };
1365
1557
  const dragenter = (e) => {
1366
1558
  e.preventDefault();
1367
- if (e.dataTransfer?.types?.length) {
1368
- document.body.classList.add("amplifyup-drag-no-drop");
1369
- }
1370
1559
  };
1371
1560
  const dragover = (e) => {
1372
1561
  e.preventDefault();
1373
- const target = e.target;
1374
- const slot = target?.closest?.(slotSelector);
1562
+ if (this.composerOverlayDragActive) {
1563
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
1564
+ return;
1565
+ }
1566
+ const slot = this.resolveSlotAtPoint(e.clientX, e.clientY);
1375
1567
  if (e.dataTransfer) {
1376
1568
  e.dataTransfer.dropEffect = slot ? "copy" : "none";
1377
1569
  }
1378
- document.querySelectorAll(`${slotSelector}.amplifyup-drag-over-slot`).forEach((el) => {
1379
- el.classList.remove("amplifyup-drag-over-slot");
1380
- });
1381
- if (slot) {
1382
- slot.classList.add("amplifyup-drag-over-slot");
1383
- document.body.classList.remove("amplifyup-drag-no-drop");
1384
- } else {
1385
- document.body.classList.add("amplifyup-drag-no-drop");
1386
- }
1570
+ this.setComposerDragHighlight(slot, e.clientX, e.clientY);
1387
1571
  };
1388
1572
  const dragleave = (e) => {
1573
+ if (this.composerOverlayDragActive) return;
1389
1574
  const related = e.relatedTarget;
1390
1575
  if (!related || !document.body.contains(related)) {
1391
1576
  clearDragOverState();
@@ -1393,20 +1578,10 @@ Available Console Commands:
1393
1578
  };
1394
1579
  const drop = (e) => {
1395
1580
  e.preventDefault();
1396
- const target = e.target;
1397
- const slot = target?.closest?.(slotSelector);
1581
+ const slot = this.resolveSlotAtPoint(e.clientX, e.clientY);
1398
1582
  clearDragOverState();
1399
1583
  if (!slot || !this.isVisualEditorMode) return;
1400
- const slotName = slot.getAttribute("data-amplifyup-slot") || slot.getAttribute("data-slot-id");
1401
- const isEmpty = slot.getAttribute("data-slot-empty") === "true";
1402
- const win = window;
1403
- if (slotName && win.parent && win.parent !== win) {
1404
- win.parent.postMessage({
1405
- type: "DROP_ON_SLOT",
1406
- slotName,
1407
- slotElement: { path: slot.getAttribute("data-slot-path"), isEmpty }
1408
- }, "*");
1409
- }
1584
+ this.postDropOnSlot(slot);
1410
1585
  };
1411
1586
  const mouseover = (e) => {
1412
1587
  if (!this.showZonesEnabled) return;
@@ -1445,7 +1620,11 @@ Available Console Commands:
1445
1620
  document.removeEventListener("mouseover", h.mouseover, true);
1446
1621
  document.removeEventListener("mouseout", h.mouseout, true);
1447
1622
  this.composerDragHandlers = null;
1623
+ this.composerOverlayDragActive = false;
1624
+ this.lastComposerDragSlot = null;
1625
+ this.composerDragMissFrames = 0;
1448
1626
  document.body.classList.remove("amplifyup-drag-no-drop");
1627
+ document.body.classList.remove("amplifyup-composer-dragging");
1449
1628
  document.querySelectorAll("[data-slot-id].amplifyup-drag-over-slot, [data-amplifyup-slot].amplifyup-drag-over-slot").forEach((el) => {
1450
1629
  el.classList.remove("amplifyup-drag-over-slot");
1451
1630
  });
@@ -1486,21 +1665,32 @@ Available Console Commands:
1486
1665
  '[data-slot-id="main"], [data-amplifyup-slot="main"] {',
1487
1666
  " min-height: 50vh !important;",
1488
1667
  "}",
1489
- '[data-slot-id="main"][data-slot-empty="true"]::before,',
1490
- '[data-amplifyup-slot="main"][data-slot-empty="true"]::before {',
1491
- ' content: "Drop components here" !important;',
1492
- " position: absolute !important;",
1493
- " top: 50% !important; left: 50% !important;",
1494
- " transform: translate(-50%, -50%) !important;",
1495
- " font-size: 14px !important; font-weight: 500 !important;",
1496
- " color: rgba(34, 197, 94, 0.65) !important;",
1668
+ /* Single empty-state hint — Slot React label uses .amplifyup-slot-placeholder */
1669
+ ".amplifyup-slot-placeholder {",
1670
+ " display: flex !important;",
1671
+ " align-items: center !important;",
1672
+ " justify-content: center !important;",
1673
+ " min-height: inherit !important;",
1674
+ " color: rgba(34, 197, 94, 0.75) !important;",
1675
+ " font-size: 14px !important;",
1676
+ " font-weight: 500 !important;",
1497
1677
  " pointer-events: none !important;",
1498
- " white-space: nowrap !important;",
1678
+ " text-align: center !important;",
1499
1679
  "}",
1500
1680
  "[data-slot-id].amplifyup-slot-hover, [data-amplifyup-slot].amplifyup-slot-hover {",
1501
1681
  " outline-color: rgba(34, 197, 94, 1) !important;",
1502
1682
  " background: rgba(34, 197, 94, 0.06) !important;",
1503
1683
  "}",
1684
+ /* During any Composer drag, hide idle slot labels so they don't flash
1685
+ when the active drag-over class briefly changes */
1686
+ "body.amplifyup-composer-dragging .amplifyup-slot-placeholder {",
1687
+ " visibility: hidden !important;",
1688
+ " opacity: 0 !important;",
1689
+ "}",
1690
+ "body.amplifyup-composer-dragging [data-slot-id]::before,",
1691
+ "body.amplifyup-composer-dragging [data-amplifyup-slot]::before {",
1692
+ " content: none !important;",
1693
+ "}",
1504
1694
  "[data-slot-id].amplifyup-drag-over-slot, [data-amplifyup-slot].amplifyup-drag-over-slot {",
1505
1695
  " outline: 2px solid rgba(34, 197, 94, 1) !important;",
1506
1696
  " background: rgba(34, 197, 94, 0.18) !important;",
@@ -1516,7 +1706,7 @@ Available Console Commands:
1516
1706
  " color: rgb(22, 163, 74) !important;",
1517
1707
  " pointer-events: none !important;",
1518
1708
  " white-space: nowrap !important;",
1519
- " z-index: 1 !important;",
1709
+ " z-index: 2 !important;",
1520
1710
  "}",
1521
1711
  "body.amplifyup-drag-no-drop { cursor: no-drop !important; }",
1522
1712
  "body.amplifyup-drag-no-drop::before {",
@@ -1643,18 +1833,23 @@ Available Console Commands:
1643
1833
  sendSelectionMessage(element, hierarchy, isEmpty) {
1644
1834
  if (!this.isVisualEditorMode) return;
1645
1835
  const componentId = element.getAttribute("data-amplifyup-component") || element.getAttribute("data-component-id");
1646
- const slotName = element.getAttribute("data-amplifyup-slot") || element.getAttribute("data-slot-id");
1836
+ const slotName = componentId ? null : element.getAttribute("data-amplifyup-slot") || element.getAttribute("data-slot-id");
1647
1837
  const componentName = element.getAttribute("data-component-name");
1648
1838
  const hierarchyInfo = hierarchy.map((el, index) => {
1649
1839
  const elComponentId = el.getAttribute("data-amplifyup-component") || el.getAttribute("data-component-id");
1650
1840
  const elSlotName = el.getAttribute("data-amplifyup-slot") || el.getAttribute("data-slot-id");
1651
1841
  const elComponentName = el.getAttribute("data-component-name");
1842
+ const isSlot = !elComponentId && !!elSlotName;
1843
+ const isPageSlot = isSlot && el.getAttribute("data-slot-source") === "page";
1844
+ const nodeId = el.getAttribute("data-node-id") || (isSlot ? el.getAttribute("data-parent-node-id") : null);
1652
1845
  return {
1653
1846
  index,
1654
1847
  componentId: elComponentId,
1655
- componentName: elComponentName,
1656
- slotName: elSlotName,
1657
- isSlot: !!elSlotName
1848
+ componentName: isPageSlot ? "Page" : elComponentName,
1849
+ slotName: isSlot ? elSlotName : null,
1850
+ isSlot,
1851
+ isPageSlot,
1852
+ nodeId
1658
1853
  };
1659
1854
  });
1660
1855
  const rect = element.getBoundingClientRect ? element.getBoundingClientRect() : null;
@@ -1691,7 +1886,6 @@ Available Console Commands:
1691
1886
  return;
1692
1887
  }
1693
1888
  const doc = document;
1694
- const win = window;
1695
1889
  if (highlightComponents) {
1696
1890
  const componentElements = doc.querySelectorAll("[data-amplifyup-component], [data-component-id]");
1697
1891
  console.log("[AmplifyUp SDK] Found", componentElements.length, "components to highlight");
@@ -1752,52 +1946,8 @@ Available Console Commands:
1752
1946
  e.stopPropagation();
1753
1947
  this.sendSelectionMessage(htmlEl, hierarchy, isEmpty);
1754
1948
  };
1755
- const handleDrop = (e) => {
1756
- e.preventDefault();
1757
- e.stopPropagation();
1758
- htmlEl.classList.remove("amplifyup-drag-over-slot");
1759
- if (!win.parent || win.parent === win) return;
1760
- win.parent.postMessage({
1761
- type: "DROP_ON_SLOT",
1762
- slotName,
1763
- slotElement: { path: htmlEl.getAttribute("data-slot-path"), isEmpty }
1764
- }, "*");
1765
- try {
1766
- const data = JSON.parse(e.dataTransfer.getData("application/json"));
1767
- if (data.type === "component") {
1768
- win.parent.postMessage({
1769
- type: "COMPONENT_DROPPED",
1770
- componentId: data.componentId,
1771
- componentName: data.componentName,
1772
- slotName,
1773
- slotElement: { path: htmlEl.getAttribute("data-slot-path"), isEmpty }
1774
- }, "*");
1775
- }
1776
- } catch (_err) {
1777
- }
1778
- };
1779
- const handleDragOver = (e) => {
1780
- e.preventDefault();
1781
- e.stopPropagation();
1782
- e.dataTransfer.dropEffect = "copy";
1783
- htmlEl.classList.add("amplifyup-drag-over-slot");
1784
- };
1785
- const handleDragLeave = (e) => {
1786
- const related = e.relatedTarget;
1787
- if (!related || !htmlEl.contains(related)) {
1788
- htmlEl.classList.remove("amplifyup-drag-over-slot");
1789
- }
1790
- };
1791
1949
  htmlEl.addEventListener("click", handleClick);
1792
- htmlEl.addEventListener("dragover", handleDragOver);
1793
- htmlEl.addEventListener("dragleave", handleDragLeave);
1794
- htmlEl.addEventListener("drop", handleDrop);
1795
- htmlEl._amplifyupSlotHandlers = {
1796
- handleClick,
1797
- handleDragOver,
1798
- handleDragLeave,
1799
- handleDrop
1800
- };
1950
+ htmlEl._amplifyupSlotHandlers = { handleClick };
1801
1951
  });
1802
1952
  }
1803
1953
  }
@@ -1911,7 +2061,9 @@ function AmplifyRenderer({
1911
2061
  if (!pageConfig) {
1912
2062
  return;
1913
2063
  }
1914
- if (!pageConfig.layoutTree || Array.isArray(pageConfig.layoutTree) && pageConfig.layoutTree.length === 0) {
2064
+ const rawTree2 = pageConfig.layoutTree;
2065
+ const normalizedTree = Array.isArray(rawTree2) ? rawTree2 : rawTree2 && typeof rawTree2 === "object" && rawTree2.componentId ? [rawTree2] : [];
2066
+ if (normalizedTree.length === 0) {
1915
2067
  if (typeof window !== "undefined" && window.parent && window.parent !== window) {
1916
2068
  window.parent.postMessage({
1917
2069
  type: "COMPONENT_TREE",
@@ -1923,6 +2075,7 @@ function AmplifyRenderer({
1923
2075
  const convertLayoutTreeToComponentTree = (nodes, parentId = null) => {
1924
2076
  const result = [];
1925
2077
  nodes.forEach((node) => {
2078
+ if (!node) return;
1926
2079
  const treeNode = {
1927
2080
  id: node.id,
1928
2081
  componentId: node.componentId,
@@ -1933,8 +2086,9 @@ function AmplifyRenderer({
1933
2086
  };
1934
2087
  if (node.slots && node.slots.length > 0) {
1935
2088
  node.slots.forEach((slot) => {
1936
- if (slot.children && slot.children.length > 0) {
1937
- const slotChildren = convertLayoutTreeToComponentTree(slot.children, node.id);
2089
+ const kids = Array.isArray(slot?.children) ? slot.children : [];
2090
+ if (kids.length > 0) {
2091
+ const slotChildren = convertLayoutTreeToComponentTree(kids, node.id);
1938
2092
  treeNode.children.push(...slotChildren);
1939
2093
  }
1940
2094
  });
@@ -1943,7 +2097,7 @@ function AmplifyRenderer({
1943
2097
  });
1944
2098
  return result;
1945
2099
  };
1946
- const layoutTree2 = pageConfig.layoutTree;
2100
+ const layoutTree2 = normalizedTree;
1947
2101
  const componentTree = convertLayoutTreeToComponentTree(layoutTree2);
1948
2102
  const describeLayoutTree = (nodes, depth = 0) => {
1949
2103
  if (!nodes?.length) return "";
@@ -2116,18 +2270,21 @@ function AmplifyRenderer({
2116
2270
  childrenCount: childrenToRender.length
2117
2271
  });
2118
2272
  } else {
2119
- childrenToRender = slot.children;
2273
+ childrenToRender = Array.isArray(slot.children) ? slot.children : [];
2120
2274
  console.log(`[AmplifyRenderer] Using direct children for slot ${slot.slotId}`, {
2121
2275
  childrenCount: childrenToRender.length
2122
2276
  });
2123
2277
  }
2278
+ const safeChildren = Array.isArray(childrenToRender) ? childrenToRender : [];
2124
2279
  slotChildren[slot.slotId] = /* @__PURE__ */ jsxRuntime.jsx(
2125
2280
  "div",
2126
2281
  {
2127
2282
  "data-slot-id": slot.slotId,
2128
2283
  "data-amplifyup-slot": slot.slotId,
2129
2284
  "data-zone-id": slot.zoneId || void 0,
2130
- children: childrenToRender.map((child) => renderNode(child))
2285
+ "data-parent-node-id": node.id,
2286
+ "data-parent-component-id": node.componentId,
2287
+ children: safeChildren.filter(Boolean).map((child) => renderNode(child))
2131
2288
  },
2132
2289
  slot.slotId
2133
2290
  );
@@ -2142,17 +2299,25 @@ function AmplifyRenderer({
2142
2299
  hasSlots: !!slotsToPass,
2143
2300
  slotKeys: slotsToPass ? Object.keys(slotsToPass) : []
2144
2301
  });
2145
- return renderComponent(
2146
- node.componentId,
2147
- node.props || {},
2148
- slotsToPass
2149
- );
2302
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-node-id": node.id, "data-amplifyup-node": "", children: renderComponent(node.componentId, node.props || {}, slotsToPass, {
2303
+ layoutNodeId: node.id
2304
+ }) });
2150
2305
  };
2151
- const layoutTree = pageConfig.layoutTree;
2152
- if (!layoutTree || layoutTree.length === 0) {
2306
+ const rawTree = pageConfig.layoutTree;
2307
+ const layoutTree = Array.isArray(rawTree) ? rawTree : rawTree && typeof rawTree === "object" && rawTree.componentId ? [rawTree] : [];
2308
+ if (layoutTree.length === 0) {
2153
2309
  return /* @__PURE__ */ jsxRuntime.jsx("main", { children: emptyComponent || /* @__PURE__ */ jsxRuntime.jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", children: "No content configured for this page." }) }) });
2154
2310
  }
2155
- return /* @__PURE__ */ jsxRuntime.jsx("main", { children: layoutTree.map((node) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderNode(node) }, node.id)) });
2311
+ return /* @__PURE__ */ jsxRuntime.jsx("main", { children: isComposerPreview() ? /* @__PURE__ */ jsxRuntime.jsx(
2312
+ "div",
2313
+ {
2314
+ "data-slot-id": "main",
2315
+ "data-amplifyup-slot": "main",
2316
+ "data-slot-source": "page",
2317
+ "data-slot-empty": layoutTree.length === 0 ? "true" : void 0,
2318
+ children: layoutTree.map((node) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderNode(node) }, node.id))
2319
+ }
2320
+ ) : layoutTree.map((node) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderNode(node) }, node.id)) });
2156
2321
  }
2157
2322
 
2158
2323
  exports.AmplifyRenderer = AmplifyRenderer;