@farcaster/snap 1.9.0 → 1.13.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.
Files changed (45) hide show
  1. package/dist/constants.d.ts +8 -0
  2. package/dist/constants.js +10 -0
  3. package/dist/index.d.ts +4 -6
  4. package/dist/index.js +2 -4
  5. package/dist/react/catalog-renderer.js +4 -0
  6. package/dist/react/components/bar-chart.d.ts +5 -0
  7. package/dist/react/components/bar-chart.js +31 -0
  8. package/dist/react/components/cell-grid.d.ts +5 -0
  9. package/dist/react/components/cell-grid.js +86 -0
  10. package/dist/react-native/catalog-renderer.js +4 -0
  11. package/dist/react-native/components/snap-bar-chart.d.ts +2 -0
  12. package/dist/react-native/components/snap-bar-chart.js +39 -0
  13. package/dist/react-native/components/snap-cell-grid.d.ts +2 -0
  14. package/dist/react-native/components/snap-cell-grid.js +96 -0
  15. package/dist/schemas.d.ts +45 -3
  16. package/dist/schemas.js +2 -2
  17. package/dist/ui/bar-chart.d.ts +30 -0
  18. package/dist/ui/bar-chart.js +30 -0
  19. package/dist/ui/catalog.d.ts +65 -0
  20. package/dist/ui/catalog.js +10 -0
  21. package/dist/ui/cell-grid.d.ts +33 -0
  22. package/dist/ui/cell-grid.js +38 -0
  23. package/dist/ui/index.d.ts +4 -0
  24. package/dist/ui/index.js +2 -0
  25. package/llms.txt +19 -4
  26. package/package.json +1 -1
  27. package/src/constants.ts +12 -0
  28. package/src/index.ts +8 -12
  29. package/src/react/catalog-renderer.tsx +4 -0
  30. package/src/react/components/bar-chart.tsx +67 -0
  31. package/src/react/components/cell-grid.tsx +131 -0
  32. package/src/react-native/catalog-renderer.tsx +4 -0
  33. package/src/react-native/components/snap-bar-chart.tsx +73 -0
  34. package/src/react-native/components/snap-cell-grid.tsx +152 -0
  35. package/src/schemas.ts +37 -11
  36. package/src/ui/bar-chart.ts +38 -0
  37. package/src/ui/catalog.ts +12 -0
  38. package/src/ui/cell-grid.ts +48 -0
  39. package/src/ui/index.ts +6 -0
  40. package/dist/dataStore.d.ts +0 -9
  41. package/dist/dataStore.js +0 -22
  42. package/dist/middleware.d.ts +0 -3
  43. package/dist/middleware.js +0 -3
  44. package/src/dataStore.ts +0 -38
  45. package/src/middleware.ts +0 -7
@@ -1,3 +1,11 @@
1
1
  export declare const SPEC_VERSION: "1.0";
2
2
  export declare const MEDIA_TYPE: "application/vnd.farcaster.snap+json";
3
3
  export declare const EFFECT_VALUES: readonly ["confetti"];
4
+ export declare const POST_GRID_TAP_KEY: "grid_tap";
5
+ export declare const GRID_MIN_COLS = 2;
6
+ export declare const GRID_MAX_COLS = 32;
7
+ export declare const GRID_MIN_ROWS = 2;
8
+ export declare const GRID_MAX_ROWS = 16;
9
+ export declare const GRID_GAP_VALUES: readonly ["none", "sm", "md", "lg"];
10
+ export declare const BAR_CHART_MAX_BARS = 6;
11
+ export declare const BAR_CHART_LABEL_MAX_CHARS = 40;
package/dist/constants.js CHANGED
@@ -1,3 +1,13 @@
1
1
  export const SPEC_VERSION = "1.0";
2
2
  export const MEDIA_TYPE = "application/vnd.farcaster.snap+json";
3
3
  export const EFFECT_VALUES = ["confetti"];
4
+ // ─── Pixel grid ────────────────────────────────────────
5
+ export const POST_GRID_TAP_KEY = "grid_tap";
6
+ export const GRID_MIN_COLS = 2;
7
+ export const GRID_MAX_COLS = 32;
8
+ export const GRID_MIN_ROWS = 2;
9
+ export const GRID_MAX_ROWS = 16;
10
+ export const GRID_GAP_VALUES = ["none", "sm", "md", "lg"];
11
+ // ─── Bar chart ─────────────────────────────────────────
12
+ export const BAR_CHART_MAX_BARS = 6;
13
+ export const BAR_CHART_LABEL_MAX_CHARS = 40;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,5 @@
1
- export type { Spec as SnapSpec, UIElement as SnapUIElement } from "@json-render/core";
2
- export { SPEC_VERSION, MEDIA_TYPE, EFFECT_VALUES, } from "./constants.js";
1
+ export type { Spec as SnapSpec, UIElement as SnapUIElement, } from "@json-render/core";
2
+ export { SPEC_VERSION, MEDIA_TYPE, EFFECT_VALUES, POST_GRID_TAP_KEY, } from "./constants.js";
3
3
  export { DEFAULT_THEME_ACCENT, PALETTE_COLOR, PALETTE_COLOR_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, type PaletteColor, } from "./colors.js";
4
- export { ACTION_TYPE_GET, ACTION_TYPE_POST, snapResponseSchema, payloadSchema, type SnapAction, type SnapContext, type SnapResponse, type SnapHandlerResult, type SnapFunction, type SnapPayload, } from "./schemas.js";
5
- export { validateSnapResponse, type ValidationResult, } from "./validator.js";
6
- export { type DataStoreValue, type SnapDataStore, createDefaultDataStore, createInMemoryDataStore, } from "./dataStore.js";
7
- export { type Middleware, useMiddleware } from "./middleware.js";
4
+ export { ACTION_TYPE_GET, ACTION_TYPE_POST, snapResponseSchema, payloadSchema, type SnapAction, type SnapContext, type SnapResponse, type SnapHandlerResult, type SnapElementInput, type SnapSpecInput, type SnapFunction, type SnapPayload, } from "./schemas.js";
5
+ export { validateSnapResponse, type ValidationResult } from "./validator.js";
package/dist/index.js CHANGED
@@ -1,6 +1,4 @@
1
- export { SPEC_VERSION, MEDIA_TYPE, EFFECT_VALUES, } from "./constants.js";
1
+ export { SPEC_VERSION, MEDIA_TYPE, EFFECT_VALUES, POST_GRID_TAP_KEY, } from "./constants.js";
2
2
  export { DEFAULT_THEME_ACCENT, PALETTE_COLOR, PALETTE_COLOR_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "./colors.js";
3
3
  export { ACTION_TYPE_GET, ACTION_TYPE_POST, snapResponseSchema, payloadSchema, } from "./schemas.js";
4
- export { validateSnapResponse, } from "./validator.js";
5
- export { createDefaultDataStore, createInMemoryDataStore, } from "./dataStore.js";
6
- export { useMiddleware } from "./middleware.js";
4
+ export { validateSnapResponse } from "./validator.js";
@@ -15,6 +15,8 @@ import { SnapStack } from "./components/stack.js";
15
15
  import { SnapSwitch } from "./components/switch.js";
16
16
  import { SnapText } from "./components/text.js";
17
17
  import { SnapToggleGroup } from "./components/toggle-group.js";
18
+ import { SnapBarChart } from "./components/bar-chart.js";
19
+ import { SnapCellGrid } from "./components/cell-grid.js";
18
20
  /**
19
21
  * Maps snap json-render catalog types to React components.
20
22
  * Keys match the snap wire-format `type` strings exactly.
@@ -34,4 +36,6 @@ export const SnapCatalogView = createRenderer(snapJsonRenderCatalog, {
34
36
  switch: SnapSwitch,
35
37
  text: SnapText,
36
38
  toggle_group: SnapToggleGroup,
39
+ bar_chart: SnapBarChart,
40
+ cell_grid: SnapCellGrid,
37
41
  });
@@ -0,0 +1,5 @@
1
+ export declare function SnapBarChart({ element: { props }, }: {
2
+ element: {
3
+ props: Record<string, unknown>;
4
+ };
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { PALETTE_LIGHT_HEX } from "@farcaster/snap";
4
+ import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent.js";
5
+ export function SnapBarChart({ element: { props }, }) {
6
+ const accentStyle = useSnapAccentScopeStyle();
7
+ const bars = Array.isArray(props.bars) ? props.bars : [];
8
+ const chartColor = String(props.color ?? "accent");
9
+ const maxVal = props.max != null
10
+ ? Number(props.max)
11
+ : Math.max(...bars.map((b) => Number(b.value ?? 0)), 1);
12
+ function barColor(bar) {
13
+ if (bar.color && bar.color in PALETTE_LIGHT_HEX) {
14
+ return `var(--snap-color-${bar.color}, ${PALETTE_LIGHT_HEX[bar.color]})`;
15
+ }
16
+ if (chartColor !== "accent" && chartColor in PALETTE_LIGHT_HEX) {
17
+ return `var(--snap-color-${chartColor}, ${PALETTE_LIGHT_HEX[chartColor]})`;
18
+ }
19
+ return "var(--primary)";
20
+ }
21
+ return (_jsx("div", { className: "flex w-full flex-col gap-2", style: accentStyle, children: bars.map((bar, i) => {
22
+ const value = Number(bar.value ?? 0);
23
+ const pct = maxVal > 0 ? Math.min(100, (value / maxVal) * 100) : 0;
24
+ const fill = barColor(bar);
25
+ return (_jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground w-20 shrink-0 truncate text-right text-xs", children: String(bar.label ?? "") }), _jsx("div", { className: "bg-muted h-2.5 flex-1 overflow-hidden rounded-full", children: _jsx("div", { className: "h-full rounded-full transition-all", style: {
26
+ width: `${pct}%`,
27
+ minWidth: pct > 0 ? 4 : 0,
28
+ background: fill,
29
+ } }) }), _jsx("span", { className: "text-muted-foreground w-8 shrink-0 text-xs tabular-nums", children: value })] }, i));
30
+ }) }));
31
+ }
@@ -0,0 +1,5 @@
1
+ export declare function SnapCellGrid({ element: { props }, }: {
2
+ element: {
3
+ props: Record<string, unknown>;
4
+ };
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,86 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useStateStore } from "@json-render/react";
4
+ import { cn } from "@neynar/ui/utils";
5
+ import { POST_GRID_TAP_KEY, PALETTE_LIGHT_HEX } from "@farcaster/snap";
6
+ import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent.js";
7
+ import { useColorMode } from "@neynar/ui/color-mode";
8
+ export function SnapCellGrid({ element: { props }, }) {
9
+ const { get, set } = useStateStore();
10
+ const accentStyle = useSnapAccentScopeStyle();
11
+ const { mode: appearance } = useColorMode();
12
+ const cols = Number(props.cols ?? 2);
13
+ const rows = Number(props.rows ?? 2);
14
+ const select = String(props.select ?? "off");
15
+ const interactive = select !== "off";
16
+ const isMultiple = select === "multiple";
17
+ const cells = Array.isArray(props.cells) ? props.cells : [];
18
+ const gap = String(props.gap ?? "sm");
19
+ const gapMap = { none: 0, sm: 1, md: 2, lg: 4 };
20
+ const gapPx = gapMap[gap] ?? 1;
21
+ const name = props.name ? String(props.name) : POST_GRID_TAP_KEY;
22
+ const tapPath = `/inputs/${name}`;
23
+ const tapRaw = get(tapPath);
24
+ // Parse selection — single mode: "row,col" string; multi mode: "row,col|row,col|..." string
25
+ const selectedSet = new Set();
26
+ if (typeof tapRaw === "string" && tapRaw.length > 0) {
27
+ for (const part of tapRaw.split("|")) {
28
+ if (part.includes(","))
29
+ selectedSet.add(part);
30
+ }
31
+ }
32
+ const isSelected = (r, c) => selectedSet.has(`${r},${c}`);
33
+ const handleTap = (r, c) => {
34
+ const key = `${r},${c}`;
35
+ if (isMultiple) {
36
+ const next = new Set(selectedSet);
37
+ if (next.has(key))
38
+ next.delete(key);
39
+ else
40
+ next.add(key);
41
+ set(tapPath, [...next].join("|"));
42
+ }
43
+ else {
44
+ set(tapPath, key);
45
+ }
46
+ };
47
+ const cellMap = new Map();
48
+ for (const c of cells) {
49
+ cellMap.set(`${Number(c.row)},${Number(c.col)}`, {
50
+ color: c.color,
51
+ content: c.content != null ? String(c.content) : undefined,
52
+ });
53
+ }
54
+ const ringColor = appearance === "dark" ? "#fff" : "#000";
55
+ const cellEls = [];
56
+ for (let r = 0; r < rows; r++) {
57
+ for (let c = 0; c < cols; c++) {
58
+ const cell = cellMap.get(`${r},${c}`);
59
+ const selected = interactive && isSelected(r, c);
60
+ const bg = cell?.color && cell.color in PALETTE_LIGHT_HEX
61
+ ? `var(--snap-color-${cell.color}, ${PALETTE_LIGHT_HEX[cell.color]})`
62
+ : "transparent";
63
+ cellEls.push(_jsx("div", { role: interactive ? "button" : undefined, tabIndex: interactive ? 0 : undefined, onClick: interactive ? () => handleTap(r, c) : undefined, onKeyDown: interactive
64
+ ? (e) => {
65
+ if (e.key === "Enter" || e.key === " ") {
66
+ e.preventDefault();
67
+ handleTap(r, c);
68
+ }
69
+ }
70
+ : undefined, className: cn("flex min-h-7 items-center justify-center rounded text-xs font-semibold", interactive ? "cursor-pointer select-none" : "cursor-default"), style: {
71
+ background: bg,
72
+ // Two-layer ring: 1px white/black inner + 2px accent outer
73
+ boxShadow: selected
74
+ ? `inset 0 0 0 1px ${appearance === "dark" ? "#000" : "#fff"}, inset 0 0 0 2px ${appearance === "dark" ? "#fff" : "#000"}`
75
+ : undefined,
76
+ }, children: cell?.content ?? "" }, `${r}-${c}`));
77
+ }
78
+ }
79
+ const selectionLabel = interactive && selectedSet.size > 0
80
+ ? `inputs.${name}: ${[...selectedSet].join(isMultiple ? " | " : "")}`
81
+ : null;
82
+ return (_jsxs("div", { style: accentStyle, children: [_jsx("div", { className: "grid w-full", style: {
83
+ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
84
+ gap: gapPx,
85
+ }, children: cellEls }), selectionLabel && (_jsx("div", { className: "text-muted-foreground mt-1.5 truncate text-xs font-mono", children: selectionLabel }))] }));
86
+ }
@@ -14,6 +14,8 @@ import { SnapStack } from "./components/snap-stack.js";
14
14
  import { SnapSwitch } from "./components/snap-switch.js";
15
15
  import { SnapText } from "./components/snap-text.js";
16
16
  import { SnapToggleGroup } from "./components/snap-toggle-group.js";
17
+ import { SnapBarChart } from "./components/snap-bar-chart.js";
18
+ import { SnapCellGrid } from "./components/snap-cell-grid.js";
17
19
  /**
18
20
  * Maps snap json-render catalog types to React Native primitives.
19
21
  * Keys match the snap wire-format `type` strings exactly (snake_case).
@@ -33,4 +35,6 @@ export const SnapCatalogView = createRenderer(snapJsonRenderCatalog, {
33
35
  switch: SnapSwitch,
34
36
  text: SnapText,
35
37
  toggle_group: SnapToggleGroup,
38
+ bar_chart: SnapBarChart,
39
+ cell_grid: SnapCellGrid,
36
40
  });
@@ -0,0 +1,2 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ export declare function SnapBarChart({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StyleSheet, Text, View } from "react-native";
3
+ import { useSnapPalette } from "../use-snap-palette.js";
4
+ import { useSnapTheme } from "../theme.js";
5
+ export function SnapBarChart({ element: { props }, }) {
6
+ const { accentHex, hex } = useSnapPalette();
7
+ const { colors } = useSnapTheme();
8
+ const bars = Array.isArray(props.bars) ? props.bars : [];
9
+ const chartColor = String(props.color ?? "accent");
10
+ const maxVal = props.max != null
11
+ ? Number(props.max)
12
+ : Math.max(...bars.map((b) => Number(b.value ?? 0)), 1);
13
+ function barFill(bar) {
14
+ if (bar.color)
15
+ return hex(bar.color);
16
+ if (chartColor !== "accent")
17
+ return hex(chartColor);
18
+ return accentHex;
19
+ }
20
+ return (_jsx(View, { style: styles.wrap, children: bars.map((bar, i) => {
21
+ const value = Number(bar.value ?? 0);
22
+ const pct = maxVal > 0 ? Math.min(100, (value / maxVal) * 100) : 0;
23
+ return (_jsxs(View, { style: styles.row, children: [_jsx(Text, { style: [styles.label, { color: colors.textSecondary }], numberOfLines: 1, children: String(bar.label ?? "") }), _jsx(View, { style: [styles.track, { backgroundColor: colors.muted }], children: _jsx(View, { style: [
24
+ styles.fill,
25
+ {
26
+ width: `${pct}%`,
27
+ backgroundColor: barFill(bar),
28
+ },
29
+ ] }) }), _jsx(Text, { style: [styles.value, { color: colors.textSecondary }], children: value })] }, i));
30
+ }) }));
31
+ }
32
+ const styles = StyleSheet.create({
33
+ wrap: { flex: 1, width: "100%", gap: 8 },
34
+ row: { flexDirection: "row", alignItems: "center", gap: 8 },
35
+ label: { width: 80, fontSize: 12, textAlign: "right" },
36
+ track: { flex: 1, height: 10, borderRadius: 9999, overflow: "hidden" },
37
+ fill: { height: "100%", borderRadius: 9999 },
38
+ value: { width: 32, fontSize: 12, fontVariant: ["tabular-nums"] },
39
+ });
@@ -0,0 +1,2 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ export declare function SnapCellGrid({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StyleSheet, Text, View, Pressable } from "react-native";
3
+ import { useStateStore } from "@json-render/react-native";
4
+ import { useSnapPalette } from "../use-snap-palette.js";
5
+ import { useSnapTheme } from "../theme.js";
6
+ import { POST_GRID_TAP_KEY } from "@farcaster/snap";
7
+ export function SnapCellGrid({ element: { props }, }) {
8
+ const { hex, appearance } = useSnapPalette();
9
+ const { colors } = useSnapTheme();
10
+ const { get, set } = useStateStore();
11
+ const cols = Number(props.cols ?? 2);
12
+ const rows = Number(props.rows ?? 2);
13
+ const cells = Array.isArray(props.cells) ? props.cells : [];
14
+ const gap = String(props.gap ?? "sm");
15
+ const gapMap = { none: 0, sm: 1, md: 2, lg: 4 };
16
+ const gapPx = gapMap[gap] ?? 1;
17
+ const select = String(props.select ?? "off");
18
+ const interactive = select !== "off";
19
+ const isMultiple = select === "multiple";
20
+ const name = props.name ? String(props.name) : POST_GRID_TAP_KEY;
21
+ const tapPath = `/inputs/${name}`;
22
+ const tapRaw = get(tapPath);
23
+ const selectedSet = new Set();
24
+ if (typeof tapRaw === "string" && tapRaw.length > 0) {
25
+ for (const part of tapRaw.split("|")) {
26
+ if (part.includes(","))
27
+ selectedSet.add(part);
28
+ }
29
+ }
30
+ const isSelected = (r, c) => selectedSet.has(`${r},${c}`);
31
+ const handleTap = (r, c) => {
32
+ const key = `${r},${c}`;
33
+ if (isMultiple) {
34
+ const next = new Set(selectedSet);
35
+ if (next.has(key))
36
+ next.delete(key);
37
+ else
38
+ next.add(key);
39
+ set(tapPath, [...next].join("|"));
40
+ }
41
+ else {
42
+ set(tapPath, key);
43
+ }
44
+ };
45
+ const cellMap = new Map();
46
+ for (const c of cells) {
47
+ cellMap.set(`${Number(c.row)},${Number(c.col)}`, {
48
+ color: c.color,
49
+ content: c.content != null ? String(c.content) : undefined,
50
+ });
51
+ }
52
+ const ringOuter = appearance === "dark" ? "#fff" : "#000";
53
+ const ringInner = appearance === "dark" ? "#000" : "#fff";
54
+ const rowEls = [];
55
+ for (let r = 0; r < rows; r++) {
56
+ const rowCells = [];
57
+ for (let c = 0; c < cols; c++) {
58
+ const cell = cellMap.get(`${r},${c}`);
59
+ const selected = interactive && isSelected(r, c);
60
+ const bg = cell?.color ? hex(cell.color) : "transparent";
61
+ const cellContent = cell?.content ? (_jsx(Text, { style: [styles.cellText, { color: colors.textPrimary }], children: cell.content })) : null;
62
+ // Two-tone ring: outer View with contrasting border, inner View with inverse border
63
+ const cellView = selected ? (_jsx(View, { style: [styles.cell, { borderWidth: 1, borderColor: ringOuter, borderRadius: 4 }], children: _jsx(View, { style: [
64
+ styles.innerCell,
65
+ { backgroundColor: bg, borderWidth: 1, borderColor: ringInner, borderRadius: 3 },
66
+ ], children: cellContent }) })) : (_jsx(View, { style: [styles.cell, { backgroundColor: bg }], children: cellContent }));
67
+ rowCells.push(interactive ? (_jsx(Pressable, { onPress: () => handleTap(r, c), style: styles.cellWrap, children: cellView }, `${r}-${c}`)) : (_jsx(View, { style: styles.cellWrap, children: cellView }, `${r}-${c}`)));
68
+ }
69
+ rowEls.push(_jsx(View, { style: [styles.gridRow, { gap: gapPx }], children: rowCells }, r));
70
+ }
71
+ const selectionLabel = interactive && selectedSet.size > 0
72
+ ? `inputs.${name}: ${[...selectedSet].join(isMultiple ? " | " : "")}`
73
+ : null;
74
+ return (_jsxs(View, { style: [styles.wrap, { gap: gapPx }], children: [rowEls, selectionLabel ? (_jsx(Text, { style: [styles.selectionText, { color: colors.textSecondary }], children: selectionLabel })) : null] }));
75
+ }
76
+ const styles = StyleSheet.create({
77
+ wrap: { width: "100%" },
78
+ gridRow: { flexDirection: "row" },
79
+ cellWrap: { flex: 1 },
80
+ cell: {
81
+ flex: 1,
82
+ minHeight: 28,
83
+ borderRadius: 4,
84
+ alignItems: "center",
85
+ justifyContent: "center",
86
+ },
87
+ innerCell: {
88
+ flex: 1,
89
+ width: "100%",
90
+ minHeight: 26,
91
+ alignItems: "center",
92
+ justifyContent: "center",
93
+ },
94
+ cellText: { fontSize: 12, fontWeight: "600" },
95
+ selectionText: { fontSize: 11, fontFamily: "monospace", marginTop: 6 },
96
+ });
package/dist/schemas.d.ts CHANGED
@@ -1,6 +1,16 @@
1
1
  import { z } from "zod";
2
2
  import type { Spec } from "@json-render/core";
3
- import { type SnapDataStore } from "./dataStore.js";
3
+ import { SPEC_VERSION } from "./constants.js";
4
+ declare const themeAccentSchema: z.ZodEnum<{
5
+ gray: "gray";
6
+ blue: "blue";
7
+ red: "red";
8
+ amber: "amber";
9
+ green: "green";
10
+ teal: "teal";
11
+ purple: "purple";
12
+ pink: "pink";
13
+ }>;
4
14
  export declare const snapResponseSchema: z.ZodObject<{
5
15
  version: z.ZodLiteral<"1.0">;
6
16
  theme: z.ZodDefault<z.ZodOptional<z.ZodObject<{
@@ -21,7 +31,40 @@ export declare const snapResponseSchema: z.ZodObject<{
21
31
  ui: z.ZodCustom<Spec, Spec>;
22
32
  }, z.core.$strict>;
23
33
  export type SnapResponse = z.infer<typeof snapResponseSchema>;
24
- export type SnapHandlerResult = z.input<typeof snapResponseSchema>;
34
+ /**
35
+ * Permissive element input type for snap handler authors.
36
+ * Allows dynamic element construction without requiring exact UIElement types.
37
+ */
38
+ export type SnapElementInput = {
39
+ type: string;
40
+ props?: Record<string, unknown>;
41
+ children?: string[];
42
+ on?: Record<string, unknown>;
43
+ [key: string]: unknown;
44
+ };
45
+ /**
46
+ * Permissive input type for the `ui` field in snap handler return values.
47
+ * Accepts dynamically-built element maps (e.g. `Record<string, SnapElementInput>`)
48
+ * without requiring exact UIElement types.
49
+ */
50
+ export type SnapSpecInput = {
51
+ root: string;
52
+ elements: Record<string, SnapElementInput>;
53
+ state?: Record<string, unknown>;
54
+ };
55
+ /**
56
+ * Return type for snap handler functions.
57
+ * Uses permissive input types so handlers can build elements dynamically
58
+ * without type casts. Runtime validation via the Zod schema still catches invalid shapes.
59
+ */
60
+ export type SnapHandlerResult = {
61
+ version: typeof SPEC_VERSION;
62
+ theme?: {
63
+ accent?: z.input<typeof themeAccentSchema>;
64
+ };
65
+ effects?: z.input<typeof snapResponseSchema>["effects"];
66
+ ui: SnapSpecInput;
67
+ };
25
68
  export declare const payloadSchema: z.ZodObject<{
26
69
  fid: z.ZodNumber;
27
70
  inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
@@ -56,7 +99,6 @@ export type SnapAction = z.infer<typeof snapActionSchema>;
56
99
  export type SnapContext = {
57
100
  action: SnapAction;
58
101
  request: Request;
59
- data: SnapDataStore;
60
102
  };
61
103
  export type SnapFunction = (ctx: SnapContext) => Promise<SnapHandlerResult>;
62
104
  export {};
package/dist/schemas.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
- import { EFFECT_VALUES, SPEC_VERSION, } from "./constants.js";
3
- import { DEFAULT_THEME_ACCENT, PALETTE_COLOR_VALUES, } from "./colors.js";
2
+ import { EFFECT_VALUES, SPEC_VERSION } from "./constants.js";
3
+ import { DEFAULT_THEME_ACCENT, PALETTE_COLOR_VALUES } from "./colors.js";
4
4
  // ─── Theme ─────────────────────────────────────────────
5
5
  const themeAccentSchema = z.enum(PALETTE_COLOR_VALUES, {
6
6
  message: `accent must be a palette color: ${PALETTE_COLOR_VALUES.join(", ")}`,
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ export declare const barChartProps: z.ZodObject<{
3
+ bars: z.ZodArray<z.ZodObject<{
4
+ label: z.ZodString;
5
+ value: z.ZodNumber;
6
+ color: z.ZodOptional<z.ZodEnum<{
7
+ gray: "gray";
8
+ blue: "blue";
9
+ red: "red";
10
+ amber: "amber";
11
+ green: "green";
12
+ teal: "teal";
13
+ purple: "purple";
14
+ pink: "pink";
15
+ }>>;
16
+ }, z.core.$strip>>;
17
+ max: z.ZodOptional<z.ZodNumber>;
18
+ color: z.ZodOptional<z.ZodEnum<{
19
+ gray: "gray";
20
+ blue: "blue";
21
+ red: "red";
22
+ amber: "amber";
23
+ green: "green";
24
+ teal: "teal";
25
+ purple: "purple";
26
+ pink: "pink";
27
+ accent: "accent";
28
+ }>>;
29
+ }, z.core.$strip>;
30
+ export type BarChartProps = z.infer<typeof barChartProps>;
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ import { BAR_CHART_COLOR_VALUES, PALETTE_COLOR_VALUES } from "../colors.js";
3
+ import { BAR_CHART_MAX_BARS, BAR_CHART_LABEL_MAX_CHARS, } from "../constants.js";
4
+ export const barChartProps = z
5
+ .object({
6
+ bars: z
7
+ .array(z.object({
8
+ label: z.string().min(1).max(BAR_CHART_LABEL_MAX_CHARS),
9
+ value: z.number().nonnegative(),
10
+ color: z.enum(PALETTE_COLOR_VALUES).optional(),
11
+ }))
12
+ .min(1)
13
+ .max(BAR_CHART_MAX_BARS),
14
+ max: z.number().nonnegative().optional(),
15
+ color: z.enum(BAR_CHART_COLOR_VALUES).optional(),
16
+ })
17
+ .superRefine((val, ctx) => {
18
+ if (val.max !== undefined) {
19
+ for (let i = 0; i < val.bars.length; i++) {
20
+ const bar = val.bars[i];
21
+ if (bar.value > val.max) {
22
+ ctx.addIssue({
23
+ code: "custom",
24
+ message: `bar value (${bar.value}) exceeds chart max (${val.max})`,
25
+ path: ["bars", i, "value"],
26
+ });
27
+ }
28
+ }
29
+ }
30
+ });
@@ -335,6 +335,71 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
335
335
  }, z.core.$strip>;
336
336
  description: string;
337
337
  };
338
+ bar_chart: {
339
+ props: z.ZodObject<{
340
+ bars: z.ZodArray<z.ZodObject<{
341
+ label: z.ZodString;
342
+ value: z.ZodNumber;
343
+ color: z.ZodOptional<z.ZodEnum<{
344
+ gray: "gray";
345
+ blue: "blue";
346
+ red: "red";
347
+ amber: "amber";
348
+ green: "green";
349
+ teal: "teal";
350
+ purple: "purple";
351
+ pink: "pink";
352
+ }>>;
353
+ }, z.core.$strip>>;
354
+ max: z.ZodOptional<z.ZodNumber>;
355
+ color: z.ZodOptional<z.ZodEnum<{
356
+ gray: "gray";
357
+ blue: "blue";
358
+ red: "red";
359
+ amber: "amber";
360
+ green: "green";
361
+ teal: "teal";
362
+ purple: "purple";
363
+ pink: "pink";
364
+ accent: "accent";
365
+ }>>;
366
+ }, z.core.$strip>;
367
+ description: string;
368
+ };
369
+ cell_grid: {
370
+ props: z.ZodObject<{
371
+ name: z.ZodOptional<z.ZodString>;
372
+ cols: z.ZodNumber;
373
+ rows: z.ZodNumber;
374
+ cells: z.ZodArray<z.ZodObject<{
375
+ row: z.ZodNumber;
376
+ col: z.ZodNumber;
377
+ color: z.ZodOptional<z.ZodEnum<{
378
+ gray: "gray";
379
+ blue: "blue";
380
+ red: "red";
381
+ amber: "amber";
382
+ green: "green";
383
+ teal: "teal";
384
+ purple: "purple";
385
+ pink: "pink";
386
+ }>>;
387
+ content: z.ZodOptional<z.ZodString>;
388
+ }, z.core.$strip>>;
389
+ gap: z.ZodOptional<z.ZodEnum<{
390
+ sm: "sm";
391
+ md: "md";
392
+ none: "none";
393
+ lg: "lg";
394
+ }>>;
395
+ select: z.ZodOptional<z.ZodEnum<{
396
+ multiple: "multiple";
397
+ off: "off";
398
+ single: "single";
399
+ }>>;
400
+ }, z.core.$strip>;
401
+ description: string;
402
+ };
338
403
  };
339
404
  actions: {
340
405
  submit: {
@@ -15,6 +15,8 @@ import { separatorProps } from "./separator.js";
15
15
  import { sliderProps } from "./slider.js";
16
16
  import { stackProps } from "./stack.js";
17
17
  import { textProps } from "./text.js";
18
+ import { barChartProps } from "./bar-chart.js";
19
+ import { cellGridProps } from "./cell-grid.js";
18
20
  const snapClientParams = z.object({
19
21
  client_action: z.record(z.string(), z.unknown()),
20
22
  });
@@ -82,6 +84,14 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
82
84
  props: textProps,
83
85
  description: "Text block — size: md (body, default), sm (caption). Optional weight and align.",
84
86
  },
87
+ bar_chart: {
88
+ props: barChartProps,
89
+ description: "Horizontal bar chart — 1–6 bars with label, value, and optional per-bar color. Optional max and default color.",
90
+ },
91
+ cell_grid: {
92
+ props: cellGridProps,
93
+ description: "Cell grid — sparse colored cells on a rows×cols grid. Optional gap and selection mode (taps write to inputs[name]).",
94
+ },
85
95
  },
86
96
  actions: {
87
97
  submit: {
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ export declare const cellGridProps: z.ZodObject<{
3
+ name: z.ZodOptional<z.ZodString>;
4
+ cols: z.ZodNumber;
5
+ rows: z.ZodNumber;
6
+ cells: z.ZodArray<z.ZodObject<{
7
+ row: z.ZodNumber;
8
+ col: z.ZodNumber;
9
+ color: z.ZodOptional<z.ZodEnum<{
10
+ gray: "gray";
11
+ blue: "blue";
12
+ red: "red";
13
+ amber: "amber";
14
+ green: "green";
15
+ teal: "teal";
16
+ purple: "purple";
17
+ pink: "pink";
18
+ }>>;
19
+ content: z.ZodOptional<z.ZodString>;
20
+ }, z.core.$strip>>;
21
+ gap: z.ZodOptional<z.ZodEnum<{
22
+ sm: "sm";
23
+ md: "md";
24
+ none: "none";
25
+ lg: "lg";
26
+ }>>;
27
+ select: z.ZodOptional<z.ZodEnum<{
28
+ multiple: "multiple";
29
+ off: "off";
30
+ single: "single";
31
+ }>>;
32
+ }, z.core.$strip>;
33
+ export type CellGridProps = z.infer<typeof cellGridProps>;