@farcaster/snap 2.6.4 → 2.7.0
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 +7 -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 +54 -58
- 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 +54 -69
- package/dist/react-native/types.d.ts +2 -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/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/react/index.tsx +13 -0
- package/src/react/snap-view-core.tsx +94 -65
- 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 +86 -80
- package/src/react-native/types.ts +3 -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/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
|
};
|
|
@@ -42,7 +43,8 @@ export type SnapActionHandlers = {
|
|
|
42
43
|
buyToken?: string;
|
|
43
44
|
}) => void;
|
|
44
45
|
};
|
|
45
|
-
export
|
|
46
|
+
export type { SnapRenderState };
|
|
47
|
+
export declare function SnapCard({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, loadingOverlay, initialRenderState, onRenderStateChange, }: {
|
|
46
48
|
snap: SnapPage;
|
|
47
49
|
handlers: SnapActionHandlers;
|
|
48
50
|
loading?: boolean;
|
|
@@ -58,4 +60,8 @@ export declare function SnapCard({ snap, handlers, loading, appearance, maxWidth
|
|
|
58
60
|
plain?: boolean;
|
|
59
61
|
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
60
62
|
loadingOverlay?: ReactNode;
|
|
63
|
+
/** JSON-render local state used to seed this presenter mount. */
|
|
64
|
+
initialRenderState?: SnapRenderState;
|
|
65
|
+
/** Called with the full JSON-render local state after state changes. */
|
|
66
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
61
67
|
}): 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,41 +6,8 @@ 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
|
-
// ─── Internal helpers ──────────────────────────────────
|
|
11
|
-
export function applyStatePaths(model, changes) {
|
|
12
|
-
if (!changes)
|
|
13
|
-
return;
|
|
14
|
-
const entries = Array.isArray(changes)
|
|
15
|
-
? changes.map((c) => [c.path, c.value])
|
|
16
|
-
: Object.entries(changes);
|
|
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
|
-
}
|
|
43
|
-
}
|
|
44
11
|
function withDefaultElementProps(spec) {
|
|
45
12
|
if (!spec || typeof spec !== "object" || !("elements" in spec))
|
|
46
13
|
return spec;
|
|
@@ -102,7 +69,7 @@ function ConfettiOverlay() {
|
|
|
102
69
|
overflow: "hidden",
|
|
103
70
|
pointerEvents: "none",
|
|
104
71
|
zIndex: 20,
|
|
105
|
-
}, children: [pieces.map(({ id, left, delay, duration, color, size, rotation, isCircle, driftX, driftMid }) => (_jsx("div", { style: {
|
|
72
|
+
}, children: [pieces.map(({ id, left, delay, duration, color, size, rotation, isCircle, driftX, driftMid, }) => (_jsx("div", { style: {
|
|
106
73
|
position: "absolute",
|
|
107
74
|
left: `${left}%`,
|
|
108
75
|
top: -20,
|
|
@@ -199,9 +166,7 @@ export function SnapLoadingOverlay({ appearance, accentHex, active, }) {
|
|
|
199
166
|
zIndex: 10,
|
|
200
167
|
background: tint,
|
|
201
168
|
backdropFilter: active ? "blur(10px) saturate(1.05)" : "none",
|
|
202
|
-
WebkitBackdropFilter: active
|
|
203
|
-
? "blur(10px) saturate(1.05)"
|
|
204
|
-
: "none",
|
|
169
|
+
WebkitBackdropFilter: active ? "blur(10px) saturate(1.05)" : "none",
|
|
205
170
|
opacity: active ? 1 : 0,
|
|
206
171
|
pointerEvents: active ? "auto" : "none",
|
|
207
172
|
transition: "opacity 0.28s ease, backdrop-filter 0.28s ease",
|
|
@@ -239,19 +204,16 @@ const PALETTE = [
|
|
|
239
204
|
];
|
|
240
205
|
// ─── SnapViewCore ────────────────────────────────────
|
|
241
206
|
// Shared rendering logic used by both v1 and v2.
|
|
242
|
-
export function SnapViewCore({ snap, handlers, loading = false, appearance = "dark", loadingOverlay, }) {
|
|
207
|
+
export function SnapViewCore({ snap, handlers, loading = false, appearance = "dark", loadingOverlay, initialRenderState, onRenderStateChange, }) {
|
|
243
208
|
const spec = useMemo(() => withDefaultElementProps(snap.ui), [snap.ui]);
|
|
244
|
-
const initialState = useMemo(() =>
|
|
209
|
+
const initialState = useMemo(() => buildInitialRenderState({
|
|
210
|
+
specState: spec.state,
|
|
211
|
+
initialRenderState,
|
|
212
|
+
themeAccent: snap.theme?.accent,
|
|
213
|
+
}), [initialRenderState, spec.state, snap.theme?.accent]);
|
|
245
214
|
const stateRef = useRef(initialState);
|
|
246
215
|
useEffect(() => {
|
|
247
|
-
stateRef.current =
|
|
248
|
-
inputs: {
|
|
249
|
-
...(initialState.inputs ?? {}),
|
|
250
|
-
},
|
|
251
|
-
theme: {
|
|
252
|
-
...(initialState.theme ?? {}),
|
|
253
|
-
},
|
|
254
|
-
};
|
|
216
|
+
stateRef.current = cloneSnapRenderState(initialState);
|
|
255
217
|
}, [initialState]);
|
|
256
218
|
useEffect(() => {
|
|
257
219
|
const catalogResult = snapJsonRenderCatalog.validate(spec);
|
|
@@ -264,16 +226,49 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
264
226
|
useEffect(() => {
|
|
265
227
|
setPageKey((k) => k + 1);
|
|
266
228
|
}, [spec]);
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
const
|
|
229
|
+
const effectSignature = snap.effects?.join("\u0000") ?? "";
|
|
230
|
+
const snapEffects = useMemo(() => (effectSignature ? effectSignature.split("\u0000") : []), [effectSignature]);
|
|
231
|
+
const showConfetti = snapEffects.includes("confetti");
|
|
232
|
+
const showFireworks = snapEffects.includes("fireworks");
|
|
233
|
+
const [effectRunKeys, setEffectRunKeys] = useState({
|
|
234
|
+
confetti: 0,
|
|
235
|
+
fireworks: 0,
|
|
236
|
+
});
|
|
237
|
+
const onRenderStateChangeRef = useRef(onRenderStateChange);
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
onRenderStateChangeRef.current = onRenderStateChange;
|
|
240
|
+
}, [onRenderStateChange]);
|
|
271
241
|
useEffect(() => {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
242
|
+
const effectsToPresent = getUnpresentedSnapEffects(stateRef.current, snapEffects);
|
|
243
|
+
if (effectsToPresent.length === 0) {
|
|
244
|
+
setEffectRunKeys((current) => {
|
|
245
|
+
const next = {
|
|
246
|
+
confetti: showConfetti ? current.confetti : 0,
|
|
247
|
+
fireworks: showFireworks ? current.fireworks : 0,
|
|
248
|
+
};
|
|
249
|
+
return next.confetti === current.confetti &&
|
|
250
|
+
next.fireworks === current.fireworks
|
|
251
|
+
? current
|
|
252
|
+
: next;
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (markSnapEffectsPresented(stateRef.current, effectsToPresent)) {
|
|
257
|
+
onRenderStateChangeRef.current?.(cloneSnapRenderState(stateRef.current));
|
|
258
|
+
}
|
|
259
|
+
setEffectRunKeys((current) => ({
|
|
260
|
+
confetti: effectsToPresent.includes("confetti")
|
|
261
|
+
? current.confetti + 1
|
|
262
|
+
: showConfetti
|
|
263
|
+
? current.confetti
|
|
264
|
+
: 0,
|
|
265
|
+
fireworks: effectsToPresent.includes("fireworks")
|
|
266
|
+
? current.fireworks + 1
|
|
267
|
+
: showFireworks
|
|
268
|
+
? current.fireworks
|
|
269
|
+
: 0,
|
|
270
|
+
}));
|
|
271
|
+
}, [initialState, showConfetti, showFireworks, snapEffects]);
|
|
277
272
|
const accentName = snap.theme?.accent ?? "purple";
|
|
278
273
|
const accentHex = useMemo(() => resolveSnapPaletteHex(accentName, appearance), [accentName, appearance]);
|
|
279
274
|
const previewSurfaceStyle = useMemo(() => {
|
|
@@ -339,7 +334,8 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
339
334
|
break;
|
|
340
335
|
}
|
|
341
336
|
}, [handlers]);
|
|
342
|
-
return (_jsxs("div", { style: { position: "relative", width: "100%" }, children: [showConfetti && _jsx(ConfettiOverlay, {},
|
|
337
|
+
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
338
|
applyStatePaths(stateRef.current, changes);
|
|
339
|
+
onRenderStateChange?.(cloneSnapRenderState(stateRef.current));
|
|
344
340
|
}, onAction: handleAction }, pageKey) }) }) })] }));
|
|
345
341
|
}
|
|
@@ -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";
|
|
@@ -8,40 +8,7 @@ import { SnapVersionProvider } from "./snap-version-context.js";
|
|
|
8
8
|
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
9
9
|
import { ActivityIndicator, StyleSheet, View } from "react-native";
|
|
10
10
|
import { DEFAULT_THEME_ACCENT, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "@farcaster/snap";
|
|
11
|
-
|
|
12
|
-
export function applyStatePaths(model, changes) {
|
|
13
|
-
if (!changes)
|
|
14
|
-
return;
|
|
15
|
-
const entries = Array.isArray(changes)
|
|
16
|
-
? changes.map((c) => [c.path, c.value])
|
|
17
|
-
: Object.entries(changes);
|
|
18
|
-
for (const [path, value] of entries) {
|
|
19
|
-
const trimmed = path.startsWith("/") ? path : `/${path}`;
|
|
20
|
-
const parts = trimmed.split("/").filter(Boolean);
|
|
21
|
-
if (parts.length < 2)
|
|
22
|
-
continue;
|
|
23
|
-
const [top, ...rest] = parts;
|
|
24
|
-
if (top === "inputs") {
|
|
25
|
-
if (typeof model.inputs !== "object" || model.inputs === null) {
|
|
26
|
-
model.inputs = {};
|
|
27
|
-
}
|
|
28
|
-
const inputs = model.inputs;
|
|
29
|
-
if (rest.length === 1) {
|
|
30
|
-
inputs[rest[0]] = value;
|
|
31
|
-
}
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (top === "theme") {
|
|
35
|
-
if (typeof model.theme !== "object" || model.theme === null) {
|
|
36
|
-
model.theme = {};
|
|
37
|
-
}
|
|
38
|
-
const theme = model.theme;
|
|
39
|
-
if (rest.length === 1) {
|
|
40
|
-
theme[rest[0]] = value;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
11
|
+
import { applyStatePaths, buildInitialRenderState, cloneSnapRenderState, getUnpresentedSnapEffects, markSnapEffectsPresented, } from "../render-state.js";
|
|
45
12
|
function withDefaultElementProps(spec) {
|
|
46
13
|
if (!spec || typeof spec !== "object" || !("elements" in spec))
|
|
47
14
|
return spec;
|
|
@@ -70,28 +37,18 @@ export function resolveAccentHex(accent, appearance) {
|
|
|
70
37
|
return map[name];
|
|
71
38
|
}
|
|
72
39
|
// ─── Core rendering component (no validation) ────────
|
|
73
|
-
export function SnapViewCoreInner({ snap, handlers, loading = false, loadingOverlay, }) {
|
|
40
|
+
export function SnapViewCoreInner({ snap, handlers, loading = false, loadingOverlay, initialRenderState, onRenderStateChange, }) {
|
|
74
41
|
const { mode } = useSnapTheme();
|
|
75
42
|
const spec = useMemo(() => withDefaultElementProps(snap.ui), [snap.ui]);
|
|
76
43
|
const accentHex = resolveAccentHex(snap.theme?.accent, mode);
|
|
77
|
-
const initialState = useMemo(() => ({
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
...(snap.theme ? { accent: snap.theme.accent } : {}),
|
|
83
|
-
},
|
|
84
|
-
}), [spec, snap.theme]);
|
|
44
|
+
const initialState = useMemo(() => buildInitialRenderState({
|
|
45
|
+
specState: spec.state,
|
|
46
|
+
initialRenderState,
|
|
47
|
+
themeAccent: snap.theme?.accent,
|
|
48
|
+
}), [initialRenderState, spec.state, snap.theme?.accent]);
|
|
85
49
|
const stateRef = useRef(initialState);
|
|
86
50
|
useEffect(() => {
|
|
87
|
-
stateRef.current =
|
|
88
|
-
inputs: {
|
|
89
|
-
...(initialState.inputs ?? {}),
|
|
90
|
-
},
|
|
91
|
-
theme: {
|
|
92
|
-
...(initialState.theme ?? {}),
|
|
93
|
-
},
|
|
94
|
-
};
|
|
51
|
+
stateRef.current = cloneSnapRenderState(initialState);
|
|
95
52
|
}, [initialState]);
|
|
96
53
|
useEffect(() => {
|
|
97
54
|
const catalogResult = snapJsonRenderCatalog.validate(spec);
|
|
@@ -104,16 +61,49 @@ export function SnapViewCoreInner({ snap, handlers, loading = false, loadingOver
|
|
|
104
61
|
useEffect(() => {
|
|
105
62
|
setPageKey((k) => k + 1);
|
|
106
63
|
}, [spec]);
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const
|
|
64
|
+
const effectSignature = snap.effects?.join("\u0000") ?? "";
|
|
65
|
+
const snapEffects = useMemo(() => (effectSignature ? effectSignature.split("\u0000") : []), [effectSignature]);
|
|
66
|
+
const showConfetti = snapEffects.includes("confetti");
|
|
67
|
+
const showFireworks = snapEffects.includes("fireworks");
|
|
68
|
+
const [effectRunKeys, setEffectRunKeys] = useState({
|
|
69
|
+
confetti: 0,
|
|
70
|
+
fireworks: 0,
|
|
71
|
+
});
|
|
72
|
+
const onRenderStateChangeRef = useRef(onRenderStateChange);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
onRenderStateChangeRef.current = onRenderStateChange;
|
|
75
|
+
}, [onRenderStateChange]);
|
|
111
76
|
useEffect(() => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
77
|
+
const effectsToPresent = getUnpresentedSnapEffects(stateRef.current, snapEffects);
|
|
78
|
+
if (effectsToPresent.length === 0) {
|
|
79
|
+
setEffectRunKeys((current) => {
|
|
80
|
+
const next = {
|
|
81
|
+
confetti: showConfetti ? current.confetti : 0,
|
|
82
|
+
fireworks: showFireworks ? current.fireworks : 0,
|
|
83
|
+
};
|
|
84
|
+
return next.confetti === current.confetti &&
|
|
85
|
+
next.fireworks === current.fireworks
|
|
86
|
+
? current
|
|
87
|
+
: next;
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (markSnapEffectsPresented(stateRef.current, effectsToPresent)) {
|
|
92
|
+
onRenderStateChangeRef.current?.(cloneSnapRenderState(stateRef.current));
|
|
93
|
+
}
|
|
94
|
+
setEffectRunKeys((current) => ({
|
|
95
|
+
confetti: effectsToPresent.includes("confetti")
|
|
96
|
+
? current.confetti + 1
|
|
97
|
+
: showConfetti
|
|
98
|
+
? current.confetti
|
|
99
|
+
: 0,
|
|
100
|
+
fireworks: effectsToPresent.includes("fireworks")
|
|
101
|
+
? current.fireworks + 1
|
|
102
|
+
: showFireworks
|
|
103
|
+
? current.fireworks
|
|
104
|
+
: 0,
|
|
105
|
+
}));
|
|
106
|
+
}, [initialState, showConfetti, showFireworks, snapEffects]);
|
|
117
107
|
const handlersRef = useRef(handlers);
|
|
118
108
|
handlersRef.current = handlers;
|
|
119
109
|
const handleAction = useCallback((name, params) => {
|
|
@@ -169,21 +159,16 @@ export function SnapViewCoreInner({ snap, handlers, loading = false, loadingOver
|
|
|
169
159
|
break;
|
|
170
160
|
}
|
|
171
161
|
}, []);
|
|
172
|
-
return (_jsxs(View, { style: styles.container, children: [loading
|
|
173
|
-
? loadingOverlay === undefined
|
|
174
|
-
? (_jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex }))
|
|
175
|
-
: loadingOverlay
|
|
176
|
-
: null, _jsx(SnapVersionProvider, { value: snap.version === "2.0" ? "2.0" : "1.0", children: _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
|
|
162
|
+
return (_jsxs(View, { style: styles.container, children: [loading ? (loadingOverlay === undefined ? (_jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex })) : (loadingOverlay)) : null, _jsx(SnapVersionProvider, { value: snap.version === "2.0" ? "2.0" : "1.0", children: _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
|
|
177
163
|
applyStatePaths(stateRef.current, changes);
|
|
178
|
-
|
|
164
|
+
onRenderStateChange?.(cloneSnapRenderState(stateRef.current));
|
|
165
|
+
}, onAction: handleAction }, pageKey) }), showConfetti && effectRunKeys.confetti > 0 && (_jsx(ConfettiOverlay, {}, effectRunKeys.confetti)), showFireworks && effectRunKeys.fireworks > 0 && (_jsx(FireworksOverlay, {}, effectRunKeys.fireworks))] }));
|
|
179
166
|
}
|
|
180
167
|
export function SnapLoadingOverlay({ appearance, accentHex, }) {
|
|
181
168
|
return (_jsx(View, { style: [
|
|
182
169
|
styles.overlay,
|
|
183
170
|
{
|
|
184
|
-
backgroundColor: appearance === "dark"
|
|
185
|
-
? "rgba(0,0,0,0.1)"
|
|
186
|
-
: "rgba(255,255,255,0.2)",
|
|
171
|
+
backgroundColor: appearance === "dark" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.2)",
|
|
187
172
|
},
|
|
188
173
|
], children: _jsx(ActivityIndicator, { size: "large", color: accentHex }) }));
|
|
189
174
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { type SnapNativeColors } from "../theme.js";
|
|
3
|
-
import type { SnapPage,
|
|
4
|
-
export declare function SnapViewV1Inner({ snap, handlers, loading, loadingOverlay, }: {
|
|
3
|
+
import type { SnapActionHandlers, SnapPage, SnapRenderState } from "../types.js";
|
|
4
|
+
export declare function SnapViewV1Inner({ snap, handlers, loading, loadingOverlay, initialRenderState, onRenderStateChange, }: {
|
|
5
5
|
snap: SnapPage;
|
|
6
6
|
handlers: SnapActionHandlers;
|
|
7
7
|
loading?: boolean;
|
|
8
8
|
loadingOverlay?: ReactNode;
|
|
9
|
+
initialRenderState?: SnapRenderState;
|
|
10
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
9
11
|
}): import("react").JSX.Element;
|
|
10
|
-
export declare function SnapViewV1({ snap, handlers, loading, appearance, colors, loadingOverlay, }: {
|
|
12
|
+
export declare function SnapViewV1({ snap, handlers, loading, appearance, colors, loadingOverlay, initialRenderState, onRenderStateChange, }: {
|
|
11
13
|
snap: SnapPage;
|
|
12
14
|
handlers: SnapActionHandlers;
|
|
13
15
|
loading?: boolean;
|
|
@@ -15,8 +17,10 @@ export declare function SnapViewV1({ snap, handlers, loading, appearance, colors
|
|
|
15
17
|
colors?: Partial<SnapNativeColors>;
|
|
16
18
|
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
17
19
|
loadingOverlay?: ReactNode;
|
|
20
|
+
initialRenderState?: SnapRenderState;
|
|
21
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
18
22
|
}): import("react").JSX.Element;
|
|
19
|
-
export declare function SnapCardV1({ snap, handlers, loading, appearance, colors, borderRadius, actionError, plain, loadingOverlay, forceExpanded, expandButtonLabel, onExpandPress, }: {
|
|
23
|
+
export declare function SnapCardV1({ snap, handlers, loading, appearance, colors, borderRadius, actionError, plain, loadingOverlay, forceExpanded, expandButtonLabel, onExpandPress, initialRenderState, onRenderStateChange, }: {
|
|
20
24
|
snap: SnapPage;
|
|
21
25
|
handlers: SnapActionHandlers;
|
|
22
26
|
loading?: boolean;
|
|
@@ -33,4 +37,8 @@ export declare function SnapCardV1({ snap, handlers, loading, appearance, colors
|
|
|
33
37
|
expandButtonLabel?: string;
|
|
34
38
|
/** Called from the collapsed expand button instead of toggling internal state. */
|
|
35
39
|
onExpandPress?: () => void;
|
|
40
|
+
/** JSON-render local state used to seed this presenter mount. */
|
|
41
|
+
initialRenderState?: SnapRenderState;
|
|
42
|
+
/** Called with the full JSON-render local state after state changes. */
|
|
43
|
+
onRenderStateChange?: (state: SnapRenderState) => void;
|
|
36
44
|
}): import("react").JSX.Element;
|