@farcaster/snap 2.6.4 → 2.7.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/index.d.ts +1 -0
- package/dist/react/index.d.ts +7 -1
- package/dist/react/index.js +3 -3
- package/dist/react/snap-view-core.d.ts +4 -5
- package/dist/react/snap-view-core.js +54 -58
- package/dist/react/v1/snap-view.d.ts +9 -3
- package/dist/react/v1/snap-view.js +4 -4
- package/dist/react/v2/snap-view.d.ts +9 -3
- package/dist/react/v2/snap-view.js +4 -4
- package/dist/react-native/index.d.ts +7 -3
- package/dist/react-native/index.js +3 -3
- package/dist/react-native/snap-view-core.d.ts +4 -5
- package/dist/react-native/snap-view-core.js +54 -69
- package/dist/react-native/types.d.ts +2 -0
- package/dist/react-native/v1/snap-view.d.ts +12 -4
- package/dist/react-native/v1/snap-view.js +8 -8
- package/dist/react-native/v2/snap-view.d.ts +12 -4
- package/dist/react-native/v2/snap-view.js +8 -8
- package/dist/render-state.d.ts +14 -0
- package/dist/render-state.js +116 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/react/index.tsx +13 -0
- package/src/react/snap-view-core.tsx +94 -65
- package/src/react/v1/snap-view.tsx +15 -1
- package/src/react/v2/snap-view.tsx +15 -1
- package/src/react-native/index.tsx +22 -2
- package/src/react-native/snap-view-core.tsx +86 -80
- package/src/react-native/types.ts +3 -0
- package/src/react-native/v1/snap-view.tsx +27 -1
- package/src/react-native/v2/snap-view.tsx +27 -1
- package/src/render-state.ts +184 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
export type SnapRenderState = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export type SnapRenderStateChanges =
|
|
4
|
+
| { path: string; value: unknown }[]
|
|
5
|
+
| Record<string, unknown>
|
|
6
|
+
| null
|
|
7
|
+
| undefined;
|
|
8
|
+
|
|
9
|
+
const SNAP_RENDER_STATE_META_KEY = "__snapRender";
|
|
10
|
+
|
|
11
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeEffects(effects: readonly string[] | undefined): string[] {
|
|
16
|
+
if (!effects) return [];
|
|
17
|
+
|
|
18
|
+
return Array.from(
|
|
19
|
+
new Set(
|
|
20
|
+
effects.filter(
|
|
21
|
+
(effect): effect is string =>
|
|
22
|
+
typeof effect === "string" && effect.length > 0,
|
|
23
|
+
),
|
|
24
|
+
),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getRenderStateMeta(
|
|
29
|
+
model: SnapRenderState,
|
|
30
|
+
): Record<string, unknown> | undefined {
|
|
31
|
+
const meta = model[SNAP_RENDER_STATE_META_KEY];
|
|
32
|
+
return isRecord(meta) ? meta : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getPresentedSnapEffects(model: SnapRenderState): Set<string> {
|
|
36
|
+
const presentedEffects = getRenderStateMeta(model)?.presentedEffects;
|
|
37
|
+
if (!Array.isArray(presentedEffects)) return new Set();
|
|
38
|
+
|
|
39
|
+
return new Set(
|
|
40
|
+
presentedEffects.filter(
|
|
41
|
+
(effect): effect is string =>
|
|
42
|
+
typeof effect === "string" && effect.length > 0,
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function cloneSnapRenderState<T>(value: T): T {
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
return value.map((item) => cloneSnapRenderState(item)) as T;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (isRecord(value)) {
|
|
53
|
+
const next: Record<string, unknown> = {};
|
|
54
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
55
|
+
next[key] = cloneSnapRenderState(nestedValue);
|
|
56
|
+
}
|
|
57
|
+
return next as T;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function mergeRenderState(
|
|
64
|
+
base: Record<string, unknown>,
|
|
65
|
+
override: Record<string, unknown> | undefined,
|
|
66
|
+
): Record<string, unknown> {
|
|
67
|
+
if (!override) return cloneSnapRenderState(base);
|
|
68
|
+
|
|
69
|
+
const next = cloneSnapRenderState(base);
|
|
70
|
+
for (const [key, value] of Object.entries(override)) {
|
|
71
|
+
const existing = next[key];
|
|
72
|
+
next[key] =
|
|
73
|
+
isRecord(existing) && isRecord(value)
|
|
74
|
+
? mergeRenderState(existing, value)
|
|
75
|
+
: cloneSnapRenderState(value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return next;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeStatePath(path: string): string[] {
|
|
82
|
+
const trimmed = path.startsWith("/") ? path.slice(1) : path;
|
|
83
|
+
return trimmed.split("/").filter(Boolean);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function setStateValue(
|
|
87
|
+
model: Record<string, unknown>,
|
|
88
|
+
parts: string[],
|
|
89
|
+
value: unknown,
|
|
90
|
+
) {
|
|
91
|
+
let cursor = model;
|
|
92
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
93
|
+
const part = parts[index]!;
|
|
94
|
+
if (index === parts.length - 1) {
|
|
95
|
+
cursor[part] = cloneSnapRenderState(value);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const next = cursor[part];
|
|
100
|
+
if (!isRecord(next)) {
|
|
101
|
+
cursor[part] = {};
|
|
102
|
+
}
|
|
103
|
+
cursor = cursor[part] as Record<string, unknown>;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function applyStatePaths(
|
|
108
|
+
model: Record<string, unknown>,
|
|
109
|
+
changes: SnapRenderStateChanges,
|
|
110
|
+
): void {
|
|
111
|
+
if (!changes) return;
|
|
112
|
+
|
|
113
|
+
const entries = Array.isArray(changes)
|
|
114
|
+
? changes.map((change) => [change.path, change.value] as const)
|
|
115
|
+
: Object.entries(changes);
|
|
116
|
+
|
|
117
|
+
for (const [path, value] of entries) {
|
|
118
|
+
const parts = normalizeStatePath(path);
|
|
119
|
+
if (parts.length === 0) continue;
|
|
120
|
+
setStateValue(model, parts, value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function getUnpresentedSnapEffects(
|
|
125
|
+
model: SnapRenderState,
|
|
126
|
+
effects: readonly string[] | undefined,
|
|
127
|
+
): string[] {
|
|
128
|
+
const presentedEffects = getPresentedSnapEffects(model);
|
|
129
|
+
return normalizeEffects(effects).filter(
|
|
130
|
+
(effect) => !presentedEffects.has(effect),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function markSnapEffectsPresented(
|
|
135
|
+
model: SnapRenderState,
|
|
136
|
+
effects: readonly string[] | undefined,
|
|
137
|
+
): boolean {
|
|
138
|
+
const nextEffects = normalizeEffects(effects);
|
|
139
|
+
if (nextEffects.length === 0) return false;
|
|
140
|
+
|
|
141
|
+
const presentedEffects = getPresentedSnapEffects(model);
|
|
142
|
+
let changed = false;
|
|
143
|
+
for (const effect of nextEffects) {
|
|
144
|
+
if (!presentedEffects.has(effect)) {
|
|
145
|
+
presentedEffects.add(effect);
|
|
146
|
+
changed = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (!changed) return false;
|
|
150
|
+
|
|
151
|
+
let meta = getRenderStateMeta(model);
|
|
152
|
+
if (!meta) {
|
|
153
|
+
meta = {};
|
|
154
|
+
model[SNAP_RENDER_STATE_META_KEY] = meta;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
meta.presentedEffects = Array.from(presentedEffects);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function buildInitialRenderState({
|
|
162
|
+
specState,
|
|
163
|
+
initialRenderState,
|
|
164
|
+
themeAccent,
|
|
165
|
+
}: {
|
|
166
|
+
specState: unknown;
|
|
167
|
+
initialRenderState?: SnapRenderState;
|
|
168
|
+
themeAccent?: string;
|
|
169
|
+
}): SnapRenderState {
|
|
170
|
+
const authoredState = isRecord(specState) ? specState : {};
|
|
171
|
+
const restoredState = initialRenderState
|
|
172
|
+
? mergeRenderState(authoredState, initialRenderState)
|
|
173
|
+
: cloneSnapRenderState(authoredState);
|
|
174
|
+
|
|
175
|
+
if (!isRecord(restoredState.inputs)) {
|
|
176
|
+
restoredState.inputs = {};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const theme = isRecord(restoredState.theme) ? restoredState.theme : {};
|
|
180
|
+
restoredState.theme =
|
|
181
|
+
themeAccent === undefined ? theme : { ...theme, accent: themeAccent };
|
|
182
|
+
|
|
183
|
+
return restoredState;
|
|
184
|
+
}
|