@farcaster/snap 2.0.0 → 2.0.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.
Files changed (194) hide show
  1. package/dist/colors.d.ts +4 -4
  2. package/dist/colors.js +20 -20
  3. package/dist/constants.d.ts +17 -1
  4. package/dist/constants.js +19 -1
  5. package/dist/index.d.ts +4 -6
  6. package/dist/index.js +2 -4
  7. package/dist/react/accent-context.d.ts +3 -1
  8. package/dist/react/accent-context.js +7 -4
  9. package/dist/react/catalog-renderer.js +4 -0
  10. package/dist/react/components/action-button.d.ts +2 -1
  11. package/dist/react/components/action-button.js +32 -13
  12. package/dist/react/components/badge.js +8 -8
  13. package/dist/react/components/bar-chart.d.ts +5 -0
  14. package/dist/react/components/bar-chart.js +26 -0
  15. package/dist/react/components/cell-grid.d.ts +5 -0
  16. package/dist/react/components/cell-grid.js +87 -0
  17. package/dist/react/components/icon.js +4 -10
  18. package/dist/react/components/input.js +12 -6
  19. package/dist/react/components/item-group.js +3 -1
  20. package/dist/react/components/item.d.ts +3 -3
  21. package/dist/react/components/item.js +4 -3
  22. package/dist/react/components/progress.js +3 -3
  23. package/dist/react/components/separator.js +3 -1
  24. package/dist/react/components/slider.js +15 -10
  25. package/dist/react/components/switch.js +10 -12
  26. package/dist/react/components/text.js +6 -14
  27. package/dist/react/components/toggle-group.js +20 -6
  28. package/dist/react/hooks/use-snap-colors.d.ts +38 -0
  29. package/dist/react/hooks/use-snap-colors.js +81 -0
  30. package/dist/react/index.d.ts +13 -1
  31. package/dist/react/index.js +9 -188
  32. package/dist/react/snap-view-core.d.ts +11 -0
  33. package/dist/react/snap-view-core.js +224 -0
  34. package/dist/react/v1/snap-view.d.ts +16 -0
  35. package/dist/react/v1/snap-view.js +90 -0
  36. package/dist/react/v2/snap-view.d.ts +23 -0
  37. package/dist/react/v2/snap-view.js +91 -0
  38. package/dist/react-native/catalog-renderer.d.ts +5 -0
  39. package/dist/react-native/catalog-renderer.js +40 -0
  40. package/dist/react-native/components/snap-action-button.d.ts +2 -0
  41. package/dist/react-native/components/snap-action-button.js +69 -0
  42. package/dist/react-native/components/snap-badge.d.ts +2 -0
  43. package/dist/react-native/components/snap-badge.js +41 -0
  44. package/dist/react-native/components/snap-bar-chart.d.ts +2 -0
  45. package/dist/react-native/components/snap-bar-chart.js +39 -0
  46. package/dist/react-native/components/snap-cell-grid.d.ts +2 -0
  47. package/dist/react-native/components/snap-cell-grid.js +94 -0
  48. package/dist/react-native/components/snap-icon.d.ts +5 -0
  49. package/dist/react-native/components/snap-icon.js +56 -0
  50. package/dist/react-native/components/snap-image.d.ts +2 -0
  51. package/dist/react-native/components/snap-image.js +23 -0
  52. package/dist/react-native/components/snap-input.d.ts +2 -0
  53. package/dist/react-native/components/snap-input.js +37 -0
  54. package/dist/react-native/components/snap-item-group.d.ts +5 -0
  55. package/dist/react-native/components/snap-item-group.js +23 -0
  56. package/dist/react-native/components/snap-item.d.ts +5 -0
  57. package/dist/react-native/components/snap-item.js +42 -0
  58. package/dist/react-native/components/snap-progress.d.ts +2 -0
  59. package/dist/react-native/components/snap-progress.js +26 -0
  60. package/dist/react-native/components/snap-separator.d.ts +2 -0
  61. package/dist/react-native/components/snap-separator.js +23 -0
  62. package/dist/react-native/components/snap-slider.d.ts +2 -0
  63. package/dist/react-native/components/snap-slider.js +43 -0
  64. package/dist/react-native/components/snap-stack.d.ts +5 -0
  65. package/dist/react-native/components/snap-stack.js +49 -0
  66. package/dist/react-native/components/snap-switch.d.ts +2 -0
  67. package/dist/react-native/components/snap-switch.js +31 -0
  68. package/dist/react-native/components/snap-text.d.ts +2 -0
  69. package/dist/react-native/components/snap-text.js +35 -0
  70. package/dist/react-native/components/snap-toggle-group.d.ts +2 -0
  71. package/dist/react-native/components/snap-toggle-group.js +99 -0
  72. package/dist/react-native/confetti-overlay.d.ts +1 -0
  73. package/dist/react-native/confetti-overlay.js +106 -0
  74. package/dist/react-native/index.d.ts +28 -0
  75. package/dist/react-native/index.js +15 -0
  76. package/dist/react-native/snap-view-core.d.ts +11 -0
  77. package/dist/react-native/snap-view-core.js +153 -0
  78. package/dist/react-native/theme.d.ts +27 -0
  79. package/dist/react-native/theme.js +43 -0
  80. package/dist/react-native/types.d.ts +42 -0
  81. package/dist/react-native/types.js +1 -0
  82. package/dist/react-native/use-snap-palette.d.ts +13 -0
  83. package/dist/react-native/use-snap-palette.js +48 -0
  84. package/dist/react-native/v1/snap-view.d.ts +24 -0
  85. package/dist/react-native/v1/snap-view.js +96 -0
  86. package/dist/react-native/v2/snap-view.d.ts +33 -0
  87. package/dist/react-native/v2/snap-view.js +114 -0
  88. package/dist/schemas.d.ts +100 -13
  89. package/dist/schemas.js +28 -10
  90. package/dist/server/parseRequest.d.ts +10 -0
  91. package/dist/server/parseRequest.js +48 -7
  92. package/dist/server/verify.d.ts +1 -0
  93. package/dist/server/verify.js +1 -0
  94. package/dist/ui/badge.d.ts +7 -2
  95. package/dist/ui/badge.js +2 -0
  96. package/dist/ui/bar-chart.d.ts +30 -0
  97. package/dist/ui/bar-chart.js +30 -0
  98. package/dist/ui/button.d.ts +4 -6
  99. package/dist/ui/button.js +1 -1
  100. package/dist/ui/catalog.d.ts +90 -16
  101. package/dist/ui/catalog.js +17 -3
  102. package/dist/ui/cell-grid.d.ts +34 -0
  103. package/dist/ui/cell-grid.js +39 -0
  104. package/dist/ui/icon.d.ts +2 -2
  105. package/dist/ui/image.d.ts +1 -2
  106. package/dist/ui/image.js +1 -1
  107. package/dist/ui/index.d.ts +4 -0
  108. package/dist/ui/index.js +2 -0
  109. package/dist/ui/item.d.ts +1 -3
  110. package/dist/ui/item.js +1 -1
  111. package/dist/ui/schema.d.ts +6 -2
  112. package/dist/ui/schema.js +2 -2
  113. package/dist/ui/slider.d.ts +1 -0
  114. package/dist/ui/slider.js +2 -0
  115. package/dist/ui/text.d.ts +2 -4
  116. package/dist/ui/text.js +2 -2
  117. package/dist/validator.d.ts +3 -2
  118. package/dist/validator.js +198 -2
  119. package/llms.txt +199 -0
  120. package/package.json +9 -3
  121. package/src/colors.ts +20 -20
  122. package/src/constants.ts +23 -1
  123. package/src/index.ts +16 -13
  124. package/src/react/accent-context.tsx +13 -6
  125. package/src/react/catalog-renderer.tsx +4 -0
  126. package/src/react/components/action-button.tsx +47 -20
  127. package/src/react/components/badge.tsx +14 -18
  128. package/src/react/components/bar-chart.tsx +69 -0
  129. package/src/react/components/cell-grid.tsx +128 -0
  130. package/src/react/components/icon.tsx +5 -18
  131. package/src/react/components/input.tsx +20 -9
  132. package/src/react/components/item-group.tsx +4 -1
  133. package/src/react/components/item.tsx +13 -10
  134. package/src/react/components/progress.tsx +12 -7
  135. package/src/react/components/separator.tsx +8 -1
  136. package/src/react/components/slider.tsx +28 -15
  137. package/src/react/components/switch.tsx +12 -16
  138. package/src/react/components/text.tsx +14 -23
  139. package/src/react/components/toggle-group.tsx +26 -9
  140. package/src/react/hooks/use-snap-colors.ts +128 -0
  141. package/src/react/index.tsx +49 -265
  142. package/src/react/snap-view-core.tsx +340 -0
  143. package/src/react/v1/snap-view.tsx +176 -0
  144. package/src/react/v2/snap-view.tsx +199 -0
  145. package/src/react-native/catalog-renderer.tsx +41 -0
  146. package/src/react-native/components/snap-action-button.tsx +96 -0
  147. package/src/react-native/components/snap-badge.tsx +60 -0
  148. package/src/react-native/components/snap-bar-chart.tsx +73 -0
  149. package/src/react-native/components/snap-cell-grid.tsx +150 -0
  150. package/src/react-native/components/snap-icon.tsx +102 -0
  151. package/src/react-native/components/snap-image.tsx +37 -0
  152. package/src/react-native/components/snap-input.tsx +58 -0
  153. package/src/react-native/components/snap-item-group.tsx +43 -0
  154. package/src/react-native/components/snap-item.tsx +66 -0
  155. package/src/react-native/components/snap-progress.tsx +40 -0
  156. package/src/react-native/components/snap-separator.tsx +32 -0
  157. package/src/react-native/components/snap-slider.tsx +85 -0
  158. package/src/react-native/components/snap-stack.tsx +66 -0
  159. package/src/react-native/components/snap-switch.tsx +46 -0
  160. package/src/react-native/components/snap-text.tsx +51 -0
  161. package/src/react-native/components/snap-toggle-group.tsx +127 -0
  162. package/src/react-native/confetti-overlay.tsx +134 -0
  163. package/src/react-native/index.tsx +83 -0
  164. package/src/react-native/snap-view-core.tsx +209 -0
  165. package/src/react-native/theme.tsx +85 -0
  166. package/src/react-native/types.ts +38 -0
  167. package/src/react-native/use-snap-palette.ts +64 -0
  168. package/src/react-native/v1/snap-view.tsx +229 -0
  169. package/src/react-native/v2/snap-view.tsx +283 -0
  170. package/src/schemas.ts +68 -17
  171. package/src/server/parseRequest.ts +68 -9
  172. package/src/server/verify.ts +2 -0
  173. package/src/ui/README.md +8 -8
  174. package/src/ui/badge.ts +2 -0
  175. package/src/ui/bar-chart.ts +38 -0
  176. package/src/ui/button.ts +1 -1
  177. package/src/ui/catalog.ts +19 -3
  178. package/src/ui/cell-grid.ts +49 -0
  179. package/src/ui/image.ts +1 -1
  180. package/src/ui/index.ts +6 -0
  181. package/src/ui/item.ts +1 -1
  182. package/src/ui/schema.ts +2 -2
  183. package/src/ui/slider.ts +2 -0
  184. package/src/ui/text.ts +2 -2
  185. package/src/validator.ts +246 -2
  186. package/dist/dataStore.d.ts +0 -12
  187. package/dist/dataStore.js +0 -35
  188. package/dist/middleware.d.ts +0 -3
  189. package/dist/middleware.js +0 -3
  190. package/dist/react/hooks/use-snap-accent.d.ts +0 -13
  191. package/dist/react/hooks/use-snap-accent.js +0 -32
  192. package/src/dataStore.ts +0 -62
  193. package/src/middleware.ts +0 -7
  194. package/src/react/hooks/use-snap-accent.ts +0 -45
@@ -0,0 +1,153 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { snapJsonRenderCatalog } from "@farcaster/snap/ui";
3
+ import { SnapCatalogView } from "./catalog-renderer.js";
4
+ import { useSnapTheme } from "./theme.js";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+ import { ActivityIndicator, StyleSheet, View } from "react-native";
7
+ import { DEFAULT_THEME_ACCENT, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "@farcaster/snap";
8
+ // ─── Shared helpers ──────────────────────────────────
9
+ export function applyStatePaths(model, changes) {
10
+ const entries = Array.isArray(changes)
11
+ ? changes.map((c) => [c.path, c.value])
12
+ : Object.entries(changes);
13
+ for (const [path, value] of entries) {
14
+ const trimmed = path.startsWith("/") ? path : `/${path}`;
15
+ const parts = trimmed.split("/").filter(Boolean);
16
+ if (parts.length < 2)
17
+ continue;
18
+ const [top, ...rest] = parts;
19
+ if (top === "inputs") {
20
+ if (typeof model.inputs !== "object" || model.inputs === null) {
21
+ model.inputs = {};
22
+ }
23
+ const inputs = model.inputs;
24
+ if (rest.length === 1) {
25
+ inputs[rest[0]] = value;
26
+ }
27
+ continue;
28
+ }
29
+ if (top === "theme") {
30
+ if (typeof model.theme !== "object" || model.theme === null) {
31
+ model.theme = {};
32
+ }
33
+ const theme = model.theme;
34
+ if (rest.length === 1) {
35
+ theme[rest[0]] = value;
36
+ }
37
+ }
38
+ }
39
+ }
40
+ export function resolveAccentHex(accent, appearance) {
41
+ const map = appearance === "dark" ? PALETTE_DARK_HEX : PALETTE_LIGHT_HEX;
42
+ const name = accent && Object.hasOwn(map, accent)
43
+ ? accent
44
+ : DEFAULT_THEME_ACCENT;
45
+ return map[name];
46
+ }
47
+ // ─── Core rendering component (no validation) ────────
48
+ export function SnapViewCoreInner({ snap, handlers, loading = false, }) {
49
+ const { mode } = useSnapTheme();
50
+ const spec = snap.ui;
51
+ const accentHex = resolveAccentHex(snap.theme?.accent, mode);
52
+ const initialState = useMemo(() => ({
53
+ ...(spec.state ?? {}),
54
+ inputs: { ...(spec.state?.inputs ?? {}) },
55
+ theme: {
56
+ ...(spec.state?.theme ?? {}),
57
+ ...(snap.theme ? { accent: snap.theme.accent } : {}),
58
+ },
59
+ }), [spec, snap.theme]);
60
+ const stateRef = useRef(initialState);
61
+ useEffect(() => {
62
+ stateRef.current = {
63
+ inputs: {
64
+ ...(initialState.inputs ?? {}),
65
+ },
66
+ theme: {
67
+ ...(initialState.theme ?? {}),
68
+ },
69
+ };
70
+ }, [initialState]);
71
+ useEffect(() => {
72
+ const catalogResult = snapJsonRenderCatalog.validate(spec);
73
+ if (!catalogResult.success) {
74
+ // eslint-disable-next-line no-console
75
+ console.warn("[Snap] catalog validation issues:", catalogResult.error);
76
+ }
77
+ }, [spec]);
78
+ const [pageKey, setPageKey] = useState(0);
79
+ useEffect(() => {
80
+ setPageKey((k) => k + 1);
81
+ }, [spec]);
82
+ const handlersRef = useRef(handlers);
83
+ handlersRef.current = handlers;
84
+ const handleAction = useCallback((name, params) => {
85
+ const inputs = (stateRef.current.inputs ?? {});
86
+ const p = (params ?? {});
87
+ const h = handlersRef.current;
88
+ switch (name) {
89
+ case "submit":
90
+ h.submit(String(p.target ?? ""), inputs);
91
+ break;
92
+ case "open_url":
93
+ h.open_url(String(p.target ?? ""));
94
+ break;
95
+ case "open_mini_app":
96
+ h.open_mini_app(String(p.target ?? ""));
97
+ break;
98
+ case "view_cast":
99
+ h.view_cast({ hash: String(p.hash ?? "") });
100
+ break;
101
+ case "view_profile":
102
+ h.view_profile({ fid: Number(p.fid ?? 0) });
103
+ break;
104
+ case "compose_cast":
105
+ h.compose_cast({
106
+ text: p.text ? String(p.text) : undefined,
107
+ channelKey: p.channelKey ? String(p.channelKey) : undefined,
108
+ embeds: Array.isArray(p.embeds) ? p.embeds : undefined,
109
+ });
110
+ break;
111
+ case "view_token":
112
+ h.view_token({ token: String(p.token ?? "") });
113
+ break;
114
+ case "send_token":
115
+ h.send_token({
116
+ token: String(p.token ?? ""),
117
+ amount: p.amount ? String(p.amount) : undefined,
118
+ recipientFid: p.recipientFid ? Number(p.recipientFid) : undefined,
119
+ recipientAddress: p.recipientAddress
120
+ ? String(p.recipientAddress)
121
+ : undefined,
122
+ });
123
+ break;
124
+ case "swap_token":
125
+ h.swap_token({
126
+ sellToken: p.sellToken ? String(p.sellToken) : undefined,
127
+ buyToken: p.buyToken ? String(p.buyToken) : undefined,
128
+ });
129
+ break;
130
+ default:
131
+ break;
132
+ }
133
+ }, []);
134
+ return (_jsxs(View, { style: styles.container, children: [loading ? (_jsx(View, { style: [
135
+ styles.overlay,
136
+ {
137
+ backgroundColor: mode === "dark" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.2)",
138
+ },
139
+ ], children: _jsx(ActivityIndicator, { size: "large", color: accentHex }) })) : null, _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
140
+ applyStatePaths(stateRef.current, changes);
141
+ }, onAction: handleAction }, pageKey)] }));
142
+ }
143
+ const styles = StyleSheet.create({
144
+ container: {
145
+ width: "100%",
146
+ },
147
+ overlay: {
148
+ ...StyleSheet.absoluteFillObject,
149
+ alignItems: "center",
150
+ justifyContent: "center",
151
+ zIndex: 10,
152
+ },
153
+ });
@@ -0,0 +1,27 @@
1
+ import { type ReactNode } from "react";
2
+ export type SnapNativeColors = {
3
+ bg: string;
4
+ surface: string;
5
+ text: string;
6
+ textSecondary: string;
7
+ border: string;
8
+ inputBg: string;
9
+ muted: string;
10
+ /** Subtle tint for toggle button resting state */
11
+ mutedSubtle: string;
12
+ /** Slightly stronger tint for hover/press state */
13
+ mutedHover: string;
14
+ /** Stronger tint for selected state (toggle group) */
15
+ mutedSelected: string;
16
+ };
17
+ interface SnapThemeValue {
18
+ mode: "light" | "dark";
19
+ colors: SnapNativeColors;
20
+ }
21
+ export declare function SnapThemeProvider({ appearance, colors, children, }: {
22
+ appearance: "light" | "dark";
23
+ colors?: Partial<SnapNativeColors>;
24
+ children: ReactNode;
25
+ }): import("react").JSX.Element;
26
+ export declare function useSnapTheme(): SnapThemeValue;
27
+ export {};
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useMemo } from "react";
3
+ const DEFAULT_LIGHT = {
4
+ bg: "#dfe3e8",
5
+ surface: "#ffffff",
6
+ text: "rgba(0,0,0,0.9)",
7
+ textSecondary: "rgba(0,0,0,0.5)",
8
+ border: "rgba(0,0,0,0.1)",
9
+ inputBg: "rgba(0,0,0,0.06)",
10
+ muted: "rgba(0,0,0,0.08)",
11
+ mutedSubtle: "rgba(0,0,0,0.04)",
12
+ mutedHover: "rgba(0,0,0,0.12)",
13
+ mutedSelected: "rgba(0,0,0,0.16)",
14
+ };
15
+ const DEFAULT_DARK = {
16
+ bg: "#111318",
17
+ surface: "#1a1d24",
18
+ text: "rgba(255,255,255,0.9)",
19
+ textSecondary: "rgba(255,255,255,0.5)",
20
+ border: "rgba(255,255,255,0.1)",
21
+ inputBg: "rgba(255,255,255,0.04)",
22
+ muted: "rgba(255,255,255,0.06)",
23
+ mutedSubtle: "rgba(255,255,255,0.03)",
24
+ mutedHover: "rgba(255,255,255,0.08)",
25
+ mutedSelected: "rgba(255,255,255,0.12)",
26
+ };
27
+ const SnapThemeContext = createContext({
28
+ mode: "dark",
29
+ colors: DEFAULT_DARK,
30
+ });
31
+ export function SnapThemeProvider({ appearance, colors, children, }) {
32
+ const value = useMemo(() => {
33
+ const defaults = appearance === "dark" ? DEFAULT_DARK : DEFAULT_LIGHT;
34
+ return {
35
+ mode: appearance,
36
+ colors: colors ? { ...defaults, ...colors } : defaults,
37
+ };
38
+ }, [appearance, colors]);
39
+ return (_jsx(SnapThemeContext.Provider, { value: value, children: children }));
40
+ }
41
+ export function useSnapTheme() {
42
+ return useContext(SnapThemeContext);
43
+ }
@@ -0,0 +1,42 @@
1
+ import type { Spec } from "@json-render/core";
2
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
3
+ [key: string]: JsonValue;
4
+ };
5
+ export type SnapPage = {
6
+ version: string;
7
+ theme?: {
8
+ accent?: string;
9
+ };
10
+ effects?: string[];
11
+ ui: Spec;
12
+ };
13
+ export type SnapActionHandlers = {
14
+ submit: (target: string, inputs: Record<string, JsonValue>) => void;
15
+ open_url: (target: string) => void;
16
+ open_snap: (target: string) => void;
17
+ open_mini_app: (target: string) => void;
18
+ view_cast: (params: {
19
+ hash: string;
20
+ }) => void;
21
+ view_profile: (params: {
22
+ fid: number;
23
+ }) => void;
24
+ compose_cast: (params: {
25
+ text?: string;
26
+ channelKey?: string;
27
+ embeds?: string[];
28
+ }) => void;
29
+ view_token: (params: {
30
+ token: string;
31
+ }) => void;
32
+ send_token: (params: {
33
+ token: string;
34
+ amount?: string;
35
+ recipientFid?: number;
36
+ recipientAddress?: string;
37
+ }) => void;
38
+ swap_token: (params: {
39
+ sellToken?: string;
40
+ buyToken?: string;
41
+ }) => void;
42
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export declare function useSnapPalette(): {
2
+ appearance: "light" | "dark";
3
+ accentName: "gray" | "blue" | "red" | "amber" | "green" | "teal" | "purple" | "pink";
4
+ accentHex: string;
5
+ hex: (semantic: string) => string;
6
+ };
7
+ /** `#RRGGBB` + alpha → `rgba(...)` for React Native styles. */
8
+ export declare function hexToRgba(hex: string, alpha: number): string;
9
+ export declare function useSnapPreviewChromePalette(themeAccent: string | undefined): {
10
+ appearance: "light" | "dark";
11
+ accentName: "gray" | "blue" | "red" | "amber" | "green" | "teal" | "purple" | "pink";
12
+ accentHex: string;
13
+ };
@@ -0,0 +1,48 @@
1
+ import { DEFAULT_THEME_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "@farcaster/snap";
2
+ import { useStateStore } from "@json-render/react-native";
3
+ import { useSnapTheme } from "./theme.js";
4
+ function resolveHex(name, appearance) {
5
+ const map = appearance === "dark" ? PALETTE_DARK_HEX : PALETTE_LIGHT_HEX;
6
+ if (Object.hasOwn(map, name)) {
7
+ return map[name];
8
+ }
9
+ return map.purple;
10
+ }
11
+ function isPaletteColor(s) {
12
+ return PALETTE_COLOR_VALUES.includes(s);
13
+ }
14
+ function themeAccentFromStore(get) {
15
+ const raw = get("/theme/accent");
16
+ if (typeof raw === "string" && isPaletteColor(raw)) {
17
+ return raw;
18
+ }
19
+ return DEFAULT_THEME_ACCENT;
20
+ }
21
+ export function useSnapPalette() {
22
+ const { mode } = useSnapTheme();
23
+ const { get } = useStateStore();
24
+ const accentName = themeAccentFromStore(get);
25
+ const accentHex = resolveHex(accentName, mode);
26
+ const hex = (semantic) => semantic === "accent" ? accentHex : resolveHex(semantic, mode);
27
+ return { appearance: mode, accentName, accentHex, hex };
28
+ }
29
+ /** `#RRGGBB` + alpha → `rgba(...)` for React Native styles. */
30
+ export function hexToRgba(hex, alpha) {
31
+ const m = /^#([0-9a-fA-F]{6})$/.exec(hex.trim());
32
+ if (!m) {
33
+ return `rgba(0,0,0,${alpha})`;
34
+ }
35
+ const n = Number.parseInt(m[1], 16);
36
+ const r = (n >> 16) & 255;
37
+ const g = (n >> 8) & 255;
38
+ const b = n & 255;
39
+ return `rgba(${r},${g},${b},${alpha})`;
40
+ }
41
+ export function useSnapPreviewChromePalette(themeAccent) {
42
+ const { mode } = useSnapTheme();
43
+ const accentName = typeof themeAccent === "string" && isPaletteColor(themeAccent)
44
+ ? themeAccent
45
+ : DEFAULT_THEME_ACCENT;
46
+ const accentHex = resolveHex(accentName, mode);
47
+ return { appearance: mode, accentName, accentHex };
48
+ }
@@ -0,0 +1,24 @@
1
+ import { type SnapNativeColors } from "../theme.js";
2
+ import type { SnapPage, SnapActionHandlers } from "../types.js";
3
+ export declare function SnapViewV1Inner({ snap, handlers, loading, }: {
4
+ snap: SnapPage;
5
+ handlers: SnapActionHandlers;
6
+ loading?: boolean;
7
+ }): import("react").JSX.Element;
8
+ export declare function SnapViewV1({ snap, handlers, loading, appearance, colors, }: {
9
+ snap: SnapPage;
10
+ handlers: SnapActionHandlers;
11
+ loading?: boolean;
12
+ appearance?: "light" | "dark";
13
+ colors?: Partial<SnapNativeColors>;
14
+ }): import("react").JSX.Element;
15
+ export declare function SnapCardV1({ snap, handlers, loading, appearance, colors, borderRadius, actionError, plain, }: {
16
+ snap: SnapPage;
17
+ handlers: SnapActionHandlers;
18
+ loading?: boolean;
19
+ appearance?: "light" | "dark";
20
+ colors?: Partial<SnapNativeColors>;
21
+ borderRadius?: number;
22
+ actionError?: string | null;
23
+ plain?: boolean;
24
+ }): import("react").JSX.Element;
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { View, Text, StyleSheet, Pressable } from "react-native";
4
+ import { SnapThemeProvider, useSnapTheme } from "../theme.js";
5
+ import { SnapViewCoreInner } from "../snap-view-core.js";
6
+ const SNAP_MAX_HEIGHT = 500;
7
+ // ─── SnapViewV1 (no validation) ──────────────────────
8
+ export function SnapViewV1Inner({ snap, handlers, loading = false, }) {
9
+ return (_jsx(SnapViewCoreInner, { snap: snap, handlers: handlers, loading: loading }));
10
+ }
11
+ export function SnapViewV1({ snap, handlers, loading = false, appearance = "dark", colors, }) {
12
+ return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapViewV1Inner, { snap: snap, handlers: handlers, loading: loading }) }));
13
+ }
14
+ // ─── SnapCardV1 (card frame with expandable clipping) ──
15
+ function SnapCardV1Inner({ snap, handlers, loading = false, borderRadius, actionError, appearance, plain, }) {
16
+ const { colors } = useSnapTheme();
17
+ const [contentHeight, setContentHeight] = useState(0);
18
+ const [isExpanded, setIsExpanded] = useState(false);
19
+ useEffect(() => {
20
+ setIsExpanded(false);
21
+ setContentHeight(0);
22
+ }, [snap]);
23
+ const isExpandable = contentHeight > SNAP_MAX_HEIGHT + 1;
24
+ const isClipped = isExpandable && !isExpanded;
25
+ return (_jsxs(_Fragment, { children: [_jsx(View, { style: cardStyles.frameRing, children: _jsxs(View, { style: [
26
+ plain ? undefined : cardStyles.card,
27
+ plain ? undefined : {
28
+ borderRadius,
29
+ borderColor: colors.border,
30
+ backgroundColor: colors.surface,
31
+ },
32
+ ], children: [_jsx(View, { style: isClipped ? { maxHeight: SNAP_MAX_HEIGHT, overflow: "hidden" } : undefined, children: _jsx(View, { collapsable: false, onLayout: (event) => {
33
+ const nextHeight = Math.round(event.nativeEvent.layout.height);
34
+ setContentHeight((currentHeight) => isClipped
35
+ ? Math.max(currentHeight, nextHeight)
36
+ : currentHeight === nextHeight
37
+ ? currentHeight
38
+ : nextHeight);
39
+ }, style: plain ? undefined : cardStyles.body, children: _jsx(SnapViewV1Inner, { snap: snap, handlers: handlers, loading: loading }) }) }), isExpandable ? (_jsx(View, { style: [
40
+ cardStyles.expandRow,
41
+ plain
42
+ ? cardStyles.expandRowPlain
43
+ : { borderTopColor: colors.border },
44
+ ], children: _jsx(Pressable, { style: ({ pressed }) => [
45
+ cardStyles.expandButton,
46
+ {
47
+ backgroundColor: pressed
48
+ ? colors.mutedHover
49
+ : colors.muted,
50
+ },
51
+ ], onPress: () => {
52
+ setIsExpanded((value) => !value);
53
+ }, children: _jsx(Text, { style: [cardStyles.expandButtonText, { color: colors.text }], children: isExpanded ? "Show less" : "Show more" }) }) })) : null] }) }), actionError && (_jsx(Text, { style: [
54
+ cardStyles.actionError,
55
+ {
56
+ color: appearance === "dark"
57
+ ? "rgba(255,100,100,0.9)"
58
+ : "rgba(200,0,0,0.8)",
59
+ },
60
+ ], children: actionError }))] }));
61
+ }
62
+ export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, actionError, plain = false, }) {
63
+ return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapCardV1Inner, { snap: snap, handlers: handlers, loading: loading, borderRadius: borderRadius, actionError: actionError, appearance: appearance, plain: plain }) }));
64
+ }
65
+ const cardStyles = StyleSheet.create({
66
+ frameRing: { alignSelf: "stretch" },
67
+ card: { overflow: "hidden", borderWidth: 1, minHeight: 120 },
68
+ body: { paddingHorizontal: 16, paddingVertical: 16 },
69
+ expandRow: {
70
+ alignItems: "center",
71
+ paddingHorizontal: 16,
72
+ paddingTop: 10,
73
+ paddingBottom: 12,
74
+ borderTopWidth: StyleSheet.hairlineWidth,
75
+ },
76
+ expandRowPlain: {
77
+ paddingHorizontal: 0,
78
+ paddingTop: 8,
79
+ paddingBottom: 0,
80
+ borderTopWidth: 0,
81
+ },
82
+ expandButton: {
83
+ minWidth: 92,
84
+ alignItems: "center",
85
+ justifyContent: "center",
86
+ borderRadius: 9999,
87
+ paddingHorizontal: 10,
88
+ paddingVertical: 6,
89
+ },
90
+ expandButtonText: {
91
+ fontSize: 13,
92
+ lineHeight: 18,
93
+ fontWeight: "600",
94
+ },
95
+ actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
96
+ });
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from "react";
2
+ import { type SnapNativeColors } from "../theme.js";
3
+ import { type ValidationResult } from "@farcaster/snap";
4
+ import type { SnapPage, SnapActionHandlers } from "../types.js";
5
+ export declare function SnapViewV2Inner({ snap, handlers, loading, onValidationError, validationErrorFallback, }: {
6
+ snap: SnapPage;
7
+ handlers: SnapActionHandlers;
8
+ loading?: boolean;
9
+ onValidationError?: (result: ValidationResult) => void;
10
+ validationErrorFallback?: ReactNode;
11
+ }): import("react").JSX.Element;
12
+ export declare function SnapViewV2({ snap, handlers, loading, appearance, colors, onValidationError, validationErrorFallback, }: {
13
+ snap: SnapPage;
14
+ handlers: SnapActionHandlers;
15
+ loading?: boolean;
16
+ appearance?: "light" | "dark";
17
+ colors?: Partial<SnapNativeColors>;
18
+ onValidationError?: (result: ValidationResult) => void;
19
+ validationErrorFallback?: ReactNode;
20
+ }): import("react").JSX.Element;
21
+ export declare function SnapCardV2({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
22
+ snap: SnapPage;
23
+ handlers: SnapActionHandlers;
24
+ loading?: boolean;
25
+ appearance?: "light" | "dark";
26
+ colors?: Partial<SnapNativeColors>;
27
+ borderRadius?: number;
28
+ showOverflowWarning?: boolean;
29
+ onValidationError?: (result: ValidationResult) => void;
30
+ validationErrorFallback?: ReactNode;
31
+ actionError?: string | null;
32
+ plain?: boolean;
33
+ }): import("react").JSX.Element;
@@ -0,0 +1,114 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { Platform, StyleSheet, Text, View } from "react-native";
4
+ import { SnapThemeProvider, useSnapTheme } from "../theme.js";
5
+ import { SnapViewCoreInner } from "../snap-view-core.js";
6
+ import { validateSnapResponse, } from "@farcaster/snap";
7
+ // ─── Constants ───────────────────────────────────────
8
+ const SNAP_MAX_HEIGHT = 500;
9
+ const SNAP_WARNING_HEIGHT = 700;
10
+ // ─── Validation fallback ─────────────────────────────
11
+ function SnapValidationFallback({ message }) {
12
+ const { colors } = useSnapTheme();
13
+ return (_jsx(View, { style: fallbackStyles.container, children: _jsx(Text, { style: [fallbackStyles.text, { color: colors.textSecondary }], children: message ? `Unable to render snap: ${message}` : "Unable to render snap" }) }));
14
+ }
15
+ const fallbackStyles = StyleSheet.create({
16
+ container: {
17
+ width: "100%",
18
+ padding: 16,
19
+ alignItems: "center",
20
+ justifyContent: "center",
21
+ },
22
+ text: {
23
+ fontSize: 14,
24
+ },
25
+ });
26
+ // ─── SnapViewV2 (with validation) ────────────────────
27
+ export function SnapViewV2Inner({ snap, handlers, loading = false, onValidationError, validationErrorFallback, }) {
28
+ const validation = useMemo(() => validateSnapResponse(snap), [snap]);
29
+ const valid = validation.valid;
30
+ const validationMessage = validation.issues[0]?.message;
31
+ useEffect(() => {
32
+ if (!valid) {
33
+ if (onValidationError) {
34
+ onValidationError(validation);
35
+ }
36
+ else {
37
+ // eslint-disable-next-line no-console
38
+ console.warn("[Snap] validation issues:", validation.issues);
39
+ }
40
+ }
41
+ }, [valid, validation, onValidationError]);
42
+ if (!valid) {
43
+ if (validationErrorFallback === null)
44
+ return null;
45
+ return (_jsx(_Fragment, { children: validationErrorFallback ?? _jsx(SnapValidationFallback, { message: validationMessage }) }));
46
+ }
47
+ return (_jsx(SnapViewCoreInner, { snap: snap, handlers: handlers, loading: loading }));
48
+ }
49
+ export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark", colors, onValidationError, validationErrorFallback, }) {
50
+ return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback }) }));
51
+ }
52
+ // ─── SnapCardV2 (card frame + height limits) ─────────
53
+ function SnapCardV2Inner({ snap, handlers, loading, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, appearance, plain, }) {
54
+ const { colors } = useSnapTheme();
55
+ const [contentHeight, setContentHeight] = useState(0);
56
+ const content = (_jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback }));
57
+ if (plain) {
58
+ return content;
59
+ }
60
+ const overflowAmount = showOverflowWarning ? contentHeight - SNAP_MAX_HEIGHT : 0;
61
+ return (_jsxs(_Fragment, { children: [_jsxs(View, { style: {
62
+ borderRadius,
63
+ borderWidth: 1,
64
+ borderColor: colors.border,
65
+ backgroundColor: colors.surface,
66
+ maxHeight: showOverflowWarning ? undefined : SNAP_MAX_HEIGHT,
67
+ overflow: "hidden",
68
+ minHeight: 120,
69
+ }, children: [_jsx(View, { collapsable: false, onLayout: (e) => setContentHeight(Math.round(e.nativeEvent.layout.height)), style: { paddingHorizontal: 16, paddingVertical: 16 }, children: content }), showOverflowWarning && contentHeight > SNAP_MAX_HEIGHT && (_jsxs(View, { style: { position: "absolute", top: SNAP_MAX_HEIGHT, left: 0, right: 0, height: overflowAmount, zIndex: 10, pointerEvents: "none" }, children: [_jsx(View, { style: { height: 1, borderTopWidth: 1, borderStyle: "dashed", borderColor: "rgba(255,100,100,0.6)" } }), _jsx(View, { style: { position: "absolute", top: -10, right: 4, backgroundColor: "rgba(0,0,0,0.7)", paddingHorizontal: 4, paddingVertical: 1, borderRadius: 3 }, children: _jsxs(Text, { style: { fontSize: 10, color: "rgba(255,100,100,0.7)", fontFamily: Platform.select({ ios: "Menlo", default: "monospace" }) }, children: [SNAP_MAX_HEIGHT, "px"] }) }), _jsx(View, { style: { flex: 1, backgroundColor: "rgba(255,50,50,0.15)" } })] }))] }), actionError && (_jsx(Text, { style: {
70
+ paddingHorizontal: 12,
71
+ paddingVertical: 8,
72
+ fontSize: 13,
73
+ color: appearance === "dark"
74
+ ? "rgba(255,100,100,0.9)"
75
+ : "rgba(200,0,0,0.8)",
76
+ }, children: actionError }))] }));
77
+ }
78
+ export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, }) {
79
+ 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, plain: plain }) }));
80
+ }
81
+ const cardStyles = StyleSheet.create({
82
+ frameRing: { alignSelf: "stretch" },
83
+ card: { borderWidth: 1, minHeight: 120, overflow: "hidden" },
84
+ body: { paddingHorizontal: 16, paddingVertical: 16 },
85
+ actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
86
+ warningOverlay: {
87
+ position: "absolute",
88
+ top: SNAP_MAX_HEIGHT,
89
+ left: 0,
90
+ right: 0,
91
+ bottom: 0,
92
+ zIndex: 10,
93
+ },
94
+ warningLine: {
95
+ height: 1,
96
+ borderTopWidth: 1,
97
+ borderStyle: "dashed",
98
+ borderColor: "rgba(255,100,100,0.6)",
99
+ },
100
+ warningLabel: {
101
+ position: "absolute",
102
+ top: -10,
103
+ right: 4,
104
+ backgroundColor: "rgba(0,0,0,0.7)",
105
+ paddingHorizontal: 4,
106
+ paddingVertical: 1,
107
+ borderRadius: 3,
108
+ },
109
+ warningLabelText: {
110
+ fontSize: 10,
111
+ color: "rgba(255,100,100,0.7)",
112
+ fontFamily: Platform.select({ ios: "Menlo", default: "monospace" }),
113
+ },
114
+ });