@amplifyup/sdk 0.1.61 → 0.1.63

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/react.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use client';
2
- import React, { createContext, memo, useState, useEffect, Fragment as Fragment$1, useContext, Component, useRef, useSyncExternalStore } from 'react';
3
- import { marked } from 'marked';
2
+ import React2, { createContext, memo, useState, useEffect, Fragment as Fragment$1, useContext, Component, useMemo, useRef, useSyncExternalStore, useCallback } from 'react';
4
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
+ import { marked } from 'marked';
5
5
 
6
6
  /* @amplifyup/sdk - https://amplifyup.ai */
7
7
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -405,6 +405,200 @@ function isComposerPreview() {
405
405
  return false;
406
406
  }
407
407
 
408
+ // src/field-envelope.ts
409
+ var FIELDS_BRAND = Symbol("amplifyup.fields");
410
+ function isBrandedFields(value) {
411
+ return !!value && typeof value === "object" && FIELDS_BRAND in value;
412
+ }
413
+ function brandFields(bag) {
414
+ if (isBrandedFields(bag)) return bag;
415
+ Object.defineProperty(bag, FIELDS_BRAND, {
416
+ value: true,
417
+ enumerable: false,
418
+ configurable: false
419
+ });
420
+ return bag;
421
+ }
422
+ function isFieldEnvelope(value) {
423
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
424
+ const rec = value;
425
+ return typeof rec.name === "string" && "value" in rec && !("url" in rec);
426
+ }
427
+ function isPortableImageShape(value) {
428
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
429
+ return "url" in value;
430
+ }
431
+ function isEntryRefItem(value) {
432
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
433
+ const rec = value;
434
+ return typeof rec.id === "string" && rec.id.trim().length > 0 || typeof rec._id === "string" && rec._id.trim().length > 0 || typeof rec.docId === "string" && rec.docId.trim().length > 0;
435
+ }
436
+ function isRepeaterArray(value) {
437
+ if (!Array.isArray(value) || value.length === 0) return false;
438
+ const sample = value.find((item) => item != null);
439
+ if (sample === void 0) return false;
440
+ if (typeof sample !== "object" || Array.isArray(sample)) return false;
441
+ if (isPortableImageShape(sample)) return false;
442
+ if (isEntryRefItem(sample)) return false;
443
+ if (sample.$$typeof) return false;
444
+ return true;
445
+ }
446
+ function joinPath(prefix, key) {
447
+ return prefix ? `${prefix}.${key}` : key;
448
+ }
449
+ function emptyValue(raw) {
450
+ if (raw === void 0) return null;
451
+ return raw;
452
+ }
453
+ function toFieldEnvelopes(source, prefix = "") {
454
+ const input = source && typeof source === "object" && !Array.isArray(source) ? source : {};
455
+ if (isBrandedFields(input)) {
456
+ return input;
457
+ }
458
+ const out = {};
459
+ for (const [key, raw] of Object.entries(input)) {
460
+ const path = joinPath(prefix, key);
461
+ if (isFieldEnvelope(raw)) {
462
+ const inner = raw.value;
463
+ if (isRepeaterArray(inner)) {
464
+ out[key] = {
465
+ value: envelopeRepeaterItems(inner, raw.name || path),
466
+ name: raw.name || path
467
+ };
468
+ continue;
469
+ }
470
+ out[key] = { value: raw.value ?? null, name: raw.name || path };
471
+ continue;
472
+ }
473
+ if (isRepeaterArray(raw)) {
474
+ out[key] = { value: envelopeRepeaterItems(raw, path), name: path };
475
+ continue;
476
+ }
477
+ if (raw && typeof raw === "object" && !Array.isArray(raw) && !isPortableImageShape(raw) && !raw.$$typeof) {
478
+ out[key] = toFieldEnvelopes(raw, path);
479
+ continue;
480
+ }
481
+ out[key] = { value: emptyValue(raw), name: path };
482
+ }
483
+ return brandFields(out);
484
+ }
485
+ function envelopeRepeaterItems(items, prefix) {
486
+ return items.map((item, i) => {
487
+ const path = joinPath(prefix, String(i));
488
+ if (isBrandedFields(item)) return item;
489
+ if (item && typeof item === "object" && !Array.isArray(item)) {
490
+ return toFieldEnvelopes(item, path);
491
+ }
492
+ return toFieldEnvelopes({}, path);
493
+ });
494
+ }
495
+ function attachFieldEnvelopes(props) {
496
+ const raw = props && typeof props === "object" ? { ...props } : {};
497
+ if (isBrandedFields(raw.fields)) return raw;
498
+ const { fields: existing, ...content } = raw;
499
+ const source = existing && typeof existing === "object" && !isFieldEnvelope(existing) ? existing : content;
500
+ return { ...raw, fields: toFieldEnvelopes(source) };
501
+ }
502
+ function walkLayoutNodes(nodes) {
503
+ if (!Array.isArray(nodes)) return;
504
+ for (const node of nodes) {
505
+ if (!node || typeof node !== "object") continue;
506
+ const rec = node;
507
+ rec.props = attachFieldEnvelopes(rec.props);
508
+ for (const slot of rec.slots || []) {
509
+ walkLayoutNodes(slot.children);
510
+ }
511
+ }
512
+ }
513
+ function envelopePageConfig(page) {
514
+ if (!page || typeof page !== "object") return page ?? null;
515
+ const tree = page.layoutTree;
516
+ if (Array.isArray(tree)) walkLayoutNodes(tree);
517
+ return page;
518
+ }
519
+ function collectFieldPatches(props) {
520
+ const out = [];
521
+ const walkRepeater = (items, prefix) => {
522
+ items.forEach((item, i) => {
523
+ const itemPath = joinPath(prefix, String(i));
524
+ if (isBrandedFields(item) || item && typeof item === "object" && !Array.isArray(item) && !isFieldEnvelope(item)) {
525
+ walk(item, itemPath);
526
+ }
527
+ });
528
+ };
529
+ const walk = (obj, prefix) => {
530
+ for (const [key, raw] of Object.entries(obj)) {
531
+ if (key === "fields" && raw && typeof raw === "object" && !Array.isArray(raw) && !isFieldEnvelope(raw)) {
532
+ walk(raw, prefix);
533
+ continue;
534
+ }
535
+ const path = joinPath(prefix, key);
536
+ if (isFieldEnvelope(raw)) {
537
+ if (isRepeaterArray(raw.value)) {
538
+ walkRepeater(raw.value, raw.name || path);
539
+ continue;
540
+ }
541
+ out.push({ name: raw.name || path, value: raw.value });
542
+ continue;
543
+ }
544
+ if (isRepeaterArray(raw)) {
545
+ walkRepeater(raw, path);
546
+ continue;
547
+ }
548
+ if (raw && typeof raw === "object" && !Array.isArray(raw) && !isPortableImageShape(raw) && !raw.$$typeof) {
549
+ walk(raw, path);
550
+ continue;
551
+ }
552
+ out.push({ name: path, value: emptyValue(raw) });
553
+ }
554
+ };
555
+ if (props && typeof props === "object" && !Array.isArray(props)) {
556
+ walk(props, "");
557
+ }
558
+ return out;
559
+ }
560
+ function unwrapFieldsBag(item) {
561
+ if (!item || typeof item !== "object" || Array.isArray(item)) return {};
562
+ const out = {};
563
+ for (const [key, raw] of Object.entries(item)) {
564
+ if (isFieldEnvelope(raw)) {
565
+ out[key] = raw.value;
566
+ continue;
567
+ }
568
+ if (raw && typeof raw === "object" && !Array.isArray(raw) && !isPortableImageShape(raw) && !isEntryRefItem(raw) && !raw.$$typeof) {
569
+ out[key] = unwrapFieldsBag(raw);
570
+ continue;
571
+ }
572
+ out[key] = raw;
573
+ }
574
+ return out;
575
+ }
576
+ function resolveCollectionItems(field, overlay) {
577
+ const name = typeof field?.name === "string" ? field.name : "";
578
+ const raw = overlay !== void 0 ? overlay : field?.value;
579
+ const list = Array.isArray(raw) ? raw : isFieldEnvelope(raw) && Array.isArray(raw.value) ? raw.value : [];
580
+ if (list.length === 0) return [];
581
+ if (isRepeaterArray(list) || list.every((item) => isBrandedFields(item))) {
582
+ return envelopeRepeaterItems(list, name);
583
+ }
584
+ return envelopeRepeaterItems(
585
+ list.map(
586
+ (item) => item && typeof item === "object" && !Array.isArray(item) ? item : {}
587
+ ),
588
+ name
589
+ );
590
+ }
591
+ function resolveEditableBinding(field, value, name) {
592
+ if (isFieldEnvelope(field)) {
593
+ return { name: field.name, value: field.value, requiresName: false };
594
+ }
595
+ return {
596
+ name: name || "",
597
+ value,
598
+ requiresName: value !== void 0 && !name
599
+ };
600
+ }
601
+
408
602
  // src/page-context.ts
409
603
  function extractRouteParams(pattern, url) {
410
604
  const patternParts = String(pattern || "").split("/").filter(Boolean);
@@ -588,10 +782,34 @@ function hydratePageResourceValue(value, fields) {
588
782
  if (value.path) return getByDotPath(fields, value.path) ?? null;
589
783
  return fields;
590
784
  }
785
+ function hydrateFieldsBag(bag, fields) {
786
+ const out = {};
787
+ for (const [key, val] of Object.entries(bag)) {
788
+ if (val && typeof val === "object" && !Array.isArray(val) && typeof val.name === "string" && "value" in val && !("url" in val)) {
789
+ const env = val;
790
+ out[key] = {
791
+ name: env.name,
792
+ value: isPageResourceSentinel(env.value) ? hydratePageResourceValue(env.value, fields) : env.value
793
+ };
794
+ continue;
795
+ }
796
+ if (val && typeof val === "object" && !Array.isArray(val) && !("url" in val)) {
797
+ out[key] = hydrateFieldsBag(val, fields);
798
+ continue;
799
+ }
800
+ out[key] = isPageResourceSentinel(val) ? hydratePageResourceValue(val, fields) : val;
801
+ }
802
+ return out;
803
+ }
591
804
  function hydratePageResourceProps(props, fields) {
592
805
  const source = props && typeof props === "object" ? props : {};
593
806
  const out = { ...source };
807
+ if (out.fields && typeof out.fields === "object" && !Array.isArray(out.fields)) {
808
+ const next = hydrateFieldsBag(out.fields, fields);
809
+ out.fields = isBrandedFields(out.fields) ? brandFields(next) : next;
810
+ }
594
811
  for (const [key, val] of Object.entries(out)) {
812
+ if (key === "fields") continue;
595
813
  if (isPageResourceSentinel(val)) {
596
814
  out[key] = hydratePageResourceValue(val, fields);
597
815
  }
@@ -728,7 +946,25 @@ async function fetchEdgeResolve(options) {
728
946
  body
729
947
  );
730
948
  }
731
- return body;
949
+ return envelopeResolvedPayload(body);
950
+ }
951
+ function envelopeResolvedComponent(node) {
952
+ node.props = attachFieldEnvelopes(node.props);
953
+ if (!node.children) return;
954
+ for (const kids of Object.values(node.children)) {
955
+ for (const child of kids || []) envelopeResolvedComponent(child);
956
+ }
957
+ }
958
+ function envelopeResolvedPayload(payload) {
959
+ if (payload.tree) {
960
+ for (const node of payload.tree) envelopeResolvedComponent(node);
961
+ }
962
+ if (payload.zones) {
963
+ for (const roots of Object.values(payload.zones)) {
964
+ for (const node of roots || []) envelopeResolvedComponent(node);
965
+ }
966
+ }
967
+ return payload;
732
968
  }
733
969
 
734
970
  // src/edge/mapResolvedToPageConfig.ts
@@ -788,7 +1024,9 @@ async function fetchContent(options) {
788
1024
  const preview = options.preview ?? (typeof window !== "undefined" ? isComposerPreview() : false);
789
1025
  if (preview) {
790
1026
  try {
791
- const page = await options.fetchOrchestratorPage(options.route, true);
1027
+ const page = envelopePageConfig(
1028
+ await options.fetchOrchestratorPage(options.route, true)
1029
+ );
792
1030
  return {
793
1031
  pageConfig: page,
794
1032
  source: "orchestrator",
@@ -2059,7 +2297,7 @@ Available Console Commands:
2059
2297
  this.refreshEventTracking();
2060
2298
  }, 800);
2061
2299
  }
2062
- return pageConfig;
2300
+ return envelopePageConfig(pageConfig);
2063
2301
  }
2064
2302
  /**
2065
2303
  * Set up view-level event tracking based on configuration
@@ -3432,125 +3670,83 @@ function getSDK(adapter) {
3432
3670
  var trackContentView = (contentId, contentType, properties) => getSDK().trackContentView(contentId, contentType, properties);
3433
3671
  var refreshEventTracking = () => getSDK().refreshEventTracking();
3434
3672
  getSDK();
3435
- function AddComponentIcon({ size = 28 }) {
3436
- return /* @__PURE__ */ jsxs(
3437
- "svg",
3438
- {
3439
- width: size,
3440
- height: size,
3441
- viewBox: "0 0 24 24",
3442
- fill: "none",
3443
- xmlns: "http://www.w3.org/2000/svg",
3444
- "aria-hidden": "true",
3445
- focusable: "false",
3446
- children: [
3447
- /* @__PURE__ */ jsx(
3448
- "rect",
3449
- {
3450
- x: "3",
3451
- y: "3",
3452
- width: "18",
3453
- height: "18",
3454
- rx: "4",
3455
- stroke: "currentColor",
3456
- strokeWidth: "1.75"
3457
- }
3458
- ),
3459
- /* @__PURE__ */ jsx(
3460
- "path",
3461
- {
3462
- d: "M12 8v8M8 12h8",
3463
- stroke: "currentColor",
3464
- strokeWidth: "1.75",
3465
- strokeLinecap: "round"
3466
- }
3467
- )
3468
- ]
3673
+
3674
+ // src/composer-store.ts
3675
+ var ComposerFieldStore = class {
3676
+ constructor() {
3677
+ this.values = /* @__PURE__ */ new Map();
3678
+ this.listeners = /* @__PURE__ */ new Map();
3679
+ }
3680
+ get(name) {
3681
+ return this.values.has(name) ? this.values.get(name) : void 0;
3682
+ }
3683
+ set(name, value) {
3684
+ if (!name) return;
3685
+ this.values.set(name, value);
3686
+ const set = this.listeners.get(name);
3687
+ if (!set) return;
3688
+ for (const fn of set) fn();
3689
+ }
3690
+ subscribe(name, fn) {
3691
+ if (!name) return () => {
3692
+ };
3693
+ let set = this.listeners.get(name);
3694
+ if (!set) {
3695
+ set = /* @__PURE__ */ new Set();
3696
+ this.listeners.set(name, set);
3469
3697
  }
3470
- );
3698
+ set.add(fn);
3699
+ return () => {
3700
+ set.delete(fn);
3701
+ if (set.size === 0) this.listeners.delete(name);
3702
+ };
3703
+ }
3704
+ keys() {
3705
+ return [...this.values.keys()];
3706
+ }
3707
+ delete(name) {
3708
+ if (!name || !this.values.has(name)) return;
3709
+ this.values.delete(name);
3710
+ const set = this.listeners.get(name);
3711
+ if (!set) return;
3712
+ for (const fn of set) fn();
3713
+ }
3714
+ };
3715
+ var ComposerStoreContext = createContext(null);
3716
+ function matchesNode(eventNodeId, layoutNodeId) {
3717
+ if (eventNodeId && layoutNodeId && String(eventNodeId) !== String(layoutNodeId)) {
3718
+ return false;
3719
+ }
3720
+ return true;
3471
3721
  }
3472
- function EmptySlotHint({
3473
- label = "Click here to Add Component",
3474
- compact = false
3722
+ function ComposerProvider({
3723
+ layoutNodeId,
3724
+ children
3475
3725
  }) {
3476
- const iconSize = compact ? 20 : 28;
3477
- return /* @__PURE__ */ jsxs(
3478
- "div",
3479
- {
3480
- className: "amplifyup-slot-placeholder",
3481
- style: {
3482
- display: "flex",
3483
- flexDirection: "column",
3484
- alignItems: "center",
3485
- justifyContent: "center",
3486
- gap: compact ? 8 : 12,
3487
- minHeight: compact ? 60 : void 0,
3488
- width: "100%",
3489
- pointerEvents: "none",
3490
- textAlign: "center",
3491
- boxSizing: "border-box",
3492
- padding: compact ? "12px 8px" : "24px 16px"
3493
- },
3494
- children: [
3495
- /* @__PURE__ */ jsx(
3496
- "span",
3497
- {
3498
- className: "amplifyup-slot-placeholder-icon",
3499
- style: {
3500
- display: "inline-flex",
3501
- alignItems: "center",
3502
- justifyContent: "center",
3503
- width: compact ? 36 : 48,
3504
- height: compact ? 36 : 48,
3505
- borderRadius: 12,
3506
- border: "1.5px dashed currentColor",
3507
- opacity: 0.9
3508
- },
3509
- children: /* @__PURE__ */ jsx(AddComponentIcon, { size: iconSize })
3510
- }
3511
- ),
3512
- /* @__PURE__ */ jsx(
3513
- "span",
3514
- {
3515
- className: "amplifyup-slot-placeholder-label",
3516
- style: {
3517
- fontSize: compact ? 13 : 15,
3518
- fontWeight: 600,
3519
- lineHeight: 1.3,
3520
- letterSpacing: "0.01em"
3521
- },
3522
- children: label
3523
- }
3524
- )
3525
- ]
3526
- }
3527
- );
3528
- }
3529
- var ComponentContext = createContext(null);
3530
- function useLiveComposerProps(layoutNodeId, props) {
3531
- const [patch, setPatch] = useState(null);
3726
+ const storeRef = useRef(null);
3727
+ if (!storeRef.current) storeRef.current = new ComposerFieldStore();
3728
+ const store = storeRef.current;
3532
3729
  useEffect(() => {
3533
- setPatch(null);
3534
- const matchesNode = (nodeId) => {
3535
- if (nodeId && layoutNodeId && String(nodeId) !== String(layoutNodeId)) {
3536
- return false;
3537
- }
3538
- return true;
3539
- };
3540
3730
  const applyField = (fieldName, value, nodeId) => {
3541
3731
  if (!fieldName || typeof fieldName !== "string") return;
3542
- if (!matchesNode(nodeId)) return;
3543
- setPatch((prev) => ({ ...prev || {}, [fieldName]: value }));
3732
+ if (!matchesNode(nodeId, layoutNodeId)) return;
3733
+ store.set(fieldName, value);
3734
+ if (value && typeof value === "object") {
3735
+ for (const patch of collectFieldPatches(
3736
+ toFieldEnvelopes({ [fieldName]: value })
3737
+ )) {
3738
+ store.set(patch.name, patch.value);
3739
+ }
3740
+ }
3544
3741
  };
3545
3742
  const applyProps = (nextProps, nodeId) => {
3546
3743
  if (!nextProps || typeof nextProps !== "object" || Array.isArray(nextProps)) {
3547
3744
  return;
3548
3745
  }
3549
- if (!matchesNode(nodeId)) return;
3550
- setPatch((prev) => ({
3551
- ...prev || {},
3552
- ...nextProps
3553
- }));
3746
+ if (!matchesNode(nodeId, layoutNodeId)) return;
3747
+ for (const patch of collectFieldPatches(nextProps)) {
3748
+ store.set(patch.name, patch.value);
3749
+ }
3554
3750
  };
3555
3751
  const onPatch = (event) => {
3556
3752
  const detail = event.detail || {};
@@ -3576,1321 +3772,1481 @@ function useLiveComposerProps(layoutNodeId, props) {
3576
3772
  window.removeEventListener("amplifyup:props-patch", onPropsPatch);
3577
3773
  window.removeEventListener("message", onMessage);
3578
3774
  };
3579
- }, [layoutNodeId]);
3580
- return patch ? { ...props, ...patch } : props;
3775
+ }, [layoutNodeId, store]);
3776
+ return /* @__PURE__ */ jsx(ComposerStoreContext.Provider, { value: store, children });
3581
3777
  }
3582
- function useComponentProps() {
3583
- const context = useContext(ComponentContext);
3584
- if (!context) {
3585
- console.warn(
3586
- "[AmplifyUp SDK] useComponentProps used outside ComponentContextProvider"
3587
- );
3588
- return {};
3589
- }
3590
- return context.props || {};
3778
+ function useComposerFieldValue(name, fallback) {
3779
+ const store = useContext(ComposerStoreContext);
3780
+ const subscribe = useCallback(
3781
+ (onStoreChange) => {
3782
+ if (!store || !name) return () => {
3783
+ };
3784
+ return store.subscribe(name, onStoreChange);
3785
+ },
3786
+ [store, name]
3787
+ );
3788
+ const getSnapshot = useCallback(() => {
3789
+ if (!store || !name) return void 0;
3790
+ return store.get(name);
3791
+ }, [store, name]);
3792
+ const overlay = useSyncExternalStore(subscribe, getSnapshot, () => void 0);
3793
+ return overlay !== void 0 ? overlay : fallback;
3591
3794
  }
3592
- function ComponentContextProvider({
3593
- children,
3594
- slots,
3595
- props,
3795
+ function useComposerStore() {
3796
+ return useContext(ComposerStoreContext);
3797
+ }
3798
+ function MaybeComposerProvider({
3799
+ enabled,
3596
3800
  layoutNodeId,
3597
- componentId
3801
+ children
3598
3802
  }) {
3599
- const liveProps = useLiveComposerProps(
3600
- layoutNodeId,
3601
- hydratePageResourceProps(props || {}, getPageResourceFields())
3803
+ const content = useMemo(() => children, [children]);
3804
+ if (!enabled) return /* @__PURE__ */ jsx(Fragment, { children: content });
3805
+ return /* @__PURE__ */ jsx(ComposerProvider, { layoutNodeId, children });
3806
+ }
3807
+ function nodeRenderProps(node) {
3808
+ const settings = node.settings || {};
3809
+ const hydrated = hydratePageResourceProps(
3810
+ node.props || {},
3811
+ getPageResourceFields()
3602
3812
  );
3603
- return /* @__PURE__ */ jsx(
3604
- ComponentContext.Provider,
3605
- {
3606
- value: { slots, props: liveProps, layoutNodeId, componentId },
3607
- children
3608
- }
3813
+ const fields = hydrated.fields;
3814
+ const branded = isBrandedFields(fields) ? fields : brandFields(
3815
+ fields && typeof fields === "object" ? fields : {}
3609
3816
  );
3817
+ return {
3818
+ ...settings,
3819
+ ...hydrated,
3820
+ fields: branded
3821
+ };
3610
3822
  }
3611
- function Slot({
3612
- name,
3613
- className
3614
- }) {
3615
- const context = useContext(ComponentContext);
3616
- if (!context) {
3617
- console.warn(`[AmplifyUp SDK] Slot "${name}" used outside ComponentContextProvider`);
3618
- return null;
3823
+ var ComponentRenderBoundary = class extends Component {
3824
+ constructor() {
3825
+ super(...arguments);
3826
+ this.state = { error: null };
3619
3827
  }
3620
- const slotContent = context.slots?.[name];
3621
- const isEmpty = !slotContent;
3622
- const isEditorMode = typeof window !== "undefined" && // Check for preview mode flag set by proxy
3623
- (window.__AMPLIFYUP_PREVIEW_MODE__ === true || // Check URL query params
3624
- new URLSearchParams(window.location.search).get("preview") === "true" || new URLSearchParams(window.location.search).get("visualEditor") === "true" || // Check if in iframe from orchestrator (check referrer hostname/port)
3625
- window.self !== window.top && document.referrer && (() => {
3626
- try {
3627
- const referrerUrl = new URL(document.referrer);
3628
- return referrerUrl.hostname === "localhost" && referrerUrl.port === "5001" || referrerUrl.pathname.includes("/dashboard/composer") || referrerUrl.pathname.includes("/dashboard/routes/");
3629
- } catch {
3630
- return false;
3631
- }
3632
- })());
3633
- sdkDebugOnce(
3634
- `slot:${context.layoutNodeId || ""}:${name}`,
3635
- `[Slot:${name}]`,
3636
- {
3637
- isEmpty,
3638
- isEditorMode,
3639
- availableSlotKeys: context.slots ? Object.keys(context.slots) : []
3640
- }
3641
- );
3642
- return /* @__PURE__ */ jsx(
3643
- "div",
3644
- {
3645
- className,
3646
- "data-slot-name": name,
3647
- "data-slot-id": name,
3648
- "data-amplifyup-slot": name,
3649
- "data-slot-empty": isEmpty ? "true" : void 0,
3650
- "data-parent-node-id": context.layoutNodeId || void 0,
3651
- "data-parent-component-id": context.componentId || void 0,
3652
- style: {
3653
- // Only add minimum height when empty (for drop target visibility)
3654
- minHeight: isEmpty ? "60px" : void 0,
3655
- // Only add position relative for overlay positioning in editor mode
3656
- position: isEditorMode ? "relative" : void 0
3657
- },
3658
- children: isEmpty ? isEditorMode ? /* @__PURE__ */ jsx(EmptySlotHint, { compact: true }) : /* @__PURE__ */ jsx(
3659
- "div",
3660
- {
3661
- className: "amplifyup-slot-placeholder",
3662
- style: {
3663
- minHeight: "60px",
3664
- color: "#9ca3af",
3665
- fontSize: "14px",
3666
- pointerEvents: "none"
3667
- },
3668
- children: `Empty slot: ${name}`
3669
- }
3670
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
3671
- isEditorMode && /* @__PURE__ */ jsxs(
3828
+ static getDerivedStateFromError(error) {
3829
+ return { error };
3830
+ }
3831
+ componentDidCatch(error) {
3832
+ console.error(
3833
+ `[AmplifyRenderer] "${this.props.componentId}" (${this.props.nodeId}) threw while rendering:`,
3834
+ error
3835
+ );
3836
+ }
3837
+ render() {
3838
+ if (this.state.error) {
3839
+ if (process.env.NODE_ENV !== "production") {
3840
+ return /* @__PURE__ */ jsxs(
3672
3841
  "div",
3673
3842
  {
3674
- className: "amplifyup-slot-label",
3843
+ "data-amplifyup-render-error": "",
3844
+ "data-component-id": this.props.componentId,
3845
+ "data-node-id": this.props.nodeId,
3675
3846
  style: {
3676
- position: "absolute",
3677
- top: "4px",
3678
- left: "4px",
3679
- fontSize: "10px",
3680
- color: "#9ca3af",
3681
- pointerEvents: "none",
3682
- zIndex: 1
3847
+ margin: "1rem 0",
3848
+ padding: "0.75rem 1rem",
3849
+ border: "1px solid #fca5a5",
3850
+ borderRadius: "0.5rem",
3851
+ background: "#fef2f2",
3852
+ color: "#991b1b",
3853
+ fontSize: "0.875rem"
3683
3854
  },
3684
3855
  children: [
3685
- "Slot: ",
3686
- name
3856
+ 'Component "',
3857
+ this.props.componentId,
3858
+ '" failed to render:',
3859
+ " ",
3860
+ this.state.error.message
3687
3861
  ]
3688
3862
  }
3689
- ),
3690
- slotContent
3691
- ] })
3863
+ );
3864
+ }
3865
+ return null;
3692
3866
  }
3693
- );
3694
- }
3695
- function isComposerEditMode() {
3696
- if (typeof window === "undefined") return false;
3697
- return isComposerPreview() && window.__AMPLIFYUP_EVENT_SELECTION__ !== true;
3698
- }
3699
- function isLikelyNonTextFieldName(name) {
3700
- const n = name.toLowerCase();
3701
- return n.includes("href") || n.includes("url") || n.includes("src") || n.includes("image") || n.includes("icon") || n.includes("overlay") || n.includes("color");
3702
- }
3703
- function isPlainTextValue(value) {
3704
- return typeof value === "string" || typeof value === "number";
3705
- }
3706
- var COMPOSER_EMPTY_FIELD_HINT = "Click here to edit this field";
3707
- var EMPTY_FIELD_PLACEHOLDERS = /* @__PURE__ */ new Set([
3708
- COMPOSER_EMPTY_FIELD_HINT,
3709
- "Click here to enter text",
3710
- "Sample Title",
3711
- "Sample Heading",
3712
- "Sample Subheading"
3713
- ]);
3714
- function isComposerEmptyText(value) {
3715
- if (value === void 0 || value === null) return true;
3716
- if (typeof value !== "string") return false;
3717
- const trimmed = value.trim();
3718
- return trimmed === "" || EMPTY_FIELD_PLACEHOLDERS.has(trimmed);
3719
- }
3720
- function isReactNodeFallback(fallback) {
3721
- if (fallback == null || fallback === false) return false;
3722
- return !isPlainTextValue(fallback);
3723
- }
3724
- function classNameIsBlock(className) {
3725
- return /(?:^|\s)block(?:\s|$)/.test(className || "");
3726
- }
3727
- function canInlineEdit(name, value) {
3728
- if (!name || isLikelyNonTextFieldName(name)) return false;
3729
- if (value !== void 0 && value !== null && typeof value === "object") {
3730
- return false;
3731
- }
3732
- if (typeof value === "boolean") return false;
3733
- return true;
3734
- }
3735
- function canInlineEditRichText(name, value) {
3736
- if (!name || isLikelyNonTextFieldName(name)) return false;
3737
- if (typeof value === "boolean") return false;
3738
- if (value !== void 0 && value !== null && typeof value === "object" && !Array.isArray(value)) {
3739
- return false;
3740
- }
3741
- return true;
3742
- }
3743
- function isLikelyImagePropName(name) {
3744
- const n = name.toLowerCase();
3745
- return n.includes("image") || n.includes("thumbnail") || n.includes("photo") || n.includes("avatar") || n.includes("poster") || n.includes("logo") || n.endsWith("img") || n.endsWith("_src") || n.endsWith("Src");
3746
- }
3747
- function portableImageUrl(value) {
3748
- if (typeof value === "string") return value.trim();
3749
- if (value && typeof value === "object" && !Array.isArray(value) && "url" in value) {
3750
- const url = value.url;
3751
- if (url == null) return "";
3752
- if (typeof url === "string") return url.trim();
3753
- return null;
3867
+ return this.props.children;
3754
3868
  }
3755
- return null;
3756
- }
3757
- function inferEditName(explicitName, contextProps, sourceValue) {
3758
- if (explicitName) return explicitName;
3759
- if (!contextProps) return "";
3760
- const keys = Object.keys(contextProps).filter(
3761
- (key) => Object.is(contextProps[key], sourceValue)
3762
- );
3763
- if (keys.length === 1) return keys[0];
3764
- const srcUrl = portableImageUrl(sourceValue);
3765
- if (srcUrl !== null) {
3766
- const byUrl = Object.keys(contextProps).filter((key) => {
3767
- const other = portableImageUrl(contextProps[key]);
3768
- return other !== null && other === srcUrl;
3769
- });
3770
- if (byUrl.length === 1) return byUrl[0];
3771
- if (srcUrl === "") {
3772
- const emptyImageKeys = Object.keys(contextProps).filter((key) => {
3773
- if (!isLikelyImagePropName(key)) return false;
3774
- const other = portableImageUrl(contextProps[key]);
3775
- return other === "" || contextProps[key] == null;
3776
- });
3777
- if (emptyImageKeys.length === 1) return emptyImageKeys[0];
3869
+ };
3870
+ function NodeWithFields({
3871
+ nodeId,
3872
+ componentId,
3873
+ props,
3874
+ slots,
3875
+ renderComponent,
3876
+ composerPreview
3877
+ }) {
3878
+ return /* @__PURE__ */ jsx(
3879
+ MaybeComposerProvider,
3880
+ {
3881
+ enabled: !!(composerPreview || isComposerPreview()),
3882
+ layoutNodeId: nodeId,
3883
+ children: renderComponent(componentId, props, slots, {
3884
+ layoutNodeId: nodeId
3885
+ })
3778
3886
  }
3779
- }
3780
- if (keys.length === 0 || isComposerEmptyText(sourceValue)) return "";
3781
- return keys[0];
3782
- }
3783
- function subscribeComposerEdit(onStoreChange) {
3784
- if (typeof window === "undefined") return () => {
3785
- };
3786
- window.addEventListener("amplifyup:composer-edit", onStoreChange);
3787
- return () => window.removeEventListener("amplifyup:composer-edit", onStoreChange);
3788
- }
3789
- function useComposerEditMode() {
3790
- return useSyncExternalStore(
3791
- subscribeComposerEdit,
3792
- isComposerEditMode,
3793
- () => false
3794
3887
  );
3795
3888
  }
3796
- function useFieldOverlay(layoutNodeId, name, source) {
3797
- const [overlay, setOverlay] = useState(void 0);
3889
+ function AmplifyRendererInner({
3890
+ renderComponent,
3891
+ pageConfig: serverPageConfig,
3892
+ loadingComponent,
3893
+ errorComponent,
3894
+ emptyComponent,
3895
+ composerPreview
3896
+ }) {
3897
+ const clientHook = useAmplifyUp();
3898
+ const pageConfig = serverPageConfig || clientHook.pageConfig || null;
3899
+ const loading = serverPageConfig ? false : clientHook.loading;
3900
+ const error = serverPageConfig ? null : clientHook.error;
3901
+ const contentSource = clientHook.source;
3902
+ const [variantCache, setVariantCache] = useState({});
3798
3903
  useEffect(() => {
3799
- const onPatch = (event) => {
3800
- const detail = event.detail || {};
3801
- if (detail.fieldName !== name) return;
3802
- if (detail.layoutNodeId && layoutNodeId && String(detail.layoutNodeId) !== String(layoutNodeId)) {
3904
+ if (typeof window !== "undefined") {
3905
+ if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
3803
3906
  return;
3804
3907
  }
3805
- setOverlay(detail.value);
3806
- };
3807
- window.addEventListener("amplifyup:field-patch", onPatch);
3808
- return () => window.removeEventListener("amplifyup:field-patch", onPatch);
3809
- }, [layoutNodeId, name]);
3810
- return overlay !== void 0 ? overlay : source;
3811
- }
3812
- function commitComposerField(layoutNodeId, componentId, name, value) {
3813
- if (typeof window === "undefined" || window.parent === window) return;
3814
- window.parent.postMessage(
3815
- {
3816
- type: "AMPLIFYUP_FIELD_COMMIT",
3817
- layoutNodeId,
3818
- componentId,
3819
- fieldName: name,
3820
- value
3821
- },
3822
- "*"
3823
- );
3824
- }
3825
- function looksLikeRichCanvasField(name) {
3826
- const tokens = String(name || "").replace(/([a-z])([A-Z])/g, "$1 $2").split(/[._\-\s/]+/).map((s) => s.toLowerCase()).filter(Boolean);
3827
- const last = tokens[tokens.length - 1] || "";
3828
- return last === "body" || last === "content" || last === "copy" || last === "excerpt" || last === "markdown" || last === "richtext";
3829
- }
3830
- var lastRichTextRequestAt = 0;
3831
- function requestCanvasRichTextEdit(opts) {
3832
- if (typeof window === "undefined" || window.parent === window) return;
3833
- if (!opts.name) return;
3834
- const now = Date.now();
3835
- if (now - lastRichTextRequestAt < 400) return;
3836
- lastRichTextRequestAt = now;
3837
- const rect = opts.el.getBoundingClientRect();
3838
- window.parent.postMessage(
3839
- {
3840
- type: "AMPLIFYUP_RICHTEXT_EDIT",
3841
- layoutNodeId: opts.layoutNodeId,
3842
- componentId: opts.componentId,
3843
- fieldName: opts.name,
3844
- value: opts.value,
3845
- bounds: {
3846
- top: rect.top,
3847
- left: rect.left,
3848
- width: Math.max(rect.width, 240),
3849
- height: Math.max(rect.height, 64)
3908
+ if (window.location.pathname.includes("/api/pages/proxy")) {
3909
+ return;
3910
+ }
3911
+ if (window.self !== window.top) {
3912
+ return;
3913
+ }
3914
+ const urlParams = new URLSearchParams(window.location.search);
3915
+ if (urlParams.get("preview") === "true" || urlParams.get("visualEditor") === "true") {
3916
+ return;
3917
+ }
3918
+ if (window.location.href.includes("preview=true") || window.location.href.includes("visualEditor=true")) {
3919
+ return;
3850
3920
  }
3851
- },
3852
- "*"
3853
- );
3854
- }
3855
- function placeCaret(el, atStart = false) {
3856
- const range = document.createRange();
3857
- range.selectNodeContents(el);
3858
- range.collapse(atStart);
3859
- const sel = window.getSelection();
3860
- sel?.removeAllRanges();
3861
- sel?.addRange(range);
3862
- }
3863
- function ComposerInlineField({
3864
- name,
3865
- value,
3866
- fallback,
3867
- className,
3868
- render,
3869
- layoutNodeId,
3870
- componentId,
3871
- rich = false
3872
- }) {
3873
- const [editing, setEditing] = useState(false);
3874
- const [focused, setFocused] = useState(false);
3875
- const elRef = useRef(null);
3876
- const storedEmpty = isComposerEmptyText(value);
3877
- const text = storedEmpty ? "" : formatFieldValue(value);
3878
- const fallbackText = typeof fallback === "string" || typeof fallback === "number" ? String(fallback) : "";
3879
- const hint = COMPOSER_EMPTY_FIELD_HINT;
3880
- const idleDisplay = storedEmpty ? hint : text || fallbackText;
3881
- const showRendered = !!render && !editing;
3882
- const showHint = storedEmpty && !focused && !showRendered;
3883
- useEffect(() => {
3884
- const el = elRef.current;
3885
- if (!el || showRendered) return;
3886
- if (document.activeElement === el) return;
3887
- if (el.querySelector(
3888
- "input, button, select, textarea, form, svg, iframe, img"
3889
- )) {
3890
- return;
3891
- }
3892
- const next = idleDisplay || "";
3893
- if (el.textContent !== next) el.textContent = next;
3894
- }, [idleDisplay, showRendered]);
3895
- useEffect(() => {
3896
- if (!editing || !elRef.current) return;
3897
- const el = elRef.current;
3898
- if (storedEmpty) {
3899
- el.textContent = "";
3900
- el.focus();
3901
- placeCaret(el, true);
3902
- return;
3903
3921
  }
3904
- el.textContent = idleDisplay || "";
3905
- el.focus();
3906
- placeCaret(el, false);
3907
- }, [editing, storedEmpty, idleDisplay]);
3908
- const finishEdit = () => {
3909
- const raw = (elRef.current?.innerText ?? "").replace(/\u00a0/g, " ").replace(/\n+$/g, "");
3910
- const next = raw.trim() === "" ? "" : raw;
3911
- setEditing(false);
3912
- setFocused(false);
3913
- if (storedEmpty && next === "") {
3914
- if (elRef.current) elRef.current.textContent = hint;
3915
- if (typeof value === "string" && EMPTY_FIELD_PLACEHOLDERS.has(value.trim())) {
3916
- commitComposerField(layoutNodeId, componentId, name, "");
3917
- }
3918
- return;
3922
+ if (pageConfig && pageConfig.id !== "default") {
3923
+ trackContentView(pageConfig.id, "page", {
3924
+ route: pageConfig.route,
3925
+ title: pageConfig.title
3926
+ });
3919
3927
  }
3920
- if (next === text) return;
3921
- if (!text && !storedEmpty && next === fallbackText) return;
3922
- commitComposerField(layoutNodeId, componentId, name, next);
3923
- };
3924
- const openRichEditor = (event) => {
3925
- if (!isComposerEditMode() || !elRef.current) return;
3926
- event?.preventDefault();
3927
- event?.stopPropagation();
3928
- const source = typeof value === "string" && !storedEmpty ? value : text || "";
3929
- requestCanvasRichTextEdit({
3930
- el: elRef.current,
3931
- layoutNodeId,
3932
- componentId,
3933
- name,
3934
- value: source
3935
- });
3936
- };
3937
- return /* @__PURE__ */ jsx(
3938
- "span",
3939
- {
3940
- ref: elRef,
3941
- className,
3942
- "data-amplifyup-field": name,
3943
- "data-amplifyup-field-type": rich ? "richtext" : "text",
3944
- "data-amplifyup-node-id": layoutNodeId || void 0,
3945
- "data-amplifyup-empty-hint": showHint ? "true" : void 0,
3946
- contentEditable: !rich && !showRendered,
3947
- suppressContentEditableWarning: true,
3948
- role: "textbox",
3949
- tabIndex: 0,
3950
- onMouseDown: (event) => {
3951
- event.stopPropagation();
3952
- },
3953
- onClick: (event) => {
3954
- if (!isComposerEditMode()) return;
3955
- const link = event.target?.closest?.("a");
3956
- if (link) event.preventDefault();
3957
- if (rich) {
3958
- openRichEditor(event);
3959
- return;
3960
- }
3961
- if (render) {
3962
- setEditing(true);
3963
- }
3964
- },
3965
- onFocus: () => {
3966
- if (!isComposerEditMode()) return;
3967
- if (rich) {
3968
- openRichEditor();
3969
- return;
3970
- }
3971
- setFocused(true);
3972
- const el = elRef.current;
3973
- if (el && storedEmpty) {
3974
- el.textContent = "";
3975
- placeCaret(el, true);
3976
- }
3977
- if (render) setEditing(true);
3978
- },
3979
- onBlur: rich ? void 0 : finishEdit,
3980
- onKeyDown: (event) => {
3981
- event.stopPropagation();
3982
- if (event.key === "Enter" && !event.shiftKey) {
3983
- event.preventDefault();
3984
- event.currentTarget.blur();
3985
- }
3986
- if (event.key === "Escape") {
3987
- event.preventDefault();
3988
- if (elRef.current) elRef.current.textContent = idleDisplay || "";
3989
- setEditing(false);
3990
- setFocused(false);
3991
- event.currentTarget.blur();
3928
+ }, [pageConfig]);
3929
+ useEffect(() => {
3930
+ if (!pageConfig?.eventTracking?.length) return;
3931
+ const sdk = getSDK();
3932
+ if (!sdk.isInitialized()) return;
3933
+ sdk.setupEventTracking(pageConfig.eventTracking);
3934
+ const t1 = window.setTimeout(() => {
3935
+ sdk.setupComponentViewTracking();
3936
+ refreshEventTracking();
3937
+ }, 200);
3938
+ const t2 = window.setTimeout(() => {
3939
+ sdk.setupComponentViewTracking();
3940
+ refreshEventTracking();
3941
+ }, 1e3);
3942
+ return () => {
3943
+ window.clearTimeout(t1);
3944
+ window.clearTimeout(t2);
3945
+ };
3946
+ }, [pageConfig?.id, pageConfig?.eventTracking]);
3947
+ useEffect(() => {
3948
+ if (!pageConfig) return;
3949
+ const t = window.setTimeout(() => {
3950
+ getSDK().setupComponentViewTracking();
3951
+ refreshEventTracking();
3952
+ }, 100);
3953
+ return () => window.clearTimeout(t);
3954
+ }, [variantCache, pageConfig?.id]);
3955
+ useEffect(() => {
3956
+ const isVisualEditorMode = () => {
3957
+ if (typeof window === "undefined") return false;
3958
+ const urlParams = new URLSearchParams(window.location.search);
3959
+ if (urlParams.get("visualEditor") === "true" || urlParams.get("preview") === "true") {
3960
+ return true;
3961
+ }
3962
+ const inIframe = window.self !== window.top;
3963
+ if (!inIframe) return false;
3964
+ try {
3965
+ if (document.referrer) {
3966
+ const referrerUrl = new URL(document.referrer);
3967
+ if (referrerUrl.hostname === "localhost" && referrerUrl.port === "5001") {
3968
+ return true;
3969
+ }
3992
3970
  }
3993
- },
3994
- style: {
3995
- cursor: "text",
3996
- outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
3997
- outlineOffset: "2px",
3998
- minWidth: "1ch",
3999
- minHeight: "1em",
4000
- // Do not force `inline` — that overrides Tailwind `block` and parks
4001
- // an empty-field hint on the same line as a sibling that has a value.
4002
- opacity: showHint ? 0.5 : void 0
4003
- },
4004
- children: showRendered ? value != null && value !== "" && !storedEmpty ? render(value) : fallback ?? idleDisplay : isReactNodeFallback(fallback) && !idleDisplay ? fallback : null
3971
+ } catch (e) {
3972
+ }
3973
+ return false;
3974
+ };
3975
+ if (!isVisualEditorMode()) {
3976
+ return;
4005
3977
  }
4006
- );
4007
- }
4008
- function ComposerInlineHtml({
4009
- name,
4010
- html,
4011
- sourceValue,
4012
- className,
4013
- Tag = "div",
4014
- layoutNodeId,
4015
- componentId
4016
- }) {
4017
- const elRef = useRef(null);
4018
- useEffect(() => {
4019
- const el = elRef.current;
4020
- if (!el) return;
4021
- if (el.innerHTML !== html) el.innerHTML = html;
4022
- }, [html]);
4023
- const openRichEditor = (event) => {
4024
- if (!elRef.current) return;
4025
- event.preventDefault();
4026
- event.stopPropagation();
4027
- requestCanvasRichTextEdit({
4028
- el: elRef.current,
4029
- layoutNodeId,
4030
- componentId,
4031
- name,
4032
- value: sourceValue
4033
- });
4034
- };
4035
- return React.createElement(Tag, {
4036
- ref: elRef,
4037
- className,
4038
- "data-amplifyup-field": name,
4039
- "data-amplifyup-field-type": "richtext",
4040
- "data-amplifyup-node-id": layoutNodeId || void 0,
4041
- dangerouslySetInnerHTML: { __html: html },
4042
- onMouseDown: (event) => {
4043
- event.stopPropagation();
4044
- },
4045
- onClick: openRichEditor,
4046
- style: {
4047
- cursor: "text",
4048
- outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
4049
- outlineOffset: "2px"
3978
+ if (!pageConfig) {
3979
+ return;
4050
3980
  }
4051
- });
4052
- }
4053
- function formatFieldValue(value) {
4054
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4055
- return String(value);
4056
- }
4057
- if (Array.isArray(value)) {
4058
- const parts = value.map((item) => {
4059
- if (typeof item === "string") return item;
4060
- if (item && typeof item === "object" && "children" in item && Array.isArray(item.children)) {
4061
- return item.children.map((c) => typeof c?.text === "string" ? c.text : "").join("");
3981
+ const rawTree2 = pageConfig.layoutTree;
3982
+ const normalizedTree = Array.isArray(rawTree2) ? rawTree2 : rawTree2 && typeof rawTree2 === "object" && rawTree2.componentId ? [rawTree2] : [];
3983
+ if (normalizedTree.length === 0) {
3984
+ if (typeof window !== "undefined" && window.parent && window.parent !== window) {
3985
+ window.parent.postMessage({
3986
+ type: "COMPONENT_TREE",
3987
+ tree: []
3988
+ }, "*");
4062
3989
  }
4063
- return "";
4064
- }).filter(Boolean);
4065
- if (parts.length > 0) return parts.join(" ");
4066
- }
4067
- if (value && typeof value === "object") {
4068
- return "";
4069
- }
4070
- return "";
4071
- }
4072
- function warnProjectionProps(component, name, hasExplicitValue) {
4073
- if (process.env.NODE_ENV === "production") return;
4074
- if (hasExplicitValue) return;
4075
- if (name) {
4076
- console.warn(
4077
- `[AmplifyUp SDK] ${component} \`name\` is deprecated and will be removed. Prefer value={${name}} from your component props.`
4078
- );
4079
- }
4080
- }
4081
- function hasOwnProp(props, key) {
4082
- return Object.prototype.hasOwnProperty.call(props, key);
4083
- }
4084
- function isLinkListItem(value) {
4085
- return !!value && typeof value === "object" && !Array.isArray(value);
4086
- }
4087
- function renderLinkList(items, className) {
4088
- const links = items.filter(isLinkListItem).filter((item) => {
4089
- const label = typeof item.label === "string" ? item.label.trim() : "";
4090
- const href = typeof item.href === "string" ? item.href.trim() : "";
4091
- return !!(label || href);
4092
- });
4093
- if (links.length === 0) return null;
4094
- return /* @__PURE__ */ jsx("span", { className, style: { display: "contents" }, children: links.map((item, index) => {
4095
- const label = typeof item.label === "string" && item.label.trim() ? item.label : typeof item.href === "string" ? item.href : "Link";
4096
- const href = typeof item.href === "string" && item.href.trim() ? item.href : "#";
4097
- const style = typeof item.style === "string" ? item.style.trim() : "";
4098
- return /* @__PURE__ */ jsx(
4099
- "a",
4100
- {
4101
- href,
4102
- target: item.openInNewTab ? "_blank" : void 0,
4103
- rel: item.openInNewTab ? "noreferrer" : void 0,
4104
- "data-amplifyup-action-style": style || void 0,
4105
- children: label
4106
- },
4107
- typeof item._key === "string" && item._key ? item._key : `${href}-${index}`
4108
- );
4109
- }) });
4110
- }
4111
- function Field(props) {
4112
- const { value, name, fallback, className, render } = props;
4113
- const context = useContext(ComponentContext);
4114
- const composerEdit = useComposerEditMode();
4115
- const hasExplicitValue = hasOwnProp(props, "value");
4116
- const sourceValue = hasExplicitValue ? value : name ? context?.props?.[name] : void 0;
4117
- const editName = inferEditName(name, context?.props, sourceValue);
4118
- const fieldValue = useFieldOverlay(
4119
- context?.layoutNodeId,
4120
- editName,
4121
- sourceValue
4122
- );
4123
- warnProjectionProps("Field", name, hasExplicitValue);
4124
- if (!context && !hasExplicitValue) {
4125
- console.warn(
4126
- "[AmplifyUp SDK] Field needs a `value` prop (or deprecated `name` alone)"
3990
+ return;
3991
+ }
3992
+ const convertLayoutTreeToComponentTree = (nodes, parentId = null) => {
3993
+ const result = [];
3994
+ nodes.forEach((node) => {
3995
+ if (!node) return;
3996
+ const treeNode = {
3997
+ id: node.id,
3998
+ componentId: node.componentId,
3999
+ componentName: node.componentId,
4000
+ // Will be enhanced with actual name if available
4001
+ parentId,
4002
+ children: []
4003
+ };
4004
+ if (node.slots && node.slots.length > 0) {
4005
+ node.slots.forEach((slot) => {
4006
+ const kids = Array.isArray(slot?.children) ? slot.children : [];
4007
+ if (kids.length > 0) {
4008
+ const slotChildren = convertLayoutTreeToComponentTree(kids, node.id);
4009
+ treeNode.children.push(...slotChildren);
4010
+ }
4011
+ });
4012
+ }
4013
+ result.push(treeNode);
4014
+ });
4015
+ return result;
4016
+ };
4017
+ const layoutTree2 = normalizedTree;
4018
+ const componentTree = convertLayoutTreeToComponentTree(layoutTree2);
4019
+ const describeLayoutTree = (nodes, depth = 0) => {
4020
+ if (!nodes?.length) return "";
4021
+ const indent = " ".repeat(depth);
4022
+ return nodes.map((n) => {
4023
+ const slotsDesc = (n.slots || []).map((s) => {
4024
+ const childCount = Array.isArray(s.children) ? s.children.length : 0;
4025
+ const childrenDesc = childCount > 0 ? describeLayoutTree(s.children || [], depth + 1) : "";
4026
+ return `${indent} slot "${s.slotId || s.slotName || "?"}" (${childCount} children)${childrenDesc ? "\n" + childrenDesc : ""}`;
4027
+ }).join("\n");
4028
+ return `${indent}- ${n.id} (${n.componentId || "?"})${slotsDesc ? "\n" + slotsDesc : ""}`;
4029
+ }).join("\n");
4030
+ };
4031
+ sdkDebugOnce(
4032
+ `layoutTree:${pageConfig?.id || ""}:${pageConfig?.route || ""}`,
4033
+ "[AmplifyRenderer] layoutTree received:\n%s",
4034
+ describeLayoutTree(layoutTree2)
4127
4035
  );
4128
- return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
4129
- }
4130
- if (render) {
4131
- if (composerEdit && editName && context && canInlineEdit(editName, fieldValue)) {
4132
- return /* @__PURE__ */ jsx(
4133
- ComposerInlineField,
4134
- {
4135
- name: editName,
4136
- value: fieldValue,
4137
- fallback,
4138
- className,
4139
- render,
4140
- layoutNodeId: context.layoutNodeId,
4141
- componentId: context.componentId,
4142
- rich: looksLikeRichCanvasField(editName)
4036
+ if (typeof window !== "undefined" && window.parent && window.parent !== window) {
4037
+ const message = {
4038
+ type: "COMPONENT_TREE",
4039
+ tree: componentTree
4040
+ };
4041
+ window.parent.postMessage(message, "*");
4042
+ sdkDebug(
4043
+ "[AmplifyRenderer] Sent component tree from layoutTree:",
4044
+ componentTree.length,
4045
+ "root components"
4046
+ );
4047
+ }
4048
+ }, [pageConfig]);
4049
+ useEffect(() => {
4050
+ if (!pageConfig?.layoutTree || !pageConfig.layoutId) return;
4051
+ const roots = pageConfig.layoutTree;
4052
+ const fromEdge = contentSource === "edge" || roots.some((n) => n._contentSource === "edge");
4053
+ if (fromEdge) return;
4054
+ const sdk = getSDK();
4055
+ const instanceId = sdk.getInstanceId();
4056
+ const decisionApiUrl = sdk.getDecisionApiUrl();
4057
+ const orchestratorUrl = sdk.getOrchestratorUrl();
4058
+ if (!decisionApiUrl && !orchestratorUrl || !instanceId) return;
4059
+ const fetchVariants = async () => {
4060
+ const nodes = pageConfig.layoutTree;
4061
+ const newCache = {};
4062
+ const findZonedSlots = (nodeList) => {
4063
+ const results = [];
4064
+ for (const node of nodeList) {
4065
+ if (node.slots) {
4066
+ for (const slot of node.slots) {
4067
+ if (slot.zoneId && slot.variants) {
4068
+ const key = `${node.id}-${slot.slotId}`;
4069
+ results.push({ node, slot, key });
4070
+ }
4071
+ }
4072
+ }
4143
4073
  }
4074
+ return results;
4075
+ };
4076
+ const zonedSlots = findZonedSlots(nodes);
4077
+ await Promise.all(
4078
+ zonedSlots.map(async ({ node, slot, key }) => {
4079
+ try {
4080
+ let personalizationUrl;
4081
+ if (decisionApiUrl) {
4082
+ const url = new URL(decisionApiUrl);
4083
+ url.searchParams.set("instanceId", instanceId);
4084
+ const zoneId = slot.zoneId;
4085
+ if (zoneId) {
4086
+ url.searchParams.set("zoneId", zoneId);
4087
+ }
4088
+ const campaignId = slot.campaignId;
4089
+ if (campaignId) {
4090
+ url.searchParams.set("campaignId", campaignId);
4091
+ }
4092
+ personalizationUrl = url.toString();
4093
+ } else if (orchestratorUrl) {
4094
+ personalizationUrl = `${orchestratorUrl}/api/personalization/variant?instanceId=${encodeURIComponent(instanceId)}&layoutId=${encodeURIComponent(pageConfig.layoutId)}&slotId=${encodeURIComponent(slot.slotId)}&nodeId=${encodeURIComponent(node.id)}`;
4095
+ } else {
4096
+ return;
4097
+ }
4098
+ const response = await fetch(personalizationUrl);
4099
+ if (response.ok) {
4100
+ const data = await response.json();
4101
+ if (data.variantId && data.componentTree) {
4102
+ newCache[key] = {
4103
+ variantId: data.variantId,
4104
+ children: data.componentTree
4105
+ };
4106
+ if (typeof window !== "undefined") {
4107
+ if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
4108
+ return;
4109
+ }
4110
+ if (window.location.pathname.includes("/api/pages/proxy")) {
4111
+ return;
4112
+ }
4113
+ if (window.self !== window.top) {
4114
+ return;
4115
+ }
4116
+ }
4117
+ sdk.track("Zone Impression", {
4118
+ zoneId: slot.zoneId,
4119
+ variantId: data.variantId,
4120
+ layoutId: pageConfig.layoutId,
4121
+ route: pageConfig.route
4122
+ });
4123
+ } else {
4124
+ const defaultVariant = slot.variants?.find((v) => v.isDefault);
4125
+ if (defaultVariant) {
4126
+ newCache[key] = {
4127
+ variantId: defaultVariant.variantId,
4128
+ children: defaultVariant.children
4129
+ };
4130
+ }
4131
+ }
4132
+ }
4133
+ } catch (err) {
4134
+ console.error(`Error fetching variant for slot ${key}:`, err);
4135
+ const defaultVariant = slot.variants?.find((v) => v.isDefault);
4136
+ if (defaultVariant) {
4137
+ newCache[key] = {
4138
+ variantId: defaultVariant.variantId,
4139
+ children: defaultVariant.children
4140
+ };
4141
+ }
4142
+ }
4143
+ })
4144
4144
  );
4145
- }
4146
- return /* @__PURE__ */ jsx("span", { className, style: { display: "contents" }, children: render(fieldValue) });
4147
- }
4148
- if (Array.isArray(fieldValue)) {
4149
- const list = renderLinkList(fieldValue, className);
4150
- if (list) return list;
4151
- }
4152
- const text = formatFieldValue(fieldValue);
4153
- const showVisualFallback = !text && isReactNodeFallback(fallback);
4154
- const emptyBlockField = isComposerEmptyText(fieldValue) && classNameIsBlock(className);
4155
- if (composerEdit && editName && context && canInlineEdit(editName, fieldValue) && !showVisualFallback && !emptyBlockField) {
4156
- return /* @__PURE__ */ jsx(
4157
- ComposerInlineField,
4158
- {
4159
- name: editName,
4160
- value: fieldValue,
4161
- fallback,
4162
- className,
4163
- layoutNodeId: context.layoutNodeId,
4164
- componentId: context.componentId,
4165
- rich: looksLikeRichCanvasField(editName)
4166
- }
4167
- );
4168
- }
4169
- if (fieldValue === void 0 || fieldValue === null || isComposerEmptyText(fieldValue)) {
4170
- return fallback ? /* @__PURE__ */ jsx("span", { className, children: fallback }) : null;
4171
- }
4172
- if (!text) {
4173
- return fallback ? /* @__PURE__ */ jsx("span", { className, children: fallback }) : null;
4145
+ setVariantCache(newCache);
4146
+ };
4147
+ fetchVariants();
4148
+ }, [pageConfig?.layoutTree, pageConfig?.layoutId, pageConfig?.route, contentSource]);
4149
+ if (loading) {
4150
+ return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50", children: /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsxs("div", { className: "animate-pulse", children: [
4151
+ /* @__PURE__ */ jsx("div", { className: "h-8 bg-gray-200 rounded w-1/4 mb-4" }),
4152
+ /* @__PURE__ */ jsx("div", { className: "h-4 bg-gray-200 rounded w-1/2 mb-8" }),
4153
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
4154
+ /* @__PURE__ */ jsx("div", { className: "h-64 bg-gray-200 rounded" }),
4155
+ /* @__PURE__ */ jsx("div", { className: "h-32 bg-gray-200 rounded" })
4156
+ ] })
4157
+ ] }) }) }) });
4174
4158
  }
4175
- return /* @__PURE__ */ jsx("span", { className, children: text });
4176
- }
4177
- function markdownToHtml(markdown) {
4178
- try {
4179
- const result = marked.parse(markdown, { async: false });
4180
- return typeof result === "string" ? result : "";
4181
- } catch {
4182
- return markdown;
4159
+ if (error) {
4160
+ return /* @__PURE__ */ jsx(Fragment, { children: errorComponent ? errorComponent(error) : /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50", children: /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsxs("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6", children: [
4161
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-red-900 mb-2", children: "Error Loading Page" }),
4162
+ /* @__PURE__ */ jsx("p", { className: "text-red-700", children: error })
4163
+ ] }) }) }) });
4183
4164
  }
4184
- }
4185
- function RichText(props) {
4186
- const {
4187
- value,
4188
- name,
4189
- fallback,
4190
- className,
4191
- as: Tag = "div",
4192
- children
4193
- } = props;
4194
- const context = useContext(ComponentContext);
4195
- const composerEdit = useComposerEditMode();
4196
- const childrenValue = typeof children === "string" || typeof children === "number" ? String(children) : void 0;
4197
- const hasValueProp = hasOwnProp(props, "value");
4198
- const hasExplicitValue = hasValueProp || childrenValue !== void 0;
4199
- const sourceValue = hasValueProp ? value : childrenValue !== void 0 ? childrenValue : name ? context?.props?.[name] : void 0;
4200
- const editName = inferEditName(name, context?.props, sourceValue);
4201
- const fieldValue = useFieldOverlay(
4202
- context?.layoutNodeId,
4203
- editName,
4204
- sourceValue
4205
- );
4206
- if (!context && !hasExplicitValue) {
4207
- console.warn(
4208
- "[AmplifyUp SDK] RichText needs a `value` prop (or string children), or deprecated `name` alone"
4209
- );
4210
- return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
4165
+ if (!pageConfig) {
4166
+ return null;
4211
4167
  }
4212
- warnProjectionProps("RichText", name, hasExplicitValue);
4213
- const canEdit = !!context && canInlineEditRichText(editName, fieldValue);
4214
- if (isComposerEmptyText(fieldValue)) {
4215
- if (composerEdit && canEdit && !isReactNodeFallback(fallback) && !classNameIsBlock(className)) {
4168
+ pageConfig.layout?.header !== false;
4169
+ pageConfig.layout?.footer !== false;
4170
+ const renderNode = (node) => {
4171
+ const zoneIdFromProps = node.componentId === "AmplifyZone" ? String(node.props?.zoneId || "") : "";
4172
+ if (node.componentId === "AmplifyZone") {
4173
+ const contentSlot = node.slots?.find((s) => s.slotId === "content") || node.slots?.[0];
4174
+ const children = Array.isArray(contentSlot?.children) ? contentSlot.children : [];
4216
4175
  return /* @__PURE__ */ jsx(
4217
- ComposerInlineField,
4176
+ "div",
4218
4177
  {
4219
- name: editName,
4220
- value: fieldValue,
4221
- fallback: typeof fallback === "string" || typeof fallback === "number" ? fallback : void 0,
4222
- className,
4223
- layoutNodeId: context?.layoutNodeId,
4224
- componentId: context?.componentId,
4225
- rich: true
4178
+ "data-node-id": node.id,
4179
+ "data-amplifyup-node": "",
4180
+ "data-amplifyup-component": "AmplifyZone",
4181
+ "data-amplifyup-zone": zoneIdFromProps || void 0,
4182
+ "data-zone-id": zoneIdFromProps || void 0,
4183
+ children: /* @__PURE__ */ jsx(
4184
+ "div",
4185
+ {
4186
+ "data-slot-id": "content",
4187
+ "data-amplifyup-slot": "content",
4188
+ "data-parent-node-id": node.id,
4189
+ "data-parent-component-id": "AmplifyZone",
4190
+ children: children.filter(Boolean).map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child) }, child.id))
4191
+ }
4192
+ )
4226
4193
  }
4227
4194
  );
4228
4195
  }
4229
- return fallback ? /* @__PURE__ */ jsx(Tag, { className, children: fallback }) : null;
4230
- }
4231
- let html = "";
4232
- if (typeof fieldValue === "string") {
4233
- const trimmed = fieldValue.trim();
4234
- if (trimmed.startsWith("<") && trimmed.includes(">")) {
4235
- html = fieldValue;
4236
- } else {
4237
- html = markdownToHtml(fieldValue);
4238
- }
4239
- } else {
4240
- const text = formatFieldValue(fieldValue);
4241
- html = text ? markdownToHtml(text) : "";
4242
- }
4243
- if (!html) {
4244
- return fallback ? /* @__PURE__ */ jsx(Tag, { className, children: fallback }) : null;
4245
- }
4246
- if (composerEdit && canEdit) {
4247
- return /* @__PURE__ */ jsx(
4248
- ComposerInlineHtml,
4249
- {
4250
- name: editName,
4251
- html,
4252
- sourceValue: typeof fieldValue === "string" ? fieldValue : html,
4253
- className,
4254
- Tag,
4255
- layoutNodeId: context?.layoutNodeId,
4256
- componentId: context?.componentId
4196
+ let slotChildren = {};
4197
+ sdkDebugOnce(`node:${node.id}`, `[AmplifyRenderer] Rendering node ${node.id} (${node.componentId})`, {
4198
+ hasSlots: !!(node.slots && node.slots.length > 0),
4199
+ slotCount: node.slots?.length || 0,
4200
+ slots: node.slots?.map((s) => ({ slotId: s.slotId, zoneId: s.zoneId, childrenCount: s.children?.length || 0 }))
4201
+ });
4202
+ if (node.slots && node.slots.length > 0) {
4203
+ for (const slot of node.slots) {
4204
+ const slotKey = `${node.id}-${slot.slotId}`;
4205
+ let childrenToRender = [];
4206
+ if (slot.zoneId && slot.variants && variantCache[slotKey]) {
4207
+ childrenToRender = variantCache[slotKey].children;
4208
+ } else if (slot.zoneId && slot.variants) {
4209
+ const defaultVariant = slot.variants.find((v) => v.isDefault);
4210
+ childrenToRender = defaultVariant?.children || [];
4211
+ } else {
4212
+ childrenToRender = Array.isArray(slot.children) ? slot.children : [];
4213
+ }
4214
+ const safeChildren = Array.isArray(childrenToRender) ? childrenToRender : [];
4215
+ slotChildren[slot.slotId] = /* @__PURE__ */ jsx(
4216
+ "div",
4217
+ {
4218
+ "data-slot-id": slot.slotId,
4219
+ "data-amplifyup-slot": slot.slotId,
4220
+ "data-zone-id": slot.zoneId || void 0,
4221
+ "data-parent-node-id": node.id,
4222
+ "data-parent-component-id": node.componentId,
4223
+ children: safeChildren.filter(Boolean).map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child) }, child.id))
4224
+ },
4225
+ slot.slotId
4226
+ );
4257
4227
  }
4258
- );
4259
- }
4260
- return /* @__PURE__ */ jsx(Tag, { className, dangerouslySetInnerHTML: { __html: html } });
4261
- }
4262
- function isPortableImageValue(value) {
4263
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
4264
- const url = value.url;
4265
- return typeof url === "string" && url.trim().length > 0;
4266
- }
4267
- function imageSrc(value) {
4268
- if (typeof value === "string") return value.trim();
4269
- if (isPortableImageValue(value)) return value.url.trim();
4270
- return "";
4271
- }
4272
- function Image(props) {
4273
- const {
4274
- value,
4275
- name,
4276
- alt: altProp,
4277
- className,
4278
- width: widthProp,
4279
- height: heightProp,
4280
- ...imgProps
4281
- } = props;
4282
- const context = useContext(ComponentContext);
4283
- const hasExplicitValue = hasOwnProp(props, "value");
4284
- const sourceValue = hasExplicitValue ? value : name ? context?.props?.[name] : void 0;
4285
- const fieldValue = useFieldOverlay(
4286
- context?.layoutNodeId,
4287
- inferEditName(name, context?.props, sourceValue),
4288
- sourceValue
4289
- );
4290
- const composerEdit = useComposerEditMode();
4291
- const editName = inferEditName(name, context?.props, sourceValue);
4292
- warnProjectionProps("Image", name, hasExplicitValue);
4293
- if (!context && !hasExplicitValue) {
4294
- console.warn(
4295
- "[AmplifyUp SDK] Image needs a `value` prop (or deprecated `name` alone)"
4296
- );
4297
- return null;
4298
- }
4299
- const src = imageSrc(fieldValue);
4300
- const portable = isPortableImageValue(fieldValue) ? fieldValue : null;
4301
- const alt = altProp ?? portable?.alt ?? "";
4302
- const width = widthProp ?? portable?.width;
4303
- const height = heightProp ?? portable?.height;
4304
- const fieldAttrs = composerEdit && editName ? {
4305
- "data-amplifyup-field": editName,
4306
- "data-amplifyup-field-type": "image",
4307
- "data-amplifyup-node-id": context?.layoutNodeId || void 0
4308
- } : null;
4309
- if (composerEdit && !editName && (hasExplicitValue || name)) {
4310
- console.warn(
4311
- '[AmplifyUp SDK] Image could not infer a Composer field name from value={\u2026}. Pass name="yourProp" (same as the component prop) so the image can be selected.'
4312
- );
4313
- }
4314
- if (!src) {
4315
- if (!composerEdit || !fieldAttrs) return null;
4228
+ }
4229
+ const slotsToPass = Object.keys(slotChildren).length > 0 ? slotChildren : void 0;
4316
4230
  return /* @__PURE__ */ jsx(
4317
- "span",
4231
+ "div",
4318
4232
  {
4319
- ...fieldAttrs,
4320
- className,
4321
- role: "img",
4322
- "aria-label": alt || editName || "Image",
4323
- style: {
4324
- display: "block",
4325
- boxSizing: "border-box",
4326
- minWidth: width ? void 0 : 120,
4327
- minHeight: height ? void 0 : 80,
4328
- width: width || "100%",
4329
- height: height || void 0,
4330
- background: "color-mix(in srgb, currentColor 8%, transparent)",
4331
- outline: "2px dashed hsl(82 100% 45%)",
4332
- outlineOffset: 2,
4333
- cursor: "pointer"
4334
- }
4233
+ "data-node-id": node.id,
4234
+ "data-amplifyup-node": "",
4235
+ "data-component-id": node.componentId,
4236
+ "data-amplifyup-component": node.componentId,
4237
+ children: /* @__PURE__ */ jsx(
4238
+ ComponentRenderBoundary,
4239
+ {
4240
+ componentId: node.componentId,
4241
+ nodeId: node.id,
4242
+ children: /* @__PURE__ */ jsx(
4243
+ NodeWithFields,
4244
+ {
4245
+ nodeId: node.id,
4246
+ componentId: node.componentId,
4247
+ props: nodeRenderProps(node),
4248
+ slots: slotsToPass,
4249
+ renderComponent,
4250
+ composerPreview
4251
+ }
4252
+ )
4253
+ }
4254
+ )
4335
4255
  }
4336
4256
  );
4257
+ };
4258
+ const rawTree = pageConfig.layoutTree;
4259
+ const layoutTree = Array.isArray(rawTree) ? rawTree : rawTree && typeof rawTree === "object" && rawTree.componentId ? [rawTree] : [];
4260
+ if (layoutTree.length === 0) {
4261
+ return /* @__PURE__ */ jsx("main", { children: emptyComponent || /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No content configured for this page." }) }) });
4337
4262
  }
4338
- return /* @__PURE__ */ jsx(
4339
- "img",
4263
+ return /* @__PURE__ */ jsx("main", { children: composerPreview || isComposerPreview() ? /* @__PURE__ */ jsx(
4264
+ "div",
4340
4265
  {
4341
- ...imgProps,
4342
- ...fieldAttrs,
4343
- src,
4344
- alt,
4345
- className,
4346
- width,
4347
- height,
4348
- style: {
4349
- ...typeof imgProps.style === "object" && imgProps.style ? imgProps.style : {},
4350
- ...composerEdit && fieldAttrs ? { cursor: "pointer" } : null
4351
- }
4266
+ "data-slot-id": "main",
4267
+ "data-amplifyup-slot": "main",
4268
+ "data-slot-source": "page",
4269
+ "data-slot-empty": layoutTree.length === 0 ? "true" : void 0,
4270
+ children: layoutTree.map((node) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(node) }, node.id))
4352
4271
  }
4353
- );
4354
- }
4355
- function nodeRenderProps(node) {
4356
- return {
4357
- ...node.settings || {},
4358
- ...node.props || {}
4359
- };
4272
+ ) : layoutTree.map((node) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(node) }, node.id)) });
4360
4273
  }
4361
- var ComponentRenderBoundary = class extends Component {
4362
- constructor() {
4363
- super(...arguments);
4364
- this.state = { error: null };
4365
- }
4366
- static getDerivedStateFromError(error) {
4367
- return { error };
4368
- }
4369
- componentDidCatch(error) {
4370
- console.error(
4371
- `[AmplifyRenderer] "${this.props.componentId}" (${this.props.nodeId}) threw while rendering:`,
4372
- error
4373
- );
4374
- }
4375
- render() {
4376
- if (this.state.error) {
4377
- if (process.env.NODE_ENV !== "production") {
4378
- return /* @__PURE__ */ jsxs(
4379
- "div",
4274
+ var AmplifyRenderer = memo(AmplifyRendererInner);
4275
+ function AddComponentIcon({ size = 28 }) {
4276
+ return /* @__PURE__ */ jsxs(
4277
+ "svg",
4278
+ {
4279
+ width: size,
4280
+ height: size,
4281
+ viewBox: "0 0 24 24",
4282
+ fill: "none",
4283
+ xmlns: "http://www.w3.org/2000/svg",
4284
+ "aria-hidden": "true",
4285
+ focusable: "false",
4286
+ children: [
4287
+ /* @__PURE__ */ jsx(
4288
+ "rect",
4380
4289
  {
4381
- "data-amplifyup-render-error": "",
4382
- "data-component-id": this.props.componentId,
4383
- "data-node-id": this.props.nodeId,
4384
- style: {
4385
- margin: "1rem 0",
4386
- padding: "0.75rem 1rem",
4387
- border: "1px solid #fca5a5",
4388
- borderRadius: "0.5rem",
4389
- background: "#fef2f2",
4390
- color: "#991b1b",
4391
- fontSize: "0.875rem"
4392
- },
4393
- children: [
4394
- 'Component "',
4395
- this.props.componentId,
4396
- '" failed to render:',
4397
- " ",
4398
- this.state.error.message
4399
- ]
4290
+ x: "3",
4291
+ y: "3",
4292
+ width: "18",
4293
+ height: "18",
4294
+ rx: "4",
4295
+ stroke: "currentColor",
4296
+ strokeWidth: "1.75"
4400
4297
  }
4401
- );
4402
- }
4403
- return null;
4298
+ ),
4299
+ /* @__PURE__ */ jsx(
4300
+ "path",
4301
+ {
4302
+ d: "M12 8v8M8 12h8",
4303
+ stroke: "currentColor",
4304
+ strokeWidth: "1.75",
4305
+ strokeLinecap: "round"
4306
+ }
4307
+ )
4308
+ ]
4404
4309
  }
4405
- return this.props.children;
4406
- }
4407
- };
4408
- function LiveNodeRender({
4409
- nodeId,
4410
- componentId,
4411
- props,
4412
- slots,
4413
- renderComponent
4414
- }) {
4415
- const liveProps = useLiveComposerProps(nodeId, props);
4416
- return /* @__PURE__ */ jsx(Fragment, { children: renderComponent(componentId, liveProps, slots, {
4417
- layoutNodeId: nodeId
4418
- }) });
4310
+ );
4419
4311
  }
4420
- function AmplifyRendererInner({
4421
- renderComponent,
4422
- pageConfig: serverPageConfig,
4423
- loadingComponent,
4424
- errorComponent,
4425
- emptyComponent,
4426
- composerPreview
4312
+ function EmptySlotHint({
4313
+ label = "Click here to Add Component",
4314
+ compact = false
4427
4315
  }) {
4428
- const clientHook = useAmplifyUp();
4429
- const pageConfig = serverPageConfig || clientHook.pageConfig || null;
4430
- const loading = serverPageConfig ? false : clientHook.loading;
4431
- const error = serverPageConfig ? null : clientHook.error;
4432
- const contentSource = clientHook.source;
4433
- const [variantCache, setVariantCache] = useState({});
4434
- useEffect(() => {
4435
- if (typeof window !== "undefined") {
4436
- if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
4437
- return;
4438
- }
4439
- if (window.location.pathname.includes("/api/pages/proxy")) {
4440
- return;
4441
- }
4442
- if (window.self !== window.top) {
4443
- return;
4444
- }
4445
- const urlParams = new URLSearchParams(window.location.search);
4446
- if (urlParams.get("preview") === "true" || urlParams.get("visualEditor") === "true") {
4447
- return;
4448
- }
4449
- if (window.location.href.includes("preview=true") || window.location.href.includes("visualEditor=true")) {
4450
- return;
4451
- }
4452
- }
4453
- if (pageConfig && pageConfig.id !== "default") {
4454
- trackContentView(pageConfig.id, "page", {
4455
- route: pageConfig.route,
4456
- title: pageConfig.title
4457
- });
4458
- }
4459
- }, [pageConfig]);
4460
- useEffect(() => {
4461
- if (!pageConfig?.eventTracking?.length) return;
4462
- const sdk = getSDK();
4463
- if (!sdk.isInitialized()) return;
4464
- sdk.setupEventTracking(pageConfig.eventTracking);
4465
- const t1 = window.setTimeout(() => {
4466
- sdk.setupComponentViewTracking();
4467
- refreshEventTracking();
4468
- }, 200);
4469
- const t2 = window.setTimeout(() => {
4470
- sdk.setupComponentViewTracking();
4471
- refreshEventTracking();
4472
- }, 1e3);
4473
- return () => {
4474
- window.clearTimeout(t1);
4475
- window.clearTimeout(t2);
4476
- };
4477
- }, [pageConfig?.id, pageConfig?.eventTracking]);
4478
- useEffect(() => {
4479
- if (!pageConfig) return;
4480
- const t = window.setTimeout(() => {
4481
- getSDK().setupComponentViewTracking();
4482
- refreshEventTracking();
4483
- }, 100);
4484
- return () => window.clearTimeout(t);
4485
- }, [variantCache, pageConfig?.id]);
4486
- useEffect(() => {
4487
- const isVisualEditorMode = () => {
4488
- if (typeof window === "undefined") return false;
4489
- const urlParams = new URLSearchParams(window.location.search);
4490
- if (urlParams.get("visualEditor") === "true" || urlParams.get("preview") === "true") {
4491
- return true;
4492
- }
4493
- const inIframe = window.self !== window.top;
4494
- if (!inIframe) return false;
4495
- try {
4496
- if (document.referrer) {
4497
- const referrerUrl = new URL(document.referrer);
4498
- if (referrerUrl.hostname === "localhost" && referrerUrl.port === "5001") {
4499
- return true;
4316
+ const iconSize = compact ? 20 : 28;
4317
+ return /* @__PURE__ */ jsxs(
4318
+ "div",
4319
+ {
4320
+ className: "amplifyup-slot-placeholder",
4321
+ style: {
4322
+ display: "flex",
4323
+ flexDirection: "column",
4324
+ alignItems: "center",
4325
+ justifyContent: "center",
4326
+ gap: compact ? 8 : 12,
4327
+ minHeight: compact ? 60 : void 0,
4328
+ width: "100%",
4329
+ pointerEvents: "none",
4330
+ textAlign: "center",
4331
+ boxSizing: "border-box",
4332
+ padding: compact ? "12px 8px" : "24px 16px"
4333
+ },
4334
+ children: [
4335
+ /* @__PURE__ */ jsx(
4336
+ "span",
4337
+ {
4338
+ className: "amplifyup-slot-placeholder-icon",
4339
+ style: {
4340
+ display: "inline-flex",
4341
+ alignItems: "center",
4342
+ justifyContent: "center",
4343
+ width: compact ? 36 : 48,
4344
+ height: compact ? 36 : 48,
4345
+ borderRadius: 12,
4346
+ border: "1.5px dashed currentColor",
4347
+ opacity: 0.9
4348
+ },
4349
+ children: /* @__PURE__ */ jsx(AddComponentIcon, { size: iconSize })
4500
4350
  }
4501
- }
4502
- } catch (e) {
4503
- }
4504
- return false;
4505
- };
4506
- if (!isVisualEditorMode()) {
4507
- return;
4351
+ ),
4352
+ /* @__PURE__ */ jsx(
4353
+ "span",
4354
+ {
4355
+ className: "amplifyup-slot-placeholder-label",
4356
+ style: {
4357
+ fontSize: compact ? 13 : 15,
4358
+ fontWeight: 600,
4359
+ lineHeight: 1.3,
4360
+ letterSpacing: "0.01em"
4361
+ },
4362
+ children: label
4363
+ }
4364
+ )
4365
+ ]
4508
4366
  }
4509
- if (!pageConfig) {
4510
- return;
4367
+ );
4368
+ }
4369
+ function defaultPreviewCanvas() {
4370
+ return /* @__PURE__ */ jsx(
4371
+ "div",
4372
+ {
4373
+ "data-slot-id": "main",
4374
+ "data-amplifyup-slot": "main",
4375
+ "data-slot-source": "page",
4376
+ "data-slot-empty": "true",
4377
+ style: { minHeight: "50vh", width: "100%", display: "block", position: "relative" },
4378
+ children: /* @__PURE__ */ jsx(EmptySlotHint, {})
4511
4379
  }
4512
- const rawTree2 = pageConfig.layoutTree;
4513
- const normalizedTree = Array.isArray(rawTree2) ? rawTree2 : rawTree2 && typeof rawTree2 === "object" && rawTree2.componentId ? [rawTree2] : [];
4514
- if (normalizedTree.length === 0) {
4515
- if (typeof window !== "undefined" && window.parent && window.parent !== window) {
4516
- window.parent.postMessage({
4517
- type: "COMPONENT_TREE",
4518
- tree: []
4519
- }, "*");
4380
+ );
4381
+ }
4382
+ function hasAmplifyLayout(pageConfig) {
4383
+ return !!(pageConfig && pageConfig.id !== "default" && Array.isArray(pageConfig.layoutTree) && pageConfig.layoutTree.length > 0);
4384
+ }
4385
+ function normalizeRoutePath(route) {
4386
+ if (!route) return "";
4387
+ const path = route.split("?")[0] || "/";
4388
+ if (path.length > 1 && path.endsWith("/")) return path.replace(/\/+$/, "");
4389
+ return path || "/";
4390
+ }
4391
+ function samePageRoute(a, b) {
4392
+ const ar = normalizeRoutePath(a?.route);
4393
+ const br = normalizeRoutePath(b?.route);
4394
+ return !!ar && !!br && ar === br;
4395
+ }
4396
+ function layoutTreesEqual(a, b) {
4397
+ if (!a || !b) return a === b;
4398
+ return JSON.stringify(a.layoutTree) === JSON.stringify(b.layoutTree);
4399
+ }
4400
+ function AmplifyPageContent({
4401
+ fallback,
4402
+ renderComponent,
4403
+ pageConfig: pageConfigProp,
4404
+ forceComposerPreview = false,
4405
+ previewLoadingComponent,
4406
+ previewEmptyComponent,
4407
+ loadingComponent,
4408
+ emptyComponent,
4409
+ errorComponent
4410
+ }) {
4411
+ const client = useAmplifyUp();
4412
+ const preview = forceComposerPreview || isComposerPreview();
4413
+ const clientLayout = hasAmplifyLayout(client.pageConfig) ? client.pageConfig : null;
4414
+ const serverLayout = hasAmplifyLayout(pageConfigProp) ? pageConfigProp : null;
4415
+ let pageConfig = serverLayout ?? clientLayout;
4416
+ if (preview && clientLayout) {
4417
+ pageConfig = clientLayout;
4418
+ } else if (clientLayout && serverLayout && samePageRoute(clientLayout, serverLayout) && !layoutTreesEqual(clientLayout, serverLayout)) {
4419
+ pageConfig = clientLayout;
4420
+ } else if (clientLayout && !serverLayout) {
4421
+ pageConfig = clientLayout;
4422
+ }
4423
+ const loading = !hasAmplifyLayout(pageConfig) && client.loading;
4424
+ const error = hasAmplifyLayout(pageConfig) ? null : pageConfigProp ? null : client.error;
4425
+ const previewCanvas = previewEmptyComponent ?? defaultPreviewCanvas();
4426
+ const previewLoading = previewLoadingComponent ?? previewCanvas;
4427
+ if (preview) {
4428
+ return /* @__PURE__ */ jsx(
4429
+ AmplifyRenderer,
4430
+ {
4431
+ pageConfig: pageConfig ?? void 0,
4432
+ renderComponent,
4433
+ composerPreview: true,
4434
+ loadingComponent: loadingComponent ?? previewLoading,
4435
+ emptyComponent: emptyComponent ?? previewCanvas,
4436
+ errorComponent: errorComponent ?? (() => previewCanvas)
4520
4437
  }
4521
- return;
4522
- }
4523
- const convertLayoutTreeToComponentTree = (nodes, parentId = null) => {
4524
- const result = [];
4525
- nodes.forEach((node) => {
4526
- if (!node) return;
4527
- const treeNode = {
4528
- id: node.id,
4529
- componentId: node.componentId,
4530
- componentName: node.componentId,
4531
- // Will be enhanced with actual name if available
4532
- parentId,
4533
- children: []
4534
- };
4535
- if (node.slots && node.slots.length > 0) {
4536
- node.slots.forEach((slot) => {
4537
- const kids = Array.isArray(slot?.children) ? slot.children : [];
4538
- if (kids.length > 0) {
4539
- const slotChildren = convertLayoutTreeToComponentTree(kids, node.id);
4540
- treeNode.children.push(...slotChildren);
4541
- }
4542
- });
4543
- }
4544
- result.push(treeNode);
4545
- });
4546
- return result;
4547
- };
4548
- const layoutTree2 = normalizedTree;
4549
- const componentTree = convertLayoutTreeToComponentTree(layoutTree2);
4550
- const describeLayoutTree = (nodes, depth = 0) => {
4551
- if (!nodes?.length) return "";
4552
- const indent = " ".repeat(depth);
4553
- return nodes.map((n) => {
4554
- const slotsDesc = (n.slots || []).map((s) => {
4555
- const childCount = Array.isArray(s.children) ? s.children.length : 0;
4556
- const childrenDesc = childCount > 0 ? describeLayoutTree(s.children || [], depth + 1) : "";
4557
- return `${indent} slot "${s.slotId || s.slotName || "?"}" (${childCount} children)${childrenDesc ? "\n" + childrenDesc : ""}`;
4558
- }).join("\n");
4559
- return `${indent}- ${n.id} (${n.componentId || "?"})${slotsDesc ? "\n" + slotsDesc : ""}`;
4560
- }).join("\n");
4561
- };
4562
- sdkDebugOnce(
4563
- `layoutTree:${pageConfig?.id || ""}:${pageConfig?.route || ""}`,
4564
- "[AmplifyRenderer] layoutTree received:\n%s",
4565
- describeLayoutTree(layoutTree2)
4566
4438
  );
4567
- if (typeof window !== "undefined" && window.parent && window.parent !== window) {
4568
- const message = {
4569
- type: "COMPONENT_TREE",
4570
- tree: componentTree
4571
- };
4572
- window.parent.postMessage(message, "*");
4573
- sdkDebug(
4574
- "[AmplifyRenderer] Sent component tree from layoutTree:",
4575
- componentTree.length,
4576
- "root components"
4577
- );
4578
- }
4579
- }, [pageConfig]);
4580
- useEffect(() => {
4581
- if (!pageConfig?.layoutTree || !pageConfig.layoutId) return;
4582
- const roots = pageConfig.layoutTree;
4583
- const fromEdge = contentSource === "edge" || roots.some((n) => n._contentSource === "edge");
4584
- if (fromEdge) return;
4585
- const sdk = getSDK();
4586
- const instanceId = sdk.getInstanceId();
4587
- const decisionApiUrl = sdk.getDecisionApiUrl();
4588
- const orchestratorUrl = sdk.getOrchestratorUrl();
4589
- if (!decisionApiUrl && !orchestratorUrl || !instanceId) return;
4590
- const fetchVariants = async () => {
4591
- const nodes = pageConfig.layoutTree;
4592
- const newCache = {};
4593
- const findZonedSlots = (nodeList) => {
4594
- const results = [];
4595
- for (const node of nodeList) {
4596
- if (node.slots) {
4597
- for (const slot of node.slots) {
4598
- if (slot.zoneId && slot.variants) {
4599
- const key = `${node.id}-${slot.slotId}`;
4600
- results.push({ node, slot, key });
4601
- }
4602
- }
4603
- }
4604
- }
4605
- return results;
4606
- };
4607
- const zonedSlots = findZonedSlots(nodes);
4608
- await Promise.all(
4609
- zonedSlots.map(async ({ node, slot, key }) => {
4610
- try {
4611
- let personalizationUrl;
4612
- if (decisionApiUrl) {
4613
- const url = new URL(decisionApiUrl);
4614
- url.searchParams.set("instanceId", instanceId);
4615
- const zoneId = slot.zoneId;
4616
- if (zoneId) {
4617
- url.searchParams.set("zoneId", zoneId);
4618
- }
4619
- const campaignId = slot.campaignId;
4620
- if (campaignId) {
4621
- url.searchParams.set("campaignId", campaignId);
4622
- }
4623
- personalizationUrl = url.toString();
4624
- } else if (orchestratorUrl) {
4625
- personalizationUrl = `${orchestratorUrl}/api/personalization/variant?instanceId=${encodeURIComponent(instanceId)}&layoutId=${encodeURIComponent(pageConfig.layoutId)}&slotId=${encodeURIComponent(slot.slotId)}&nodeId=${encodeURIComponent(node.id)}`;
4626
- } else {
4627
- return;
4628
- }
4629
- const response = await fetch(personalizationUrl);
4630
- if (response.ok) {
4631
- const data = await response.json();
4632
- if (data.variantId && data.componentTree) {
4633
- newCache[key] = {
4634
- variantId: data.variantId,
4635
- children: data.componentTree
4636
- };
4637
- if (typeof window !== "undefined") {
4638
- if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
4639
- return;
4640
- }
4641
- if (window.location.pathname.includes("/api/pages/proxy")) {
4642
- return;
4643
- }
4644
- if (window.self !== window.top) {
4645
- return;
4646
- }
4647
- }
4648
- sdk.track("Zone Impression", {
4649
- zoneId: slot.zoneId,
4650
- variantId: data.variantId,
4651
- layoutId: pageConfig.layoutId,
4652
- route: pageConfig.route
4653
- });
4654
- } else {
4655
- const defaultVariant = slot.variants?.find((v) => v.isDefault);
4656
- if (defaultVariant) {
4657
- newCache[key] = {
4658
- variantId: defaultVariant.variantId,
4659
- children: defaultVariant.children
4660
- };
4661
- }
4662
- }
4663
- }
4664
- } catch (err) {
4665
- console.error(`Error fetching variant for slot ${key}:`, err);
4666
- const defaultVariant = slot.variants?.find((v) => v.isDefault);
4667
- if (defaultVariant) {
4668
- newCache[key] = {
4669
- variantId: defaultVariant.variantId,
4670
- children: defaultVariant.children
4671
- };
4672
- }
4673
- }
4674
- })
4675
- );
4676
- setVariantCache(newCache);
4677
- };
4678
- fetchVariants();
4679
- }, [pageConfig?.layoutTree, pageConfig?.layoutId, pageConfig?.route, contentSource]);
4439
+ }
4680
4440
  if (loading) {
4681
- return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50", children: /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsxs("div", { className: "animate-pulse", children: [
4682
- /* @__PURE__ */ jsx("div", { className: "h-8 bg-gray-200 rounded w-1/4 mb-4" }),
4683
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-gray-200 rounded w-1/2 mb-8" }),
4684
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
4685
- /* @__PURE__ */ jsx("div", { className: "h-64 bg-gray-200 rounded" }),
4686
- /* @__PURE__ */ jsx("div", { className: "h-32 bg-gray-200 rounded" })
4687
- ] })
4688
- ] }) }) }) });
4441
+ return loadingComponent ? /* @__PURE__ */ jsx(Fragment, { children: loadingComponent }) : null;
4689
4442
  }
4690
- if (error) {
4691
- return /* @__PURE__ */ jsx(Fragment, { children: errorComponent ? errorComponent(error) : /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50", children: /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsxs("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6", children: [
4692
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-red-900 mb-2", children: "Error Loading Page" }),
4693
- /* @__PURE__ */ jsx("p", { className: "text-red-700", children: error })
4694
- ] }) }) }) });
4443
+ if (error) {
4444
+ if (errorComponent) return /* @__PURE__ */ jsx(Fragment, { children: errorComponent(error) });
4445
+ return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
4695
4446
  }
4696
- if (!pageConfig) {
4447
+ if (!hasAmplifyLayout(pageConfig)) {
4448
+ return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
4449
+ }
4450
+ return /* @__PURE__ */ jsx(
4451
+ AmplifyRenderer,
4452
+ {
4453
+ pageConfig: pageConfig ?? void 0,
4454
+ renderComponent,
4455
+ loadingComponent,
4456
+ emptyComponent,
4457
+ errorComponent
4458
+ }
4459
+ );
4460
+ }
4461
+
4462
+ // src/composer-edit-mode.ts
4463
+ function isComposerEditMode() {
4464
+ if (typeof window === "undefined") return false;
4465
+ return isComposerPreview() && window.__AMPLIFYUP_EVENT_SELECTION__ !== true;
4466
+ }
4467
+ function subscribeComposerEdit(onStoreChange) {
4468
+ if (typeof window === "undefined") return () => {
4469
+ };
4470
+ window.addEventListener("amplifyup:composer-edit", onStoreChange);
4471
+ return () => window.removeEventListener("amplifyup:composer-edit", onStoreChange);
4472
+ }
4473
+ function getComposerEditModeServer() {
4474
+ return false;
4475
+ }
4476
+ var ComponentContext = createContext(null);
4477
+ function ComponentContextProvider({
4478
+ children,
4479
+ slots,
4480
+ props,
4481
+ layoutNodeId,
4482
+ componentId
4483
+ }) {
4484
+ const next = hydratePageResourceProps(props || {}, getPageResourceFields());
4485
+ return /* @__PURE__ */ jsx(
4486
+ ComponentContext.Provider,
4487
+ {
4488
+ value: { slots, props: next, layoutNodeId, componentId },
4489
+ children
4490
+ }
4491
+ );
4492
+ }
4493
+ function Slot({
4494
+ name,
4495
+ className
4496
+ }) {
4497
+ const context = useContext(ComponentContext);
4498
+ if (!context) {
4499
+ console.warn(`[AmplifyUp SDK] Slot "${name}" used outside ComponentContextProvider`);
4697
4500
  return null;
4698
4501
  }
4699
- pageConfig.layout?.header !== false;
4700
- pageConfig.layout?.footer !== false;
4701
- const renderNode = (node) => {
4702
- const zoneIdFromProps = node.componentId === "AmplifyZone" ? String(node.props?.zoneId || "") : "";
4703
- if (node.componentId === "AmplifyZone") {
4704
- const contentSlot = node.slots?.find((s) => s.slotId === "content") || node.slots?.[0];
4705
- const children = Array.isArray(contentSlot?.children) ? contentSlot.children : [];
4706
- return /* @__PURE__ */ jsx(
4502
+ const slotContent = context.slots?.[name];
4503
+ const isEmpty = !slotContent;
4504
+ const isEditorMode = typeof window !== "undefined" && // Check for preview mode flag set by proxy
4505
+ (window.__AMPLIFYUP_PREVIEW_MODE__ === true || // Check URL query params
4506
+ new URLSearchParams(window.location.search).get("preview") === "true" || new URLSearchParams(window.location.search).get("visualEditor") === "true" || // Check if in iframe from orchestrator (check referrer hostname/port)
4507
+ window.self !== window.top && document.referrer && (() => {
4508
+ try {
4509
+ const referrerUrl = new URL(document.referrer);
4510
+ return referrerUrl.hostname === "localhost" && referrerUrl.port === "5001" || referrerUrl.pathname.includes("/dashboard/composer") || referrerUrl.pathname.includes("/dashboard/routes/");
4511
+ } catch {
4512
+ return false;
4513
+ }
4514
+ })());
4515
+ sdkDebugOnce(
4516
+ `slot:${context.layoutNodeId || ""}:${name}`,
4517
+ `[Slot:${name}]`,
4518
+ {
4519
+ isEmpty,
4520
+ isEditorMode,
4521
+ availableSlotKeys: context.slots ? Object.keys(context.slots) : []
4522
+ }
4523
+ );
4524
+ return /* @__PURE__ */ jsx(
4525
+ "div",
4526
+ {
4527
+ className,
4528
+ "data-slot-name": name,
4529
+ "data-slot-id": name,
4530
+ "data-amplifyup-slot": name,
4531
+ "data-slot-empty": isEmpty ? "true" : void 0,
4532
+ "data-parent-node-id": context.layoutNodeId || void 0,
4533
+ "data-parent-component-id": context.componentId || void 0,
4534
+ style: {
4535
+ // Only add minimum height when empty (for drop target visibility)
4536
+ minHeight: isEmpty ? "60px" : void 0,
4537
+ // Only add position relative for overlay positioning in editor mode
4538
+ position: isEditorMode ? "relative" : void 0
4539
+ },
4540
+ children: isEmpty ? isEditorMode ? /* @__PURE__ */ jsx(EmptySlotHint, { compact: true }) : /* @__PURE__ */ jsx(
4707
4541
  "div",
4708
4542
  {
4709
- "data-node-id": node.id,
4710
- "data-amplifyup-node": "",
4711
- "data-amplifyup-component": "AmplifyZone",
4712
- "data-amplifyup-zone": zoneIdFromProps || void 0,
4713
- "data-zone-id": zoneIdFromProps || void 0,
4714
- children: /* @__PURE__ */ jsx(
4715
- "div",
4716
- {
4717
- "data-slot-id": "content",
4718
- "data-amplifyup-slot": "content",
4719
- "data-parent-node-id": node.id,
4720
- "data-parent-component-id": "AmplifyZone",
4721
- children: children.filter(Boolean).map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child) }, child.id))
4722
- }
4723
- )
4543
+ className: "amplifyup-slot-placeholder",
4544
+ style: {
4545
+ minHeight: "60px",
4546
+ color: "#9ca3af",
4547
+ fontSize: "14px",
4548
+ pointerEvents: "none"
4549
+ },
4550
+ children: `Empty slot: ${name}`
4724
4551
  }
4725
- );
4552
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
4553
+ isEditorMode && /* @__PURE__ */ jsxs(
4554
+ "div",
4555
+ {
4556
+ className: "amplifyup-slot-label",
4557
+ style: {
4558
+ position: "absolute",
4559
+ top: "4px",
4560
+ left: "4px",
4561
+ fontSize: "10px",
4562
+ color: "#9ca3af",
4563
+ pointerEvents: "none",
4564
+ zIndex: 1
4565
+ },
4566
+ children: [
4567
+ "Slot: ",
4568
+ name
4569
+ ]
4570
+ }
4571
+ ),
4572
+ slotContent
4573
+ ] })
4574
+ }
4575
+ );
4576
+ }
4577
+ function isLikelyNonTextFieldName(name) {
4578
+ const n = name.toLowerCase();
4579
+ return n.includes("href") || n.includes("url") || n.includes("src") || n.includes("image") || n.includes("icon") || n.includes("overlay") || n.includes("color");
4580
+ }
4581
+ var COMPOSER_EMPTY_FIELD_HINT = "Click here to edit this field";
4582
+ var EMPTY_FIELD_PLACEHOLDERS = /* @__PURE__ */ new Set([
4583
+ COMPOSER_EMPTY_FIELD_HINT,
4584
+ "Click here to enter text",
4585
+ "Sample Title",
4586
+ "Sample Heading",
4587
+ "Sample Subheading"
4588
+ ]);
4589
+ function isComposerEmptyText(value) {
4590
+ if (value === void 0 || value === null) return true;
4591
+ if (typeof value !== "string") return false;
4592
+ const trimmed = value.trim();
4593
+ return trimmed === "" || EMPTY_FIELD_PLACEHOLDERS.has(trimmed);
4594
+ }
4595
+ function canInlineEdit(name, value) {
4596
+ if (!name || isLikelyNonTextFieldName(name)) return false;
4597
+ if (value !== void 0 && value !== null && typeof value === "object") {
4598
+ return false;
4599
+ }
4600
+ if (typeof value === "boolean") return false;
4601
+ return true;
4602
+ }
4603
+ function canInlineEditRichText(name, value) {
4604
+ if (!name || isLikelyNonTextFieldName(name)) return false;
4605
+ if (typeof value === "boolean") return false;
4606
+ if (value !== void 0 && value !== null && typeof value === "object" && !Array.isArray(value)) {
4607
+ return false;
4608
+ }
4609
+ return true;
4610
+ }
4611
+ function useComposerEditMode() {
4612
+ return useSyncExternalStore(
4613
+ subscribeComposerEdit,
4614
+ isComposerEditMode,
4615
+ getComposerEditModeServer
4616
+ );
4617
+ }
4618
+ function useBoundField(field, value, name, component) {
4619
+ const bound = resolveEditableBinding(field, value, name);
4620
+ if (bound.requiresName) {
4621
+ console.warn(
4622
+ `[AmplifyUp SDK] ${component} value={\u2026} requires name="\u2026" for computed values`
4623
+ );
4624
+ }
4625
+ const fieldValue = useComposerFieldValue(bound.name, bound.value);
4626
+ return { editName: bound.name, fieldValue };
4627
+ }
4628
+ function commitComposerField(layoutNodeId, componentId, name, value) {
4629
+ if (typeof window === "undefined" || window.parent === window) return;
4630
+ window.parent.postMessage(
4631
+ {
4632
+ type: "AMPLIFYUP_FIELD_COMMIT",
4633
+ layoutNodeId,
4634
+ componentId,
4635
+ fieldName: name,
4636
+ value
4637
+ },
4638
+ "*"
4639
+ );
4640
+ }
4641
+ function looksLikeRichCanvasField(name) {
4642
+ const tokens = String(name || "").replace(/([a-z])([A-Z])/g, "$1 $2").split(/[._\-\s/]+/).map((s) => s.toLowerCase()).filter(Boolean);
4643
+ const last = tokens[tokens.length - 1] || "";
4644
+ return last === "body" || last === "content" || last === "copy" || last === "excerpt" || last === "markdown" || last === "richtext";
4645
+ }
4646
+ var lastRichTextRequestAt = 0;
4647
+ function requestCanvasRichTextEdit(opts) {
4648
+ if (typeof window === "undefined" || window.parent === window) return;
4649
+ if (!opts.name) return;
4650
+ const now = Date.now();
4651
+ if (now - lastRichTextRequestAt < 400) return;
4652
+ lastRichTextRequestAt = now;
4653
+ const rect = opts.el.getBoundingClientRect();
4654
+ window.parent.postMessage(
4655
+ {
4656
+ type: "AMPLIFYUP_RICHTEXT_EDIT",
4657
+ layoutNodeId: opts.layoutNodeId,
4658
+ componentId: opts.componentId,
4659
+ fieldName: opts.name,
4660
+ value: opts.value,
4661
+ bounds: {
4662
+ top: rect.top,
4663
+ left: rect.left,
4664
+ width: Math.max(rect.width, 240),
4665
+ height: Math.max(rect.height, 64)
4666
+ }
4667
+ },
4668
+ "*"
4669
+ );
4670
+ }
4671
+ function placeCaret(el, atStart = false) {
4672
+ const range = document.createRange();
4673
+ range.selectNodeContents(el);
4674
+ range.collapse(atStart);
4675
+ const sel = window.getSelection();
4676
+ sel?.removeAllRanges();
4677
+ sel?.addRange(range);
4678
+ }
4679
+ function ComposerInlineField({
4680
+ name,
4681
+ value,
4682
+ className,
4683
+ layoutNodeId,
4684
+ componentId,
4685
+ rich = false
4686
+ }) {
4687
+ const [focused, setFocused] = useState(false);
4688
+ const elRef = useRef(null);
4689
+ const storedEmpty = isComposerEmptyText(value);
4690
+ const text = storedEmpty ? "" : formatFieldValue(value);
4691
+ const hint = COMPOSER_EMPTY_FIELD_HINT;
4692
+ const idleDisplay = storedEmpty ? hint : text;
4693
+ const showHint = storedEmpty && !focused;
4694
+ useEffect(() => {
4695
+ const el = elRef.current;
4696
+ if (!el) return;
4697
+ if (document.activeElement === el) return;
4698
+ if (el.querySelector(
4699
+ "input, button, select, textarea, form, svg, iframe, img"
4700
+ )) {
4701
+ return;
4726
4702
  }
4727
- let slotChildren = {};
4728
- sdkDebugOnce(`node:${node.id}`, `[AmplifyRenderer] Rendering node ${node.id} (${node.componentId})`, {
4729
- hasSlots: !!(node.slots && node.slots.length > 0),
4730
- slotCount: node.slots?.length || 0,
4731
- slots: node.slots?.map((s) => ({ slotId: s.slotId, zoneId: s.zoneId, childrenCount: s.children?.length || 0 }))
4703
+ const next = idleDisplay || "";
4704
+ if (el.textContent !== next) el.textContent = next;
4705
+ }, [idleDisplay]);
4706
+ const finishEdit = () => {
4707
+ const raw = (elRef.current?.innerText ?? "").replace(/\u00a0/g, " ").replace(/\n+$/g, "");
4708
+ const next = raw.trim() === "" ? "" : raw;
4709
+ setFocused(false);
4710
+ if (storedEmpty && next === "") {
4711
+ if (elRef.current) elRef.current.textContent = hint;
4712
+ if (typeof value === "string" && EMPTY_FIELD_PLACEHOLDERS.has(value.trim())) {
4713
+ commitComposerField(layoutNodeId, componentId, name, "");
4714
+ }
4715
+ return;
4716
+ }
4717
+ if (next === text) return;
4718
+ commitComposerField(layoutNodeId, componentId, name, next);
4719
+ };
4720
+ const openRichEditor = (event) => {
4721
+ if (!isComposerEditMode() || !elRef.current) return;
4722
+ event?.preventDefault();
4723
+ event?.stopPropagation();
4724
+ const source = typeof value === "string" && !storedEmpty ? value : text || "";
4725
+ requestCanvasRichTextEdit({
4726
+ el: elRef.current,
4727
+ layoutNodeId,
4728
+ componentId,
4729
+ name,
4730
+ value: source
4732
4731
  });
4733
- if (node.slots && node.slots.length > 0) {
4734
- for (const slot of node.slots) {
4735
- const slotKey = `${node.id}-${slot.slotId}`;
4736
- let childrenToRender = [];
4737
- if (slot.zoneId && slot.variants && variantCache[slotKey]) {
4738
- childrenToRender = variantCache[slotKey].children;
4739
- } else if (slot.zoneId && slot.variants) {
4740
- const defaultVariant = slot.variants.find((v) => v.isDefault);
4741
- childrenToRender = defaultVariant?.children || [];
4742
- } else {
4743
- childrenToRender = Array.isArray(slot.children) ? slot.children : [];
4732
+ };
4733
+ return /* @__PURE__ */ jsx(
4734
+ "span",
4735
+ {
4736
+ ref: elRef,
4737
+ className,
4738
+ "data-amplifyup-field": name,
4739
+ "data-amplifyup-field-type": rich ? "richtext" : "text",
4740
+ "data-amplifyup-node-id": layoutNodeId || void 0,
4741
+ "data-amplifyup-empty-hint": showHint ? "true" : void 0,
4742
+ contentEditable: !rich,
4743
+ suppressContentEditableWarning: true,
4744
+ role: "textbox",
4745
+ tabIndex: 0,
4746
+ onMouseDown: (event) => {
4747
+ event.stopPropagation();
4748
+ },
4749
+ onClick: (event) => {
4750
+ if (!isComposerEditMode()) return;
4751
+ const link = event.target?.closest?.("a");
4752
+ if (link) event.preventDefault();
4753
+ if (rich) {
4754
+ openRichEditor(event);
4755
+ return;
4744
4756
  }
4745
- const safeChildren = Array.isArray(childrenToRender) ? childrenToRender : [];
4746
- slotChildren[slot.slotId] = /* @__PURE__ */ jsx(
4747
- "div",
4748
- {
4749
- "data-slot-id": slot.slotId,
4750
- "data-amplifyup-slot": slot.slotId,
4751
- "data-zone-id": slot.zoneId || void 0,
4752
- "data-parent-node-id": node.id,
4753
- "data-parent-component-id": node.componentId,
4754
- children: safeChildren.filter(Boolean).map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child) }, child.id))
4755
- },
4756
- slot.slotId
4757
- );
4757
+ },
4758
+ onFocus: () => {
4759
+ if (!isComposerEditMode()) return;
4760
+ if (rich) {
4761
+ openRichEditor();
4762
+ return;
4763
+ }
4764
+ setFocused(true);
4765
+ const el = elRef.current;
4766
+ if (el && storedEmpty) {
4767
+ el.textContent = "";
4768
+ placeCaret(el, true);
4769
+ }
4770
+ },
4771
+ onBlur: rich ? void 0 : finishEdit,
4772
+ onKeyDown: (event) => {
4773
+ event.stopPropagation();
4774
+ if (event.key === "Enter" && !event.shiftKey) {
4775
+ event.preventDefault();
4776
+ event.currentTarget.blur();
4777
+ }
4778
+ if (event.key === "Escape") {
4779
+ event.preventDefault();
4780
+ if (elRef.current) elRef.current.textContent = idleDisplay || "";
4781
+ setFocused(false);
4782
+ event.currentTarget.blur();
4783
+ }
4784
+ },
4785
+ style: {
4786
+ cursor: "text",
4787
+ outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
4788
+ outlineOffset: "2px",
4789
+ minWidth: "1ch",
4790
+ minHeight: "1em",
4791
+ // Do not force `inline` — that overrides Tailwind `block` and parks
4792
+ // an empty-field hint on the same line as a sibling that has a value.
4793
+ opacity: showHint ? 0.5 : void 0
4758
4794
  }
4759
4795
  }
4760
- const slotsToPass = Object.keys(slotChildren).length > 0 ? slotChildren : void 0;
4796
+ );
4797
+ }
4798
+ function ComposerInlineHtml({
4799
+ name,
4800
+ html,
4801
+ sourceValue,
4802
+ className,
4803
+ Tag = "div",
4804
+ layoutNodeId,
4805
+ componentId
4806
+ }) {
4807
+ const elRef = useRef(null);
4808
+ useEffect(() => {
4809
+ const el = elRef.current;
4810
+ if (!el) return;
4811
+ if (el.innerHTML !== html) el.innerHTML = html;
4812
+ }, [html]);
4813
+ const openRichEditor = (event) => {
4814
+ if (!elRef.current) return;
4815
+ event.preventDefault();
4816
+ event.stopPropagation();
4817
+ requestCanvasRichTextEdit({
4818
+ el: elRef.current,
4819
+ layoutNodeId,
4820
+ componentId,
4821
+ name,
4822
+ value: sourceValue
4823
+ });
4824
+ };
4825
+ return React2.createElement(Tag, {
4826
+ ref: elRef,
4827
+ className,
4828
+ "data-amplifyup-field": name,
4829
+ "data-amplifyup-field-type": "richtext",
4830
+ "data-amplifyup-node-id": layoutNodeId || void 0,
4831
+ dangerouslySetInnerHTML: { __html: html },
4832
+ onMouseDown: (event) => {
4833
+ event.stopPropagation();
4834
+ },
4835
+ onClick: openRichEditor,
4836
+ style: {
4837
+ cursor: "text",
4838
+ outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
4839
+ outlineOffset: "2px"
4840
+ }
4841
+ });
4842
+ }
4843
+ function formatFieldValue(value) {
4844
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4845
+ return String(value);
4846
+ }
4847
+ if (Array.isArray(value)) {
4848
+ const parts = value.map((item) => {
4849
+ if (typeof item === "string") return item;
4850
+ if (item && typeof item === "object" && "children" in item && Array.isArray(item.children)) {
4851
+ return item.children.map((c) => typeof c?.text === "string" ? c.text : "").join("");
4852
+ }
4853
+ return "";
4854
+ }).filter(Boolean);
4855
+ if (parts.length > 0) return parts.join(" ");
4856
+ }
4857
+ if (value && typeof value === "object") {
4858
+ return "";
4859
+ }
4860
+ return "";
4861
+ }
4862
+ function warnProjectionProps(component, hasField, hasValue, name) {
4863
+ if (process.env.NODE_ENV === "production") return;
4864
+ if (!hasField && !hasValue && !name) {
4865
+ console.warn(
4866
+ `[AmplifyUp SDK] ${component} needs field={fields.yourProp} (or value + name for computed values)`
4867
+ );
4868
+ }
4869
+ }
4870
+ function hasOwnProp(props, key) {
4871
+ return Object.prototype.hasOwnProperty.call(props, key);
4872
+ }
4873
+ function Field(props) {
4874
+ const { field, value, name, className } = props;
4875
+ const context = useContext(ComponentContext);
4876
+ const composerEdit = useComposerEditMode();
4877
+ const hasField = isFieldEnvelope(field);
4878
+ const hasValue = hasOwnProp(props, "value");
4879
+ const { editName, fieldValue } = useBoundField(field, value, name, "Field");
4880
+ warnProjectionProps("Field", hasField, hasValue, name);
4881
+ if (!hasField && !hasValue && !name) {
4882
+ return null;
4883
+ }
4884
+ if (Array.isArray(fieldValue)) {
4885
+ return null;
4886
+ }
4887
+ const empty = isComposerEmptyText(fieldValue);
4888
+ if (composerEdit && editName && context && canInlineEdit(editName, fieldValue)) {
4761
4889
  return /* @__PURE__ */ jsx(
4762
- "div",
4890
+ ComposerInlineField,
4763
4891
  {
4764
- "data-node-id": node.id,
4765
- "data-amplifyup-node": "",
4766
- "data-component-id": node.componentId,
4767
- "data-amplifyup-component": node.componentId,
4768
- children: /* @__PURE__ */ jsx(
4769
- ComponentRenderBoundary,
4770
- {
4771
- componentId: node.componentId,
4772
- nodeId: node.id,
4773
- children: /* @__PURE__ */ jsx(
4774
- LiveNodeRender,
4775
- {
4776
- nodeId: node.id,
4777
- componentId: node.componentId,
4778
- props: nodeRenderProps(node),
4779
- slots: slotsToPass,
4780
- renderComponent
4781
- }
4782
- )
4783
- }
4784
- )
4892
+ name: editName,
4893
+ value: fieldValue,
4894
+ className,
4895
+ layoutNodeId: context.layoutNodeId,
4896
+ componentId: context.componentId,
4897
+ rich: looksLikeRichCanvasField(editName)
4898
+ }
4899
+ );
4900
+ }
4901
+ if (empty) return null;
4902
+ const text = formatFieldValue(fieldValue);
4903
+ if (!text) return null;
4904
+ return /* @__PURE__ */ jsx("span", { className, children: text });
4905
+ }
4906
+ var COMPOSER_EMPTY_COLLECTION_HINT = "Click here to add an item";
4907
+ function CollectionChromeButton({
4908
+ label,
4909
+ onClick
4910
+ }) {
4911
+ return /* @__PURE__ */ jsx(
4912
+ "button",
4913
+ {
4914
+ type: "button",
4915
+ onMouseDown: (event) => event.stopPropagation(),
4916
+ onClick: (event) => {
4917
+ event.preventDefault();
4918
+ event.stopPropagation();
4919
+ onClick();
4920
+ },
4921
+ style: {
4922
+ fontSize: 11,
4923
+ lineHeight: 1.2,
4924
+ padding: "4px 8px",
4925
+ border: "1px solid color-mix(in srgb, currentColor 28%, transparent)",
4926
+ borderRadius: 6,
4927
+ background: "transparent",
4928
+ cursor: "pointer",
4929
+ color: "inherit"
4930
+ },
4931
+ children: label
4932
+ }
4933
+ );
4934
+ }
4935
+ function Collection({
4936
+ field,
4937
+ children,
4938
+ className
4939
+ }) {
4940
+ const context = useContext(ComponentContext);
4941
+ const store = useComposerStore();
4942
+ const composerEdit = useComposerEditMode();
4943
+ const name = isFieldEnvelope(field) ? field.name : "";
4944
+ const overlay = useComposerFieldValue(name, void 0);
4945
+ const items = resolveCollectionItems(field, overlay);
4946
+ const writeItems = (nextPlain) => {
4947
+ if (name && store) {
4948
+ const prefixed = name ? store.keys().filter((key) => key === name || key.startsWith(`${name}.`)) : [];
4949
+ for (const key of prefixed) store.delete(key);
4950
+ store.set(name, nextPlain);
4951
+ for (const patch of collectFieldPatches(
4952
+ toFieldEnvelopes({ [name]: nextPlain })
4953
+ )) {
4954
+ store.set(patch.name, patch.value);
4785
4955
  }
4956
+ }
4957
+ commitComposerField(
4958
+ context?.layoutNodeId,
4959
+ context?.componentId,
4960
+ name,
4961
+ nextPlain
4786
4962
  );
4787
4963
  };
4788
- const rawTree = pageConfig.layoutTree;
4789
- const layoutTree = Array.isArray(rawTree) ? rawTree : rawTree && typeof rawTree === "object" && rawTree.componentId ? [rawTree] : [];
4790
- if (layoutTree.length === 0) {
4791
- return /* @__PURE__ */ jsx("main", { children: emptyComponent || /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No content configured for this page." }) }) });
4792
- }
4793
- return /* @__PURE__ */ jsx("main", { children: composerPreview || isComposerPreview() ? /* @__PURE__ */ jsx(
4794
- "div",
4795
- {
4796
- "data-slot-id": "main",
4797
- "data-amplifyup-slot": "main",
4798
- "data-slot-source": "page",
4799
- "data-slot-empty": layoutTree.length === 0 ? "true" : void 0,
4800
- children: layoutTree.map((node) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(node) }, node.id))
4964
+ const plainItems = () => items.map((item, index) => {
4965
+ const base = unwrapFieldsBag(item);
4966
+ if (!store || !name) return base;
4967
+ const out = { ...base };
4968
+ for (const key of Object.keys(out)) {
4969
+ const overlayValue = store.get(`${name}.${index}.${key}`);
4970
+ if (overlayValue !== void 0) out[key] = overlayValue;
4801
4971
  }
4802
- ) : layoutTree.map((node) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(node) }, node.id)) });
4803
- }
4804
- var AmplifyRenderer = memo(AmplifyRendererInner);
4805
- function defaultPreviewCanvas() {
4806
- return /* @__PURE__ */ jsx(
4807
- "div",
4972
+ return out;
4973
+ });
4974
+ const addItem = () => writeItems([...plainItems(), {}]);
4975
+ const removeItem = (index) => writeItems(plainItems().filter((_, i) => i !== index));
4976
+ const moveItem = (index, delta) => {
4977
+ const next = plainItems();
4978
+ const target = index + delta;
4979
+ if (target < 0 || target >= next.length) return;
4980
+ const swap = next[index];
4981
+ next[index] = next[target];
4982
+ next[target] = swap;
4983
+ writeItems(next);
4984
+ };
4985
+ if (!composerEdit) {
4986
+ if (items.length === 0) return null;
4987
+ return /* @__PURE__ */ jsx("span", { className, style: { display: "contents" }, children: items.map((item, index) => /* @__PURE__ */ jsx(React2.Fragment, { children: children(item, index) }, item && typeof item === "object" ? `${name}.${index}` : index)) });
4988
+ }
4989
+ if (items.length === 0) {
4990
+ return /* @__PURE__ */ jsx(
4991
+ "span",
4992
+ {
4993
+ className,
4994
+ "data-amplifyup-collection": name || void 0,
4995
+ "data-amplifyup-collection-empty": "true",
4996
+ "data-amplifyup-node-id": context?.layoutNodeId || void 0,
4997
+ role: "button",
4998
+ tabIndex: 0,
4999
+ onMouseDown: (event) => event.stopPropagation(),
5000
+ onClick: (event) => {
5001
+ event.preventDefault();
5002
+ event.stopPropagation();
5003
+ addItem();
5004
+ },
5005
+ onKeyDown: (event) => {
5006
+ if (event.key === "Enter" || event.key === " ") {
5007
+ event.preventDefault();
5008
+ addItem();
5009
+ }
5010
+ },
5011
+ style: {
5012
+ display: "block",
5013
+ cursor: "pointer",
5014
+ outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
5015
+ outlineOffset: 2,
5016
+ minHeight: "3em",
5017
+ opacity: 0.5
5018
+ },
5019
+ children: /* @__PURE__ */ jsx(EmptySlotHint, { label: COMPOSER_EMPTY_COLLECTION_HINT, compact: true })
5020
+ }
5021
+ );
5022
+ }
5023
+ return /* @__PURE__ */ jsxs(
5024
+ "span",
4808
5025
  {
4809
- "data-slot-id": "main",
4810
- "data-amplifyup-slot": "main",
4811
- "data-slot-source": "page",
4812
- "data-slot-empty": "true",
4813
- style: { minHeight: "50vh", width: "100%", display: "block", position: "relative" },
4814
- children: /* @__PURE__ */ jsx(EmptySlotHint, {})
5026
+ className,
5027
+ "data-amplifyup-collection": name || void 0,
5028
+ "data-amplifyup-node-id": context?.layoutNodeId || void 0,
5029
+ style: { display: "flex", flexDirection: "column", gap: 8 },
5030
+ children: [
5031
+ items.map((item, index) => /* @__PURE__ */ jsxs(
5032
+ "span",
5033
+ {
5034
+ "data-amplifyup-collection-item": `${name}.${index}`,
5035
+ style: {
5036
+ display: "block",
5037
+ outline: "1px dashed color-mix(in srgb, currentColor 28%, transparent)",
5038
+ outlineOffset: 2,
5039
+ padding: 8
5040
+ },
5041
+ children: [
5042
+ /* @__PURE__ */ jsxs(
5043
+ "span",
5044
+ {
5045
+ style: {
5046
+ display: "flex",
5047
+ gap: 6,
5048
+ justifyContent: "flex-end",
5049
+ marginBottom: 8
5050
+ },
5051
+ children: [
5052
+ /* @__PURE__ */ jsx(
5053
+ CollectionChromeButton,
5054
+ {
5055
+ label: "Move up",
5056
+ onClick: () => moveItem(index, -1)
5057
+ }
5058
+ ),
5059
+ /* @__PURE__ */ jsx(
5060
+ CollectionChromeButton,
5061
+ {
5062
+ label: "Move down",
5063
+ onClick: () => moveItem(index, 1)
5064
+ }
5065
+ ),
5066
+ /* @__PURE__ */ jsx(
5067
+ CollectionChromeButton,
5068
+ {
5069
+ label: "Remove",
5070
+ onClick: () => removeItem(index)
5071
+ }
5072
+ )
5073
+ ]
5074
+ }
5075
+ ),
5076
+ children(item, index)
5077
+ ]
5078
+ },
5079
+ `${name}.${index}`
5080
+ )),
5081
+ /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(CollectionChromeButton, { label: "Add item", onClick: addItem }) })
5082
+ ]
4815
5083
  }
4816
5084
  );
4817
5085
  }
4818
- function hasAmplifyLayout(pageConfig) {
4819
- return !!(pageConfig && pageConfig.id !== "default" && Array.isArray(pageConfig.layoutTree) && pageConfig.layoutTree.length > 0);
4820
- }
4821
- function normalizeRoutePath(route) {
4822
- if (!route) return "";
4823
- const path = route.split("?")[0] || "/";
4824
- if (path.length > 1 && path.endsWith("/")) return path.replace(/\/+$/, "");
4825
- return path || "/";
4826
- }
4827
- function samePageRoute(a, b) {
4828
- const ar = normalizeRoutePath(a?.route);
4829
- const br = normalizeRoutePath(b?.route);
4830
- return !!ar && !!br && ar === br;
4831
- }
4832
- function layoutTreesEqual(a, b) {
4833
- if (!a || !b) return a === b;
4834
- return JSON.stringify(a.layoutTree) === JSON.stringify(b.layoutTree);
5086
+ function markdownToHtml(markdown) {
5087
+ try {
5088
+ const result = marked.parse(markdown, { async: false });
5089
+ return typeof result === "string" ? result : "";
5090
+ } catch {
5091
+ return markdown;
5092
+ }
4835
5093
  }
4836
- function AmplifyPageContent({
4837
- fallback,
4838
- renderComponent,
4839
- pageConfig: pageConfigProp,
4840
- forceComposerPreview = false,
4841
- previewLoadingComponent,
4842
- previewEmptyComponent,
4843
- loadingComponent,
4844
- emptyComponent,
4845
- errorComponent
4846
- }) {
4847
- const client = useAmplifyUp();
4848
- const preview = forceComposerPreview || isComposerPreview();
4849
- const clientLayout = hasAmplifyLayout(client.pageConfig) ? client.pageConfig : null;
4850
- const serverLayout = hasAmplifyLayout(pageConfigProp) ? pageConfigProp : null;
4851
- let pageConfig = serverLayout ?? clientLayout;
4852
- if (preview && clientLayout) {
4853
- pageConfig = clientLayout;
4854
- } else if (clientLayout && serverLayout && samePageRoute(clientLayout, serverLayout) && !layoutTreesEqual(clientLayout, serverLayout)) {
4855
- pageConfig = clientLayout;
4856
- } else if (clientLayout && !serverLayout) {
4857
- pageConfig = clientLayout;
5094
+ function RichText(props) {
5095
+ const {
5096
+ field,
5097
+ value,
5098
+ name,
5099
+ className,
5100
+ as: Tag = "div",
5101
+ children
5102
+ } = props;
5103
+ const context = useContext(ComponentContext);
5104
+ const composerEdit = useComposerEditMode();
5105
+ const childrenValue = typeof children === "string" || typeof children === "number" ? String(children) : void 0;
5106
+ const hasField = isFieldEnvelope(field);
5107
+ const hasValueProp = hasOwnProp(props, "value") || childrenValue !== void 0;
5108
+ const boundValue = hasOwnProp(props, "value") ? value : childrenValue !== void 0 ? childrenValue : value;
5109
+ const { editName, fieldValue } = useBoundField(
5110
+ field,
5111
+ boundValue,
5112
+ name,
5113
+ "RichText"
5114
+ );
5115
+ if (!hasField && !hasValueProp && !name) {
5116
+ return null;
4858
5117
  }
4859
- const loading = !hasAmplifyLayout(pageConfig) && client.loading;
4860
- const error = hasAmplifyLayout(pageConfig) ? null : pageConfigProp ? null : client.error;
4861
- const previewCanvas = previewEmptyComponent ?? defaultPreviewCanvas();
4862
- const previewLoading = previewLoadingComponent ?? previewCanvas;
4863
- if (preview) {
5118
+ warnProjectionProps("RichText", hasField, hasValueProp, name);
5119
+ const canEdit = !!context && canInlineEditRichText(editName, fieldValue);
5120
+ if (isComposerEmptyText(fieldValue)) {
5121
+ if (composerEdit && canEdit) {
5122
+ return /* @__PURE__ */ jsx(
5123
+ ComposerInlineField,
5124
+ {
5125
+ name: editName,
5126
+ value: fieldValue,
5127
+ className,
5128
+ layoutNodeId: context?.layoutNodeId,
5129
+ componentId: context?.componentId,
5130
+ rich: true
5131
+ }
5132
+ );
5133
+ }
5134
+ return null;
5135
+ }
5136
+ let html = "";
5137
+ if (typeof fieldValue === "string") {
5138
+ const trimmed = fieldValue.trim();
5139
+ if (trimmed.startsWith("<") && trimmed.includes(">")) {
5140
+ html = fieldValue;
5141
+ } else {
5142
+ html = markdownToHtml(fieldValue);
5143
+ }
5144
+ } else {
5145
+ const text = formatFieldValue(fieldValue);
5146
+ html = text ? markdownToHtml(text) : "";
5147
+ }
5148
+ if (!html) {
5149
+ return null;
5150
+ }
5151
+ if (composerEdit && canEdit) {
4864
5152
  return /* @__PURE__ */ jsx(
4865
- AmplifyRenderer,
5153
+ ComposerInlineHtml,
4866
5154
  {
4867
- pageConfig: pageConfig ?? void 0,
4868
- renderComponent,
4869
- composerPreview: true,
4870
- loadingComponent: loadingComponent ?? previewLoading,
4871
- emptyComponent: emptyComponent ?? previewCanvas,
4872
- errorComponent: errorComponent ?? (() => previewCanvas)
5155
+ name: editName,
5156
+ html,
5157
+ sourceValue: typeof fieldValue === "string" ? fieldValue : html,
5158
+ className,
5159
+ Tag,
5160
+ layoutNodeId: context?.layoutNodeId,
5161
+ componentId: context?.componentId
4873
5162
  }
4874
5163
  );
4875
5164
  }
4876
- if (loading) {
4877
- return loadingComponent ? /* @__PURE__ */ jsx(Fragment, { children: loadingComponent }) : null;
5165
+ return /* @__PURE__ */ jsx(Tag, { className, dangerouslySetInnerHTML: { __html: html } });
5166
+ }
5167
+ function isPortableImageValue(value) {
5168
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
5169
+ const url = value.url;
5170
+ return typeof url === "string" && url.trim().length > 0;
5171
+ }
5172
+ function imageSrc(value) {
5173
+ if (typeof value === "string") return value.trim();
5174
+ if (isPortableImageValue(value)) return value.url.trim();
5175
+ return "";
5176
+ }
5177
+ function Image(props) {
5178
+ const {
5179
+ field,
5180
+ value,
5181
+ name,
5182
+ alt: altProp,
5183
+ className,
5184
+ width: widthProp,
5185
+ height: heightProp,
5186
+ ...imgProps
5187
+ } = props;
5188
+ const context = useContext(ComponentContext);
5189
+ const hasField = isFieldEnvelope(field);
5190
+ const hasValue = hasOwnProp(props, "value");
5191
+ const { editName, fieldValue } = useBoundField(field, value, name, "Image");
5192
+ const composerEdit = useComposerEditMode();
5193
+ warnProjectionProps("Image", hasField, hasValue, name);
5194
+ if (!hasField && !hasValue && !name) {
5195
+ return null;
4878
5196
  }
4879
- if (error) {
4880
- if (errorComponent) return /* @__PURE__ */ jsx(Fragment, { children: errorComponent(error) });
4881
- return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
5197
+ const src = imageSrc(fieldValue);
5198
+ const portable = isPortableImageValue(fieldValue) ? fieldValue : null;
5199
+ const alt = altProp ?? portable?.alt ?? "";
5200
+ const width = widthProp ?? portable?.width;
5201
+ const height = heightProp ?? portable?.height;
5202
+ const fieldAttrs = composerEdit && editName ? {
5203
+ "data-amplifyup-field": editName,
5204
+ "data-amplifyup-field-type": "image",
5205
+ "data-amplifyup-node-id": context?.layoutNodeId || void 0
5206
+ } : null;
5207
+ if (composerEdit && !editName && (hasField || hasValue)) {
5208
+ console.warn(
5209
+ '[AmplifyUp SDK] Image value={\u2026} requires name="\u2026" for computed values'
5210
+ );
4882
5211
  }
4883
- if (!hasAmplifyLayout(pageConfig)) {
4884
- return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
5212
+ if (!src) {
5213
+ if (!composerEdit || !fieldAttrs) return null;
5214
+ return /* @__PURE__ */ jsx(
5215
+ "span",
5216
+ {
5217
+ ...fieldAttrs,
5218
+ className,
5219
+ role: "img",
5220
+ "aria-label": alt || editName || "Image",
5221
+ style: {
5222
+ display: "block",
5223
+ boxSizing: "border-box",
5224
+ minWidth: width ? void 0 : 120,
5225
+ minHeight: height ? void 0 : 80,
5226
+ width: width || "100%",
5227
+ height: height || void 0,
5228
+ background: "color-mix(in srgb, currentColor 8%, transparent)",
5229
+ outline: "2px dashed hsl(82 100% 45%)",
5230
+ outlineOffset: 2,
5231
+ cursor: "pointer"
5232
+ }
5233
+ }
5234
+ );
4885
5235
  }
4886
5236
  return /* @__PURE__ */ jsx(
4887
- AmplifyRenderer,
5237
+ "img",
4888
5238
  {
4889
- pageConfig: pageConfig ?? void 0,
4890
- renderComponent,
4891
- loadingComponent,
4892
- emptyComponent,
4893
- errorComponent
5239
+ ...imgProps,
5240
+ ...fieldAttrs,
5241
+ src,
5242
+ alt,
5243
+ className,
5244
+ width,
5245
+ height,
5246
+ style: {
5247
+ ...typeof imgProps.style === "object" && imgProps.style ? imgProps.style : {},
5248
+ ...composerEdit && fieldAttrs ? { cursor: "pointer" } : null
5249
+ }
4894
5250
  }
4895
5251
  );
4896
5252
  }
@@ -5115,6 +5471,6 @@ function useAmplifyUp() {
5115
5471
  return context;
5116
5472
  }
5117
5473
 
5118
- export { AmplifyPageContent, AmplifyRenderer, AmplifyUpProvider, ComponentContextProvider, Field, Image, QueryContentError, RichText, SelectVariantsError, Slot, isComposerPreview, isPreviewRequest, isWorkingContentRequest, nextPageSpec, queryContent, searchSpec, selectVariants, useAmplifyUp, useComponentProps, useLiveComposerProps };
5474
+ export { AmplifyPageContent, AmplifyRenderer, AmplifyUpProvider, Collection, ComponentContextProvider, ComposerProvider, Field, Image, MaybeComposerProvider, QueryContentError, RichText, SelectVariantsError, Slot, isBrandedFields, isComposerPreview, isFieldEnvelope, isPreviewRequest, isWorkingContentRequest, nextPageSpec, queryContent, searchSpec, selectVariants, toFieldEnvelopes, useAmplifyUp };
5119
5475
  //# sourceMappingURL=react.mjs.map
5120
5476
  //# sourceMappingURL=react.mjs.map