@farcaster/snap 2.0.1 → 2.0.3
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/react/components/action-button.js +16 -13
- package/dist/react/index.d.ts +3 -1
- package/dist/react/index.js +3 -3
- package/dist/react/snap-view-core.d.ts +12 -1
- package/dist/react/snap-view-core.js +7 -4
- package/dist/react/v1/snap-view.d.ts +7 -2
- package/dist/react/v1/snap-view.js +9 -7
- package/dist/react/v2/snap-view.d.ts +6 -2
- package/dist/react/v2/snap-view.js +7 -5
- package/dist/react-native/index.d.ts +3 -1
- package/dist/react-native/index.js +3 -3
- package/dist/react-native/snap-view-core.d.ts +11 -1
- package/dist/react-native/snap-view-core.js +20 -8
- package/dist/react-native/v1/snap-view.d.ts +9 -3
- package/dist/react-native/v1/snap-view.js +15 -10
- package/dist/react-native/v2/snap-view.d.ts +8 -3
- package/dist/react-native/v2/snap-view.js +21 -12
- package/dist/ui/catalog.js +1 -1
- package/dist/validator.js +7 -2
- package/llms.txt +4 -2
- package/package.json +1 -1
- package/src/react/components/action-button.tsx +16 -13
- package/src/react/index.tsx +5 -0
- package/src/react/snap-view-core.tsx +20 -6
- package/src/react/v1/snap-view.tsx +25 -2
- package/src/react/v2/snap-view.tsx +23 -1
- package/src/react-native/index.tsx +5 -0
- package/src/react-native/snap-view-core.tsx +51 -14
- package/src/react-native/v1/snap-view.tsx +37 -5
- package/src/react-native/v2/snap-view.tsx +41 -4
- package/src/ui/catalog.ts +1 -1
- package/src/validator.ts +7 -2
|
@@ -2,7 +2,11 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
import { useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { Platform, StyleSheet, Text, View } from "react-native";
|
|
4
4
|
import { SnapThemeProvider, useSnapTheme, type SnapNativeColors } from "../theme";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
SnapLoadingOverlay,
|
|
7
|
+
SnapViewCoreInner,
|
|
8
|
+
resolveAccentHex,
|
|
9
|
+
} from "../snap-view-core";
|
|
6
10
|
import {
|
|
7
11
|
validateSnapResponse,
|
|
8
12
|
type ValidationResult,
|
|
@@ -47,12 +51,14 @@ export function SnapViewV2Inner({
|
|
|
47
51
|
loading = false,
|
|
48
52
|
onValidationError,
|
|
49
53
|
validationErrorFallback,
|
|
54
|
+
loadingOverlay,
|
|
50
55
|
}: {
|
|
51
56
|
snap: SnapPage;
|
|
52
57
|
handlers: SnapActionHandlers;
|
|
53
58
|
loading?: boolean;
|
|
54
59
|
onValidationError?: (result: ValidationResult) => void;
|
|
55
60
|
validationErrorFallback?: ReactNode;
|
|
61
|
+
loadingOverlay?: ReactNode;
|
|
56
62
|
}) {
|
|
57
63
|
const validation = useMemo(() => validateSnapResponse(snap), [snap]);
|
|
58
64
|
const valid = validation.valid;
|
|
@@ -77,7 +83,12 @@ export function SnapViewV2Inner({
|
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
return (
|
|
80
|
-
<SnapViewCoreInner
|
|
86
|
+
<SnapViewCoreInner
|
|
87
|
+
snap={snap}
|
|
88
|
+
handlers={handlers}
|
|
89
|
+
loading={loading}
|
|
90
|
+
loadingOverlay={loadingOverlay}
|
|
91
|
+
/>
|
|
81
92
|
);
|
|
82
93
|
}
|
|
83
94
|
|
|
@@ -89,6 +100,7 @@ export function SnapViewV2({
|
|
|
89
100
|
colors,
|
|
90
101
|
onValidationError,
|
|
91
102
|
validationErrorFallback,
|
|
103
|
+
loadingOverlay,
|
|
92
104
|
}: {
|
|
93
105
|
snap: SnapPage;
|
|
94
106
|
handlers: SnapActionHandlers;
|
|
@@ -97,6 +109,8 @@ export function SnapViewV2({
|
|
|
97
109
|
colors?: Partial<SnapNativeColors>;
|
|
98
110
|
onValidationError?: (result: ValidationResult) => void;
|
|
99
111
|
validationErrorFallback?: ReactNode;
|
|
112
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
113
|
+
loadingOverlay?: ReactNode;
|
|
100
114
|
}) {
|
|
101
115
|
return (
|
|
102
116
|
<SnapThemeProvider appearance={appearance} colors={colors}>
|
|
@@ -106,6 +120,7 @@ export function SnapViewV2({
|
|
|
106
120
|
loading={loading}
|
|
107
121
|
onValidationError={onValidationError}
|
|
108
122
|
validationErrorFallback={validationErrorFallback}
|
|
123
|
+
loadingOverlay={loadingOverlay}
|
|
109
124
|
/>
|
|
110
125
|
</SnapThemeProvider>
|
|
111
126
|
);
|
|
@@ -124,6 +139,7 @@ function SnapCardV2Inner({
|
|
|
124
139
|
actionError,
|
|
125
140
|
appearance,
|
|
126
141
|
plain,
|
|
142
|
+
loadingOverlay,
|
|
127
143
|
}: {
|
|
128
144
|
snap: SnapPage;
|
|
129
145
|
handlers: SnapActionHandlers;
|
|
@@ -135,8 +151,10 @@ function SnapCardV2Inner({
|
|
|
135
151
|
actionError?: string | null;
|
|
136
152
|
appearance: "light" | "dark";
|
|
137
153
|
plain: boolean;
|
|
154
|
+
loadingOverlay?: ReactNode;
|
|
138
155
|
}) {
|
|
139
|
-
const { colors } = useSnapTheme();
|
|
156
|
+
const { colors, mode } = useSnapTheme();
|
|
157
|
+
const accentHex = resolveAccentHex(snap.theme?.accent, mode);
|
|
140
158
|
const [contentHeight, setContentHeight] = useState(0);
|
|
141
159
|
|
|
142
160
|
const content = (
|
|
@@ -146,11 +164,21 @@ function SnapCardV2Inner({
|
|
|
146
164
|
loading={loading}
|
|
147
165
|
onValidationError={onValidationError}
|
|
148
166
|
validationErrorFallback={validationErrorFallback}
|
|
167
|
+
loadingOverlay={null}
|
|
149
168
|
/>
|
|
150
169
|
);
|
|
151
170
|
|
|
152
171
|
if (plain) {
|
|
153
|
-
return
|
|
172
|
+
return (
|
|
173
|
+
<>
|
|
174
|
+
{content}
|
|
175
|
+
{loading
|
|
176
|
+
? loadingOverlay === undefined
|
|
177
|
+
? <SnapLoadingOverlay appearance={mode} accentHex={accentHex} />
|
|
178
|
+
: loadingOverlay
|
|
179
|
+
: null}
|
|
180
|
+
</>
|
|
181
|
+
);
|
|
154
182
|
}
|
|
155
183
|
|
|
156
184
|
const overflowAmount = showOverflowWarning ? contentHeight - SNAP_MAX_HEIGHT : 0;
|
|
@@ -184,6 +212,11 @@ function SnapCardV2Inner({
|
|
|
184
212
|
<View style={{ flex: 1, backgroundColor: "rgba(255,50,50,0.15)" }} />
|
|
185
213
|
</View>
|
|
186
214
|
)}
|
|
215
|
+
{loading
|
|
216
|
+
? loadingOverlay === undefined
|
|
217
|
+
? <SnapLoadingOverlay appearance={mode} accentHex={accentHex} />
|
|
218
|
+
: loadingOverlay
|
|
219
|
+
: null}
|
|
187
220
|
</View>
|
|
188
221
|
{actionError && (
|
|
189
222
|
<Text
|
|
@@ -216,6 +249,7 @@ export function SnapCardV2({
|
|
|
216
249
|
validationErrorFallback,
|
|
217
250
|
actionError,
|
|
218
251
|
plain = false,
|
|
252
|
+
loadingOverlay,
|
|
219
253
|
}: {
|
|
220
254
|
snap: SnapPage;
|
|
221
255
|
handlers: SnapActionHandlers;
|
|
@@ -228,6 +262,8 @@ export function SnapCardV2({
|
|
|
228
262
|
validationErrorFallback?: ReactNode;
|
|
229
263
|
actionError?: string | null;
|
|
230
264
|
plain?: boolean;
|
|
265
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
266
|
+
loadingOverlay?: ReactNode;
|
|
231
267
|
}) {
|
|
232
268
|
return (
|
|
233
269
|
<SnapThemeProvider appearance={appearance} colors={colors}>
|
|
@@ -242,6 +278,7 @@ export function SnapCardV2({
|
|
|
242
278
|
actionError={actionError}
|
|
243
279
|
appearance={appearance}
|
|
244
280
|
plain={plain}
|
|
281
|
+
loadingOverlay={loadingOverlay}
|
|
245
282
|
/>
|
|
246
283
|
</SnapThemeProvider>
|
|
247
284
|
);
|
package/src/ui/catalog.ts
CHANGED
|
@@ -58,7 +58,7 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
|
|
|
58
58
|
item: {
|
|
59
59
|
props: itemProps,
|
|
60
60
|
description:
|
|
61
|
-
"Content row with title and optional description. Children render in the actions slot (right side) —
|
|
61
|
+
"Content row with title and optional description. Children render in the actions slot (right side) — badge, button, and icon elements are all valid. The item itself is not interactive, so avoid navigation-style icons (`chevron-right`, `arrow-right`, `external-link`) that imply the row navigates.",
|
|
62
62
|
},
|
|
63
63
|
item_group: {
|
|
64
64
|
props: itemGroupProps,
|
package/src/validator.ts
CHANGED
|
@@ -11,7 +11,12 @@ export type ValidationResult = {
|
|
|
11
11
|
// ─── Helpers ──────────────────────────────────────────
|
|
12
12
|
|
|
13
13
|
/** Actions whose `params.target` must be a valid URL. */
|
|
14
|
-
const URL_TARGET_ACTIONS = new Set([
|
|
14
|
+
const URL_TARGET_ACTIONS = new Set([
|
|
15
|
+
"submit",
|
|
16
|
+
"open_url",
|
|
17
|
+
"open_snap",
|
|
18
|
+
"open_mini_app",
|
|
19
|
+
]);
|
|
15
20
|
|
|
16
21
|
/** Image file extensions allowed in image URLs. */
|
|
17
22
|
const ALLOWED_IMAGE_EXTENSIONS = new Set(["jpg", "jpeg", "png", "gif", "webp"]);
|
|
@@ -169,7 +174,7 @@ function validateStructure(
|
|
|
169
174
|
/**
|
|
170
175
|
* Validate all URLs in the snap:
|
|
171
176
|
* - image.url: must be HTTPS with allowed extension
|
|
172
|
-
* - action target URLs (submit, open_url, open_mini_app): must be HTTPS
|
|
177
|
+
* - action target URLs (submit, open_url, open_snap, open_mini_app): must be HTTPS
|
|
173
178
|
*/
|
|
174
179
|
function validateUrls(
|
|
175
180
|
elements: Record<string, unknown>,
|