@localess/react 3.0.1-dev.20260408170501 → 3.0.1-dev.20260408172608
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/README.md +99 -25
- package/SKILL.md +91 -46
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -249,40 +249,92 @@ const Image = ({ data }) => (
|
|
|
249
249
|
|
|
250
250
|
---
|
|
251
251
|
|
|
252
|
+
## `useLocaless` Hook
|
|
253
|
+
|
|
254
|
+
`useLocaless<T>` fetches content by slug in a Client Component and automatically subscribes to Visual Editor live updates when `enableSync` is active.
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
'use client';
|
|
258
|
+
|
|
259
|
+
import { useLocaless, LocalessComponent } from "@localess/react";
|
|
260
|
+
import type { Page } from "./.localess/localess";
|
|
261
|
+
|
|
262
|
+
export function PageView({ slug }: { slug: string }) {
|
|
263
|
+
const content = useLocaless<Page>(slug, { locale: 'en' });
|
|
264
|
+
|
|
265
|
+
if (!content) return <div>Loading…</div>;
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<main>
|
|
269
|
+
{content.data.body.map(item => (
|
|
270
|
+
<LocalessComponent key={item._id} data={item} links={content.links} />
|
|
271
|
+
))}
|
|
272
|
+
</main>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Parameters
|
|
278
|
+
|
|
279
|
+
| Parameter | Type | Required | Description |
|
|
280
|
+
|-----------|------|----------|-------------|
|
|
281
|
+
| `slug` | `string \| string[]` | ✅ | Content slug. Arrays are joined with `/` — e.g. `['blog', 'post']` → `'blog/post'` |
|
|
282
|
+
| `options` | `ContentFetchParams` | ❌ | Same fetch options as `getContentBySlug` (locale, version, resolveReference, resolveLink) |
|
|
283
|
+
|
|
284
|
+
Returns `Content<T> | undefined` — `undefined` while the initial fetch is in progress.
|
|
285
|
+
|
|
286
|
+
When `enableSync` is active and the page is rendered inside the Localess Visual Editor iframe, the hook automatically subscribes to `input` / `change` events and updates the returned content in place.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Link Utilities
|
|
291
|
+
|
|
292
|
+
### `findLink(links, link)`
|
|
293
|
+
|
|
294
|
+
Resolves a `ContentLink` field to a URL string. Use it to build `href` values from Localess content links.
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
import { findLink } from "@localess/react";
|
|
298
|
+
|
|
299
|
+
// type: 'content' → '/' + fullSlug, or '/not-found' if not in map
|
|
300
|
+
// type: 'url' → raw URI unchanged
|
|
301
|
+
const href = findLink(content.links, data.ctaLink);
|
|
302
|
+
|
|
303
|
+
const NavLink = ({ data, links }) => (
|
|
304
|
+
<a href={findLink(links, data.link)}>{data.label}</a>
|
|
305
|
+
);
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
252
310
|
## Visual Editor Events
|
|
253
311
|
|
|
254
|
-
When
|
|
312
|
+
When `enableSync: true` is set in `localessInit`, Visual Editor live-editing is handled **automatically** by the `useLocaless` hook — no manual event wiring needed.
|
|
255
313
|
|
|
256
314
|
```tsx
|
|
257
315
|
'use client';
|
|
258
316
|
|
|
259
|
-
import {
|
|
260
|
-
import {
|
|
261
|
-
import type { Content } from "@localess/react";
|
|
317
|
+
import { useLocaless, LocalessComponent, localessEditable } from "@localess/react";
|
|
318
|
+
import type { Page } from "./.localess/localess";
|
|
262
319
|
|
|
263
|
-
export function
|
|
264
|
-
|
|
320
|
+
export function PageView({ slug, locale }: { slug: string; locale?: string }) {
|
|
321
|
+
// Automatically subscribes to Visual Editor input/change events when enableSync is active
|
|
322
|
+
const content = useLocaless<Page>(slug, { locale });
|
|
265
323
|
|
|
266
|
-
|
|
267
|
-
if (window.localess) {
|
|
268
|
-
window.localess.on(['input', 'change'], (event) => {
|
|
269
|
-
if (event.type === 'input' || event.type === 'change') {
|
|
270
|
-
setPageData(event.data);
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}, []);
|
|
324
|
+
if (!content) return null;
|
|
275
325
|
|
|
276
326
|
return (
|
|
277
|
-
<main {...localessEditable(
|
|
278
|
-
{
|
|
279
|
-
<LocalessComponent key={item._id} data={item} />
|
|
327
|
+
<main {...localessEditable(content.data)}>
|
|
328
|
+
{content.data?.body.map(item => (
|
|
329
|
+
<LocalessComponent key={item._id} data={item} links={content.links} references={content.references} />
|
|
280
330
|
))}
|
|
281
331
|
</main>
|
|
282
332
|
);
|
|
283
333
|
}
|
|
284
334
|
```
|
|
285
335
|
|
|
336
|
+
> `useLocaless` handles the full cycle: initial fetch + live sync updates when inside the editor iframe.
|
|
337
|
+
|
|
286
338
|
---
|
|
287
339
|
|
|
288
340
|
## Full Example (Next.js 15 App Router)
|
|
@@ -296,7 +348,7 @@ localessInit({
|
|
|
296
348
|
origin: process.env.LOCALESS_ORIGIN!,
|
|
297
349
|
spaceId: process.env.LOCALESS_SPACE_ID!,
|
|
298
350
|
token: process.env.LOCALESS_TOKEN!,
|
|
299
|
-
enableSync: process.env.NODE_ENV
|
|
351
|
+
enableSync: process.env.NODE_ENV !== 'production',
|
|
300
352
|
components: { Page, Header, Teaser, Footer },
|
|
301
353
|
});
|
|
302
354
|
|
|
@@ -306,19 +358,41 @@ export default function RootLayout({ children }) {
|
|
|
306
358
|
```
|
|
307
359
|
|
|
308
360
|
```tsx
|
|
309
|
-
// app/page.tsx (Server Component)
|
|
310
|
-
import { getLocalessClient
|
|
361
|
+
// app/[locale]/page.tsx (Server Component — fetches initial data)
|
|
362
|
+
import { getLocalessClient } from "@localess/react";
|
|
363
|
+
import { PageClient } from "./page-client";
|
|
311
364
|
import type { Page } from "./.localess/localess";
|
|
312
365
|
|
|
313
366
|
export default async function HomePage({
|
|
314
|
-
|
|
367
|
+
params,
|
|
315
368
|
}: {
|
|
316
|
-
|
|
369
|
+
params: Promise<{ locale?: string }>;
|
|
317
370
|
}) {
|
|
318
|
-
const { locale } = await
|
|
371
|
+
const { locale } = await params;
|
|
319
372
|
const client = getLocalessClient();
|
|
320
373
|
const content = await client.getContentBySlug<Page>('home', { locale });
|
|
321
374
|
|
|
375
|
+
return <PageClient initialContent={content} locale={locale} />;
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
```tsx
|
|
380
|
+
// app/[locale]/page-client.tsx (Client Component — renders + auto-syncs with Visual Editor)
|
|
381
|
+
'use client';
|
|
382
|
+
|
|
383
|
+
import { useLocaless, LocalessComponent, localessEditable } from "@localess/react";
|
|
384
|
+
import type { Content, Page } from "./.localess/localess";
|
|
385
|
+
|
|
386
|
+
export function PageClient({
|
|
387
|
+
initialContent,
|
|
388
|
+
locale,
|
|
389
|
+
}: {
|
|
390
|
+
initialContent: Content<Page>;
|
|
391
|
+
locale?: string;
|
|
392
|
+
}) {
|
|
393
|
+
// Handles initial fetch and Visual Editor live updates automatically
|
|
394
|
+
const content = useLocaless<Page>('home', { locale }) ?? initialContent;
|
|
395
|
+
|
|
322
396
|
return (
|
|
323
397
|
<main {...localessEditable(content.data)}>
|
|
324
398
|
{content.data?.body.map(item => (
|
|
@@ -342,7 +416,7 @@ The following are re-exported for convenience so you only need to import from `@
|
|
|
342
416
|
|
|
343
417
|
**Types:** `Content`, `ContentData`, `ContentMetadata`, `ContentDataSchema`, `ContentDataField`, `ContentAsset`, `ContentRichText`, `ContentLink`, `ContentReference`, `Links`, `References`, `Translations`, `LocalessClient`, `LocalessSync`, `EventToApp`, `EventCallback`, `EventToAppType`
|
|
344
418
|
|
|
345
|
-
**Functions:** `localessEditable`, `localessEditableField`, `llEditable` *(deprecated)*, `llEditableField` *(deprecated)*, `isBrowser`, `isServer`, `isIframe`
|
|
419
|
+
**Functions:** `localessEditable`, `localessEditableField`, `llEditable` *(deprecated)*, `llEditableField` *(deprecated)*, `isBrowser`, `isServer`, `isIframe`, `resolveAsset`, `findLink`, `useLocaless`, `renderRichTextToReact`, `localessInit`, `getLocalessClient`, `registerComponent`, `unregisterComponent`, `setComponents`, `getComponent`, `setFallbackComponent`, `getFallbackComponent`, `isSyncEnabled`
|
|
346
420
|
|
|
347
421
|
---
|
|
348
422
|
|
package/SKILL.md
CHANGED
|
@@ -176,37 +176,26 @@ localessInit({
|
|
|
176
176
|
|
|
177
177
|
### Receiving Real-time Editor Events
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
Use the `useLocaless` hook in a Client Component — it **automatically subscribes** to `input` / `change` events when `enableSync` is active. No manual `window.localess.on()` wiring needed.
|
|
180
180
|
|
|
181
181
|
```tsx
|
|
182
182
|
'use client';
|
|
183
183
|
|
|
184
|
-
import {
|
|
185
|
-
import { LocalessComponent, localessEditable } from "@localess/react";
|
|
184
|
+
import { useLocaless, LocalessComponent, localessEditable } from "@localess/react";
|
|
186
185
|
import type { Content, Page } from "./.localess/localess";
|
|
187
186
|
|
|
188
|
-
export function PageClient({ initialContent }: { initialContent: Content<Page
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
useEffect(() => {
|
|
192
|
-
if (window.localess) {
|
|
193
|
-
window.localess.on(['input', 'change'], (event) => {
|
|
194
|
-
if (event.type === 'input' || event.type === 'change') {
|
|
195
|
-
setPageData(event.data);
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
// No cleanup needed: window.localess has no .off() method
|
|
200
|
-
}, []);
|
|
187
|
+
export function PageClient({ initialContent, locale }: { initialContent: Content<Page>; locale?: string }) {
|
|
188
|
+
// Fetches content and auto-syncs with Visual Editor live updates
|
|
189
|
+
const content = useLocaless<Page>('home', { locale }) ?? initialContent;
|
|
201
190
|
|
|
202
191
|
return (
|
|
203
|
-
<main {...localessEditable(
|
|
204
|
-
{
|
|
192
|
+
<main {...localessEditable(content.data)}>
|
|
193
|
+
{content.data?.body?.map(item => (
|
|
205
194
|
<LocalessComponent
|
|
206
195
|
key={item._id}
|
|
207
196
|
data={item}
|
|
208
|
-
links={
|
|
209
|
-
references={
|
|
197
|
+
links={content.links}
|
|
198
|
+
references={content.references}
|
|
210
199
|
/>
|
|
211
200
|
))}
|
|
212
201
|
</main>
|
|
@@ -214,7 +203,7 @@ export function PageClient({ initialContent }: { initialContent: Content<Page> }
|
|
|
214
203
|
}
|
|
215
204
|
```
|
|
216
205
|
|
|
217
|
-
**Available events
|
|
206
|
+
**Available events (handled internally by `useLocaless`):**
|
|
218
207
|
|
|
219
208
|
| Event | When |
|
|
220
209
|
|---------------|-----------------------------------------------|
|
|
@@ -230,56 +219,45 @@ export function PageClient({ initialContent }: { initialContent: Content<Page> }
|
|
|
230
219
|
|
|
231
220
|
### Pattern: Split Server/Client Components (Next.js App Router)
|
|
232
221
|
|
|
233
|
-
|
|
222
|
+
Keep data fetching server-side and delegate rendering + sync to a Client Component using `useLocaless`:
|
|
234
223
|
|
|
235
224
|
```tsx
|
|
236
|
-
// app/[locale]/page.tsx — Server Component: fetches data
|
|
225
|
+
// app/[locale]/page.tsx — Server Component: fetches initial data
|
|
237
226
|
import { getLocalessClient } from "@localess/react";
|
|
238
227
|
import { PageClient } from "./page-client";
|
|
239
228
|
import type { Page } from "./.localess/localess";
|
|
240
229
|
|
|
241
230
|
export default async function HomePage({
|
|
242
|
-
|
|
231
|
+
params,
|
|
243
232
|
}: {
|
|
244
|
-
|
|
233
|
+
params: Promise<{ locale?: string }>;
|
|
245
234
|
}) {
|
|
246
|
-
const { locale } = await
|
|
235
|
+
const { locale } = await params;
|
|
247
236
|
const client = getLocalessClient();
|
|
248
237
|
const content = await client.getContentBySlug<Page>('home', { locale });
|
|
249
238
|
|
|
250
|
-
return <PageClient initialContent={content} />;
|
|
239
|
+
return <PageClient initialContent={content} locale={locale} />;
|
|
251
240
|
}
|
|
252
241
|
```
|
|
253
242
|
|
|
254
243
|
```tsx
|
|
255
|
-
// app/[locale]/page-client.tsx — Client Component: renders +
|
|
244
|
+
// app/[locale]/page-client.tsx — Client Component: renders + auto-syncs
|
|
256
245
|
'use client';
|
|
257
246
|
|
|
258
|
-
import {
|
|
259
|
-
import { LocalessComponent, localessEditable } from "@localess/react";
|
|
247
|
+
import { useLocaless, LocalessComponent, localessEditable } from "@localess/react";
|
|
260
248
|
import type { Content, Page } from "./.localess/localess";
|
|
261
249
|
|
|
262
|
-
export function PageClient({ initialContent }: { initialContent: Content<Page
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
useEffect(() => {
|
|
266
|
-
if (window.localess) {
|
|
267
|
-
window.localess.on(['input', 'change'], (event) => {
|
|
268
|
-
if (event.type === 'input' || event.type === 'change') {
|
|
269
|
-
setPageData(event.data);
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}, []);
|
|
250
|
+
export function PageClient({ initialContent, locale }: { initialContent: Content<Page>; locale?: string }) {
|
|
251
|
+
const content = useLocaless<Page>('home', { locale }) ?? initialContent;
|
|
274
252
|
|
|
275
253
|
return (
|
|
276
|
-
<main {...localessEditable(
|
|
277
|
-
{
|
|
254
|
+
<main {...localessEditable(content.data)}>
|
|
255
|
+
{content.data?.body?.map(item => (
|
|
278
256
|
<LocalessComponent
|
|
279
257
|
key={item._id}
|
|
280
258
|
data={item}
|
|
281
|
-
links={
|
|
282
|
-
references={
|
|
259
|
+
links={content.links}
|
|
260
|
+
references={content.references}
|
|
283
261
|
/>
|
|
284
262
|
))}
|
|
285
263
|
</main>
|
|
@@ -289,6 +267,67 @@ export function PageClient({ initialContent }: { initialContent: Content<Page> }
|
|
|
289
267
|
|
|
290
268
|
---
|
|
291
269
|
|
|
270
|
+
## `useLocaless` Hook
|
|
271
|
+
|
|
272
|
+
`useLocaless<T>` fetches content by slug on the client side and automatically wires up Visual Editor live updates when `enableSync` is active.
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
'use client';
|
|
276
|
+
|
|
277
|
+
import { useLocaless } from "@localess/react";
|
|
278
|
+
import type { Page } from "./.localess/localess";
|
|
279
|
+
|
|
280
|
+
export function PageView({ slug }: { slug: string }) {
|
|
281
|
+
const content = useLocaless<Page>(slug, { locale: 'en' });
|
|
282
|
+
|
|
283
|
+
if (!content) return <div>Loading…</div>;
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<main>
|
|
287
|
+
{content.data.body.map(item => (
|
|
288
|
+
<LocalessComponent key={item._id} data={item} links={content.links} />
|
|
289
|
+
))}
|
|
290
|
+
</main>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Signature
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
useLocaless<T extends ContentData = ContentData>(
|
|
299
|
+
slug: string | string[], // string[] is joined with '/' — e.g. ['blog', 'post'] → 'blog/post'
|
|
300
|
+
options?: ContentFetchParams
|
|
301
|
+
): Content<T> | undefined
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
- Returns `undefined` while the initial fetch is in flight.
|
|
305
|
+
- When `enableSync` is active and the page is inside the Localess Visual Editor, automatically subscribes to `input` / `change` events and updates the returned value in place.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Link Utilities
|
|
310
|
+
|
|
311
|
+
### `findLink(links, link)`
|
|
312
|
+
|
|
313
|
+
Resolves a `ContentLink` to a URL string. Use this to convert link fields from Localess content into href values.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { findLink } from "@localess/react";
|
|
317
|
+
|
|
318
|
+
const href = findLink(content.links, data.ctaLink);
|
|
319
|
+
// type: 'content' → '/' + fullSlug (e.g. '/blog/my-post'), or '/not-found' if not in links map
|
|
320
|
+
// type: 'url' → raw URI (e.g. 'https://example.com')
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
const NavLink = ({ data, links }) => (
|
|
325
|
+
<a href={findLink(links, data.link)}>{data.label}</a>
|
|
326
|
+
);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
292
331
|
## Rich Text Rendering
|
|
293
332
|
|
|
294
333
|
Converts Localess `ContentRichText` (Tiptap JSON) to a React node tree.
|
|
@@ -440,6 +479,12 @@ export { LocalessComponent } // Dynamic schema-to-component renderer
|
|
|
440
479
|
export { renderRichTextToReact } // Rich text → React nodes
|
|
441
480
|
export { resolveAsset } // ContentAsset → full URL
|
|
442
481
|
|
|
482
|
+
// Hooks
|
|
483
|
+
export { useLocaless } // Client-side content fetching with sync support
|
|
484
|
+
|
|
485
|
+
// Utilities
|
|
486
|
+
export { findLink } // ContentLink → URL string
|
|
487
|
+
|
|
443
488
|
// Visual editor (re-exported from @localess/client)
|
|
444
489
|
export { localessEditable, localessEditableField }
|
|
445
490
|
export { llEditable, llEditableField } // Deprecated
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@localess/react",
|
|
3
|
-
"version": "3.0.1-dev.
|
|
3
|
+
"version": "3.0.1-dev.20260408172608",
|
|
4
4
|
"description": "ReactJS JavaScript/TypeScript SDK for Localess's API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"localess",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"react-dom": "^17 || ^18 || ^19"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@localess/client": "3.0.1-dev.
|
|
49
|
+
"@localess/client": "3.0.1-dev.20260408172608",
|
|
50
50
|
"@tiptap/static-renderer": "^3.20.1",
|
|
51
51
|
"@tiptap/html": "^3.20.1",
|
|
52
52
|
"@tiptap/extension-bold": "^3.20.1",
|