@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
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { SPEC_VERSION, SPEC_VERSION_1, SPEC_VERSION_2, SUPPORTED_SPEC_VERSIONS,
|
|
|
3
3
|
export { DEFAULT_THEME_ACCENT, PALETTE_COLOR, PALETTE_COLOR_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, isSnapHexColorString, readableTextOnHex, resolveSnapColorHex, type PaletteColor, } from "./colors.js";
|
|
4
4
|
export { ACTION_TYPE_GET, ACTION_TYPE_POST, snapResponseSchema, payloadSchema, getPayloadSchema, type SnapAction, type SnapGetAction, type SnapContext, type SnapResponse, type SnapHandlerResult, type SnapElementInput, type SnapSpecInput, type SnapFunction, type SnapPayload, type SnapGetPayload, } from "./schemas.js";
|
|
5
5
|
export { validateSnapResponse, type ValidationResult } from "./validator.js";
|
|
6
|
+
export type { SnapRenderState } from "./render-state.js";
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Spec } from "@json-render/core";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import type { ValidationResult } from "../validator.js";
|
|
4
|
+
import type { SnapRenderState } from "../render-state.js";
|
|
4
5
|
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
5
6
|
[key: string]: JsonValue;
|
|
6
7
|
};
|
|
@@ -12,6 +13,27 @@ export type SnapPage = {
|
|
|
12
13
|
effects?: string[];
|
|
13
14
|
ui: Spec;
|
|
14
15
|
};
|
|
16
|
+
export type SnapSendTransactionParams = {
|
|
17
|
+
chainId: string;
|
|
18
|
+
to: string;
|
|
19
|
+
data?: string;
|
|
20
|
+
value?: string;
|
|
21
|
+
gas?: string;
|
|
22
|
+
gasPrice?: string;
|
|
23
|
+
maxFeePerGas?: string;
|
|
24
|
+
maxPriorityFeePerGas?: string;
|
|
25
|
+
};
|
|
26
|
+
export type SnapSendCallsParams = {
|
|
27
|
+
version?: "1.0";
|
|
28
|
+
chainId: string;
|
|
29
|
+
atomicRequired?: boolean;
|
|
30
|
+
id?: string;
|
|
31
|
+
calls: Array<{
|
|
32
|
+
to?: string;
|
|
33
|
+
data?: string;
|
|
34
|
+
value?: string;
|
|
35
|
+
}>;
|
|
36
|
+
};
|
|
15
37
|
export type SnapActionHandlers = {
|
|
16
38
|
submit: (target: string, inputs: Record<string, JsonValue>) => void;
|
|
17
39
|
open_url: (target: string) => void;
|
|
@@ -41,8 +63,11 @@ export type SnapActionHandlers = {
|
|
|
41
63
|
sellToken?: string;
|
|
42
64
|
buyToken?: string;
|
|
43
65
|
}) => void;
|
|
66
|
+
send_transaction?: (params: SnapSendTransactionParams) => void;
|
|
67
|
+
send_calls?: (params: SnapSendCallsParams) => void;
|
|
44
68
|
};
|
|
45
|
-
export
|
|
69
|
+
export type { SnapRenderState };
|
|
70
|
+
export declare function SnapCard({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, loadingOverlay, initialRenderState, onRenderStateChange, }: {
|
|
46
71
|
snap: SnapPage;
|
|
47
72
|
handlers: SnapActionHandlers;
|
|
48
73
|
loading?: boolean;
|
|
@@ -58,4 +83,8 @@ export declare function SnapCard({ snap, handlers, loading, appearance, maxWidth
|
|
|
58
83
|
plain?: boolean;
|
|
59
84
|
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
60
85
|
loadingOverlay?: ReactNode;
|
|
86
|
+
/** JSON-render local state used to seed this presenter mount. */
|
|
87
|
+
initialRenderState?: SnapRenderState;
|
|
88
|
+
/** Called with the full JSON-render local state after state changes. */
|
|
89
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
61
90
|
}): import("react/jsx-runtime").JSX.Element;
|
package/dist/react/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import { SPEC_VERSION_2 } from "../constants.js";
|
|
|
4
4
|
import { SnapCardV1 } from "./v1/snap-view.js";
|
|
5
5
|
import { SnapCardV2 } from "./v2/snap-view.js";
|
|
6
6
|
// ─── SnapCard ────────────────────────────────────────
|
|
7
|
-
export function SnapCard({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, loadingOverlay, }) {
|
|
7
|
+
export function SnapCard({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, loadingOverlay, initialRenderState, onRenderStateChange, }) {
|
|
8
8
|
if (snap.version === SPEC_VERSION_2) {
|
|
9
|
-
return (_jsx(SnapCardV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, maxWidth: maxWidth, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay }));
|
|
9
|
+
return (_jsx(SnapCardV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, maxWidth: maxWidth, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay, initialRenderState: initialRenderState, onRenderStateChange: onRenderStateChange }));
|
|
10
10
|
}
|
|
11
|
-
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, maxWidth: maxWidth, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay }));
|
|
11
|
+
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, maxWidth: maxWidth, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay, initialRenderState: initialRenderState, onRenderStateChange: onRenderStateChange }));
|
|
12
12
|
}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
+
import { type SnapRenderState } from "../render-state.js";
|
|
1
2
|
import { type ReactNode } from "react";
|
|
2
3
|
import type { SnapActionHandlers, SnapPage } from "./index.js";
|
|
3
|
-
export declare function applyStatePaths(model: Record<string, unknown>, changes: {
|
|
4
|
-
path: string;
|
|
5
|
-
value: unknown;
|
|
6
|
-
}[] | Record<string, unknown> | null | undefined): void;
|
|
7
4
|
export declare function SnapLoadingOverlay({ appearance, accentHex, active, }: {
|
|
8
5
|
appearance: "light" | "dark";
|
|
9
6
|
accentHex: string;
|
|
10
7
|
active: boolean;
|
|
11
8
|
}): import("react/jsx-runtime").JSX.Element;
|
|
12
|
-
export declare function SnapViewCore({ snap, handlers, loading, appearance, loadingOverlay, }: {
|
|
9
|
+
export declare function SnapViewCore({ snap, handlers, loading, appearance, loadingOverlay, initialRenderState, onRenderStateChange, }: {
|
|
13
10
|
snap: SnapPage;
|
|
14
11
|
handlers: SnapActionHandlers;
|
|
15
12
|
loading?: boolean;
|
|
@@ -19,4 +16,6 @@ export declare function SnapViewCore({ snap, handlers, loading, appearance, load
|
|
|
19
16
|
* the built-in spinner + backdrop is used. Pass `null` to render nothing.
|
|
20
17
|
*/
|
|
21
18
|
loadingOverlay?: ReactNode;
|
|
19
|
+
initialRenderState?: SnapRenderState;
|
|
20
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
22
21
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -6,40 +6,15 @@ import { SnapPreviewAccentProvider } from "./accent-context.js";
|
|
|
6
6
|
import { SnapVersionProvider } from "./snap-version-context.js";
|
|
7
7
|
import { resolveSnapPaletteHex } from "./lib/resolve-palette-hex.js";
|
|
8
8
|
import { snapPreviewPrimaryCssProperties } from "./lib/preview-primary-css.js";
|
|
9
|
+
import { applyStatePaths, buildInitialRenderState, cloneSnapRenderState, getUnpresentedSnapEffects, markSnapEffectsPresented, } from "../render-state.js";
|
|
9
10
|
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
for (const [path, value] of entries) {
|
|
18
|
-
const trimmed = path.startsWith("/") ? path : `/${path}`;
|
|
19
|
-
const parts = trimmed.split("/").filter(Boolean);
|
|
20
|
-
if (parts.length < 2)
|
|
21
|
-
continue;
|
|
22
|
-
const [top, ...rest] = parts;
|
|
23
|
-
if (top === "inputs") {
|
|
24
|
-
if (typeof model.inputs !== "object" || model.inputs === null) {
|
|
25
|
-
model.inputs = {};
|
|
26
|
-
}
|
|
27
|
-
const inputs = model.inputs;
|
|
28
|
-
if (rest.length === 1) {
|
|
29
|
-
inputs[rest[0]] = value;
|
|
30
|
-
}
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
if (top === "theme") {
|
|
34
|
-
if (typeof model.theme !== "object" || model.theme === null) {
|
|
35
|
-
model.theme = {};
|
|
36
|
-
}
|
|
37
|
-
const theme = model.theme;
|
|
38
|
-
if (rest.length === 1) {
|
|
39
|
-
theme[rest[0]] = value;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
11
|
+
function asRecord(value) {
|
|
12
|
+
return value && typeof value === "object"
|
|
13
|
+
? value
|
|
14
|
+
: {};
|
|
15
|
+
}
|
|
16
|
+
function optionalString(value) {
|
|
17
|
+
return value ? String(value) : undefined;
|
|
43
18
|
}
|
|
44
19
|
function withDefaultElementProps(spec) {
|
|
45
20
|
if (!spec || typeof spec !== "object" || !("elements" in spec))
|
|
@@ -102,7 +77,7 @@ function ConfettiOverlay() {
|
|
|
102
77
|
overflow: "hidden",
|
|
103
78
|
pointerEvents: "none",
|
|
104
79
|
zIndex: 20,
|
|
105
|
-
}, children: [pieces.map(({ id, left, delay, duration, color, size, rotation, isCircle, driftX, driftMid }) => (_jsx("div", { style: {
|
|
80
|
+
}, children: [pieces.map(({ id, left, delay, duration, color, size, rotation, isCircle, driftX, driftMid, }) => (_jsx("div", { style: {
|
|
106
81
|
position: "absolute",
|
|
107
82
|
left: `${left}%`,
|
|
108
83
|
top: -20,
|
|
@@ -199,9 +174,7 @@ export function SnapLoadingOverlay({ appearance, accentHex, active, }) {
|
|
|
199
174
|
zIndex: 10,
|
|
200
175
|
background: tint,
|
|
201
176
|
backdropFilter: active ? "blur(10px) saturate(1.05)" : "none",
|
|
202
|
-
WebkitBackdropFilter: active
|
|
203
|
-
? "blur(10px) saturate(1.05)"
|
|
204
|
-
: "none",
|
|
177
|
+
WebkitBackdropFilter: active ? "blur(10px) saturate(1.05)" : "none",
|
|
205
178
|
opacity: active ? 1 : 0,
|
|
206
179
|
pointerEvents: active ? "auto" : "none",
|
|
207
180
|
transition: "opacity 0.28s ease, backdrop-filter 0.28s ease",
|
|
@@ -239,19 +212,16 @@ const PALETTE = [
|
|
|
239
212
|
];
|
|
240
213
|
// ─── SnapViewCore ────────────────────────────────────
|
|
241
214
|
// Shared rendering logic used by both v1 and v2.
|
|
242
|
-
export function SnapViewCore({ snap, handlers, loading = false, appearance = "dark", loadingOverlay, }) {
|
|
215
|
+
export function SnapViewCore({ snap, handlers, loading = false, appearance = "dark", loadingOverlay, initialRenderState, onRenderStateChange, }) {
|
|
243
216
|
const spec = useMemo(() => withDefaultElementProps(snap.ui), [snap.ui]);
|
|
244
|
-
const initialState = useMemo(() =>
|
|
217
|
+
const initialState = useMemo(() => buildInitialRenderState({
|
|
218
|
+
specState: spec.state,
|
|
219
|
+
initialRenderState,
|
|
220
|
+
themeAccent: snap.theme?.accent,
|
|
221
|
+
}), [initialRenderState, spec.state, snap.theme?.accent]);
|
|
245
222
|
const stateRef = useRef(initialState);
|
|
246
223
|
useEffect(() => {
|
|
247
|
-
stateRef.current =
|
|
248
|
-
inputs: {
|
|
249
|
-
...(initialState.inputs ?? {}),
|
|
250
|
-
},
|
|
251
|
-
theme: {
|
|
252
|
-
...(initialState.theme ?? {}),
|
|
253
|
-
},
|
|
254
|
-
};
|
|
224
|
+
stateRef.current = cloneSnapRenderState(initialState);
|
|
255
225
|
}, [initialState]);
|
|
256
226
|
useEffect(() => {
|
|
257
227
|
const catalogResult = snapJsonRenderCatalog.validate(spec);
|
|
@@ -264,16 +234,49 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
264
234
|
useEffect(() => {
|
|
265
235
|
setPageKey((k) => k + 1);
|
|
266
236
|
}, [spec]);
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
const
|
|
237
|
+
const effectSignature = snap.effects?.join("\u0000") ?? "";
|
|
238
|
+
const snapEffects = useMemo(() => (effectSignature ? effectSignature.split("\u0000") : []), [effectSignature]);
|
|
239
|
+
const showConfetti = snapEffects.includes("confetti");
|
|
240
|
+
const showFireworks = snapEffects.includes("fireworks");
|
|
241
|
+
const [effectRunKeys, setEffectRunKeys] = useState({
|
|
242
|
+
confetti: 0,
|
|
243
|
+
fireworks: 0,
|
|
244
|
+
});
|
|
245
|
+
const onRenderStateChangeRef = useRef(onRenderStateChange);
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
onRenderStateChangeRef.current = onRenderStateChange;
|
|
248
|
+
}, [onRenderStateChange]);
|
|
271
249
|
useEffect(() => {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
250
|
+
const effectsToPresent = getUnpresentedSnapEffects(stateRef.current, snapEffects);
|
|
251
|
+
if (effectsToPresent.length === 0) {
|
|
252
|
+
setEffectRunKeys((current) => {
|
|
253
|
+
const next = {
|
|
254
|
+
confetti: showConfetti ? current.confetti : 0,
|
|
255
|
+
fireworks: showFireworks ? current.fireworks : 0,
|
|
256
|
+
};
|
|
257
|
+
return next.confetti === current.confetti &&
|
|
258
|
+
next.fireworks === current.fireworks
|
|
259
|
+
? current
|
|
260
|
+
: next;
|
|
261
|
+
});
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (markSnapEffectsPresented(stateRef.current, effectsToPresent)) {
|
|
265
|
+
onRenderStateChangeRef.current?.(cloneSnapRenderState(stateRef.current));
|
|
266
|
+
}
|
|
267
|
+
setEffectRunKeys((current) => ({
|
|
268
|
+
confetti: effectsToPresent.includes("confetti")
|
|
269
|
+
? current.confetti + 1
|
|
270
|
+
: showConfetti
|
|
271
|
+
? current.confetti
|
|
272
|
+
: 0,
|
|
273
|
+
fireworks: effectsToPresent.includes("fireworks")
|
|
274
|
+
? current.fireworks + 1
|
|
275
|
+
: showFireworks
|
|
276
|
+
? current.fireworks
|
|
277
|
+
: 0,
|
|
278
|
+
}));
|
|
279
|
+
}, [initialState, showConfetti, showFireworks, snapEffects]);
|
|
277
280
|
const accentName = snap.theme?.accent ?? "purple";
|
|
278
281
|
const accentHex = useMemo(() => resolveSnapPaletteHex(accentName, appearance), [accentName, appearance]);
|
|
279
282
|
const previewSurfaceStyle = useMemo(() => {
|
|
@@ -335,11 +338,44 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
335
338
|
buyToken: p.buyToken ? String(p.buyToken) : undefined,
|
|
336
339
|
});
|
|
337
340
|
break;
|
|
341
|
+
case "send_transaction":
|
|
342
|
+
handlers.send_transaction?.({
|
|
343
|
+
chainId: String(p.chainId ?? ""),
|
|
344
|
+
to: String(p.to ?? ""),
|
|
345
|
+
data: optionalString(p.data),
|
|
346
|
+
value: optionalString(p.value),
|
|
347
|
+
gas: optionalString(p.gas),
|
|
348
|
+
gasPrice: optionalString(p.gasPrice),
|
|
349
|
+
maxFeePerGas: optionalString(p.maxFeePerGas),
|
|
350
|
+
maxPriorityFeePerGas: optionalString(p.maxPriorityFeePerGas),
|
|
351
|
+
});
|
|
352
|
+
break;
|
|
353
|
+
case "send_calls":
|
|
354
|
+
handlers.send_calls?.({
|
|
355
|
+
version: p.version === "1.0" ? "1.0" : undefined,
|
|
356
|
+
chainId: String(p.chainId ?? ""),
|
|
357
|
+
atomicRequired: typeof p.atomicRequired === "boolean"
|
|
358
|
+
? p.atomicRequired
|
|
359
|
+
: undefined,
|
|
360
|
+
id: optionalString(p.id),
|
|
361
|
+
calls: Array.isArray(p.calls)
|
|
362
|
+
? p.calls.map((call) => {
|
|
363
|
+
const c = asRecord(call);
|
|
364
|
+
return {
|
|
365
|
+
to: optionalString(c.to),
|
|
366
|
+
data: optionalString(c.data),
|
|
367
|
+
value: optionalString(c.value),
|
|
368
|
+
};
|
|
369
|
+
})
|
|
370
|
+
: [],
|
|
371
|
+
});
|
|
372
|
+
break;
|
|
338
373
|
default:
|
|
339
374
|
break;
|
|
340
375
|
}
|
|
341
376
|
}, [handlers]);
|
|
342
|
-
return (_jsxs("div", { style: { position: "relative", width: "100%" }, children: [showConfetti && _jsx(ConfettiOverlay, {},
|
|
377
|
+
return (_jsxs("div", { style: { position: "relative", width: "100%" }, children: [showConfetti && effectRunKeys.confetti > 0 && (_jsx(ConfettiOverlay, {}, effectRunKeys.confetti)), showFireworks && effectRunKeys.fireworks > 0 && (_jsx(FireworksOverlay, {}, effectRunKeys.fireworks)), loadingOverlay === undefined ? (_jsx(SnapLoadingOverlay, { appearance: appearance, accentHex: accentHex, active: loading })) : loading ? (_jsx(_Fragment, { children: loadingOverlay })) : null, _jsx("div", { style: previewSurfaceStyle, children: _jsx(SnapPreviewAccentProvider, { pageAccent: snap.theme?.accent, appearance: appearance, children: _jsx(SnapVersionProvider, { value: snap.version === "2.0" ? "2.0" : "1.0", children: _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
|
|
343
378
|
applyStatePaths(stateRef.current, changes);
|
|
379
|
+
onRenderStateChange?.(cloneSnapRenderState(stateRef.current));
|
|
344
380
|
}, onAction: handleAction }, pageKey) }) }) })] }));
|
|
345
381
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type { SnapPage,
|
|
3
|
-
export declare function SnapViewV1({ snap, handlers, loading, appearance, loadingOverlay, }: {
|
|
2
|
+
import type { SnapActionHandlers, SnapPage, SnapRenderState } from "../index.js";
|
|
3
|
+
export declare function SnapViewV1({ snap, handlers, loading, appearance, loadingOverlay, initialRenderState, onRenderStateChange, }: {
|
|
4
4
|
snap: SnapPage;
|
|
5
5
|
handlers: SnapActionHandlers;
|
|
6
6
|
loading?: boolean;
|
|
7
7
|
appearance?: "light" | "dark";
|
|
8
8
|
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
9
9
|
loadingOverlay?: ReactNode;
|
|
10
|
+
initialRenderState?: SnapRenderState;
|
|
11
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
10
12
|
}): import("react/jsx-runtime").JSX.Element;
|
|
11
|
-
export declare function SnapCardV1({ snap, handlers, loading, appearance, maxWidth, actionError, plain, loadingOverlay, }: {
|
|
13
|
+
export declare function SnapCardV1({ snap, handlers, loading, appearance, maxWidth, actionError, plain, loadingOverlay, initialRenderState, onRenderStateChange, }: {
|
|
12
14
|
snap: SnapPage;
|
|
13
15
|
handlers: SnapActionHandlers;
|
|
14
16
|
loading?: boolean;
|
|
@@ -18,4 +20,8 @@ export declare function SnapCardV1({ snap, handlers, loading, appearance, maxWid
|
|
|
18
20
|
plain?: boolean;
|
|
19
21
|
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
20
22
|
loadingOverlay?: ReactNode;
|
|
23
|
+
/** JSON-render local state used to seed this presenter mount. */
|
|
24
|
+
initialRenderState?: SnapRenderState;
|
|
25
|
+
/** Called with the full JSON-render local state after state changes. */
|
|
26
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
21
27
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,10 +4,10 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
4
4
|
import { SnapViewCore, SnapLoadingOverlay } from "../snap-view-core.js";
|
|
5
5
|
import { resolveSnapPaletteHex } from "../lib/resolve-palette-hex.js";
|
|
6
6
|
const SNAP_MAX_HEIGHT = 500;
|
|
7
|
-
export function SnapViewV1({ snap, handlers, loading = false, appearance = "dark", loadingOverlay, }) {
|
|
8
|
-
return (_jsx(SnapViewCore, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, loadingOverlay: loadingOverlay }));
|
|
7
|
+
export function SnapViewV1({ snap, handlers, loading = false, appearance = "dark", loadingOverlay, initialRenderState, onRenderStateChange, }) {
|
|
8
|
+
return (_jsx(SnapViewCore, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, loadingOverlay: loadingOverlay, initialRenderState: initialRenderState, onRenderStateChange: onRenderStateChange }));
|
|
9
9
|
}
|
|
10
|
-
export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, actionError, plain = false, loadingOverlay, }) {
|
|
10
|
+
export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, actionError, plain = false, loadingOverlay, initialRenderState, onRenderStateChange, }) {
|
|
11
11
|
const isDark = appearance === "dark";
|
|
12
12
|
const borderColor = isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
|
13
13
|
const surfaceBg = isDark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.02)";
|
|
@@ -62,7 +62,7 @@ export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark
|
|
|
62
62
|
maxHeight: SNAP_MAX_HEIGHT,
|
|
63
63
|
overflow: "hidden",
|
|
64
64
|
}
|
|
65
|
-
: undefined, children: _jsx("div", { ref: contentRef, style: plain ? undefined : { padding: 16 }, children: _jsx(SnapViewV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, loadingOverlay: null }) }) }), loadingOverlay === undefined ? (_jsx(SnapLoadingOverlay, { appearance: appearance, accentHex: accentHex, active: loading })) : loading ? (_jsx(_Fragment, { children: loadingOverlay })) : null] }), isExpandable ? (_jsx("button", { type: "button", "aria-expanded": isExpanded, onClick: () => setIsExpanded((value) => !value), style: {
|
|
65
|
+
: undefined, children: _jsx("div", { ref: contentRef, style: plain ? undefined : { padding: 16 }, children: _jsx(SnapViewV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, loadingOverlay: null, initialRenderState: initialRenderState, onRenderStateChange: onRenderStateChange }) }) }), loadingOverlay === undefined ? (_jsx(SnapLoadingOverlay, { appearance: appearance, accentHex: accentHex, active: loading })) : loading ? (_jsx(_Fragment, { children: loadingOverlay })) : null] }), isExpandable ? (_jsx("button", { type: "button", "aria-expanded": isExpanded, onClick: () => setIsExpanded((value) => !value), style: {
|
|
66
66
|
position: "absolute",
|
|
67
67
|
bottom: 0,
|
|
68
68
|
left: "50%",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import type { ValidationResult } from "../../validator.js";
|
|
3
|
-
import type { SnapPage,
|
|
4
|
-
export declare function SnapViewV2({ snap, handlers, loading, appearance, onValidationError, validationErrorFallback, loadingOverlay, }: {
|
|
3
|
+
import type { SnapActionHandlers, SnapPage, SnapRenderState } from "../index.js";
|
|
4
|
+
export declare function SnapViewV2({ snap, handlers, loading, appearance, onValidationError, validationErrorFallback, loadingOverlay, initialRenderState, onRenderStateChange, }: {
|
|
5
5
|
snap: SnapPage;
|
|
6
6
|
handlers: SnapActionHandlers;
|
|
7
7
|
loading?: boolean;
|
|
@@ -10,8 +10,10 @@ export declare function SnapViewV2({ snap, handlers, loading, appearance, onVali
|
|
|
10
10
|
validationErrorFallback?: ReactNode;
|
|
11
11
|
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
12
12
|
loadingOverlay?: ReactNode;
|
|
13
|
+
initialRenderState?: SnapRenderState;
|
|
14
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
13
15
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
14
|
-
export declare function SnapCardV2({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, loadingOverlay, }: {
|
|
16
|
+
export declare function SnapCardV2({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, loadingOverlay, initialRenderState, onRenderStateChange, }: {
|
|
15
17
|
snap: SnapPage;
|
|
16
18
|
handlers: SnapActionHandlers;
|
|
17
19
|
loading?: boolean;
|
|
@@ -24,4 +26,8 @@ export declare function SnapCardV2({ snap, handlers, loading, appearance, maxWid
|
|
|
24
26
|
plain?: boolean;
|
|
25
27
|
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
26
28
|
loadingOverlay?: ReactNode;
|
|
29
|
+
/** JSON-render local state used to seed this presenter mount. */
|
|
30
|
+
initialRenderState?: SnapRenderState;
|
|
31
|
+
/** Called with the full JSON-render local state after state changes. */
|
|
32
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
27
33
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -21,7 +21,7 @@ function SnapValidationFallback({ appearance, message, }) {
|
|
|
21
21
|
}, children: _jsx("span", { children: message ? `Unable to render snap: ${message}` : "Unable to render snap" }) }));
|
|
22
22
|
}
|
|
23
23
|
// ─── SnapViewV2 ──────────────────────────────────────
|
|
24
|
-
export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark", onValidationError, validationErrorFallback, loadingOverlay, }) {
|
|
24
|
+
export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark", onValidationError, validationErrorFallback, loadingOverlay, initialRenderState, onRenderStateChange, }) {
|
|
25
25
|
const validation = useMemo(() => validateSnapResponse(snap), [snap]);
|
|
26
26
|
const valid = validation.valid;
|
|
27
27
|
const validationMessage = validation.issues[0]?.message;
|
|
@@ -41,10 +41,10 @@ export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark
|
|
|
41
41
|
return null;
|
|
42
42
|
return _jsx(_Fragment, { children: validationErrorFallback ?? _jsx(SnapValidationFallback, { appearance: appearance, message: validationMessage }) });
|
|
43
43
|
}
|
|
44
|
-
return (_jsx(SnapViewCore, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, loadingOverlay: loadingOverlay }));
|
|
44
|
+
return (_jsx(SnapViewCore, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, loadingOverlay: loadingOverlay, initialRenderState: initialRenderState, onRenderStateChange: onRenderStateChange }));
|
|
45
45
|
}
|
|
46
46
|
// ─── SnapCardV2 ──────────────────────────────────────
|
|
47
|
-
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, loadingOverlay, }) {
|
|
47
|
+
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, loadingOverlay, initialRenderState, onRenderStateChange, }) {
|
|
48
48
|
const isDark = appearance === "dark";
|
|
49
49
|
const bg = isDark ? "rgba(0,0,0,0.85)" : "rgba(255,255,255,0.9)";
|
|
50
50
|
const borderColor = isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
|
@@ -101,7 +101,7 @@ export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark
|
|
|
101
101
|
}),
|
|
102
102
|
}, children: [_jsx("div", { style: isClipped
|
|
103
103
|
? { maxHeight: SNAP_MAX_HEIGHT, overflow: "hidden" }
|
|
104
|
-
: undefined, children: _jsx("div", { ref: contentRef, style: plain ? undefined : { padding: 16 }, children: _jsx(SnapViewV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, loadingOverlay: null }) }) }), loadingOverlay === undefined ? (_jsx(SnapLoadingOverlay, { appearance: appearance, accentHex: accentHex, active: loading })) : loading ? (_jsx(_Fragment, { children: loadingOverlay })) : null, showOverflowWarning && (_jsxs("div", { style: {
|
|
104
|
+
: undefined, children: _jsx("div", { ref: contentRef, style: plain ? undefined : { padding: 16 }, children: _jsx(SnapViewV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, loadingOverlay: null, initialRenderState: initialRenderState, onRenderStateChange: onRenderStateChange }) }) }), loadingOverlay === undefined ? (_jsx(SnapLoadingOverlay, { appearance: appearance, accentHex: accentHex, active: loading })) : loading ? (_jsx(_Fragment, { children: loadingOverlay })) : null, showOverflowWarning && (_jsxs("div", { style: {
|
|
105
105
|
position: "absolute",
|
|
106
106
|
top: SNAP_MAX_HEIGHT,
|
|
107
107
|
left: 0,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { ValidationResult } from "@farcaster/snap";
|
|
3
3
|
import type { SnapNativeColors } from "./theme.js";
|
|
4
|
-
import type { SnapPage,
|
|
4
|
+
import type { SnapActionHandlers, SnapPage, SnapRenderState } from "./types.js";
|
|
5
5
|
import { useSnapTheme } from "./theme.js";
|
|
6
6
|
import { hexToRgba } from "./use-snap-palette.js";
|
|
7
|
-
export type { JsonValue, SnapPage,
|
|
7
|
+
export type { JsonValue, SnapActionHandlers, SnapPage, SnapRenderState, } from "./types.js";
|
|
8
8
|
export { useSnapTheme, hexToRgba };
|
|
9
9
|
export type { SnapNativeColors };
|
|
10
|
-
export declare function SnapCard({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, loadingOverlay, forceExpanded, expandButtonLabel, onExpandPress, }: {
|
|
10
|
+
export declare function SnapCard({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, loadingOverlay, forceExpanded, expandButtonLabel, onExpandPress, initialRenderState, onRenderStateChange, }: {
|
|
11
11
|
snap: SnapPage;
|
|
12
12
|
handlers: SnapActionHandlers;
|
|
13
13
|
loading?: boolean;
|
|
@@ -33,4 +33,8 @@ export declare function SnapCard({ snap, handlers, loading, appearance, colors,
|
|
|
33
33
|
expandButtonLabel?: string;
|
|
34
34
|
/** Called from the collapsed expand button instead of toggling internal state. */
|
|
35
35
|
onExpandPress?: () => void;
|
|
36
|
+
/** JSON-render local state used to seed this presenter mount. */
|
|
37
|
+
initialRenderState?: SnapRenderState;
|
|
38
|
+
/** Called with the full JSON-render local state after state changes. */
|
|
39
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
36
40
|
}): import("react").JSX.Element;
|
|
@@ -7,9 +7,9 @@ import { SnapCardV2 } from "./v2/snap-view.js";
|
|
|
7
7
|
// ─── Re-exports ───────────────────────────────────────
|
|
8
8
|
export { useSnapTheme, hexToRgba };
|
|
9
9
|
// ─── SnapCard (version-switching) ─────────────────────
|
|
10
|
-
export function SnapCard({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, loadingOverlay, forceExpanded, expandButtonLabel, onExpandPress, }) {
|
|
10
|
+
export function SnapCard({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, loadingOverlay, forceExpanded, expandButtonLabel, onExpandPress, initialRenderState, onRenderStateChange, }) {
|
|
11
11
|
if (snap.version === SPEC_VERSION_2) {
|
|
12
|
-
return (_jsx(SnapCardV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay, forceExpanded: forceExpanded, expandButtonLabel: expandButtonLabel, onExpandPress: onExpandPress }));
|
|
12
|
+
return (_jsx(SnapCardV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay, forceExpanded: forceExpanded, expandButtonLabel: expandButtonLabel, onExpandPress: onExpandPress, initialRenderState: initialRenderState, onRenderStateChange: onRenderStateChange }));
|
|
13
13
|
}
|
|
14
|
-
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay, forceExpanded: forceExpanded, expandButtonLabel: expandButtonLabel, onExpandPress: onExpandPress }));
|
|
14
|
+
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay, forceExpanded: forceExpanded, expandButtonLabel: expandButtonLabel, onExpandPress: onExpandPress, initialRenderState: initialRenderState, onRenderStateChange: onRenderStateChange }));
|
|
15
15
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
+
import { type SnapRenderState } from "../render-state.js";
|
|
2
3
|
import type { SnapPage, SnapActionHandlers } from "./types.js";
|
|
3
|
-
export declare function applyStatePaths(model: Record<string, unknown>, changes: {
|
|
4
|
-
path: string;
|
|
5
|
-
value: unknown;
|
|
6
|
-
}[] | Record<string, unknown> | null | undefined): void;
|
|
7
4
|
export declare function resolveAccentHex(accent: string | undefined, appearance: "light" | "dark"): string;
|
|
8
|
-
export declare function SnapViewCoreInner({ snap, handlers, loading, loadingOverlay, }: {
|
|
5
|
+
export declare function SnapViewCoreInner({ snap, handlers, loading, loadingOverlay, initialRenderState, onRenderStateChange, }: {
|
|
9
6
|
snap: SnapPage;
|
|
10
7
|
handlers: SnapActionHandlers;
|
|
11
8
|
loading?: boolean;
|
|
@@ -14,6 +11,8 @@ export declare function SnapViewCoreInner({ snap, handlers, loading, loadingOver
|
|
|
14
11
|
* the built-in ActivityIndicator overlay is used. Pass `null` to render nothing.
|
|
15
12
|
*/
|
|
16
13
|
loadingOverlay?: ReactNode;
|
|
14
|
+
initialRenderState?: SnapRenderState;
|
|
15
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
17
16
|
}): import("react").JSX.Element;
|
|
18
17
|
export declare function SnapLoadingOverlay({ appearance, accentHex, }: {
|
|
19
18
|
appearance: "light" | "dark";
|