@localess/react 3.0.1-dev.20260408172608 → 3.0.1-dev.20260408183951
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 +183 -21
- package/SKILL.md +144 -18
- package/dist/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +33 -12
- package/dist/index.mjs +27 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -309,7 +309,9 @@ const NavLink = ({ data, links }) => (
|
|
|
309
309
|
|
|
310
310
|
## Visual Editor Events
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
### With `useLocaless` Hook
|
|
313
|
+
|
|
314
|
+
When `enableSync: true` is set in `localessInit`, the `useLocaless` hook handles the full cycle automatically — initial fetch and live sync updates — with no extra wiring needed.
|
|
313
315
|
|
|
314
316
|
```tsx
|
|
315
317
|
'use client';
|
|
@@ -318,7 +320,6 @@ import { useLocaless, LocalessComponent, localessEditable } from "@localess/reac
|
|
|
318
320
|
import type { Page } from "./.localess/localess";
|
|
319
321
|
|
|
320
322
|
export function PageView({ slug, locale }: { slug: string; locale?: string }) {
|
|
321
|
-
// Automatically subscribes to Visual Editor input/change events when enableSync is active
|
|
322
323
|
const content = useLocaless<Page>(slug, { locale });
|
|
323
324
|
|
|
324
325
|
if (!content) return null;
|
|
@@ -333,14 +334,101 @@ export function PageView({ slug, locale }: { slug: string; locale?: string }) {
|
|
|
333
334
|
}
|
|
334
335
|
```
|
|
335
336
|
|
|
336
|
-
|
|
337
|
+
### With `LocalessDocument` Component
|
|
338
|
+
|
|
339
|
+
`LocalessDocument` is a component alternative to the hook. Pass it server-fetched content data and it handles live sync updates internally, delegating rendering to `LocalessComponent`.
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
// app/[locale]/page.tsx (Server Component — fetches data)
|
|
343
|
+
import { getLocalessClient, LocalessDocument } from "@localess/react";
|
|
344
|
+
import type { Page } from "./.localess/localess";
|
|
345
|
+
|
|
346
|
+
export default async function HomePage({ params }: { params: Promise<{ locale?: string }> }) {
|
|
347
|
+
const { locale } = await params;
|
|
348
|
+
const client = getLocalessClient();
|
|
349
|
+
const content = await client.getContentBySlug<Page>('home', { locale });
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<LocalessDocument
|
|
353
|
+
data={content.data}
|
|
354
|
+
links={content.links}
|
|
355
|
+
references={content.references}
|
|
356
|
+
/>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Props:**
|
|
362
|
+
|
|
363
|
+
| Prop | Type | Required | Description |
|
|
364
|
+
|------|------|----------|-------------|
|
|
365
|
+
| `data` | `ContentData` | ✅ | Initial content data (typically server-fetched) |
|
|
366
|
+
| `links` | `Links` | ❌ | Resolved links map, forwarded to the inner `LocalessComponent` |
|
|
367
|
+
| `references` | `References` | ❌ | Resolved references map, forwarded to the inner `LocalessComponent` |
|
|
368
|
+
| `ref` | `React.Ref<HTMLElement>` | ❌ | Forwarded to the rendered root element |
|
|
369
|
+
| `...rest` | `any` | ❌ | Any additional props are forwarded |
|
|
370
|
+
|
|
371
|
+
> `LocalessDocument` subscribes to `input` / `change` editor events automatically when `enableSync` is active. It is a Client Component internally — no `'use client'` directive needed at the call site in Server Components.
|
|
372
|
+
|
|
373
|
+
### Manual Integration
|
|
374
|
+
|
|
375
|
+
If you manage content state yourself without `useLocaless` or `LocalessDocument`, subscribe to editor events directly via `window.localess`:
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
'use client';
|
|
379
|
+
|
|
380
|
+
import { useEffect, useState } from "react";
|
|
381
|
+
import { LocalessComponent, localessEditable, isSyncEnabled, isBrowser } from "@localess/react";
|
|
382
|
+
import type { Content, Page } from "./.localess/localess";
|
|
383
|
+
|
|
384
|
+
export function PageClient({ initialContent }: { initialContent: Content<Page> }) {
|
|
385
|
+
const [pageData, setPageData] = useState(initialContent.data);
|
|
386
|
+
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
if (isSyncEnabled() && isBrowser() && window.localess) {
|
|
389
|
+
window.localess.on(['input', 'change'], (event) => {
|
|
390
|
+
if (event.type === 'input' || event.type === 'change') {
|
|
391
|
+
setPageData(event.data);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
// No cleanup needed: window.localess has no .off() method
|
|
396
|
+
}, []);
|
|
397
|
+
|
|
398
|
+
return (
|
|
399
|
+
<main {...localessEditable(pageData)}>
|
|
400
|
+
{pageData?.body.map(item => (
|
|
401
|
+
<LocalessComponent key={item._id} data={item} links={initialContent.links} references={initialContent.references} />
|
|
402
|
+
))}
|
|
403
|
+
</main>
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Available events via `window.localess.on()`:**
|
|
409
|
+
|
|
410
|
+
| Event | When |
|
|
411
|
+
|-------|------|
|
|
412
|
+
| `input` | User is typing in a field (real-time preview) |
|
|
413
|
+
| `change` | Field value confirmed |
|
|
414
|
+
| `save` | Content saved |
|
|
415
|
+
| `publish` | Content published |
|
|
416
|
+
| `pong` | Editor heartbeat response |
|
|
417
|
+
| `enterSchema` | Editor cursor enters a schema block |
|
|
418
|
+
| `hoverSchema` | Editor cursor hovers over a schema block |
|
|
419
|
+
|
|
420
|
+
> `window.localess` only exposes `.on()` and `.onChange()` — there is no `.off()` method.
|
|
337
421
|
|
|
338
422
|
---
|
|
339
423
|
|
|
340
|
-
## Full Example (Next.js
|
|
424
|
+
## Full Example (Next.js 16.2 App Router)
|
|
425
|
+
|
|
426
|
+
The recommended Next.js pattern is to **preload data server-side** and pass it to the Client Component. This avoids a loading flash — the page renders immediately with server data, then Visual Editor sync kicks in if active.
|
|
427
|
+
|
|
428
|
+
### Setup — `app/layout.tsx`
|
|
341
429
|
|
|
342
430
|
```tsx
|
|
343
|
-
//
|
|
431
|
+
// Server Component — safe to use API token here
|
|
344
432
|
import { localessInit } from "@localess/react";
|
|
345
433
|
import { Page, Header, Teaser, Footer } from "@/components";
|
|
346
434
|
|
|
@@ -352,16 +440,21 @@ localessInit({
|
|
|
352
440
|
components: { Page, Header, Teaser, Footer },
|
|
353
441
|
});
|
|
354
442
|
|
|
355
|
-
export default function RootLayout({ children }) {
|
|
443
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
356
444
|
return <html><body>{children}</body></html>;
|
|
357
445
|
}
|
|
358
446
|
```
|
|
359
447
|
|
|
448
|
+
### Server Component — `app/[locale]/page.tsx`
|
|
449
|
+
|
|
450
|
+
Fetches content during SSR and passes it as a prop. The client component receives it already populated — no loading state needed.
|
|
451
|
+
|
|
360
452
|
```tsx
|
|
361
|
-
// app/[locale]/page.tsx (Server Component — fetches initial data)
|
|
362
453
|
import { getLocalessClient } from "@localess/react";
|
|
363
|
-
import {
|
|
364
|
-
|
|
454
|
+
import type { Content, Page } from "./.localess/localess";
|
|
455
|
+
|
|
456
|
+
// Choose one of the three client components below
|
|
457
|
+
import { PageClientHook } from "./page-client-hook";
|
|
365
458
|
|
|
366
459
|
export default async function HomePage({
|
|
367
460
|
params,
|
|
@@ -369,39 +462,106 @@ export default async function HomePage({
|
|
|
369
462
|
params: Promise<{ locale?: string }>;
|
|
370
463
|
}) {
|
|
371
464
|
const { locale } = await params;
|
|
372
|
-
const
|
|
373
|
-
const content = await client.getContentBySlug<Page>('home', { locale });
|
|
465
|
+
const content = await getLocalessClient().getContentBySlug<Page>('home', { locale });
|
|
374
466
|
|
|
375
|
-
return <
|
|
467
|
+
return <PageClientHook initialContent={content} locale={locale} />;
|
|
376
468
|
}
|
|
377
469
|
```
|
|
378
470
|
|
|
471
|
+
### Client Component — Option A: `useLocaless` Hook
|
|
472
|
+
|
|
473
|
+
The hook re-fetches on the client and falls back to the server-preloaded data until it resolves. Live sync is wired automatically.
|
|
474
|
+
|
|
379
475
|
```tsx
|
|
380
|
-
// app/[locale]/page-client.tsx
|
|
476
|
+
// app/[locale]/page-client-hook.tsx
|
|
381
477
|
'use client';
|
|
382
478
|
|
|
383
479
|
import { useLocaless, LocalessComponent, localessEditable } from "@localess/react";
|
|
384
480
|
import type { Content, Page } from "./.localess/localess";
|
|
385
481
|
|
|
386
|
-
export function
|
|
482
|
+
export function PageClientHook({
|
|
387
483
|
initialContent,
|
|
388
484
|
locale,
|
|
389
485
|
}: {
|
|
390
486
|
initialContent: Content<Page>;
|
|
391
487
|
locale?: string;
|
|
392
488
|
}) {
|
|
393
|
-
//
|
|
489
|
+
// ?? initialContent: renders with server data immediately, switches to hook result once ready
|
|
394
490
|
const content = useLocaless<Page>('home', { locale }) ?? initialContent;
|
|
395
491
|
|
|
396
492
|
return (
|
|
397
493
|
<main {...localessEditable(content.data)}>
|
|
398
494
|
{content.data?.body.map(item => (
|
|
399
|
-
<LocalessComponent
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
495
|
+
<LocalessComponent key={item._id} data={item} links={content.links} references={content.references} />
|
|
496
|
+
))}
|
|
497
|
+
</main>
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Client Component — Option B: `LocalessDocument` Component
|
|
503
|
+
|
|
504
|
+
Skips client re-fetch entirely — uses server-preloaded data and only subscribes to live sync events. Simpler when you don't need client-side refetching.
|
|
505
|
+
|
|
506
|
+
```tsx
|
|
507
|
+
// app/[locale]/page.tsx (Server Component — no separate client file needed)
|
|
508
|
+
import { getLocalessClient, LocalessDocument } from "@localess/react";
|
|
509
|
+
import type { Page } from "./.localess/localess";
|
|
510
|
+
|
|
511
|
+
export default async function HomePage({
|
|
512
|
+
params,
|
|
513
|
+
}: {
|
|
514
|
+
params: Promise<{ locale?: string }>;
|
|
515
|
+
}) {
|
|
516
|
+
const { locale } = await params;
|
|
517
|
+
const content = await getLocalessClient().getContentBySlug<Page>('home', { locale });
|
|
518
|
+
|
|
519
|
+
// LocalessDocument handles sync internally — no 'use client' wrapper needed here
|
|
520
|
+
return (
|
|
521
|
+
<LocalessDocument
|
|
522
|
+
data={content.data}
|
|
523
|
+
links={content.links}
|
|
524
|
+
references={content.references}
|
|
525
|
+
/>
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Client Component — Option C: Manual
|
|
531
|
+
|
|
532
|
+
Full control over state and sync subscription. Use when you need custom logic around live updates.
|
|
533
|
+
|
|
534
|
+
```tsx
|
|
535
|
+
// app/[locale]/page-client-manual.tsx
|
|
536
|
+
'use client';
|
|
537
|
+
|
|
538
|
+
import { useEffect, useState } from "react";
|
|
539
|
+
import { LocalessComponent, localessEditable, isSyncEnabled, isBrowser } from "@localess/react";
|
|
540
|
+
import type { Content, Page } from "./.localess/localess";
|
|
541
|
+
|
|
542
|
+
export function PageClientManual({
|
|
543
|
+
initialContent,
|
|
544
|
+
}: {
|
|
545
|
+
initialContent: Content<Page>;
|
|
546
|
+
}) {
|
|
547
|
+
// Initialize with server-preloaded data — no loading state needed
|
|
548
|
+
const [pageData, setPageData] = useState(initialContent.data);
|
|
549
|
+
|
|
550
|
+
useEffect(() => {
|
|
551
|
+
if (isSyncEnabled() && isBrowser() && window.localess) {
|
|
552
|
+
window.localess.on(['input', 'change'], (event) => {
|
|
553
|
+
if (event.type === 'input' || event.type === 'change') {
|
|
554
|
+
setPageData(event.data);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
// No cleanup needed: window.localess has no .off() method
|
|
559
|
+
}, []);
|
|
560
|
+
|
|
561
|
+
return (
|
|
562
|
+
<main {...localessEditable(pageData)}>
|
|
563
|
+
{pageData?.body.map(item => (
|
|
564
|
+
<LocalessComponent key={item._id} data={item} links={initialContent.links} references={initialContent.references} />
|
|
405
565
|
))}
|
|
406
566
|
</main>
|
|
407
567
|
);
|
|
@@ -418,6 +578,8 @@ The following are re-exported for convenience so you only need to import from `@
|
|
|
418
578
|
|
|
419
579
|
**Functions:** `localessEditable`, `localessEditableField`, `llEditable` *(deprecated)*, `llEditableField` *(deprecated)*, `isBrowser`, `isServer`, `isIframe`, `resolveAsset`, `findLink`, `useLocaless`, `renderRichTextToReact`, `localessInit`, `getLocalessClient`, `registerComponent`, `unregisterComponent`, `setComponents`, `getComponent`, `setFallbackComponent`, `getFallbackComponent`, `isSyncEnabled`
|
|
420
580
|
|
|
581
|
+
**Components:** `LocalessComponent`, `LocalessDocument`
|
|
582
|
+
|
|
421
583
|
---
|
|
422
584
|
|
|
423
585
|
## AI Coding Agents
|
package/SKILL.md
CHANGED
|
@@ -174,9 +174,9 @@ localessInit({
|
|
|
174
174
|
|
|
175
175
|
> Never enable sync in production — the script is only meaningful inside the Localess Visual Editor iframe.
|
|
176
176
|
|
|
177
|
-
###
|
|
177
|
+
### With `useLocaless` Hook
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
The `useLocaless` hook handles the full cycle automatically — initial fetch and live sync updates — with no extra wiring needed.
|
|
180
180
|
|
|
181
181
|
```tsx
|
|
182
182
|
'use client';
|
|
@@ -185,7 +185,6 @@ import { useLocaless, LocalessComponent, localessEditable } from "@localess/reac
|
|
|
185
185
|
import type { Content, Page } from "./.localess/localess";
|
|
186
186
|
|
|
187
187
|
export function PageClient({ initialContent, locale }: { initialContent: Content<Page>; locale?: string }) {
|
|
188
|
-
// Fetches content and auto-syncs with Visual Editor live updates
|
|
189
188
|
const content = useLocaless<Page>('home', { locale }) ?? initialContent;
|
|
190
189
|
|
|
191
190
|
return (
|
|
@@ -203,7 +202,82 @@ export function PageClient({ initialContent, locale }: { initialContent: Content
|
|
|
203
202
|
}
|
|
204
203
|
```
|
|
205
204
|
|
|
206
|
-
|
|
205
|
+
### With `LocalessDocument` Component
|
|
206
|
+
|
|
207
|
+
`LocalessDocument` is a component alternative to the hook. It accepts server-fetched `data` and manages live sync updates internally, delegating rendering to `LocalessComponent`. Useful when you prefer a component-based approach over hooks.
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
// Server Component — pass fetched data directly to LocalessDocument
|
|
211
|
+
import { getLocalessClient, LocalessDocument } from "@localess/react";
|
|
212
|
+
import type { Page } from "./.localess/localess";
|
|
213
|
+
|
|
214
|
+
export default async function HomePage({ params }: { params: Promise<{ locale?: string }> }) {
|
|
215
|
+
const { locale } = await params;
|
|
216
|
+
const client = getLocalessClient();
|
|
217
|
+
const content = await client.getContentBySlug<Page>('home', { locale });
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<LocalessDocument
|
|
221
|
+
data={content.data}
|
|
222
|
+
links={content.links}
|
|
223
|
+
references={content.references}
|
|
224
|
+
/>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Props** (same shape as `LocalessComponent`):
|
|
230
|
+
|
|
231
|
+
| Prop | Type | Required | Description |
|
|
232
|
+
|------|------|----------|-------------|
|
|
233
|
+
| `data` | `ContentData` | ✅ | Initial content data |
|
|
234
|
+
| `links` | `Links` | ❌ | Resolved links map |
|
|
235
|
+
| `references` | `References` | ❌ | Resolved references map |
|
|
236
|
+
| `ref` | `React.Ref<HTMLElement>` | ❌ | Forwarded to the rendered root element |
|
|
237
|
+
|
|
238
|
+
> Subscribes to `input` / `change` events automatically when `enableSync` is active. Unlike `useLocaless`, it does not fetch content — it only handles live sync for data passed in as props.
|
|
239
|
+
|
|
240
|
+
### Manual Integration
|
|
241
|
+
|
|
242
|
+
If you manage content state yourself without `useLocaless` or `LocalessDocument`, subscribe to editor events directly via `window.localess`:
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
'use client';
|
|
246
|
+
|
|
247
|
+
import { useEffect, useState } from "react";
|
|
248
|
+
import { LocalessComponent, localessEditable, isSyncEnabled, isBrowser } from "@localess/react";
|
|
249
|
+
import type { Content, Page } from "./.localess/localess";
|
|
250
|
+
|
|
251
|
+
export function PageClient({ initialContent }: { initialContent: Content<Page> }) {
|
|
252
|
+
const [pageData, setPageData] = useState(initialContent.data);
|
|
253
|
+
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
if (isSyncEnabled() && isBrowser() && window.localess) {
|
|
256
|
+
window.localess.on(['input', 'change'], (event) => {
|
|
257
|
+
if (event.type === 'input' || event.type === 'change') {
|
|
258
|
+
setPageData(event.data);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// No cleanup needed: window.localess has no .off() method
|
|
263
|
+
}, []);
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<main {...localessEditable(pageData)}>
|
|
267
|
+
{pageData?.body?.map(item => (
|
|
268
|
+
<LocalessComponent
|
|
269
|
+
key={item._id}
|
|
270
|
+
data={item}
|
|
271
|
+
links={initialContent.links}
|
|
272
|
+
references={initialContent.references}
|
|
273
|
+
/>
|
|
274
|
+
))}
|
|
275
|
+
</main>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Available events via `window.localess.on()`:**
|
|
207
281
|
|
|
208
282
|
| Event | When |
|
|
209
283
|
|---------------|-----------------------------------------------|
|
|
@@ -219,13 +293,14 @@ export function PageClient({ initialContent, locale }: { initialContent: Content
|
|
|
219
293
|
|
|
220
294
|
### Pattern: Split Server/Client Components (Next.js App Router)
|
|
221
295
|
|
|
222
|
-
|
|
296
|
+
**Preload data server-side** and pass it to the Client Component. The page renders immediately with server data — no loading flash — and Visual Editor sync kicks in on top.
|
|
297
|
+
|
|
298
|
+
**Server Component** (same for all three options below):
|
|
223
299
|
|
|
224
300
|
```tsx
|
|
225
|
-
// app/[locale]/page.tsx
|
|
301
|
+
// app/[locale]/page.tsx
|
|
226
302
|
import { getLocalessClient } from "@localess/react";
|
|
227
|
-
import {
|
|
228
|
-
import type { Page } from "./.localess/localess";
|
|
303
|
+
import type { Content, Page } from "./.localess/localess";
|
|
229
304
|
|
|
230
305
|
export default async function HomePage({
|
|
231
306
|
params,
|
|
@@ -233,32 +308,82 @@ export default async function HomePage({
|
|
|
233
308
|
params: Promise<{ locale?: string }>;
|
|
234
309
|
}) {
|
|
235
310
|
const { locale } = await params;
|
|
236
|
-
|
|
237
|
-
const content = await
|
|
311
|
+
// Data fetched during SSR — preloaded into the client component as a prop
|
|
312
|
+
const content = await getLocalessClient().getContentBySlug<Page>('home', { locale });
|
|
238
313
|
|
|
239
314
|
return <PageClient initialContent={content} locale={locale} />;
|
|
240
315
|
}
|
|
241
316
|
```
|
|
242
317
|
|
|
318
|
+
**Option A — `useLocaless` hook**: re-fetches on client, falls back to server data until resolved, auto-syncs with editor.
|
|
319
|
+
|
|
243
320
|
```tsx
|
|
244
|
-
// app/[locale]/page-client.tsx
|
|
321
|
+
// app/[locale]/page-client.tsx
|
|
245
322
|
'use client';
|
|
246
323
|
|
|
247
324
|
import { useLocaless, LocalessComponent, localessEditable } from "@localess/react";
|
|
248
325
|
import type { Content, Page } from "./.localess/localess";
|
|
249
326
|
|
|
250
327
|
export function PageClient({ initialContent, locale }: { initialContent: Content<Page>; locale?: string }) {
|
|
328
|
+
// ?? initialContent: renders server data immediately, switches to hook result once ready
|
|
251
329
|
const content = useLocaless<Page>('home', { locale }) ?? initialContent;
|
|
252
330
|
|
|
253
331
|
return (
|
|
254
332
|
<main {...localessEditable(content.data)}>
|
|
255
333
|
{content.data?.body?.map(item => (
|
|
256
|
-
<LocalessComponent
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
334
|
+
<LocalessComponent key={item._id} data={item} links={content.links} references={content.references} />
|
|
335
|
+
))}
|
|
336
|
+
</main>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Option B — `LocalessDocument` component**: no client re-fetch, uses server data directly, auto-syncs with editor.
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
// app/[locale]/page.tsx — no separate client file needed
|
|
345
|
+
import { getLocalessClient, LocalessDocument } from "@localess/react";
|
|
346
|
+
import type { Page } from "./.localess/localess";
|
|
347
|
+
|
|
348
|
+
export default async function HomePage({ params }: { params: Promise<{ locale?: string }> }) {
|
|
349
|
+
const { locale } = await params;
|
|
350
|
+
const content = await getLocalessClient().getContentBySlug<Page>('home', { locale });
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<LocalessDocument data={content.data} links={content.links} references={content.references} />
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Option C — Manual**: full control, initialise state with server-preloaded data, subscribe to sync yourself.
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
// app/[locale]/page-client.tsx
|
|
362
|
+
'use client';
|
|
363
|
+
|
|
364
|
+
import { useEffect, useState } from "react";
|
|
365
|
+
import { LocalessComponent, localessEditable, isSyncEnabled, isBrowser } from "@localess/react";
|
|
366
|
+
import type { Content, Page } from "./.localess/localess";
|
|
367
|
+
|
|
368
|
+
export function PageClient({ initialContent }: { initialContent: Content<Page> }) {
|
|
369
|
+
// Server-preloaded data used as initial state — no loading flash
|
|
370
|
+
const [pageData, setPageData] = useState(initialContent.data);
|
|
371
|
+
|
|
372
|
+
useEffect(() => {
|
|
373
|
+
if (isSyncEnabled() && isBrowser() && window.localess) {
|
|
374
|
+
window.localess.on(['input', 'change'], (event) => {
|
|
375
|
+
if (event.type === 'input' || event.type === 'change') {
|
|
376
|
+
setPageData(event.data);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
// No cleanup needed: window.localess has no .off() method
|
|
381
|
+
}, []);
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<main {...localessEditable(pageData)}>
|
|
385
|
+
{pageData?.body?.map(item => (
|
|
386
|
+
<LocalessComponent key={item._id} data={item} links={initialContent.links} references={initialContent.references} />
|
|
262
387
|
))}
|
|
263
388
|
</main>
|
|
264
389
|
);
|
|
@@ -400,7 +525,7 @@ const [content, translations] = await Promise.all([
|
|
|
400
525
|
|
|
401
526
|
---
|
|
402
527
|
|
|
403
|
-
## Full Next.js
|
|
528
|
+
## Full Next.js 16.2 App Router Setup
|
|
404
529
|
|
|
405
530
|
```typescript
|
|
406
531
|
// app/layout.tsx (Server Component)
|
|
@@ -476,6 +601,7 @@ export { setFallbackComponent, getFallbackComponent, isSyncEnabled }
|
|
|
476
601
|
|
|
477
602
|
// Rendering
|
|
478
603
|
export { LocalessComponent } // Dynamic schema-to-component renderer
|
|
604
|
+
export { LocalessDocument } // Schema renderer + built-in Visual Editor sync
|
|
479
605
|
export { renderRichTextToReact } // Rich text → React nodes
|
|
480
606
|
export { resolveAsset } // ContentAsset → full URL
|
|
481
607
|
|
package/dist/index.d.mts
CHANGED
|
@@ -10,6 +10,13 @@ type LocalessComponentProps<T extends ContentData = ContentData> = {
|
|
|
10
10
|
};
|
|
11
11
|
declare const LocalessComponent: React.ForwardRefExoticComponent<LocalessComponentProps<ContentData> & React.RefAttributes<HTMLElement>>;
|
|
12
12
|
|
|
13
|
+
type LocalessDocumentProps<T extends ContentData = ContentData> = {
|
|
14
|
+
data: T;
|
|
15
|
+
links?: Links;
|
|
16
|
+
references?: References;
|
|
17
|
+
};
|
|
18
|
+
declare const LocalessDocument: React.ForwardRefExoticComponent<LocalessDocumentProps<ContentData> & React.RefAttributes<HTMLElement>>;
|
|
19
|
+
|
|
13
20
|
type LocalessOptions = LocalessClientOptions & {
|
|
14
21
|
/**
|
|
15
22
|
* Components mapping for Localess Component integration
|
|
@@ -91,4 +98,4 @@ declare function isSyncEnabled(): boolean;
|
|
|
91
98
|
*/
|
|
92
99
|
declare function resolveAsset(asset: ContentAsset): string;
|
|
93
100
|
|
|
94
|
-
export { LocalessComponent, type LocalessComponentProps, type LocalessOptions, type UseLocalessOptions, findLink, getComponent, getFallbackComponent, getLocalessClient, isSyncEnabled, localessInit, registerComponent, renderRichTextToReact, resolveAsset, setComponents, setFallbackComponent, unregisterComponent, useLocaless };
|
|
101
|
+
export { LocalessComponent, type LocalessComponentProps, LocalessDocument, type LocalessDocumentProps, type LocalessOptions, type UseLocalessOptions, findLink, getComponent, getFallbackComponent, getLocalessClient, isSyncEnabled, localessInit, registerComponent, renderRichTextToReact, resolveAsset, setComponents, setFallbackComponent, unregisterComponent, useLocaless };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,13 @@ type LocalessComponentProps<T extends ContentData = ContentData> = {
|
|
|
10
10
|
};
|
|
11
11
|
declare const LocalessComponent: React.ForwardRefExoticComponent<LocalessComponentProps<ContentData> & React.RefAttributes<HTMLElement>>;
|
|
12
12
|
|
|
13
|
+
type LocalessDocumentProps<T extends ContentData = ContentData> = {
|
|
14
|
+
data: T;
|
|
15
|
+
links?: Links;
|
|
16
|
+
references?: References;
|
|
17
|
+
};
|
|
18
|
+
declare const LocalessDocument: React.ForwardRefExoticComponent<LocalessDocumentProps<ContentData> & React.RefAttributes<HTMLElement>>;
|
|
19
|
+
|
|
13
20
|
type LocalessOptions = LocalessClientOptions & {
|
|
14
21
|
/**
|
|
15
22
|
* Components mapping for Localess Component integration
|
|
@@ -91,4 +98,4 @@ declare function isSyncEnabled(): boolean;
|
|
|
91
98
|
*/
|
|
92
99
|
declare function resolveAsset(asset: ContentAsset): string;
|
|
93
100
|
|
|
94
|
-
export { LocalessComponent, type LocalessComponentProps, type LocalessOptions, type UseLocalessOptions, findLink, getComponent, getFallbackComponent, getLocalessClient, isSyncEnabled, localessInit, registerComponent, renderRichTextToReact, resolveAsset, setComponents, setFallbackComponent, unregisterComponent, useLocaless };
|
|
101
|
+
export { LocalessComponent, type LocalessComponentProps, LocalessDocument, type LocalessDocumentProps, type LocalessOptions, type UseLocalessOptions, findLink, getComponent, getFallbackComponent, getLocalessClient, isSyncEnabled, localessInit, registerComponent, renderRichTextToReact, resolveAsset, setComponents, setFallbackComponent, unregisterComponent, useLocaless };
|
package/dist/index.js
CHANGED
|
@@ -21,16 +21,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
LocalessComponent: () => LocalessComponent,
|
|
24
|
+
LocalessDocument: () => LocalessDocument,
|
|
24
25
|
findLink: () => findLink,
|
|
25
26
|
getComponent: () => getComponent,
|
|
26
27
|
getFallbackComponent: () => getFallbackComponent,
|
|
27
28
|
getLocalessClient: () => getLocalessClient,
|
|
28
|
-
isBrowser: () =>
|
|
29
|
-
isIframe: () =>
|
|
30
|
-
isServer: () =>
|
|
29
|
+
isBrowser: () => import_client5.isBrowser,
|
|
30
|
+
isIframe: () => import_client5.isIframe,
|
|
31
|
+
isServer: () => import_client5.isServer,
|
|
31
32
|
isSyncEnabled: () => isSyncEnabled,
|
|
32
|
-
localessEditable: () =>
|
|
33
|
-
localessEditableField: () =>
|
|
33
|
+
localessEditable: () => import_client5.localessEditable,
|
|
34
|
+
localessEditableField: () => import_client5.localessEditableField,
|
|
34
35
|
localessInit: () => localessInit,
|
|
35
36
|
registerComponent: () => registerComponent,
|
|
36
37
|
renderRichTextToReact: () => renderRichTextToReact,
|
|
@@ -41,7 +42,7 @@ __export(index_exports, {
|
|
|
41
42
|
useLocaless: () => useLocaless
|
|
42
43
|
});
|
|
43
44
|
module.exports = __toCommonJS(index_exports);
|
|
44
|
-
var
|
|
45
|
+
var import_client5 = require("@localess/client");
|
|
45
46
|
|
|
46
47
|
// src/components/localess-component.tsx
|
|
47
48
|
var import_react = require("react");
|
|
@@ -137,11 +138,30 @@ var LocalessComponent = (0, import_react.forwardRef)(({ data, links, references,
|
|
|
137
138
|
] });
|
|
138
139
|
});
|
|
139
140
|
|
|
140
|
-
// src/
|
|
141
|
+
// src/components/localess-document.tsx
|
|
141
142
|
var import_react2 = require("react");
|
|
142
143
|
var import_client3 = require("@localess/client");
|
|
144
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
145
|
+
var LocalessDocument = (0, import_react2.forwardRef)(({ data, links, references, ...restProps }, ref) => {
|
|
146
|
+
const [contentData, setContentData] = (0, import_react2.useState)(data);
|
|
147
|
+
(0, import_react2.useEffect)(() => {
|
|
148
|
+
if (isSyncEnabled() && (0, import_client3.isBrowser)()) {
|
|
149
|
+
window.localess?.on(["input", "change"], (event) => {
|
|
150
|
+
console.log("Localess:event", event);
|
|
151
|
+
if (event.type === "change" || event.type === "input") {
|
|
152
|
+
setContentData(event.data);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LocalessComponent, { data: contentData, links, references, ...restProps });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// src/hooks/use-localess.ts
|
|
161
|
+
var import_react3 = require("react");
|
|
162
|
+
var import_client4 = require("@localess/client");
|
|
143
163
|
var useLocaless = (slug, options = {}) => {
|
|
144
|
-
const [document, setDocument] = (0,
|
|
164
|
+
const [document, setDocument] = (0, import_react3.useState)();
|
|
145
165
|
const client = getLocalessClient();
|
|
146
166
|
let normalizedSlug;
|
|
147
167
|
if (Array.isArray(slug)) {
|
|
@@ -149,11 +169,11 @@ var useLocaless = (slug, options = {}) => {
|
|
|
149
169
|
} else {
|
|
150
170
|
normalizedSlug = slug;
|
|
151
171
|
}
|
|
152
|
-
(0,
|
|
172
|
+
(0, import_react3.useEffect)(() => {
|
|
153
173
|
async function loadDocument() {
|
|
154
174
|
const document2 = await client.getContentBySlug(normalizedSlug, options);
|
|
155
175
|
setDocument(document2);
|
|
156
|
-
if (isSyncEnabled() && (0,
|
|
176
|
+
if (isSyncEnabled() && (0, import_client4.isBrowser)()) {
|
|
157
177
|
window.localess?.on(["input", "change"], (event) => {
|
|
158
178
|
if (event.type === "change" || event.type === "input") {
|
|
159
179
|
setDocument({ ...document2, data: event.data });
|
|
@@ -188,7 +208,7 @@ function findLink(links, link) {
|
|
|
188
208
|
}
|
|
189
209
|
|
|
190
210
|
// src/richtext.ts
|
|
191
|
-
var
|
|
211
|
+
var import_react4 = require("@tiptap/static-renderer/pm/react");
|
|
192
212
|
var import_extension_document = require("@tiptap/extension-document");
|
|
193
213
|
var import_extension_text = require("@tiptap/extension-text");
|
|
194
214
|
var import_extension_paragraph = require("@tiptap/extension-paragraph");
|
|
@@ -205,7 +225,7 @@ var import_extension_code = require("@tiptap/extension-code");
|
|
|
205
225
|
var import_extension_code_block_lowlight = require("@tiptap/extension-code-block-lowlight");
|
|
206
226
|
var import_extension_link = require("@tiptap/extension-link");
|
|
207
227
|
function renderRichTextToReact(content) {
|
|
208
|
-
return (0,
|
|
228
|
+
return (0, import_react4.renderToReactElement)({
|
|
209
229
|
content,
|
|
210
230
|
extensions: [
|
|
211
231
|
import_extension_document.Document,
|
|
@@ -231,6 +251,7 @@ function renderRichTextToReact(content) {
|
|
|
231
251
|
// Annotate the CommonJS export names for ESM import in node:
|
|
232
252
|
0 && (module.exports = {
|
|
233
253
|
LocalessComponent,
|
|
254
|
+
LocalessDocument,
|
|
234
255
|
findLink,
|
|
235
256
|
getComponent,
|
|
236
257
|
getFallbackComponent,
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { localessEditable as localessEditable2, localessEditableField, isBrowser as
|
|
2
|
+
import { localessEditable as localessEditable2, localessEditableField, isBrowser as isBrowser3, isServer, isIframe } from "@localess/client";
|
|
3
3
|
|
|
4
4
|
// src/components/localess-component.tsx
|
|
5
5
|
import { forwardRef } from "react";
|
|
@@ -95,11 +95,30 @@ var LocalessComponent = forwardRef(({ data, links, references, ...restProps }, r
|
|
|
95
95
|
] });
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
// src/
|
|
99
|
-
import { useEffect, useState } from "react";
|
|
98
|
+
// src/components/localess-document.tsx
|
|
99
|
+
import { forwardRef as forwardRef2, useEffect, useState } from "react";
|
|
100
100
|
import { isBrowser } from "@localess/client";
|
|
101
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
102
|
+
var LocalessDocument = forwardRef2(({ data, links, references, ...restProps }, ref) => {
|
|
103
|
+
const [contentData, setContentData] = useState(data);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (isSyncEnabled() && isBrowser()) {
|
|
106
|
+
window.localess?.on(["input", "change"], (event) => {
|
|
107
|
+
console.log("Localess:event", event);
|
|
108
|
+
if (event.type === "change" || event.type === "input") {
|
|
109
|
+
setContentData(event.data);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return /* @__PURE__ */ jsx2(LocalessComponent, { data: contentData, links, references, ...restProps });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// src/hooks/use-localess.ts
|
|
118
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
119
|
+
import { isBrowser as isBrowser2 } from "@localess/client";
|
|
101
120
|
var useLocaless = (slug, options = {}) => {
|
|
102
|
-
const [document, setDocument] =
|
|
121
|
+
const [document, setDocument] = useState2();
|
|
103
122
|
const client = getLocalessClient();
|
|
104
123
|
let normalizedSlug;
|
|
105
124
|
if (Array.isArray(slug)) {
|
|
@@ -107,11 +126,11 @@ var useLocaless = (slug, options = {}) => {
|
|
|
107
126
|
} else {
|
|
108
127
|
normalizedSlug = slug;
|
|
109
128
|
}
|
|
110
|
-
|
|
129
|
+
useEffect2(() => {
|
|
111
130
|
async function loadDocument() {
|
|
112
131
|
const document2 = await client.getContentBySlug(normalizedSlug, options);
|
|
113
132
|
setDocument(document2);
|
|
114
|
-
if (isSyncEnabled() &&
|
|
133
|
+
if (isSyncEnabled() && isBrowser2()) {
|
|
115
134
|
window.localess?.on(["input", "change"], (event) => {
|
|
116
135
|
if (event.type === "change" || event.type === "input") {
|
|
117
136
|
setDocument({ ...document2, data: event.data });
|
|
@@ -188,11 +207,12 @@ function renderRichTextToReact(content) {
|
|
|
188
207
|
}
|
|
189
208
|
export {
|
|
190
209
|
LocalessComponent,
|
|
210
|
+
LocalessDocument,
|
|
191
211
|
findLink,
|
|
192
212
|
getComponent,
|
|
193
213
|
getFallbackComponent,
|
|
194
214
|
getLocalessClient,
|
|
195
|
-
|
|
215
|
+
isBrowser3 as isBrowser,
|
|
196
216
|
isIframe,
|
|
197
217
|
isServer,
|
|
198
218
|
isSyncEnabled,
|
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.20260408183951",
|
|
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.20260408183951",
|
|
50
50
|
"@tiptap/static-renderer": "^3.20.1",
|
|
51
51
|
"@tiptap/html": "^3.20.1",
|
|
52
52
|
"@tiptap/extension-bold": "^3.20.1",
|