@amplifyup/sdk 0.1.77 → 0.1.79

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.
Files changed (48) hide show
  1. package/README.md +93 -28
  2. package/dist/AmplifyRenderer-BNWBrFQN.d.ts +47 -0
  3. package/dist/AmplifyRenderer-CPUr9Bkc.d.mts +47 -0
  4. package/dist/AmplifyRenderer.d.mts +4 -40
  5. package/dist/AmplifyRenderer.d.ts +4 -40
  6. package/dist/AmplifyRenderer.js +119 -17
  7. package/dist/AmplifyRenderer.js.map +1 -1
  8. package/dist/AmplifyRenderer.mjs +119 -17
  9. package/dist/AmplifyRenderer.mjs.map +1 -1
  10. package/dist/cli.d.mts +1 -0
  11. package/dist/cli.d.ts +1 -0
  12. package/dist/cli.js +266 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/cli.mjs +260 -0
  15. package/dist/cli.mjs.map +1 -0
  16. package/dist/config.d.mts +18 -0
  17. package/dist/config.d.ts +18 -0
  18. package/dist/config.js +10 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/config.mjs +8 -0
  21. package/dist/config.mjs.map +1 -0
  22. package/dist/index.d.mts +3 -3
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +6 -5
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.mjs +6 -5
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/next.d.mts +9 -0
  29. package/dist/next.d.ts +9 -0
  30. package/dist/next.js +259 -0
  31. package/dist/next.js.map +1 -0
  32. package/dist/next.mjs +253 -0
  33. package/dist/next.mjs.map +1 -0
  34. package/dist/query-DYjs4LQQ.d.mts +262 -0
  35. package/dist/query-Xo9cy-2F.d.ts +262 -0
  36. package/dist/react.d.mts +19 -21
  37. package/dist/react.d.ts +19 -21
  38. package/dist/react.js +1488 -1336
  39. package/dist/react.js.map +1 -1
  40. package/dist/react.mjs +1488 -1337
  41. package/dist/react.mjs.map +1 -1
  42. package/dist/server.d.mts +3 -3
  43. package/dist/server.d.ts +3 -3
  44. package/dist/{types-D4TSd2Yt.d.mts → types-ffQNIQCK.d.mts} +1 -102
  45. package/dist/{types-D4TSd2Yt.d.ts → types-ffQNIQCK.d.ts} +1 -102
  46. package/package.json +17 -2
  47. package/dist/query-B23UsNIv.d.ts +0 -161
  48. package/dist/query-CV8x3oQT.d.mts +0 -161
package/dist/react.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import React3, { createContext, memo, useState, useEffect, Fragment as Fragment$1, useContext, useRef, Component, useSyncExternalStore, useCallback } from 'react';
2
+ import React3, { createContext, memo, useState, useEffect, Fragment as Fragment$1, useContext, useRef, Component, useCallback, useSyncExternalStore } from 'react';
3
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
4
  import { marked } from 'marked';
5
5
 
@@ -166,6 +166,7 @@ var AWSAdapter = class {
166
166
  return;
167
167
  }
168
168
  const trackingId = eventData?.trackingId || this.config?.trackingId;
169
+ const debug = this.config?.debug;
169
170
  const query = trackingId ? `?tid=${encodeURIComponent(trackingId)}` : "";
170
171
  const url = `${this.apiGatewayUrl}${endpoint}${query}`;
171
172
  try {
@@ -180,15 +181,15 @@ var AWSAdapter = class {
180
181
  keepalive: true
181
182
  });
182
183
  if (!response.ok) {
183
- if (this.config.debug) {
184
+ if (debug) {
184
185
  console.warn(`AmplifyUp SDK: Event tracking failed: ${response.status} ${response.statusText}`);
185
186
  }
186
- } else if (this.config.debug) {
187
+ } else if (debug) {
187
188
  const result = await response.json().catch(() => ({}));
188
189
  console.log("AmplifyUp SDK: Event tracked", result);
189
190
  }
190
191
  } catch (error) {
191
- if (this.config.debug) {
192
+ if (debug) {
192
193
  console.error("AmplifyUp SDK: Event tracking error:", error);
193
194
  }
194
195
  }
@@ -3951,6 +3952,14 @@ function getSDK(adapter) {
3951
3952
  var trackContentView = (contentId, contentType, properties) => getSDK().trackContentView(contentId, contentType, properties);
3952
3953
  var refreshEventTracking = () => getSDK().refreshEventTracking();
3953
3954
  getSDK();
3955
+ var PageContentRegistryContext = createContext(null);
3956
+ function useRegisterPageContent(enabled = true) {
3957
+ const register = useContext(PageContentRegistryContext);
3958
+ useEffect(() => {
3959
+ if (!enabled || !register) return;
3960
+ return register();
3961
+ }, [enabled, register]);
3962
+ }
3954
3963
  function useInComposer(force = false) {
3955
3964
  const [detected, setDetected] = useState(false);
3956
3965
  useEffect(() => {
@@ -4105,1417 +4114,1524 @@ function MaybeComposerProvider({
4105
4114
  }) {
4106
4115
  return /* @__PURE__ */ jsx(ComposerProvider, { enabled, children: /* @__PURE__ */ jsx(ComposerNodeContext.Provider, { value: enabled ? layoutNodeId : void 0, children }) });
4107
4116
  }
4108
- function nodeRenderProps(node) {
4109
- const settings = node.settings || {};
4110
- const hydrated = hydratePageResourceProps(
4111
- node.props || {},
4112
- getPageResourceFields()
4113
- );
4114
- const fields = hydrated.fields;
4115
- const branded = isBrandedFields(fields) ? fields : brandFields(
4116
- fields && typeof fields === "object" ? fields : {}
4117
+ function AddComponentIcon({ size = 28 }) {
4118
+ return /* @__PURE__ */ jsxs(
4119
+ "svg",
4120
+ {
4121
+ width: size,
4122
+ height: size,
4123
+ viewBox: "0 0 24 24",
4124
+ fill: "none",
4125
+ xmlns: "http://www.w3.org/2000/svg",
4126
+ "aria-hidden": "true",
4127
+ focusable: "false",
4128
+ children: [
4129
+ /* @__PURE__ */ jsx(
4130
+ "rect",
4131
+ {
4132
+ x: "3",
4133
+ y: "3",
4134
+ width: "18",
4135
+ height: "18",
4136
+ rx: "4",
4137
+ stroke: "currentColor",
4138
+ strokeWidth: "1.75"
4139
+ }
4140
+ ),
4141
+ /* @__PURE__ */ jsx(
4142
+ "path",
4143
+ {
4144
+ d: "M12 8v8M8 12h8",
4145
+ stroke: "currentColor",
4146
+ strokeWidth: "1.75",
4147
+ strokeLinecap: "round"
4148
+ }
4149
+ )
4150
+ ]
4151
+ }
4117
4152
  );
4118
- return {
4119
- ...settings,
4120
- ...hydrated,
4121
- fields: branded
4122
- };
4123
4153
  }
4124
- var ComponentRenderBoundary = class extends Component {
4125
- constructor() {
4126
- super(...arguments);
4127
- this.state = { error: null };
4128
- }
4129
- static getDerivedStateFromError(error) {
4130
- return { error };
4131
- }
4132
- componentDidCatch(error) {
4133
- console.error(
4134
- `[AmplifyRenderer] "${this.props.componentId}" (${this.props.nodeId}) threw while rendering:`,
4135
- error
4136
- );
4137
- }
4138
- render() {
4139
- if (this.state.error) {
4140
- if (process.env.NODE_ENV !== "production") {
4141
- return /* @__PURE__ */ jsxs(
4142
- "div",
4154
+ function EmptySlotHint({
4155
+ label = "Click here to Add Component",
4156
+ compact = false
4157
+ }) {
4158
+ const iconSize = compact ? 20 : 28;
4159
+ return /* @__PURE__ */ jsxs(
4160
+ "div",
4161
+ {
4162
+ className: "amplifyup-slot-placeholder",
4163
+ style: {
4164
+ display: "flex",
4165
+ flexDirection: "column",
4166
+ alignItems: "center",
4167
+ justifyContent: "center",
4168
+ gap: compact ? 8 : 12,
4169
+ minHeight: compact ? 60 : void 0,
4170
+ width: "100%",
4171
+ pointerEvents: "none",
4172
+ textAlign: "center",
4173
+ boxSizing: "border-box",
4174
+ padding: compact ? "12px 8px" : "24px 16px"
4175
+ },
4176
+ children: [
4177
+ /* @__PURE__ */ jsx(
4178
+ "span",
4143
4179
  {
4144
- "data-amplifyup-render-error": "",
4145
- "data-component-id": this.props.componentId,
4146
- "data-node-id": this.props.nodeId,
4180
+ className: "amplifyup-slot-placeholder-icon",
4147
4181
  style: {
4148
- margin: "1rem 0",
4149
- padding: "0.75rem 1rem",
4150
- border: "1px solid #fca5a5",
4151
- borderRadius: "0.5rem",
4152
- background: "#fef2f2",
4153
- color: "#991b1b",
4154
- fontSize: "0.875rem"
4182
+ display: "inline-flex",
4183
+ alignItems: "center",
4184
+ justifyContent: "center",
4185
+ width: compact ? 36 : 48,
4186
+ height: compact ? 36 : 48,
4187
+ borderRadius: 12,
4188
+ border: "1.5px dashed currentColor",
4189
+ opacity: 0.9
4155
4190
  },
4156
- children: [
4157
- 'Component "',
4158
- this.props.componentId,
4159
- '" failed to render:',
4160
- " ",
4161
- this.state.error.message
4162
- ]
4191
+ children: /* @__PURE__ */ jsx(AddComponentIcon, { size: iconSize })
4163
4192
  }
4164
- );
4165
- }
4166
- return null;
4193
+ ),
4194
+ /* @__PURE__ */ jsx(
4195
+ "span",
4196
+ {
4197
+ className: "amplifyup-slot-placeholder-label",
4198
+ style: {
4199
+ fontSize: compact ? 13 : 15,
4200
+ fontWeight: 600,
4201
+ lineHeight: 1.3,
4202
+ letterSpacing: "0.01em"
4203
+ },
4204
+ children: label
4205
+ }
4206
+ )
4207
+ ]
4167
4208
  }
4168
- return this.props.children;
4209
+ );
4210
+ }
4211
+ var ComposerLayoutStatusContext = createContext({
4212
+ ready: true
4213
+ });
4214
+ function ComposerLayoutStatusProvider({
4215
+ ready,
4216
+ children
4217
+ }) {
4218
+ return /* @__PURE__ */ jsx(ComposerLayoutStatusContext.Provider, { value: { ready }, children });
4219
+ }
4220
+ function useComposerLayoutStatus() {
4221
+ return useContext(ComposerLayoutStatusContext);
4222
+ }
4223
+
4224
+ // src/slot-state.ts
4225
+ function resolveSlotState(opts) {
4226
+ if (!opts.layoutReady) return "loading";
4227
+ return opts.hasContent ? "filled" : "empty";
4228
+ }
4229
+ function slotShowsAddComponent(state) {
4230
+ return state === "empty";
4231
+ }
4232
+ function slotStateFromEl(el) {
4233
+ if (!el) return null;
4234
+ const explicit = el.getAttribute("data-slot-state");
4235
+ if (explicit === "loading" || explicit === "empty" || explicit === "filled") {
4236
+ return explicit;
4169
4237
  }
4170
- };
4171
- function NodeWithFields({
4172
- nodeId,
4173
- componentId,
4174
- props,
4238
+ if (el.getAttribute("data-slot-empty") === "true") return "empty";
4239
+ return null;
4240
+ }
4241
+
4242
+ // src/composer-edit-mode.ts
4243
+ function isComposerEditMode() {
4244
+ if (typeof window === "undefined") return false;
4245
+ return isComposerPreview() && window.__AMPLIFYUP_EVENT_SELECTION__ !== true;
4246
+ }
4247
+ function subscribeComposerEdit(onStoreChange) {
4248
+ if (typeof window === "undefined") return () => {
4249
+ };
4250
+ window.addEventListener("amplifyup:composer-edit", onStoreChange);
4251
+ return () => window.removeEventListener("amplifyup:composer-edit", onStoreChange);
4252
+ }
4253
+ function getComposerEditModeServer() {
4254
+ return false;
4255
+ }
4256
+ var ComponentContext = createContext(null);
4257
+ function ComponentContextProvider({
4258
+ children,
4175
4259
  slots,
4176
- renderComponent,
4177
- composerPreview
4178
- }) {
4179
- return /* @__PURE__ */ jsx(MaybeComposerProvider, { enabled: !!composerPreview, layoutNodeId: nodeId, children: renderComponent(componentId, props, slots, {
4180
- layoutNodeId: nodeId
4181
- }) });
4182
- }
4183
- function AmplifyRendererInner({
4184
- renderComponent,
4185
- pageConfig: serverPageConfig,
4186
- loadingComponent,
4187
- errorComponent,
4188
- emptyComponent,
4189
- composerPreview
4260
+ props,
4261
+ layoutNodeId,
4262
+ componentId
4190
4263
  }) {
4191
- const clientHook = useAmplifyUp();
4192
- const composerPreviewFlag = useComposerPreviewFlag(!!composerPreview);
4193
- const pageConfig = serverPageConfig || clientHook.pageConfig || null;
4194
- const loading = serverPageConfig ? false : clientHook.loading;
4195
- const error = serverPageConfig ? null : clientHook.error;
4196
- const contentSource = clientHook.source;
4197
- const [variantCache, setVariantCache] = useState({});
4198
- useEffect(() => {
4199
- if (typeof window !== "undefined") {
4200
- if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
4201
- return;
4202
- }
4203
- if (window.location.pathname.includes("/api/pages/proxy")) {
4204
- return;
4205
- }
4206
- if (window.self !== window.top) {
4207
- return;
4208
- }
4209
- const urlParams = new URLSearchParams(window.location.search);
4210
- if (urlParams.get("preview") === "true" || urlParams.get("visualEditor") === "true") {
4211
- return;
4212
- }
4213
- if (window.location.href.includes("preview=true") || window.location.href.includes("visualEditor=true")) {
4214
- return;
4215
- }
4216
- }
4217
- if (pageConfig && pageConfig.id !== "default") {
4218
- trackContentView(pageConfig.id, "page", {
4219
- route: pageConfig.route,
4220
- title: pageConfig.title
4221
- });
4222
- }
4223
- }, [pageConfig]);
4224
- useEffect(() => {
4225
- if (!pageConfig?.eventTracking?.length) return;
4226
- const sdk = getSDK();
4227
- if (!sdk.isInitialized()) return;
4228
- sdk.setupEventTracking(pageConfig.eventTracking);
4229
- const t1 = window.setTimeout(() => {
4230
- sdk.setupComponentViewTracking();
4231
- refreshEventTracking();
4232
- }, 200);
4233
- const t2 = window.setTimeout(() => {
4234
- sdk.setupComponentViewTracking();
4235
- refreshEventTracking();
4236
- }, 1e3);
4237
- return () => {
4238
- window.clearTimeout(t1);
4239
- window.clearTimeout(t2);
4240
- };
4241
- }, [pageConfig?.id, pageConfig?.eventTracking]);
4242
- useEffect(() => {
4243
- if (!pageConfig) return;
4244
- const t = window.setTimeout(() => {
4245
- getSDK().setupComponentViewTracking();
4246
- refreshEventTracking();
4247
- }, 100);
4248
- return () => window.clearTimeout(t);
4249
- }, [variantCache, pageConfig?.id]);
4250
- useEffect(() => {
4251
- if (!isComposerPreview()) {
4252
- return;
4253
- }
4254
- if (!pageConfig) {
4255
- return;
4256
- }
4257
- const rawTree2 = pageConfig.layoutTree;
4258
- const normalizedTree = Array.isArray(rawTree2) ? rawTree2 : rawTree2 && typeof rawTree2 === "object" && rawTree2.componentId ? [rawTree2] : [];
4259
- if (normalizedTree.length === 0) {
4260
- if (typeof window !== "undefined" && window.parent && window.parent !== window) {
4261
- window.parent.postMessage({
4262
- type: "COMPONENT_TREE",
4263
- tree: []
4264
- }, "*");
4265
- }
4266
- return;
4267
- }
4268
- const convertLayoutTreeToComponentTree = (nodes, parentId = null) => {
4269
- const result = [];
4270
- nodes.forEach((node) => {
4271
- if (!node) return;
4272
- const treeNode = {
4273
- id: node.id,
4274
- componentId: node.componentId,
4275
- componentName: node.componentId,
4276
- // Will be enhanced with actual name if available
4277
- parentId,
4278
- children: []
4279
- };
4280
- if (node.slots && node.slots.length > 0) {
4281
- node.slots.forEach((slot) => {
4282
- const kids = Array.isArray(slot?.children) ? slot.children : [];
4283
- if (kids.length > 0) {
4284
- const slotChildren = convertLayoutTreeToComponentTree(kids, node.id);
4285
- treeNode.children.push(...slotChildren);
4286
- }
4287
- });
4288
- }
4289
- result.push(treeNode);
4290
- });
4291
- return result;
4292
- };
4293
- const layoutTree2 = normalizedTree;
4294
- const componentTree = convertLayoutTreeToComponentTree(layoutTree2);
4295
- const describeLayoutTree = (nodes, depth = 0) => {
4296
- if (!nodes?.length) return "";
4297
- const indent = " ".repeat(depth);
4298
- return nodes.map((n) => {
4299
- const slotsDesc = (n.slots || []).map((s) => {
4300
- const childCount = Array.isArray(s.children) ? s.children.length : 0;
4301
- const childrenDesc = childCount > 0 ? describeLayoutTree(s.children || [], depth + 1) : "";
4302
- return `${indent} slot "${s.slotId || s.slotName || "?"}" (${childCount} children)${childrenDesc ? "\n" + childrenDesc : ""}`;
4303
- }).join("\n");
4304
- return `${indent}- ${n.id} (${n.componentId || "?"})${slotsDesc ? "\n" + slotsDesc : ""}`;
4305
- }).join("\n");
4306
- };
4307
- sdkDebugOnce(
4308
- `layoutTree:${pageConfig?.id || ""}:${pageConfig?.route || ""}`,
4309
- "[AmplifyRenderer] layoutTree received:\n%s",
4310
- describeLayoutTree(layoutTree2)
4311
- );
4312
- if (typeof window !== "undefined" && window.parent && window.parent !== window) {
4313
- const message = {
4314
- type: "COMPONENT_TREE",
4315
- tree: componentTree
4316
- };
4317
- window.parent.postMessage(message, "*");
4318
- sdkDebug(
4319
- "[AmplifyRenderer] Sent component tree from layoutTree:",
4320
- componentTree.length,
4321
- "root components"
4322
- );
4323
- }
4324
- }, [pageConfig]);
4325
- useEffect(() => {
4326
- if (!pageConfig?.layoutTree || !pageConfig.layoutId) return;
4327
- const roots = pageConfig.layoutTree;
4328
- const fromEdge = contentSource === "edge" || roots.some((n) => n._contentSource === "edge");
4329
- if (fromEdge) return;
4330
- const sdk = getSDK();
4331
- const instanceId = sdk.getInstanceId();
4332
- const decisionApiUrl = sdk.getDecisionApiUrl();
4333
- const orchestratorUrl = sdk.getOrchestratorUrl();
4334
- if (!decisionApiUrl && !orchestratorUrl || !instanceId) return;
4335
- const fetchVariants = async () => {
4336
- const nodes = pageConfig.layoutTree;
4337
- const newCache = {};
4338
- const findZonedSlots = (nodeList) => {
4339
- const results = [];
4340
- for (const node of nodeList) {
4341
- if (node.slots) {
4342
- for (const slot of node.slots) {
4343
- if (slot.zoneId && slot.variants) {
4344
- const key = `${node.id}-${slot.slotId}`;
4345
- results.push({ node, slot, key });
4346
- }
4347
- }
4348
- }
4349
- }
4350
- return results;
4351
- };
4352
- const zonedSlots = findZonedSlots(nodes);
4353
- await Promise.all(
4354
- zonedSlots.map(async ({ node, slot, key }) => {
4355
- try {
4356
- let personalizationUrl;
4357
- if (decisionApiUrl) {
4358
- const url = new URL(decisionApiUrl);
4359
- url.searchParams.set("instanceId", instanceId);
4360
- const zoneId = slot.zoneId;
4361
- if (zoneId) {
4362
- url.searchParams.set("zoneId", zoneId);
4363
- }
4364
- const campaignId = slot.campaignId;
4365
- if (campaignId) {
4366
- url.searchParams.set("campaignId", campaignId);
4367
- }
4368
- personalizationUrl = url.toString();
4369
- } else if (orchestratorUrl) {
4370
- personalizationUrl = `${orchestratorUrl}/api/personalization/variant?instanceId=${encodeURIComponent(instanceId)}&layoutId=${encodeURIComponent(pageConfig.layoutId)}&slotId=${encodeURIComponent(slot.slotId)}&nodeId=${encodeURIComponent(node.id)}`;
4371
- } else {
4372
- return;
4373
- }
4374
- const response = await fetch(personalizationUrl);
4375
- if (response.ok) {
4376
- const data = await response.json();
4377
- if (data.variantId && data.componentTree) {
4378
- newCache[key] = {
4379
- variantId: data.variantId,
4380
- children: data.componentTree
4381
- };
4382
- if (typeof window !== "undefined") {
4383
- if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
4384
- return;
4385
- }
4386
- if (window.location.pathname.includes("/api/pages/proxy")) {
4387
- return;
4388
- }
4389
- if (window.self !== window.top) {
4390
- return;
4391
- }
4392
- }
4393
- sdk.track("Zone Impression", {
4394
- zoneId: slot.zoneId,
4395
- variantId: data.variantId,
4396
- layoutId: pageConfig.layoutId,
4397
- route: pageConfig.route
4398
- });
4399
- } else {
4400
- const defaultVariant = slot.variants?.find((v) => v.isDefault);
4401
- if (defaultVariant) {
4402
- newCache[key] = {
4403
- variantId: defaultVariant.variantId,
4404
- children: defaultVariant.children
4405
- };
4406
- }
4407
- }
4408
- }
4409
- } catch (err) {
4410
- console.error(`Error fetching variant for slot ${key}:`, err);
4411
- const defaultVariant = slot.variants?.find((v) => v.isDefault);
4412
- if (defaultVariant) {
4413
- newCache[key] = {
4414
- variantId: defaultVariant.variantId,
4415
- children: defaultVariant.children
4416
- };
4417
- }
4418
- }
4419
- })
4420
- );
4421
- setVariantCache(newCache);
4422
- };
4423
- fetchVariants();
4424
- }, [pageConfig?.layoutTree, pageConfig?.layoutId, pageConfig?.route, contentSource]);
4425
- if (loading) {
4426
- return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50", children: /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsxs("div", { className: "animate-pulse", children: [
4427
- /* @__PURE__ */ jsx("div", { className: "h-8 bg-gray-200 rounded w-1/4 mb-4" }),
4428
- /* @__PURE__ */ jsx("div", { className: "h-4 bg-gray-200 rounded w-1/2 mb-8" }),
4429
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
4430
- /* @__PURE__ */ jsx("div", { className: "h-64 bg-gray-200 rounded" }),
4431
- /* @__PURE__ */ jsx("div", { className: "h-32 bg-gray-200 rounded" })
4432
- ] })
4433
- ] }) }) }) });
4434
- }
4435
- if (error) {
4436
- return /* @__PURE__ */ jsx(Fragment, { children: errorComponent ? errorComponent(error) : /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50", children: /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsxs("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6", children: [
4437
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-red-900 mb-2", children: "Error Loading Page" }),
4438
- /* @__PURE__ */ jsx("p", { className: "text-red-700", children: error })
4439
- ] }) }) }) });
4440
- }
4441
- if (!pageConfig) {
4442
- return null;
4443
- }
4444
- pageConfig.layout?.header !== false;
4445
- pageConfig.layout?.footer !== false;
4446
- const renderNode = (node) => {
4447
- const zoneIdFromProps = node.componentId === "AmplifyZone" ? String(node.props?.zoneId || "") : "";
4448
- if (node.componentId === "AmplifyZone") {
4449
- const contentSlot = node.slots?.find((s) => s.slotId === "content") || node.slots?.[0];
4450
- const children = Array.isArray(contentSlot?.children) ? contentSlot.children : [];
4451
- return /* @__PURE__ */ jsx(
4452
- "div",
4453
- {
4454
- "data-node-id": node.id,
4455
- "data-amplifyup-node": "",
4456
- "data-amplifyup-component": "AmplifyZone",
4457
- "data-amplifyup-zone": zoneIdFromProps || void 0,
4458
- "data-zone-id": zoneIdFromProps || void 0,
4459
- children: /* @__PURE__ */ jsx(
4460
- "div",
4461
- {
4462
- "data-slot-id": "content",
4463
- "data-amplifyup-slot": "content",
4464
- "data-parent-node-id": node.id,
4465
- "data-parent-component-id": "AmplifyZone",
4466
- children: children.filter(Boolean).map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child) }, child.id))
4467
- }
4468
- )
4469
- }
4470
- );
4471
- }
4472
- let slotChildren = {};
4473
- sdkDebugOnce(`node:${node.id}`, `[AmplifyRenderer] Rendering node ${node.id} (${node.componentId})`, {
4474
- hasSlots: !!(node.slots && node.slots.length > 0),
4475
- slotCount: node.slots?.length || 0,
4476
- slots: node.slots?.map((s) => ({ slotId: s.slotId, zoneId: s.zoneId, childrenCount: s.children?.length || 0 }))
4477
- });
4478
- if (node.slots && node.slots.length > 0) {
4479
- for (const slot of node.slots) {
4480
- const slotKey = `${node.id}-${slot.slotId}`;
4481
- let childrenToRender = [];
4482
- if (slot.zoneId && slot.variants && variantCache[slotKey]) {
4483
- childrenToRender = variantCache[slotKey].children;
4484
- } else if (slot.zoneId && slot.variants) {
4485
- const defaultVariant = slot.variants.find((v) => v.isDefault);
4486
- childrenToRender = defaultVariant?.children || [];
4487
- } else {
4488
- childrenToRender = Array.isArray(slot.children) ? slot.children : [];
4489
- }
4490
- const safeChildren = Array.isArray(childrenToRender) ? childrenToRender : [];
4491
- slotChildren[slot.slotId] = /* @__PURE__ */ jsx(
4492
- "div",
4493
- {
4494
- "data-slot-id": slot.slotId,
4495
- "data-amplifyup-slot": slot.slotId,
4496
- "data-zone-id": slot.zoneId || void 0,
4497
- "data-parent-node-id": node.id,
4498
- "data-parent-component-id": node.componentId,
4499
- children: safeChildren.filter(Boolean).map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child) }, child.id))
4500
- },
4501
- slot.slotId
4502
- );
4503
- }
4504
- }
4505
- const slotsToPass = Object.keys(slotChildren).length > 0 ? slotChildren : void 0;
4506
- return /* @__PURE__ */ jsx(
4507
- "div",
4508
- {
4509
- "data-node-id": node.id,
4510
- "data-amplifyup-node": "",
4511
- "data-component-id": node.componentId,
4512
- "data-amplifyup-component": node.componentId,
4513
- children: /* @__PURE__ */ jsx(
4514
- ComponentRenderBoundary,
4515
- {
4516
- componentId: node.componentId,
4517
- nodeId: node.id,
4518
- children: /* @__PURE__ */ jsx(
4519
- NodeWithFields,
4520
- {
4521
- nodeId: node.id,
4522
- componentId: node.componentId,
4523
- props: nodeRenderProps(node),
4524
- slots: slotsToPass,
4525
- renderComponent,
4526
- composerPreview: composerPreviewFlag
4527
- }
4528
- )
4529
- }
4530
- )
4531
- }
4532
- );
4533
- };
4534
- const rawTree = pageConfig.layoutTree;
4535
- const layoutTree = Array.isArray(rawTree) ? rawTree : rawTree && typeof rawTree === "object" && rawTree.componentId ? [rawTree] : [];
4536
- if (layoutTree.length === 0) {
4537
- return /* @__PURE__ */ jsx("main", { children: emptyComponent || /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No content configured for this page." }) }) });
4538
- }
4539
- return /* @__PURE__ */ jsx(ComposerProvider, { children: /* @__PURE__ */ jsx(
4540
- "main",
4541
- {
4542
- "data-slot-id": composerPreviewFlag ? "main" : void 0,
4543
- "data-amplifyup-slot": composerPreviewFlag ? "main" : void 0,
4544
- "data-slot-source": composerPreviewFlag ? "page" : void 0,
4545
- "data-slot-state": composerPreviewFlag ? "filled" : void 0,
4546
- children: layoutTree.map((node) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(node) }, node.id))
4547
- }
4548
- ) });
4549
- }
4550
- var AmplifyRenderer = memo(AmplifyRendererInner);
4551
- function AddComponentIcon({ size = 28 }) {
4552
- return /* @__PURE__ */ jsxs(
4553
- "svg",
4554
- {
4555
- width: size,
4556
- height: size,
4557
- viewBox: "0 0 24 24",
4558
- fill: "none",
4559
- xmlns: "http://www.w3.org/2000/svg",
4560
- "aria-hidden": "true",
4561
- focusable: "false",
4562
- children: [
4563
- /* @__PURE__ */ jsx(
4564
- "rect",
4565
- {
4566
- x: "3",
4567
- y: "3",
4568
- width: "18",
4569
- height: "18",
4570
- rx: "4",
4571
- stroke: "currentColor",
4572
- strokeWidth: "1.75"
4573
- }
4574
- ),
4575
- /* @__PURE__ */ jsx(
4576
- "path",
4577
- {
4578
- d: "M12 8v8M8 12h8",
4579
- stroke: "currentColor",
4580
- strokeWidth: "1.75",
4581
- strokeLinecap: "round"
4582
- }
4583
- )
4584
- ]
4264
+ const next = hydratePageResourceProps(props || {}, getPageResourceFields());
4265
+ return /* @__PURE__ */ jsx(
4266
+ ComponentContext.Provider,
4267
+ {
4268
+ value: { slots, props: next, layoutNodeId, componentId },
4269
+ children
4585
4270
  }
4586
4271
  );
4587
4272
  }
4588
- function EmptySlotHint({
4589
- label = "Click here to Add Component",
4590
- compact = false
4273
+ function Slot({
4274
+ name,
4275
+ className
4591
4276
  }) {
4592
- const iconSize = compact ? 20 : 28;
4593
- return /* @__PURE__ */ jsxs(
4277
+ const context = useContext(ComponentContext);
4278
+ if (!context) {
4279
+ console.warn(
4280
+ `[AmplifyUp SDK] Slot "${name}" only works inside a component rendered from your component registry`
4281
+ );
4282
+ return null;
4283
+ }
4284
+ const slotContent = context.slots?.[name];
4285
+ const { ready: layoutReady } = useComposerLayoutStatus();
4286
+ const slotState = resolveSlotState({
4287
+ layoutReady,
4288
+ hasContent: !!slotContent
4289
+ });
4290
+ const isEmpty = slotState === "empty";
4291
+ const isEditorMode = typeof window !== "undefined" && // Check for preview mode flag set by proxy
4292
+ (window.__AMPLIFYUP_PREVIEW_MODE__ === true || // Check URL query params
4293
+ 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)
4294
+ window.self !== window.top && document.referrer && (() => {
4295
+ try {
4296
+ const referrerUrl = new URL(document.referrer);
4297
+ return referrerUrl.hostname === "localhost" && referrerUrl.port === "5001" || referrerUrl.pathname.includes("/dashboard/composer") || referrerUrl.pathname.includes("/dashboard/routes/");
4298
+ } catch {
4299
+ return false;
4300
+ }
4301
+ })());
4302
+ sdkDebugOnce(
4303
+ `slot:${context.layoutNodeId || ""}:${name}`,
4304
+ `[Slot:${name}]`,
4305
+ {
4306
+ slotState,
4307
+ isEmpty,
4308
+ isEditorMode,
4309
+ availableSlotKeys: context.slots ? Object.keys(context.slots) : []
4310
+ }
4311
+ );
4312
+ return /* @__PURE__ */ jsx(
4594
4313
  "div",
4595
4314
  {
4596
- className: "amplifyup-slot-placeholder",
4315
+ className,
4316
+ "data-slot-name": name,
4317
+ "data-slot-id": name,
4318
+ "data-amplifyup-slot": name,
4319
+ "data-slot-state": slotState,
4320
+ "data-slot-empty": isEmpty ? "true" : void 0,
4321
+ "data-parent-node-id": context.layoutNodeId || void 0,
4322
+ "data-parent-component-id": context.componentId || void 0,
4597
4323
  style: {
4598
- display: "flex",
4599
- flexDirection: "column",
4600
- alignItems: "center",
4601
- justifyContent: "center",
4602
- gap: compact ? 8 : 12,
4603
- minHeight: compact ? 60 : void 0,
4604
- width: "100%",
4605
- pointerEvents: "none",
4606
- textAlign: "center",
4607
- boxSizing: "border-box",
4608
- padding: compact ? "12px 8px" : "24px 16px"
4324
+ // Only add minimum height when empty (for drop target visibility)
4325
+ minHeight: isEmpty ? "60px" : void 0,
4326
+ // Only add position relative for overlay positioning in editor mode
4327
+ position: isEditorMode ? "relative" : void 0
4609
4328
  },
4610
- children: [
4611
- /* @__PURE__ */ jsx(
4612
- "span",
4329
+ children: slotState === "loading" ? null : isEmpty ? isEditorMode ? /* @__PURE__ */ jsx(EmptySlotHint, { compact: true }) : /* @__PURE__ */ jsx(
4330
+ "div",
4331
+ {
4332
+ className: "amplifyup-slot-placeholder",
4333
+ style: {
4334
+ minHeight: "60px",
4335
+ color: "#9ca3af",
4336
+ fontSize: "14px",
4337
+ pointerEvents: "none"
4338
+ },
4339
+ children: `Empty slot: ${name}`
4340
+ }
4341
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
4342
+ isEditorMode && /* @__PURE__ */ jsxs(
4343
+ "div",
4613
4344
  {
4614
- className: "amplifyup-slot-placeholder-icon",
4345
+ className: "amplifyup-slot-label",
4615
4346
  style: {
4616
- display: "inline-flex",
4617
- alignItems: "center",
4618
- justifyContent: "center",
4619
- width: compact ? 36 : 48,
4620
- height: compact ? 36 : 48,
4621
- borderRadius: 12,
4622
- border: "1.5px dashed currentColor",
4623
- opacity: 0.9
4347
+ position: "absolute",
4348
+ top: "4px",
4349
+ left: "4px",
4350
+ fontSize: "10px",
4351
+ color: "#9ca3af",
4352
+ pointerEvents: "none",
4353
+ zIndex: 1
4624
4354
  },
4625
- children: /* @__PURE__ */ jsx(AddComponentIcon, { size: iconSize })
4355
+ children: [
4356
+ "Slot: ",
4357
+ name
4358
+ ]
4626
4359
  }
4627
4360
  ),
4628
- /* @__PURE__ */ jsx(
4629
- "span",
4630
- {
4631
- className: "amplifyup-slot-placeholder-label",
4632
- style: {
4633
- fontSize: compact ? 13 : 15,
4634
- fontWeight: 600,
4635
- lineHeight: 1.3,
4636
- letterSpacing: "0.01em"
4637
- },
4638
- children: label
4639
- }
4640
- )
4641
- ]
4361
+ slotContent
4362
+ ] })
4363
+ }
4364
+ );
4365
+ }
4366
+ function isLikelyNonTextFieldName(name) {
4367
+ const n = name.toLowerCase();
4368
+ return n.includes("href") || n.includes("url") || n.includes("src") || n.includes("image") || n.includes("icon") || n.includes("overlay") || n.includes("color");
4369
+ }
4370
+ var COMPOSER_EMPTY_FIELD_HINT = "Click here to edit this field";
4371
+ var EMPTY_FIELD_PLACEHOLDERS = /* @__PURE__ */ new Set([
4372
+ COMPOSER_EMPTY_FIELD_HINT,
4373
+ "Click here to enter text",
4374
+ "Sample Title",
4375
+ "Sample Heading",
4376
+ "Sample Subheading"
4377
+ ]);
4378
+ function isComposerEmptyText(value) {
4379
+ if (value === void 0 || value === null) return true;
4380
+ if (typeof value !== "string") return false;
4381
+ const trimmed = value.trim();
4382
+ return trimmed === "" || EMPTY_FIELD_PLACEHOLDERS.has(trimmed);
4383
+ }
4384
+ function canInlineEdit(name, value) {
4385
+ if (!name || isLikelyNonTextFieldName(name)) return false;
4386
+ if (value !== void 0 && value !== null && typeof value === "object") {
4387
+ return false;
4388
+ }
4389
+ if (typeof value === "boolean") return false;
4390
+ return true;
4391
+ }
4392
+ function canInlineEditRichText(name, value) {
4393
+ if (!name || isLikelyNonTextFieldName(name)) return false;
4394
+ if (typeof value === "boolean") return false;
4395
+ if (value !== void 0 && value !== null && typeof value === "object" && !Array.isArray(value)) {
4396
+ return false;
4397
+ }
4398
+ return true;
4399
+ }
4400
+ function useComposerEditMode() {
4401
+ return useSyncExternalStore(
4402
+ subscribeComposerEdit,
4403
+ isComposerEditMode,
4404
+ getComposerEditModeServer
4405
+ );
4406
+ }
4407
+ function useBoundField(field, value, name, component) {
4408
+ const bound = resolveEditableBinding(field, value, name);
4409
+ if (bound.requiresName) {
4410
+ console.warn(
4411
+ `[AmplifyUp SDK] ${component} value={\u2026} requires name="\u2026" for computed values`
4412
+ );
4413
+ }
4414
+ const fieldValue = useComposerFieldValue(bound.name, bound.value, bound.ref);
4415
+ return { editName: bound.name, fieldValue, ref: bound.ref };
4416
+ }
4417
+ function commitComposerField(layoutNodeId, componentId, name, value, ref) {
4418
+ if (typeof window === "undefined" || window.parent === window) return;
4419
+ window.parent.postMessage(
4420
+ {
4421
+ type: "AMPLIFYUP_FIELD_COMMIT",
4422
+ layoutNodeId,
4423
+ componentId,
4424
+ fieldName: name,
4425
+ value,
4426
+ ...ref ? { ref } : {}
4427
+ },
4428
+ "*"
4429
+ );
4430
+ }
4431
+ function looksLikeRichCanvasField(name) {
4432
+ const tokens = String(name || "").replace(/([a-z])([A-Z])/g, "$1 $2").split(/[._\-\s/]+/).map((s) => s.toLowerCase()).filter(Boolean);
4433
+ const last = tokens[tokens.length - 1] || "";
4434
+ return last === "body" || last === "content" || last === "copy" || last === "excerpt" || last === "markdown" || last === "richtext";
4435
+ }
4436
+ var lastRichTextRequestAt = 0;
4437
+ function requestCanvasRichTextEdit(opts) {
4438
+ if (typeof window === "undefined" || window.parent === window) return;
4439
+ if (!opts.name) return;
4440
+ if (opts.el.classList.contains("au-rte-active") || opts.el.querySelector(".ProseMirror")) {
4441
+ return;
4442
+ }
4443
+ const now = Date.now();
4444
+ if (now - lastRichTextRequestAt < 400) return;
4445
+ lastRichTextRequestAt = now;
4446
+ const rect = opts.el.getBoundingClientRect();
4447
+ window.parent.postMessage(
4448
+ {
4449
+ type: "AMPLIFYUP_RICHTEXT_EDIT",
4450
+ layoutNodeId: opts.layoutNodeId,
4451
+ componentId: opts.componentId,
4452
+ fieldName: opts.name,
4453
+ value: opts.value,
4454
+ ...opts.fieldRef ? { ref: opts.fieldRef } : {},
4455
+ bounds: {
4456
+ top: rect.top,
4457
+ left: rect.left,
4458
+ width: Math.max(rect.width, 240),
4459
+ height: Math.max(rect.height, 64)
4460
+ }
4461
+ },
4462
+ "*"
4463
+ );
4464
+ }
4465
+ function placeCaret(el, atStart = false) {
4466
+ const range = document.createRange();
4467
+ range.selectNodeContents(el);
4468
+ range.collapse(atStart);
4469
+ const sel = window.getSelection();
4470
+ sel?.removeAllRanges();
4471
+ sel?.addRange(range);
4472
+ }
4473
+ function ComposerInlineField({
4474
+ name,
4475
+ value,
4476
+ className,
4477
+ layoutNodeId,
4478
+ componentId,
4479
+ fieldRef,
4480
+ rich = false
4481
+ }) {
4482
+ const [focused, setFocused] = useState(false);
4483
+ const elRef = useRef(null);
4484
+ const storedEmpty = isComposerEmptyText(value);
4485
+ const text = storedEmpty ? "" : formatFieldValue(value);
4486
+ const hint = COMPOSER_EMPTY_FIELD_HINT;
4487
+ const idleDisplay = storedEmpty ? hint : text;
4488
+ const showHint = storedEmpty && !focused;
4489
+ useEffect(() => {
4490
+ const el = elRef.current;
4491
+ if (!el) return;
4492
+ if (document.activeElement === el) return;
4493
+ if (el.querySelector(
4494
+ "input, button, select, textarea, form, svg, iframe, img"
4495
+ )) {
4496
+ return;
4642
4497
  }
4643
- );
4644
- }
4645
- var ComposerLayoutStatusContext = createContext({
4646
- ready: true
4647
- });
4648
- function ComposerLayoutStatusProvider({
4649
- ready,
4650
- children
4651
- }) {
4652
- return /* @__PURE__ */ jsx(ComposerLayoutStatusContext.Provider, { value: { ready }, children });
4653
- }
4654
- function useComposerLayoutStatus() {
4655
- return useContext(ComposerLayoutStatusContext);
4656
- }
4657
- function previewPageSlot(state) {
4498
+ const next = idleDisplay || "";
4499
+ if (el.textContent !== next) el.textContent = next;
4500
+ }, [idleDisplay]);
4501
+ const finishEdit = () => {
4502
+ const raw = (elRef.current?.innerText ?? "").replace(/\u00a0/g, " ").replace(/\n+$/g, "");
4503
+ const next = raw.trim() === "" ? "" : raw;
4504
+ setFocused(false);
4505
+ if (storedEmpty && next === "") {
4506
+ if (elRef.current) elRef.current.textContent = hint;
4507
+ if (typeof value === "string" && EMPTY_FIELD_PLACEHOLDERS.has(value.trim())) {
4508
+ commitComposerField(layoutNodeId, componentId, name, "", fieldRef);
4509
+ }
4510
+ return;
4511
+ }
4512
+ if (next === text) return;
4513
+ commitComposerField(layoutNodeId, componentId, name, next, fieldRef);
4514
+ };
4515
+ const openRichEditor = (event) => {
4516
+ if (!isComposerEditMode() || !elRef.current) return;
4517
+ event?.preventDefault();
4518
+ event?.stopPropagation();
4519
+ const source = typeof value === "string" && !storedEmpty ? value : text || "";
4520
+ requestCanvasRichTextEdit({
4521
+ el: elRef.current,
4522
+ layoutNodeId,
4523
+ componentId,
4524
+ name,
4525
+ value: source,
4526
+ fieldRef
4527
+ });
4528
+ };
4658
4529
  return /* @__PURE__ */ jsx(
4659
- "div",
4530
+ "span",
4660
4531
  {
4661
- "data-slot-id": "main",
4662
- "data-amplifyup-slot": "main",
4663
- "data-slot-source": "page",
4664
- "data-slot-state": state,
4665
- "data-slot-empty": state === "empty" ? "true" : void 0,
4666
- style: {
4667
- minHeight: state === "empty" ? "50vh" : void 0,
4668
- width: "100%",
4669
- display: "block",
4670
- position: "relative"
4532
+ ref: elRef,
4533
+ className,
4534
+ "data-amplifyup-field": name,
4535
+ "data-amplifyup-field-type": rich ? "richtext" : "text",
4536
+ "data-amplifyup-node-id": layoutNodeId || void 0,
4537
+ "data-amplifyup-ref-provider": fieldRef?.providerId || void 0,
4538
+ "data-amplifyup-ref-entity": fieldRef?.entity || void 0,
4539
+ "data-amplifyup-ref-id": fieldRef?.id || void 0,
4540
+ "data-amplifyup-empty-hint": showHint ? "true" : void 0,
4541
+ contentEditable: !rich,
4542
+ suppressContentEditableWarning: true,
4543
+ role: "textbox",
4544
+ tabIndex: 0,
4545
+ onMouseDown: (event) => {
4546
+ event.stopPropagation();
4671
4547
  },
4672
- children: state === "empty" ? /* @__PURE__ */ jsx(EmptySlotHint, {}) : null
4548
+ onClick: (event) => {
4549
+ if (!isComposerEditMode()) return;
4550
+ const link = event.target?.closest?.("a");
4551
+ if (link) event.preventDefault();
4552
+ if (rich) {
4553
+ openRichEditor(event);
4554
+ return;
4555
+ }
4556
+ },
4557
+ onFocus: () => {
4558
+ if (!isComposerEditMode()) return;
4559
+ if (rich) {
4560
+ openRichEditor();
4561
+ return;
4562
+ }
4563
+ setFocused(true);
4564
+ const el = elRef.current;
4565
+ if (el && storedEmpty) {
4566
+ el.textContent = "";
4567
+ placeCaret(el, true);
4568
+ }
4569
+ },
4570
+ onBlur: rich ? void 0 : finishEdit,
4571
+ onKeyDown: (event) => {
4572
+ event.stopPropagation();
4573
+ if (event.key === "Enter" && !event.shiftKey) {
4574
+ event.preventDefault();
4575
+ event.currentTarget.blur();
4576
+ }
4577
+ if (event.key === "Escape") {
4578
+ event.preventDefault();
4579
+ if (elRef.current) elRef.current.textContent = idleDisplay || "";
4580
+ setFocused(false);
4581
+ event.currentTarget.blur();
4582
+ }
4583
+ },
4584
+ style: {
4585
+ cursor: "text",
4586
+ outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
4587
+ outlineOffset: "6px",
4588
+ minWidth: "1ch",
4589
+ minHeight: "1em",
4590
+ // Do not force `inline` — that overrides Tailwind `block` and parks
4591
+ // an empty-field hint on the same line as a sibling that has a value.
4592
+ opacity: showHint ? 0.5 : void 0
4593
+ }
4673
4594
  }
4674
4595
  );
4675
4596
  }
4676
- function hasAmplifyLayout(pageConfig) {
4677
- return !!(pageConfig && pageConfig.id !== "default" && Array.isArray(pageConfig.layoutTree) && pageConfig.layoutTree.length > 0);
4678
- }
4679
- function normalizeRoutePath(route) {
4680
- if (!route) return "";
4681
- const path = route.split("?")[0] || "/";
4682
- if (path.length > 1 && path.endsWith("/")) return path.replace(/\/+$/, "");
4683
- return path || "/";
4684
- }
4685
- function samePageRoute(a, b) {
4686
- const ar = normalizeRoutePath(a?.route);
4687
- const br = normalizeRoutePath(b?.route);
4688
- return !!ar && !!br && ar === br;
4689
- }
4690
- function layoutTreesEqual(a, b) {
4691
- if (!a || !b) return a === b;
4692
- return JSON.stringify(a.layoutTree) === JSON.stringify(b.layoutTree);
4693
- }
4694
- function AmplifyPageContent({
4695
- fallback,
4696
- renderComponent,
4697
- pageConfig: pageConfigProp,
4698
- forceComposerPreview = false,
4699
- previewLoadingComponent,
4700
- previewEmptyComponent,
4701
- loadingComponent,
4702
- emptyComponent,
4703
- errorComponent
4597
+ function ComposerInlineHtml({
4598
+ name,
4599
+ html,
4600
+ sourceValue,
4601
+ className,
4602
+ Tag = "div",
4603
+ layoutNodeId,
4604
+ componentId,
4605
+ fieldRef
4704
4606
  }) {
4705
- const client = useAmplifyUp();
4706
- const preview = useComposerPreviewFlag(forceComposerPreview);
4707
- const clientLayout = hasAmplifyLayout(client.pageConfig) ? client.pageConfig : null;
4708
- const serverLayout = hasAmplifyLayout(pageConfigProp) ? pageConfigProp : null;
4709
- let pageConfig = serverLayout ?? clientLayout;
4710
- if (preview && clientLayout) {
4711
- pageConfig = clientLayout;
4712
- } else if (clientLayout && serverLayout && samePageRoute(clientLayout, serverLayout) && !layoutTreesEqual(clientLayout, serverLayout)) {
4713
- pageConfig = clientLayout;
4714
- } else if (clientLayout && !serverLayout) {
4715
- pageConfig = clientLayout;
4716
- }
4717
- const loading = !hasAmplifyLayout(pageConfig) && client.loading;
4718
- const error = hasAmplifyLayout(pageConfig) ? null : pageConfigProp ? null : client.error;
4719
- const previewEmpty = previewEmptyComponent ?? previewPageSlot("empty");
4720
- const previewLoading = previewLoadingComponent ?? previewPageSlot("loading");
4721
- if (preview) {
4722
- const ready = previewLayoutReady({
4723
- clientLoading: client.loading,
4724
- hasClientLayout: !!clientLayout,
4725
- hasServerLayout: !!serverLayout
4607
+ const elRef = useRef(null);
4608
+ useEffect(() => {
4609
+ const el = elRef.current;
4610
+ if (!el) return;
4611
+ if (el.innerHTML !== html) el.innerHTML = html;
4612
+ }, [html]);
4613
+ const openRichEditor = (event) => {
4614
+ if (!elRef.current) return;
4615
+ event.preventDefault();
4616
+ event.stopPropagation();
4617
+ requestCanvasRichTextEdit({
4618
+ el: elRef.current,
4619
+ layoutNodeId,
4620
+ componentId,
4621
+ name,
4622
+ value: sourceValue,
4623
+ fieldRef
4726
4624
  });
4727
- if (!ready) {
4728
- return /* @__PURE__ */ jsx(ComposerLayoutStatusProvider, { ready: false, children: previewLoading });
4625
+ };
4626
+ return React3.createElement(Tag, {
4627
+ ref: elRef,
4628
+ className,
4629
+ "data-amplifyup-field": name,
4630
+ "data-amplifyup-field-type": "richtext",
4631
+ "data-amplifyup-source": sourceValue,
4632
+ "data-amplifyup-node-id": layoutNodeId || void 0,
4633
+ "data-amplifyup-ref-provider": fieldRef?.providerId || void 0,
4634
+ "data-amplifyup-ref-entity": fieldRef?.entity || void 0,
4635
+ "data-amplifyup-ref-id": fieldRef?.id || void 0,
4636
+ dangerouslySetInnerHTML: { __html: html },
4637
+ onMouseDown: (event) => {
4638
+ event.stopPropagation();
4639
+ },
4640
+ onClick: openRichEditor,
4641
+ style: {
4642
+ cursor: "text",
4643
+ outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
4644
+ outlineOffset: "6px"
4729
4645
  }
4730
- const layoutSettled = !client.loading || !!clientLayout;
4731
- return /* @__PURE__ */ jsx(ComposerLayoutStatusProvider, { ready: layoutSettled, children: /* @__PURE__ */ jsx(
4732
- AmplifyRenderer,
4733
- {
4734
- pageConfig: pageConfig ?? void 0,
4735
- renderComponent,
4736
- composerPreview: true,
4737
- loadingComponent: loadingComponent ?? previewLoading,
4738
- emptyComponent: emptyComponent ?? previewEmpty,
4739
- errorComponent: errorComponent ?? (() => previewEmpty)
4646
+ });
4647
+ }
4648
+ function formatFieldValue(value) {
4649
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4650
+ return String(value);
4651
+ }
4652
+ if (Array.isArray(value)) {
4653
+ const parts = value.map((item) => {
4654
+ if (typeof item === "string") return item;
4655
+ if (item && typeof item === "object" && "children" in item && Array.isArray(item.children)) {
4656
+ return item.children.map((c) => typeof c?.text === "string" ? c.text : "").join("");
4740
4657
  }
4741
- ) });
4658
+ return "";
4659
+ }).filter(Boolean);
4660
+ if (parts.length > 0) return parts.join(" ");
4742
4661
  }
4743
- if (loading) {
4744
- return loadingComponent ? /* @__PURE__ */ jsx(Fragment, { children: loadingComponent }) : null;
4662
+ if (value && typeof value === "object") {
4663
+ return "";
4745
4664
  }
4746
- if (error) {
4747
- if (errorComponent) return /* @__PURE__ */ jsx(Fragment, { children: errorComponent(error) });
4748
- return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
4665
+ return "";
4666
+ }
4667
+ var FIELD_SCALAR_HINT = "Field expects a scalar field. Did you mean <Field field={post.title} />?";
4668
+ function warnNotScalarField(component, field, fieldValue) {
4669
+ if (process.env.NODE_ENV === "production") return false;
4670
+ if (isBrandedFields(field)) {
4671
+ console.error(`[AmplifyUp SDK] ${component}: ${FIELD_SCALAR_HINT}`);
4672
+ return true;
4749
4673
  }
4750
- if (!hasAmplifyLayout(pageConfig)) {
4751
- return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
4674
+ if (isFieldEnvelope(field) && Array.isArray(field.value)) {
4675
+ console.error(`[AmplifyUp SDK] ${component}: ${FIELD_SCALAR_HINT}`);
4676
+ return true;
4752
4677
  }
4753
- return /* @__PURE__ */ jsx(ComposerLayoutStatusProvider, { ready: true, children: /* @__PURE__ */ jsx(
4754
- AmplifyRenderer,
4755
- {
4756
- pageConfig: pageConfig ?? void 0,
4757
- renderComponent,
4758
- loadingComponent,
4759
- emptyComponent,
4760
- errorComponent
4761
- }
4762
- ) });
4678
+ if (Array.isArray(fieldValue)) {
4679
+ console.error(`[AmplifyUp SDK] ${component}: ${FIELD_SCALAR_HINT}`);
4680
+ return true;
4681
+ }
4682
+ return false;
4763
4683
  }
4764
-
4765
- // src/slot-state.ts
4766
- function resolveSlotState(opts) {
4767
- if (!opts.layoutReady) return "loading";
4768
- return opts.hasContent ? "filled" : "empty";
4684
+ function assertNamedEnvelope(component, field) {
4685
+ if (process.env.NODE_ENV === "production") return;
4686
+ if (!field || typeof field !== "object" || Array.isArray(field)) return;
4687
+ const rec = field;
4688
+ if (!("value" in rec)) return;
4689
+ if (typeof rec.name === "string" && rec.name) return;
4690
+ throw new Error(
4691
+ `[AmplifyUp SDK] ${component} received an envelope-shaped object with a \`value\` but no \`name\` \u2014 a partial envelope was manufactured upstream (keys: ${Object.keys(
4692
+ rec
4693
+ ).join(", ")}). Find the producer; do not strip or rebuild envelopes.`
4694
+ );
4769
4695
  }
4770
- function slotShowsAddComponent(state) {
4771
- return state === "empty";
4696
+ var warnedReadOnlyFields = /* @__PURE__ */ new Set();
4697
+ function warnReadOnlyRowField(name) {
4698
+ if (process.env.NODE_ENV === "production") return;
4699
+ const key = name || "(unnamed)";
4700
+ if (warnedReadOnlyFields.has(key)) return;
4701
+ warnedReadOnlyFields.add(key);
4702
+ console.warn(
4703
+ `[AmplifyUp SDK] ${key} has no write target; rendered read-only.`
4704
+ );
4772
4705
  }
4773
- function slotStateFromEl(el) {
4774
- if (!el) return null;
4775
- const explicit = el.getAttribute("data-slot-state");
4776
- if (explicit === "loading" || explicit === "empty" || explicit === "filled") {
4777
- return explicit;
4706
+ function canComposerBind(field, ref, editName) {
4707
+ if (isReadOnlyRowField(field) || isFieldEnvelope(field) && field.readOnly) {
4708
+ warnReadOnlyRowField(editName || (isFieldEnvelope(field) ? field.name : ""));
4709
+ return false;
4778
4710
  }
4779
- if (el.getAttribute("data-slot-empty") === "true") return "empty";
4780
- return null;
4711
+ return true;
4781
4712
  }
4782
-
4783
- // src/composer-edit-mode.ts
4784
- function isComposerEditMode() {
4785
- if (typeof window === "undefined") return false;
4786
- return isComposerPreview() && window.__AMPLIFYUP_EVENT_SELECTION__ !== true;
4713
+ function warnProjectionProps(component, hasField, hasValue, name) {
4714
+ if (process.env.NODE_ENV === "production") return;
4715
+ if (!hasField && !hasValue && !name) {
4716
+ console.warn(
4717
+ `[AmplifyUp SDK] ${component} needs field={fields.yourProp} (or value + name for computed values)`
4718
+ );
4719
+ }
4787
4720
  }
4788
- function subscribeComposerEdit(onStoreChange) {
4789
- if (typeof window === "undefined") return () => {
4790
- };
4791
- window.addEventListener("amplifyup:composer-edit", onStoreChange);
4792
- return () => window.removeEventListener("amplifyup:composer-edit", onStoreChange);
4721
+ function hasOwnProp(props, key) {
4722
+ return Object.prototype.hasOwnProperty.call(props, key);
4793
4723
  }
4794
- function getComposerEditModeServer() {
4795
- return false;
4724
+ function Field(props) {
4725
+ const { field, value, name, className } = props;
4726
+ const context = useContext(ComponentContext);
4727
+ const composerEdit = useComposerEditMode();
4728
+ const hasField = isFieldEnvelope(field);
4729
+ const hasValue = hasOwnProp(props, "value");
4730
+ const { editName, fieldValue, ref } = useBoundField(field, value, name, "Field");
4731
+ assertNamedEnvelope("Field", field);
4732
+ warnProjectionProps("Field", hasField, hasValue, name);
4733
+ if (warnNotScalarField("Field", field, fieldValue)) {
4734
+ return null;
4735
+ }
4736
+ if (!hasField && !hasValue && !name) {
4737
+ return null;
4738
+ }
4739
+ if (isBrandedFields(field) || Array.isArray(fieldValue)) {
4740
+ return null;
4741
+ }
4742
+ const empty = isComposerEmptyText(fieldValue);
4743
+ const bindComposer = composerEdit && editName && context && canInlineEdit(editName, fieldValue) && canComposerBind(field, ref, editName);
4744
+ if (bindComposer) {
4745
+ return /* @__PURE__ */ jsx(
4746
+ ComposerInlineField,
4747
+ {
4748
+ name: editName,
4749
+ value: fieldValue,
4750
+ className,
4751
+ layoutNodeId: context.layoutNodeId,
4752
+ componentId: context.componentId,
4753
+ fieldRef: ref,
4754
+ rich: looksLikeRichCanvasField(editName)
4755
+ }
4756
+ );
4757
+ }
4758
+ if (empty) return null;
4759
+ const text = formatFieldValue(fieldValue);
4760
+ if (!text) return null;
4761
+ return /* @__PURE__ */ jsx("span", { className, children: text });
4796
4762
  }
4797
- var ComponentContext = createContext(null);
4798
- function ComponentContextProvider({
4799
- children,
4800
- slots,
4801
- props,
4802
- layoutNodeId,
4803
- componentId
4804
- }) {
4805
- const next = hydratePageResourceProps(props || {}, getPageResourceFields());
4806
- return /* @__PURE__ */ jsx(
4807
- ComponentContext.Provider,
4808
- {
4809
- value: { slots, props: next, layoutNodeId, componentId },
4810
- children
4811
- }
4812
- );
4763
+ function markdownToHtml(markdown) {
4764
+ try {
4765
+ const result = marked.parse(markdown, { async: false });
4766
+ return typeof result === "string" ? result : "";
4767
+ } catch {
4768
+ return markdown;
4769
+ }
4813
4770
  }
4814
- function Slot({
4815
- name,
4816
- className
4817
- }) {
4771
+ function RichText(props) {
4772
+ const {
4773
+ field,
4774
+ value,
4775
+ name,
4776
+ className,
4777
+ as: Tag = "div",
4778
+ children
4779
+ } = props;
4818
4780
  const context = useContext(ComponentContext);
4819
- if (!context) {
4820
- console.warn(`[AmplifyUp SDK] Slot "${name}" used outside ComponentContextProvider`);
4781
+ const composerEdit = useComposerEditMode();
4782
+ const childrenValue = typeof children === "string" || typeof children === "number" ? String(children) : void 0;
4783
+ const hasField = isFieldEnvelope(field);
4784
+ const hasValueProp = hasOwnProp(props, "value") || childrenValue !== void 0;
4785
+ const boundValue = hasOwnProp(props, "value") ? value : childrenValue !== void 0 ? childrenValue : value;
4786
+ const { editName, fieldValue, ref } = useBoundField(
4787
+ field,
4788
+ boundValue,
4789
+ name,
4790
+ "RichText"
4791
+ );
4792
+ assertNamedEnvelope("RichText", field);
4793
+ if (!hasField && !hasValueProp && !name) {
4821
4794
  return null;
4822
4795
  }
4823
- const slotContent = context.slots?.[name];
4824
- const { ready: layoutReady } = useComposerLayoutStatus();
4825
- const slotState = resolveSlotState({
4826
- layoutReady,
4827
- hasContent: !!slotContent
4828
- });
4829
- const isEmpty = slotState === "empty";
4830
- const isEditorMode = typeof window !== "undefined" && // Check for preview mode flag set by proxy
4831
- (window.__AMPLIFYUP_PREVIEW_MODE__ === true || // Check URL query params
4832
- 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)
4833
- window.self !== window.top && document.referrer && (() => {
4834
- try {
4835
- const referrerUrl = new URL(document.referrer);
4836
- return referrerUrl.hostname === "localhost" && referrerUrl.port === "5001" || referrerUrl.pathname.includes("/dashboard/composer") || referrerUrl.pathname.includes("/dashboard/routes/");
4837
- } catch {
4838
- return false;
4839
- }
4840
- })());
4841
- sdkDebugOnce(
4842
- `slot:${context.layoutNodeId || ""}:${name}`,
4843
- `[Slot:${name}]`,
4844
- {
4845
- slotState,
4846
- isEmpty,
4847
- isEditorMode,
4848
- availableSlotKeys: context.slots ? Object.keys(context.slots) : []
4849
- }
4850
- );
4851
- return /* @__PURE__ */ jsx(
4852
- "div",
4853
- {
4854
- className,
4855
- "data-slot-name": name,
4856
- "data-slot-id": name,
4857
- "data-amplifyup-slot": name,
4858
- "data-slot-state": slotState,
4859
- "data-slot-empty": isEmpty ? "true" : void 0,
4860
- "data-parent-node-id": context.layoutNodeId || void 0,
4861
- "data-parent-component-id": context.componentId || void 0,
4862
- style: {
4863
- // Only add minimum height when empty (for drop target visibility)
4864
- minHeight: isEmpty ? "60px" : void 0,
4865
- // Only add position relative for overlay positioning in editor mode
4866
- position: isEditorMode ? "relative" : void 0
4867
- },
4868
- children: slotState === "loading" ? null : isEmpty ? isEditorMode ? /* @__PURE__ */ jsx(EmptySlotHint, { compact: true }) : /* @__PURE__ */ jsx(
4869
- "div",
4796
+ warnProjectionProps("RichText", hasField, hasValueProp, name);
4797
+ if (warnNotScalarField("RichText", field, fieldValue)) {
4798
+ return null;
4799
+ }
4800
+ const canEdit = !!context && canInlineEditRichText(editName, fieldValue) && canComposerBind(field, ref, editName);
4801
+ if (isComposerEmptyText(fieldValue)) {
4802
+ if (composerEdit && canEdit) {
4803
+ return /* @__PURE__ */ jsx(
4804
+ ComposerInlineField,
4870
4805
  {
4871
- className: "amplifyup-slot-placeholder",
4872
- style: {
4873
- minHeight: "60px",
4874
- color: "#9ca3af",
4875
- fontSize: "14px",
4876
- pointerEvents: "none"
4877
- },
4878
- children: `Empty slot: ${name}`
4806
+ name: editName,
4807
+ value: fieldValue,
4808
+ className,
4809
+ layoutNodeId: context?.layoutNodeId,
4810
+ componentId: context?.componentId,
4811
+ fieldRef: ref,
4812
+ rich: true
4879
4813
  }
4880
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
4881
- isEditorMode && /* @__PURE__ */ jsxs(
4882
- "div",
4883
- {
4884
- className: "amplifyup-slot-label",
4885
- style: {
4886
- position: "absolute",
4887
- top: "4px",
4888
- left: "4px",
4889
- fontSize: "10px",
4890
- color: "#9ca3af",
4891
- pointerEvents: "none",
4892
- zIndex: 1
4893
- },
4894
- children: [
4895
- "Slot: ",
4896
- name
4897
- ]
4898
- }
4899
- ),
4900
- slotContent
4901
- ] })
4814
+ );
4902
4815
  }
4903
- );
4904
- }
4905
- function isLikelyNonTextFieldName(name) {
4906
- const n = name.toLowerCase();
4907
- return n.includes("href") || n.includes("url") || n.includes("src") || n.includes("image") || n.includes("icon") || n.includes("overlay") || n.includes("color");
4908
- }
4909
- var COMPOSER_EMPTY_FIELD_HINT = "Click here to edit this field";
4910
- var EMPTY_FIELD_PLACEHOLDERS = /* @__PURE__ */ new Set([
4911
- COMPOSER_EMPTY_FIELD_HINT,
4912
- "Click here to enter text",
4913
- "Sample Title",
4914
- "Sample Heading",
4915
- "Sample Subheading"
4916
- ]);
4917
- function isComposerEmptyText(value) {
4918
- if (value === void 0 || value === null) return true;
4919
- if (typeof value !== "string") return false;
4920
- const trimmed = value.trim();
4921
- return trimmed === "" || EMPTY_FIELD_PLACEHOLDERS.has(trimmed);
4922
- }
4923
- function canInlineEdit(name, value) {
4924
- if (!name || isLikelyNonTextFieldName(name)) return false;
4925
- if (value !== void 0 && value !== null && typeof value === "object") {
4926
- return false;
4816
+ return null;
4927
4817
  }
4928
- if (typeof value === "boolean") return false;
4929
- return true;
4930
- }
4931
- function canInlineEditRichText(name, value) {
4932
- if (!name || isLikelyNonTextFieldName(name)) return false;
4933
- if (typeof value === "boolean") return false;
4934
- if (value !== void 0 && value !== null && typeof value === "object" && !Array.isArray(value)) {
4935
- return false;
4818
+ let html = "";
4819
+ if (typeof fieldValue === "string") {
4820
+ const trimmed = fieldValue.trim();
4821
+ if (trimmed.startsWith("<") && trimmed.includes(">")) {
4822
+ html = fieldValue;
4823
+ } else {
4824
+ html = markdownToHtml(fieldValue);
4825
+ }
4826
+ } else {
4827
+ const text = formatFieldValue(fieldValue);
4828
+ html = text ? markdownToHtml(text) : "";
4936
4829
  }
4937
- return true;
4938
- }
4939
- function useComposerEditMode() {
4940
- return useSyncExternalStore(
4941
- subscribeComposerEdit,
4942
- isComposerEditMode,
4943
- getComposerEditModeServer
4944
- );
4945
- }
4946
- function useBoundField(field, value, name, component) {
4947
- const bound = resolveEditableBinding(field, value, name);
4948
- if (bound.requiresName) {
4949
- console.warn(
4950
- `[AmplifyUp SDK] ${component} value={\u2026} requires name="\u2026" for computed values`
4830
+ if (!html) {
4831
+ return null;
4832
+ }
4833
+ if (composerEdit && canEdit) {
4834
+ return /* @__PURE__ */ jsx(
4835
+ ComposerInlineHtml,
4836
+ {
4837
+ name: editName,
4838
+ html,
4839
+ sourceValue: typeof fieldValue === "string" ? fieldValue : html,
4840
+ className,
4841
+ Tag,
4842
+ layoutNodeId: context?.layoutNodeId,
4843
+ componentId: context?.componentId,
4844
+ fieldRef: ref
4845
+ }
4951
4846
  );
4952
4847
  }
4953
- const fieldValue = useComposerFieldValue(bound.name, bound.value, bound.ref);
4954
- return { editName: bound.name, fieldValue, ref: bound.ref };
4848
+ return /* @__PURE__ */ jsx(Tag, { className, dangerouslySetInnerHTML: { __html: html } });
4955
4849
  }
4956
- function commitComposerField(layoutNodeId, componentId, name, value, ref) {
4957
- if (typeof window === "undefined" || window.parent === window) return;
4958
- window.parent.postMessage(
4959
- {
4960
- type: "AMPLIFYUP_FIELD_COMMIT",
4961
- layoutNodeId,
4962
- componentId,
4963
- fieldName: name,
4964
- value,
4965
- ...ref ? { ref } : {}
4966
- },
4967
- "*"
4968
- );
4850
+ function isPortableImageValue(value) {
4851
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
4852
+ const url = value.url;
4853
+ return typeof url === "string" && url.trim().length > 0;
4969
4854
  }
4970
- function looksLikeRichCanvasField(name) {
4971
- const tokens = String(name || "").replace(/([a-z])([A-Z])/g, "$1 $2").split(/[._\-\s/]+/).map((s) => s.toLowerCase()).filter(Boolean);
4972
- const last = tokens[tokens.length - 1] || "";
4973
- return last === "body" || last === "content" || last === "copy" || last === "excerpt" || last === "markdown" || last === "richtext";
4855
+ function imageSrc(value) {
4856
+ if (typeof value === "string") return value.trim();
4857
+ if (isPortableImageValue(value)) return value.url.trim();
4858
+ return "";
4974
4859
  }
4975
- var lastRichTextRequestAt = 0;
4976
- function requestCanvasRichTextEdit(opts) {
4977
- if (typeof window === "undefined" || window.parent === window) return;
4978
- if (!opts.name) return;
4979
- if (opts.el.classList.contains("au-rte-active") || opts.el.querySelector(".ProseMirror")) {
4980
- return;
4860
+ function leafText(field) {
4861
+ if (isFieldEnvelope(field)) return field.value;
4862
+ return field;
4863
+ }
4864
+ function coerceImageField(field, value, name) {
4865
+ if (isFieldEnvelope(field)) return { field, value, name };
4866
+ if (!isBrandedFields(field) || !field || typeof field !== "object") {
4867
+ return { field: void 0, value, name };
4981
4868
  }
4982
- const now = Date.now();
4983
- if (now - lastRichTextRequestAt < 400) return;
4984
- lastRichTextRequestAt = now;
4985
- const rect = opts.el.getBoundingClientRect();
4986
- window.parent.postMessage(
4987
- {
4988
- type: "AMPLIFYUP_RICHTEXT_EDIT",
4989
- layoutNodeId: opts.layoutNodeId,
4990
- componentId: opts.componentId,
4991
- fieldName: opts.name,
4992
- value: opts.value,
4993
- ...opts.fieldRef ? { ref: opts.fieldRef } : {},
4994
- bounds: {
4995
- top: rect.top,
4996
- left: rect.left,
4997
- width: Math.max(rect.width, 240),
4998
- height: Math.max(rect.height, 64)
4999
- }
4869
+ const bag = field;
4870
+ const urlField = bag.url;
4871
+ const urlName = isFieldEnvelope(urlField) && typeof urlField.name === "string" ? urlField.name.replace(/\.url$/, "") : "";
4872
+ const editName = urlName || name || "";
4873
+ if (!editName) return { field: void 0, value, name };
4874
+ const ref = isFieldEnvelope(urlField) ? urlField.ref : void 0;
4875
+ const width = leafText(bag.width);
4876
+ const height = leafText(bag.height);
4877
+ return {
4878
+ field: {
4879
+ value: {
4880
+ url: String(leafText(urlField) ?? ""),
4881
+ alt: leafText(bag.alt),
4882
+ ...typeof width === "number" ? { width } : {},
4883
+ ...typeof height === "number" ? { height } : {}
4884
+ },
4885
+ name: editName,
4886
+ ...ref ? { ref } : {}
5000
4887
  },
5001
- "*"
5002
- );
5003
- }
5004
- function placeCaret(el, atStart = false) {
5005
- const range = document.createRange();
5006
- range.selectNodeContents(el);
5007
- range.collapse(atStart);
5008
- const sel = window.getSelection();
5009
- sel?.removeAllRanges();
5010
- sel?.addRange(range);
4888
+ value,
4889
+ name: editName
4890
+ };
5011
4891
  }
5012
- function ComposerInlineField({
5013
- name,
5014
- value,
5015
- className,
5016
- layoutNodeId,
5017
- componentId,
5018
- fieldRef,
5019
- rich = false
5020
- }) {
5021
- const [focused, setFocused] = useState(false);
5022
- const elRef = useRef(null);
5023
- const storedEmpty = isComposerEmptyText(value);
5024
- const text = storedEmpty ? "" : formatFieldValue(value);
5025
- const hint = COMPOSER_EMPTY_FIELD_HINT;
5026
- const idleDisplay = storedEmpty ? hint : text;
5027
- const showHint = storedEmpty && !focused;
5028
- useEffect(() => {
5029
- const el = elRef.current;
5030
- if (!el) return;
5031
- if (document.activeElement === el) return;
5032
- if (el.querySelector(
5033
- "input, button, select, textarea, form, svg, iframe, img"
5034
- )) {
5035
- return;
5036
- }
5037
- const next = idleDisplay || "";
5038
- if (el.textContent !== next) el.textContent = next;
5039
- }, [idleDisplay]);
5040
- const finishEdit = () => {
5041
- const raw = (elRef.current?.innerText ?? "").replace(/\u00a0/g, " ").replace(/\n+$/g, "");
5042
- const next = raw.trim() === "" ? "" : raw;
5043
- setFocused(false);
5044
- if (storedEmpty && next === "") {
5045
- if (elRef.current) elRef.current.textContent = hint;
5046
- if (typeof value === "string" && EMPTY_FIELD_PLACEHOLDERS.has(value.trim())) {
5047
- commitComposerField(layoutNodeId, componentId, name, "", fieldRef);
4892
+ function Image(props) {
4893
+ const {
4894
+ field,
4895
+ value,
4896
+ name,
4897
+ alt: altProp,
4898
+ className,
4899
+ width: widthProp,
4900
+ height: heightProp,
4901
+ ...imgProps
4902
+ } = props;
4903
+ const context = useContext(ComponentContext);
4904
+ assertNamedEnvelope("Image", field);
4905
+ const coerced = coerceImageField(field, value, name);
4906
+ const boundField = coerced.field;
4907
+ const hasField = isFieldEnvelope(boundField);
4908
+ const hasValue = hasOwnProp(props, "value");
4909
+ const { editName, fieldValue, ref } = useBoundField(
4910
+ boundField,
4911
+ coerced.value,
4912
+ coerced.name,
4913
+ "Image"
4914
+ );
4915
+ const composerEdit = useComposerEditMode();
4916
+ warnProjectionProps("Image", hasField || isBrandedFields(field), hasValue, coerced.name || name);
4917
+ if (!isBrandedFields(field) && warnNotScalarField("Image", boundField, fieldValue)) {
4918
+ return null;
4919
+ }
4920
+ if (!hasField && !hasValue && !name && !editName) {
4921
+ return null;
4922
+ }
4923
+ const src = imageSrc(fieldValue);
4924
+ const portable = isPortableImageValue(fieldValue) ? fieldValue : null;
4925
+ const alt = altProp ?? portable?.alt ?? "";
4926
+ const width = widthProp ?? portable?.width;
4927
+ const height = heightProp ?? portable?.height;
4928
+ const fieldAttrs = composerEdit && editName && canComposerBind(boundField, ref, editName) ? {
4929
+ "data-amplifyup-field": editName,
4930
+ "data-amplifyup-field-type": "image",
4931
+ "data-amplifyup-node-id": context?.layoutNodeId || void 0,
4932
+ "data-amplifyup-ref-provider": ref?.providerId || void 0,
4933
+ "data-amplifyup-ref-entity": ref?.entity || void 0,
4934
+ "data-amplifyup-ref-id": ref?.id || void 0
4935
+ } : null;
4936
+ if (composerEdit && !editName && (hasField || hasValue)) {
4937
+ console.warn(
4938
+ '[AmplifyUp SDK] Image value={\u2026} requires name="\u2026" for computed values'
4939
+ );
4940
+ }
4941
+ if (!src) {
4942
+ if (!composerEdit || !fieldAttrs) return null;
4943
+ return /* @__PURE__ */ jsx(
4944
+ "span",
4945
+ {
4946
+ ...fieldAttrs,
4947
+ className,
4948
+ role: "img",
4949
+ "aria-label": alt || editName || "Image",
4950
+ style: {
4951
+ display: "block",
4952
+ boxSizing: "border-box",
4953
+ minWidth: width ? void 0 : 120,
4954
+ minHeight: height ? void 0 : 80,
4955
+ width: width || "100%",
4956
+ height: height || void 0,
4957
+ background: "color-mix(in srgb, currentColor 8%, transparent)",
4958
+ cursor: "pointer"
4959
+ }
5048
4960
  }
5049
- return;
5050
- }
5051
- if (next === text) return;
5052
- commitComposerField(layoutNodeId, componentId, name, next, fieldRef);
5053
- };
5054
- const openRichEditor = (event) => {
5055
- if (!isComposerEditMode() || !elRef.current) return;
5056
- event?.preventDefault();
5057
- event?.stopPropagation();
5058
- const source = typeof value === "string" && !storedEmpty ? value : text || "";
5059
- requestCanvasRichTextEdit({
5060
- el: elRef.current,
5061
- layoutNodeId,
5062
- componentId,
5063
- name,
5064
- value: source,
5065
- fieldRef
5066
- });
5067
- };
4961
+ );
4962
+ }
5068
4963
  return /* @__PURE__ */ jsx(
5069
- "span",
4964
+ "img",
5070
4965
  {
5071
- ref: elRef,
4966
+ ...imgProps,
4967
+ ...fieldAttrs,
4968
+ src,
4969
+ alt,
5072
4970
  className,
5073
- "data-amplifyup-field": name,
5074
- "data-amplifyup-field-type": rich ? "richtext" : "text",
5075
- "data-amplifyup-node-id": layoutNodeId || void 0,
5076
- "data-amplifyup-ref-provider": fieldRef?.providerId || void 0,
5077
- "data-amplifyup-ref-entity": fieldRef?.entity || void 0,
5078
- "data-amplifyup-ref-id": fieldRef?.id || void 0,
5079
- "data-amplifyup-empty-hint": showHint ? "true" : void 0,
5080
- contentEditable: !rich,
5081
- suppressContentEditableWarning: true,
5082
- role: "textbox",
5083
- tabIndex: 0,
5084
- onMouseDown: (event) => {
5085
- event.stopPropagation();
5086
- },
5087
- onClick: (event) => {
5088
- if (!isComposerEditMode()) return;
5089
- const link = event.target?.closest?.("a");
5090
- if (link) event.preventDefault();
5091
- if (rich) {
5092
- openRichEditor(event);
5093
- return;
5094
- }
5095
- },
5096
- onFocus: () => {
5097
- if (!isComposerEditMode()) return;
5098
- if (rich) {
5099
- openRichEditor();
5100
- return;
5101
- }
5102
- setFocused(true);
5103
- const el = elRef.current;
5104
- if (el && storedEmpty) {
5105
- el.textContent = "";
5106
- placeCaret(el, true);
5107
- }
5108
- },
5109
- onBlur: rich ? void 0 : finishEdit,
5110
- onKeyDown: (event) => {
5111
- event.stopPropagation();
5112
- if (event.key === "Enter" && !event.shiftKey) {
5113
- event.preventDefault();
5114
- event.currentTarget.blur();
5115
- }
5116
- if (event.key === "Escape") {
5117
- event.preventDefault();
5118
- if (elRef.current) elRef.current.textContent = idleDisplay || "";
5119
- setFocused(false);
5120
- event.currentTarget.blur();
5121
- }
5122
- },
4971
+ width,
4972
+ height,
5123
4973
  style: {
5124
- cursor: "text",
5125
- outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
5126
- outlineOffset: "6px",
5127
- minWidth: "1ch",
5128
- minHeight: "1em",
5129
- // Do not force `inline` — that overrides Tailwind `block` and parks
5130
- // an empty-field hint on the same line as a sibling that has a value.
5131
- opacity: showHint ? 0.5 : void 0
4974
+ ...typeof imgProps.style === "object" && imgProps.style ? imgProps.style : {},
4975
+ ...composerEdit && fieldAttrs ? { cursor: "pointer" } : null
5132
4976
  }
5133
4977
  }
5134
4978
  );
5135
4979
  }
5136
- function ComposerInlineHtml({
5137
- name,
5138
- html,
5139
- sourceValue,
5140
- className,
5141
- Tag = "div",
5142
- layoutNodeId,
5143
- componentId,
5144
- fieldRef
5145
- }) {
5146
- const elRef = useRef(null);
5147
- useEffect(() => {
5148
- const el = elRef.current;
5149
- if (!el) return;
5150
- if (el.innerHTML !== html) el.innerHTML = html;
5151
- }, [html]);
5152
- const openRichEditor = (event) => {
5153
- if (!elRef.current) return;
5154
- event.preventDefault();
5155
- event.stopPropagation();
5156
- requestCanvasRichTextEdit({
5157
- el: elRef.current,
5158
- layoutNodeId,
5159
- componentId,
5160
- name,
5161
- value: sourceValue,
5162
- fieldRef
5163
- });
5164
- };
5165
- return React3.createElement(Tag, {
5166
- ref: elRef,
5167
- className,
5168
- "data-amplifyup-field": name,
5169
- "data-amplifyup-field-type": "richtext",
5170
- "data-amplifyup-source": sourceValue,
5171
- "data-amplifyup-node-id": layoutNodeId || void 0,
5172
- "data-amplifyup-ref-provider": fieldRef?.providerId || void 0,
5173
- "data-amplifyup-ref-entity": fieldRef?.entity || void 0,
5174
- "data-amplifyup-ref-id": fieldRef?.id || void 0,
5175
- dangerouslySetInnerHTML: { __html: html },
5176
- onMouseDown: (event) => {
5177
- event.stopPropagation();
5178
- },
5179
- onClick: openRichEditor,
5180
- style: {
5181
- cursor: "text",
5182
- outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
5183
- outlineOffset: "6px"
4980
+
4981
+ // src/component-key.ts
4982
+ var componentKey = (id) => id.toLowerCase().replace(/[^a-z0-9]/g, "");
4983
+ var REGISTRY_BRAND = Symbol.for("amplifyup.componentRegistry");
4984
+ var defineComponentRegistry = (components) => {
4985
+ const index = /* @__PURE__ */ new Map();
4986
+ for (const [name, component] of Object.entries(components)) {
4987
+ const key = componentKey(name);
4988
+ if (index.has(key)) {
4989
+ throw new Error(
4990
+ `[Amplify UP] Component registry has two components named like "${name}". Rebuild it with withAmplifyUp or \`amplifyup registry\`.`
4991
+ );
5184
4992
  }
5185
- });
5186
- }
5187
- function formatFieldValue(value) {
5188
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
5189
- return String(value);
5190
- }
5191
- if (Array.isArray(value)) {
5192
- const parts = value.map((item) => {
5193
- if (typeof item === "string") return item;
5194
- if (item && typeof item === "object" && "children" in item && Array.isArray(item.children)) {
5195
- return item.children.map((c) => typeof c?.text === "string" ? c.text : "").join("");
5196
- }
5197
- return "";
5198
- }).filter(Boolean);
5199
- if (parts.length > 0) return parts.join(" ");
5200
- }
5201
- if (value && typeof value === "object") {
5202
- return "";
5203
- }
5204
- return "";
5205
- }
5206
- var FIELD_SCALAR_HINT = "Field expects a scalar field. Did you mean <Field field={post.title} />?";
5207
- function warnNotScalarField(component, field, fieldValue) {
5208
- if (process.env.NODE_ENV === "production") return false;
5209
- if (isBrandedFields(field)) {
5210
- console.error(`[AmplifyUp SDK] ${component}: ${FIELD_SCALAR_HINT}`);
5211
- return true;
4993
+ index.set(key, component);
5212
4994
  }
5213
- if (isFieldEnvelope(field) && Array.isArray(field.value)) {
5214
- console.error(`[AmplifyUp SDK] ${component}: ${FIELD_SCALAR_HINT}`);
5215
- return true;
4995
+ return {
4996
+ [REGISTRY_BRAND]: true,
4997
+ components,
4998
+ resolve: (componentId) => index.get(componentKey(componentId))
4999
+ };
5000
+ };
5001
+ var isComponentRegistry = (value) => !!value && typeof value === "object" && value[REGISTRY_BRAND] === true;
5002
+ var warnedMissing = /* @__PURE__ */ new Set();
5003
+ var warnMissingOnce = (componentId) => {
5004
+ if (warnedMissing.has(componentId)) return;
5005
+ warnedMissing.add(componentId);
5006
+ console.warn(
5007
+ `[Amplify UP] No component in your component registry matches "${componentId}". Add a file with that name to your components folder.`
5008
+ );
5009
+ };
5010
+ var MissingComponentHint = ({ componentId }) => /* @__PURE__ */ jsxs(
5011
+ "div",
5012
+ {
5013
+ "data-amplifyup-missing-component": componentId,
5014
+ style: {
5015
+ margin: "0.5rem 0",
5016
+ padding: "0.75rem 1rem",
5017
+ border: "1.5px dashed currentColor",
5018
+ borderRadius: 8,
5019
+ opacity: 0.7,
5020
+ fontSize: 14,
5021
+ textAlign: "center"
5022
+ },
5023
+ children: [
5024
+ "\u201C",
5025
+ componentId,
5026
+ "\u201D isn\u2019t set up on this site yet."
5027
+ ]
5216
5028
  }
5217
- if (Array.isArray(fieldValue)) {
5218
- console.error(`[AmplifyUp SDK] ${component}: ${FIELD_SCALAR_HINT}`);
5219
- return true;
5029
+ );
5030
+ var RegistryComponent = ({
5031
+ registry,
5032
+ componentId,
5033
+ props,
5034
+ slots,
5035
+ layoutNodeId,
5036
+ composerPreview
5037
+ }) => {
5038
+ const Component2 = registry.resolve(componentId);
5039
+ if (!Component2) {
5040
+ warnMissingOnce(componentId);
5041
+ return composerPreview ? /* @__PURE__ */ jsx(MissingComponentHint, { componentId }) : null;
5220
5042
  }
5221
- return false;
5222
- }
5223
- function assertNamedEnvelope(component, field) {
5224
- if (process.env.NODE_ENV === "production") return;
5225
- if (!field || typeof field !== "object" || Array.isArray(field)) return;
5226
- const rec = field;
5227
- if (!("value" in rec)) return;
5228
- if (typeof rec.name === "string" && rec.name) return;
5229
- throw new Error(
5230
- `[AmplifyUp SDK] ${component} received an envelope-shaped object with a \`value\` but no \`name\` \u2014 a partial envelope was manufactured upstream (keys: ${Object.keys(
5231
- rec
5232
- ).join(", ")}). Find the producer; do not strip or rebuild envelopes.`
5233
- );
5234
- }
5235
- var warnedReadOnlyFields = /* @__PURE__ */ new Set();
5236
- function warnReadOnlyRowField(name) {
5237
- if (process.env.NODE_ENV === "production") return;
5238
- const key = name || "(unnamed)";
5239
- if (warnedReadOnlyFields.has(key)) return;
5240
- warnedReadOnlyFields.add(key);
5241
- console.warn(
5242
- `[AmplifyUp SDK] ${key} has no write target; rendered read-only.`
5043
+ return /* @__PURE__ */ jsx(
5044
+ ComponentContextProvider,
5045
+ {
5046
+ props,
5047
+ slots,
5048
+ layoutNodeId,
5049
+ componentId,
5050
+ children: /* @__PURE__ */ jsx(Component2, { ...props })
5051
+ }
5243
5052
  );
5244
- }
5245
- function canComposerBind(field, ref, editName) {
5246
- if (isReadOnlyRowField(field) || isFieldEnvelope(field) && field.readOnly) {
5247
- warnReadOnlyRowField(editName || (isFieldEnvelope(field) ? field.name : ""));
5248
- return false;
5053
+ };
5054
+ var assertComponentRegistry = (owner, props) => {
5055
+ if ("renderComponent" in props) {
5056
+ throw new Error(
5057
+ `[Amplify UP] ${owner}: renderComponent was removed in @amplifyup/sdk 0.2.0. Components now load automatically \u2014 wrap next.config with withAmplifyUp and render <AmplifyPage /> from the generated component registry. See "Upgrading from 0.1" in the SDK README.`
5058
+ );
5249
5059
  }
5250
- return true;
5251
- }
5252
- function warnProjectionProps(component, hasField, hasValue, name) {
5253
- if (process.env.NODE_ENV === "production") return;
5254
- if (!hasField && !hasValue && !name) {
5255
- console.warn(
5256
- `[AmplifyUp SDK] ${component} needs field={fields.yourProp} (or value + name for computed values)`
5060
+ if (!isComponentRegistry(props.registry)) {
5061
+ throw new Error(
5062
+ `[Amplify UP] ${owner} needs your component registry. Render <AmplifyPage /> from the generated .amplifyup/component-registry module (created by withAmplifyUp or \`amplifyup registry\`).`
5257
5063
  );
5258
5064
  }
5065
+ };
5066
+ function nodeRenderProps(node) {
5067
+ const settings = node.settings || {};
5068
+ const hydrated = hydratePageResourceProps(
5069
+ node.props || {},
5070
+ getPageResourceFields()
5071
+ );
5072
+ const fields = hydrated.fields;
5073
+ const branded = isBrandedFields(fields) ? fields : brandFields(
5074
+ fields && typeof fields === "object" ? fields : {}
5075
+ );
5076
+ return {
5077
+ ...settings,
5078
+ ...hydrated,
5079
+ fields: branded
5080
+ };
5259
5081
  }
5260
- function hasOwnProp(props, key) {
5261
- return Object.prototype.hasOwnProperty.call(props, key);
5262
- }
5263
- function Field(props) {
5264
- const { field, value, name, className } = props;
5265
- const context = useContext(ComponentContext);
5266
- const composerEdit = useComposerEditMode();
5267
- const hasField = isFieldEnvelope(field);
5268
- const hasValue = hasOwnProp(props, "value");
5269
- const { editName, fieldValue, ref } = useBoundField(field, value, name, "Field");
5270
- assertNamedEnvelope("Field", field);
5271
- warnProjectionProps("Field", hasField, hasValue, name);
5272
- if (warnNotScalarField("Field", field, fieldValue)) {
5273
- return null;
5082
+ var ComponentRenderBoundary = class extends Component {
5083
+ constructor() {
5084
+ super(...arguments);
5085
+ this.state = { error: null };
5274
5086
  }
5275
- if (!hasField && !hasValue && !name) {
5276
- return null;
5087
+ static getDerivedStateFromError(error) {
5088
+ return { error };
5277
5089
  }
5278
- if (isBrandedFields(field) || Array.isArray(fieldValue)) {
5279
- return null;
5090
+ componentDidCatch(error) {
5091
+ console.error(
5092
+ `[AmplifyRenderer] "${this.props.componentId}" (${this.props.nodeId}) threw while rendering:`,
5093
+ error
5094
+ );
5280
5095
  }
5281
- const empty = isComposerEmptyText(fieldValue);
5282
- const bindComposer = composerEdit && editName && context && canInlineEdit(editName, fieldValue) && canComposerBind(field, ref, editName);
5283
- if (bindComposer) {
5284
- return /* @__PURE__ */ jsx(
5285
- ComposerInlineField,
5286
- {
5287
- name: editName,
5288
- value: fieldValue,
5289
- className,
5290
- layoutNodeId: context.layoutNodeId,
5291
- componentId: context.componentId,
5292
- fieldRef: ref,
5293
- rich: looksLikeRichCanvasField(editName)
5096
+ render() {
5097
+ if (this.state.error) {
5098
+ if (process.env.NODE_ENV !== "production") {
5099
+ return /* @__PURE__ */ jsxs(
5100
+ "div",
5101
+ {
5102
+ "data-amplifyup-render-error": "",
5103
+ "data-component-id": this.props.componentId,
5104
+ "data-node-id": this.props.nodeId,
5105
+ style: {
5106
+ margin: "1rem 0",
5107
+ padding: "0.75rem 1rem",
5108
+ border: "1px solid #fca5a5",
5109
+ borderRadius: "0.5rem",
5110
+ background: "#fef2f2",
5111
+ color: "#991b1b",
5112
+ fontSize: "0.875rem"
5113
+ },
5114
+ children: [
5115
+ 'Component "',
5116
+ this.props.componentId,
5117
+ '" failed to render:',
5118
+ " ",
5119
+ this.state.error.message
5120
+ ]
5121
+ }
5122
+ );
5294
5123
  }
5295
- );
5124
+ return null;
5125
+ }
5126
+ return this.props.children;
5296
5127
  }
5297
- if (empty) return null;
5298
- const text = formatFieldValue(fieldValue);
5299
- if (!text) return null;
5300
- return /* @__PURE__ */ jsx("span", { className, children: text });
5128
+ };
5129
+ function NodeWithFields({
5130
+ nodeId,
5131
+ componentId,
5132
+ props,
5133
+ slots,
5134
+ registry,
5135
+ composerPreview
5136
+ }) {
5137
+ return /* @__PURE__ */ jsx(MaybeComposerProvider, { enabled: !!composerPreview, layoutNodeId: nodeId, children: /* @__PURE__ */ jsx(
5138
+ RegistryComponent,
5139
+ {
5140
+ registry,
5141
+ componentId,
5142
+ props,
5143
+ slots,
5144
+ layoutNodeId: nodeId,
5145
+ composerPreview: !!composerPreview
5146
+ }
5147
+ ) });
5301
5148
  }
5302
- function markdownToHtml(markdown) {
5303
- try {
5304
- const result = marked.parse(markdown, { async: false });
5305
- return typeof result === "string" ? result : "";
5306
- } catch {
5307
- return markdown;
5149
+ function AmplifyRendererInner(rendererProps) {
5150
+ assertComponentRegistry("AmplifyRenderer", rendererProps);
5151
+ const {
5152
+ registry,
5153
+ pageConfig: serverPageConfig,
5154
+ loadingComponent,
5155
+ errorComponent,
5156
+ emptyComponent,
5157
+ composerPreview
5158
+ } = rendererProps;
5159
+ const clientHook = useAmplifyUp();
5160
+ useRegisterPageContent(!serverPageConfig);
5161
+ const composerPreviewFlag = useComposerPreviewFlag(!!composerPreview);
5162
+ const pageConfig = serverPageConfig || clientHook.pageConfig || null;
5163
+ const loading = serverPageConfig ? false : clientHook.loading;
5164
+ const error = serverPageConfig ? null : clientHook.error;
5165
+ const contentSource = clientHook.source;
5166
+ const [variantCache, setVariantCache] = useState({});
5167
+ useEffect(() => {
5168
+ if (typeof window !== "undefined") {
5169
+ if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
5170
+ return;
5171
+ }
5172
+ if (window.location.pathname.includes("/api/pages/proxy")) {
5173
+ return;
5174
+ }
5175
+ if (window.self !== window.top) {
5176
+ return;
5177
+ }
5178
+ const urlParams = new URLSearchParams(window.location.search);
5179
+ if (urlParams.get("preview") === "true" || urlParams.get("visualEditor") === "true") {
5180
+ return;
5181
+ }
5182
+ if (window.location.href.includes("preview=true") || window.location.href.includes("visualEditor=true")) {
5183
+ return;
5184
+ }
5185
+ }
5186
+ if (pageConfig && pageConfig.id !== "default") {
5187
+ trackContentView(pageConfig.id, "page", {
5188
+ route: pageConfig.route,
5189
+ title: pageConfig.title
5190
+ });
5191
+ }
5192
+ }, [pageConfig]);
5193
+ useEffect(() => {
5194
+ if (!pageConfig?.eventTracking?.length) return;
5195
+ const sdk = getSDK();
5196
+ if (!sdk.isInitialized()) return;
5197
+ sdk.setupEventTracking(pageConfig.eventTracking);
5198
+ const t1 = window.setTimeout(() => {
5199
+ sdk.setupComponentViewTracking();
5200
+ refreshEventTracking();
5201
+ }, 200);
5202
+ const t2 = window.setTimeout(() => {
5203
+ sdk.setupComponentViewTracking();
5204
+ refreshEventTracking();
5205
+ }, 1e3);
5206
+ return () => {
5207
+ window.clearTimeout(t1);
5208
+ window.clearTimeout(t2);
5209
+ };
5210
+ }, [pageConfig?.id, pageConfig?.eventTracking]);
5211
+ useEffect(() => {
5212
+ if (!pageConfig) return;
5213
+ const t = window.setTimeout(() => {
5214
+ getSDK().setupComponentViewTracking();
5215
+ refreshEventTracking();
5216
+ }, 100);
5217
+ return () => window.clearTimeout(t);
5218
+ }, [variantCache, pageConfig?.id]);
5219
+ useEffect(() => {
5220
+ if (!isComposerPreview()) {
5221
+ return;
5222
+ }
5223
+ if (!pageConfig) {
5224
+ return;
5225
+ }
5226
+ const rawTree2 = pageConfig.layoutTree;
5227
+ const normalizedTree = Array.isArray(rawTree2) ? rawTree2 : rawTree2 && typeof rawTree2 === "object" && rawTree2.componentId ? [rawTree2] : [];
5228
+ if (normalizedTree.length === 0) {
5229
+ if (typeof window !== "undefined" && window.parent && window.parent !== window) {
5230
+ window.parent.postMessage({
5231
+ type: "COMPONENT_TREE",
5232
+ tree: []
5233
+ }, "*");
5234
+ }
5235
+ return;
5236
+ }
5237
+ const convertLayoutTreeToComponentTree = (nodes, parentId = null) => {
5238
+ const result = [];
5239
+ nodes.forEach((node) => {
5240
+ if (!node) return;
5241
+ const treeNode = {
5242
+ id: node.id,
5243
+ componentId: node.componentId,
5244
+ componentName: node.componentId,
5245
+ // Will be enhanced with actual name if available
5246
+ parentId,
5247
+ children: []
5248
+ };
5249
+ if (node.slots && node.slots.length > 0) {
5250
+ node.slots.forEach((slot) => {
5251
+ const kids = Array.isArray(slot?.children) ? slot.children : [];
5252
+ if (kids.length > 0) {
5253
+ const slotChildren = convertLayoutTreeToComponentTree(kids, node.id);
5254
+ treeNode.children.push(...slotChildren);
5255
+ }
5256
+ });
5257
+ }
5258
+ result.push(treeNode);
5259
+ });
5260
+ return result;
5261
+ };
5262
+ const layoutTree2 = normalizedTree;
5263
+ const componentTree = convertLayoutTreeToComponentTree(layoutTree2);
5264
+ const describeLayoutTree = (nodes, depth = 0) => {
5265
+ if (!nodes?.length) return "";
5266
+ const indent = " ".repeat(depth);
5267
+ return nodes.map((n) => {
5268
+ const slotsDesc = (n.slots || []).map((s) => {
5269
+ const childCount = Array.isArray(s.children) ? s.children.length : 0;
5270
+ const childrenDesc = childCount > 0 ? describeLayoutTree(s.children || [], depth + 1) : "";
5271
+ return `${indent} slot "${s.slotId || s.slotName || "?"}" (${childCount} children)${childrenDesc ? "\n" + childrenDesc : ""}`;
5272
+ }).join("\n");
5273
+ return `${indent}- ${n.id} (${n.componentId || "?"})${slotsDesc ? "\n" + slotsDesc : ""}`;
5274
+ }).join("\n");
5275
+ };
5276
+ sdkDebugOnce(
5277
+ `layoutTree:${pageConfig?.id || ""}:${pageConfig?.route || ""}`,
5278
+ "[AmplifyRenderer] layoutTree received:\n%s",
5279
+ describeLayoutTree(layoutTree2)
5280
+ );
5281
+ if (typeof window !== "undefined" && window.parent && window.parent !== window) {
5282
+ const message = {
5283
+ type: "COMPONENT_TREE",
5284
+ tree: componentTree
5285
+ };
5286
+ window.parent.postMessage(message, "*");
5287
+ sdkDebug(
5288
+ "[AmplifyRenderer] Sent component tree from layoutTree:",
5289
+ componentTree.length,
5290
+ "root components"
5291
+ );
5292
+ }
5293
+ }, [pageConfig]);
5294
+ useEffect(() => {
5295
+ if (!pageConfig?.layoutTree || !pageConfig.layoutId) return;
5296
+ const roots = pageConfig.layoutTree;
5297
+ const fromEdge = contentSource === "edge" || roots.some((n) => n._contentSource === "edge");
5298
+ if (fromEdge) return;
5299
+ const sdk = getSDK();
5300
+ const instanceId = sdk.getInstanceId();
5301
+ const decisionApiUrl = sdk.getDecisionApiUrl();
5302
+ const orchestratorUrl = sdk.getOrchestratorUrl();
5303
+ if (!decisionApiUrl && !orchestratorUrl || !instanceId) return;
5304
+ const fetchVariants = async () => {
5305
+ const nodes = pageConfig.layoutTree;
5306
+ const newCache = {};
5307
+ const findZonedSlots = (nodeList) => {
5308
+ const results = [];
5309
+ for (const node of nodeList) {
5310
+ if (node.slots) {
5311
+ for (const slot of node.slots) {
5312
+ if (slot.zoneId && slot.variants) {
5313
+ const key = `${node.id}-${slot.slotId}`;
5314
+ results.push({ node, slot, key });
5315
+ }
5316
+ }
5317
+ }
5318
+ }
5319
+ return results;
5320
+ };
5321
+ const zonedSlots = findZonedSlots(nodes);
5322
+ await Promise.all(
5323
+ zonedSlots.map(async ({ node, slot, key }) => {
5324
+ try {
5325
+ let personalizationUrl;
5326
+ if (decisionApiUrl) {
5327
+ const url = new URL(decisionApiUrl);
5328
+ url.searchParams.set("instanceId", instanceId);
5329
+ const zoneId = slot.zoneId;
5330
+ if (zoneId) {
5331
+ url.searchParams.set("zoneId", zoneId);
5332
+ }
5333
+ const campaignId = slot.campaignId;
5334
+ if (campaignId) {
5335
+ url.searchParams.set("campaignId", campaignId);
5336
+ }
5337
+ personalizationUrl = url.toString();
5338
+ } else if (orchestratorUrl) {
5339
+ personalizationUrl = `${orchestratorUrl}/api/personalization/variant?instanceId=${encodeURIComponent(instanceId)}&layoutId=${encodeURIComponent(pageConfig.layoutId)}&slotId=${encodeURIComponent(slot.slotId)}&nodeId=${encodeURIComponent(node.id)}`;
5340
+ } else {
5341
+ return;
5342
+ }
5343
+ const response = await fetch(personalizationUrl);
5344
+ if (response.ok) {
5345
+ const data = await response.json();
5346
+ if (data.variantId && data.componentTree) {
5347
+ newCache[key] = {
5348
+ variantId: data.variantId,
5349
+ children: data.componentTree
5350
+ };
5351
+ if (typeof window !== "undefined") {
5352
+ if (window.__AMPLIFYUP_PREVIEW_MODE__ === true) {
5353
+ return;
5354
+ }
5355
+ if (window.location.pathname.includes("/api/pages/proxy")) {
5356
+ return;
5357
+ }
5358
+ if (window.self !== window.top) {
5359
+ return;
5360
+ }
5361
+ }
5362
+ sdk.track("Zone Impression", {
5363
+ zoneId: slot.zoneId,
5364
+ variantId: data.variantId,
5365
+ layoutId: pageConfig.layoutId,
5366
+ route: pageConfig.route
5367
+ });
5368
+ } else {
5369
+ const defaultVariant = slot.variants?.find((v) => v.isDefault);
5370
+ if (defaultVariant) {
5371
+ newCache[key] = {
5372
+ variantId: defaultVariant.variantId,
5373
+ children: defaultVariant.children
5374
+ };
5375
+ }
5376
+ }
5377
+ }
5378
+ } catch (err) {
5379
+ console.error(`Error fetching variant for slot ${key}:`, err);
5380
+ const defaultVariant = slot.variants?.find((v) => v.isDefault);
5381
+ if (defaultVariant) {
5382
+ newCache[key] = {
5383
+ variantId: defaultVariant.variantId,
5384
+ children: defaultVariant.children
5385
+ };
5386
+ }
5387
+ }
5388
+ })
5389
+ );
5390
+ setVariantCache(newCache);
5391
+ };
5392
+ fetchVariants();
5393
+ }, [pageConfig?.layoutTree, pageConfig?.layoutId, pageConfig?.route, contentSource]);
5394
+ if (loading) {
5395
+ return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50", children: /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsxs("div", { className: "animate-pulse", children: [
5396
+ /* @__PURE__ */ jsx("div", { className: "h-8 bg-gray-200 rounded w-1/4 mb-4" }),
5397
+ /* @__PURE__ */ jsx("div", { className: "h-4 bg-gray-200 rounded w-1/2 mb-8" }),
5398
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
5399
+ /* @__PURE__ */ jsx("div", { className: "h-64 bg-gray-200 rounded" }),
5400
+ /* @__PURE__ */ jsx("div", { className: "h-32 bg-gray-200 rounded" })
5401
+ ] })
5402
+ ] }) }) }) });
5308
5403
  }
5309
- }
5310
- function RichText(props) {
5311
- const {
5312
- field,
5313
- value,
5314
- name,
5315
- className,
5316
- as: Tag = "div",
5317
- children
5318
- } = props;
5319
- const context = useContext(ComponentContext);
5320
- const composerEdit = useComposerEditMode();
5321
- const childrenValue = typeof children === "string" || typeof children === "number" ? String(children) : void 0;
5322
- const hasField = isFieldEnvelope(field);
5323
- const hasValueProp = hasOwnProp(props, "value") || childrenValue !== void 0;
5324
- const boundValue = hasOwnProp(props, "value") ? value : childrenValue !== void 0 ? childrenValue : value;
5325
- const { editName, fieldValue, ref } = useBoundField(
5326
- field,
5327
- boundValue,
5328
- name,
5329
- "RichText"
5330
- );
5331
- assertNamedEnvelope("RichText", field);
5332
- if (!hasField && !hasValueProp && !name) {
5333
- return null;
5404
+ if (error) {
5405
+ return /* @__PURE__ */ jsx(Fragment, { children: errorComponent ? errorComponent(error) : /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50", children: /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsxs("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6", children: [
5406
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-red-900 mb-2", children: "Error Loading Page" }),
5407
+ /* @__PURE__ */ jsx("p", { className: "text-red-700", children: error })
5408
+ ] }) }) }) });
5334
5409
  }
5335
- warnProjectionProps("RichText", hasField, hasValueProp, name);
5336
- if (warnNotScalarField("RichText", field, fieldValue)) {
5410
+ if (!pageConfig) {
5337
5411
  return null;
5338
5412
  }
5339
- const canEdit = !!context && canInlineEditRichText(editName, fieldValue) && canComposerBind(field, ref, editName);
5340
- if (isComposerEmptyText(fieldValue)) {
5341
- if (composerEdit && canEdit) {
5413
+ pageConfig.layout?.header !== false;
5414
+ pageConfig.layout?.footer !== false;
5415
+ const renderNode = (node) => {
5416
+ const zoneIdFromProps = node.componentId === "AmplifyZone" ? String(node.props?.zoneId || "") : "";
5417
+ if (node.componentId === "AmplifyZone") {
5418
+ const contentSlot = node.slots?.find((s) => s.slotId === "content") || node.slots?.[0];
5419
+ const children = Array.isArray(contentSlot?.children) ? contentSlot.children : [];
5342
5420
  return /* @__PURE__ */ jsx(
5343
- ComposerInlineField,
5421
+ "div",
5344
5422
  {
5345
- name: editName,
5346
- value: fieldValue,
5347
- className,
5348
- layoutNodeId: context?.layoutNodeId,
5349
- componentId: context?.componentId,
5350
- fieldRef: ref,
5351
- rich: true
5423
+ "data-node-id": node.id,
5424
+ "data-amplifyup-node": "",
5425
+ "data-amplifyup-component": "AmplifyZone",
5426
+ "data-amplifyup-zone": zoneIdFromProps || void 0,
5427
+ "data-zone-id": zoneIdFromProps || void 0,
5428
+ children: /* @__PURE__ */ jsx(
5429
+ "div",
5430
+ {
5431
+ "data-slot-id": "content",
5432
+ "data-amplifyup-slot": "content",
5433
+ "data-parent-node-id": node.id,
5434
+ "data-parent-component-id": "AmplifyZone",
5435
+ children: children.filter(Boolean).map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child) }, child.id))
5436
+ }
5437
+ )
5352
5438
  }
5353
5439
  );
5354
5440
  }
5355
- return null;
5356
- }
5357
- let html = "";
5358
- if (typeof fieldValue === "string") {
5359
- const trimmed = fieldValue.trim();
5360
- if (trimmed.startsWith("<") && trimmed.includes(">")) {
5361
- html = fieldValue;
5362
- } else {
5363
- html = markdownToHtml(fieldValue);
5441
+ let slotChildren = {};
5442
+ sdkDebugOnce(`node:${node.id}`, `[AmplifyRenderer] Rendering node ${node.id} (${node.componentId})`, {
5443
+ hasSlots: !!(node.slots && node.slots.length > 0),
5444
+ slotCount: node.slots?.length || 0,
5445
+ slots: node.slots?.map((s) => ({ slotId: s.slotId, zoneId: s.zoneId, childrenCount: s.children?.length || 0 }))
5446
+ });
5447
+ if (node.slots && node.slots.length > 0) {
5448
+ for (const slot of node.slots) {
5449
+ const slotKey = `${node.id}-${slot.slotId}`;
5450
+ let childrenToRender = [];
5451
+ if (slot.zoneId && slot.variants && variantCache[slotKey]) {
5452
+ childrenToRender = variantCache[slotKey].children;
5453
+ } else if (slot.zoneId && slot.variants) {
5454
+ const defaultVariant = slot.variants.find((v) => v.isDefault);
5455
+ childrenToRender = defaultVariant?.children || [];
5456
+ } else {
5457
+ childrenToRender = Array.isArray(slot.children) ? slot.children : [];
5458
+ }
5459
+ const safeChildren = Array.isArray(childrenToRender) ? childrenToRender : [];
5460
+ slotChildren[slot.slotId] = /* @__PURE__ */ jsx(
5461
+ "div",
5462
+ {
5463
+ "data-slot-id": slot.slotId,
5464
+ "data-amplifyup-slot": slot.slotId,
5465
+ "data-zone-id": slot.zoneId || void 0,
5466
+ "data-parent-node-id": node.id,
5467
+ "data-parent-component-id": node.componentId,
5468
+ children: safeChildren.filter(Boolean).map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child) }, child.id))
5469
+ },
5470
+ slot.slotId
5471
+ );
5472
+ }
5364
5473
  }
5365
- } else {
5366
- const text = formatFieldValue(fieldValue);
5367
- html = text ? markdownToHtml(text) : "";
5368
- }
5369
- if (!html) {
5370
- return null;
5371
- }
5372
- if (composerEdit && canEdit) {
5474
+ const slotsToPass = Object.keys(slotChildren).length > 0 ? slotChildren : void 0;
5373
5475
  return /* @__PURE__ */ jsx(
5374
- ComposerInlineHtml,
5476
+ "div",
5375
5477
  {
5376
- name: editName,
5377
- html,
5378
- sourceValue: typeof fieldValue === "string" ? fieldValue : html,
5379
- className,
5380
- Tag,
5381
- layoutNodeId: context?.layoutNodeId,
5382
- componentId: context?.componentId,
5383
- fieldRef: ref
5478
+ "data-node-id": node.id,
5479
+ "data-amplifyup-node": "",
5480
+ "data-component-id": node.componentId,
5481
+ "data-amplifyup-component": node.componentId,
5482
+ children: /* @__PURE__ */ jsx(
5483
+ ComponentRenderBoundary,
5484
+ {
5485
+ componentId: node.componentId,
5486
+ nodeId: node.id,
5487
+ children: /* @__PURE__ */ jsx(
5488
+ NodeWithFields,
5489
+ {
5490
+ nodeId: node.id,
5491
+ componentId: node.componentId,
5492
+ props: nodeRenderProps(node),
5493
+ slots: slotsToPass,
5494
+ registry,
5495
+ composerPreview: composerPreviewFlag
5496
+ }
5497
+ )
5498
+ }
5499
+ )
5384
5500
  }
5385
5501
  );
5502
+ };
5503
+ const rawTree = pageConfig.layoutTree;
5504
+ const layoutTree = Array.isArray(rawTree) ? rawTree : rawTree && typeof rawTree === "object" && rawTree.componentId ? [rawTree] : [];
5505
+ if (layoutTree.length === 0) {
5506
+ return /* @__PURE__ */ jsx("main", { children: emptyComponent || /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-16", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No content configured for this page." }) }) });
5386
5507
  }
5387
- return /* @__PURE__ */ jsx(Tag, { className, dangerouslySetInnerHTML: { __html: html } });
5508
+ return /* @__PURE__ */ jsx(ComposerProvider, { children: /* @__PURE__ */ jsx(
5509
+ "main",
5510
+ {
5511
+ "data-slot-id": composerPreviewFlag ? "main" : void 0,
5512
+ "data-amplifyup-slot": composerPreviewFlag ? "main" : void 0,
5513
+ "data-slot-source": composerPreviewFlag ? "page" : void 0,
5514
+ "data-slot-state": composerPreviewFlag ? "filled" : void 0,
5515
+ children: layoutTree.map((node) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(node) }, node.id))
5516
+ }
5517
+ ) });
5518
+ }
5519
+ var AmplifyRenderer = memo(AmplifyRendererInner);
5520
+ function previewPageSlot(state) {
5521
+ return /* @__PURE__ */ jsx(
5522
+ "div",
5523
+ {
5524
+ "data-slot-id": "main",
5525
+ "data-amplifyup-slot": "main",
5526
+ "data-slot-source": "page",
5527
+ "data-slot-state": state,
5528
+ "data-slot-empty": state === "empty" ? "true" : void 0,
5529
+ style: {
5530
+ minHeight: state === "empty" ? "50vh" : void 0,
5531
+ width: "100%",
5532
+ display: "block",
5533
+ position: "relative"
5534
+ },
5535
+ children: state === "empty" ? /* @__PURE__ */ jsx(EmptySlotHint, {}) : null
5536
+ }
5537
+ );
5388
5538
  }
5389
- function isPortableImageValue(value) {
5390
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
5391
- const url = value.url;
5392
- return typeof url === "string" && url.trim().length > 0;
5539
+ function hasAmplifyLayout(pageConfig) {
5540
+ return !!(pageConfig && pageConfig.id !== "default" && Array.isArray(pageConfig.layoutTree) && pageConfig.layoutTree.length > 0);
5393
5541
  }
5394
- function imageSrc(value) {
5395
- if (typeof value === "string") return value.trim();
5396
- if (isPortableImageValue(value)) return value.url.trim();
5397
- return "";
5542
+ function normalizeRoutePath(route) {
5543
+ if (!route) return "";
5544
+ const path = route.split("?")[0] || "/";
5545
+ if (path.length > 1 && path.endsWith("/")) return path.replace(/\/+$/, "");
5546
+ return path || "/";
5398
5547
  }
5399
- function leafText(field) {
5400
- if (isFieldEnvelope(field)) return field.value;
5401
- return field;
5548
+ function samePageRoute(a, b) {
5549
+ const ar = normalizeRoutePath(a?.route);
5550
+ const br = normalizeRoutePath(b?.route);
5551
+ return !!ar && !!br && ar === br;
5402
5552
  }
5403
- function coerceImageField(field, value, name) {
5404
- if (isFieldEnvelope(field)) return { field, value, name };
5405
- if (!isBrandedFields(field) || !field || typeof field !== "object") {
5406
- return { field: void 0, value, name };
5407
- }
5408
- const bag = field;
5409
- const urlField = bag.url;
5410
- const urlName = isFieldEnvelope(urlField) && typeof urlField.name === "string" ? urlField.name.replace(/\.url$/, "") : "";
5411
- const editName = urlName || name || "";
5412
- if (!editName) return { field: void 0, value, name };
5413
- const ref = isFieldEnvelope(urlField) ? urlField.ref : void 0;
5414
- const width = leafText(bag.width);
5415
- const height = leafText(bag.height);
5416
- return {
5417
- field: {
5418
- value: {
5419
- url: String(leafText(urlField) ?? ""),
5420
- alt: leafText(bag.alt),
5421
- ...typeof width === "number" ? { width } : {},
5422
- ...typeof height === "number" ? { height } : {}
5423
- },
5424
- name: editName,
5425
- ...ref ? { ref } : {}
5426
- },
5427
- value,
5428
- name: editName
5429
- };
5553
+ function layoutTreesEqual(a, b) {
5554
+ if (!a || !b) return a === b;
5555
+ return JSON.stringify(a.layoutTree) === JSON.stringify(b.layoutTree);
5430
5556
  }
5431
- function Image(props) {
5557
+ function AmplifyPageContent(contentProps) {
5558
+ assertComponentRegistry("AmplifyPageContent", contentProps);
5432
5559
  const {
5433
- field,
5434
- value,
5435
- name,
5436
- alt: altProp,
5437
- className,
5438
- width: widthProp,
5439
- height: heightProp,
5440
- ...imgProps
5441
- } = props;
5442
- const context = useContext(ComponentContext);
5443
- assertNamedEnvelope("Image", field);
5444
- const coerced = coerceImageField(field, value, name);
5445
- const boundField = coerced.field;
5446
- const hasField = isFieldEnvelope(boundField);
5447
- const hasValue = hasOwnProp(props, "value");
5448
- const { editName, fieldValue, ref } = useBoundField(
5449
- boundField,
5450
- coerced.value,
5451
- coerced.name,
5452
- "Image"
5453
- );
5454
- const composerEdit = useComposerEditMode();
5455
- warnProjectionProps("Image", hasField || isBrandedFields(field), hasValue, coerced.name || name);
5456
- if (!isBrandedFields(field) && warnNotScalarField("Image", boundField, fieldValue)) {
5457
- return null;
5458
- }
5459
- if (!hasField && !hasValue && !name && !editName) {
5460
- return null;
5461
- }
5462
- const src = imageSrc(fieldValue);
5463
- const portable = isPortableImageValue(fieldValue) ? fieldValue : null;
5464
- const alt = altProp ?? portable?.alt ?? "";
5465
- const width = widthProp ?? portable?.width;
5466
- const height = heightProp ?? portable?.height;
5467
- const fieldAttrs = composerEdit && editName && canComposerBind(boundField, ref, editName) ? {
5468
- "data-amplifyup-field": editName,
5469
- "data-amplifyup-field-type": "image",
5470
- "data-amplifyup-node-id": context?.layoutNodeId || void 0,
5471
- "data-amplifyup-ref-provider": ref?.providerId || void 0,
5472
- "data-amplifyup-ref-entity": ref?.entity || void 0,
5473
- "data-amplifyup-ref-id": ref?.id || void 0
5474
- } : null;
5475
- if (composerEdit && !editName && (hasField || hasValue)) {
5476
- console.warn(
5477
- '[AmplifyUp SDK] Image value={\u2026} requires name="\u2026" for computed values'
5478
- );
5560
+ fallback,
5561
+ registry,
5562
+ pageConfig: pageConfigProp,
5563
+ forceComposerPreview = false,
5564
+ previewLoadingComponent,
5565
+ previewEmptyComponent,
5566
+ loadingComponent,
5567
+ emptyComponent,
5568
+ errorComponent
5569
+ } = contentProps;
5570
+ const client = useAmplifyUp();
5571
+ useRegisterPageContent();
5572
+ const preview = useComposerPreviewFlag(forceComposerPreview);
5573
+ const clientLayout = hasAmplifyLayout(client.pageConfig) ? client.pageConfig : null;
5574
+ const serverLayout = hasAmplifyLayout(pageConfigProp) ? pageConfigProp : null;
5575
+ let pageConfig = serverLayout ?? clientLayout;
5576
+ if (preview && clientLayout) {
5577
+ pageConfig = clientLayout;
5578
+ } else if (clientLayout && serverLayout && samePageRoute(clientLayout, serverLayout) && !layoutTreesEqual(clientLayout, serverLayout)) {
5579
+ pageConfig = clientLayout;
5580
+ } else if (clientLayout && !serverLayout) {
5581
+ pageConfig = clientLayout;
5479
5582
  }
5480
- if (!src) {
5481
- if (!composerEdit || !fieldAttrs) return null;
5482
- return /* @__PURE__ */ jsx(
5483
- "span",
5583
+ const loading = !hasAmplifyLayout(pageConfig) && client.loading;
5584
+ const error = hasAmplifyLayout(pageConfig) ? null : pageConfigProp ? null : client.error;
5585
+ const previewEmpty = previewEmptyComponent ?? previewPageSlot("empty");
5586
+ const previewLoading = previewLoadingComponent ?? previewPageSlot("loading");
5587
+ if (preview) {
5588
+ const ready = previewLayoutReady({
5589
+ clientLoading: client.loading,
5590
+ hasClientLayout: !!clientLayout,
5591
+ hasServerLayout: !!serverLayout
5592
+ });
5593
+ if (!ready) {
5594
+ return /* @__PURE__ */ jsx(ComposerLayoutStatusProvider, { ready: false, children: previewLoading });
5595
+ }
5596
+ const layoutSettled = !client.loading || !!clientLayout;
5597
+ return /* @__PURE__ */ jsx(ComposerLayoutStatusProvider, { ready: layoutSettled, children: /* @__PURE__ */ jsx(
5598
+ AmplifyRenderer,
5484
5599
  {
5485
- ...fieldAttrs,
5486
- className,
5487
- role: "img",
5488
- "aria-label": alt || editName || "Image",
5489
- style: {
5490
- display: "block",
5491
- boxSizing: "border-box",
5492
- minWidth: width ? void 0 : 120,
5493
- minHeight: height ? void 0 : 80,
5494
- width: width || "100%",
5495
- height: height || void 0,
5496
- background: "color-mix(in srgb, currentColor 8%, transparent)",
5497
- cursor: "pointer"
5498
- }
5600
+ pageConfig: pageConfig ?? void 0,
5601
+ registry,
5602
+ composerPreview: true,
5603
+ loadingComponent: loadingComponent ?? previewLoading,
5604
+ emptyComponent: emptyComponent ?? previewEmpty,
5605
+ errorComponent: errorComponent ?? (() => previewEmpty)
5499
5606
  }
5500
- );
5607
+ ) });
5501
5608
  }
5502
- return /* @__PURE__ */ jsx(
5503
- "img",
5609
+ if (loading) {
5610
+ return loadingComponent ? /* @__PURE__ */ jsx(Fragment, { children: loadingComponent }) : null;
5611
+ }
5612
+ if (error) {
5613
+ if (errorComponent) return /* @__PURE__ */ jsx(Fragment, { children: errorComponent(error) });
5614
+ return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
5615
+ }
5616
+ if (!hasAmplifyLayout(pageConfig)) {
5617
+ return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
5618
+ }
5619
+ return /* @__PURE__ */ jsx(ComposerLayoutStatusProvider, { ready: true, children: /* @__PURE__ */ jsx(
5620
+ AmplifyRenderer,
5504
5621
  {
5505
- ...imgProps,
5506
- ...fieldAttrs,
5507
- src,
5508
- alt,
5509
- className,
5510
- width,
5511
- height,
5512
- style: {
5513
- ...typeof imgProps.style === "object" && imgProps.style ? imgProps.style : {},
5514
- ...composerEdit && fieldAttrs ? { cursor: "pointer" } : null
5515
- }
5622
+ pageConfig: pageConfig ?? void 0,
5623
+ registry,
5624
+ loadingComponent,
5625
+ emptyComponent,
5626
+ errorComponent
5516
5627
  }
5517
- );
5628
+ ) });
5518
5629
  }
5630
+ var bindAmplifyPage = (registry) => {
5631
+ const AmplifyPage = (props) => /* @__PURE__ */ jsx(AmplifyPageContent, { ...props, registry });
5632
+ AmplifyPage.displayName = "AmplifyPage";
5633
+ return AmplifyPage;
5634
+ };
5519
5635
  var usePathname = null;
5520
5636
  if (typeof window !== "undefined") {
5521
5637
  try {
@@ -5599,11 +5715,24 @@ function AmplifyUpProvider({
5599
5715
  if (!initialized || !pageContext) return;
5600
5716
  getSDK().setPageContext(pageContext);
5601
5717
  }, [initialized, pageContextKey]);
5718
+ const contentMountsRef = useRef(0);
5719
+ const [hasPageContent, setHasPageContent] = useState(false);
5720
+ const registerPageContent = useCallback(() => {
5721
+ contentMountsRef.current += 1;
5722
+ setHasPageContent(true);
5723
+ return () => {
5724
+ contentMountsRef.current -= 1;
5725
+ if (contentMountsRef.current === 0) setHasPageContent(false);
5726
+ };
5727
+ }, []);
5728
+ const [initVersion, setInitVersion] = useState(0);
5729
+ const overlayEnabled = () => isComposerPreview() || config.clientPersonalization !== false;
5602
5730
  useEffect(() => {
5603
5731
  let cancelled = false;
5604
5732
  const sdk = getSDK();
5605
- const heldRoute = isComposerPreview() || config.clientPersonalization !== false ? getActualRoute() : null;
5606
- if (heldRoute) sdk.beginPageResolve(heldRoute);
5733
+ if (contentMountsRef.current > 0 && overlayEnabled()) {
5734
+ sdk.beginPageResolve(getActualRoute());
5735
+ }
5607
5736
  const run = async () => {
5608
5737
  try {
5609
5738
  await sdk.init({
@@ -5613,20 +5742,59 @@ function AmplifyUpProvider({
5613
5742
  if (cancelled) return;
5614
5743
  if (pageContext) sdk.setPageContext(pageContext);
5615
5744
  const actualRoute = getActualRoute();
5616
- if (!actualRoute) {
5745
+ if (actualRoute) sdk.routeChange(actualRoute);
5746
+ } catch (err) {
5747
+ if (cancelled) return;
5748
+ console.error("Error initializing AmplifyUp SDK:", err);
5749
+ setError(err instanceof Error ? err.message : "Unknown error");
5750
+ } finally {
5751
+ if (!cancelled) {
5617
5752
  setInitialized(true);
5618
- setLoading(false);
5619
- return;
5753
+ setInitVersion((v) => v + 1);
5620
5754
  }
5621
- sdk.routeChange(actualRoute);
5755
+ }
5756
+ };
5757
+ void run();
5758
+ return () => {
5759
+ cancelled = true;
5760
+ };
5761
+ }, [
5762
+ pathname,
5763
+ config.trackingId,
5764
+ config.target,
5765
+ config.debug,
5766
+ config.dev,
5767
+ config.siteId,
5768
+ config.clientPersonalization,
5769
+ pageContextKey
5770
+ ]);
5771
+ const resolveRunRef = useRef(null);
5772
+ useEffect(() => {
5773
+ return () => {
5774
+ if (resolveRunRef.current) resolveRunRef.current.cancelled = true;
5775
+ };
5776
+ }, []);
5777
+ useEffect(() => {
5778
+ if (initVersion === 0) return;
5779
+ if (resolveRunRef.current?.version === initVersion) return;
5780
+ const sdk = getSDK();
5781
+ const actualRoute = getActualRoute();
5782
+ if (!actualRoute || contentMountsRef.current === 0 || !overlayEnabled()) {
5783
+ if (actualRoute) sdk.endPageResolve(actualRoute);
5784
+ setPageConfig(null);
5785
+ setLoading(false);
5786
+ return;
5787
+ }
5788
+ if (resolveRunRef.current) resolveRunRef.current.cancelled = true;
5789
+ const runState = { version: initVersion, cancelled: false };
5790
+ resolveRunRef.current = runState;
5791
+ const finish = () => {
5792
+ sdk.endPageResolve(actualRoute);
5793
+ if (!runState.cancelled) setLoading(false);
5794
+ };
5795
+ const run = async () => {
5796
+ try {
5622
5797
  const preview = isComposerPreview();
5623
- const overlay = preview || config.clientPersonalization !== false;
5624
- if (!overlay) {
5625
- setInitialized(true);
5626
- setPageConfig(null);
5627
- setLoading(false);
5628
- return;
5629
- }
5630
5798
  if (loadedRouteRef.current && loadedRouteRef.current !== actualRoute) {
5631
5799
  setPageConfig(null);
5632
5800
  setError(null);
@@ -5635,9 +5803,7 @@ function AmplifyUpProvider({
5635
5803
  const orchestratorUrl = sdk.getOrchestratorUrl();
5636
5804
  const trackingId = sdk.getTrackingId();
5637
5805
  if (!trackingId || !orchestratorUrl || !edgeUrl) {
5638
- setInitialized(true);
5639
5806
  setError("SDK not initialized");
5640
- setLoading(false);
5641
5807
  return;
5642
5808
  }
5643
5809
  let variantKeys;
@@ -5670,7 +5836,7 @@ function AmplifyUpProvider({
5670
5836
  variantKeys,
5671
5837
  fetchOrchestratorPage: (route, isPreview) => sdk.fetchPageConfig(route, isPreview)
5672
5838
  });
5673
- if (cancelled) return;
5839
+ if (runState.cancelled) return;
5674
5840
  const nextConfig = result.pageConfig || {
5675
5841
  id: "default",
5676
5842
  route: actualRoute,
@@ -5687,7 +5853,6 @@ function AmplifyUpProvider({
5687
5853
  result.pageConfig.pageTaxonomies || null
5688
5854
  );
5689
5855
  }
5690
- setInitialized(true);
5691
5856
  setSource((prev) => prev === result.source ? prev : result.source);
5692
5857
  setMeta(
5693
5858
  (prev) => prev === result.meta || JSON.stringify(prev) === JSON.stringify(result.meta) ? prev : result.meta
@@ -5701,31 +5866,17 @@ function AmplifyUpProvider({
5701
5866
  });
5702
5867
  loadedRouteRef.current = actualRoute;
5703
5868
  } catch (err) {
5704
- if (cancelled) return;
5869
+ if (runState.cancelled) return;
5705
5870
  console.error("Error fetching page config:", err);
5706
- setInitialized(true);
5707
5871
  setError(err instanceof Error ? err.message : "Unknown error");
5708
5872
  setSource(null);
5709
5873
  setMeta(null);
5710
5874
  } finally {
5711
- if (heldRoute) sdk.endPageResolve(heldRoute);
5712
- if (!cancelled) setLoading(false);
5875
+ finish();
5713
5876
  }
5714
5877
  };
5715
5878
  void run();
5716
- return () => {
5717
- cancelled = true;
5718
- };
5719
- }, [
5720
- pathname,
5721
- config.trackingId,
5722
- config.target,
5723
- config.debug,
5724
- config.dev,
5725
- config.siteId,
5726
- config.clientPersonalization,
5727
- pageContextKey
5728
- ]);
5879
+ }, [initVersion, hasPageContent]);
5729
5880
  useEffect(() => {
5730
5881
  const applyLayoutTree = (layoutTree) => {
5731
5882
  if (!Array.isArray(layoutTree)) return;
@@ -5759,13 +5910,13 @@ function AmplifyUpProvider({
5759
5910
  window.removeEventListener("amplifyup:layout-patch", onCustom);
5760
5911
  };
5761
5912
  }, []);
5762
- return /* @__PURE__ */ jsx(
5913
+ return /* @__PURE__ */ jsx(PageContentRegistryContext.Provider, { value: registerPageContent, children: /* @__PURE__ */ jsx(
5763
5914
  AmplifyUpContext.Provider,
5764
5915
  {
5765
5916
  value: { pageConfig, loading, error, source, meta },
5766
5917
  children
5767
5918
  }
5768
- );
5919
+ ) });
5769
5920
  }
5770
5921
  function useAmplifyUp() {
5771
5922
  const context = useContext(AmplifyUpContext);
@@ -5775,6 +5926,6 @@ function useAmplifyUp() {
5775
5926
  return context;
5776
5927
  }
5777
5928
 
5778
- export { AmplifyPageContent, AmplifyRenderer, AmplifyUpProvider, ComponentContextProvider, ComposerLayoutStatusProvider, ComposerProvider, Field, Image, MaybeComposerProvider, QueryContentError, RichText, SelectVariantsError, Slot, attachFieldEnvelopes, composerStoreKey, envelopeListRows, isBrandedFields, isComposerPreview, isFieldEnvelope, isPreviewRequest, isReadOnlyRowField, isWorkingContentRequest, listOwnerFromSpec, nextPageSpec, queryContent, resolveSlotState, searchSpec, selectVariants, slotShowsAddComponent, slotStateFromEl, toFieldEnvelopes, useAmplifyUp, useComposerPreviewFlag, useInComposer };
5929
+ export { AmplifyPageContent, AmplifyRenderer, AmplifyUpProvider, ComposerLayoutStatusProvider, ComposerProvider, Field, Image, MaybeComposerProvider, QueryContentError, RichText, SelectVariantsError, Slot, attachFieldEnvelopes, bindAmplifyPage, composerStoreKey, defineComponentRegistry, envelopeListRows, isBrandedFields, isComposerPreview, isFieldEnvelope, isPreviewRequest, isReadOnlyRowField, isWorkingContentRequest, listOwnerFromSpec, nextPageSpec, queryContent, resolveSlotState, searchSpec, selectVariants, slotShowsAddComponent, slotStateFromEl, toFieldEnvelopes, useAmplifyUp, useComposerPreviewFlag, useInComposer };
5779
5930
  //# sourceMappingURL=react.mjs.map
5780
5931
  //# sourceMappingURL=react.mjs.map