@amplifyup/sdk 0.1.60 → 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,1280 +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
- ] })
3698
- }
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 inferEditName(explicitName, contextProps, sourceValue) {
3750
- if (explicitName) return explicitName;
3751
- if (!contextProps) return "";
3752
- const keys = Object.keys(contextProps).filter(
3753
- (key) => Object.is(contextProps[key], sourceValue)
3754
- );
3755
- if (keys.length === 1) return keys[0];
3756
- if (keys.length === 0 || isComposerEmptyText(sourceValue)) return "";
3757
- return keys[0];
3758
- }
3759
- function subscribeComposerEdit(onStoreChange) {
3760
- if (typeof window === "undefined") return () => {
3761
- };
3762
- window.addEventListener("amplifyup:composer-edit", onStoreChange);
3763
- return () => window.removeEventListener("amplifyup:composer-edit", onStoreChange);
3764
- }
3765
- function useComposerEditMode() {
3766
- return React.useSyncExternalStore(
3767
- subscribeComposerEdit,
3768
- isComposerEditMode,
3769
- () => false
3770
- );
3771
- }
3772
- function useFieldOverlay(layoutNodeId, name, source) {
3773
- const [overlay, setOverlay] = React.useState(void 0);
3774
- React.useEffect(() => {
3775
- const onPatch = (event) => {
3776
- const detail = event.detail || {};
3777
- if (detail.fieldName !== name) return;
3778
- if (detail.layoutNodeId && layoutNodeId && String(detail.layoutNodeId) !== String(layoutNodeId)) {
3779
- return;
3872
+ }
3873
+ } catch (e) {
3780
3874
  }
3781
- setOverlay(detail.value);
3875
+ return false;
3782
3876
  };
3783
- window.addEventListener("amplifyup:field-patch", onPatch);
3784
- return () => window.removeEventListener("amplifyup:field-patch", onPatch);
3785
- }, [layoutNodeId, name]);
3786
- return overlay !== void 0 ? overlay : source;
3787
- }
3788
- function commitComposerField(layoutNodeId, componentId, name, value) {
3789
- if (typeof window === "undefined" || window.parent === window) return;
3790
- window.parent.postMessage(
3791
- {
3792
- type: "AMPLIFYUP_FIELD_COMMIT",
3793
- layoutNodeId,
3794
- componentId,
3795
- fieldName: name,
3796
- value
3797
- },
3798
- "*"
3799
- );
3800
- }
3801
- function looksLikeRichCanvasField(name) {
3802
- const tokens = String(name || "").replace(/([a-z])([A-Z])/g, "$1 $2").split(/[._\-\s/]+/).map((s) => s.toLowerCase()).filter(Boolean);
3803
- const last = tokens[tokens.length - 1] || "";
3804
- return last === "body" || last === "content" || last === "copy" || last === "excerpt" || last === "markdown" || last === "richtext";
3805
- }
3806
- var lastRichTextRequestAt = 0;
3807
- function requestCanvasRichTextEdit(opts) {
3808
- if (typeof window === "undefined" || window.parent === window) return;
3809
- if (!opts.name) return;
3810
- const now = Date.now();
3811
- if (now - lastRichTextRequestAt < 400) return;
3812
- lastRichTextRequestAt = now;
3813
- const rect = opts.el.getBoundingClientRect();
3814
- window.parent.postMessage(
3815
- {
3816
- type: "AMPLIFYUP_RICHTEXT_EDIT",
3817
- layoutNodeId: opts.layoutNodeId,
3818
- componentId: opts.componentId,
3819
- fieldName: opts.name,
3820
- value: opts.value,
3821
- bounds: {
3822
- top: rect.top,
3823
- left: rect.left,
3824
- width: Math.max(rect.width, 240),
3825
- height: Math.max(rect.height, 64)
3877
+ if (!isVisualEditorMode()) {
3878
+ return;
3879
+ }
3880
+ if (!pageConfig) {
3881
+ return;
3882
+ }
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
+ }, "*");
3826
3891
  }
3827
- },
3828
- "*"
3829
- );
3830
- }
3831
- function placeCaret(el, atStart = false) {
3832
- const range = document.createRange();
3833
- range.selectNodeContents(el);
3834
- range.collapse(atStart);
3835
- const sel = window.getSelection();
3836
- sel?.removeAllRanges();
3837
- sel?.addRange(range);
3838
- }
3839
- function ComposerInlineField({
3840
- name,
3841
- value,
3842
- fallback,
3843
- className,
3844
- render,
3845
- layoutNodeId,
3846
- componentId,
3847
- rich = false
3848
- }) {
3849
- const [editing, setEditing] = React.useState(false);
3850
- const [focused, setFocused] = React.useState(false);
3851
- const elRef = React.useRef(null);
3852
- const storedEmpty = isComposerEmptyText(value);
3853
- const text = storedEmpty ? "" : formatFieldValue(value);
3854
- const fallbackText = typeof fallback === "string" || typeof fallback === "number" ? String(fallback) : "";
3855
- const hint = COMPOSER_EMPTY_FIELD_HINT;
3856
- const idleDisplay = storedEmpty ? hint : text || fallbackText;
3857
- const showRendered = !!render && !editing;
3858
- const showHint = storedEmpty && !focused && !showRendered;
3859
- React.useEffect(() => {
3860
- const el = elRef.current;
3861
- if (!el || showRendered) return;
3862
- if (document.activeElement === el) return;
3863
- if (el.querySelector(
3864
- "input, button, select, textarea, form, svg, iframe, img"
3865
- )) {
3866
3892
  return;
3867
3893
  }
3868
- const next = idleDisplay || "";
3869
- if (el.textContent !== next) el.textContent = next;
3870
- }, [idleDisplay, showRendered]);
3871
- React.useEffect(() => {
3872
- if (!editing || !elRef.current) return;
3873
- const el = elRef.current;
3874
- if (storedEmpty) {
3875
- el.textContent = "";
3876
- el.focus();
3877
- placeCaret(el, true);
3878
- return;
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;
3918
+ };
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
+ );
3879
3949
  }
3880
- el.textContent = idleDisplay || "";
3881
- el.focus();
3882
- placeCaret(el, false);
3883
- }, [editing, storedEmpty, idleDisplay]);
3884
- const finishEdit = () => {
3885
- const raw = (elRef.current?.innerText ?? "").replace(/\u00a0/g, " ").replace(/\n+$/g, "");
3886
- const next = raw.trim() === "" ? "" : raw;
3887
- setEditing(false);
3888
- setFocused(false);
3889
- if (storedEmpty && next === "") {
3890
- if (elRef.current) elRef.current.textContent = hint;
3891
- if (typeof value === "string" && EMPTY_FIELD_PLACEHOLDERS.has(value.trim())) {
3892
- commitComposerField(layoutNodeId, componentId, name, "");
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
+ );
4097
+ }
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
+ );
4129
+ }
4130
+ }
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
+ )
3893
4157
  }
3894
- return;
3895
- }
3896
- if (next === text) return;
3897
- if (!text && !storedEmpty && next === fallbackText) return;
3898
- commitComposerField(layoutNodeId, componentId, name, next);
3899
- };
3900
- const openRichEditor = (event) => {
3901
- if (!isComposerEditMode() || !elRef.current) return;
3902
- event?.preventDefault();
3903
- event?.stopPropagation();
3904
- const source = typeof value === "string" && !storedEmpty ? value : text || "";
3905
- requestCanvasRichTextEdit({
3906
- el: elRef.current,
3907
- layoutNodeId,
3908
- componentId,
3909
- name,
3910
- value: source
3911
- });
4158
+ );
3912
4159
  };
3913
- return /* @__PURE__ */ jsxRuntime.jsx(
3914
- "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",
3915
4167
  {
3916
- ref: elRef,
3917
- className,
3918
- "data-amplifyup-field": name,
3919
- "data-amplifyup-field-type": rich ? "richtext" : "text",
3920
- "data-amplifyup-node-id": layoutNodeId || void 0,
3921
- "data-amplifyup-empty-hint": showHint ? "true" : void 0,
3922
- contentEditable: !rich && !showRendered,
3923
- suppressContentEditableWarning: true,
3924
- role: "textbox",
3925
- tabIndex: 0,
3926
- onMouseDown: (event) => {
3927
- event.stopPropagation();
3928
- },
3929
- onClick: (event) => {
3930
- if (!isComposerEditMode()) return;
3931
- const link = event.target?.closest?.("a");
3932
- if (link) event.preventDefault();
3933
- if (rich) {
3934
- openRichEditor(event);
3935
- return;
3936
- }
3937
- if (render) {
3938
- setEditing(true);
3939
- }
3940
- },
3941
- onFocus: () => {
3942
- if (!isComposerEditMode()) return;
3943
- if (rich) {
3944
- openRichEditor();
3945
- return;
3946
- }
3947
- setFocused(true);
3948
- const el = elRef.current;
3949
- if (el && storedEmpty) {
3950
- el.textContent = "";
3951
- placeCaret(el, true);
3952
- }
3953
- if (render) setEditing(true);
3954
- },
3955
- onBlur: rich ? void 0 : finishEdit,
3956
- onKeyDown: (event) => {
3957
- event.stopPropagation();
3958
- if (event.key === "Enter" && !event.shiftKey) {
3959
- event.preventDefault();
3960
- event.currentTarget.blur();
3961
- }
3962
- if (event.key === "Escape") {
3963
- event.preventDefault();
3964
- if (elRef.current) elRef.current.textContent = idleDisplay || "";
3965
- setEditing(false);
3966
- setFocused(false);
3967
- event.currentTarget.blur();
3968
- }
3969
- },
3970
- style: {
3971
- cursor: "text",
3972
- outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
3973
- outlineOffset: "2px",
3974
- minWidth: "1ch",
3975
- minHeight: "1em",
3976
- // Do not force `inline` — that overrides Tailwind `block` and parks
3977
- // an empty-field hint on the same line as a sibling that has a value.
3978
- opacity: showHint ? 0.5 : void 0
3979
- },
3980
- children: showRendered ? value != null && value !== "" && !storedEmpty ? render(value) : fallback ?? idleDisplay : isReactNodeFallback(fallback) && !idleDisplay ? fallback : null
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
+ ]
3981
4211
  }
3982
4212
  );
3983
4213
  }
3984
- function ComposerInlineHtml({
3985
- name,
3986
- html,
3987
- sourceValue,
3988
- className,
3989
- Tag = "div",
3990
- layoutNodeId,
3991
- componentId
4214
+ function EmptySlotHint({
4215
+ label = "Click here to Add Component",
4216
+ compact = false
3992
4217
  }) {
3993
- const elRef = React.useRef(null);
3994
- React.useEffect(() => {
3995
- const el = elRef.current;
3996
- if (!el) return;
3997
- if (el.innerHTML !== html) el.innerHTML = html;
3998
- }, [html]);
3999
- const openRichEditor = (event) => {
4000
- if (!elRef.current) return;
4001
- event.preventDefault();
4002
- event.stopPropagation();
4003
- requestCanvasRichTextEdit({
4004
- el: elRef.current,
4005
- layoutNodeId,
4006
- componentId,
4007
- name,
4008
- value: sourceValue
4009
- });
4010
- };
4011
- return React__default.default.createElement(Tag, {
4012
- ref: elRef,
4013
- className,
4014
- "data-amplifyup-field": name,
4015
- "data-amplifyup-field-type": "richtext",
4016
- "data-amplifyup-node-id": layoutNodeId || void 0,
4017
- dangerouslySetInnerHTML: { __html: html },
4018
- onMouseDown: (event) => {
4019
- event.stopPropagation();
4020
- },
4021
- onClick: openRichEditor,
4022
- style: {
4023
- cursor: "text",
4024
- outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
4025
- outlineOffset: "2px"
4218
+ const iconSize = compact ? 20 : 28;
4219
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4220
+ "div",
4221
+ {
4222
+ className: "amplifyup-slot-placeholder",
4223
+ style: {
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"
4235
+ },
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
+ ]
4026
4268
  }
4027
- });
4269
+ );
4028
4270
  }
4029
- function formatFieldValue(value) {
4030
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4031
- return String(value);
4032
- }
4033
- if (Array.isArray(value)) {
4034
- const parts = value.map((item) => {
4035
- if (typeof item === "string") return item;
4036
- if (item && typeof item === "object" && "children" in item && Array.isArray(item.children)) {
4037
- return item.children.map((c) => typeof c?.text === "string" ? c.text : "").join("");
4038
- }
4039
- return "";
4040
- }).filter(Boolean);
4041
- if (parts.length > 0) return parts.join(" ");
4042
- }
4043
- if (value && typeof value === "object") {
4044
- return "";
4045
- }
4046
- return "";
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, {})
4281
+ }
4282
+ );
4047
4283
  }
4048
- function warnProjectionProps(component, name, hasExplicitValue) {
4049
- if (process.env.NODE_ENV === "production") return;
4050
- if (hasExplicitValue) return;
4051
- if (name) {
4052
- console.warn(
4053
- `[AmplifyUp SDK] ${component} \`name\` is deprecated and will be removed. Prefer value={${name}} from your component props.`
4054
- );
4055
- }
4284
+ function hasAmplifyLayout(pageConfig) {
4285
+ return !!(pageConfig && pageConfig.id !== "default" && Array.isArray(pageConfig.layoutTree) && pageConfig.layoutTree.length > 0);
4056
4286
  }
4057
- function hasOwnProp(props, key) {
4058
- return Object.prototype.hasOwnProperty.call(props, key);
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 || "/";
4059
4292
  }
4060
- function isLinkListItem(value) {
4061
- return !!value && typeof value === "object" && !Array.isArray(value);
4293
+ function samePageRoute(a, b) {
4294
+ const ar = normalizeRoutePath(a?.route);
4295
+ const br = normalizeRoutePath(b?.route);
4296
+ return !!ar && !!br && ar === br;
4062
4297
  }
4063
- function renderLinkList(items, className) {
4064
- const links = items.filter(isLinkListItem).filter((item) => {
4065
- const label = typeof item.label === "string" ? item.label.trim() : "";
4066
- const href = typeof item.href === "string" ? item.href.trim() : "";
4067
- return !!(label || href);
4068
- });
4069
- if (links.length === 0) return null;
4070
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className, style: { display: "contents" }, children: links.map((item, index) => {
4071
- const label = typeof item.label === "string" && item.label.trim() ? item.label : typeof item.href === "string" ? item.href : "Link";
4072
- const href = typeof item.href === "string" && item.href.trim() ? item.href : "#";
4073
- const style = typeof item.style === "string" ? item.style.trim() : "";
4298
+ function layoutTreesEqual(a, b) {
4299
+ if (!a || !b) return a === b;
4300
+ return JSON.stringify(a.layoutTree) === JSON.stringify(b.layoutTree);
4301
+ }
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) {
4074
4330
  return /* @__PURE__ */ jsxRuntime.jsx(
4075
- "a",
4331
+ AmplifyRenderer,
4076
4332
  {
4077
- href,
4078
- target: item.openInNewTab ? "_blank" : void 0,
4079
- rel: item.openInNewTab ? "noreferrer" : void 0,
4080
- "data-amplifyup-action-style": style || void 0,
4081
- children: label
4082
- },
4083
- 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
+ }
4084
4340
  );
4085
- }) });
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
+ );
4086
4362
  }
4087
- function Field(props) {
4088
- const { value, name, fallback, className, render } = props;
4089
- const context = React.useContext(ComponentContext);
4090
- const composerEdit = useComposerEditMode();
4091
- const hasExplicitValue = hasOwnProp(props, "value");
4092
- const sourceValue = hasExplicitValue ? value : name ? context?.props?.[name] : void 0;
4093
- const editName = inferEditName(name, context?.props, sourceValue);
4094
- const fieldValue = useFieldOverlay(
4095
- context?.layoutNodeId,
4096
- editName,
4097
- 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
+ }
4098
4378
  );
4099
- warnProjectionProps("Field", name, hasExplicitValue);
4100
- if (!context && !hasExplicitValue) {
4101
- console.warn(
4102
- "[AmplifyUp SDK] Field needs a `value` prop (or deprecated `name` alone)"
4103
- );
4104
- 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;
4105
4388
  }
4106
- if (render) {
4107
- if (composerEdit && editName && context && canInlineEdit(editName, fieldValue)) {
4108
- return /* @__PURE__ */ jsxRuntime.jsx(
4109
- 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",
4110
4429
  {
4111
- name: editName,
4112
- value: fieldValue,
4113
- fallback,
4114
- className,
4115
- render,
4116
- layoutNodeId: context.layoutNodeId,
4117
- componentId: context.componentId,
4118
- 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}`
4119
4438
  }
4120
- );
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
+ ] })
4121
4461
  }
4122
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className, style: { display: "contents" }, children: render(fieldValue) });
4123
- }
4124
- if (Array.isArray(fieldValue)) {
4125
- const list = renderLinkList(fieldValue, className);
4126
- if (list) return list;
4127
- }
4128
- const text = formatFieldValue(fieldValue);
4129
- const showVisualFallback = !text && isReactNodeFallback(fallback);
4130
- const emptyBlockField = isComposerEmptyText(fieldValue) && classNameIsBlock(className);
4131
- if (composerEdit && editName && context && canInlineEdit(editName, fieldValue) && !showVisualFallback && !emptyBlockField) {
4132
- return /* @__PURE__ */ jsxRuntime.jsx(
4133
- ComposerInlineField,
4134
- {
4135
- name: editName,
4136
- value: fieldValue,
4137
- fallback,
4138
- className,
4139
- layoutNodeId: context.layoutNodeId,
4140
- componentId: context.componentId,
4141
- rich: looksLikeRichCanvasField(editName)
4142
- }
4143
- );
4144
- }
4145
- if (fieldValue === void 0 || fieldValue === null || isComposerEmptyText(fieldValue)) {
4146
- return fallback ? /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: fallback }) : null;
4147
- }
4148
- if (!text) {
4149
- 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;
4150
4490
  }
4151
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: text });
4491
+ if (typeof value === "boolean") return false;
4492
+ return true;
4152
4493
  }
4153
- function markdownToHtml(markdown) {
4154
- try {
4155
- const result = marked.marked.parse(markdown, { async: false });
4156
- return typeof result === "string" ? result : "";
4157
- } catch {
4158
- 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;
4159
4499
  }
4500
+ return true;
4160
4501
  }
4161
- function RichText(props) {
4162
- const {
4163
- value,
4164
- name,
4165
- fallback,
4166
- className,
4167
- as: Tag = "div",
4168
- children
4169
- } = props;
4170
- const context = React.useContext(ComponentContext);
4171
- const composerEdit = useComposerEditMode();
4172
- const childrenValue = typeof children === "string" || typeof children === "number" ? String(children) : void 0;
4173
- const hasValueProp = hasOwnProp(props, "value");
4174
- const hasExplicitValue = hasValueProp || childrenValue !== void 0;
4175
- const sourceValue = hasValueProp ? value : childrenValue !== void 0 ? childrenValue : name ? context?.props?.[name] : void 0;
4176
- const editName = inferEditName(name, context?.props, sourceValue);
4177
- const fieldValue = useFieldOverlay(
4178
- context?.layoutNodeId,
4179
- editName,
4180
- sourceValue
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);
4507
+ }
4508
+ function useComposerEditMode() {
4509
+ return React2.useSyncExternalStore(
4510
+ subscribeComposerEdit,
4511
+ isComposerEditMode,
4512
+ () => false
4181
4513
  );
4182
- if (!context && !hasExplicitValue) {
4514
+ }
4515
+ function useBoundField(field, value, name, component) {
4516
+ const bound = resolveEditableBinding(field, value, name);
4517
+ if (bound.requiresName) {
4183
4518
  console.warn(
4184
- "[AmplifyUp SDK] RichText needs a `value` prop (or string children), or deprecated `name` alone"
4185
- );
4186
- return fallback ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback }) : null;
4187
- }
4188
- warnProjectionProps("RichText", name, hasExplicitValue);
4189
- const canEdit = !!context && canInlineEditRichText(editName, fieldValue);
4190
- if (isComposerEmptyText(fieldValue)) {
4191
- if (composerEdit && canEdit && !isReactNodeFallback(fallback) && !classNameIsBlock(className)) {
4192
- return /* @__PURE__ */ jsxRuntime.jsx(
4193
- ComposerInlineField,
4194
- {
4195
- name: editName,
4196
- value: fieldValue,
4197
- fallback: typeof fallback === "string" || typeof fallback === "number" ? fallback : void 0,
4198
- className,
4199
- layoutNodeId: context?.layoutNodeId,
4200
- componentId: context?.componentId,
4201
- rich: true
4202
- }
4203
- );
4204
- }
4205
- return fallback ? /* @__PURE__ */ jsxRuntime.jsx(Tag, { className, children: fallback }) : null;
4206
- }
4207
- let html = "";
4208
- if (typeof fieldValue === "string") {
4209
- const trimmed = fieldValue.trim();
4210
- if (trimmed.startsWith("<") && trimmed.includes(">")) {
4211
- html = fieldValue;
4212
- } else {
4213
- html = markdownToHtml(fieldValue);
4214
- }
4215
- } else {
4216
- const text = formatFieldValue(fieldValue);
4217
- html = text ? markdownToHtml(text) : "";
4218
- }
4219
- if (!html) {
4220
- return fallback ? /* @__PURE__ */ jsxRuntime.jsx(Tag, { className, children: fallback }) : null;
4221
- }
4222
- if (composerEdit && canEdit) {
4223
- return /* @__PURE__ */ jsxRuntime.jsx(
4224
- ComposerInlineHtml,
4225
- {
4226
- name: editName,
4227
- html,
4228
- sourceValue: typeof fieldValue === "string" ? fieldValue : html,
4229
- className,
4230
- Tag,
4231
- layoutNodeId: context?.layoutNodeId,
4232
- componentId: context?.componentId
4233
- }
4519
+ `[AmplifyUp SDK] ${component} value={\u2026} requires name="\u2026" for computed values`
4234
4520
  );
4235
4521
  }
4236
- return /* @__PURE__ */ jsxRuntime.jsx(Tag, { className, dangerouslySetInnerHTML: { __html: html } });
4522
+ const fieldValue = useComposerFieldValue(bound.name, bound.value);
4523
+ return { editName: bound.name, fieldValue };
4237
4524
  }
4238
- function isPortableImageValue(value) {
4239
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
4240
- const url = value.url;
4241
- 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
+ );
4242
4537
  }
4243
- function imageSrc(value) {
4244
- if (typeof value === "string") return value.trim();
4245
- if (isPortableImageValue(value)) return value.url.trim();
4246
- 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";
4247
4542
  }
4248
- function Image(props) {
4249
- const {
4250
- value,
4251
- name,
4252
- alt: altProp,
4253
- className,
4254
- width: widthProp,
4255
- height: heightProp,
4256
- ...imgProps
4257
- } = props;
4258
- const context = React.useContext(ComponentContext);
4259
- const hasExplicitValue = hasOwnProp(props, "value");
4260
- const sourceValue = hasExplicitValue ? value : name ? context?.props?.[name] : void 0;
4261
- const fieldValue = useFieldOverlay(
4262
- context?.layoutNodeId,
4263
- inferEditName(name, context?.props, sourceValue),
4264
- 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
+ "*"
4265
4566
  );
4266
- const composerEdit = useComposerEditMode();
4267
- const editName = inferEditName(name, context?.props, sourceValue);
4268
- warnProjectionProps("Image", name, hasExplicitValue);
4269
- if (!context && !hasExplicitValue) {
4270
- console.warn(
4271
- "[AmplifyUp SDK] Image needs a `value` prop (or deprecated `name` alone)"
4272
- );
4273
- return null;
4274
- }
4275
- const src = imageSrc(fieldValue);
4276
- const portable = isPortableImageValue(fieldValue) ? fieldValue : null;
4277
- const alt = altProp ?? portable?.alt ?? "";
4278
- const width = widthProp ?? portable?.width;
4279
- const height = heightProp ?? portable?.height;
4280
- const fieldAttrs = composerEdit && editName ? {
4281
- "data-amplifyup-field": editName,
4282
- "data-amplifyup-field-type": "image",
4283
- "data-amplifyup-node-id": context?.layoutNodeId || void 0
4284
- } : null;
4285
- if (!src) {
4286
- if (!composerEdit || !fieldAttrs) return null;
4287
- return /* @__PURE__ */ jsxRuntime.jsx(
4288
- "span",
4289
- {
4290
- ...fieldAttrs,
4291
- className,
4292
- role: "img",
4293
- "aria-label": alt || editName || "Image",
4294
- style: {
4295
- display: "inline-block",
4296
- minWidth: width ? void 0 : 48,
4297
- minHeight: height ? void 0 : 48,
4298
- width: width || void 0,
4299
- height: height || void 0,
4300
- background: "color-mix(in srgb, currentColor 8%, transparent)",
4301
- outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
4302
- outlineOffset: 2
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;
4303
4670
  }
4304
- }
4305
- );
4306
- }
4307
- return /* @__PURE__ */ jsxRuntime.jsx(
4308
- "img",
4309
- {
4310
- ...imgProps,
4311
- ...fieldAttrs,
4312
- src,
4313
- alt,
4314
- className,
4315
- width,
4316
- height
4671
+ if (render) {
4672
+ setEditing(true);
4673
+ }
4674
+ },
4675
+ onFocus: () => {
4676
+ if (!isComposerEditMode()) return;
4677
+ if (rich) {
4678
+ openRichEditor();
4679
+ return;
4680
+ }
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
+ },
4704
+ style: {
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
4317
4715
  }
4318
4716
  );
4319
4717
  }
4320
- function nodeRenderProps(node) {
4321
- return {
4322
- ...node.settings || {},
4323
- ...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
+ });
4324
4744
  };
4325
- }
4326
- var ComponentRenderBoundary = class extends React.Component {
4327
- constructor() {
4328
- super(...arguments);
4329
- this.state = { error: null };
4330
- }
4331
- static getDerivedStateFromError(error) {
4332
- return { error };
4333
- }
4334
- componentDidCatch(error) {
4335
- console.error(
4336
- `[AmplifyRenderer] "${this.props.componentId}" (${this.props.nodeId}) threw while rendering:`,
4337
- error
4338
- );
4339
- }
4340
- render() {
4341
- if (this.state.error) {
4342
- if (process.env.NODE_ENV !== "production") {
4343
- return /* @__PURE__ */ jsxRuntime.jsxs(
4344
- "div",
4345
- {
4346
- "data-amplifyup-render-error": "",
4347
- "data-component-id": this.props.componentId,
4348
- "data-node-id": this.props.nodeId,
4349
- style: {
4350
- margin: "1rem 0",
4351
- padding: "0.75rem 1rem",
4352
- border: "1px solid #fca5a5",
4353
- borderRadius: "0.5rem",
4354
- background: "#fef2f2",
4355
- color: "#991b1b",
4356
- fontSize: "0.875rem"
4357
- },
4358
- children: [
4359
- 'Component "',
4360
- this.props.componentId,
4361
- '" failed to render:',
4362
- " ",
4363
- this.state.error.message
4364
- ]
4365
- }
4366
- );
4367
- }
4368
- return null;
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"
4369
4760
  }
4370
- return this.props.children;
4371
- }
4372
- };
4373
- function LiveNodeRender({
4374
- nodeId,
4375
- componentId,
4376
- props,
4377
- slots,
4378
- renderComponent
4379
- }) {
4380
- const liveProps = useLiveComposerProps(nodeId, props);
4381
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderComponent(componentId, liveProps, slots, {
4382
- layoutNodeId: nodeId
4383
- }) });
4761
+ });
4384
4762
  }
4385
- function AmplifyRendererInner({
4386
- renderComponent,
4387
- pageConfig: serverPageConfig,
4388
- loadingComponent,
4389
- errorComponent,
4390
- emptyComponent,
4391
- composerPreview
4392
- }) {
4393
- const clientHook = useAmplifyUp();
4394
- const pageConfig = serverPageConfig || clientHook.pageConfig || null;
4395
- const loading = serverPageConfig ? false : clientHook.loading;
4396
- const error = serverPageConfig ? null : clientHook.error;
4397
- const contentSource = clientHook.source;
4398
- const [variantCache, setVariantCache] = React.useState({});
4399
- React.useEffect(() => {
4400
- if (typeof window !== "undefined") {
4401
- if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
4402
- return;
4403
- }
4404
- if (window.location.pathname.includes("/api/pages/proxy")) {
4405
- return;
4406
- }
4407
- if (window.self !== window.top) {
4408
- return;
4409
- }
4410
- const urlParams = new URLSearchParams(window.location.search);
4411
- if (urlParams.get("preview") === "true" || urlParams.get("visualEditor") === "true") {
4412
- return;
4413
- }
4414
- if (window.location.href.includes("preview=true") || window.location.href.includes("visualEditor=true")) {
4415
- return;
4416
- }
4417
- }
4418
- if (pageConfig && pageConfig.id !== "default") {
4419
- trackContentView(pageConfig.id, "page", {
4420
- route: pageConfig.route,
4421
- title: pageConfig.title
4422
- });
4423
- }
4424
- }, [pageConfig]);
4425
- React.useEffect(() => {
4426
- if (!pageConfig?.eventTracking?.length) return;
4427
- const sdk = getSDK();
4428
- if (!sdk.isInitialized()) return;
4429
- sdk.setupEventTracking(pageConfig.eventTracking);
4430
- const t1 = window.setTimeout(() => {
4431
- sdk.setupComponentViewTracking();
4432
- refreshEventTracking();
4433
- }, 200);
4434
- const t2 = window.setTimeout(() => {
4435
- sdk.setupComponentViewTracking();
4436
- refreshEventTracking();
4437
- }, 1e3);
4438
- return () => {
4439
- window.clearTimeout(t1);
4440
- window.clearTimeout(t2);
4441
- };
4442
- }, [pageConfig?.id, pageConfig?.eventTracking]);
4443
- React.useEffect(() => {
4444
- if (!pageConfig) return;
4445
- const t = window.setTimeout(() => {
4446
- getSDK().setupComponentViewTracking();
4447
- refreshEventTracking();
4448
- }, 100);
4449
- return () => window.clearTimeout(t);
4450
- }, [variantCache, pageConfig?.id]);
4451
- React.useEffect(() => {
4452
- const isVisualEditorMode = () => {
4453
- if (typeof window === "undefined") return false;
4454
- const urlParams = new URLSearchParams(window.location.search);
4455
- if (urlParams.get("visualEditor") === "true" || urlParams.get("preview") === "true") {
4456
- return true;
4457
- }
4458
- const inIframe = window.self !== window.top;
4459
- if (!inIframe) return false;
4460
- try {
4461
- if (document.referrer) {
4462
- const referrerUrl = new URL(document.referrer);
4463
- if (referrerUrl.hostname === "localhost" && referrerUrl.port === "5001") {
4464
- return true;
4465
- }
4466
- }
4467
- } catch (e) {
4763
+ function formatFieldValue(value) {
4764
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4765
+ return String(value);
4766
+ }
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("");
4468
4772
  }
4469
- return false;
4470
- };
4471
- if (!isVisualEditorMode()) {
4472
- return;
4473
- }
4474
- if (!pageConfig) {
4475
- return;
4476
- }
4477
- const rawTree2 = pageConfig.layoutTree;
4478
- const normalizedTree = Array.isArray(rawTree2) ? rawTree2 : rawTree2 && typeof rawTree2 === "object" && rawTree2.componentId ? [rawTree2] : [];
4479
- if (normalizedTree.length === 0) {
4480
- if (typeof window !== "undefined" && window.parent && window.parent !== window) {
4481
- window.parent.postMessage({
4482
- type: "COMPONENT_TREE",
4483
- tree: []
4484
- }, "*");
4773
+ return "";
4774
+ }).filter(Boolean);
4775
+ if (parts.length > 0) return parts.join(" ");
4776
+ }
4777
+ if (value && typeof value === "object") {
4778
+ return "";
4779
+ }
4780
+ return "";
4781
+ }
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)
4485
4843
  }
4486
- return;
4487
- }
4488
- const convertLayoutTreeToComponentTree = (nodes, parentId = null) => {
4489
- const result = [];
4490
- nodes.forEach((node) => {
4491
- if (!node) return;
4492
- const treeNode = {
4493
- id: node.id,
4494
- componentId: node.componentId,
4495
- componentName: node.componentId,
4496
- // Will be enhanced with actual name if available
4497
- parentId,
4498
- children: []
4499
- };
4500
- if (node.slots && node.slots.length > 0) {
4501
- node.slots.forEach((slot) => {
4502
- const kids = Array.isArray(slot?.children) ? slot.children : [];
4503
- if (kids.length > 0) {
4504
- const slotChildren = convertLayoutTreeToComponentTree(kids, node.id);
4505
- treeNode.children.push(...slotChildren);
4506
- }
4507
- });
4508
- }
4509
- result.push(treeNode);
4510
- });
4511
- return result;
4512
- };
4513
- const layoutTree2 = normalizedTree;
4514
- const componentTree = convertLayoutTreeToComponentTree(layoutTree2);
4515
- const describeLayoutTree = (nodes, depth = 0) => {
4516
- if (!nodes?.length) return "";
4517
- const indent = " ".repeat(depth);
4518
- return nodes.map((n) => {
4519
- const slotsDesc = (n.slots || []).map((s) => {
4520
- const childCount = Array.isArray(s.children) ? s.children.length : 0;
4521
- const childrenDesc = childCount > 0 ? describeLayoutTree(s.children || [], depth + 1) : "";
4522
- return `${indent} slot "${s.slotId || s.slotName || "?"}" (${childCount} children)${childrenDesc ? "\n" + childrenDesc : ""}`;
4523
- }).join("\n");
4524
- return `${indent}- ${n.id} (${n.componentId || "?"})${slotsDesc ? "\n" + slotsDesc : ""}`;
4525
- }).join("\n");
4526
- };
4527
- sdkDebugOnce(
4528
- `layoutTree:${pageConfig?.id || ""}:${pageConfig?.route || ""}`,
4529
- "[AmplifyRenderer] layoutTree received:\n%s",
4530
- describeLayoutTree(layoutTree2)
4531
4844
  );
4532
- if (typeof window !== "undefined" && window.parent && window.parent !== window) {
4533
- const message = {
4534
- type: "COMPONENT_TREE",
4535
- tree: componentTree
4536
- };
4537
- window.parent.postMessage(message, "*");
4538
- sdkDebug(
4539
- "[AmplifyRenderer] Sent component tree from layoutTree:",
4540
- componentTree.length,
4541
- "root components"
4542
- );
4543
- }
4544
- }, [pageConfig]);
4545
- React.useEffect(() => {
4546
- if (!pageConfig?.layoutTree || !pageConfig.layoutId) return;
4547
- const roots = pageConfig.layoutTree;
4548
- const fromEdge = contentSource === "edge" || roots.some((n) => n._contentSource === "edge");
4549
- if (fromEdge) return;
4550
- const sdk = getSDK();
4551
- const instanceId = sdk.getInstanceId();
4552
- const decisionApiUrl = sdk.getDecisionApiUrl();
4553
- const orchestratorUrl = sdk.getOrchestratorUrl();
4554
- if (!decisionApiUrl && !orchestratorUrl || !instanceId) return;
4555
- const fetchVariants = async () => {
4556
- const nodes = pageConfig.layoutTree;
4557
- const newCache = {};
4558
- const findZonedSlots = (nodeList) => {
4559
- const results = [];
4560
- for (const node of nodeList) {
4561
- if (node.slots) {
4562
- for (const slot of node.slots) {
4563
- if (slot.zoneId && slot.variants) {
4564
- const key = `${node.id}-${slot.slotId}`;
4565
- results.push({ node, slot, key });
4566
- }
4567
- }
4568
- }
4569
- }
4570
- return results;
4571
- };
4572
- const zonedSlots = findZonedSlots(nodes);
4573
- await Promise.all(
4574
- zonedSlots.map(async ({ node, slot, key }) => {
4575
- try {
4576
- let personalizationUrl;
4577
- if (decisionApiUrl) {
4578
- const url = new URL(decisionApiUrl);
4579
- url.searchParams.set("instanceId", instanceId);
4580
- const zoneId = slot.zoneId;
4581
- if (zoneId) {
4582
- url.searchParams.set("zoneId", zoneId);
4583
- }
4584
- const campaignId = slot.campaignId;
4585
- if (campaignId) {
4586
- url.searchParams.set("campaignId", campaignId);
4587
- }
4588
- personalizationUrl = url.toString();
4589
- } else if (orchestratorUrl) {
4590
- personalizationUrl = `${orchestratorUrl}/api/personalization/variant?instanceId=${encodeURIComponent(instanceId)}&layoutId=${encodeURIComponent(pageConfig.layoutId)}&slotId=${encodeURIComponent(slot.slotId)}&nodeId=${encodeURIComponent(node.id)}`;
4591
- } else {
4592
- return;
4593
- }
4594
- const response = await fetch(personalizationUrl);
4595
- if (response.ok) {
4596
- const data = await response.json();
4597
- if (data.variantId && data.componentTree) {
4598
- newCache[key] = {
4599
- variantId: data.variantId,
4600
- children: data.componentTree
4601
- };
4602
- if (typeof window !== "undefined") {
4603
- if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
4604
- return;
4605
- }
4606
- if (window.location.pathname.includes("/api/pages/proxy")) {
4607
- return;
4608
- }
4609
- if (window.self !== window.top) {
4610
- return;
4611
- }
4612
- }
4613
- sdk.track("Zone Impression", {
4614
- zoneId: slot.zoneId,
4615
- variantId: data.variantId,
4616
- layoutId: pageConfig.layoutId,
4617
- route: pageConfig.route
4618
- });
4619
- } else {
4620
- const defaultVariant = slot.variants?.find((v) => v.isDefault);
4621
- if (defaultVariant) {
4622
- newCache[key] = {
4623
- variantId: defaultVariant.variantId,
4624
- children: defaultVariant.children
4625
- };
4626
- }
4627
- }
4628
- }
4629
- } catch (err) {
4630
- console.error(`Error fetching variant for slot ${key}:`, err);
4631
- const defaultVariant = slot.variants?.find((v) => v.isDefault);
4632
- if (defaultVariant) {
4633
- newCache[key] = {
4634
- variantId: defaultVariant.variantId,
4635
- children: defaultVariant.children
4636
- };
4637
- }
4638
- }
4639
- })
4640
- );
4641
- setVariantCache(newCache);
4642
- };
4643
- fetchVariants();
4644
- }, [pageConfig?.layoutTree, pageConfig?.layoutId, pageConfig?.route, contentSource]);
4645
- if (loading) {
4646
- 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: [
4647
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 bg-gray-200 rounded w-1/4 mb-4" }),
4648
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 bg-gray-200 rounded w-1/2 mb-8" }),
4649
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
4650
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-64 bg-gray-200 rounded" }),
4651
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-32 bg-gray-200 rounded" })
4652
- ] })
4653
- ] }) }) }) });
4654
4845
  }
4655
- if (error) {
4656
- 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: [
4657
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold text-red-900 mb-2", children: "Error Loading Page" }),
4658
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-700", children: error })
4659
- ] }) }) }) });
4846
+ if (empty) return null;
4847
+ if (render) {
4848
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className, style: { display: "contents" }, children: render(fieldValue) });
4660
4849
  }
4661
- 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) {
4662
4888
  return null;
4663
4889
  }
4664
- pageConfig.layout?.header !== false;
4665
- pageConfig.layout?.footer !== false;
4666
- const renderNode = (node) => {
4667
- const zoneIdFromProps = node.componentId === "AmplifyZone" ? String(node.props?.zoneId || "") : "";
4668
- if (node.componentId === "AmplifyZone") {
4669
- const contentSlot = node.slots?.find((s) => s.slotId === "content") || node.slots?.[0];
4670
- 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) {
4671
4894
  return /* @__PURE__ */ jsxRuntime.jsx(
4672
- "div",
4895
+ ComposerInlineField,
4673
4896
  {
4674
- "data-node-id": node.id,
4675
- "data-amplifyup-node": "",
4676
- "data-amplifyup-component": "AmplifyZone",
4677
- "data-amplifyup-zone": zoneIdFromProps || void 0,
4678
- "data-zone-id": zoneIdFromProps || void 0,
4679
- children: /* @__PURE__ */ jsxRuntime.jsx(
4680
- "div",
4681
- {
4682
- "data-slot-id": "content",
4683
- "data-amplifyup-slot": "content",
4684
- "data-parent-node-id": node.id,
4685
- "data-parent-component-id": "AmplifyZone",
4686
- children: children.filter(Boolean).map((child) => /* @__PURE__ */ jsxRuntime.jsx(React.Fragment, { children: renderNode(child) }, child.id))
4687
- }
4688
- )
4897
+ name: editName,
4898
+ value: fieldValue,
4899
+ className,
4900
+ layoutNodeId: context?.layoutNodeId,
4901
+ componentId: context?.componentId,
4902
+ rich: true
4689
4903
  }
4690
4904
  );
4691
4905
  }
4692
- let slotChildren = {};
4693
- sdkDebugOnce(`node:${node.id}`, `[AmplifyRenderer] Rendering node ${node.id} (${node.componentId})`, {
4694
- hasSlots: !!(node.slots && node.slots.length > 0),
4695
- slotCount: node.slots?.length || 0,
4696
- slots: node.slots?.map((s) => ({ slotId: s.slotId, zoneId: s.zoneId, childrenCount: s.children?.length || 0 }))
4697
- });
4698
- if (node.slots && node.slots.length > 0) {
4699
- for (const slot of node.slots) {
4700
- const slotKey = `${node.id}-${slot.slotId}`;
4701
- let childrenToRender = [];
4702
- if (slot.zoneId && slot.variants && variantCache[slotKey]) {
4703
- childrenToRender = variantCache[slotKey].children;
4704
- } else if (slot.zoneId && slot.variants) {
4705
- const defaultVariant = slot.variants.find((v) => v.isDefault);
4706
- childrenToRender = defaultVariant?.children || [];
4707
- } else {
4708
- childrenToRender = Array.isArray(slot.children) ? slot.children : [];
4709
- }
4710
- const safeChildren = Array.isArray(childrenToRender) ? childrenToRender : [];
4711
- slotChildren[slot.slotId] = /* @__PURE__ */ jsxRuntime.jsx(
4712
- "div",
4713
- {
4714
- "data-slot-id": slot.slotId,
4715
- "data-amplifyup-slot": slot.slotId,
4716
- "data-zone-id": slot.zoneId || void 0,
4717
- "data-parent-node-id": node.id,
4718
- "data-parent-component-id": node.componentId,
4719
- children: safeChildren.filter(Boolean).map((child) => /* @__PURE__ */ jsxRuntime.jsx(React.Fragment, { children: renderNode(child) }, child.id))
4720
- },
4721
- slot.slotId
4722
- );
4723
- }
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);
4724
4915
  }
4725
- 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) {
4726
4924
  return /* @__PURE__ */ jsxRuntime.jsx(
4727
- "div",
4925
+ ComposerInlineHtml,
4728
4926
  {
4729
- "data-node-id": node.id,
4730
- "data-amplifyup-node": "",
4731
- "data-component-id": node.componentId,
4732
- "data-amplifyup-component": node.componentId,
4733
- children: /* @__PURE__ */ jsxRuntime.jsx(
4734
- ComponentRenderBoundary,
4735
- {
4736
- componentId: node.componentId,
4737
- nodeId: node.id,
4738
- children: /* @__PURE__ */ jsxRuntime.jsx(
4739
- LiveNodeRender,
4740
- {
4741
- nodeId: node.id,
4742
- componentId: node.componentId,
4743
- props: nodeRenderProps(node),
4744
- slots: slotsToPass,
4745
- renderComponent
4746
- }
4747
- )
4748
- }
4749
- )
4927
+ name: editName,
4928
+ html,
4929
+ sourceValue: typeof fieldValue === "string" ? fieldValue : html,
4930
+ className,
4931
+ Tag,
4932
+ layoutNodeId: context?.layoutNodeId,
4933
+ componentId: context?.componentId
4750
4934
  }
4751
4935
  );
4752
- };
4753
- const rawTree = pageConfig.layoutTree;
4754
- const layoutTree = Array.isArray(rawTree) ? rawTree : rawTree && typeof rawTree === "object" && rawTree.componentId ? [rawTree] : [];
4755
- if (layoutTree.length === 0) {
4756
- 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." }) }) });
4757
4936
  }
4758
- return /* @__PURE__ */ jsxRuntime.jsx("main", { children: composerPreview || isComposerPreview() ? /* @__PURE__ */ jsxRuntime.jsx(
4759
- "div",
4760
- {
4761
- "data-slot-id": "main",
4762
- "data-amplifyup-slot": "main",
4763
- "data-slot-source": "page",
4764
- "data-slot-empty": layoutTree.length === 0 ? "true" : void 0,
4765
- children: layoutTree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(React.Fragment, { children: renderNode(node) }, node.id))
4766
- }
4767
- ) : layoutTree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(React.Fragment, { children: renderNode(node) }, node.id)) });
4768
- }
4769
- var AmplifyRenderer = React.memo(AmplifyRendererInner);
4770
- function defaultPreviewCanvas() {
4771
- return /* @__PURE__ */ jsxRuntime.jsx(
4772
- "div",
4773
- {
4774
- "data-slot-id": "main",
4775
- "data-amplifyup-slot": "main",
4776
- "data-slot-source": "page",
4777
- "data-slot-empty": "true",
4778
- style: { minHeight: "50vh", width: "100%", display: "block", position: "relative" },
4779
- children: /* @__PURE__ */ jsxRuntime.jsx(EmptySlotHint, {})
4780
- }
4781
- );
4782
- }
4783
- function hasAmplifyLayout(pageConfig) {
4784
- return !!(pageConfig && pageConfig.id !== "default" && Array.isArray(pageConfig.layoutTree) && pageConfig.layoutTree.length > 0);
4785
- }
4786
- function normalizeRoutePath(route) {
4787
- if (!route) return "";
4788
- const path = route.split("?")[0] || "/";
4789
- if (path.length > 1 && path.endsWith("/")) return path.replace(/\/+$/, "");
4790
- return path || "/";
4937
+ return /* @__PURE__ */ jsxRuntime.jsx(Tag, { className, dangerouslySetInnerHTML: { __html: html } });
4791
4938
  }
4792
- function samePageRoute(a, b) {
4793
- const ar = normalizeRoutePath(a?.route);
4794
- const br = normalizeRoutePath(b?.route);
4795
- 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;
4796
4943
  }
4797
- function layoutTreesEqual(a, b) {
4798
- if (!a || !b) return a === b;
4799
- 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 "";
4800
4948
  }
4801
- function AmplifyPageContent({
4802
- fallback,
4803
- renderComponent,
4804
- pageConfig: pageConfigProp,
4805
- forceComposerPreview = false,
4806
- previewLoadingComponent,
4807
- previewEmptyComponent,
4808
- loadingComponent,
4809
- emptyComponent,
4810
- errorComponent
4811
- }) {
4812
- const client = useAmplifyUp();
4813
- const preview = forceComposerPreview || isComposerPreview();
4814
- const clientLayout = hasAmplifyLayout(client.pageConfig) ? client.pageConfig : null;
4815
- const serverLayout = hasAmplifyLayout(pageConfigProp) ? pageConfigProp : null;
4816
- let pageConfig = serverLayout ?? clientLayout;
4817
- if (preview && clientLayout) {
4818
- pageConfig = clientLayout;
4819
- } else if (clientLayout && serverLayout && samePageRoute(clientLayout, serverLayout) && !layoutTreesEqual(clientLayout, serverLayout)) {
4820
- pageConfig = clientLayout;
4821
- } else if (clientLayout && !serverLayout) {
4822
- 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;
4823
4968
  }
4824
- const loading = !hasAmplifyLayout(pageConfig) && client.loading;
4825
- const error = hasAmplifyLayout(pageConfig) ? null : pageConfigProp ? null : client.error;
4826
- const previewCanvas = previewEmptyComponent ?? defaultPreviewCanvas();
4827
- const previewLoading = previewLoadingComponent ?? previewCanvas;
4828
- 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;
4829
4986
  return /* @__PURE__ */ jsxRuntime.jsx(
4830
- AmplifyRenderer,
4987
+ "span",
4831
4988
  {
4832
- pageConfig: pageConfig ?? void 0,
4833
- renderComponent,
4834
- composerPreview: true,
4835
- loadingComponent: loadingComponent ?? previewLoading,
4836
- emptyComponent: emptyComponent ?? previewCanvas,
4837
- 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
+ }
4838
5005
  }
4839
5006
  );
4840
5007
  }
4841
- if (loading) {
4842
- return loadingComponent ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent }) : null;
4843
- }
4844
- if (error) {
4845
- if (errorComponent) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: errorComponent(error) });
4846
- return fallback ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback }) : null;
4847
- }
4848
- if (!hasAmplifyLayout(pageConfig)) {
4849
- return fallback ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback }) : null;
4850
- }
4851
5008
  return /* @__PURE__ */ jsxRuntime.jsx(
4852
- AmplifyRenderer,
5009
+ "img",
4853
5010
  {
4854
- pageConfig: pageConfig ?? void 0,
4855
- renderComponent,
4856
- loadingComponent,
4857
- emptyComponent,
4858
- 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
+ }
4859
5022
  }
4860
5023
  );
4861
5024
  }
@@ -4869,7 +5032,7 @@ if (typeof window !== "undefined") {
4869
5032
  } catch {
4870
5033
  }
4871
5034
  }
4872
- var AmplifyUpContext = React.createContext(null);
5035
+ var AmplifyUpContext = React2.createContext(null);
4873
5036
  function normalizeRoutePath2(route) {
4874
5037
  if (!route) return "";
4875
5038
  const path = route.split("?")[0] || "/";
@@ -4881,12 +5044,12 @@ function AmplifyUpProvider({
4881
5044
  config,
4882
5045
  pageContext
4883
5046
  }) {
4884
- const [initialized, setInitialized] = React.useState(false);
4885
- const [pageConfig, setPageConfig] = React.useState(null);
4886
- const [loading, setLoading] = React.useState(true);
4887
- const [error, setError] = React.useState(null);
4888
- const [source, setSource] = React.useState(null);
4889
- 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);
4890
5053
  const getActualRoute = () => {
4891
5054
  if (typeof window === "undefined") return "";
4892
5055
  const currentPath = window.location.pathname;
@@ -4903,15 +5066,15 @@ function AmplifyUpProvider({
4903
5066
  }
4904
5067
  return currentPath;
4905
5068
  };
4906
- const loadedRouteRef = React.useRef(null);
5069
+ const loadedRouteRef = React2.useRef(null);
4907
5070
  const nextPathname = usePathname ? usePathname() : null;
4908
- const [pathname, setPathname] = React.useState(() => {
5071
+ const [pathname, setPathname] = React2.useState(() => {
4909
5072
  if (typeof window !== "undefined") {
4910
5073
  return getActualRoute();
4911
5074
  }
4912
5075
  return "";
4913
5076
  });
4914
- React.useEffect(() => {
5077
+ React2.useEffect(() => {
4915
5078
  if (typeof window !== "undefined") {
4916
5079
  const actualRoute = getActualRoute();
4917
5080
  if (window.location.pathname === "/api/pages/proxy" || window.location.pathname.startsWith("/api/pages/proxy")) {
@@ -4938,11 +5101,11 @@ function AmplifyUpProvider({
4938
5101
  }
4939
5102
  }, [nextPathname]);
4940
5103
  const pageContextKey = pageContext ? `${pageContext.resourceType}:${pageContext.key}` : "";
4941
- React.useEffect(() => {
5104
+ React2.useEffect(() => {
4942
5105
  if (!initialized || !pageContext) return;
4943
5106
  getSDK().setPageContext(pageContext);
4944
5107
  }, [initialized, pageContextKey]);
4945
- React.useEffect(() => {
5108
+ React2.useEffect(() => {
4946
5109
  let cancelled = false;
4947
5110
  const sdk = getSDK();
4948
5111
  const run = async () => {
@@ -5073,7 +5236,7 @@ function AmplifyUpProvider({
5073
5236
  );
5074
5237
  }
5075
5238
  function useAmplifyUp() {
5076
- const context = React.useContext(AmplifyUpContext);
5239
+ const context = React2.useContext(AmplifyUpContext);
5077
5240
  if (!context) {
5078
5241
  throw new Error("useAmplifyUp must be used within AmplifyUpProvider");
5079
5242
  }
@@ -5084,21 +5247,24 @@ exports.AmplifyPageContent = AmplifyPageContent;
5084
5247
  exports.AmplifyRenderer = AmplifyRenderer;
5085
5248
  exports.AmplifyUpProvider = AmplifyUpProvider;
5086
5249
  exports.ComponentContextProvider = ComponentContextProvider;
5250
+ exports.ComposerProvider = ComposerProvider;
5087
5251
  exports.Field = Field;
5088
5252
  exports.Image = Image;
5253
+ exports.MaybeComposerProvider = MaybeComposerProvider;
5089
5254
  exports.QueryContentError = QueryContentError;
5090
5255
  exports.RichText = RichText;
5091
5256
  exports.SelectVariantsError = SelectVariantsError;
5092
5257
  exports.Slot = Slot;
5258
+ exports.isBrandedFields = isBrandedFields;
5093
5259
  exports.isComposerPreview = isComposerPreview;
5260
+ exports.isFieldEnvelope = isFieldEnvelope;
5094
5261
  exports.isPreviewRequest = isPreviewRequest;
5095
5262
  exports.isWorkingContentRequest = isWorkingContentRequest;
5096
5263
  exports.nextPageSpec = nextPageSpec;
5097
5264
  exports.queryContent = queryContent;
5098
5265
  exports.searchSpec = searchSpec;
5099
5266
  exports.selectVariants = selectVariants;
5267
+ exports.toFieldEnvelopes = toFieldEnvelopes;
5100
5268
  exports.useAmplifyUp = useAmplifyUp;
5101
- exports.useComponentProps = useComponentProps;
5102
- exports.useLiveComposerProps = useLiveComposerProps;
5103
5269
  //# sourceMappingURL=react.js.map
5104
5270
  //# sourceMappingURL=react.js.map