@amplifyup/sdk 0.1.61 → 0.1.63

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