@amplifyup/sdk 0.1.61 → 0.1.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,25 +61,417 @@ 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
+ import type { Fields } from '@amplifyup/sdk/react';
68
69
 
69
- export function Hero({ heading }: { heading: string }) {
70
+ export function Hero({ fields }: { fields: Fields<{ heading: string }> }) {
70
71
  return (
71
72
  <section>
72
73
  <h1>
73
- <Field value={heading} fallback="Welcome" />
74
+ <Field field={fields.heading} />
74
75
  </h1>
75
76
  </section>
76
77
  );
77
78
  }
78
79
  ```
79
80
 
80
- Pass `value={…}` from your component props (preferred). Deprecated `name="…"`
81
- lookup still works alone — do not pass both. Connect CMS documents in Composer,
82
- then **Deploy** so Edge 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
+
102
+ `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.
103
+
104
+ **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 }`.
105
+
106
+ ### Two ways to read a field
107
+
108
+ ```tsx
109
+ // Editable — use an SDK component
110
+ <Field field={fields.heading} />
111
+
112
+ // Display only — read .value
113
+ <title>{fields.heading.value}</title>
114
+ ```
115
+
116
+ 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.
117
+
118
+ ```tsx
119
+ export function Card({ fields }: { fields: Fields<{ title: string; link: string; image: ImageValue }> }) {
120
+ return (
121
+ <a href={fields.link.value ?? '#'} aria-label={fields.title.value ?? undefined}>
122
+ <Image field={fields.image} />
123
+ <h3><Field field={fields.title} /></h3>
124
+ </a>
125
+ );
126
+ }
127
+ ```
128
+
129
+ ### SDK field components
130
+
131
+ | Component | Field type | Renders |
132
+ | --- | --- | --- |
133
+ | `<Field>` | `string`, `number` | text node — no wrapper element |
134
+ | `<RichText>` | markdown / rich text | rendered HTML |
135
+ | `<Image>` | `ImageValue` | `<img>` |
136
+
137
+ 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.
138
+
139
+ ```tsx
140
+ <Field field={fields.eyebrow} className="text-xs uppercase" />
141
+ <RichText field={fields.body} className="prose" />
142
+ <Image field={fields.cover} className="rounded-lg" loading="lazy" />
143
+ ```
144
+
145
+ `<Field>` is scalar-only. Passing a list is a type error:
146
+
147
+ ```tsx
148
+ // ✗ Field<string[]> is not assignable to Field<string | number>
149
+ <Field field={fields.tags} />
150
+ ```
151
+
152
+ ### Empty fields
153
+
154
+ An empty field renders **nothing** on the live site and a clickable placeholder in Composer. The SDK never invents default copy.
155
+
156
+ ```tsx
157
+ // A new page, `subheading` not yet filled in:
158
+
159
+ <Field field={fields.subheading} />
160
+ // live: (nothing)
161
+ // Composer: clickable empty placeholder
162
+ ```
163
+
164
+ If you want a default, it's yours to write, and it reads from `.value`:
165
+
166
+ ```tsx
167
+ // ✓ your default, in your code
168
+ <p>{fields.subheading.value ?? 'Built for teams that ship.'}</p>
169
+
170
+ // ✓ hide the whole component when the key field is empty
171
+ export function Banner({ fields }: { fields: Fields<{ message: string }> }) {
172
+ if (!fields.message.value) return null;
173
+ return <div className="banner"><Field field={fields.message} /></div>;
174
+ }
175
+ ```
176
+
177
+ 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.
178
+
179
+ ### Wrapper elements
180
+
181
+ `<Field>` renders a text node, not an element, so you supply the tag:
182
+
183
+ ```tsx
184
+ // ✓
185
+ <h1><Field field={fields.heading} /></h1>
186
+ <p className="lead"><Field field={fields.intro} /></p>
187
+
188
+ // ✗ — Field has no `as` prop; put the element around it
189
+ <Field as="h1" field={fields.heading} />
190
+ ```
191
+
192
+ If the wrapper should disappear when the field is empty, check `.value`:
193
+
194
+ ```tsx
195
+ {fields.intro.value ? (
196
+ <p className="lead"><Field field={fields.intro} /></p>
197
+ ) : null}
198
+ ```
199
+
200
+ ### Computed values
201
+
202
+ 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:
203
+
204
+ ```tsx
205
+ // Show a formatted price but edit the raw number
206
+ <Field
207
+ value={formatPrice(fields.price.value)}
208
+ name={fields.price.name}
209
+ />
210
+ ```
211
+
212
+ `name` is only accepted alongside `value`. This is the one place you write it.
213
+
214
+ ```tsx
215
+ // ✗ — redundant, and a type error
216
+ <Field field={fields.price} name="price" />
217
+
218
+ // ✗ — value without name has nothing to bind to
219
+ <Field value={formatPrice(fields.price.value)} />
220
+ ```
221
+
222
+ ### Rendering differently in Composer
223
+
224
+ 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.
225
+
226
+ `useInComposer()` tells you which mode you're in:
227
+
228
+ ```tsx
229
+ import { Field, Image, useInComposer } from '@amplifyup/sdk/react';
230
+
231
+ export function HeroBackground({ fields }: { fields: Fields<{ heading: string; image: ImageValue }> }) {
232
+ const inComposer = useInComposer();
233
+
234
+ return (
235
+ <section
236
+ className="hero"
237
+ style={{ backgroundImage: `url(${fields.image.value?.url ?? ''})` }}
238
+ >
239
+ {/* Live: image is a CSS background. Composer: also render it so it's clickable. */}
240
+ {inComposer ? <Image field={fields.image} className="hidden" /> : null}
241
+ <h1><Field field={fields.heading} /></h1>
242
+ </section>
243
+ );
244
+ }
245
+ ```
246
+
247
+ Other common cases:
248
+
249
+ ```tsx
250
+ // Attribute on the live site, editable in Composer
251
+ const inComposer = useInComposer();
252
+ return (
253
+ <>
254
+ <button aria-label={fields.label.value ?? undefined}>
255
+ <Icon />
256
+ {inComposer ? <Field field={fields.label} /> : null}
257
+ </button>
258
+ </>
259
+ );
260
+ ```
261
+
262
+ ```tsx
263
+ // Component that hides itself when empty on live, but stays visible for authors
264
+ export function Announcement({ fields }: { fields: Fields<{ message: string }> }) {
265
+ const inComposer = useInComposer();
266
+ if (!fields.message.value && !inComposer) return null;
267
+ return <div className="announcement"><Field field={fields.message} /></div>;
268
+ }
269
+ ```
270
+
271
+ ```tsx
272
+ // Interaction that fights the canvas — turn it off in Composer
273
+ export function Carousel({ fields }) {
274
+ const inComposer = useInComposer();
275
+ return (
276
+ <Collection field={fields.slides}>
277
+ {(slide) => <Slide slide={slide} autoplay={!inComposer} />}
278
+ </Collection>
279
+ );
280
+ }
281
+ ```
282
+
283
+ Rules of thumb:
284
+
285
+ - 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>` / `<Collection>` in Composer mode, even if the live site reads `.value`.
286
+ - **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.
287
+ - **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`.
288
+ - **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.
289
+
290
+ ```tsx
291
+ // ✗ — heading is visible on live; no reason to fork
292
+ {inComposer ? <Field field={fields.heading} /> : fields.heading.value}
293
+
294
+ // ✓
295
+ <Field field={fields.heading} />
296
+ ```
297
+
298
+ ---
299
+
300
+ ## Lists
301
+
302
+ There are two kinds of list field and they are handled differently. Check your schema to know which you have.
303
+
304
+ | Shape | Schema | What it holds | In your component | Edited in Composer via |
305
+ | --- | --- | --- | --- | --- |
306
+ | **Reference list** | `array` + `entity` `of` | Linked entries — a query or a curated set of documents | Map `fields.posts.value` — plain rows | Props panel (pick, reorder) |
307
+ | **Repeater** | `array` + `of: 'json'` | Inline-authored items (slides, cards, FAQs) | `<Collection>` + `<Field>` on each item | Canvas (add, remove, reorder, click to edit) |
308
+
309
+ Scalar lists (tags, multi-select) are plain `.value` arrays edited in the props panel — same as reference lists.
310
+
311
+ ### Reference lists
312
+
313
+ Rows are plain objects, not envelopes. Read them directly:
314
+
315
+ ```tsx
316
+ export function LatestPosts({ fields }: { fields: Fields<{ heading: string; posts: Post[] }> }) {
317
+ const posts = fields.posts.value ?? [];
318
+ return (
319
+ <section>
320
+ <h2><Field field={fields.heading} /></h2>
321
+ <ul>
322
+ {posts.map((post) => (
323
+ <li key={post.id}>
324
+ <img src={post.mainImage?.url} alt={post.mainImage?.alt ?? ''} />
325
+ <a href={`/blog/${post.slug}`}>{post.title}</a>
326
+ </li>
327
+ ))}
328
+ </ul>
329
+ </section>
330
+ );
331
+ }
332
+ ```
333
+
334
+ ```tsx
335
+ // ✗ — reference rows aren't editable inline; there's nothing for Field to bind to
336
+ {posts.map((post) => <Field field={post.title} />)}
337
+
338
+ // ✗ — Collection is for repeaters
339
+ <Collection field={fields.posts}>…</Collection>
340
+ ```
341
+
342
+ The list itself (which posts, in what order) is edited in the props panel. When the component is selected on the canvas, the component outline is the affordance — you don't need to add one.
343
+
344
+ ### Repeaters
345
+
346
+ Items **are** enveloped. `fields.slides.value[0].title` is `{ value, name: 'slides.0.title' }`. Render through `<Collection>`:
347
+
348
+ ```tsx
349
+ import { Collection, Field, Image } from '@amplifyup/sdk/react';
350
+
351
+ export function Slideshow({
352
+ fields,
353
+ }: {
354
+ fields: Fields<{ slides: Array<{ title: string; caption: string; image: ImageValue }> }>;
355
+ }) {
356
+ return (
357
+ <Collection field={fields.slides}>
358
+ {(slide, i) => (
359
+ <figure className="slide" data-index={i}>
360
+ <Image field={slide.image} />
361
+ <figcaption>
362
+ <h2><Field field={slide.title} /></h2>
363
+ <Field field={slide.caption} className="text-sm" />
364
+ </figcaption>
365
+ </figure>
366
+ )}
367
+ </Collection>
368
+ );
369
+ }
370
+ ```
371
+
372
+ On the live site `Collection` renders your children and nothing else — no wrapper element. In Composer it adds an empty-state placeholder and add / remove / reorder controls.
373
+
374
+ Need a wrapping `<ul>`? Put it outside:
375
+
376
+ ```tsx
377
+ <ul className="faq">
378
+ <Collection field={fields.questions}>
379
+ {(q) => (
380
+ <li>
381
+ <h3><Field field={q.question} /></h3>
382
+ <RichText field={q.answer} />
383
+ </li>
384
+ )}
385
+ </Collection>
386
+ </ul>
387
+ ```
388
+
389
+ ```tsx
390
+ // ✗ — mapping .value skips the canvas chrome; items won't be addable in Composer
391
+ {fields.slides.value.map((slide) => <Field field={slide.title} />)}
392
+ ```
393
+
394
+ ### Mixing a list with a runtime query
395
+
396
+ Search or "load more" results come from `queryContent`, which returns plain rows. Your reference list is also plain rows, so they share a renderer:
397
+
398
+ ```tsx
399
+ const posts = hits ?? fields.posts.value ?? [];
400
+ return <PostGrid posts={posts} />;
401
+ ```
402
+
403
+ No SDK component is involved on either path.
404
+
405
+ ---
406
+
407
+ ## Slots
408
+
409
+ A slot is a region where authors drop other components. Render it with `<Slot>`:
410
+
411
+ ```tsx
412
+ import { Field, Slot } from '@amplifyup/sdk/react';
413
+
414
+ export function TwoColumn({ fields, slots }) {
415
+ return (
416
+ <div className="grid grid-cols-2">
417
+ <div><Slot name="left" slots={slots} /></div>
418
+ <div><Slot name="right" slots={slots} /></div>
419
+ </div>
420
+ );
421
+ }
422
+ ```
423
+
424
+ Slots hold components, not fields — nothing in a slot is read through `fields`.
425
+
426
+ ---
427
+
428
+ ## Do / Don't
429
+
430
+ | Do | Don't |
431
+ | --- | --- |
432
+ | `<Field field={fields.heading} />` | `<Field field={fields.heading} name="heading" />` |
433
+ | `{fields.heading.value}` for non-visible uses | `{fields.heading}` as a JSX child (it's an object) |
434
+ | `{fields.x.value ?? 'default'}` in your code | expect the SDK to supply default copy |
435
+ | `if (!fields.x.value) return null` to hide a component | wrap `Field` in conditional logic that hides the field in Composer |
436
+ | `<h1><Field … /></h1>` | look for an `as` / `wrapper` prop |
437
+ | `fields.posts.value.map(…)` for reference lists | `<Field>` or `<Collection>` on reference rows |
438
+ | `<Collection>` for repeaters | `fields.slides.value.map(…)` for repeaters |
439
+ | `value` + `name` together for computed output | `value` alone, or `name` alone |
440
+ | `<Image field={fields.cover} />` | `<img src={fields.cover.value.url} />` when it should be editable |
441
+ | `queryContent` rows as plain data | treat query rows as `fields` |
442
+ | `useInComposer()` to expose a field the live markup can't (CSS background, attribute, hidden panel) | fork the whole component's return on `inComposer` |
443
+ | `if (!x.value && !inComposer) return null` | `if (!x.value) return null` — authors can't click what isn't rendered |
444
+
445
+ ---
446
+
447
+ ## Types
448
+
449
+ ```ts
450
+ import type { Field, Fields, ImageValue } from '@amplifyup/sdk/react';
451
+
452
+ type Field<T> = { value: T | null; name: string };
453
+
454
+ type Fields<T> = { [K in keyof T]: /* Field<T[K]>, recursing into objects and repeater arrays */ };
455
+ ```
456
+
457
+ Declare the schema shape once on the component and let inference do the rest:
458
+
459
+ ```ts
460
+ type HeroFields = Fields<{
461
+ eyebrow: string;
462
+ heading: string;
463
+ body: string; // markdown → use <RichText>
464
+ image: ImageValue; // → use <Image>
465
+ ctas: Array<{ label: string; href: string }>; // repeater
466
+ related: Post[]; // reference list — rows are plain Post
467
+ }>;
468
+
469
+ export function Hero({ fields }: { fields: HeroFields }) { … }
470
+ ```
471
+
472
+ `fields.heading.value` is `string | null`. `fields.image.value` is `ImageValue | null`. `fields.ctas.value[0].label` is `Field<string>`. `fields.related.value[0]` is `Post`.
473
+
474
+ ---
83
475
 
84
476
  ## Content sources
85
477
 
@@ -90,6 +482,8 @@ then **Deploy** so Edge can serve the page.
90
482
  | Composer preview | Orchestrator (Control Plane) layout provider | `?preview=true` / Composer iframe |
91
483
  | Runtime queries | Edge `/v1/query` | `queryContent` — search, load more, pickers |
92
484
 
485
+ 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.
486
+
93
487
  ## Pattern pages (From page)
94
488
 
95
489
  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.
@@ -111,7 +505,7 @@ Components using Composer **From page** require `pageContext` on the provider (o
111
505
 
112
506
  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.
113
507
 
114
- 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`).
508
+ 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`).
115
509
 
116
510
  ## Runtime queries
117
511
 
@@ -138,7 +532,7 @@ const hits = await queryContent({
138
532
  });
139
533
  ```
140
534
 
141
- 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).
535
+ Only entities published with that route can be queried, and visitors only see published content. Results are plain rows — not `fields` envelopes, not canvas-editable. Also available from `@amplifyup/sdk/server`. See the [runtime queries guide](../../docs/sdk-site-setup.md#8-runtime-content-queries).
142
536
 
143
537
  ## Track events
144
538
 
@@ -162,4 +556,4 @@ React context: `useAmplifyUp()` → `{ pageConfig, loading, error, source, meta
162
556
 
163
557
  ## License
164
558
 
165
- MIT
559
+ MIT
@@ -0,0 +1,93 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { P as PageConfig } from './types-Db_eLrfU.js';
5
+
6
+ /**
7
+ * Field envelopes — same shape in published, draft, and Composer edit.
8
+ * `{ value, name }` where `name` is the full dot path (`hero.image`).
9
+ * Produced once at the Edge resolve boundary (`fetchEdgeResolve`).
10
+ */
11
+ declare const FIELDS_BRAND: unique symbol;
12
+ type ImageValue = {
13
+ url: string;
14
+ alt?: string;
15
+ width?: number;
16
+ height?: number;
17
+ };
18
+ type Field<T> = {
19
+ value: T;
20
+ name: string;
21
+ };
22
+ /** Portable entry ref after normalize (`id` required). */
23
+ type PortableRef = {
24
+ id: string;
25
+ type?: string;
26
+ };
27
+ /**
28
+ * Repeater envelope: each item is a `Fields` bag.
29
+ * Paths are dotted indexes (`slides.0.title`).
30
+ */
31
+ type CollectionField<T extends Record<string, unknown>> = {
32
+ value: Array<Fields<T>>;
33
+ name: string;
34
+ };
35
+ type IsPlainObject<T> = T extends object ? T extends readonly unknown[] ? false : T extends ImageValue ? false : true : false;
36
+ /** Resolved / authored entry refs carry `id` (see `stampPlatformIdentity`). */
37
+ type IsEntryRefItem<T> = T extends {
38
+ id: string;
39
+ } ? true : false;
40
+ type AsRecord<T> = T extends Record<string, unknown> ? T : Record<string, unknown>;
41
+ type FieldsProp<T> = NonNullable<T> extends ImageValue ? Field<T | null> : NonNullable<T> extends readonly (infer U)[] ? IsEntryRefItem<U> extends true ? Field<T> : IsPlainObject<U> extends true ? CollectionField<AsRecord<U>> : Field<T> : IsPlainObject<NonNullable<T>> extends true ? Fields<NonNullable<T>> : Field<T>;
42
+ type Fields<T> = {
43
+ readonly [FIELDS_BRAND]: true;
44
+ } & {
45
+ [K in keyof T]-?: FieldsProp<T[K]>;
46
+ };
47
+ /** Props passed into site components. Raw CMS bags are not assignable. */
48
+ type LayoutComponentProps<T extends Record<string, unknown> = Record<string, unknown>> = {
49
+ fields: Fields<T>;
50
+ };
51
+ declare function isBrandedFields(value: unknown): value is Fields<Record<string, unknown>>;
52
+ declare function isFieldEnvelope(value: unknown): value is Field<unknown>;
53
+ /**
54
+ * Wrap a props bag as envelopes. Already-enveloped bags are returned as-is.
55
+ * Nested plain objects become nested bags; portable images stay a leaf.
56
+ */
57
+ declare function toFieldEnvelopes<T extends Record<string, unknown>>(source: T | null | undefined, prefix?: string): Fields<T>;
58
+
59
+ interface AmplifyRendererProps {
60
+ /**
61
+ * Component renderer function that maps componentId to React component
62
+ * This is site-specific and must be provided by the developer
63
+ */
64
+ renderComponent: (componentId: string, props: LayoutComponentProps, slots?: Record<string, ReactNode>, context?: {
65
+ layoutNodeId: string;
66
+ }) => ReactNode;
67
+ /**
68
+ * Optional page config for server-side rendering (SSR/SSG)
69
+ * If provided, the component will use this instead of fetching client-side
70
+ */
71
+ pageConfig?: PageConfig;
72
+ /**
73
+ * Optional loading component
74
+ */
75
+ loadingComponent?: ReactNode;
76
+ /**
77
+ * Optional error component
78
+ */
79
+ errorComponent?: (error: string) => ReactNode;
80
+ /**
81
+ * Optional empty state component (when no content is configured)
82
+ */
83
+ emptyComponent?: ReactNode;
84
+ /**
85
+ * Force Composer page-slot wrapper (avoids SSR miss when window isn't available yet).
86
+ * When true, root components stay under [data-slot-source="page"] so hierarchy can climb to Page.
87
+ */
88
+ composerPreview?: boolean;
89
+ }
90
+ declare function AmplifyRendererInner({ renderComponent, pageConfig: serverPageConfig, loadingComponent, errorComponent, emptyComponent, composerPreview, }: AmplifyRendererProps): react_jsx_runtime.JSX.Element | null;
91
+ declare const AmplifyRenderer: react.MemoExoticComponent<typeof AmplifyRendererInner>;
92
+
93
+ export { AmplifyRenderer as A, type CollectionField as C, type Field as F, type ImageValue as I, type LayoutComponentProps as L, type PortableRef as P, type Fields as a, isBrandedFields as b, isFieldEnvelope as i, toFieldEnvelopes as t };
@@ -0,0 +1,93 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { P as PageConfig } from './types-Db_eLrfU.mjs';
5
+
6
+ /**
7
+ * Field envelopes — same shape in published, draft, and Composer edit.
8
+ * `{ value, name }` where `name` is the full dot path (`hero.image`).
9
+ * Produced once at the Edge resolve boundary (`fetchEdgeResolve`).
10
+ */
11
+ declare const FIELDS_BRAND: unique symbol;
12
+ type ImageValue = {
13
+ url: string;
14
+ alt?: string;
15
+ width?: number;
16
+ height?: number;
17
+ };
18
+ type Field<T> = {
19
+ value: T;
20
+ name: string;
21
+ };
22
+ /** Portable entry ref after normalize (`id` required). */
23
+ type PortableRef = {
24
+ id: string;
25
+ type?: string;
26
+ };
27
+ /**
28
+ * Repeater envelope: each item is a `Fields` bag.
29
+ * Paths are dotted indexes (`slides.0.title`).
30
+ */
31
+ type CollectionField<T extends Record<string, unknown>> = {
32
+ value: Array<Fields<T>>;
33
+ name: string;
34
+ };
35
+ type IsPlainObject<T> = T extends object ? T extends readonly unknown[] ? false : T extends ImageValue ? false : true : false;
36
+ /** Resolved / authored entry refs carry `id` (see `stampPlatformIdentity`). */
37
+ type IsEntryRefItem<T> = T extends {
38
+ id: string;
39
+ } ? true : false;
40
+ type AsRecord<T> = T extends Record<string, unknown> ? T : Record<string, unknown>;
41
+ type FieldsProp<T> = NonNullable<T> extends ImageValue ? Field<T | null> : NonNullable<T> extends readonly (infer U)[] ? IsEntryRefItem<U> extends true ? Field<T> : IsPlainObject<U> extends true ? CollectionField<AsRecord<U>> : Field<T> : IsPlainObject<NonNullable<T>> extends true ? Fields<NonNullable<T>> : Field<T>;
42
+ type Fields<T> = {
43
+ readonly [FIELDS_BRAND]: true;
44
+ } & {
45
+ [K in keyof T]-?: FieldsProp<T[K]>;
46
+ };
47
+ /** Props passed into site components. Raw CMS bags are not assignable. */
48
+ type LayoutComponentProps<T extends Record<string, unknown> = Record<string, unknown>> = {
49
+ fields: Fields<T>;
50
+ };
51
+ declare function isBrandedFields(value: unknown): value is Fields<Record<string, unknown>>;
52
+ declare function isFieldEnvelope(value: unknown): value is Field<unknown>;
53
+ /**
54
+ * Wrap a props bag as envelopes. Already-enveloped bags are returned as-is.
55
+ * Nested plain objects become nested bags; portable images stay a leaf.
56
+ */
57
+ declare function toFieldEnvelopes<T extends Record<string, unknown>>(source: T | null | undefined, prefix?: string): Fields<T>;
58
+
59
+ interface AmplifyRendererProps {
60
+ /**
61
+ * Component renderer function that maps componentId to React component
62
+ * This is site-specific and must be provided by the developer
63
+ */
64
+ renderComponent: (componentId: string, props: LayoutComponentProps, slots?: Record<string, ReactNode>, context?: {
65
+ layoutNodeId: string;
66
+ }) => ReactNode;
67
+ /**
68
+ * Optional page config for server-side rendering (SSR/SSG)
69
+ * If provided, the component will use this instead of fetching client-side
70
+ */
71
+ pageConfig?: PageConfig;
72
+ /**
73
+ * Optional loading component
74
+ */
75
+ loadingComponent?: ReactNode;
76
+ /**
77
+ * Optional error component
78
+ */
79
+ errorComponent?: (error: string) => ReactNode;
80
+ /**
81
+ * Optional empty state component (when no content is configured)
82
+ */
83
+ emptyComponent?: ReactNode;
84
+ /**
85
+ * Force Composer page-slot wrapper (avoids SSR miss when window isn't available yet).
86
+ * When true, root components stay under [data-slot-source="page"] so hierarchy can climb to Page.
87
+ */
88
+ composerPreview?: boolean;
89
+ }
90
+ declare function AmplifyRendererInner({ renderComponent, pageConfig: serverPageConfig, loadingComponent, errorComponent, emptyComponent, composerPreview, }: AmplifyRendererProps): react_jsx_runtime.JSX.Element | null;
91
+ declare const AmplifyRenderer: react.MemoExoticComponent<typeof AmplifyRendererInner>;
92
+
93
+ export { AmplifyRenderer as A, type CollectionField as C, type Field as F, type ImageValue as I, type LayoutComponentProps as L, type PortableRef as P, type Fields as a, isBrandedFields as b, isFieldEnvelope as i, toFieldEnvelopes as t };
@@ -1,40 +1,4 @@
1
- import * as React from 'react';
2
- import { ReactNode } from 'react';
3
- import * as react_jsx_runtime from 'react/jsx-runtime';
4
- import { P as PageConfig } from './types-Db_eLrfU.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: Record<string, any>, 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
+ import 'react';
2
+ import 'react/jsx-runtime';
3
+ export { A as AmplifyRenderer } from './AmplifyRenderer-TawbUIKZ.mjs';
4
+ import './types-Db_eLrfU.mjs';