@amplifyup/sdk 0.1.60 → 0.1.62

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
@@ -65,21 +65,26 @@ export default function Page() {
65
65
 
66
66
  ```tsx
67
67
  import { Field, Slot } 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
+ 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.
83
88
 
84
89
  ## Content sources
85
90
 
@@ -138,7 +143,7 @@ const hits = await queryContent({
138
143
  });
139
144
  ```
140
145
 
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).
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).
142
147
 
143
148
  ## Track events
144
149
 
@@ -0,0 +1,74 @@
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
+ type IsPlainObject<T> = T extends object ? T extends readonly unknown[] ? false : T extends ImageValue ? false : true : false;
23
+ type Fields<T> = {
24
+ readonly [FIELDS_BRAND]: true;
25
+ } & {
26
+ [K in keyof T]-?: NonNullable<T[K]> extends ImageValue ? Field<T[K] | null> : IsPlainObject<NonNullable<T[K]>> extends true ? Fields<NonNullable<T[K]>> : Field<T[K]>;
27
+ };
28
+ /** Props passed into site components. Raw CMS bags are not assignable. */
29
+ type LayoutComponentProps<T extends Record<string, unknown> = Record<string, unknown>> = {
30
+ fields: Fields<T>;
31
+ };
32
+ declare function isBrandedFields(value: unknown): value is Fields<Record<string, unknown>>;
33
+ declare function isFieldEnvelope(value: unknown): value is Field<unknown>;
34
+ /**
35
+ * Wrap a props bag as envelopes. Already-enveloped bags are returned as-is.
36
+ * Nested plain objects become nested bags; portable images stay a leaf.
37
+ */
38
+ declare function toFieldEnvelopes<T extends Record<string, unknown>>(source: T | null | undefined, prefix?: string): Fields<T>;
39
+
40
+ interface AmplifyRendererProps {
41
+ /**
42
+ * Component renderer function that maps componentId to React component
43
+ * This is site-specific and must be provided by the developer
44
+ */
45
+ renderComponent: (componentId: string, props: LayoutComponentProps, slots?: Record<string, ReactNode>, context?: {
46
+ layoutNodeId: string;
47
+ }) => ReactNode;
48
+ /**
49
+ * Optional page config for server-side rendering (SSR/SSG)
50
+ * If provided, the component will use this instead of fetching client-side
51
+ */
52
+ pageConfig?: PageConfig;
53
+ /**
54
+ * Optional loading component
55
+ */
56
+ loadingComponent?: ReactNode;
57
+ /**
58
+ * Optional error component
59
+ */
60
+ errorComponent?: (error: string) => ReactNode;
61
+ /**
62
+ * Optional empty state component (when no content is configured)
63
+ */
64
+ emptyComponent?: ReactNode;
65
+ /**
66
+ * Force Composer page-slot wrapper (avoids SSR miss when window isn't available yet).
67
+ * When true, root components stay under [data-slot-source="page"] so hierarchy can climb to Page.
68
+ */
69
+ composerPreview?: boolean;
70
+ }
71
+ declare function AmplifyRendererInner({ renderComponent, pageConfig: serverPageConfig, loadingComponent, errorComponent, emptyComponent, composerPreview, }: AmplifyRendererProps): react_jsx_runtime.JSX.Element | null;
72
+ declare const AmplifyRenderer: React.MemoExoticComponent<typeof AmplifyRendererInner>;
73
+
74
+ export { AmplifyRenderer as A, type Field as F, type ImageValue as I, type LayoutComponentProps as L, isBrandedFields as a, type Fields as b, isFieldEnvelope as i, toFieldEnvelopes as t };
@@ -0,0 +1,74 @@
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
+ type IsPlainObject<T> = T extends object ? T extends readonly unknown[] ? false : T extends ImageValue ? false : true : false;
23
+ type Fields<T> = {
24
+ readonly [FIELDS_BRAND]: true;
25
+ } & {
26
+ [K in keyof T]-?: NonNullable<T[K]> extends ImageValue ? Field<T[K] | null> : IsPlainObject<NonNullable<T[K]>> extends true ? Fields<NonNullable<T[K]>> : Field<T[K]>;
27
+ };
28
+ /** Props passed into site components. Raw CMS bags are not assignable. */
29
+ type LayoutComponentProps<T extends Record<string, unknown> = Record<string, unknown>> = {
30
+ fields: Fields<T>;
31
+ };
32
+ declare function isBrandedFields(value: unknown): value is Fields<Record<string, unknown>>;
33
+ declare function isFieldEnvelope(value: unknown): value is Field<unknown>;
34
+ /**
35
+ * Wrap a props bag as envelopes. Already-enveloped bags are returned as-is.
36
+ * Nested plain objects become nested bags; portable images stay a leaf.
37
+ */
38
+ declare function toFieldEnvelopes<T extends Record<string, unknown>>(source: T | null | undefined, prefix?: string): Fields<T>;
39
+
40
+ interface AmplifyRendererProps {
41
+ /**
42
+ * Component renderer function that maps componentId to React component
43
+ * This is site-specific and must be provided by the developer
44
+ */
45
+ renderComponent: (componentId: string, props: LayoutComponentProps, slots?: Record<string, ReactNode>, context?: {
46
+ layoutNodeId: string;
47
+ }) => ReactNode;
48
+ /**
49
+ * Optional page config for server-side rendering (SSR/SSG)
50
+ * If provided, the component will use this instead of fetching client-side
51
+ */
52
+ pageConfig?: PageConfig;
53
+ /**
54
+ * Optional loading component
55
+ */
56
+ loadingComponent?: ReactNode;
57
+ /**
58
+ * Optional error component
59
+ */
60
+ errorComponent?: (error: string) => ReactNode;
61
+ /**
62
+ * Optional empty state component (when no content is configured)
63
+ */
64
+ emptyComponent?: ReactNode;
65
+ /**
66
+ * Force Composer page-slot wrapper (avoids SSR miss when window isn't available yet).
67
+ * When true, root components stay under [data-slot-source="page"] so hierarchy can climb to Page.
68
+ */
69
+ composerPreview?: boolean;
70
+ }
71
+ declare function AmplifyRendererInner({ renderComponent, pageConfig: serverPageConfig, loadingComponent, errorComponent, emptyComponent, composerPreview, }: AmplifyRendererProps): react_jsx_runtime.JSX.Element | null;
72
+ declare const AmplifyRenderer: React.MemoExoticComponent<typeof AmplifyRendererInner>;
73
+
74
+ export { AmplifyRenderer as A, type Field as F, type ImageValue as I, type LayoutComponentProps as L, isBrandedFields as a, type Fields 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-W7eqltSj.mjs';
4
+ import './types-Db_eLrfU.mjs';
@@ -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.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: 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-BQ8-H2H4.js';
4
+ import './types-Db_eLrfU.js';
@@ -407,6 +407,106 @@ function isComposerPreview() {
407
407
  return false;
408
408
  }
409
409
 
410
+ // src/field-envelope.ts
411
+ var FIELDS_BRAND = Symbol("amplifyup.fields");
412
+ function isBrandedFields(value) {
413
+ return !!value && typeof value === "object" && FIELDS_BRAND in value;
414
+ }
415
+ function brandFields(bag) {
416
+ if (isBrandedFields(bag)) return bag;
417
+ Object.defineProperty(bag, FIELDS_BRAND, {
418
+ value: true,
419
+ enumerable: false,
420
+ configurable: false
421
+ });
422
+ return bag;
423
+ }
424
+ function isFieldEnvelope(value) {
425
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
426
+ const rec = value;
427
+ return typeof rec.name === "string" && "value" in rec && !("url" in rec);
428
+ }
429
+ function isPortableImageShape(value) {
430
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
431
+ return "url" in value;
432
+ }
433
+ function joinPath(prefix, key) {
434
+ return prefix ? `${prefix}.${key}` : key;
435
+ }
436
+ function emptyValue(raw) {
437
+ if (raw === void 0) return null;
438
+ return raw;
439
+ }
440
+ function toFieldEnvelopes(source, prefix = "") {
441
+ const input = source && typeof source === "object" && !Array.isArray(source) ? source : {};
442
+ if (isBrandedFields(input)) {
443
+ return input;
444
+ }
445
+ const out = {};
446
+ for (const [key, raw] of Object.entries(input)) {
447
+ const path = joinPath(prefix, key);
448
+ if (isFieldEnvelope(raw)) {
449
+ out[key] = { value: raw.value ?? null, name: raw.name || path };
450
+ continue;
451
+ }
452
+ if (raw && typeof raw === "object" && !Array.isArray(raw) && !isPortableImageShape(raw) && !raw.$$typeof) {
453
+ out[key] = toFieldEnvelopes(raw, path);
454
+ continue;
455
+ }
456
+ out[key] = { value: emptyValue(raw), name: path };
457
+ }
458
+ return brandFields(out);
459
+ }
460
+ function attachFieldEnvelopes(props) {
461
+ const raw = props && typeof props === "object" ? { ...props } : {};
462
+ if (isBrandedFields(raw.fields)) return raw;
463
+ const { fields: existing, ...content } = raw;
464
+ const source = existing && typeof existing === "object" && !isFieldEnvelope(existing) ? existing : content;
465
+ return { ...raw, fields: toFieldEnvelopes(source) };
466
+ }
467
+ function walkLayoutNodes(nodes) {
468
+ if (!Array.isArray(nodes)) return;
469
+ for (const node of nodes) {
470
+ if (!node || typeof node !== "object") continue;
471
+ const rec = node;
472
+ rec.props = attachFieldEnvelopes(rec.props);
473
+ for (const slot of rec.slots || []) {
474
+ walkLayoutNodes(slot.children);
475
+ }
476
+ }
477
+ }
478
+ function envelopePageConfig(page) {
479
+ if (!page || typeof page !== "object") return page ?? null;
480
+ const tree = page.layoutTree;
481
+ if (Array.isArray(tree)) walkLayoutNodes(tree);
482
+ return page;
483
+ }
484
+ function collectFieldPatches(props) {
485
+ const out = [];
486
+ const walk = (obj, prefix) => {
487
+ for (const [key, raw] of Object.entries(obj)) {
488
+ if (key === "fields" && raw && typeof raw === "object" && !Array.isArray(raw) && !isFieldEnvelope(raw)) {
489
+ walk(raw, prefix);
490
+ continue;
491
+ }
492
+ const path = joinPath(prefix, key);
493
+ if (isFieldEnvelope(raw)) {
494
+ out.push({ name: raw.name || path, value: raw.value });
495
+ continue;
496
+ }
497
+ if (raw && typeof raw === "object" && !Array.isArray(raw) && !isPortableImageShape(raw) && !raw.$$typeof) {
498
+ walk(raw, path);
499
+ continue;
500
+ }
501
+ out.push({ name: path, value: emptyValue(raw) });
502
+ }
503
+ };
504
+ if (props && typeof props === "object" && !Array.isArray(props)) {
505
+ walk(props, "");
506
+ }
507
+ return out;
508
+ }
509
+
410
510
  // src/page-context.ts
411
511
  var defaultLog = {
412
512
  warn: (...args) => {
@@ -555,7 +655,70 @@ function mergeEventContext(properties, attached) {
555
655
  ...attached.pageRoute ? { pageRoute: attached.pageRoute } : {}
556
656
  };
557
657
  }
658
+ var PAGE_RESOURCE_MARKER = "__amplifyupPageResource";
659
+ function isPageResourceSentinel(value) {
660
+ return !!value && typeof value === "object" && !Array.isArray(value) && typeof value[PAGE_RESOURCE_MARKER] === "string";
661
+ }
662
+ function getByDotPath(source, path) {
663
+ if (!path) return source;
664
+ const parts = path.split(".").filter(Boolean);
665
+ let cur = source;
666
+ for (const part of parts) {
667
+ if (cur == null || typeof cur !== "object" || Array.isArray(cur)) {
668
+ return void 0;
669
+ }
670
+ cur = cur[part];
671
+ }
672
+ return cur;
673
+ }
674
+ function hydratePageResourceValue(value, fields) {
675
+ if (!isPageResourceSentinel(value)) return value;
676
+ if (!fields) return null;
677
+ if (value.path) return getByDotPath(fields, value.path) ?? null;
678
+ return fields;
679
+ }
680
+ function hydrateFieldsBag(bag, fields) {
681
+ const out = {};
682
+ for (const [key, val] of Object.entries(bag)) {
683
+ if (val && typeof val === "object" && !Array.isArray(val) && typeof val.name === "string" && "value" in val && !("url" in val)) {
684
+ const env = val;
685
+ out[key] = {
686
+ name: env.name,
687
+ value: isPageResourceSentinel(env.value) ? hydratePageResourceValue(env.value, fields) : env.value
688
+ };
689
+ continue;
690
+ }
691
+ if (val && typeof val === "object" && !Array.isArray(val) && !("url" in val)) {
692
+ out[key] = hydrateFieldsBag(val, fields);
693
+ continue;
694
+ }
695
+ out[key] = isPageResourceSentinel(val) ? hydratePageResourceValue(val, fields) : val;
696
+ }
697
+ return out;
698
+ }
699
+ function hydratePageResourceProps(props, fields) {
700
+ const source = props && typeof props === "object" ? props : {};
701
+ const out = { ...source };
702
+ if (out.fields && typeof out.fields === "object" && !Array.isArray(out.fields)) {
703
+ const next = hydrateFieldsBag(out.fields, fields);
704
+ out.fields = isBrandedFields(out.fields) ? brandFields(next) : next;
705
+ }
706
+ for (const [key, val] of Object.entries(out)) {
707
+ if (key === "fields") continue;
708
+ if (isPageResourceSentinel(val)) {
709
+ out[key] = hydratePageResourceValue(val, fields);
710
+ }
711
+ }
712
+ return out;
713
+ }
714
+
715
+ // src/page-resource-runtime.ts
716
+ var pageResourceFields = null;
558
717
  function setPageResourceFields(fields) {
718
+ pageResourceFields = fields && typeof fields === "object" ? fields : null;
719
+ }
720
+ function getPageResourceFields() {
721
+ return pageResourceFields;
559
722
  }
560
723
 
561
724
  // src/debug.ts
@@ -1238,6 +1401,7 @@ var AmplifyUpSDK = class {
1238
1401
  this.resolvedPageData,
1239
1402
  this.resolvedPageRoute
1240
1403
  );
1404
+ setPageResourceFields(null);
1241
1405
  return;
1242
1406
  }
1243
1407
  this.attachedPageContext = applyPageContext(this.pendingPageContext, {
@@ -1666,7 +1830,7 @@ Available Console Commands:
1666
1830
  this.refreshEventTracking();
1667
1831
  }, 800);
1668
1832
  }
1669
- return pageConfig;
1833
+ return envelopePageConfig(pageConfig);
1670
1834
  }
1671
1835
  /**
1672
1836
  * Set up view-level event tracking based on configuration
@@ -3039,31 +3203,66 @@ function getSDK(adapter) {
3039
3203
  var trackContentView = (contentId, contentType, properties) => getSDK().trackContentView(contentId, contentType, properties);
3040
3204
  var refreshEventTracking = () => getSDK().refreshEventTracking();
3041
3205
  getSDK();
3042
- react.createContext(null);
3043
- function useLiveComposerProps(layoutNodeId, props) {
3044
- const [patch, setPatch] = react.useState(null);
3045
- react.useEffect(() => {
3046
- setPatch(null);
3047
- const matchesNode = (nodeId) => {
3048
- if (nodeId && layoutNodeId && String(nodeId) !== String(layoutNodeId)) {
3049
- return false;
3050
- }
3051
- return true;
3206
+
3207
+ // src/composer-store.ts
3208
+ var ComposerFieldStore = class {
3209
+ constructor() {
3210
+ this.values = /* @__PURE__ */ new Map();
3211
+ this.listeners = /* @__PURE__ */ new Map();
3212
+ }
3213
+ get(name) {
3214
+ return this.values.has(name) ? this.values.get(name) : void 0;
3215
+ }
3216
+ set(name, value) {
3217
+ if (!name) return;
3218
+ this.values.set(name, value);
3219
+ const set = this.listeners.get(name);
3220
+ if (!set) return;
3221
+ for (const fn of set) fn();
3222
+ }
3223
+ subscribe(name, fn) {
3224
+ if (!name) return () => {
3052
3225
  };
3226
+ let set = this.listeners.get(name);
3227
+ if (!set) {
3228
+ set = /* @__PURE__ */ new Set();
3229
+ this.listeners.set(name, set);
3230
+ }
3231
+ set.add(fn);
3232
+ return () => {
3233
+ set.delete(fn);
3234
+ if (set.size === 0) this.listeners.delete(name);
3235
+ };
3236
+ }
3237
+ };
3238
+ var ComposerStoreContext = react.createContext(null);
3239
+ function matchesNode(eventNodeId, layoutNodeId) {
3240
+ if (eventNodeId && layoutNodeId && String(eventNodeId) !== String(layoutNodeId)) {
3241
+ return false;
3242
+ }
3243
+ return true;
3244
+ }
3245
+ function ComposerProvider({
3246
+ layoutNodeId,
3247
+ children
3248
+ }) {
3249
+ const storeRef = react.useRef(null);
3250
+ if (!storeRef.current) storeRef.current = new ComposerFieldStore();
3251
+ const store = storeRef.current;
3252
+ react.useEffect(() => {
3053
3253
  const applyField = (fieldName, value, nodeId) => {
3054
3254
  if (!fieldName || typeof fieldName !== "string") return;
3055
- if (!matchesNode(nodeId)) return;
3056
- setPatch((prev) => ({ ...prev || {}, [fieldName]: value }));
3255
+ if (!matchesNode(nodeId, layoutNodeId)) return;
3256
+ store.set(fieldName, value);
3057
3257
  };
3058
3258
  const applyProps = (nextProps, nodeId) => {
3059
3259
  if (!nextProps || typeof nextProps !== "object" || Array.isArray(nextProps)) {
3060
3260
  return;
3061
3261
  }
3062
- if (!matchesNode(nodeId)) return;
3063
- setPatch((prev) => ({
3064
- ...prev || {},
3065
- ...nextProps
3066
- }));
3262
+ if (!matchesNode(nodeId, layoutNodeId)) return;
3263
+ for (const patch of collectFieldPatches(nextProps)) {
3264
+ store.set(patch.name, patch.value);
3265
+ }
3067
3266
  };
3068
3267
  const onPatch = (event) => {
3069
3268
  const detail = event.detail || {};
@@ -3089,9 +3288,19 @@ function useLiveComposerProps(layoutNodeId, props) {
3089
3288
  window.removeEventListener("amplifyup:props-patch", onPropsPatch);
3090
3289
  window.removeEventListener("message", onMessage);
3091
3290
  };
3092
- }, [layoutNodeId]);
3093
- return patch ? { ...props, ...patch } : props;
3291
+ }, [layoutNodeId, store]);
3292
+ return /* @__PURE__ */ jsxRuntime.jsx(ComposerStoreContext.Provider, { value: store, children });
3094
3293
  }
3294
+ function MaybeComposerProvider({
3295
+ enabled,
3296
+ layoutNodeId,
3297
+ children
3298
+ }) {
3299
+ const content = react.useMemo(() => children, [children]);
3300
+ if (!enabled) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: content });
3301
+ return /* @__PURE__ */ jsxRuntime.jsx(ComposerProvider, { layoutNodeId, children });
3302
+ }
3303
+ react.createContext(null);
3095
3304
  var usePathname = null;
3096
3305
  if (typeof window !== "undefined") {
3097
3306
  try {
@@ -3111,9 +3320,19 @@ function useAmplifyUp() {
3111
3320
  return context;
3112
3321
  }
3113
3322
  function nodeRenderProps(node) {
3323
+ const settings = node.settings || {};
3324
+ const hydrated = hydratePageResourceProps(
3325
+ node.props || {},
3326
+ getPageResourceFields()
3327
+ );
3328
+ const fields = hydrated.fields;
3329
+ const branded = isBrandedFields(fields) ? fields : brandFields(
3330
+ fields && typeof fields === "object" ? fields : {}
3331
+ );
3114
3332
  return {
3115
- ...node.settings || {},
3116
- ...node.props || {}
3333
+ ...settings,
3334
+ ...hydrated,
3335
+ fields: branded
3117
3336
  };
3118
3337
  }
3119
3338
  var ComponentRenderBoundary = class extends react.Component {
@@ -3163,17 +3382,24 @@ var ComponentRenderBoundary = class extends react.Component {
3163
3382
  return this.props.children;
3164
3383
  }
3165
3384
  };
3166
- function LiveNodeRender({
3385
+ function NodeWithFields({
3167
3386
  nodeId,
3168
3387
  componentId,
3169
3388
  props,
3170
3389
  slots,
3171
- renderComponent
3390
+ renderComponent,
3391
+ composerPreview
3172
3392
  }) {
3173
- const liveProps = useLiveComposerProps(nodeId, props);
3174
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderComponent(componentId, liveProps, slots, {
3175
- layoutNodeId: nodeId
3176
- }) });
3393
+ return /* @__PURE__ */ jsxRuntime.jsx(
3394
+ MaybeComposerProvider,
3395
+ {
3396
+ enabled: !!(composerPreview || isComposerPreview()),
3397
+ layoutNodeId: nodeId,
3398
+ children: renderComponent(componentId, props, slots, {
3399
+ layoutNodeId: nodeId
3400
+ })
3401
+ }
3402
+ );
3177
3403
  }
3178
3404
  function AmplifyRendererInner({
3179
3405
  renderComponent,
@@ -3529,13 +3755,14 @@ function AmplifyRendererInner({
3529
3755
  componentId: node.componentId,
3530
3756
  nodeId: node.id,
3531
3757
  children: /* @__PURE__ */ jsxRuntime.jsx(
3532
- LiveNodeRender,
3758
+ NodeWithFields,
3533
3759
  {
3534
3760
  nodeId: node.id,
3535
3761
  componentId: node.componentId,
3536
3762
  props: nodeRenderProps(node),
3537
3763
  slots: slotsToPass,
3538
- renderComponent
3764
+ renderComponent,
3765
+ composerPreview
3539
3766
  }
3540
3767
  )
3541
3768
  }