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