@amplifyup/sdk 0.1.61 → 0.1.62

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,116 @@ 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 joinPath(prefix, key) {
432
+ return prefix ? `${prefix}.${key}` : key;
433
+ }
434
+ function emptyValue(raw) {
435
+ if (raw === void 0) return null;
436
+ return raw;
437
+ }
438
+ function toFieldEnvelopes(source, prefix = "") {
439
+ const input = source && typeof source === "object" && !Array.isArray(source) ? source : {};
440
+ if (isBrandedFields(input)) {
441
+ return input;
442
+ }
443
+ const out = {};
444
+ for (const [key, raw] of Object.entries(input)) {
445
+ const path = joinPath(prefix, key);
446
+ if (isFieldEnvelope(raw)) {
447
+ out[key] = { value: raw.value ?? null, name: raw.name || path };
448
+ continue;
449
+ }
450
+ if (raw && typeof raw === "object" && !Array.isArray(raw) && !isPortableImageShape(raw) && !raw.$$typeof) {
451
+ out[key] = toFieldEnvelopes(raw, path);
452
+ continue;
453
+ }
454
+ out[key] = { value: emptyValue(raw), name: path };
455
+ }
456
+ return brandFields(out);
457
+ }
458
+ function attachFieldEnvelopes(props) {
459
+ const raw = props && typeof props === "object" ? { ...props } : {};
460
+ if (isBrandedFields(raw.fields)) return raw;
461
+ const { fields: existing, ...content } = raw;
462
+ const source = existing && typeof existing === "object" && !isFieldEnvelope(existing) ? existing : content;
463
+ return { ...raw, fields: toFieldEnvelopes(source) };
464
+ }
465
+ function walkLayoutNodes(nodes) {
466
+ if (!Array.isArray(nodes)) return;
467
+ for (const node of nodes) {
468
+ if (!node || typeof node !== "object") continue;
469
+ const rec = node;
470
+ rec.props = attachFieldEnvelopes(rec.props);
471
+ for (const slot of rec.slots || []) {
472
+ walkLayoutNodes(slot.children);
473
+ }
474
+ }
475
+ }
476
+ function envelopePageConfig(page) {
477
+ if (!page || typeof page !== "object") return page ?? null;
478
+ const tree = page.layoutTree;
479
+ if (Array.isArray(tree)) walkLayoutNodes(tree);
480
+ return page;
481
+ }
482
+ function collectFieldPatches(props) {
483
+ const out = [];
484
+ const walk = (obj, prefix) => {
485
+ for (const [key, raw] of Object.entries(obj)) {
486
+ if (key === "fields" && raw && typeof raw === "object" && !Array.isArray(raw) && !isFieldEnvelope(raw)) {
487
+ walk(raw, prefix);
488
+ continue;
489
+ }
490
+ const path = joinPath(prefix, key);
491
+ if (isFieldEnvelope(raw)) {
492
+ out.push({ name: raw.name || path, value: raw.value });
493
+ continue;
494
+ }
495
+ if (raw && typeof raw === "object" && !Array.isArray(raw) && !isPortableImageShape(raw) && !raw.$$typeof) {
496
+ walk(raw, path);
497
+ continue;
498
+ }
499
+ out.push({ name: path, value: emptyValue(raw) });
500
+ }
501
+ };
502
+ if (props && typeof props === "object" && !Array.isArray(props)) {
503
+ walk(props, "");
504
+ }
505
+ return out;
506
+ }
507
+ function resolveEditableBinding(field, value, name) {
508
+ if (isFieldEnvelope(field)) {
509
+ return { name: field.name, value: field.value, requiresName: false };
510
+ }
511
+ return {
512
+ name: name || "",
513
+ value,
514
+ requiresName: value !== void 0 && !name
515
+ };
516
+ }
517
+
408
518
  // src/page-context.ts
409
519
  function extractRouteParams(pattern, url) {
410
520
  const patternParts = String(pattern || "").split("/").filter(Boolean);
@@ -588,10 +698,34 @@ function hydratePageResourceValue(value, fields) {
588
698
  if (value.path) return getByDotPath(fields, value.path) ?? null;
589
699
  return fields;
590
700
  }
701
+ function hydrateFieldsBag(bag, fields) {
702
+ const out = {};
703
+ for (const [key, val] of Object.entries(bag)) {
704
+ if (val && typeof val === "object" && !Array.isArray(val) && typeof val.name === "string" && "value" in val && !("url" in val)) {
705
+ const env = val;
706
+ out[key] = {
707
+ name: env.name,
708
+ value: isPageResourceSentinel(env.value) ? hydratePageResourceValue(env.value, fields) : env.value
709
+ };
710
+ continue;
711
+ }
712
+ if (val && typeof val === "object" && !Array.isArray(val) && !("url" in val)) {
713
+ out[key] = hydrateFieldsBag(val, fields);
714
+ continue;
715
+ }
716
+ out[key] = isPageResourceSentinel(val) ? hydratePageResourceValue(val, fields) : val;
717
+ }
718
+ return out;
719
+ }
591
720
  function hydratePageResourceProps(props, fields) {
592
721
  const source = props && typeof props === "object" ? props : {};
593
722
  const out = { ...source };
723
+ if (out.fields && typeof out.fields === "object" && !Array.isArray(out.fields)) {
724
+ const next = hydrateFieldsBag(out.fields, fields);
725
+ out.fields = isBrandedFields(out.fields) ? brandFields(next) : next;
726
+ }
594
727
  for (const [key, val] of Object.entries(out)) {
728
+ if (key === "fields") continue;
595
729
  if (isPageResourceSentinel(val)) {
596
730
  out[key] = hydratePageResourceValue(val, fields);
597
731
  }
@@ -728,7 +862,25 @@ async function fetchEdgeResolve(options) {
728
862
  body
729
863
  );
730
864
  }
731
- return body;
865
+ return envelopeResolvedPayload(body);
866
+ }
867
+ function envelopeResolvedComponent(node) {
868
+ node.props = attachFieldEnvelopes(node.props);
869
+ if (!node.children) return;
870
+ for (const kids of Object.values(node.children)) {
871
+ for (const child of kids || []) envelopeResolvedComponent(child);
872
+ }
873
+ }
874
+ function envelopeResolvedPayload(payload) {
875
+ if (payload.tree) {
876
+ for (const node of payload.tree) envelopeResolvedComponent(node);
877
+ }
878
+ if (payload.zones) {
879
+ for (const roots of Object.values(payload.zones)) {
880
+ for (const node of roots || []) envelopeResolvedComponent(node);
881
+ }
882
+ }
883
+ return payload;
732
884
  }
733
885
 
734
886
  // src/edge/mapResolvedToPageConfig.ts
@@ -788,7 +940,9 @@ async function fetchContent(options) {
788
940
  const preview = options.preview ?? (typeof window !== "undefined" ? isComposerPreview() : false);
789
941
  if (preview) {
790
942
  try {
791
- const page = await options.fetchOrchestratorPage(options.route, true);
943
+ const page = envelopePageConfig(
944
+ await options.fetchOrchestratorPage(options.route, true)
945
+ );
792
946
  return {
793
947
  pageConfig: page,
794
948
  source: "orchestrator",
@@ -2059,7 +2213,7 @@ Available Console Commands:
2059
2213
  this.refreshEventTracking();
2060
2214
  }, 800);
2061
2215
  }
2062
- return pageConfig;
2216
+ return envelopePageConfig(pageConfig);
2063
2217
  }
2064
2218
  /**
2065
2219
  * Set up view-level event tracking based on configuration
@@ -3432,125 +3586,66 @@ function getSDK(adapter) {
3432
3586
  var trackContentView = (contentId, contentType, properties) => getSDK().trackContentView(contentId, contentType, properties);
3433
3587
  var refreshEventTracking = () => getSDK().refreshEventTracking();
3434
3588
  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
- ]
3589
+
3590
+ // src/composer-store.ts
3591
+ var ComposerFieldStore = class {
3592
+ constructor() {
3593
+ this.values = /* @__PURE__ */ new Map();
3594
+ this.listeners = /* @__PURE__ */ new Map();
3595
+ }
3596
+ get(name) {
3597
+ return this.values.has(name) ? this.values.get(name) : void 0;
3598
+ }
3599
+ set(name, value) {
3600
+ if (!name) return;
3601
+ this.values.set(name, value);
3602
+ const set = this.listeners.get(name);
3603
+ if (!set) return;
3604
+ for (const fn of set) fn();
3605
+ }
3606
+ subscribe(name, fn) {
3607
+ if (!name) return () => {
3608
+ };
3609
+ let set = this.listeners.get(name);
3610
+ if (!set) {
3611
+ set = /* @__PURE__ */ new Set();
3612
+ this.listeners.set(name, set);
3469
3613
  }
3470
- );
3614
+ set.add(fn);
3615
+ return () => {
3616
+ set.delete(fn);
3617
+ if (set.size === 0) this.listeners.delete(name);
3618
+ };
3619
+ }
3620
+ };
3621
+ var ComposerStoreContext = createContext(null);
3622
+ function matchesNode(eventNodeId, layoutNodeId) {
3623
+ if (eventNodeId && layoutNodeId && String(eventNodeId) !== String(layoutNodeId)) {
3624
+ return false;
3625
+ }
3626
+ return true;
3471
3627
  }
3472
- function EmptySlotHint({
3473
- label = "Click here to Add Component",
3474
- compact = false
3628
+ function ComposerProvider({
3629
+ layoutNodeId,
3630
+ children
3475
3631
  }) {
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);
3632
+ const storeRef = useRef(null);
3633
+ if (!storeRef.current) storeRef.current = new ComposerFieldStore();
3634
+ const store = storeRef.current;
3532
3635
  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
3636
  const applyField = (fieldName, value, nodeId) => {
3541
3637
  if (!fieldName || typeof fieldName !== "string") return;
3542
- if (!matchesNode(nodeId)) return;
3543
- setPatch((prev) => ({ ...prev || {}, [fieldName]: value }));
3638
+ if (!matchesNode(nodeId, layoutNodeId)) return;
3639
+ store.set(fieldName, value);
3544
3640
  };
3545
3641
  const applyProps = (nextProps, nodeId) => {
3546
3642
  if (!nextProps || typeof nextProps !== "object" || Array.isArray(nextProps)) {
3547
3643
  return;
3548
3644
  }
3549
- if (!matchesNode(nodeId)) return;
3550
- setPatch((prev) => ({
3551
- ...prev || {},
3552
- ...nextProps
3553
- }));
3645
+ if (!matchesNode(nodeId, layoutNodeId)) return;
3646
+ for (const patch of collectFieldPatches(nextProps)) {
3647
+ store.set(patch.name, patch.value);
3648
+ }
3554
3649
  };
3555
3650
  const onPatch = (event) => {
3556
3651
  const detail = event.detail || {};
@@ -3576,1321 +3671,1348 @@ function useLiveComposerProps(layoutNodeId, props) {
3576
3671
  window.removeEventListener("amplifyup:props-patch", onPropsPatch);
3577
3672
  window.removeEventListener("message", onMessage);
3578
3673
  };
3579
- }, [layoutNodeId]);
3580
- return patch ? { ...props, ...patch } : props;
3674
+ }, [layoutNodeId, store]);
3675
+ return /* @__PURE__ */ jsx(ComposerStoreContext.Provider, { value: store, children });
3581
3676
  }
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 || {};
3677
+ function useComposerFieldValue(name, fallback) {
3678
+ const store = useContext(ComposerStoreContext);
3679
+ const subscribe = useCallback(
3680
+ (onStoreChange) => {
3681
+ if (!store || !name) return () => {
3682
+ };
3683
+ return store.subscribe(name, onStoreChange);
3684
+ },
3685
+ [store, name]
3686
+ );
3687
+ const getSnapshot = useCallback(() => {
3688
+ if (!store || !name) return void 0;
3689
+ return store.get(name);
3690
+ }, [store, name]);
3691
+ const overlay = useSyncExternalStore(subscribe, getSnapshot, () => void 0);
3692
+ return overlay !== void 0 ? overlay : fallback;
3591
3693
  }
3592
- function ComponentContextProvider({
3593
- children,
3594
- slots,
3595
- props,
3694
+ function MaybeComposerProvider({
3695
+ enabled,
3596
3696
  layoutNodeId,
3597
- componentId
3697
+ children
3598
3698
  }) {
3599
- const liveProps = useLiveComposerProps(
3600
- layoutNodeId,
3601
- hydratePageResourceProps(props || {}, getPageResourceFields())
3699
+ const content = useMemo(() => children, [children]);
3700
+ if (!enabled) return /* @__PURE__ */ jsx(Fragment, { children: content });
3701
+ return /* @__PURE__ */ jsx(ComposerProvider, { layoutNodeId, children });
3702
+ }
3703
+ function nodeRenderProps(node) {
3704
+ const settings = node.settings || {};
3705
+ const hydrated = hydratePageResourceProps(
3706
+ node.props || {},
3707
+ getPageResourceFields()
3708
+ );
3709
+ const fields = hydrated.fields;
3710
+ const branded = isBrandedFields(fields) ? fields : brandFields(
3711
+ fields && typeof fields === "object" ? fields : {}
3602
3712
  );
3713
+ return {
3714
+ ...settings,
3715
+ ...hydrated,
3716
+ fields: branded
3717
+ };
3718
+ }
3719
+ var ComponentRenderBoundary = class extends Component {
3720
+ constructor() {
3721
+ super(...arguments);
3722
+ this.state = { error: null };
3723
+ }
3724
+ static getDerivedStateFromError(error) {
3725
+ return { error };
3726
+ }
3727
+ componentDidCatch(error) {
3728
+ console.error(
3729
+ `[AmplifyRenderer] "${this.props.componentId}" (${this.props.nodeId}) threw while rendering:`,
3730
+ error
3731
+ );
3732
+ }
3733
+ render() {
3734
+ if (this.state.error) {
3735
+ if (process.env.NODE_ENV !== "production") {
3736
+ return /* @__PURE__ */ jsxs(
3737
+ "div",
3738
+ {
3739
+ "data-amplifyup-render-error": "",
3740
+ "data-component-id": this.props.componentId,
3741
+ "data-node-id": this.props.nodeId,
3742
+ style: {
3743
+ margin: "1rem 0",
3744
+ padding: "0.75rem 1rem",
3745
+ border: "1px solid #fca5a5",
3746
+ borderRadius: "0.5rem",
3747
+ background: "#fef2f2",
3748
+ color: "#991b1b",
3749
+ fontSize: "0.875rem"
3750
+ },
3751
+ children: [
3752
+ 'Component "',
3753
+ this.props.componentId,
3754
+ '" failed to render:',
3755
+ " ",
3756
+ this.state.error.message
3757
+ ]
3758
+ }
3759
+ );
3760
+ }
3761
+ return null;
3762
+ }
3763
+ return this.props.children;
3764
+ }
3765
+ };
3766
+ function NodeWithFields({
3767
+ nodeId,
3768
+ componentId,
3769
+ props,
3770
+ slots,
3771
+ renderComponent,
3772
+ composerPreview
3773
+ }) {
3603
3774
  return /* @__PURE__ */ jsx(
3604
- ComponentContext.Provider,
3775
+ MaybeComposerProvider,
3605
3776
  {
3606
- value: { slots, props: liveProps, layoutNodeId, componentId },
3607
- children
3777
+ enabled: !!(composerPreview || isComposerPreview()),
3778
+ layoutNodeId: nodeId,
3779
+ children: renderComponent(componentId, props, slots, {
3780
+ layoutNodeId: nodeId
3781
+ })
3608
3782
  }
3609
3783
  );
3610
3784
  }
3611
- function Slot({
3612
- name,
3613
- className
3785
+ function AmplifyRendererInner({
3786
+ renderComponent,
3787
+ pageConfig: serverPageConfig,
3788
+ loadingComponent,
3789
+ errorComponent,
3790
+ emptyComponent,
3791
+ composerPreview
3614
3792
  }) {
3615
- const context = useContext(ComponentContext);
3616
- if (!context) {
3617
- console.warn(`[AmplifyUp SDK] Slot "${name}" used outside ComponentContextProvider`);
3618
- return null;
3619
- }
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;
3793
+ const clientHook = useAmplifyUp();
3794
+ const pageConfig = serverPageConfig || clientHook.pageConfig || null;
3795
+ const loading = serverPageConfig ? false : clientHook.loading;
3796
+ const error = serverPageConfig ? null : clientHook.error;
3797
+ const contentSource = clientHook.source;
3798
+ const [variantCache, setVariantCache] = useState({});
3799
+ useEffect(() => {
3800
+ if (typeof window !== "undefined") {
3801
+ if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
3802
+ return;
3803
+ }
3804
+ if (window.location.pathname.includes("/api/pages/proxy")) {
3805
+ return;
3806
+ }
3807
+ if (window.self !== window.top) {
3808
+ return;
3809
+ }
3810
+ const urlParams = new URLSearchParams(window.location.search);
3811
+ if (urlParams.get("preview") === "true" || urlParams.get("visualEditor") === "true") {
3812
+ return;
3813
+ }
3814
+ if (window.location.href.includes("preview=true") || window.location.href.includes("visualEditor=true")) {
3815
+ return;
3816
+ }
3631
3817
  }
3632
- })());
3633
- sdkDebugOnce(
3634
- `slot:${context.layoutNodeId || ""}:${name}`,
3635
- `[Slot:${name}]`,
3636
- {
3637
- isEmpty,
3638
- isEditorMode,
3639
- availableSlotKeys: context.slots ? Object.keys(context.slots) : []
3818
+ if (pageConfig && pageConfig.id !== "default") {
3819
+ trackContentView(pageConfig.id, "page", {
3820
+ route: pageConfig.route,
3821
+ title: pageConfig.title
3822
+ });
3640
3823
  }
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(
3672
- "div",
3673
- {
3674
- className: "amplifyup-slot-label",
3675
- style: {
3676
- position: "absolute",
3677
- top: "4px",
3678
- left: "4px",
3679
- fontSize: "10px",
3680
- color: "#9ca3af",
3681
- pointerEvents: "none",
3682
- zIndex: 1
3683
- },
3684
- children: [
3685
- "Slot: ",
3686
- name
3687
- ]
3824
+ }, [pageConfig]);
3825
+ useEffect(() => {
3826
+ if (!pageConfig?.eventTracking?.length) return;
3827
+ const sdk = getSDK();
3828
+ if (!sdk.isInitialized()) return;
3829
+ sdk.setupEventTracking(pageConfig.eventTracking);
3830
+ const t1 = window.setTimeout(() => {
3831
+ sdk.setupComponentViewTracking();
3832
+ refreshEventTracking();
3833
+ }, 200);
3834
+ const t2 = window.setTimeout(() => {
3835
+ sdk.setupComponentViewTracking();
3836
+ refreshEventTracking();
3837
+ }, 1e3);
3838
+ return () => {
3839
+ window.clearTimeout(t1);
3840
+ window.clearTimeout(t2);
3841
+ };
3842
+ }, [pageConfig?.id, pageConfig?.eventTracking]);
3843
+ useEffect(() => {
3844
+ if (!pageConfig) return;
3845
+ const t = window.setTimeout(() => {
3846
+ getSDK().setupComponentViewTracking();
3847
+ refreshEventTracking();
3848
+ }, 100);
3849
+ return () => window.clearTimeout(t);
3850
+ }, [variantCache, pageConfig?.id]);
3851
+ useEffect(() => {
3852
+ const isVisualEditorMode = () => {
3853
+ if (typeof window === "undefined") return false;
3854
+ const urlParams = new URLSearchParams(window.location.search);
3855
+ if (urlParams.get("visualEditor") === "true" || urlParams.get("preview") === "true") {
3856
+ return true;
3857
+ }
3858
+ const inIframe = window.self !== window.top;
3859
+ if (!inIframe) return false;
3860
+ try {
3861
+ if (document.referrer) {
3862
+ const referrerUrl = new URL(document.referrer);
3863
+ if (referrerUrl.hostname === "localhost" && referrerUrl.port === "5001") {
3864
+ return true;
3688
3865
  }
3689
- ),
3690
- slotContent
3691
- ] })
3866
+ }
3867
+ } catch (e) {
3868
+ }
3869
+ return false;
3870
+ };
3871
+ if (!isVisualEditorMode()) {
3872
+ return;
3692
3873
  }
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;
3754
- }
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];
3874
+ if (!pageConfig) {
3875
+ return;
3778
3876
  }
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
- );
3795
- }
3796
- function useFieldOverlay(layoutNodeId, name, source) {
3797
- const [overlay, setOverlay] = useState(void 0);
3798
- 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)) {
3803
- return;
3877
+ const rawTree2 = pageConfig.layoutTree;
3878
+ const normalizedTree = Array.isArray(rawTree2) ? rawTree2 : rawTree2 && typeof rawTree2 === "object" && rawTree2.componentId ? [rawTree2] : [];
3879
+ if (normalizedTree.length === 0) {
3880
+ if (typeof window !== "undefined" && window.parent && window.parent !== window) {
3881
+ window.parent.postMessage({
3882
+ type: "COMPONENT_TREE",
3883
+ tree: []
3884
+ }, "*");
3804
3885
  }
3805
- setOverlay(detail.value);
3886
+ return;
3887
+ }
3888
+ const convertLayoutTreeToComponentTree = (nodes, parentId = null) => {
3889
+ const result = [];
3890
+ nodes.forEach((node) => {
3891
+ if (!node) return;
3892
+ const treeNode = {
3893
+ id: node.id,
3894
+ componentId: node.componentId,
3895
+ componentName: node.componentId,
3896
+ // Will be enhanced with actual name if available
3897
+ parentId,
3898
+ children: []
3899
+ };
3900
+ if (node.slots && node.slots.length > 0) {
3901
+ node.slots.forEach((slot) => {
3902
+ const kids = Array.isArray(slot?.children) ? slot.children : [];
3903
+ if (kids.length > 0) {
3904
+ const slotChildren = convertLayoutTreeToComponentTree(kids, node.id);
3905
+ treeNode.children.push(...slotChildren);
3906
+ }
3907
+ });
3908
+ }
3909
+ result.push(treeNode);
3910
+ });
3911
+ return result;
3806
3912
  };
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)
3850
- }
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;
3913
+ const layoutTree2 = normalizedTree;
3914
+ const componentTree = convertLayoutTreeToComponentTree(layoutTree2);
3915
+ const describeLayoutTree = (nodes, depth = 0) => {
3916
+ if (!nodes?.length) return "";
3917
+ const indent = " ".repeat(depth);
3918
+ return nodes.map((n) => {
3919
+ const slotsDesc = (n.slots || []).map((s) => {
3920
+ const childCount = Array.isArray(s.children) ? s.children.length : 0;
3921
+ const childrenDesc = childCount > 0 ? describeLayoutTree(s.children || [], depth + 1) : "";
3922
+ return `${indent} slot "${s.slotId || s.slotName || "?"}" (${childCount} children)${childrenDesc ? "\n" + childrenDesc : ""}`;
3923
+ }).join("\n");
3924
+ return `${indent}- ${n.id} (${n.componentId || "?"})${slotsDesc ? "\n" + slotsDesc : ""}`;
3925
+ }).join("\n");
3926
+ };
3927
+ sdkDebugOnce(
3928
+ `layoutTree:${pageConfig?.id || ""}:${pageConfig?.route || ""}`,
3929
+ "[AmplifyRenderer] layoutTree received:\n%s",
3930
+ describeLayoutTree(layoutTree2)
3931
+ );
3932
+ if (typeof window !== "undefined" && window.parent && window.parent !== window) {
3933
+ const message = {
3934
+ type: "COMPONENT_TREE",
3935
+ tree: componentTree
3936
+ };
3937
+ window.parent.postMessage(message, "*");
3938
+ sdkDebug(
3939
+ "[AmplifyRenderer] Sent component tree from layoutTree:",
3940
+ componentTree.length,
3941
+ "root components"
3942
+ );
3891
3943
  }
3892
- const next = idleDisplay || "";
3893
- if (el.textContent !== next) el.textContent = next;
3894
- }, [idleDisplay, showRendered]);
3944
+ }, [pageConfig]);
3895
3945
  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
- }
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;
3919
- }
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();
3946
+ if (!pageConfig?.layoutTree || !pageConfig.layoutId) return;
3947
+ const roots = pageConfig.layoutTree;
3948
+ const fromEdge = contentSource === "edge" || roots.some((n) => n._contentSource === "edge");
3949
+ if (fromEdge) return;
3950
+ const sdk = getSDK();
3951
+ const instanceId = sdk.getInstanceId();
3952
+ const decisionApiUrl = sdk.getDecisionApiUrl();
3953
+ const orchestratorUrl = sdk.getOrchestratorUrl();
3954
+ if (!decisionApiUrl && !orchestratorUrl || !instanceId) return;
3955
+ const fetchVariants = async () => {
3956
+ const nodes = pageConfig.layoutTree;
3957
+ const newCache = {};
3958
+ const findZonedSlots = (nodeList) => {
3959
+ const results = [];
3960
+ for (const node of nodeList) {
3961
+ if (node.slots) {
3962
+ for (const slot of node.slots) {
3963
+ if (slot.zoneId && slot.variants) {
3964
+ const key = `${node.id}-${slot.slotId}`;
3965
+ results.push({ node, slot, key });
3966
+ }
3967
+ }
3968
+ }
3992
3969
  }
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
4005
- }
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
3970
+ return results;
3971
+ };
3972
+ const zonedSlots = findZonedSlots(nodes);
3973
+ await Promise.all(
3974
+ zonedSlots.map(async ({ node, slot, key }) => {
3975
+ try {
3976
+ let personalizationUrl;
3977
+ if (decisionApiUrl) {
3978
+ const url = new URL(decisionApiUrl);
3979
+ url.searchParams.set("instanceId", instanceId);
3980
+ const zoneId = slot.zoneId;
3981
+ if (zoneId) {
3982
+ url.searchParams.set("zoneId", zoneId);
3983
+ }
3984
+ const campaignId = slot.campaignId;
3985
+ if (campaignId) {
3986
+ url.searchParams.set("campaignId", campaignId);
3987
+ }
3988
+ personalizationUrl = url.toString();
3989
+ } else if (orchestratorUrl) {
3990
+ personalizationUrl = `${orchestratorUrl}/api/personalization/variant?instanceId=${encodeURIComponent(instanceId)}&layoutId=${encodeURIComponent(pageConfig.layoutId)}&slotId=${encodeURIComponent(slot.slotId)}&nodeId=${encodeURIComponent(node.id)}`;
3991
+ } else {
3992
+ return;
3993
+ }
3994
+ const response = await fetch(personalizationUrl);
3995
+ if (response.ok) {
3996
+ const data = await response.json();
3997
+ if (data.variantId && data.componentTree) {
3998
+ newCache[key] = {
3999
+ variantId: data.variantId,
4000
+ children: data.componentTree
4001
+ };
4002
+ if (typeof window !== "undefined") {
4003
+ if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
4004
+ return;
4005
+ }
4006
+ if (window.location.pathname.includes("/api/pages/proxy")) {
4007
+ return;
4008
+ }
4009
+ if (window.self !== window.top) {
4010
+ return;
4011
+ }
4012
+ }
4013
+ sdk.track("Zone Impression", {
4014
+ zoneId: slot.zoneId,
4015
+ variantId: data.variantId,
4016
+ layoutId: pageConfig.layoutId,
4017
+ route: pageConfig.route
4018
+ });
4019
+ } else {
4020
+ const defaultVariant = slot.variants?.find((v) => v.isDefault);
4021
+ if (defaultVariant) {
4022
+ newCache[key] = {
4023
+ variantId: defaultVariant.variantId,
4024
+ children: defaultVariant.children
4025
+ };
4026
+ }
4027
+ }
4028
+ }
4029
+ } catch (err) {
4030
+ console.error(`Error fetching variant for slot ${key}:`, err);
4031
+ const defaultVariant = slot.variants?.find((v) => v.isDefault);
4032
+ if (defaultVariant) {
4033
+ newCache[key] = {
4034
+ variantId: defaultVariant.variantId,
4035
+ children: defaultVariant.children
4036
+ };
4037
+ }
4038
+ }
4039
+ })
4040
+ );
4041
+ setVariantCache(newCache);
4042
+ };
4043
+ fetchVariants();
4044
+ }, [pageConfig?.layoutTree, pageConfig?.layoutId, pageConfig?.route, contentSource]);
4045
+ if (loading) {
4046
+ 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: [
4047
+ /* @__PURE__ */ jsx("div", { className: "h-8 bg-gray-200 rounded w-1/4 mb-4" }),
4048
+ /* @__PURE__ */ jsx("div", { className: "h-4 bg-gray-200 rounded w-1/2 mb-8" }),
4049
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
4050
+ /* @__PURE__ */ jsx("div", { className: "h-64 bg-gray-200 rounded" }),
4051
+ /* @__PURE__ */ jsx("div", { className: "h-32 bg-gray-200 rounded" })
4052
+ ] })
4053
+ ] }) }) }) });
4054
+ }
4055
+ if (error) {
4056
+ 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: [
4057
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-red-900 mb-2", children: "Error Loading Page" }),
4058
+ /* @__PURE__ */ jsx("p", { className: "text-red-700", children: error })
4059
+ ] }) }) }) });
4060
+ }
4061
+ if (!pageConfig) {
4062
+ return null;
4063
+ }
4064
+ pageConfig.layout?.header !== false;
4065
+ pageConfig.layout?.footer !== false;
4066
+ const renderNode = (node) => {
4067
+ const zoneIdFromProps = node.componentId === "AmplifyZone" ? String(node.props?.zoneId || "") : "";
4068
+ if (node.componentId === "AmplifyZone") {
4069
+ const contentSlot = node.slots?.find((s) => s.slotId === "content") || node.slots?.[0];
4070
+ const children = Array.isArray(contentSlot?.children) ? contentSlot.children : [];
4071
+ return /* @__PURE__ */ jsx(
4072
+ "div",
4073
+ {
4074
+ "data-node-id": node.id,
4075
+ "data-amplifyup-node": "",
4076
+ "data-amplifyup-component": "AmplifyZone",
4077
+ "data-amplifyup-zone": zoneIdFromProps || void 0,
4078
+ "data-zone-id": zoneIdFromProps || void 0,
4079
+ children: /* @__PURE__ */ jsx(
4080
+ "div",
4081
+ {
4082
+ "data-slot-id": "content",
4083
+ "data-amplifyup-slot": "content",
4084
+ "data-parent-node-id": node.id,
4085
+ "data-parent-component-id": "AmplifyZone",
4086
+ children: children.filter(Boolean).map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child) }, child.id))
4087
+ }
4088
+ )
4089
+ }
4090
+ );
4091
+ }
4092
+ let slotChildren = {};
4093
+ sdkDebugOnce(`node:${node.id}`, `[AmplifyRenderer] Rendering node ${node.id} (${node.componentId})`, {
4094
+ hasSlots: !!(node.slots && node.slots.length > 0),
4095
+ slotCount: node.slots?.length || 0,
4096
+ slots: node.slots?.map((s) => ({ slotId: s.slotId, zoneId: s.zoneId, childrenCount: s.children?.length || 0 }))
4033
4097
  });
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"
4098
+ if (node.slots && node.slots.length > 0) {
4099
+ for (const slot of node.slots) {
4100
+ const slotKey = `${node.id}-${slot.slotId}`;
4101
+ let childrenToRender = [];
4102
+ if (slot.zoneId && slot.variants && variantCache[slotKey]) {
4103
+ childrenToRender = variantCache[slotKey].children;
4104
+ } else if (slot.zoneId && slot.variants) {
4105
+ const defaultVariant = slot.variants.find((v) => v.isDefault);
4106
+ childrenToRender = defaultVariant?.children || [];
4107
+ } else {
4108
+ childrenToRender = Array.isArray(slot.children) ? slot.children : [];
4109
+ }
4110
+ const safeChildren = Array.isArray(childrenToRender) ? childrenToRender : [];
4111
+ slotChildren[slot.slotId] = /* @__PURE__ */ jsx(
4112
+ "div",
4113
+ {
4114
+ "data-slot-id": slot.slotId,
4115
+ "data-amplifyup-slot": slot.slotId,
4116
+ "data-zone-id": slot.zoneId || void 0,
4117
+ "data-parent-node-id": node.id,
4118
+ "data-parent-component-id": node.componentId,
4119
+ children: safeChildren.filter(Boolean).map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child) }, child.id))
4120
+ },
4121
+ slot.slotId
4122
+ );
4123
+ }
4050
4124
  }
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("");
4125
+ const slotsToPass = Object.keys(slotChildren).length > 0 ? slotChildren : void 0;
4126
+ return /* @__PURE__ */ jsx(
4127
+ "div",
4128
+ {
4129
+ "data-node-id": node.id,
4130
+ "data-amplifyup-node": "",
4131
+ "data-component-id": node.componentId,
4132
+ "data-amplifyup-component": node.componentId,
4133
+ children: /* @__PURE__ */ jsx(
4134
+ ComponentRenderBoundary,
4135
+ {
4136
+ componentId: node.componentId,
4137
+ nodeId: node.id,
4138
+ children: /* @__PURE__ */ jsx(
4139
+ NodeWithFields,
4140
+ {
4141
+ nodeId: node.id,
4142
+ componentId: node.componentId,
4143
+ props: nodeRenderProps(node),
4144
+ slots: slotsToPass,
4145
+ renderComponent,
4146
+ composerPreview
4147
+ }
4148
+ )
4149
+ }
4150
+ )
4062
4151
  }
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
4152
  );
4153
+ };
4154
+ const rawTree = pageConfig.layoutTree;
4155
+ const layoutTree = Array.isArray(rawTree) ? rawTree : rawTree && typeof rawTree === "object" && rawTree.componentId ? [rawTree] : [];
4156
+ if (layoutTree.length === 0) {
4157
+ 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." }) }) });
4079
4158
  }
4159
+ return /* @__PURE__ */ jsx("main", { children: composerPreview || isComposerPreview() ? /* @__PURE__ */ jsx(
4160
+ "div",
4161
+ {
4162
+ "data-slot-id": "main",
4163
+ "data-amplifyup-slot": "main",
4164
+ "data-slot-source": "page",
4165
+ "data-slot-empty": layoutTree.length === 0 ? "true" : void 0,
4166
+ children: layoutTree.map((node) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(node) }, node.id))
4167
+ }
4168
+ ) : layoutTree.map((node) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(node) }, node.id)) });
4080
4169
  }
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);
4170
+ var AmplifyRenderer = memo(AmplifyRendererInner);
4171
+ function AddComponentIcon({ size = 28 }) {
4172
+ return /* @__PURE__ */ jsxs(
4173
+ "svg",
4174
+ {
4175
+ width: size,
4176
+ height: size,
4177
+ viewBox: "0 0 24 24",
4178
+ fill: "none",
4179
+ xmlns: "http://www.w3.org/2000/svg",
4180
+ "aria-hidden": "true",
4181
+ focusable: "false",
4182
+ children: [
4183
+ /* @__PURE__ */ jsx(
4184
+ "rect",
4185
+ {
4186
+ x: "3",
4187
+ y: "3",
4188
+ width: "18",
4189
+ height: "18",
4190
+ rx: "4",
4191
+ stroke: "currentColor",
4192
+ strokeWidth: "1.75"
4193
+ }
4194
+ ),
4195
+ /* @__PURE__ */ jsx(
4196
+ "path",
4197
+ {
4198
+ d: "M12 8v8M8 12h8",
4199
+ stroke: "currentColor",
4200
+ strokeWidth: "1.75",
4201
+ strokeLinecap: "round"
4202
+ }
4203
+ )
4204
+ ]
4205
+ }
4206
+ );
4086
4207
  }
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
4208
+ function EmptySlotHint({
4209
+ label = "Click here to Add Component",
4210
+ compact = false
4211
+ }) {
4212
+ const iconSize = compact ? 20 : 28;
4213
+ return /* @__PURE__ */ jsxs(
4214
+ "div",
4215
+ {
4216
+ className: "amplifyup-slot-placeholder",
4217
+ style: {
4218
+ display: "flex",
4219
+ flexDirection: "column",
4220
+ alignItems: "center",
4221
+ justifyContent: "center",
4222
+ gap: compact ? 8 : 12,
4223
+ minHeight: compact ? 60 : void 0,
4224
+ width: "100%",
4225
+ pointerEvents: "none",
4226
+ textAlign: "center",
4227
+ boxSizing: "border-box",
4228
+ padding: compact ? "12px 8px" : "24px 16px"
4106
4229
  },
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
4230
+ children: [
4231
+ /* @__PURE__ */ jsx(
4232
+ "span",
4233
+ {
4234
+ className: "amplifyup-slot-placeholder-icon",
4235
+ style: {
4236
+ display: "inline-flex",
4237
+ alignItems: "center",
4238
+ justifyContent: "center",
4239
+ width: compact ? 36 : 48,
4240
+ height: compact ? 36 : 48,
4241
+ borderRadius: 12,
4242
+ border: "1.5px dashed currentColor",
4243
+ opacity: 0.9
4244
+ },
4245
+ children: /* @__PURE__ */ jsx(AddComponentIcon, { size: iconSize })
4246
+ }
4247
+ ),
4248
+ /* @__PURE__ */ jsx(
4249
+ "span",
4250
+ {
4251
+ className: "amplifyup-slot-placeholder-label",
4252
+ style: {
4253
+ fontSize: compact ? 13 : 15,
4254
+ fontWeight: 600,
4255
+ lineHeight: 1.3,
4256
+ letterSpacing: "0.01em"
4257
+ },
4258
+ children: label
4259
+ }
4260
+ )
4261
+ ]
4262
+ }
4122
4263
  );
4123
- warnProjectionProps("Field", name, hasExplicitValue);
4124
- if (!context && !hasExplicitValue) {
4125
- console.warn(
4126
- "[AmplifyUp SDK] Field needs a `value` prop (or deprecated `name` alone)"
4127
- );
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)
4143
- }
4144
- );
4264
+ }
4265
+ function defaultPreviewCanvas() {
4266
+ return /* @__PURE__ */ jsx(
4267
+ "div",
4268
+ {
4269
+ "data-slot-id": "main",
4270
+ "data-amplifyup-slot": "main",
4271
+ "data-slot-source": "page",
4272
+ "data-slot-empty": "true",
4273
+ style: { minHeight: "50vh", width: "100%", display: "block", position: "relative" },
4274
+ children: /* @__PURE__ */ jsx(EmptySlotHint, {})
4145
4275
  }
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;
4276
+ );
4277
+ }
4278
+ function hasAmplifyLayout(pageConfig) {
4279
+ return !!(pageConfig && pageConfig.id !== "default" && Array.isArray(pageConfig.layoutTree) && pageConfig.layoutTree.length > 0);
4280
+ }
4281
+ function normalizeRoutePath(route) {
4282
+ if (!route) return "";
4283
+ const path = route.split("?")[0] || "/";
4284
+ if (path.length > 1 && path.endsWith("/")) return path.replace(/\/+$/, "");
4285
+ return path || "/";
4286
+ }
4287
+ function samePageRoute(a, b) {
4288
+ const ar = normalizeRoutePath(a?.route);
4289
+ const br = normalizeRoutePath(b?.route);
4290
+ return !!ar && !!br && ar === br;
4291
+ }
4292
+ function layoutTreesEqual(a, b) {
4293
+ if (!a || !b) return a === b;
4294
+ return JSON.stringify(a.layoutTree) === JSON.stringify(b.layoutTree);
4295
+ }
4296
+ function AmplifyPageContent({
4297
+ fallback,
4298
+ renderComponent,
4299
+ pageConfig: pageConfigProp,
4300
+ forceComposerPreview = false,
4301
+ previewLoadingComponent,
4302
+ previewEmptyComponent,
4303
+ loadingComponent,
4304
+ emptyComponent,
4305
+ errorComponent
4306
+ }) {
4307
+ const client = useAmplifyUp();
4308
+ const preview = forceComposerPreview || isComposerPreview();
4309
+ const clientLayout = hasAmplifyLayout(client.pageConfig) ? client.pageConfig : null;
4310
+ const serverLayout = hasAmplifyLayout(pageConfigProp) ? pageConfigProp : null;
4311
+ let pageConfig = serverLayout ?? clientLayout;
4312
+ if (preview && clientLayout) {
4313
+ pageConfig = clientLayout;
4314
+ } else if (clientLayout && serverLayout && samePageRoute(clientLayout, serverLayout) && !layoutTreesEqual(clientLayout, serverLayout)) {
4315
+ pageConfig = clientLayout;
4316
+ } else if (clientLayout && !serverLayout) {
4317
+ pageConfig = clientLayout;
4151
4318
  }
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) {
4319
+ const loading = !hasAmplifyLayout(pageConfig) && client.loading;
4320
+ const error = hasAmplifyLayout(pageConfig) ? null : pageConfigProp ? null : client.error;
4321
+ const previewCanvas = previewEmptyComponent ?? defaultPreviewCanvas();
4322
+ const previewLoading = previewLoadingComponent ?? previewCanvas;
4323
+ if (preview) {
4156
4324
  return /* @__PURE__ */ jsx(
4157
- ComposerInlineField,
4325
+ AmplifyRenderer,
4158
4326
  {
4159
- name: editName,
4160
- value: fieldValue,
4161
- fallback,
4162
- className,
4163
- layoutNodeId: context.layoutNodeId,
4164
- componentId: context.componentId,
4165
- rich: looksLikeRichCanvasField(editName)
4327
+ pageConfig: pageConfig ?? void 0,
4328
+ renderComponent,
4329
+ composerPreview: true,
4330
+ loadingComponent: loadingComponent ?? previewLoading,
4331
+ emptyComponent: emptyComponent ?? previewCanvas,
4332
+ errorComponent: errorComponent ?? (() => previewCanvas)
4166
4333
  }
4167
4334
  );
4168
4335
  }
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;
4174
- }
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;
4336
+ if (loading) {
4337
+ return loadingComponent ? /* @__PURE__ */ jsx(Fragment, { children: loadingComponent }) : null;
4183
4338
  }
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
- );
4339
+ if (error) {
4340
+ if (errorComponent) return /* @__PURE__ */ jsx(Fragment, { children: errorComponent(error) });
4210
4341
  return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
4211
4342
  }
4212
- warnProjectionProps("RichText", name, hasExplicitValue);
4213
- const canEdit = !!context && canInlineEditRichText(editName, fieldValue);
4214
- if (isComposerEmptyText(fieldValue)) {
4215
- if (composerEdit && canEdit && !isReactNodeFallback(fallback) && !classNameIsBlock(className)) {
4216
- return /* @__PURE__ */ jsx(
4217
- ComposerInlineField,
4218
- {
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
4226
- }
4227
- );
4228
- }
4229
- return fallback ? /* @__PURE__ */ jsx(Tag, { className, children: fallback }) : null;
4343
+ if (!hasAmplifyLayout(pageConfig)) {
4344
+ return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
4230
4345
  }
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);
4346
+ return /* @__PURE__ */ jsx(
4347
+ AmplifyRenderer,
4348
+ {
4349
+ pageConfig: pageConfig ?? void 0,
4350
+ renderComponent,
4351
+ loadingComponent,
4352
+ emptyComponent,
4353
+ errorComponent
4238
4354
  }
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
4257
- }
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;
4355
+ );
4266
4356
  }
4267
- function imageSrc(value) {
4268
- if (typeof value === "string") return value.trim();
4269
- if (isPortableImageValue(value)) return value.url.trim();
4270
- return "";
4357
+ var ComponentContext = createContext(null);
4358
+ function ComponentContextProvider({
4359
+ children,
4360
+ slots,
4361
+ props,
4362
+ layoutNodeId,
4363
+ componentId
4364
+ }) {
4365
+ const next = hydratePageResourceProps(props || {}, getPageResourceFields());
4366
+ return /* @__PURE__ */ jsx(
4367
+ ComponentContext.Provider,
4368
+ {
4369
+ value: { slots, props: next, layoutNodeId, componentId },
4370
+ children
4371
+ }
4372
+ );
4271
4373
  }
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;
4374
+ function Slot({
4375
+ name,
4376
+ className
4377
+ }) {
4282
4378
  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
- );
4379
+ if (!context) {
4380
+ console.warn(`[AmplifyUp SDK] Slot "${name}" used outside ComponentContextProvider`);
4297
4381
  return null;
4298
4382
  }
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;
4316
- return /* @__PURE__ */ jsx(
4317
- "span",
4318
- {
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
- }
4335
- }
4336
- );
4337
- }
4383
+ const slotContent = context.slots?.[name];
4384
+ const isEmpty = !slotContent;
4385
+ const isEditorMode = typeof window !== "undefined" && // Check for preview mode flag set by proxy
4386
+ (window.__AMPLIFYUP_PREVIEW_MODE__ === true || // Check URL query params
4387
+ 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)
4388
+ window.self !== window.top && document.referrer && (() => {
4389
+ try {
4390
+ const referrerUrl = new URL(document.referrer);
4391
+ return referrerUrl.hostname === "localhost" && referrerUrl.port === "5001" || referrerUrl.pathname.includes("/dashboard/composer") || referrerUrl.pathname.includes("/dashboard/routes/");
4392
+ } catch {
4393
+ return false;
4394
+ }
4395
+ })());
4396
+ sdkDebugOnce(
4397
+ `slot:${context.layoutNodeId || ""}:${name}`,
4398
+ `[Slot:${name}]`,
4399
+ {
4400
+ isEmpty,
4401
+ isEditorMode,
4402
+ availableSlotKeys: context.slots ? Object.keys(context.slots) : []
4403
+ }
4404
+ );
4338
4405
  return /* @__PURE__ */ jsx(
4339
- "img",
4406
+ "div",
4340
4407
  {
4341
- ...imgProps,
4342
- ...fieldAttrs,
4343
- src,
4344
- alt,
4345
4408
  className,
4346
- width,
4347
- height,
4409
+ "data-slot-name": name,
4410
+ "data-slot-id": name,
4411
+ "data-amplifyup-slot": name,
4412
+ "data-slot-empty": isEmpty ? "true" : void 0,
4413
+ "data-parent-node-id": context.layoutNodeId || void 0,
4414
+ "data-parent-component-id": context.componentId || void 0,
4348
4415
  style: {
4349
- ...typeof imgProps.style === "object" && imgProps.style ? imgProps.style : {},
4350
- ...composerEdit && fieldAttrs ? { cursor: "pointer" } : null
4351
- }
4352
- }
4353
- );
4354
- }
4355
- function nodeRenderProps(node) {
4356
- return {
4357
- ...node.settings || {},
4358
- ...node.props || {}
4359
- };
4360
- }
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(
4416
+ // Only add minimum height when empty (for drop target visibility)
4417
+ minHeight: isEmpty ? "60px" : void 0,
4418
+ // Only add position relative for overlay positioning in editor mode
4419
+ position: isEditorMode ? "relative" : void 0
4420
+ },
4421
+ children: isEmpty ? isEditorMode ? /* @__PURE__ */ jsx(EmptySlotHint, { compact: true }) : /* @__PURE__ */ jsx(
4422
+ "div",
4423
+ {
4424
+ className: "amplifyup-slot-placeholder",
4425
+ style: {
4426
+ minHeight: "60px",
4427
+ color: "#9ca3af",
4428
+ fontSize: "14px",
4429
+ pointerEvents: "none"
4430
+ },
4431
+ children: `Empty slot: ${name}`
4432
+ }
4433
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
4434
+ isEditorMode && /* @__PURE__ */ jsxs(
4379
4435
  "div",
4380
4436
  {
4381
- "data-amplifyup-render-error": "",
4382
- "data-component-id": this.props.componentId,
4383
- "data-node-id": this.props.nodeId,
4437
+ className: "amplifyup-slot-label",
4384
4438
  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"
4439
+ position: "absolute",
4440
+ top: "4px",
4441
+ left: "4px",
4442
+ fontSize: "10px",
4443
+ color: "#9ca3af",
4444
+ pointerEvents: "none",
4445
+ zIndex: 1
4392
4446
  },
4393
4447
  children: [
4394
- 'Component "',
4395
- this.props.componentId,
4396
- '" failed to render:',
4397
- " ",
4398
- this.state.error.message
4448
+ "Slot: ",
4449
+ name
4399
4450
  ]
4400
4451
  }
4401
- );
4402
- }
4403
- return null;
4452
+ ),
4453
+ slotContent
4454
+ ] })
4404
4455
  }
4405
- return this.props.children;
4456
+ );
4457
+ }
4458
+ function isComposerEditMode() {
4459
+ if (typeof window === "undefined") return false;
4460
+ return isComposerPreview() && window.__AMPLIFYUP_EVENT_SELECTION__ !== true;
4461
+ }
4462
+ function isLikelyNonTextFieldName(name) {
4463
+ const n = name.toLowerCase();
4464
+ return n.includes("href") || n.includes("url") || n.includes("src") || n.includes("image") || n.includes("icon") || n.includes("overlay") || n.includes("color");
4465
+ }
4466
+ var COMPOSER_EMPTY_FIELD_HINT = "Click here to edit this field";
4467
+ var EMPTY_FIELD_PLACEHOLDERS = /* @__PURE__ */ new Set([
4468
+ COMPOSER_EMPTY_FIELD_HINT,
4469
+ "Click here to enter text",
4470
+ "Sample Title",
4471
+ "Sample Heading",
4472
+ "Sample Subheading"
4473
+ ]);
4474
+ function isComposerEmptyText(value) {
4475
+ if (value === void 0 || value === null) return true;
4476
+ if (typeof value !== "string") return false;
4477
+ const trimmed = value.trim();
4478
+ return trimmed === "" || EMPTY_FIELD_PLACEHOLDERS.has(trimmed);
4479
+ }
4480
+ function canInlineEdit(name, value) {
4481
+ if (!name || isLikelyNonTextFieldName(name)) return false;
4482
+ if (value !== void 0 && value !== null && typeof value === "object") {
4483
+ return false;
4406
4484
  }
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
- }) });
4485
+ if (typeof value === "boolean") return false;
4486
+ return true;
4419
4487
  }
4420
- function AmplifyRendererInner({
4421
- renderComponent,
4422
- pageConfig: serverPageConfig,
4423
- loadingComponent,
4424
- errorComponent,
4425
- emptyComponent,
4426
- composerPreview
4427
- }) {
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;
4500
- }
4501
- }
4502
- } catch (e) {
4488
+ function canInlineEditRichText(name, value) {
4489
+ if (!name || isLikelyNonTextFieldName(name)) return false;
4490
+ if (typeof value === "boolean") return false;
4491
+ if (value !== void 0 && value !== null && typeof value === "object" && !Array.isArray(value)) {
4492
+ return false;
4493
+ }
4494
+ return true;
4495
+ }
4496
+ function subscribeComposerEdit(onStoreChange) {
4497
+ if (typeof window === "undefined") return () => {
4498
+ };
4499
+ window.addEventListener("amplifyup:composer-edit", onStoreChange);
4500
+ return () => window.removeEventListener("amplifyup:composer-edit", onStoreChange);
4501
+ }
4502
+ function useComposerEditMode() {
4503
+ return useSyncExternalStore(
4504
+ subscribeComposerEdit,
4505
+ isComposerEditMode,
4506
+ () => false
4507
+ );
4508
+ }
4509
+ function useBoundField(field, value, name, component) {
4510
+ const bound = resolveEditableBinding(field, value, name);
4511
+ if (bound.requiresName) {
4512
+ console.warn(
4513
+ `[AmplifyUp SDK] ${component} value={\u2026} requires name="\u2026" for computed values`
4514
+ );
4515
+ }
4516
+ const fieldValue = useComposerFieldValue(bound.name, bound.value);
4517
+ return { editName: bound.name, fieldValue };
4518
+ }
4519
+ function commitComposerField(layoutNodeId, componentId, name, value) {
4520
+ if (typeof window === "undefined" || window.parent === window) return;
4521
+ window.parent.postMessage(
4522
+ {
4523
+ type: "AMPLIFYUP_FIELD_COMMIT",
4524
+ layoutNodeId,
4525
+ componentId,
4526
+ fieldName: name,
4527
+ value
4528
+ },
4529
+ "*"
4530
+ );
4531
+ }
4532
+ function looksLikeRichCanvasField(name) {
4533
+ const tokens = String(name || "").replace(/([a-z])([A-Z])/g, "$1 $2").split(/[._\-\s/]+/).map((s) => s.toLowerCase()).filter(Boolean);
4534
+ const last = tokens[tokens.length - 1] || "";
4535
+ return last === "body" || last === "content" || last === "copy" || last === "excerpt" || last === "markdown" || last === "richtext";
4536
+ }
4537
+ var lastRichTextRequestAt = 0;
4538
+ function requestCanvasRichTextEdit(opts) {
4539
+ if (typeof window === "undefined" || window.parent === window) return;
4540
+ if (!opts.name) return;
4541
+ const now = Date.now();
4542
+ if (now - lastRichTextRequestAt < 400) return;
4543
+ lastRichTextRequestAt = now;
4544
+ const rect = opts.el.getBoundingClientRect();
4545
+ window.parent.postMessage(
4546
+ {
4547
+ type: "AMPLIFYUP_RICHTEXT_EDIT",
4548
+ layoutNodeId: opts.layoutNodeId,
4549
+ componentId: opts.componentId,
4550
+ fieldName: opts.name,
4551
+ value: opts.value,
4552
+ bounds: {
4553
+ top: rect.top,
4554
+ left: rect.left,
4555
+ width: Math.max(rect.width, 240),
4556
+ height: Math.max(rect.height, 64)
4503
4557
  }
4504
- return false;
4505
- };
4506
- if (!isVisualEditorMode()) {
4558
+ },
4559
+ "*"
4560
+ );
4561
+ }
4562
+ function placeCaret(el, atStart = false) {
4563
+ const range = document.createRange();
4564
+ range.selectNodeContents(el);
4565
+ range.collapse(atStart);
4566
+ const sel = window.getSelection();
4567
+ sel?.removeAllRanges();
4568
+ sel?.addRange(range);
4569
+ }
4570
+ function ComposerInlineField({
4571
+ name,
4572
+ value,
4573
+ className,
4574
+ render,
4575
+ layoutNodeId,
4576
+ componentId,
4577
+ rich = false
4578
+ }) {
4579
+ const [editing, setEditing] = useState(false);
4580
+ const [focused, setFocused] = useState(false);
4581
+ const elRef = useRef(null);
4582
+ const storedEmpty = isComposerEmptyText(value);
4583
+ const text = storedEmpty ? "" : formatFieldValue(value);
4584
+ const hint = COMPOSER_EMPTY_FIELD_HINT;
4585
+ const idleDisplay = storedEmpty ? hint : text;
4586
+ const showRendered = !!render && !editing && !storedEmpty;
4587
+ const showHint = storedEmpty && !focused && !showRendered;
4588
+ useEffect(() => {
4589
+ const el = elRef.current;
4590
+ if (!el || showRendered) return;
4591
+ if (document.activeElement === el) return;
4592
+ if (el.querySelector(
4593
+ "input, button, select, textarea, form, svg, iframe, img"
4594
+ )) {
4507
4595
  return;
4508
4596
  }
4509
- if (!pageConfig) {
4597
+ const next = idleDisplay || "";
4598
+ if (el.textContent !== next) el.textContent = next;
4599
+ }, [idleDisplay, showRendered]);
4600
+ useEffect(() => {
4601
+ if (!editing || !elRef.current) return;
4602
+ const el = elRef.current;
4603
+ if (storedEmpty) {
4604
+ el.textContent = "";
4605
+ el.focus();
4606
+ placeCaret(el, true);
4510
4607
  return;
4511
4608
  }
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
- }, "*");
4609
+ el.textContent = idleDisplay || "";
4610
+ el.focus();
4611
+ placeCaret(el, false);
4612
+ }, [editing, storedEmpty, idleDisplay]);
4613
+ const finishEdit = () => {
4614
+ const raw = (elRef.current?.innerText ?? "").replace(/\u00a0/g, " ").replace(/\n+$/g, "");
4615
+ const next = raw.trim() === "" ? "" : raw;
4616
+ setEditing(false);
4617
+ setFocused(false);
4618
+ if (storedEmpty && next === "") {
4619
+ if (elRef.current) elRef.current.textContent = hint;
4620
+ if (typeof value === "string" && EMPTY_FIELD_PLACEHOLDERS.has(value.trim())) {
4621
+ commitComposerField(layoutNodeId, componentId, name, "");
4520
4622
  }
4521
4623
  return;
4522
4624
  }
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
- });
4625
+ if (next === text) return;
4626
+ commitComposerField(layoutNodeId, componentId, name, next);
4627
+ };
4628
+ const openRichEditor = (event) => {
4629
+ if (!isComposerEditMode() || !elRef.current) return;
4630
+ event?.preventDefault();
4631
+ event?.stopPropagation();
4632
+ const source = typeof value === "string" && !storedEmpty ? value : text || "";
4633
+ requestCanvasRichTextEdit({
4634
+ el: elRef.current,
4635
+ layoutNodeId,
4636
+ componentId,
4637
+ name,
4638
+ value: source
4639
+ });
4640
+ };
4641
+ return /* @__PURE__ */ jsx(
4642
+ "span",
4643
+ {
4644
+ ref: elRef,
4645
+ className,
4646
+ "data-amplifyup-field": name,
4647
+ "data-amplifyup-field-type": rich ? "richtext" : "text",
4648
+ "data-amplifyup-node-id": layoutNodeId || void 0,
4649
+ "data-amplifyup-empty-hint": showHint ? "true" : void 0,
4650
+ contentEditable: !rich && !showRendered,
4651
+ suppressContentEditableWarning: true,
4652
+ role: "textbox",
4653
+ tabIndex: 0,
4654
+ onMouseDown: (event) => {
4655
+ event.stopPropagation();
4656
+ },
4657
+ onClick: (event) => {
4658
+ if (!isComposerEditMode()) return;
4659
+ const link = event.target?.closest?.("a");
4660
+ if (link) event.preventDefault();
4661
+ if (rich) {
4662
+ openRichEditor(event);
4663
+ return;
4543
4664
  }
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
- );
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
- );
4665
+ if (render) {
4666
+ setEditing(true);
4667
+ }
4668
+ },
4669
+ onFocus: () => {
4670
+ if (!isComposerEditMode()) return;
4671
+ if (rich) {
4672
+ openRichEditor();
4673
+ return;
4674
+ }
4675
+ setFocused(true);
4676
+ const el = elRef.current;
4677
+ if (el && storedEmpty) {
4678
+ el.textContent = "";
4679
+ placeCaret(el, true);
4680
+ }
4681
+ if (render) setEditing(true);
4682
+ },
4683
+ onBlur: rich ? void 0 : finishEdit,
4684
+ onKeyDown: (event) => {
4685
+ event.stopPropagation();
4686
+ if (event.key === "Enter" && !event.shiftKey) {
4687
+ event.preventDefault();
4688
+ event.currentTarget.blur();
4689
+ }
4690
+ if (event.key === "Escape") {
4691
+ event.preventDefault();
4692
+ if (elRef.current) elRef.current.textContent = idleDisplay || "";
4693
+ setEditing(false);
4694
+ setFocused(false);
4695
+ event.currentTarget.blur();
4696
+ }
4697
+ },
4698
+ style: {
4699
+ cursor: "text",
4700
+ outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
4701
+ outlineOffset: "2px",
4702
+ minWidth: "1ch",
4703
+ minHeight: "1em",
4704
+ // Do not force `inline` — that overrides Tailwind `block` and parks
4705
+ // an empty-field hint on the same line as a sibling that has a value.
4706
+ opacity: showHint ? 0.5 : void 0
4707
+ },
4708
+ children: showRendered ? render(value) : null
4709
+ }
4710
+ );
4711
+ }
4712
+ function ComposerInlineHtml({
4713
+ name,
4714
+ html,
4715
+ sourceValue,
4716
+ className,
4717
+ Tag = "div",
4718
+ layoutNodeId,
4719
+ componentId
4720
+ }) {
4721
+ const elRef = useRef(null);
4722
+ useEffect(() => {
4723
+ const el = elRef.current;
4724
+ if (!el) return;
4725
+ if (el.innerHTML !== html) el.innerHTML = html;
4726
+ }, [html]);
4727
+ const openRichEditor = (event) => {
4728
+ if (!elRef.current) return;
4729
+ event.preventDefault();
4730
+ event.stopPropagation();
4731
+ requestCanvasRichTextEdit({
4732
+ el: elRef.current,
4733
+ layoutNodeId,
4734
+ componentId,
4735
+ name,
4736
+ value: sourceValue
4737
+ });
4738
+ };
4739
+ return React2.createElement(Tag, {
4740
+ ref: elRef,
4741
+ className,
4742
+ "data-amplifyup-field": name,
4743
+ "data-amplifyup-field-type": "richtext",
4744
+ "data-amplifyup-node-id": layoutNodeId || void 0,
4745
+ dangerouslySetInnerHTML: { __html: html },
4746
+ onMouseDown: (event) => {
4747
+ event.stopPropagation();
4748
+ },
4749
+ onClick: openRichEditor,
4750
+ style: {
4751
+ cursor: "text",
4752
+ outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
4753
+ outlineOffset: "2px"
4578
4754
  }
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]);
4680
- 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
- ] }) }) }) });
4755
+ });
4756
+ }
4757
+ function formatFieldValue(value) {
4758
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4759
+ return String(value);
4689
4760
  }
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
- ] }) }) }) });
4761
+ if (Array.isArray(value)) {
4762
+ const parts = value.map((item) => {
4763
+ if (typeof item === "string") return item;
4764
+ if (item && typeof item === "object" && "children" in item && Array.isArray(item.children)) {
4765
+ return item.children.map((c) => typeof c?.text === "string" ? c.text : "").join("");
4766
+ }
4767
+ return "";
4768
+ }).filter(Boolean);
4769
+ if (parts.length > 0) return parts.join(" ");
4695
4770
  }
4696
- if (!pageConfig) {
4771
+ if (value && typeof value === "object") {
4772
+ return "";
4773
+ }
4774
+ return "";
4775
+ }
4776
+ function warnProjectionProps(component, hasField, hasValue, name) {
4777
+ if (process.env.NODE_ENV === "production") return;
4778
+ if (!hasField && !hasValue && !name) {
4779
+ console.warn(
4780
+ `[AmplifyUp SDK] ${component} needs field={fields.yourProp} (or value + name for computed values)`
4781
+ );
4782
+ }
4783
+ }
4784
+ function hasOwnProp(props, key) {
4785
+ return Object.prototype.hasOwnProperty.call(props, key);
4786
+ }
4787
+ function isLinkListItem(value) {
4788
+ return !!value && typeof value === "object" && !Array.isArray(value);
4789
+ }
4790
+ function renderLinkList(items, className) {
4791
+ const links = items.filter(isLinkListItem).filter((item) => {
4792
+ const label = typeof item.label === "string" ? item.label.trim() : "";
4793
+ const href = typeof item.href === "string" ? item.href.trim() : "";
4794
+ return !!(label || href);
4795
+ });
4796
+ if (links.length === 0) return null;
4797
+ return /* @__PURE__ */ jsx("span", { className, style: { display: "contents" }, children: links.map((item, index) => {
4798
+ const label = typeof item.label === "string" && item.label.trim() ? item.label : typeof item.href === "string" ? item.href : "Link";
4799
+ const href = typeof item.href === "string" && item.href.trim() ? item.href : "#";
4800
+ const style = typeof item.style === "string" ? item.style.trim() : "";
4801
+ return /* @__PURE__ */ jsx(
4802
+ "a",
4803
+ {
4804
+ href,
4805
+ target: item.openInNewTab ? "_blank" : void 0,
4806
+ rel: item.openInNewTab ? "noreferrer" : void 0,
4807
+ "data-amplifyup-action-style": style || void 0,
4808
+ children: label
4809
+ },
4810
+ typeof item._key === "string" && item._key ? item._key : `${href}-${index}`
4811
+ );
4812
+ }) });
4813
+ }
4814
+ function Field(props) {
4815
+ const { field, value, name, className, render } = props;
4816
+ const context = useContext(ComponentContext);
4817
+ const composerEdit = useComposerEditMode();
4818
+ const hasField = isFieldEnvelope(field);
4819
+ const hasValue = hasOwnProp(props, "value");
4820
+ const { editName, fieldValue } = useBoundField(field, value, name, "Field");
4821
+ warnProjectionProps("Field", hasField, hasValue, name);
4822
+ if (!hasField && !hasValue && !name) {
4697
4823
  return null;
4698
4824
  }
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(
4707
- "div",
4708
- {
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
- )
4825
+ const empty = isComposerEmptyText(fieldValue);
4826
+ if (composerEdit && editName && context && canInlineEdit(editName, fieldValue)) {
4827
+ return /* @__PURE__ */ jsx(
4828
+ ComposerInlineField,
4829
+ {
4830
+ name: editName,
4831
+ value: fieldValue,
4832
+ className,
4833
+ render: empty ? void 0 : render,
4834
+ layoutNodeId: context.layoutNodeId,
4835
+ componentId: context.componentId,
4836
+ rich: looksLikeRichCanvasField(editName)
4837
+ }
4838
+ );
4839
+ }
4840
+ if (empty) return null;
4841
+ if (render) {
4842
+ return /* @__PURE__ */ jsx("span", { className, style: { display: "contents" }, children: render(fieldValue) });
4843
+ }
4844
+ if (Array.isArray(fieldValue)) {
4845
+ const list = renderLinkList(fieldValue, className);
4846
+ if (list) return list;
4847
+ }
4848
+ const text = formatFieldValue(fieldValue);
4849
+ if (!text) return null;
4850
+ return /* @__PURE__ */ jsx("span", { className, children: text });
4851
+ }
4852
+ function markdownToHtml(markdown) {
4853
+ try {
4854
+ const result = marked.parse(markdown, { async: false });
4855
+ return typeof result === "string" ? result : "";
4856
+ } catch {
4857
+ return markdown;
4858
+ }
4859
+ }
4860
+ function RichText(props) {
4861
+ const {
4862
+ field,
4863
+ value,
4864
+ name,
4865
+ className,
4866
+ as: Tag = "div",
4867
+ children
4868
+ } = props;
4869
+ const context = useContext(ComponentContext);
4870
+ const composerEdit = useComposerEditMode();
4871
+ const childrenValue = typeof children === "string" || typeof children === "number" ? String(children) : void 0;
4872
+ const hasField = isFieldEnvelope(field);
4873
+ const hasValueProp = hasOwnProp(props, "value") || childrenValue !== void 0;
4874
+ const boundValue = hasOwnProp(props, "value") ? value : childrenValue !== void 0 ? childrenValue : value;
4875
+ const { editName, fieldValue } = useBoundField(
4876
+ field,
4877
+ boundValue,
4878
+ name,
4879
+ "RichText"
4880
+ );
4881
+ if (!hasField && !hasValueProp && !name) {
4882
+ return null;
4883
+ }
4884
+ warnProjectionProps("RichText", hasField, hasValueProp, name);
4885
+ const canEdit = !!context && canInlineEditRichText(editName, fieldValue);
4886
+ if (isComposerEmptyText(fieldValue)) {
4887
+ if (composerEdit && canEdit) {
4888
+ return /* @__PURE__ */ jsx(
4889
+ ComposerInlineField,
4890
+ {
4891
+ name: editName,
4892
+ value: fieldValue,
4893
+ className,
4894
+ layoutNodeId: context?.layoutNodeId,
4895
+ componentId: context?.componentId,
4896
+ rich: true
4724
4897
  }
4725
4898
  );
4726
4899
  }
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 }))
4732
- });
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 : [];
4744
- }
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
- );
4758
- }
4900
+ return null;
4901
+ }
4902
+ let html = "";
4903
+ if (typeof fieldValue === "string") {
4904
+ const trimmed = fieldValue.trim();
4905
+ if (trimmed.startsWith("<") && trimmed.includes(">")) {
4906
+ html = fieldValue;
4907
+ } else {
4908
+ html = markdownToHtml(fieldValue);
4759
4909
  }
4760
- const slotsToPass = Object.keys(slotChildren).length > 0 ? slotChildren : void 0;
4910
+ } else {
4911
+ const text = formatFieldValue(fieldValue);
4912
+ html = text ? markdownToHtml(text) : "";
4913
+ }
4914
+ if (!html) {
4915
+ return null;
4916
+ }
4917
+ if (composerEdit && canEdit) {
4761
4918
  return /* @__PURE__ */ jsx(
4762
- "div",
4919
+ ComposerInlineHtml,
4763
4920
  {
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
- )
4921
+ name: editName,
4922
+ html,
4923
+ sourceValue: typeof fieldValue === "string" ? fieldValue : html,
4924
+ className,
4925
+ Tag,
4926
+ layoutNodeId: context?.layoutNodeId,
4927
+ componentId: context?.componentId
4785
4928
  }
4786
4929
  );
4787
- };
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
4930
  }
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))
4801
- }
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",
4808
- {
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, {})
4815
- }
4816
- );
4817
- }
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 || "/";
4931
+ return /* @__PURE__ */ jsx(Tag, { className, dangerouslySetInnerHTML: { __html: html } });
4826
4932
  }
4827
- function samePageRoute(a, b) {
4828
- const ar = normalizeRoutePath(a?.route);
4829
- const br = normalizeRoutePath(b?.route);
4830
- return !!ar && !!br && ar === br;
4933
+ function isPortableImageValue(value) {
4934
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
4935
+ const url = value.url;
4936
+ return typeof url === "string" && url.trim().length > 0;
4831
4937
  }
4832
- function layoutTreesEqual(a, b) {
4833
- if (!a || !b) return a === b;
4834
- return JSON.stringify(a.layoutTree) === JSON.stringify(b.layoutTree);
4938
+ function imageSrc(value) {
4939
+ if (typeof value === "string") return value.trim();
4940
+ if (isPortableImageValue(value)) return value.url.trim();
4941
+ return "";
4835
4942
  }
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;
4943
+ function Image(props) {
4944
+ const {
4945
+ field,
4946
+ value,
4947
+ name,
4948
+ alt: altProp,
4949
+ className,
4950
+ width: widthProp,
4951
+ height: heightProp,
4952
+ ...imgProps
4953
+ } = props;
4954
+ const context = useContext(ComponentContext);
4955
+ const hasField = isFieldEnvelope(field);
4956
+ const hasValue = hasOwnProp(props, "value");
4957
+ const { editName, fieldValue } = useBoundField(field, value, name, "Image");
4958
+ const composerEdit = useComposerEditMode();
4959
+ warnProjectionProps("Image", hasField, hasValue, name);
4960
+ if (!hasField && !hasValue && !name) {
4961
+ return null;
4858
4962
  }
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) {
4963
+ const src = imageSrc(fieldValue);
4964
+ const portable = isPortableImageValue(fieldValue) ? fieldValue : null;
4965
+ const alt = altProp ?? portable?.alt ?? "";
4966
+ const width = widthProp ?? portable?.width;
4967
+ const height = heightProp ?? portable?.height;
4968
+ const fieldAttrs = composerEdit && editName ? {
4969
+ "data-amplifyup-field": editName,
4970
+ "data-amplifyup-field-type": "image",
4971
+ "data-amplifyup-node-id": context?.layoutNodeId || void 0
4972
+ } : null;
4973
+ if (composerEdit && !editName && (hasField || hasValue)) {
4974
+ console.warn(
4975
+ '[AmplifyUp SDK] Image value={\u2026} requires name="\u2026" for computed values'
4976
+ );
4977
+ }
4978
+ if (!src) {
4979
+ if (!composerEdit || !fieldAttrs) return null;
4864
4980
  return /* @__PURE__ */ jsx(
4865
- AmplifyRenderer,
4981
+ "span",
4866
4982
  {
4867
- pageConfig: pageConfig ?? void 0,
4868
- renderComponent,
4869
- composerPreview: true,
4870
- loadingComponent: loadingComponent ?? previewLoading,
4871
- emptyComponent: emptyComponent ?? previewCanvas,
4872
- errorComponent: errorComponent ?? (() => previewCanvas)
4983
+ ...fieldAttrs,
4984
+ className,
4985
+ role: "img",
4986
+ "aria-label": alt || editName || "Image",
4987
+ style: {
4988
+ display: "block",
4989
+ boxSizing: "border-box",
4990
+ minWidth: width ? void 0 : 120,
4991
+ minHeight: height ? void 0 : 80,
4992
+ width: width || "100%",
4993
+ height: height || void 0,
4994
+ background: "color-mix(in srgb, currentColor 8%, transparent)",
4995
+ outline: "2px dashed hsl(82 100% 45%)",
4996
+ outlineOffset: 2,
4997
+ cursor: "pointer"
4998
+ }
4873
4999
  }
4874
5000
  );
4875
5001
  }
4876
- if (loading) {
4877
- return loadingComponent ? /* @__PURE__ */ jsx(Fragment, { children: loadingComponent }) : null;
4878
- }
4879
- if (error) {
4880
- if (errorComponent) return /* @__PURE__ */ jsx(Fragment, { children: errorComponent(error) });
4881
- return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
4882
- }
4883
- if (!hasAmplifyLayout(pageConfig)) {
4884
- return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
4885
- }
4886
5002
  return /* @__PURE__ */ jsx(
4887
- AmplifyRenderer,
5003
+ "img",
4888
5004
  {
4889
- pageConfig: pageConfig ?? void 0,
4890
- renderComponent,
4891
- loadingComponent,
4892
- emptyComponent,
4893
- errorComponent
5005
+ ...imgProps,
5006
+ ...fieldAttrs,
5007
+ src,
5008
+ alt,
5009
+ className,
5010
+ width,
5011
+ height,
5012
+ style: {
5013
+ ...typeof imgProps.style === "object" && imgProps.style ? imgProps.style : {},
5014
+ ...composerEdit && fieldAttrs ? { cursor: "pointer" } : null
5015
+ }
4894
5016
  }
4895
5017
  );
4896
5018
  }
@@ -5115,6 +5237,6 @@ function useAmplifyUp() {
5115
5237
  return context;
5116
5238
  }
5117
5239
 
5118
- export { AmplifyPageContent, AmplifyRenderer, AmplifyUpProvider, ComponentContextProvider, Field, Image, QueryContentError, RichText, SelectVariantsError, Slot, isComposerPreview, isPreviewRequest, isWorkingContentRequest, nextPageSpec, queryContent, searchSpec, selectVariants, useAmplifyUp, useComponentProps, useLiveComposerProps };
5240
+ export { AmplifyPageContent, AmplifyRenderer, AmplifyUpProvider, ComponentContextProvider, ComposerProvider, Field, Image, MaybeComposerProvider, QueryContentError, RichText, SelectVariantsError, Slot, isBrandedFields, isComposerPreview, isFieldEnvelope, isPreviewRequest, isWorkingContentRequest, nextPageSpec, queryContent, searchSpec, selectVariants, toFieldEnvelopes, useAmplifyUp };
5119
5241
  //# sourceMappingURL=react.mjs.map
5120
5242
  //# sourceMappingURL=react.mjs.map