@farcaster/snap 2.6.4 → 2.7.1
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/dist/index.d.ts +1 -0
- package/dist/react/index.d.ts +30 -1
- package/dist/react/index.js +3 -3
- package/dist/react/snap-view-core.d.ts +4 -5
- package/dist/react/snap-view-core.js +93 -57
- package/dist/react/v1/snap-view.d.ts +9 -3
- package/dist/react/v1/snap-view.js +4 -4
- package/dist/react/v2/snap-view.d.ts +9 -3
- package/dist/react/v2/snap-view.js +4 -4
- package/dist/react-native/index.d.ts +7 -3
- package/dist/react-native/index.js +3 -3
- package/dist/react-native/snap-view-core.d.ts +4 -5
- package/dist/react-native/snap-view-core.js +93 -68
- package/dist/react-native/types.d.ts +25 -0
- package/dist/react-native/v1/snap-view.d.ts +12 -4
- package/dist/react-native/v1/snap-view.js +8 -8
- package/dist/react-native/v2/snap-view.d.ts +12 -4
- package/dist/react-native/v2/snap-view.js +8 -8
- package/dist/render-state.d.ts +14 -0
- package/dist/render-state.js +116 -0
- package/dist/ui/catalog.d.ts +27 -0
- package/dist/ui/catalog.js +27 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/react/index.tsx +38 -0
- package/src/react/snap-view-core.tsx +134 -62
- package/src/react/v1/snap-view.tsx +15 -1
- package/src/react/v2/snap-view.tsx +15 -1
- package/src/react-native/index.tsx +22 -2
- package/src/react-native/snap-view-core.tsx +126 -77
- package/src/react-native/types.ts +28 -0
- package/src/react-native/v1/snap-view.tsx +27 -1
- package/src/react-native/v2/snap-view.tsx +27 -1
- package/src/render-state.ts +184 -0
- package/src/ui/catalog.ts +31 -0
|
@@ -20,47 +20,24 @@ import {
|
|
|
20
20
|
PALETTE_DARK_HEX,
|
|
21
21
|
type PaletteColor,
|
|
22
22
|
} from "@farcaster/snap";
|
|
23
|
+
import {
|
|
24
|
+
applyStatePaths,
|
|
25
|
+
buildInitialRenderState,
|
|
26
|
+
cloneSnapRenderState,
|
|
27
|
+
getUnpresentedSnapEffects,
|
|
28
|
+
markSnapEffectsPresented,
|
|
29
|
+
type SnapRenderState,
|
|
30
|
+
} from "../render-state";
|
|
23
31
|
import type { SnapPage, SnapActionHandlers, JsonValue } from "./types";
|
|
24
32
|
|
|
25
|
-
|
|
33
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
34
|
+
return value && typeof value === "object"
|
|
35
|
+
? (value as Record<string, unknown>)
|
|
36
|
+
: {};
|
|
37
|
+
}
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
changes:
|
|
30
|
-
| { path: string; value: unknown }[]
|
|
31
|
-
| Record<string, unknown>
|
|
32
|
-
| null
|
|
33
|
-
| undefined,
|
|
34
|
-
): void {
|
|
35
|
-
if (!changes) return;
|
|
36
|
-
const entries = Array.isArray(changes)
|
|
37
|
-
? changes.map((c) => [c.path, c.value] as const)
|
|
38
|
-
: Object.entries(changes);
|
|
39
|
-
for (const [path, value] of entries) {
|
|
40
|
-
const trimmed = path.startsWith("/") ? path : `/${path}`;
|
|
41
|
-
const parts = trimmed.split("/").filter(Boolean);
|
|
42
|
-
if (parts.length < 2) continue;
|
|
43
|
-
const [top, ...rest] = parts;
|
|
44
|
-
if (top === "inputs") {
|
|
45
|
-
if (typeof model.inputs !== "object" || model.inputs === null) {
|
|
46
|
-
model.inputs = {};
|
|
47
|
-
}
|
|
48
|
-
const inputs = model.inputs as Record<string, unknown>;
|
|
49
|
-
if (rest.length === 1) {
|
|
50
|
-
inputs[rest[0]!] = value;
|
|
51
|
-
}
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (top === "theme") {
|
|
55
|
-
if (typeof model.theme !== "object" || model.theme === null) {
|
|
56
|
-
model.theme = {};
|
|
57
|
-
}
|
|
58
|
-
const theme = model.theme as Record<string, unknown>;
|
|
59
|
-
if (rest.length === 1) {
|
|
60
|
-
theme[rest[0]!] = value;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
39
|
+
function optionalString(value: unknown): string | undefined {
|
|
40
|
+
return value ? String(value) : undefined;
|
|
64
41
|
}
|
|
65
42
|
|
|
66
43
|
function withDefaultElementProps(spec: Spec): Spec {
|
|
@@ -106,6 +83,8 @@ export function SnapViewCoreInner({
|
|
|
106
83
|
handlers,
|
|
107
84
|
loading = false,
|
|
108
85
|
loadingOverlay,
|
|
86
|
+
initialRenderState,
|
|
87
|
+
onRenderStateChange,
|
|
109
88
|
}: {
|
|
110
89
|
snap: SnapPage;
|
|
111
90
|
handlers: SnapActionHandlers;
|
|
@@ -115,6 +94,8 @@ export function SnapViewCoreInner({
|
|
|
115
94
|
* the built-in ActivityIndicator overlay is used. Pass `null` to render nothing.
|
|
116
95
|
*/
|
|
117
96
|
loadingOverlay?: ReactNode;
|
|
97
|
+
initialRenderState?: SnapRenderState;
|
|
98
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
118
99
|
}) {
|
|
119
100
|
const { mode } = useSnapTheme();
|
|
120
101
|
const spec = useMemo(
|
|
@@ -124,28 +105,19 @@ export function SnapViewCoreInner({
|
|
|
124
105
|
const accentHex = resolveAccentHex(snap.theme?.accent, mode);
|
|
125
106
|
|
|
126
107
|
const initialState = useMemo(
|
|
127
|
-
() =>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}),
|
|
135
|
-
[spec, snap.theme],
|
|
108
|
+
() =>
|
|
109
|
+
buildInitialRenderState({
|
|
110
|
+
specState: spec.state,
|
|
111
|
+
initialRenderState,
|
|
112
|
+
themeAccent: snap.theme?.accent,
|
|
113
|
+
}),
|
|
114
|
+
[initialRenderState, spec.state, snap.theme?.accent],
|
|
136
115
|
);
|
|
137
116
|
|
|
138
117
|
const stateRef = useRef<Record<string, unknown>>(initialState);
|
|
139
118
|
|
|
140
119
|
useEffect(() => {
|
|
141
|
-
stateRef.current =
|
|
142
|
-
inputs: {
|
|
143
|
-
...((initialState.inputs ?? {}) as Record<string, unknown>),
|
|
144
|
-
},
|
|
145
|
-
theme: {
|
|
146
|
-
...((initialState.theme ?? {}) as Record<string, unknown>),
|
|
147
|
-
},
|
|
148
|
-
};
|
|
120
|
+
stateRef.current = cloneSnapRenderState(initialState);
|
|
149
121
|
}, [initialState]);
|
|
150
122
|
|
|
151
123
|
useEffect(() => {
|
|
@@ -161,14 +133,58 @@ export function SnapViewCoreInner({
|
|
|
161
133
|
setPageKey((k) => k + 1);
|
|
162
134
|
}, [spec]);
|
|
163
135
|
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
136
|
+
const effectSignature = snap.effects?.join("\u0000") ?? "";
|
|
137
|
+
const snapEffects = useMemo(
|
|
138
|
+
() => (effectSignature ? effectSignature.split("\u0000") : []),
|
|
139
|
+
[effectSignature],
|
|
140
|
+
);
|
|
141
|
+
const showConfetti = snapEffects.includes("confetti");
|
|
142
|
+
const showFireworks = snapEffects.includes("fireworks");
|
|
143
|
+
const [effectRunKeys, setEffectRunKeys] = useState({
|
|
144
|
+
confetti: 0,
|
|
145
|
+
fireworks: 0,
|
|
146
|
+
});
|
|
147
|
+
const onRenderStateChangeRef = useRef(onRenderStateChange);
|
|
168
148
|
useEffect(() => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
149
|
+
onRenderStateChangeRef.current = onRenderStateChange;
|
|
150
|
+
}, [onRenderStateChange]);
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
const effectsToPresent = getUnpresentedSnapEffects(
|
|
153
|
+
stateRef.current,
|
|
154
|
+
snapEffects,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (effectsToPresent.length === 0) {
|
|
158
|
+
setEffectRunKeys((current) => {
|
|
159
|
+
const next = {
|
|
160
|
+
confetti: showConfetti ? current.confetti : 0,
|
|
161
|
+
fireworks: showFireworks ? current.fireworks : 0,
|
|
162
|
+
};
|
|
163
|
+
return next.confetti === current.confetti &&
|
|
164
|
+
next.fireworks === current.fireworks
|
|
165
|
+
? current
|
|
166
|
+
: next;
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (markSnapEffectsPresented(stateRef.current, effectsToPresent)) {
|
|
172
|
+
onRenderStateChangeRef.current?.(cloneSnapRenderState(stateRef.current));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
setEffectRunKeys((current) => ({
|
|
176
|
+
confetti: effectsToPresent.includes("confetti")
|
|
177
|
+
? current.confetti + 1
|
|
178
|
+
: showConfetti
|
|
179
|
+
? current.confetti
|
|
180
|
+
: 0,
|
|
181
|
+
fireworks: effectsToPresent.includes("fireworks")
|
|
182
|
+
? current.fireworks + 1
|
|
183
|
+
: showFireworks
|
|
184
|
+
? current.fireworks
|
|
185
|
+
: 0,
|
|
186
|
+
}));
|
|
187
|
+
}, [initialState, showConfetti, showFireworks, snapEffects]);
|
|
172
188
|
|
|
173
189
|
const handlersRef = useRef(handlers);
|
|
174
190
|
handlersRef.current = handlers;
|
|
@@ -222,6 +238,39 @@ export function SnapViewCoreInner({
|
|
|
222
238
|
buyToken: p.buyToken ? String(p.buyToken) : undefined,
|
|
223
239
|
});
|
|
224
240
|
break;
|
|
241
|
+
case "send_transaction":
|
|
242
|
+
h.send_transaction?.({
|
|
243
|
+
chainId: String(p.chainId ?? ""),
|
|
244
|
+
to: String(p.to ?? ""),
|
|
245
|
+
data: optionalString(p.data),
|
|
246
|
+
value: optionalString(p.value),
|
|
247
|
+
gas: optionalString(p.gas),
|
|
248
|
+
gasPrice: optionalString(p.gasPrice),
|
|
249
|
+
maxFeePerGas: optionalString(p.maxFeePerGas),
|
|
250
|
+
maxPriorityFeePerGas: optionalString(p.maxPriorityFeePerGas),
|
|
251
|
+
});
|
|
252
|
+
break;
|
|
253
|
+
case "send_calls":
|
|
254
|
+
h.send_calls?.({
|
|
255
|
+
version: p.version === "1.0" ? "1.0" : undefined,
|
|
256
|
+
chainId: String(p.chainId ?? ""),
|
|
257
|
+
atomicRequired:
|
|
258
|
+
typeof p.atomicRequired === "boolean"
|
|
259
|
+
? p.atomicRequired
|
|
260
|
+
: undefined,
|
|
261
|
+
id: optionalString(p.id),
|
|
262
|
+
calls: Array.isArray(p.calls)
|
|
263
|
+
? p.calls.map((call) => {
|
|
264
|
+
const c = asRecord(call);
|
|
265
|
+
return {
|
|
266
|
+
to: optionalString(c.to),
|
|
267
|
+
data: optionalString(c.data),
|
|
268
|
+
value: optionalString(c.value),
|
|
269
|
+
};
|
|
270
|
+
})
|
|
271
|
+
: [],
|
|
272
|
+
});
|
|
273
|
+
break;
|
|
225
274
|
default:
|
|
226
275
|
break;
|
|
227
276
|
}
|
|
@@ -229,16 +278,13 @@ export function SnapViewCoreInner({
|
|
|
229
278
|
|
|
230
279
|
return (
|
|
231
280
|
<View style={styles.container}>
|
|
232
|
-
{loading
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
)
|
|
240
|
-
: loadingOverlay
|
|
241
|
-
: null}
|
|
281
|
+
{loading ? (
|
|
282
|
+
loadingOverlay === undefined ? (
|
|
283
|
+
<SnapLoadingOverlay appearance={mode} accentHex={accentHex} />
|
|
284
|
+
) : (
|
|
285
|
+
loadingOverlay
|
|
286
|
+
)
|
|
287
|
+
) : null}
|
|
242
288
|
<SnapVersionProvider value={snap.version === "2.0" ? "2.0" : "1.0"}>
|
|
243
289
|
<SnapCatalogView
|
|
244
290
|
key={pageKey}
|
|
@@ -247,12 +293,17 @@ export function SnapViewCoreInner({
|
|
|
247
293
|
loading={false}
|
|
248
294
|
onStateChange={(changes) => {
|
|
249
295
|
applyStatePaths(stateRef.current, changes);
|
|
296
|
+
onRenderStateChange?.(cloneSnapRenderState(stateRef.current));
|
|
250
297
|
}}
|
|
251
298
|
onAction={handleAction}
|
|
252
299
|
/>
|
|
253
300
|
</SnapVersionProvider>
|
|
254
|
-
{showConfetti &&
|
|
255
|
-
|
|
301
|
+
{showConfetti && effectRunKeys.confetti > 0 && (
|
|
302
|
+
<ConfettiOverlay key={effectRunKeys.confetti} />
|
|
303
|
+
)}
|
|
304
|
+
{showFireworks && effectRunKeys.fireworks > 0 && (
|
|
305
|
+
<FireworksOverlay key={effectRunKeys.fireworks} />
|
|
306
|
+
)}
|
|
256
307
|
</View>
|
|
257
308
|
);
|
|
258
309
|
}
|
|
@@ -270,9 +321,7 @@ export function SnapLoadingOverlay({
|
|
|
270
321
|
styles.overlay,
|
|
271
322
|
{
|
|
272
323
|
backgroundColor:
|
|
273
|
-
appearance === "dark"
|
|
274
|
-
? "rgba(0,0,0,0.1)"
|
|
275
|
-
: "rgba(255,255,255,0.2)",
|
|
324
|
+
appearance === "dark" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.2)",
|
|
276
325
|
},
|
|
277
326
|
]}
|
|
278
327
|
>
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Spec } from "@json-render/core";
|
|
2
|
+
import type { SnapRenderState } from "../render-state";
|
|
3
|
+
|
|
4
|
+
export type { SnapRenderState };
|
|
2
5
|
|
|
3
6
|
export type JsonValue =
|
|
4
7
|
| string
|
|
@@ -15,6 +18,29 @@ export type SnapPage = {
|
|
|
15
18
|
ui: Spec;
|
|
16
19
|
};
|
|
17
20
|
|
|
21
|
+
export type SnapSendTransactionParams = {
|
|
22
|
+
chainId: string;
|
|
23
|
+
to: string;
|
|
24
|
+
data?: string;
|
|
25
|
+
value?: string;
|
|
26
|
+
gas?: string;
|
|
27
|
+
gasPrice?: string;
|
|
28
|
+
maxFeePerGas?: string;
|
|
29
|
+
maxPriorityFeePerGas?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type SnapSendCallsParams = {
|
|
33
|
+
version?: "1.0";
|
|
34
|
+
chainId: string;
|
|
35
|
+
atomicRequired?: boolean;
|
|
36
|
+
id?: string;
|
|
37
|
+
calls: Array<{
|
|
38
|
+
to?: string;
|
|
39
|
+
data?: string;
|
|
40
|
+
value?: string;
|
|
41
|
+
}>;
|
|
42
|
+
};
|
|
43
|
+
|
|
18
44
|
export type SnapActionHandlers = {
|
|
19
45
|
submit: (target: string, inputs: Record<string, JsonValue>) => void;
|
|
20
46
|
open_url: (target: string) => void;
|
|
@@ -35,4 +61,6 @@ export type SnapActionHandlers = {
|
|
|
35
61
|
recipientAddress?: string;
|
|
36
62
|
}) => void;
|
|
37
63
|
swap_token: (params: { sellToken?: string; buyToken?: string }) => void;
|
|
64
|
+
send_transaction?: (params: SnapSendTransactionParams) => void;
|
|
65
|
+
send_calls?: (params: SnapSendCallsParams) => void;
|
|
38
66
|
};
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
SnapViewCoreInner,
|
|
7
7
|
resolveAccentHex,
|
|
8
8
|
} from "../snap-view-core";
|
|
9
|
-
import type { SnapPage,
|
|
9
|
+
import type { SnapActionHandlers, SnapPage, SnapRenderState } from "../types";
|
|
10
10
|
import { getSnapExpansionState } from "../expand-state";
|
|
11
11
|
|
|
12
12
|
// ─── SnapViewV1 (no validation) ──────────────────────
|
|
@@ -16,11 +16,15 @@ export function SnapViewV1Inner({
|
|
|
16
16
|
handlers,
|
|
17
17
|
loading = false,
|
|
18
18
|
loadingOverlay,
|
|
19
|
+
initialRenderState,
|
|
20
|
+
onRenderStateChange,
|
|
19
21
|
}: {
|
|
20
22
|
snap: SnapPage;
|
|
21
23
|
handlers: SnapActionHandlers;
|
|
22
24
|
loading?: boolean;
|
|
23
25
|
loadingOverlay?: ReactNode;
|
|
26
|
+
initialRenderState?: SnapRenderState;
|
|
27
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
24
28
|
}) {
|
|
25
29
|
return (
|
|
26
30
|
<SnapViewCoreInner
|
|
@@ -28,6 +32,8 @@ export function SnapViewV1Inner({
|
|
|
28
32
|
handlers={handlers}
|
|
29
33
|
loading={loading}
|
|
30
34
|
loadingOverlay={loadingOverlay}
|
|
35
|
+
initialRenderState={initialRenderState}
|
|
36
|
+
onRenderStateChange={onRenderStateChange}
|
|
31
37
|
/>
|
|
32
38
|
);
|
|
33
39
|
}
|
|
@@ -39,6 +45,8 @@ export function SnapViewV1({
|
|
|
39
45
|
appearance = "dark",
|
|
40
46
|
colors,
|
|
41
47
|
loadingOverlay,
|
|
48
|
+
initialRenderState,
|
|
49
|
+
onRenderStateChange,
|
|
42
50
|
}: {
|
|
43
51
|
snap: SnapPage;
|
|
44
52
|
handlers: SnapActionHandlers;
|
|
@@ -47,6 +55,8 @@ export function SnapViewV1({
|
|
|
47
55
|
colors?: Partial<SnapNativeColors>;
|
|
48
56
|
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
49
57
|
loadingOverlay?: ReactNode;
|
|
58
|
+
initialRenderState?: SnapRenderState;
|
|
59
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
50
60
|
}) {
|
|
51
61
|
return (
|
|
52
62
|
<SnapThemeProvider appearance={appearance} colors={colors}>
|
|
@@ -55,6 +65,8 @@ export function SnapViewV1({
|
|
|
55
65
|
handlers={handlers}
|
|
56
66
|
loading={loading}
|
|
57
67
|
loadingOverlay={loadingOverlay}
|
|
68
|
+
initialRenderState={initialRenderState}
|
|
69
|
+
onRenderStateChange={onRenderStateChange}
|
|
58
70
|
/>
|
|
59
71
|
</SnapThemeProvider>
|
|
60
72
|
);
|
|
@@ -74,6 +86,8 @@ function SnapCardV1Inner({
|
|
|
74
86
|
forceExpanded,
|
|
75
87
|
expandButtonLabel,
|
|
76
88
|
onExpandPress,
|
|
89
|
+
initialRenderState,
|
|
90
|
+
onRenderStateChange,
|
|
77
91
|
}: {
|
|
78
92
|
snap: SnapPage;
|
|
79
93
|
handlers: SnapActionHandlers;
|
|
@@ -86,6 +100,8 @@ function SnapCardV1Inner({
|
|
|
86
100
|
forceExpanded?: boolean;
|
|
87
101
|
expandButtonLabel?: string;
|
|
88
102
|
onExpandPress?: () => void;
|
|
103
|
+
initialRenderState?: SnapRenderState;
|
|
104
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
89
105
|
}) {
|
|
90
106
|
const { colors, mode } = useSnapTheme();
|
|
91
107
|
const accentHex = resolveAccentHex(snap.theme?.accent, mode);
|
|
@@ -148,6 +164,8 @@ function SnapCardV1Inner({
|
|
|
148
164
|
handlers={handlers}
|
|
149
165
|
loading={loading}
|
|
150
166
|
loadingOverlay={null}
|
|
167
|
+
initialRenderState={initialRenderState}
|
|
168
|
+
onRenderStateChange={onRenderStateChange}
|
|
151
169
|
/>
|
|
152
170
|
</View>
|
|
153
171
|
</View>
|
|
@@ -223,6 +241,8 @@ export function SnapCardV1({
|
|
|
223
241
|
forceExpanded,
|
|
224
242
|
expandButtonLabel,
|
|
225
243
|
onExpandPress,
|
|
244
|
+
initialRenderState,
|
|
245
|
+
onRenderStateChange,
|
|
226
246
|
}: {
|
|
227
247
|
snap: SnapPage;
|
|
228
248
|
handlers: SnapActionHandlers;
|
|
@@ -240,6 +260,10 @@ export function SnapCardV1({
|
|
|
240
260
|
expandButtonLabel?: string;
|
|
241
261
|
/** Called from the collapsed expand button instead of toggling internal state. */
|
|
242
262
|
onExpandPress?: () => void;
|
|
263
|
+
/** JSON-render local state used to seed this presenter mount. */
|
|
264
|
+
initialRenderState?: SnapRenderState;
|
|
265
|
+
/** Called with the full JSON-render local state after state changes. */
|
|
266
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
243
267
|
}) {
|
|
244
268
|
return (
|
|
245
269
|
<SnapThemeProvider appearance={appearance} colors={colors}>
|
|
@@ -255,6 +279,8 @@ export function SnapCardV1({
|
|
|
255
279
|
forceExpanded={forceExpanded}
|
|
256
280
|
expandButtonLabel={expandButtonLabel}
|
|
257
281
|
onExpandPress={onExpandPress}
|
|
282
|
+
initialRenderState={initialRenderState}
|
|
283
|
+
onRenderStateChange={onRenderStateChange}
|
|
258
284
|
/>
|
|
259
285
|
</SnapThemeProvider>
|
|
260
286
|
);
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
validateSnapResponse,
|
|
12
12
|
type ValidationResult,
|
|
13
13
|
} from "@farcaster/snap";
|
|
14
|
-
import type { SnapPage,
|
|
14
|
+
import type { SnapActionHandlers, SnapPage, SnapRenderState } from "../types";
|
|
15
15
|
import { getSnapExpansionState, SNAP_MAX_HEIGHT } from "../expand-state";
|
|
16
16
|
|
|
17
17
|
// ─── Constants ───────────────────────────────────────
|
|
@@ -53,6 +53,8 @@ export function SnapViewV2Inner({
|
|
|
53
53
|
onValidationError,
|
|
54
54
|
validationErrorFallback,
|
|
55
55
|
loadingOverlay,
|
|
56
|
+
initialRenderState,
|
|
57
|
+
onRenderStateChange,
|
|
56
58
|
}: {
|
|
57
59
|
snap: SnapPage;
|
|
58
60
|
handlers: SnapActionHandlers;
|
|
@@ -60,6 +62,8 @@ export function SnapViewV2Inner({
|
|
|
60
62
|
onValidationError?: (result: ValidationResult) => void;
|
|
61
63
|
validationErrorFallback?: ReactNode;
|
|
62
64
|
loadingOverlay?: ReactNode;
|
|
65
|
+
initialRenderState?: SnapRenderState;
|
|
66
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
63
67
|
}) {
|
|
64
68
|
const validation = useMemo(() => validateSnapResponse(snap), [snap]);
|
|
65
69
|
const valid = validation.valid;
|
|
@@ -89,6 +93,8 @@ export function SnapViewV2Inner({
|
|
|
89
93
|
handlers={handlers}
|
|
90
94
|
loading={loading}
|
|
91
95
|
loadingOverlay={loadingOverlay}
|
|
96
|
+
initialRenderState={initialRenderState}
|
|
97
|
+
onRenderStateChange={onRenderStateChange}
|
|
92
98
|
/>
|
|
93
99
|
);
|
|
94
100
|
}
|
|
@@ -102,6 +108,8 @@ export function SnapViewV2({
|
|
|
102
108
|
onValidationError,
|
|
103
109
|
validationErrorFallback,
|
|
104
110
|
loadingOverlay,
|
|
111
|
+
initialRenderState,
|
|
112
|
+
onRenderStateChange,
|
|
105
113
|
}: {
|
|
106
114
|
snap: SnapPage;
|
|
107
115
|
handlers: SnapActionHandlers;
|
|
@@ -112,6 +120,8 @@ export function SnapViewV2({
|
|
|
112
120
|
validationErrorFallback?: ReactNode;
|
|
113
121
|
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
114
122
|
loadingOverlay?: ReactNode;
|
|
123
|
+
initialRenderState?: SnapRenderState;
|
|
124
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
115
125
|
}) {
|
|
116
126
|
return (
|
|
117
127
|
<SnapThemeProvider appearance={appearance} colors={colors}>
|
|
@@ -122,6 +132,8 @@ export function SnapViewV2({
|
|
|
122
132
|
onValidationError={onValidationError}
|
|
123
133
|
validationErrorFallback={validationErrorFallback}
|
|
124
134
|
loadingOverlay={loadingOverlay}
|
|
135
|
+
initialRenderState={initialRenderState}
|
|
136
|
+
onRenderStateChange={onRenderStateChange}
|
|
125
137
|
/>
|
|
126
138
|
</SnapThemeProvider>
|
|
127
139
|
);
|
|
@@ -144,6 +156,8 @@ function SnapCardV2Inner({
|
|
|
144
156
|
forceExpanded,
|
|
145
157
|
expandButtonLabel,
|
|
146
158
|
onExpandPress,
|
|
159
|
+
initialRenderState,
|
|
160
|
+
onRenderStateChange,
|
|
147
161
|
}: {
|
|
148
162
|
snap: SnapPage;
|
|
149
163
|
handlers: SnapActionHandlers;
|
|
@@ -159,6 +173,8 @@ function SnapCardV2Inner({
|
|
|
159
173
|
forceExpanded?: boolean;
|
|
160
174
|
expandButtonLabel?: string;
|
|
161
175
|
onExpandPress?: () => void;
|
|
176
|
+
initialRenderState?: SnapRenderState;
|
|
177
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
162
178
|
}) {
|
|
163
179
|
const { colors, mode } = useSnapTheme();
|
|
164
180
|
const accentHex = resolveAccentHex(snap.theme?.accent, mode);
|
|
@@ -188,6 +204,8 @@ function SnapCardV2Inner({
|
|
|
188
204
|
onValidationError={onValidationError}
|
|
189
205
|
validationErrorFallback={validationErrorFallback}
|
|
190
206
|
loadingOverlay={null}
|
|
207
|
+
initialRenderState={initialRenderState}
|
|
208
|
+
onRenderStateChange={onRenderStateChange}
|
|
191
209
|
/>
|
|
192
210
|
);
|
|
193
211
|
|
|
@@ -375,6 +393,8 @@ export function SnapCardV2({
|
|
|
375
393
|
forceExpanded,
|
|
376
394
|
expandButtonLabel,
|
|
377
395
|
onExpandPress,
|
|
396
|
+
initialRenderState,
|
|
397
|
+
onRenderStateChange,
|
|
378
398
|
}: {
|
|
379
399
|
snap: SnapPage;
|
|
380
400
|
handlers: SnapActionHandlers;
|
|
@@ -395,6 +415,10 @@ export function SnapCardV2({
|
|
|
395
415
|
expandButtonLabel?: string;
|
|
396
416
|
/** Called from the collapsed expand button instead of toggling internal state. */
|
|
397
417
|
onExpandPress?: () => void;
|
|
418
|
+
/** JSON-render local state used to seed this presenter mount. */
|
|
419
|
+
initialRenderState?: SnapRenderState;
|
|
420
|
+
/** Called with the full JSON-render local state after state changes. */
|
|
421
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
398
422
|
}) {
|
|
399
423
|
return (
|
|
400
424
|
<SnapThemeProvider appearance={appearance} colors={colors}>
|
|
@@ -413,6 +437,8 @@ export function SnapCardV2({
|
|
|
413
437
|
forceExpanded={forceExpanded}
|
|
414
438
|
expandButtonLabel={expandButtonLabel}
|
|
415
439
|
onExpandPress={onExpandPress}
|
|
440
|
+
initialRenderState={initialRenderState}
|
|
441
|
+
onRenderStateChange={onRenderStateChange}
|
|
416
442
|
/>
|
|
417
443
|
</SnapThemeProvider>
|
|
418
444
|
);
|