@amplifyup/sdk 0.1.62 → 0.1.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,10 +61,10 @@ export default function Page() {
61
61
  }
62
62
  ```
63
63
 
64
- ### 3. Components use Field / Slot
64
+ ### 3. Write a component
65
65
 
66
66
  ```tsx
67
- import { Field, Slot } from '@amplifyup/sdk/react';
67
+ import { Field } from '@amplifyup/sdk/react';
68
68
  import type { Fields } from '@amplifyup/sdk/react';
69
69
 
70
70
  export function Hero({ fields }: { fields: Fields<{ heading: string }> }) {
@@ -78,13 +78,443 @@ export function Hero({ fields }: { fields: Fields<{ heading: string }> }) {
78
78
  }
79
79
  ```
80
80
 
81
- AmplifyUp passes `fields` on the component props. Use
82
- `<Field field={fields.heading} />` to show and edit, or
83
- `{fields.heading.value}` to render the text. You do not pass `name` unless
84
- you computed the value yourself. An empty field renders nothing on the live
85
- site; in Composer it stays clickable. Default copy belongs in your component
86
- (`{fields.heading.value ?? 'Welcome'}`). Connect content in Composer, then
87
- **Deploy** so the live site can serve the page.
81
+ Connect content in Composer, then **Deploy** so the live site can serve the page.
82
+
83
+ That's the whole loop. The rest of this section is the detail.
84
+
85
+ ---
86
+
87
+ ## Working with `fields`
88
+
89
+ Every component receives a `fields` prop. It is not raw CMS data — every entry is a **field envelope**:
90
+
91
+ ```ts
92
+ fields.heading
93
+ // → { value: 'Welcome to AmplifyUp', name: 'heading' }
94
+
95
+ fields.hero.image
96
+ // → { value: { url: '…', alt: '…' }, name: 'hero.image' }
97
+
98
+ fields.subheading // empty on a new page
99
+ // → { value: null, name: 'subheading' }
100
+
101
+ fields.posts.value[0].title // a row inside a list
102
+ // → { value: 'Ship faster', name: 'title', ref: { providerId, entity, id } }
103
+ ```
104
+
105
+ `name` is the field's dot path. It is what lets `<Field>` know which field it is bound to without you telling it. `value` is the content, typed from your schema.
106
+
107
+ **The shape is identical on the live site, in a draft, and in Composer.** There is no mode where you get a bare string instead of an envelope, and no mode where an empty field is `undefined` instead of `{ value: null, name }`.
108
+
109
+ ### Write targets
110
+
111
+ One rule explains everything about editing:
112
+
113
+ > **A field is editable if and only if it carries a write target.** The write target is set by whoever produced the data. Nothing downstream guesses.
114
+
115
+ - A **page field** (`fields.heading`) has no `ref`. Its write target is the page, and Composer already knows which page you are on.
116
+ - A **list row field** (`post.title`) carries `ref: { providerId, entity, id }` — the record it belongs to. Editing it saves to that record, not to the page.
117
+ - A row field the SDK could not trace back to a producer gets no `ref` and is marked `readOnly: true`. It renders as plain text in Composer: no outline, no click target.
118
+
119
+ You never set `ref` yourself. You never read it either. It is there so that the same `<Field>` you use for a page heading also works on a row in a list.
120
+
121
+ ### Two ways to read a field
122
+
123
+ ```tsx
124
+ // Editable — use an SDK component
125
+ <Field field={fields.heading} />
126
+
127
+ // Display only — read .value
128
+ <title>{fields.heading.value}</title>
129
+ ```
130
+
131
+ Use the component when the field should be clickable in Composer. Use `.value` when it shouldn't — meta tags, `aria-label`, an `href`, a condition, anything that isn't visible text.
132
+
133
+ ```tsx
134
+ export function Card({ fields }: { fields: Fields<{ title: string; link: string; image: ImageValue }> }) {
135
+ return (
136
+ <a href={fields.link.value ?? '#'} aria-label={fields.title.value ?? undefined}>
137
+ <Image field={fields.image} />
138
+ <h3><Field field={fields.title} /></h3>
139
+ </a>
140
+ );
141
+ }
142
+ ```
143
+
144
+ ### SDK field components
145
+
146
+ | Component | Field type | Renders |
147
+ | --- | --- | --- |
148
+ | `<Field>` | `string`, `number` | a `<span>` with the text |
149
+ | `<RichText>` | markdown / rich text | rendered HTML in a `<div>` (change with `as`) |
150
+ | `<Image>` | `ImageValue` | `<img>` |
151
+
152
+ All three take a `field` prop and read `name` from it. Pass any extra props (`className`, `loading`, etc.) and they go on the rendered element.
153
+
154
+ ```tsx
155
+ <Field field={fields.eyebrow} className="text-xs uppercase" />
156
+ <RichText field={fields.body} className="prose" />
157
+ <Image field={fields.cover} className="rounded-lg" loading="lazy" />
158
+ ```
159
+
160
+ `<Field>` is scalar-only. Passing a list is a type error:
161
+
162
+ ```tsx
163
+ // ✗ Field<string[]> is not assignable to Field<string | number>
164
+ <Field field={fields.tags} />
165
+ ```
166
+
167
+ ### Empty fields
168
+
169
+ An empty field renders **nothing** on the live site and a clickable placeholder in Composer. The SDK never invents default copy.
170
+
171
+ ```tsx
172
+ // A new page, `subheading` not yet filled in:
173
+
174
+ <Field field={fields.subheading} />
175
+ // live: (nothing)
176
+ // Composer: clickable empty placeholder
177
+ ```
178
+
179
+ If you want a default, it's yours to write, and it reads from `.value`:
180
+
181
+ ```tsx
182
+ // ✓ your default, in your code
183
+ <p>{fields.subheading.value ?? 'Built for teams that ship.'}</p>
184
+
185
+ // ✓ hide the whole component when the key field is empty
186
+ export function Banner({ fields }: { fields: Fields<{ message: string }> }) {
187
+ if (!fields.message.value) return null;
188
+ return <div className="banner"><Field field={fields.message} /></div>;
189
+ }
190
+ ```
191
+
192
+ Why no `fallback` prop: a fallback turns "this field was never populated" into "this field says *Welcome*". Nobody notices until a customer does. Empty should look empty.
193
+
194
+ ### Wrapper elements
195
+
196
+ `<Field>` renders an inline `<span>`, never a heading or paragraph, so you supply the semantic tag around it:
197
+
198
+ ```tsx
199
+ // ✓
200
+ <h1><Field field={fields.heading} /></h1>
201
+ <p className="lead"><Field field={fields.intro} /></p>
202
+
203
+ // ✗ — Field has no `as` prop; put the element around it
204
+ <Field as="h1" field={fields.heading} />
205
+ ```
206
+
207
+ `<RichText>` is the exception: it owns a block element, and `as` picks which one.
208
+
209
+ ```tsx
210
+ // ✓ — default is <div>
211
+ <RichText field={fields.body} className="prose" />
212
+
213
+ // ✓ — pick a different block element
214
+ <RichText field={fields.body} as="article" className="prose" />
215
+
216
+ // ✗ — don't nest a block element inside a paragraph
217
+ <p><RichText field={fields.body} /></p>
218
+ ```
219
+
220
+ If the wrapper should disappear when the field is empty, check `.value`:
221
+
222
+ ```tsx
223
+ {fields.intro.value ? (
224
+ <p className="lead"><Field field={fields.intro} /></p>
225
+ ) : null}
226
+ ```
227
+
228
+ ### Computed values
229
+
230
+ Occasionally what you display isn't a field — it's derived from one. Then you pass `value` and must also pass `name` so Composer knows which field a click edits:
231
+
232
+ ```tsx
233
+ // Show a formatted price but edit the raw number
234
+ <Field
235
+ value={formatPrice(fields.price.value)}
236
+ name={fields.price.name}
237
+ />
238
+ ```
239
+
240
+ `name` is only accepted alongside `value`. This is the one place you write it.
241
+
242
+ ```tsx
243
+ // ✗ — redundant, and a type error
244
+ <Field field={fields.price} name="price" />
245
+
246
+ // ✗ — value without name has nothing to bind to
247
+ <Field value={formatPrice(fields.price.value)} />
248
+ ```
249
+
250
+ ### Rendering differently in Composer
251
+
252
+ Most components render the same everywhere. Sometimes the live markup can't host an editable field — the text is a CSS background, an SVG `<title>`, a `<meta>` tag, an attribute, or it's behind interaction (a closed accordion, an auto-playing carousel). In Composer you still need something the author can click.
253
+
254
+ `useInComposer()` tells you which mode you're in:
255
+
256
+ ```tsx
257
+ import { Field, Image, useInComposer } from '@amplifyup/sdk/react';
258
+
259
+ export function HeroBackground({ fields }: { fields: Fields<{ heading: string; image: ImageValue }> }) {
260
+ const inComposer = useInComposer();
261
+
262
+ return (
263
+ <section
264
+ className="hero"
265
+ style={{ backgroundImage: `url(${fields.image.value?.url ?? ''})` }}
266
+ >
267
+ {/* Live: image is a CSS background. Composer: also render it so it's clickable. */}
268
+ {inComposer ? <Image field={fields.image} className="hidden" /> : null}
269
+ <h1><Field field={fields.heading} /></h1>
270
+ </section>
271
+ );
272
+ }
273
+ ```
274
+
275
+ Other common cases:
276
+
277
+ ```tsx
278
+ // Attribute on the live site, editable in Composer
279
+ const inComposer = useInComposer();
280
+ return (
281
+ <>
282
+ <button aria-label={fields.label.value ?? undefined}>
283
+ <Icon />
284
+ {inComposer ? <Field field={fields.label} /> : null}
285
+ </button>
286
+ </>
287
+ );
288
+ ```
289
+
290
+ ```tsx
291
+ // Component that hides itself when empty on live, but stays visible for authors
292
+ export function Announcement({ fields }: { fields: Fields<{ message: string }> }) {
293
+ const inComposer = useInComposer();
294
+ if (!fields.message.value && !inComposer) return null;
295
+ return <div className="announcement"><Field field={fields.message} /></div>;
296
+ }
297
+ ```
298
+
299
+ ```tsx
300
+ // Interaction that fights the canvas — turn it off in Composer
301
+ export function Carousel({ fields }) {
302
+ const inComposer = useInComposer();
303
+ return (
304
+ <>
305
+ {(fields.slides.value ?? []).map((slide) => (
306
+ <Slide key={slide.id} slide={slide} autoplay={!inComposer} />
307
+ ))}
308
+ </>
309
+ );
310
+ }
311
+ ```
312
+
313
+ Rules of thumb:
314
+
315
+ - The **field component is the source of truth in Composer.** If a field is visible to authors, it must be rendered through `<Field>` / `<RichText>` / `<Image>` in Composer mode, even if the live site reads `.value`.
316
+ - **Don't fork the whole component.** Branch the one element that differs, not the entire return. Two divergent trees drift and the preview stops matching production.
317
+ - **Empty gating uses `inComposer`.** `if (!x.value) return null` is correct for live and wrong for authors — they can't click what isn't rendered. Add `&& !inComposer`.
318
+ - **Don't use it to hide editable text on live.** If the text is visible on the live site, render it with the field component in both modes. `useInComposer` is for content the live markup genuinely can't expose as a field.
319
+
320
+ ```tsx
321
+ // ✗ — heading is visible on live; no reason to fork
322
+ {inComposer ? <Field field={fields.heading} /> : fields.heading.value}
323
+
324
+ // ✓
325
+ <Field field={fields.heading} />
326
+ ```
327
+
328
+ ---
329
+
330
+ ## Lists
331
+
332
+ Every object array is a list of records. Each row is a fields object plus a plain `id`. Edit `post.title` in the grid and you are editing the post. Which records are in the list, and their order, is edited in the props panel — not on the canvas.
333
+
334
+ Scalar lists (tags, multi-select) stay plain: `fields.tags` is `Field<string[]>`.
335
+
336
+ ```tsx
337
+ import { Field, Image, RichText } from '@amplifyup/sdk/react';
338
+
339
+ export function LatestPosts({ fields }: { fields: Fields<{ heading: string; posts: Post[] }> }) {
340
+ const posts = fields.posts.value ?? [];
341
+ return (
342
+ <section>
343
+ <h2><Field field={fields.heading} /></h2>
344
+ <ul>
345
+ {posts.map((post) => (
346
+ <li key={post.id}>
347
+ <Image field={post.mainImage} />
348
+ <a href={`/blog/${post.slug}`}>
349
+ <Field field={post.title} />
350
+ </a>
351
+ </li>
352
+ ))}
353
+ </ul>
354
+ </section>
355
+ );
356
+ }
357
+ ```
358
+
359
+ ```tsx
360
+ // ✗ — Field expects a scalar, not the list or the row
361
+ <Field field={fields.posts} />
362
+ <Field field={post} />
363
+
364
+ // ✓ — edit the record through its fields
365
+ <Field field={post.title} />
366
+ <Image field={post.mainImage} />
367
+ ```
368
+
369
+ Search or load-more rows from `queryContent` are the same shape. Render them with the same components — one renderer for both:
370
+
371
+ ```tsx
372
+ const posts = hits ?? fields.posts.value ?? [];
373
+ return posts.map((post) => (
374
+ <article key={post.id}>
375
+ <h2><Field field={post.title} /></h2>
376
+ </article>
377
+ ));
378
+ ```
379
+
380
+ ### When a row is editable
381
+
382
+ A row is editable when the SDK knows which record it came from. That happens automatically in two cases:
383
+
384
+ | Rows came from | Editable in Composer? |
385
+ | --- | --- |
386
+ | A list prop on your component (curated or query connection) | Yes |
387
+ | `queryContent` using the published spec from `{field}Pagination.spec` | Yes |
388
+ | `queryContent` with a spec you assembled by hand, missing `providerId` or `entity` | No — renders as plain text |
389
+ | Rows you fetched yourself from your CMS and passed in as props | No — renders as plain text |
390
+
391
+ Read-only rows still render their text on the live site and in Composer. They just aren't clickable, and in development the SDK logs once per field:
392
+
393
+ ```
394
+ [AmplifyUp SDK] title has no write target; rendered read-only.
395
+ ```
396
+
397
+ If you see that and expected an editable field, the fix is upstream — use the published spec instead of a hand-built one, or bind the list to a connection in Composer. Do not try to add `ref` yourself.
398
+
399
+ ```tsx
400
+ // ✓ — spec comes from the list prop Composer published; rows stay editable
401
+ const hits = await queryContent({
402
+ trackingId,
403
+ route: '/insights',
404
+ spec: searchSpec(postsPagination, 'title', term),
405
+ });
406
+
407
+ // ✗ — hand-built spec with no connection behind it; rows render read-only
408
+ const hits = await queryContent({
409
+ trackingId,
410
+ route: '/insights',
411
+ spec: { providerId: '', entity: '', filter: [], sort: [], limit: 10, offset: 0 },
412
+ });
413
+ ```
414
+
415
+ ---
416
+
417
+ ## Slots
418
+
419
+ A slot is a region where authors drop other components. Render it with `<Slot>`:
420
+
421
+ ```tsx
422
+ import { Slot } from '@amplifyup/sdk/react';
423
+
424
+ export function TwoColumn() {
425
+ return (
426
+ <div className="grid grid-cols-2">
427
+ <div><Slot name="left" /></div>
428
+ <div><Slot name="right" /></div>
429
+ </div>
430
+ );
431
+ }
432
+ ```
433
+
434
+ `<Slot>` takes only `name` and `className`. It reads the slot content from `ComponentContextProvider`, so you never thread a `slots` prop through your component.
435
+
436
+ ```tsx
437
+ // ✗ — Slot has no `slots` prop
438
+ <Slot name="left" slots={slots} />
439
+
440
+ // ✓
441
+ <Slot name="left" />
442
+ ```
443
+
444
+ Slot names must match the slots declared on the component in AmplifyUp. Slots hold components, not fields — nothing in a slot is read through `fields`.
445
+
446
+ ---
447
+
448
+ ## Do / Don't
449
+
450
+ | Do | Don't |
451
+ | --- | --- |
452
+ | `<Field field={fields.heading} />` | `<Field field={fields.heading} name="heading" />` |
453
+ | `{fields.heading.value}` for non-visible uses | `{fields.heading}` as a JSX child (it's an object) |
454
+ | `{fields.x.value ?? 'default'}` in your code | expect the SDK to supply default copy |
455
+ | `if (!fields.x.value) return null` to hide a component | wrap `Field` in conditional logic that hides the field in Composer |
456
+ | `<h1><Field … /></h1>` | look for an `as` / `wrapper` prop |
457
+ | `<Field field={post.title} />` in a list | `<Field field={fields.posts} />` or `<Field field={post} />` |
458
+ | `value` + `name` together for computed output | `value` alone, or `name` alone |
459
+ | `<Image field={fields.cover} />` | `<img src={fields.cover.value.url} />` when it should be editable |
460
+ | `queryContent` rows through `<Field field={post.title} />` | treat query rows as raw CMS objects |
461
+ | `spec: searchSpec(postsPagination, …)` from the published list prop | hand-build a `spec` and expect rows to stay editable |
462
+ | `<Slot name="left" />` | `<Slot name="left" slots={slots} />` |
463
+ | let the producer set `ref` | set, copy, or patch `ref` yourself |
464
+ | `useInComposer()` to expose a field the live markup can't (CSS background, attribute, hidden panel) | fork the whole component's return on `inComposer` |
465
+ | `if (!x.value && !inComposer) return null` | `if (!x.value) return null` — authors can't click what isn't rendered |
466
+
467
+ ---
468
+
469
+ ## Console messages
470
+
471
+ In development the SDK tells you exactly what is wrong. Production is silent.
472
+
473
+ | Message | What it means | Fix |
474
+ | --- | --- | --- |
475
+ | `Field expects a scalar field. Did you mean <Field field={post.title} />?` | You passed a list or a whole row to `Field` / `RichText` / `Image` | Pass one scalar field off the row |
476
+ | `title has no write target; rendered read-only.` | A row field the SDK can't trace to a record | Bind the list to a connection, or query with the published spec |
477
+ | `Field value={…} requires name="…" for computed values` | You passed `value` without `name` | Add `name={fields.x.name}` |
478
+ | `Field needs field={fields.yourProp} (or value + name for computed values)` | No binding at all | Pass `field=` |
479
+ | `Slot "left" used outside ComponentContextProvider` | The component wasn't wrapped when rendered | Wrap it in `renderComponent` |
480
+
481
+ ---
482
+
483
+ ## Types
484
+
485
+ ```ts
486
+ import type { Field, Fields, ImageValue } from '@amplifyup/sdk/react';
487
+
488
+ type Field<T> = {
489
+ value: T;
490
+ name: string;
491
+ /** Write target — the record this field saves to. Page fields omit it. */
492
+ ref?: { providerId: string; entity: string; id: string };
493
+ /** Row field with no write target — display only in Composer. */
494
+ readOnly?: true;
495
+ };
496
+
497
+ type Fields<T> = { [K in keyof T]: /* Field<T[K]>, recursing into objects and lists */ };
498
+ ```
499
+
500
+ Declare the schema shape once on the component and let inference do the rest:
501
+
502
+ ```ts
503
+ type HeroFields = Fields<{
504
+ eyebrow: string;
505
+ heading: string;
506
+ body: string; // markdown → use <RichText>
507
+ image: ImageValue; // → use <Image>
508
+ posts: Post[]; // list — each row is Fields<Post> & { id: string }
509
+ tags: string[]; // scalar list — Field<string[]>
510
+ }>;
511
+
512
+ export function Hero({ fields }: { fields: HeroFields }) { … }
513
+ ```
514
+
515
+ `fields.heading.value` is `string`. `fields.image.value` is `ImageValue | null`. `fields.posts.value[0]` is `Fields<Post> & { id: string }`. `fields.posts.value[0].title` is `Field<string>` with `ref` pointing at that post.
516
+
517
+ ---
88
518
 
89
519
  ## Content sources
90
520
 
@@ -95,6 +525,8 @@ site; in Composer it stays clickable. Default copy belongs in your component
95
525
  | Composer preview | Orchestrator (Control Plane) layout provider | `?preview=true` / Composer iframe |
96
526
  | Runtime queries | Edge `/v1/query` | `queryContent` — search, load more, pickers |
97
527
 
528
+ Field envelopes are attached once, when the SDK reads the Edge response. Edge's JSON is raw; you never see it that way if you go through `AmplifyPageContent` or `fetchPageConfigServer`. If you call `fetchEdgeResolve` directly you get raw props and must map them yourself — prefer the documented path.
529
+
98
530
  ## Pattern pages (From page)
99
531
 
100
532
  Components using Composer **From page** require `pageContext` on the provider (or `init`) so the first paint and first page view include fields. Late `setPageContext` is fine for subsequent events, not for rendering those components.
@@ -116,7 +548,7 @@ Components using Composer **From page** require `pageContext` on the provider (o
116
548
 
117
549
  Pass `pageContext` at provider/init for From-page components. Do not wait until after first paint. Fixed (static) page fields still attach to events when `pageContext` is omitted.
118
550
 
119
- The SDK maps Edge’s resolved tree into `layoutTree` and renders it. Edge already applies personalization and CMS projection — the site does not call CMS or Decision APIs for layout. After paint, the overlay calls `POST /v1/select` (or `selectVariants()`) then `/v1/resolve?variants=` so decisions stay on the edge (`clientPersonalization`).
551
+ The SDK maps Edge's resolved tree into `layoutTree` and renders it. Edge already applies personalization and CMS projection — the site does not call CMS or Decision APIs for layout. After paint, the overlay calls `POST /v1/select` (or `selectVariants()`) then `/v1/resolve?variants=` so decisions stay on the edge (`clientPersonalization`).
120
552
 
121
553
  ## Runtime queries
122
554
 
@@ -143,7 +575,9 @@ const hits = await queryContent({
143
575
  });
144
576
  ```
145
577
 
146
- Only entities published with that route can be queried, and visitors only see published content. Results are plain rows, not `fields` objects — they are not canvas-editable. Also available from `@amplifyup/sdk/server`. See the [runtime queries guide](../../docs/sdk-site-setup.md#8-runtime-content-queries).
578
+ Only entities published with that route can be queried, and visitors only see published content. Also available from `@amplifyup/sdk/server`. See the [runtime queries guide](../../docs/sdk-site-setup.md#8-runtime-content-queries).
579
+
580
+ **Always start from `postsPagination.spec`.** `nextPageSpec` and `searchSpec` do that for you. The spec identifies the connection, and that is what makes the returned rows editable in Composer — see [when a row is editable](#when-a-row-is-editable). A spec you assemble by hand has no connection behind it, so its rows come back read-only.
147
581
 
148
582
  ## Track events
149
583
 
@@ -167,4 +601,4 @@ React context: `useAmplifyUp()` → `{ pageConfig, loading, error, source, meta
167
601
 
168
602
  ## License
169
603
 
170
- MIT
604
+ MIT
@@ -1,4 +1,40 @@
1
- import 'react';
2
- import 'react/jsx-runtime';
3
- export { A as AmplifyRenderer } from './AmplifyRenderer-W7eqltSj.mjs';
4
- import './types-Db_eLrfU.mjs';
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { L as LayoutComponentProps, P as PageConfig } from './types-C32MgOiv.mjs';
5
+
6
+ interface AmplifyRendererProps {
7
+ /**
8
+ * Component renderer function that maps componentId to React component
9
+ * This is site-specific and must be provided by the developer
10
+ */
11
+ renderComponent: (componentId: string, props: LayoutComponentProps, slots?: Record<string, ReactNode>, context?: {
12
+ layoutNodeId: string;
13
+ }) => ReactNode;
14
+ /**
15
+ * Optional page config for server-side rendering (SSR/SSG)
16
+ * If provided, the component will use this instead of fetching client-side
17
+ */
18
+ pageConfig?: PageConfig;
19
+ /**
20
+ * Optional loading component
21
+ */
22
+ loadingComponent?: ReactNode;
23
+ /**
24
+ * Optional error component
25
+ */
26
+ errorComponent?: (error: string) => ReactNode;
27
+ /**
28
+ * Optional empty state component (when no content is configured)
29
+ */
30
+ emptyComponent?: ReactNode;
31
+ /**
32
+ * Force Composer page-slot wrapper (avoids SSR miss when window isn't available yet).
33
+ * When true, root components stay under [data-slot-source="page"] so hierarchy can climb to Page.
34
+ */
35
+ composerPreview?: boolean;
36
+ }
37
+ declare function AmplifyRendererInner({ renderComponent, pageConfig: serverPageConfig, loadingComponent, errorComponent, emptyComponent, composerPreview, }: AmplifyRendererProps): react_jsx_runtime.JSX.Element | null;
38
+ declare const AmplifyRenderer: react.MemoExoticComponent<typeof AmplifyRendererInner>;
39
+
40
+ export { AmplifyRenderer };
@@ -1,4 +1,40 @@
1
- import 'react';
2
- import 'react/jsx-runtime';
3
- export { A as AmplifyRenderer } from './AmplifyRenderer-BQ8-H2H4.js';
4
- import './types-Db_eLrfU.js';
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { L as LayoutComponentProps, P as PageConfig } from './types-C32MgOiv.js';
5
+
6
+ interface AmplifyRendererProps {
7
+ /**
8
+ * Component renderer function that maps componentId to React component
9
+ * This is site-specific and must be provided by the developer
10
+ */
11
+ renderComponent: (componentId: string, props: LayoutComponentProps, slots?: Record<string, ReactNode>, context?: {
12
+ layoutNodeId: string;
13
+ }) => ReactNode;
14
+ /**
15
+ * Optional page config for server-side rendering (SSR/SSG)
16
+ * If provided, the component will use this instead of fetching client-side
17
+ */
18
+ pageConfig?: PageConfig;
19
+ /**
20
+ * Optional loading component
21
+ */
22
+ loadingComponent?: ReactNode;
23
+ /**
24
+ * Optional error component
25
+ */
26
+ errorComponent?: (error: string) => ReactNode;
27
+ /**
28
+ * Optional empty state component (when no content is configured)
29
+ */
30
+ emptyComponent?: ReactNode;
31
+ /**
32
+ * Force Composer page-slot wrapper (avoids SSR miss when window isn't available yet).
33
+ * When true, root components stay under [data-slot-source="page"] so hierarchy can climb to Page.
34
+ */
35
+ composerPreview?: boolean;
36
+ }
37
+ declare function AmplifyRendererInner({ renderComponent, pageConfig: serverPageConfig, loadingComponent, errorComponent, emptyComponent, composerPreview, }: AmplifyRendererProps): react_jsx_runtime.JSX.Element | null;
38
+ declare const AmplifyRenderer: react.MemoExoticComponent<typeof AmplifyRendererInner>;
39
+
40
+ export { AmplifyRenderer };