@farcaster/snap 1.17.0 → 1.17.2
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-native/index.d.ts +3 -1
- package/dist/react-native/index.js +3 -3
- package/dist/react-native/v1/snap-view.d.ts +2 -1
- package/dist/react-native/v1/snap-view.js +21 -13
- package/dist/react-native/v2/snap-view.d.ts +2 -1
- package/dist/react-native/v2/snap-view.js +20 -12
- package/dist/ui/catalog.d.ts +5 -1
- package/dist/ui/schema.d.ts +5 -1
- package/dist/ui/schema.js +1 -1
- package/dist/validator.js +5 -0
- package/package.json +1 -1
- package/src/react-native/index.tsx +5 -0
- package/src/react-native/v1/snap-view.tsx +41 -15
- package/src/react-native/v2/snap-view.tsx +55 -29
- package/src/ui/schema.ts +1 -1
- package/src/validator.ts +6 -0
|
@@ -7,7 +7,7 @@ import { hexToRgba } from "./use-snap-palette.js";
|
|
|
7
7
|
export type { JsonValue, SnapPage, SnapActionHandlers } 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, }: {
|
|
10
|
+
export declare function SnapCard({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, }: {
|
|
11
11
|
snap: SnapPage;
|
|
12
12
|
handlers: SnapActionHandlers;
|
|
13
13
|
loading?: boolean;
|
|
@@ -21,4 +21,6 @@ export declare function SnapCard({ snap, handlers, loading, appearance, colors,
|
|
|
21
21
|
onValidationError?: (result: ValidationResult) => void;
|
|
22
22
|
/** Custom fallback rendered when validation fails (v2 only). */
|
|
23
23
|
validationErrorFallback?: ReactNode;
|
|
24
|
+
/** Server-side action error message to display inline. */
|
|
25
|
+
actionError?: string | null;
|
|
24
26
|
}): 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, }) {
|
|
10
|
+
export function SnapCard({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, }) {
|
|
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 }));
|
|
12
|
+
return (_jsx(SnapCardV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError }));
|
|
13
13
|
}
|
|
14
|
-
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius }));
|
|
14
|
+
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, actionError: actionError }));
|
|
15
15
|
}
|
|
@@ -12,11 +12,12 @@ export declare function SnapViewV1({ snap, handlers, loading, appearance, colors
|
|
|
12
12
|
appearance?: "light" | "dark";
|
|
13
13
|
colors?: Partial<SnapNativeColors>;
|
|
14
14
|
}): import("react").JSX.Element;
|
|
15
|
-
export declare function SnapCardV1({ snap, handlers, loading, appearance, colors, borderRadius, }: {
|
|
15
|
+
export declare function SnapCardV1({ snap, handlers, loading, appearance, colors, borderRadius, actionError, }: {
|
|
16
16
|
snap: SnapPage;
|
|
17
17
|
handlers: SnapActionHandlers;
|
|
18
18
|
loading?: boolean;
|
|
19
19
|
appearance?: "light" | "dark";
|
|
20
20
|
colors?: Partial<SnapNativeColors>;
|
|
21
21
|
borderRadius?: number;
|
|
22
|
+
actionError?: string | null;
|
|
22
23
|
}): import("react").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { View, StyleSheet } from "react-native";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
3
3
|
import { SnapThemeProvider, useSnapTheme } from "../theme.js";
|
|
4
4
|
import { SnapViewCoreInner } from "../snap-view-core.js";
|
|
5
5
|
// ─── SnapViewV1 (no validation, no height limits) ────
|
|
@@ -10,22 +10,30 @@ export function SnapViewV1({ snap, handlers, loading = false, appearance = "dark
|
|
|
10
10
|
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapViewV1Inner, { snap: snap, handlers: handlers, loading: loading }) }));
|
|
11
11
|
}
|
|
12
12
|
// ─── SnapCardV1 (card frame, no height limits) ───────
|
|
13
|
-
function SnapCardV1Inner({ snap, handlers, loading = false, borderRadius, }) {
|
|
13
|
+
function SnapCardV1Inner({ snap, handlers, loading = false, borderRadius, actionError, appearance, }) {
|
|
14
14
|
const { colors } = useSnapTheme();
|
|
15
|
-
return (_jsx(View, { style: cardStyles.frameRing, children: _jsx(View, { style: [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
return (_jsxs(_Fragment, { children: [_jsx(View, { style: cardStyles.frameRing, children: _jsx(View, { style: [
|
|
16
|
+
cardStyles.card,
|
|
17
|
+
{
|
|
18
|
+
borderRadius,
|
|
19
|
+
borderColor: colors.border,
|
|
20
|
+
backgroundColor: colors.surface,
|
|
21
|
+
},
|
|
22
|
+
], children: _jsx(View, { style: cardStyles.body, children: _jsx(SnapViewV1Inner, { snap: snap, handlers: handlers, loading: loading }) }) }) }), actionError && (_jsx(Text, { style: [
|
|
23
|
+
cardStyles.actionError,
|
|
24
|
+
{
|
|
25
|
+
color: appearance === "dark"
|
|
26
|
+
? "rgba(255,100,100,0.9)"
|
|
27
|
+
: "rgba(200,0,0,0.8)",
|
|
28
|
+
},
|
|
29
|
+
], children: actionError }))] }));
|
|
23
30
|
}
|
|
24
|
-
export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, }) {
|
|
25
|
-
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapCardV1Inner, { snap: snap, handlers: handlers, loading: loading, borderRadius: borderRadius }) }));
|
|
31
|
+
export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, actionError, }) {
|
|
32
|
+
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapCardV1Inner, { snap: snap, handlers: handlers, loading: loading, borderRadius: borderRadius, actionError: actionError, appearance: appearance }) }));
|
|
26
33
|
}
|
|
27
34
|
const cardStyles = StyleSheet.create({
|
|
28
35
|
frameRing: { alignSelf: "stretch" },
|
|
29
36
|
card: { overflow: "hidden", borderWidth: 1, minHeight: 120 },
|
|
30
37
|
body: { paddingHorizontal: 16, paddingVertical: 16 },
|
|
38
|
+
actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
|
|
31
39
|
});
|
|
@@ -18,7 +18,7 @@ export declare function SnapViewV2({ snap, handlers, loading, appearance, colors
|
|
|
18
18
|
onValidationError?: (result: ValidationResult) => void;
|
|
19
19
|
validationErrorFallback?: ReactNode;
|
|
20
20
|
}): import("react").JSX.Element;
|
|
21
|
-
export declare function SnapCardV2({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, }: {
|
|
21
|
+
export declare function SnapCardV2({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, }: {
|
|
22
22
|
snap: SnapPage;
|
|
23
23
|
handlers: SnapActionHandlers;
|
|
24
24
|
loading?: boolean;
|
|
@@ -28,4 +28,5 @@ export declare function SnapCardV2({ snap, handlers, loading, appearance, colors
|
|
|
28
28
|
showOverflowWarning?: boolean;
|
|
29
29
|
onValidationError?: (result: ValidationResult) => void;
|
|
30
30
|
validationErrorFallback?: ReactNode;
|
|
31
|
+
actionError?: string | null;
|
|
31
32
|
}): import("react").JSX.Element;
|
|
@@ -50,26 +50,34 @@ export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark
|
|
|
50
50
|
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback }) }));
|
|
51
51
|
}
|
|
52
52
|
// ─── SnapCardV2 (card frame + height limits) ─────────
|
|
53
|
-
function SnapCardV2Inner({ snap, handlers, loading, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, }) {
|
|
53
|
+
function SnapCardV2Inner({ snap, handlers, loading, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, appearance, }) {
|
|
54
54
|
const { colors } = useSnapTheme();
|
|
55
55
|
const maxHeight = showOverflowWarning ? SNAP_WARNING_HEIGHT : SNAP_MAX_HEIGHT;
|
|
56
|
-
return (_jsx(View, { style: cardStyles.frameRing, children: _jsxs(View, { style: [
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
return (_jsxs(_Fragment, { children: [_jsx(View, { style: cardStyles.frameRing, children: _jsxs(View, { style: [
|
|
57
|
+
cardStyles.card,
|
|
58
|
+
{
|
|
59
|
+
borderRadius,
|
|
60
|
+
maxHeight,
|
|
61
|
+
borderColor: colors.border,
|
|
62
|
+
backgroundColor: colors.surface,
|
|
63
|
+
},
|
|
64
|
+
], children: [_jsx(View, { style: cardStyles.body, children: _jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback }) }), showOverflowWarning && (_jsxs(View, { style: cardStyles.warningOverlay, children: [_jsx(View, { style: cardStyles.warningLine }), _jsx(View, { style: cardStyles.warningLabel, children: _jsxs(Text, { style: cardStyles.warningLabelText, children: [SNAP_MAX_HEIGHT, "px"] }) })] }))] }) }), actionError && (_jsx(Text, { style: [
|
|
65
|
+
cardStyles.actionError,
|
|
66
|
+
{
|
|
67
|
+
color: appearance === "dark"
|
|
68
|
+
? "rgba(255,100,100,0.9)"
|
|
69
|
+
: "rgba(200,0,0,0.8)",
|
|
70
|
+
},
|
|
71
|
+
], children: actionError }))] }));
|
|
65
72
|
}
|
|
66
|
-
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, }) {
|
|
67
|
-
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapCardV2Inner, { snap: snap, handlers: handlers, loading: loading, borderRadius: borderRadius, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback }) }));
|
|
73
|
+
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, }) {
|
|
74
|
+
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapCardV2Inner, { snap: snap, handlers: handlers, loading: loading, borderRadius: borderRadius, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, appearance: appearance }) }));
|
|
68
75
|
}
|
|
69
76
|
const cardStyles = StyleSheet.create({
|
|
70
77
|
frameRing: { alignSelf: "stretch" },
|
|
71
78
|
card: { overflow: "hidden", borderWidth: 1, minHeight: 120 },
|
|
72
79
|
body: { paddingHorizontal: 16, paddingVertical: 16 },
|
|
80
|
+
actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
|
|
73
81
|
warningOverlay: {
|
|
74
82
|
position: "absolute",
|
|
75
83
|
top: SNAP_MAX_HEIGHT,
|
package/dist/ui/catalog.d.ts
CHANGED
|
@@ -10,7 +10,11 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
|
|
|
10
10
|
root: import("@json-render/core").SchemaType<"string", unknown>;
|
|
11
11
|
elements: import("@json-render/core").SchemaType<"record", import("@json-render/core").SchemaType<"object", {
|
|
12
12
|
type: import("@json-render/core").SchemaType<"ref", string>;
|
|
13
|
-
props:
|
|
13
|
+
props: {
|
|
14
|
+
optional: true;
|
|
15
|
+
kind: "propsOf";
|
|
16
|
+
inner?: string;
|
|
17
|
+
};
|
|
14
18
|
children: {
|
|
15
19
|
optional: true;
|
|
16
20
|
kind: "array";
|
package/dist/ui/schema.d.ts
CHANGED
|
@@ -7,7 +7,11 @@ export declare const snapJsonRenderSchema: import("@json-render/core").Schema<{
|
|
|
7
7
|
root: import("@json-render/core").SchemaType<"string", unknown>;
|
|
8
8
|
elements: import("@json-render/core").SchemaType<"record", import("@json-render/core").SchemaType<"object", {
|
|
9
9
|
type: import("@json-render/core").SchemaType<"ref", string>;
|
|
10
|
-
props:
|
|
10
|
+
props: {
|
|
11
|
+
optional: true;
|
|
12
|
+
kind: "propsOf";
|
|
13
|
+
inner?: string;
|
|
14
|
+
};
|
|
11
15
|
children: {
|
|
12
16
|
optional: true;
|
|
13
17
|
kind: "array";
|
package/dist/ui/schema.js
CHANGED
|
@@ -8,7 +8,7 @@ export const snapJsonRenderSchema = defineSchema((s) => ({
|
|
|
8
8
|
root: s.string(),
|
|
9
9
|
elements: s.record(s.object({
|
|
10
10
|
type: s.ref("catalog.components"),
|
|
11
|
-
props: s.propsOf("catalog.components"),
|
|
11
|
+
props: { ...s.propsOf("catalog.components"), optional: true },
|
|
12
12
|
children: { ...s.array(s.string()), optional: true },
|
|
13
13
|
})),
|
|
14
14
|
}),
|
package/dist/validator.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { snapResponseSchema } from "./schemas.js";
|
|
2
2
|
import { MAX_CHILDREN, MAX_DEPTH, MAX_ELEMENTS, MAX_ROOT_CHILDREN, SPEC_VERSION_1 } from "./constants.js";
|
|
3
|
+
import { snapJsonRenderCatalog } from "./ui/catalog.js";
|
|
3
4
|
// ─── Helpers ──────────────────────────────────────────
|
|
4
5
|
/** Actions whose `params.target` must be a valid URL. */
|
|
5
6
|
const URL_TARGET_ACTIONS = new Set(["submit", "open_url", "open_mini_app"]);
|
|
@@ -202,6 +203,10 @@ export function validateSnapResponse(json) {
|
|
|
202
203
|
if (urlIssues.length > 0) {
|
|
203
204
|
return { valid: false, issues: urlIssues };
|
|
204
205
|
}
|
|
206
|
+
const catalogResult = snapJsonRenderCatalog.validate(ui);
|
|
207
|
+
if (!catalogResult.success) {
|
|
208
|
+
return { valid: false, issues: catalogResult.error?.issues ?? [] };
|
|
209
|
+
}
|
|
205
210
|
}
|
|
206
211
|
return { valid: true, issues: [] };
|
|
207
212
|
}
|
package/package.json
CHANGED
|
@@ -29,6 +29,7 @@ export function SnapCard({
|
|
|
29
29
|
showOverflowWarning = false,
|
|
30
30
|
onValidationError,
|
|
31
31
|
validationErrorFallback,
|
|
32
|
+
actionError,
|
|
32
33
|
}: {
|
|
33
34
|
snap: SnapPage;
|
|
34
35
|
handlers: SnapActionHandlers;
|
|
@@ -43,6 +44,8 @@ export function SnapCard({
|
|
|
43
44
|
onValidationError?: (result: ValidationResult) => void;
|
|
44
45
|
/** Custom fallback rendered when validation fails (v2 only). */
|
|
45
46
|
validationErrorFallback?: ReactNode;
|
|
47
|
+
/** Server-side action error message to display inline. */
|
|
48
|
+
actionError?: string | null;
|
|
46
49
|
}) {
|
|
47
50
|
if (snap.version === SPEC_VERSION_2) {
|
|
48
51
|
return (
|
|
@@ -56,6 +59,7 @@ export function SnapCard({
|
|
|
56
59
|
showOverflowWarning={showOverflowWarning}
|
|
57
60
|
onValidationError={onValidationError}
|
|
58
61
|
validationErrorFallback={validationErrorFallback}
|
|
62
|
+
actionError={actionError}
|
|
59
63
|
/>
|
|
60
64
|
);
|
|
61
65
|
}
|
|
@@ -68,6 +72,7 @@ export function SnapCard({
|
|
|
68
72
|
appearance={appearance}
|
|
69
73
|
colors={colors}
|
|
70
74
|
borderRadius={borderRadius}
|
|
75
|
+
actionError={actionError}
|
|
71
76
|
/>
|
|
72
77
|
);
|
|
73
78
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { View, StyleSheet } from "react-native";
|
|
1
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
2
2
|
import { SnapThemeProvider, useSnapTheme, type SnapNativeColors } from "../theme";
|
|
3
3
|
import { SnapViewCoreInner } from "../snap-view-core";
|
|
4
4
|
import type { SnapPage, SnapActionHandlers } from "../types";
|
|
@@ -46,31 +46,52 @@ function SnapCardV1Inner({
|
|
|
46
46
|
handlers,
|
|
47
47
|
loading = false,
|
|
48
48
|
borderRadius,
|
|
49
|
+
actionError,
|
|
50
|
+
appearance,
|
|
49
51
|
}: {
|
|
50
52
|
snap: SnapPage;
|
|
51
53
|
handlers: SnapActionHandlers;
|
|
52
54
|
loading?: boolean;
|
|
53
55
|
borderRadius: number;
|
|
56
|
+
actionError?: string | null;
|
|
57
|
+
appearance: "light" | "dark";
|
|
54
58
|
}) {
|
|
55
59
|
const { colors } = useSnapTheme();
|
|
56
60
|
|
|
57
61
|
return (
|
|
58
|
-
|
|
59
|
-
<View
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<
|
|
62
|
+
<>
|
|
63
|
+
<View style={cardStyles.frameRing}>
|
|
64
|
+
<View
|
|
65
|
+
style={[
|
|
66
|
+
cardStyles.card,
|
|
67
|
+
{
|
|
68
|
+
borderRadius,
|
|
69
|
+
borderColor: colors.border,
|
|
70
|
+
backgroundColor: colors.surface,
|
|
71
|
+
},
|
|
72
|
+
]}
|
|
73
|
+
>
|
|
74
|
+
<View style={cardStyles.body}>
|
|
75
|
+
<SnapViewV1Inner snap={snap} handlers={handlers} loading={loading} />
|
|
76
|
+
</View>
|
|
71
77
|
</View>
|
|
72
78
|
</View>
|
|
73
|
-
|
|
79
|
+
{actionError && (
|
|
80
|
+
<Text
|
|
81
|
+
style={[
|
|
82
|
+
cardStyles.actionError,
|
|
83
|
+
{
|
|
84
|
+
color:
|
|
85
|
+
appearance === "dark"
|
|
86
|
+
? "rgba(255,100,100,0.9)"
|
|
87
|
+
: "rgba(200,0,0,0.8)",
|
|
88
|
+
},
|
|
89
|
+
]}
|
|
90
|
+
>
|
|
91
|
+
{actionError}
|
|
92
|
+
</Text>
|
|
93
|
+
)}
|
|
94
|
+
</>
|
|
74
95
|
);
|
|
75
96
|
}
|
|
76
97
|
|
|
@@ -81,6 +102,7 @@ export function SnapCardV1({
|
|
|
81
102
|
appearance = "dark",
|
|
82
103
|
colors,
|
|
83
104
|
borderRadius = 16,
|
|
105
|
+
actionError,
|
|
84
106
|
}: {
|
|
85
107
|
snap: SnapPage;
|
|
86
108
|
handlers: SnapActionHandlers;
|
|
@@ -88,6 +110,7 @@ export function SnapCardV1({
|
|
|
88
110
|
appearance?: "light" | "dark";
|
|
89
111
|
colors?: Partial<SnapNativeColors>;
|
|
90
112
|
borderRadius?: number;
|
|
113
|
+
actionError?: string | null;
|
|
91
114
|
}) {
|
|
92
115
|
return (
|
|
93
116
|
<SnapThemeProvider appearance={appearance} colors={colors}>
|
|
@@ -96,6 +119,8 @@ export function SnapCardV1({
|
|
|
96
119
|
handlers={handlers}
|
|
97
120
|
loading={loading}
|
|
98
121
|
borderRadius={borderRadius}
|
|
122
|
+
actionError={actionError}
|
|
123
|
+
appearance={appearance}
|
|
99
124
|
/>
|
|
100
125
|
</SnapThemeProvider>
|
|
101
126
|
);
|
|
@@ -105,4 +130,5 @@ const cardStyles = StyleSheet.create({
|
|
|
105
130
|
frameRing: { alignSelf: "stretch" },
|
|
106
131
|
card: { overflow: "hidden", borderWidth: 1, minHeight: 120 },
|
|
107
132
|
body: { paddingHorizontal: 16, paddingVertical: 16 },
|
|
133
|
+
actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
|
|
108
134
|
});
|
|
@@ -121,6 +121,8 @@ function SnapCardV2Inner({
|
|
|
121
121
|
showOverflowWarning,
|
|
122
122
|
onValidationError,
|
|
123
123
|
validationErrorFallback,
|
|
124
|
+
actionError,
|
|
125
|
+
appearance,
|
|
124
126
|
}: {
|
|
125
127
|
snap: SnapPage;
|
|
126
128
|
handlers: SnapActionHandlers;
|
|
@@ -129,42 +131,61 @@ function SnapCardV2Inner({
|
|
|
129
131
|
showOverflowWarning: boolean;
|
|
130
132
|
onValidationError?: (result: ValidationResult) => void;
|
|
131
133
|
validationErrorFallback?: ReactNode;
|
|
134
|
+
actionError?: string | null;
|
|
135
|
+
appearance: "light" | "dark";
|
|
132
136
|
}) {
|
|
133
137
|
const { colors } = useSnapTheme();
|
|
134
138
|
const maxHeight = showOverflowWarning ? SNAP_WARNING_HEIGHT : SNAP_MAX_HEIGHT;
|
|
135
139
|
|
|
136
140
|
return (
|
|
137
|
-
|
|
138
|
-
<View
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
{showOverflowWarning && (
|
|
159
|
-
<View style={cardStyles.warningOverlay}>
|
|
160
|
-
<View style={cardStyles.warningLine} />
|
|
161
|
-
<View style={cardStyles.warningLabel}>
|
|
162
|
-
<Text style={cardStyles.warningLabelText}>{SNAP_MAX_HEIGHT}px</Text>
|
|
163
|
-
</View>
|
|
141
|
+
<>
|
|
142
|
+
<View style={cardStyles.frameRing}>
|
|
143
|
+
<View
|
|
144
|
+
style={[
|
|
145
|
+
cardStyles.card,
|
|
146
|
+
{
|
|
147
|
+
borderRadius,
|
|
148
|
+
maxHeight,
|
|
149
|
+
borderColor: colors.border,
|
|
150
|
+
backgroundColor: colors.surface,
|
|
151
|
+
},
|
|
152
|
+
]}
|
|
153
|
+
>
|
|
154
|
+
<View style={cardStyles.body}>
|
|
155
|
+
<SnapViewV2Inner
|
|
156
|
+
snap={snap}
|
|
157
|
+
handlers={handlers}
|
|
158
|
+
loading={loading}
|
|
159
|
+
onValidationError={onValidationError}
|
|
160
|
+
validationErrorFallback={validationErrorFallback}
|
|
161
|
+
/>
|
|
164
162
|
</View>
|
|
165
|
-
|
|
163
|
+
{showOverflowWarning && (
|
|
164
|
+
<View style={cardStyles.warningOverlay}>
|
|
165
|
+
<View style={cardStyles.warningLine} />
|
|
166
|
+
<View style={cardStyles.warningLabel}>
|
|
167
|
+
<Text style={cardStyles.warningLabelText}>{SNAP_MAX_HEIGHT}px</Text>
|
|
168
|
+
</View>
|
|
169
|
+
</View>
|
|
170
|
+
)}
|
|
171
|
+
</View>
|
|
166
172
|
</View>
|
|
167
|
-
|
|
173
|
+
{actionError && (
|
|
174
|
+
<Text
|
|
175
|
+
style={[
|
|
176
|
+
cardStyles.actionError,
|
|
177
|
+
{
|
|
178
|
+
color:
|
|
179
|
+
appearance === "dark"
|
|
180
|
+
? "rgba(255,100,100,0.9)"
|
|
181
|
+
: "rgba(200,0,0,0.8)",
|
|
182
|
+
},
|
|
183
|
+
]}
|
|
184
|
+
>
|
|
185
|
+
{actionError}
|
|
186
|
+
</Text>
|
|
187
|
+
)}
|
|
188
|
+
</>
|
|
168
189
|
);
|
|
169
190
|
}
|
|
170
191
|
|
|
@@ -178,6 +199,7 @@ export function SnapCardV2({
|
|
|
178
199
|
showOverflowWarning = false,
|
|
179
200
|
onValidationError,
|
|
180
201
|
validationErrorFallback,
|
|
202
|
+
actionError,
|
|
181
203
|
}: {
|
|
182
204
|
snap: SnapPage;
|
|
183
205
|
handlers: SnapActionHandlers;
|
|
@@ -188,6 +210,7 @@ export function SnapCardV2({
|
|
|
188
210
|
showOverflowWarning?: boolean;
|
|
189
211
|
onValidationError?: (result: ValidationResult) => void;
|
|
190
212
|
validationErrorFallback?: ReactNode;
|
|
213
|
+
actionError?: string | null;
|
|
191
214
|
}) {
|
|
192
215
|
return (
|
|
193
216
|
<SnapThemeProvider appearance={appearance} colors={colors}>
|
|
@@ -199,6 +222,8 @@ export function SnapCardV2({
|
|
|
199
222
|
showOverflowWarning={showOverflowWarning}
|
|
200
223
|
onValidationError={onValidationError}
|
|
201
224
|
validationErrorFallback={validationErrorFallback}
|
|
225
|
+
actionError={actionError}
|
|
226
|
+
appearance={appearance}
|
|
202
227
|
/>
|
|
203
228
|
</SnapThemeProvider>
|
|
204
229
|
);
|
|
@@ -208,6 +233,7 @@ const cardStyles = StyleSheet.create({
|
|
|
208
233
|
frameRing: { alignSelf: "stretch" },
|
|
209
234
|
card: { overflow: "hidden", borderWidth: 1, minHeight: 120 },
|
|
210
235
|
body: { paddingHorizontal: 16, paddingVertical: 16 },
|
|
236
|
+
actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
|
|
211
237
|
warningOverlay: {
|
|
212
238
|
position: "absolute",
|
|
213
239
|
top: SNAP_MAX_HEIGHT,
|
package/src/ui/schema.ts
CHANGED
|
@@ -11,7 +11,7 @@ export const snapJsonRenderSchema = defineSchema(
|
|
|
11
11
|
elements: s.record(
|
|
12
12
|
s.object({
|
|
13
13
|
type: s.ref("catalog.components"),
|
|
14
|
-
props: s.propsOf("catalog.components"),
|
|
14
|
+
props: { ...s.propsOf("catalog.components"), optional: true },
|
|
15
15
|
children: { ...s.array(s.string()), optional: true },
|
|
16
16
|
}),
|
|
17
17
|
),
|
package/src/validator.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { snapResponseSchema } from "./schemas";
|
|
3
3
|
import { MAX_CHILDREN, MAX_DEPTH, MAX_ELEMENTS, MAX_ROOT_CHILDREN, SPEC_VERSION_1 } from "./constants";
|
|
4
|
+
import { snapJsonRenderCatalog } from "./ui/catalog.js";
|
|
4
5
|
|
|
5
6
|
export type ValidationResult = {
|
|
6
7
|
valid: boolean;
|
|
@@ -255,6 +256,11 @@ export function validateSnapResponse(json: unknown): ValidationResult {
|
|
|
255
256
|
if (urlIssues.length > 0) {
|
|
256
257
|
return { valid: false, issues: urlIssues };
|
|
257
258
|
}
|
|
259
|
+
|
|
260
|
+
const catalogResult = snapJsonRenderCatalog.validate(ui);
|
|
261
|
+
if (!catalogResult.success) {
|
|
262
|
+
return { valid: false, issues: catalogResult.error?.issues ?? [] };
|
|
263
|
+
}
|
|
258
264
|
}
|
|
259
265
|
|
|
260
266
|
return { valid: true, issues: [] };
|