@farcaster/snap 2.3.1 → 2.5.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/button-orientation-utils.d.ts +3 -0
- package/dist/button-orientation-utils.js +25 -0
- package/dist/constants.d.ts +2 -1
- package/dist/constants.js +2 -1
- package/dist/react/components/action-button.js +5 -5
- package/dist/react/components/cell-grid.js +6 -2
- package/dist/react/components/item-group.js +2 -1
- package/dist/react/components/item-layout-context.d.ts +2 -0
- package/dist/react/components/item-layout-context.js +7 -0
- package/dist/react/components/item.js +40 -3
- package/dist/react/components/stack.js +46 -37
- package/dist/react/components/toggle-group.js +6 -4
- package/dist/react/snap-view-core.js +107 -17
- package/dist/react-native/components/item-layout-context.d.ts +2 -0
- package/dist/react-native/components/item-layout-context.js +6 -0
- package/dist/react-native/components/snap-action-button.js +15 -2
- package/dist/react-native/components/snap-cell-grid.js +30 -4
- package/dist/react-native/components/snap-item-group.js +16 -6
- package/dist/react-native/components/snap-item.js +56 -13
- package/dist/react-native/components/snap-stack.js +32 -33
- package/dist/react-native/components/snap-toggle-group.js +5 -4
- package/dist/react-native/fireworks-overlay.d.ts +1 -0
- package/dist/react-native/fireworks-overlay.js +125 -0
- package/dist/react-native/snap-view-core.js +7 -2
- package/dist/schemas.d.ts +1 -0
- package/dist/stack-horizontal-utils.d.ts +7 -5
- package/dist/stack-horizontal-utils.js +24 -14
- package/dist/ui/catalog.d.ts +60 -1
- package/dist/ui/catalog.js +3 -3
- package/dist/ui/cell-grid.d.ts +4 -0
- package/dist/ui/cell-grid.js +3 -4
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +1 -1
- package/dist/ui/item.d.ts +112 -1
- package/dist/ui/item.js +28 -2
- package/dist/ui/stack.d.ts +1 -0
- package/dist/ui/stack.js +3 -1
- package/dist/validator.js +19 -1
- package/llms.txt +3 -1
- package/package.json +1 -1
- package/src/button-orientation-utils.ts +36 -0
- package/src/constants.ts +2 -1
- package/src/react/components/action-button.tsx +5 -4
- package/src/react/components/cell-grid.tsx +6 -2
- package/src/react/components/item-group.tsx +19 -17
- package/src/react/components/item-layout-context.tsx +12 -0
- package/src/react/components/item.tsx +97 -4
- package/src/react/components/stack.tsx +51 -40
- package/src/react/components/toggle-group.tsx +6 -4
- package/src/react/snap-view-core.tsx +152 -28
- package/src/react-native/components/item-layout-context.tsx +10 -0
- package/src/react-native/components/snap-action-button.tsx +15 -2
- package/src/react-native/components/snap-cell-grid.tsx +36 -4
- package/src/react-native/components/snap-item-group.tsx +31 -17
- package/src/react-native/components/snap-item.tsx +92 -14
- package/src/react-native/components/snap-stack.tsx +37 -36
- package/src/react-native/components/snap-toggle-group.tsx +5 -4
- package/src/react-native/fireworks-overlay.tsx +176 -0
- package/src/react-native/snap-view-core.tsx +6 -1
- package/src/stack-horizontal-utils.ts +32 -13
- package/src/ui/catalog.ts +5 -4
- package/src/ui/cell-grid.ts +5 -5
- package/src/ui/index.ts +2 -2
- package/src/ui/item.ts +35 -5
- package/src/ui/stack.ts +3 -1
- package/src/validator.ts +29 -1
package/src/ui/catalog.ts
CHANGED
|
@@ -48,7 +48,7 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
|
|
|
48
48
|
toggle_group: {
|
|
49
49
|
props: toggleGroupProps,
|
|
50
50
|
description:
|
|
51
|
-
"Single or multi-select choice group; `name` becomes POST inputs key.
|
|
51
|
+
"Single or multi-select choice group; `name` becomes POST inputs key. The @farcaster/snap React/React Native components choose row/column orientation from option label length, ignoring snap-sent orientation hints.",
|
|
52
52
|
},
|
|
53
53
|
input: {
|
|
54
54
|
props: inputProps,
|
|
@@ -58,7 +58,7 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
|
|
|
58
58
|
item: {
|
|
59
59
|
props: itemProps,
|
|
60
60
|
description:
|
|
61
|
-
"Content row
|
|
61
|
+
"Content row matching shadcn Item: optional media renders on the left, title and optional description render in the content area, and children render in the actions slot (right side). 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,
|
|
@@ -92,7 +92,7 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
|
|
|
92
92
|
stack: {
|
|
93
93
|
props: stackProps,
|
|
94
94
|
description:
|
|
95
|
-
"Layout container — direction: vertical (default) | horizontal. Children are element ids in order. Horizontal stacks use a single flex row so peers stay side-by-side and shrink with min-width 0. Nested stacks participate as flexible row peers.
|
|
95
|
+
"Layout container — direction: vertical (default) | horizontal. Children are element ids in order. Horizontal stacks use a single flex row so peers stay side-by-side and shrink with min-width 0. Nested stacks participate as flexible row peers. For all-button stacks, the @farcaster/snap React/React Native components choose row/column orientation from button label length, ignoring snap-sent direction hints; horizontal button rows use content-proportional widths while filling the container unless `equalWidth: true` is provided to force equal-width cells. Vertical button rows default to a tighter gap when `gap` is omitted.",
|
|
96
96
|
},
|
|
97
97
|
text: {
|
|
98
98
|
props: textProps,
|
|
@@ -121,7 +121,8 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
|
|
|
121
121
|
params: z.object({ target: z.string() }),
|
|
122
122
|
},
|
|
123
123
|
open_snap: {
|
|
124
|
-
description:
|
|
124
|
+
description:
|
|
125
|
+
"Open a snap URL inline. The client renders the target as a snap rather than opening a browser.",
|
|
125
126
|
params: z.object({ target: z.string() }),
|
|
126
127
|
},
|
|
127
128
|
open_mini_app: {
|
package/src/ui/cell-grid.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
GRID_MIN_ROWS,
|
|
7
7
|
GRID_MAX_ROWS,
|
|
8
8
|
GRID_GAP_VALUES,
|
|
9
|
+
GRID_CELL_ASPECT_RATIO_VALUES,
|
|
9
10
|
} from "../constants.js";
|
|
10
11
|
|
|
11
12
|
/** Palette name or `#rrggbb`; input is trimmed so palette and hex rules match runtime resolvers. */
|
|
@@ -13,11 +14,9 @@ const cellGridCellColorSchema = z.preprocess(
|
|
|
13
14
|
(v) => (typeof v === "string" ? v.trim() : v),
|
|
14
15
|
z.union([
|
|
15
16
|
z.enum(PALETTE_COLOR_VALUES),
|
|
16
|
-
z
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
message: "cell_grid cell hex color must be #rrggbb",
|
|
20
|
-
}),
|
|
17
|
+
z.string().refine(isSnapHexColorString, {
|
|
18
|
+
message: "cell_grid cell hex color must be #rrggbb",
|
|
19
|
+
}),
|
|
21
20
|
]),
|
|
22
21
|
);
|
|
23
22
|
|
|
@@ -36,6 +35,7 @@ export const cellGridProps = z
|
|
|
36
35
|
rows: z.number().int().min(GRID_MIN_ROWS).max(GRID_MAX_ROWS),
|
|
37
36
|
cells: z.array(cellGridCellSchema),
|
|
38
37
|
gap: z.enum(GRID_GAP_VALUES).optional(),
|
|
38
|
+
cellAspectRatio: z.enum(GRID_CELL_ASPECT_RATIO_VALUES).optional(),
|
|
39
39
|
rowHeight: z.number().int().min(8).max(64).optional(),
|
|
40
40
|
select: z.enum(["off", "single", "multiple"]).optional(),
|
|
41
41
|
})
|
package/src/ui/index.ts
CHANGED
|
@@ -16,8 +16,8 @@ export type { ToggleGroupProps } from "./toggle-group.js";
|
|
|
16
16
|
export { inputProps } from "./input.js";
|
|
17
17
|
export type { InputProps } from "./input.js";
|
|
18
18
|
|
|
19
|
-
export { itemProps } from "./item.js";
|
|
20
|
-
export type { ItemProps } from "./item.js";
|
|
19
|
+
export { itemProps, itemMediaProps } from "./item.js";
|
|
20
|
+
export type { ItemMediaProps, ItemProps } from "./item.js";
|
|
21
21
|
|
|
22
22
|
export { itemGroupProps } from "./item-group.js";
|
|
23
23
|
export type { ItemGroupProps } from "./item-group.js";
|
package/src/ui/item.ts
CHANGED
|
@@ -1,13 +1,43 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { PROGRESS_COLOR_VALUES } from "../colors.js";
|
|
3
|
+
import { ICON_NAMES } from "./icon.js";
|
|
2
4
|
|
|
3
5
|
export const ITEM_VARIANTS = ["default"] as const;
|
|
6
|
+
export const ITEM_MEDIA_VARIANTS = ["icon", "image"] as const;
|
|
4
7
|
export const ITEM_MAX_TITLE_CHARS = 100;
|
|
5
8
|
export const ITEM_MAX_DESCRIPTION_CHARS = 160;
|
|
9
|
+
export const ITEM_MAX_MEDIA_ALT_CHARS = 120;
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
const itemIconMediaProps = z
|
|
12
|
+
.object({
|
|
13
|
+
variant: z.literal("icon"),
|
|
14
|
+
name: z.enum(ICON_NAMES),
|
|
15
|
+
color: z.enum(PROGRESS_COLOR_VALUES).optional(),
|
|
16
|
+
})
|
|
17
|
+
.strict();
|
|
18
|
+
|
|
19
|
+
const itemImageMediaProps = z
|
|
20
|
+
.object({
|
|
21
|
+
variant: z.literal("image"),
|
|
22
|
+
url: z.string(),
|
|
23
|
+
alt: z.string().max(ITEM_MAX_MEDIA_ALT_CHARS).optional(),
|
|
24
|
+
round: z.boolean().optional(),
|
|
25
|
+
})
|
|
26
|
+
.strict();
|
|
27
|
+
|
|
28
|
+
export const itemMediaProps = z.discriminatedUnion("variant", [
|
|
29
|
+
itemIconMediaProps,
|
|
30
|
+
itemImageMediaProps,
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
export const itemProps = z
|
|
34
|
+
.object({
|
|
35
|
+
title: z.string().min(1).max(ITEM_MAX_TITLE_CHARS),
|
|
36
|
+
description: z.string().max(ITEM_MAX_DESCRIPTION_CHARS).optional(),
|
|
37
|
+
variant: z.enum(ITEM_VARIANTS).optional(),
|
|
38
|
+
media: itemMediaProps.optional(),
|
|
39
|
+
})
|
|
40
|
+
.strict();
|
|
12
41
|
|
|
13
42
|
export type ItemProps = z.infer<typeof itemProps>;
|
|
43
|
+
export type ItemMediaProps = z.infer<typeof itemMediaProps>;
|
package/src/ui/stack.ts
CHANGED
|
@@ -8,7 +8,9 @@ export const stackProps = z.object({
|
|
|
8
8
|
direction: z.enum(STACK_DIRECTIONS).optional(),
|
|
9
9
|
gap: z.enum(STACK_GAPS).optional(),
|
|
10
10
|
justify: z.enum(STACK_JUSTIFY).optional(),
|
|
11
|
-
/** Horizontal stacks only:
|
|
11
|
+
/** Horizontal stacks only: make direct children equal width. */
|
|
12
|
+
equalWidth: z.boolean().optional(),
|
|
13
|
+
/** Horizontal stacks only: legacy fixed equal-width column count (`2`–`6`). Prefer `equalWidth`. */
|
|
12
14
|
columns: z.union([
|
|
13
15
|
z.literal(2),
|
|
14
16
|
z.literal(3),
|
package/src/validator.ts
CHANGED
|
@@ -29,7 +29,12 @@ const URL_TARGET_ACTIONS = new Set([
|
|
|
29
29
|
*/
|
|
30
30
|
function isLoopback(url: URL): boolean {
|
|
31
31
|
const host = url.hostname;
|
|
32
|
-
return
|
|
32
|
+
return (
|
|
33
|
+
host === "localhost" ||
|
|
34
|
+
host === "127.0.0.1" ||
|
|
35
|
+
host === "::1" ||
|
|
36
|
+
host === "[::1]"
|
|
37
|
+
);
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
/**
|
|
@@ -84,6 +89,11 @@ type ElementShape = {
|
|
|
84
89
|
on?: Record<string, { action?: string; params?: Record<string, unknown> }>;
|
|
85
90
|
};
|
|
86
91
|
|
|
92
|
+
type ItemMediaShape = {
|
|
93
|
+
variant?: string;
|
|
94
|
+
url?: unknown;
|
|
95
|
+
};
|
|
96
|
+
|
|
87
97
|
// ─── Structural validation ────────────────────────────
|
|
88
98
|
|
|
89
99
|
/**
|
|
@@ -169,6 +179,24 @@ function validateUrls(elements: Record<string, unknown>): z.core.$ZodIssue[] {
|
|
|
169
179
|
}
|
|
170
180
|
}
|
|
171
181
|
|
|
182
|
+
if (
|
|
183
|
+
el.type === "item" &&
|
|
184
|
+
el.props?.media &&
|
|
185
|
+
typeof el.props.media === "object"
|
|
186
|
+
) {
|
|
187
|
+
const media = el.props.media as ItemMediaShape;
|
|
188
|
+
if (media.variant === "image" && typeof media.url === "string") {
|
|
189
|
+
const error = validateUrl(media.url);
|
|
190
|
+
if (error) {
|
|
191
|
+
issues.push({
|
|
192
|
+
code: "custom",
|
|
193
|
+
message: error,
|
|
194
|
+
path: ["ui", "elements", id, "props", "media", "url"],
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
172
200
|
// Validate action target URLs
|
|
173
201
|
if (el.on) {
|
|
174
202
|
for (const [event, binding] of Object.entries(el.on)) {
|