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