fimo 0.10.1-staging.4 → 0.11.0-staging.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/assets/skills/fimo/SKILL.md +4 -0
- package/assets/skills/fimo-code/references/content.md +19 -15
- package/assets/skills/fimo-code/references/ui.md +3 -0
- package/assets/skills/fimo-studio/references/ai-chat.md +8 -0
- package/assets/skills/fimo-studio/references/publish-and-deploy.md +6 -0
- package/dist/cli/bundle.json +1 -1
- package/dist/cli/index.js +3 -3
- package/dist/runtime/content/client/createContentClient.d.ts +15 -0
- package/dist/runtime/content/client/createContentClient.js +23 -0
- package/dist/runtime/content/client/createSingletonClient.js +3 -0
- package/dist/runtime/content/client/internal.js +63 -2
- package/dist/runtime/content/utils/retrieve-data-sources.d.ts +13 -0
- package/dist/runtime/content/utils/retrieve-data-sources.js +45 -31
- package/dist/runtime/primitives/components/Json.d.ts +25 -0
- package/dist/runtime/primitives/components/Json.js +29 -0
- package/dist/runtime/primitives/index.d.ts +1 -0
- package/dist/runtime/primitives/index.js +1 -0
- package/package.json +4 -4
- package/templates/react-router/package.json +6 -6
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import type { CollectionContentClient, ContentClientConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* React Query's structural sharing (`replaceEqualDeep`) rebuilds plain objects
|
|
4
|
+
* and arrays key-by-key from `Object.keys`, which silently drops the
|
|
5
|
+
* non-enumerable, symbol-keyed source tag `wrapField` puts on `json` values.
|
|
6
|
+
* A `json` field would then lose its `data-fimo-source` on the first refetch
|
|
7
|
+
* that changes it, making it unselectable in every editing surface. Every other
|
|
8
|
+
* wrapped type is a class instance, which structural sharing returns untouched.
|
|
9
|
+
*
|
|
10
|
+
* Turning it off costs nothing here: `wrapEntry` allocates fresh wrappers on
|
|
11
|
+
* every fetch, so a wrapped entry never compared equal to its predecessor and
|
|
12
|
+
* sharing was already returning the new object.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SOURCE_TRACKED_QUERY_OPTIONS: {
|
|
15
|
+
readonly structuralSharing: false;
|
|
16
|
+
};
|
|
2
17
|
export declare function createContentClient<T extends {
|
|
3
18
|
id: string;
|
|
4
19
|
}, TInput extends object>(config: ContentClientConfig): CollectionContentClient<T, TInput>;
|
|
@@ -2,6 +2,19 @@ import { useInfiniteQuery, useMutation, useQuery, useQueryClient, useSuspenseQue
|
|
|
2
2
|
import { parseCollectionResponse } from '../parseCollectionResponse.js';
|
|
3
3
|
import { apiBase, buildReadPath, fimoHeaders, interpretSchema, readCollectionLocale, resolveClientHandle, splitWritePayload, validateReferences, withMutationLocale, wrapEntry, } from './internal.js';
|
|
4
4
|
import { useQueryLocale } from './useQueryLocale.js';
|
|
5
|
+
/**
|
|
6
|
+
* React Query's structural sharing (`replaceEqualDeep`) rebuilds plain objects
|
|
7
|
+
* and arrays key-by-key from `Object.keys`, which silently drops the
|
|
8
|
+
* non-enumerable, symbol-keyed source tag `wrapField` puts on `json` values.
|
|
9
|
+
* A `json` field would then lose its `data-fimo-source` on the first refetch
|
|
10
|
+
* that changes it, making it unselectable in every editing surface. Every other
|
|
11
|
+
* wrapped type is a class instance, which structural sharing returns untouched.
|
|
12
|
+
*
|
|
13
|
+
* Turning it off costs nothing here: `wrapEntry` allocates fresh wrappers on
|
|
14
|
+
* every fetch, so a wrapped entry never compared equal to its predecessor and
|
|
15
|
+
* sharing was already returning the new object.
|
|
16
|
+
*/
|
|
17
|
+
export const SOURCE_TRACKED_QUERY_OPTIONS = { structuralSharing: false };
|
|
5
18
|
export function createContentClient(config) {
|
|
6
19
|
const references = config.references ?? {};
|
|
7
20
|
const info = interpretSchema(config.schema);
|
|
@@ -147,6 +160,7 @@ export function createContentClient(config) {
|
|
|
147
160
|
return useQuery({
|
|
148
161
|
queryKey: qk.byId(id, localizedOptions),
|
|
149
162
|
queryFn: () => getById(id, localizedOptions),
|
|
163
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
150
164
|
});
|
|
151
165
|
}
|
|
152
166
|
function useGetBySlug(slug, options = {}) {
|
|
@@ -155,6 +169,7 @@ export function createContentClient(config) {
|
|
|
155
169
|
return useQuery({
|
|
156
170
|
queryKey: qk.bySlug(slug, localizedOptions),
|
|
157
171
|
queryFn: () => getBySlug(slug, localizedOptions),
|
|
172
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
158
173
|
enabled: !!slug,
|
|
159
174
|
});
|
|
160
175
|
}
|
|
@@ -164,6 +179,7 @@ export function createContentClient(config) {
|
|
|
164
179
|
return useQuery({
|
|
165
180
|
queryKey: qk.byField(fieldName, value, localizedOptions),
|
|
166
181
|
queryFn: () => getByField(fieldName, value, localizedOptions),
|
|
182
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
167
183
|
enabled: !!value,
|
|
168
184
|
});
|
|
169
185
|
}
|
|
@@ -173,6 +189,7 @@ export function createContentClient(config) {
|
|
|
173
189
|
return useQuery({
|
|
174
190
|
queryKey: qk.list(localizedParams),
|
|
175
191
|
queryFn: () => list(localizedParams),
|
|
192
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
176
193
|
select: (page) => page.items,
|
|
177
194
|
});
|
|
178
195
|
}
|
|
@@ -182,6 +199,7 @@ export function createContentClient(config) {
|
|
|
182
199
|
return useQuery({
|
|
183
200
|
queryKey: qk.list(localizedParams),
|
|
184
201
|
queryFn: () => list(localizedParams),
|
|
202
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
185
203
|
});
|
|
186
204
|
}
|
|
187
205
|
function useInfiniteList(params = {}) {
|
|
@@ -190,6 +208,7 @@ export function createContentClient(config) {
|
|
|
190
208
|
const query = useInfiniteQuery({
|
|
191
209
|
queryKey: qk.infiniteList(localizedParams),
|
|
192
210
|
queryFn: ({ pageParam }) => list({ ...localizedParams, offset: pageParam }),
|
|
211
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
193
212
|
initialPageParam: 0,
|
|
194
213
|
getNextPageParam: (lastPage) => lastPage.pageInfo.nextOffset ?? undefined,
|
|
195
214
|
});
|
|
@@ -208,6 +227,7 @@ export function createContentClient(config) {
|
|
|
208
227
|
return useSuspenseQuery({
|
|
209
228
|
queryKey: qk.byId(id, localizedOptions),
|
|
210
229
|
queryFn: () => getById(id, localizedOptions),
|
|
230
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
211
231
|
});
|
|
212
232
|
}
|
|
213
233
|
function useSuspenseGetBySlug(slug, options = {}) {
|
|
@@ -216,6 +236,7 @@ export function createContentClient(config) {
|
|
|
216
236
|
return useSuspenseQuery({
|
|
217
237
|
queryKey: qk.bySlug(slug, localizedOptions),
|
|
218
238
|
queryFn: () => getBySlug(slug, localizedOptions),
|
|
239
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
219
240
|
});
|
|
220
241
|
}
|
|
221
242
|
function useSuspenseGetByField(fieldName, value, options = {}) {
|
|
@@ -224,6 +245,7 @@ export function createContentClient(config) {
|
|
|
224
245
|
return useSuspenseQuery({
|
|
225
246
|
queryKey: qk.byField(fieldName, value, localizedOptions),
|
|
226
247
|
queryFn: () => getByField(fieldName, value, localizedOptions),
|
|
248
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
227
249
|
});
|
|
228
250
|
}
|
|
229
251
|
function useSuspenseGet(params = {}) {
|
|
@@ -232,6 +254,7 @@ export function createContentClient(config) {
|
|
|
232
254
|
return useSuspenseQuery({
|
|
233
255
|
queryKey: qk.list(localizedParams),
|
|
234
256
|
queryFn: () => list(localizedParams),
|
|
257
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
235
258
|
select: (page) => page.items,
|
|
236
259
|
});
|
|
237
260
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useMutation, useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
|
|
2
|
+
import { SOURCE_TRACKED_QUERY_OPTIONS } from './createContentClient.js';
|
|
2
3
|
import { apiBase, buildReadPath, fimoHeaders, interpretSchema, readSingletonLocale, referenceTargetUids, resolveClientHandle, splitWritePayload, validateReferences, withMutationLocale, wrapEntry, } from './internal.js';
|
|
3
4
|
import { useQueryLocale } from './useQueryLocale.js';
|
|
4
5
|
export function createSingletonClient(config) {
|
|
@@ -78,6 +79,7 @@ export function createSingletonClient(config) {
|
|
|
78
79
|
return useQuery({
|
|
79
80
|
queryKey: qk.document(localizedOptions),
|
|
80
81
|
queryFn: () => get(localizedOptions),
|
|
82
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
81
83
|
});
|
|
82
84
|
}
|
|
83
85
|
function useSuspenseGet(options = {}) {
|
|
@@ -86,6 +88,7 @@ export function createSingletonClient(config) {
|
|
|
86
88
|
return useSuspenseQuery({
|
|
87
89
|
queryKey: qk.document(localizedOptions),
|
|
88
90
|
queryFn: () => get(localizedOptions),
|
|
91
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
89
92
|
});
|
|
90
93
|
}
|
|
91
94
|
function useUpdate() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { pascalCase } from 'change-case';
|
|
2
2
|
// @ts-ignore - virtual module provided by fimoConfigPlugin in fimo/vite
|
|
3
3
|
import fimoConfig from 'virtual:fimo-config';
|
|
4
|
-
import { FimoBoolean, FimoMedia, FimoRichText, FimoString } from '../../primitives/lib/primitives.js';
|
|
4
|
+
import { FIMO_SOURCE, FimoBoolean, FimoMedia, FimoRichText, FimoString } from '../../primitives/lib/primitives.js';
|
|
5
5
|
export function interpretSchema(schema) {
|
|
6
6
|
if (!schema || typeof schema.uid !== 'string' || schema.uid.length === 0) {
|
|
7
7
|
throw new Error('Content schema is missing a "uid".');
|
|
@@ -176,15 +176,59 @@ function wrapReference(value, uid, resolve) {
|
|
|
176
176
|
}
|
|
177
177
|
return target.wrapWithSource(value);
|
|
178
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Blocks values have no wrapper class of their own — the array structure stays
|
|
181
|
+
* plain JSON (matching the API/codegen shape) and only the scalar leaves inside
|
|
182
|
+
* each item are wrapped. `_key`/`_type` are structural metadata, never fields.
|
|
183
|
+
*/
|
|
184
|
+
function blockItemFields(field, item) {
|
|
185
|
+
const schema = field.schema;
|
|
186
|
+
if (!schema) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
if ('oneOf' in schema) {
|
|
190
|
+
return schema.oneOf.find((variant) => variant.uid === item._type)?.fields;
|
|
191
|
+
}
|
|
192
|
+
return schema.fields;
|
|
193
|
+
}
|
|
194
|
+
function wrapBlockItem(field, item, source, resolve) {
|
|
195
|
+
if (item == null || typeof item !== 'object' || Array.isArray(item)) {
|
|
196
|
+
return item;
|
|
197
|
+
}
|
|
198
|
+
const record = item;
|
|
199
|
+
const fields = blockItemFields(field, record);
|
|
200
|
+
if (!fields) {
|
|
201
|
+
return item;
|
|
202
|
+
}
|
|
203
|
+
const wrapped = { ...record };
|
|
204
|
+
for (const [key, childField] of Object.entries(fields)) {
|
|
205
|
+
wrapped[key] = wrapField(childField, record[key], `${source}.${key}`, resolve);
|
|
206
|
+
}
|
|
207
|
+
return wrapped;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* A JSON field's declared shape is a plain object/array — tagging it in place
|
|
211
|
+
* (rather than boxing it, like the scalar wrappers below) keeps that shape
|
|
212
|
+
* intact so consumers that read it as ordinary JSON see no difference.
|
|
213
|
+
*/
|
|
214
|
+
function tagWithSource(value, source) {
|
|
215
|
+
Object.defineProperty(value, FIMO_SOURCE, { value: source, enumerable: false, configurable: true });
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
179
218
|
function wrapField(field, value, source, resolve) {
|
|
180
219
|
switch (field.type) {
|
|
181
220
|
case 'string':
|
|
182
221
|
case 'text':
|
|
183
|
-
|
|
222
|
+
case 'date':
|
|
223
|
+
return value != null ? new FimoString(String(value), source) : value;
|
|
184
224
|
case 'richtext':
|
|
185
225
|
return value != null ? new FimoRichText(value, source) : value;
|
|
186
226
|
case 'boolean':
|
|
187
227
|
return value != null ? new FimoBoolean(value, source) : value;
|
|
228
|
+
case 'json':
|
|
229
|
+
return value != null && typeof value === 'object'
|
|
230
|
+
? tagWithSource(Array.isArray(value) ? [...value] : { ...value }, source)
|
|
231
|
+
: value;
|
|
188
232
|
case 'media':
|
|
189
233
|
if (field.multiple) {
|
|
190
234
|
return (value?.map((m, i) => new FimoMedia(m, `${source}[${i}]`)) ?? value);
|
|
@@ -207,6 +251,23 @@ function wrapField(field, value, source, resolve) {
|
|
|
207
251
|
}
|
|
208
252
|
return wrapReference(value, uid, resolve);
|
|
209
253
|
}
|
|
254
|
+
case 'blocks':
|
|
255
|
+
return Array.isArray(value)
|
|
256
|
+
? value.map((item, i) => wrapBlockItem(field, item, `${source}[${i}]`, resolve))
|
|
257
|
+
: value;
|
|
258
|
+
// 'number' deliberately falls through here — this is a decision, not a
|
|
259
|
+
// gap. React renders a boxed String directly (`<span>{new FimoString(...)}
|
|
260
|
+
// </span>` works, because React special-cases String objects), but it
|
|
261
|
+
// throws "Objects are not valid as a React child" on a boxed Number. A
|
|
262
|
+
// FimoNumber wrapper — whether a plain class or `extends Number` — would
|
|
263
|
+
// break `{post.views}`, which is exactly how a raw number field renders
|
|
264
|
+
// in every Fimo site today. `extends Number` would recover `.toFixed()`/
|
|
265
|
+
// `.toPrecision()`/`.toLocaleString()` and make `JSON.stringify` emit `7`
|
|
266
|
+
// instead of `{"value":7}`, but it does NOT fix JSX rendering, and
|
|
267
|
+
// `typeof`/`===`/`Number.isInteger` stay broken either way (boxed
|
|
268
|
+
// primitives never satisfy those). Revisiting this needs a `<Number>`
|
|
269
|
+
// component in `fimo/ui` (none exists yet — every other wrapped type has
|
|
270
|
+
// one) plus a migration path for sites already rendering numbers raw.
|
|
210
271
|
default:
|
|
211
272
|
return value;
|
|
212
273
|
}
|
|
@@ -6,6 +6,19 @@ interface RecordData {
|
|
|
6
6
|
visible: boolean;
|
|
7
7
|
}
|
|
8
8
|
export type GroupedDataSources = Record<string, RecordData[]>;
|
|
9
|
+
/**
|
|
10
|
+
* Parses a `data-fimo-source` attribute into its schema, identity, and field
|
|
11
|
+
* parts. The field itself may contain dots (e.g. a blocks leaf such as
|
|
12
|
+
* "body[0].title"), so only the first two segments are fixed positions.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseDataSource(source: string): {
|
|
15
|
+
schemaName: string;
|
|
16
|
+
identity: {
|
|
17
|
+
id: string;
|
|
18
|
+
locale?: string;
|
|
19
|
+
};
|
|
20
|
+
fieldName: string;
|
|
21
|
+
} | null;
|
|
9
22
|
/**
|
|
10
23
|
* Finds all visible elements with a `data-fimo-source` attribute and groups them
|
|
11
24
|
* by schema and ID into a structured object.
|
|
@@ -14,6 +14,24 @@ function parseRecordIdentity(token) {
|
|
|
14
14
|
return null;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Parses a `data-fimo-source` attribute into its schema, identity, and field
|
|
19
|
+
* parts. The field itself may contain dots (e.g. a blocks leaf such as
|
|
20
|
+
* "body[0].title"), so only the first two segments are fixed positions.
|
|
21
|
+
*/
|
|
22
|
+
export function parseDataSource(source) {
|
|
23
|
+
const parts = source.split('.');
|
|
24
|
+
if (parts.length < 3) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const [schemaName, identityToken] = parts;
|
|
28
|
+
const fieldName = parts.slice(2).join('.');
|
|
29
|
+
const identity = parseRecordIdentity(identityToken);
|
|
30
|
+
if (!identity) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return { schemaName, identity, fieldName };
|
|
34
|
+
}
|
|
17
35
|
/**
|
|
18
36
|
* Checks if an element is genuinely visible to the user within the viewport.
|
|
19
37
|
* @param el The element to check.
|
|
@@ -51,37 +69,33 @@ export function retrieveVisibleDataSources() {
|
|
|
51
69
|
if (!source) {
|
|
52
70
|
return;
|
|
53
71
|
}
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Add the field and its text content to the record
|
|
82
|
-
if (el.textContent) {
|
|
83
|
-
tempGroupedData[schemaName][recordKey].data[fieldName] = el.textContent.trim();
|
|
84
|
-
}
|
|
72
|
+
const parsed = parseDataSource(source);
|
|
73
|
+
if (!parsed) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const { schemaName, identity, fieldName } = parsed;
|
|
77
|
+
const { id, locale } = identity;
|
|
78
|
+
const recordKey = JSON.stringify([id, locale ?? null]);
|
|
79
|
+
// Skip __translations
|
|
80
|
+
if (schemaName === '__translations') {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Ensure the schema key exists
|
|
84
|
+
if (!tempGroupedData[schemaName]) {
|
|
85
|
+
tempGroupedData[schemaName] = {};
|
|
86
|
+
}
|
|
87
|
+
// Ensure the record (by ID) exists for this schema
|
|
88
|
+
if (!tempGroupedData[schemaName][recordKey]) {
|
|
89
|
+
tempGroupedData[schemaName][recordKey] = {
|
|
90
|
+
id: id,
|
|
91
|
+
...(locale ? { locale } : {}),
|
|
92
|
+
data: {},
|
|
93
|
+
visible: isElementVisible(el),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// Add the field and its text content to the record
|
|
97
|
+
if (el.textContent) {
|
|
98
|
+
tempGroupedData[schemaName][recordKey].data[fieldName] = el.textContent.trim();
|
|
85
99
|
}
|
|
86
100
|
});
|
|
87
101
|
// Convert the temporary object into the final desired array structure.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef, type ElementType, type ReactNode } from 'react';
|
|
2
|
+
type JsonProps<T extends ElementType = 'span'> = {
|
|
3
|
+
value: unknown;
|
|
4
|
+
as?: T;
|
|
5
|
+
children?: (value: unknown) => ReactNode;
|
|
6
|
+
} & Omit<ComponentPropsWithoutRef<T>, 'value' | 'as' | 'children'>;
|
|
7
|
+
/**
|
|
8
|
+
* Json component for rendering JSON field values with source tracking.
|
|
9
|
+
*
|
|
10
|
+
* Use children render function to customize how the value is displayed.
|
|
11
|
+
* Pass `as="pre"` (with a `white-space: pre-wrap` class) for formatted output.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Default (single-line JSON text)
|
|
15
|
+
* <Json value={product.metadata} />
|
|
16
|
+
*
|
|
17
|
+
* // Pretty-printed
|
|
18
|
+
* <Json value={product.metadata} as="pre" className="whitespace-pre-wrap" />
|
|
19
|
+
*
|
|
20
|
+
* // With render function for custom display
|
|
21
|
+
* <Json value={product.metadata}>{(val) => val.tags?.join(', ')}</Json>
|
|
22
|
+
*/
|
|
23
|
+
export declare function Json<T extends ElementType = 'span'>({ value, as, children, ...props }: JsonProps<T>): import("react/jsx-runtime").JSX.Element | null;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=Json.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { getFimoSource, useFimoLiveValue } from '../lib/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Json component for rendering JSON field values with source tracking.
|
|
5
|
+
*
|
|
6
|
+
* Use children render function to customize how the value is displayed.
|
|
7
|
+
* Pass `as="pre"` (with a `white-space: pre-wrap` class) for formatted output.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // Default (single-line JSON text)
|
|
11
|
+
* <Json value={product.metadata} />
|
|
12
|
+
*
|
|
13
|
+
* // Pretty-printed
|
|
14
|
+
* <Json value={product.metadata} as="pre" className="whitespace-pre-wrap" />
|
|
15
|
+
*
|
|
16
|
+
* // With render function for custom display
|
|
17
|
+
* <Json value={product.metadata}>{(val) => val.tags?.join(', ')}</Json>
|
|
18
|
+
*/
|
|
19
|
+
export function Json({ value, as, children, ...props }) {
|
|
20
|
+
const Component = as || 'span';
|
|
21
|
+
const { value: liveValue, ref } = useFimoLiveValue(value);
|
|
22
|
+
// Defensive: return null if no value
|
|
23
|
+
if (value === null || value === undefined) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const source = getFimoSource(value);
|
|
27
|
+
const content = children ? children(liveValue) : JSON.stringify(liveValue);
|
|
28
|
+
return (_jsx(Component, { ref: ref, ...props, "data-fimo-source": source, children: content }));
|
|
29
|
+
}
|
|
@@ -4,6 +4,7 @@ export { StaticImage } from './components/StaticImage.js';
|
|
|
4
4
|
export { Video } from './components/Video.js';
|
|
5
5
|
export { Date } from './components/Date.js';
|
|
6
6
|
export { Boolean } from './components/Boolean.js';
|
|
7
|
+
export { Json } from './components/Json.js';
|
|
7
8
|
export { RichText } from './components/RichText.js';
|
|
8
9
|
export { useLabels, useTranslations } from './translations.js';
|
|
9
10
|
export { FimoString, FimoDate, FimoMedia, FimoBoolean, FimoRichText, type FimoPolymorphicReference, type FimoReference, type FimoSource, type FimoPart, type FimoRichTextContent, type FimoRichTextNodeProps, type FimoRichTextMarkProps, type FimoRichTextComponents, FIMO_SOURCE, FIMO_PARTS, isFimoString, isFimoDate, hasFimoSource, getFimoSource, getFimoParts, fimo, useFimoLiveValue, } from './lib/index.js';
|
|
@@ -7,6 +7,7 @@ export { StaticImage } from './components/StaticImage.js';
|
|
|
7
7
|
export { Video } from './components/Video.js';
|
|
8
8
|
export { Date } from './components/Date.js';
|
|
9
9
|
export { Boolean } from './components/Boolean.js';
|
|
10
|
+
export { Json } from './components/Json.js';
|
|
10
11
|
export { RichText } from './components/RichText.js';
|
|
11
12
|
// Labels
|
|
12
13
|
export { useLabels, useTranslations } from './translations.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fimo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0-staging.2",
|
|
4
4
|
"description": "Fimo CLI - create, deploy, and manage Fimo projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fimo": "dist/cli/index.js"
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"eta": "^3.5.0",
|
|
69
69
|
"oxc-parser": "^0.118.0",
|
|
70
70
|
"react-error-boundary": "^6.0.0",
|
|
71
|
-
"zod": "4.
|
|
71
|
+
"zod": "4.4.3"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@clack/core": "^1.2.0",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"@types/react": "^19.2.14",
|
|
83
83
|
"@types/react-dom": "^19.2.3",
|
|
84
84
|
"agent-install": "^0.0.5",
|
|
85
|
-
"better-auth": "1.6.
|
|
85
|
+
"better-auth": "1.6.25",
|
|
86
86
|
"cheerio": "^1.0.0",
|
|
87
87
|
"commander": "^13.1.0",
|
|
88
88
|
"esbuild": "^0.25.10",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"typescript": "5.9.2",
|
|
95
95
|
"vite": "^8.0.13",
|
|
96
96
|
"vitest": "^4.1.6",
|
|
97
|
-
"wrangler": "
|
|
97
|
+
"wrangler": "4.114.0",
|
|
98
98
|
"yaml": "2.8.2"
|
|
99
99
|
},
|
|
100
100
|
"peerDependencies": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@base-ui/react": "^1.3.0",
|
|
15
|
-
"@hookform/resolvers": "^5.
|
|
15
|
+
"@hookform/resolvers": "^5.5.7",
|
|
16
16
|
"@react-router/node": "^8.2.0",
|
|
17
17
|
"@tailwindcss/typography": "^0.5.19",
|
|
18
18
|
"@tanstack/react-query": "^5.80.6",
|
|
@@ -21,25 +21,25 @@
|
|
|
21
21
|
"cmdk": "^1.1.1",
|
|
22
22
|
"date-fns": "^4.1.0",
|
|
23
23
|
"embla-carousel-react": "^8.6.0",
|
|
24
|
-
"fimo": "0.
|
|
24
|
+
"fimo": "0.11.0-staging.2",
|
|
25
25
|
"input-otp": "^1.4.2",
|
|
26
26
|
"isbot": "^5",
|
|
27
27
|
"lucide-react": "^0.577.0",
|
|
28
|
-
"motion": "^12.
|
|
28
|
+
"motion": "^12.42.2",
|
|
29
29
|
"next-themes": "^0.4.6",
|
|
30
|
-
"radix-ui": "^1.
|
|
30
|
+
"radix-ui": "^1.6.7",
|
|
31
31
|
"react": "^19.2.7",
|
|
32
32
|
"react-day-picker": "^9.14.0",
|
|
33
33
|
"react-dom": "^19.2.7",
|
|
34
34
|
"react-error-boundary": "^6.0.0",
|
|
35
|
-
"react-hook-form": "^7.
|
|
35
|
+
"react-hook-form": "^7.83.0",
|
|
36
36
|
"react-resizable-panels": "^4.9.0",
|
|
37
37
|
"react-router": "^8.2.0",
|
|
38
38
|
"recharts": "^3.8.0",
|
|
39
39
|
"sonner": "^2.0.7",
|
|
40
40
|
"tailwind-merge": "^3.2.0",
|
|
41
41
|
"vaul": "^1.1.2",
|
|
42
|
-
"zod": "^4.3
|
|
42
|
+
"zod": "^4.4.3"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@react-router/dev": "^8.2.0",
|