@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.
Files changed (3) hide show
  1. package/README.md +99 -25
  2. package/SKILL.md +91 -46
  3. 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 your application is opened inside the Localess Visual Editor, subscribe to live-editing events via `window.localess`.
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 { useEffect, useState } from "react";
260
- import { getLocalessClient } from "@localess/react";
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 PageClient({ initialData }: { initialData: Content<Page> }) {
264
- const [pageData, setPageData] = useState(initialData.data);
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
- useEffect(() => {
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(pageData)}>
278
- {pageData.body.map(item => (
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 === 'development',
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, LocalessComponent, localessEditable } from "@localess/react";
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
- searchParams,
367
+ params,
315
368
  }: {
316
- searchParams: Promise<{ locale?: string }>;
369
+ params: Promise<{ locale?: string }>;
317
370
  }) {
318
- const { locale } = await searchParams;
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
- Once the sync script is loaded, `window.localess` becomes available inside the iframe. Subscribe to it in a Client Component to apply live updates:
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 { useEffect, useState } from "react";
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
- const [pageData, setPageData] = useState(initialContent.data);
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(pageData)}>
204
- {pageData?.body?.map(item => (
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={initialContent.links}
209
- references={initialContent.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 via `window.localess.on()`:**
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
- The recommended pattern keeps data fetching server-side while the Client Component handles live sync:
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, no sync logic
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
- searchParams,
231
+ params,
243
232
  }: {
244
- searchParams: Promise<{ locale?: string }>;
233
+ params: Promise<{ locale?: string }>;
245
234
  }) {
246
- const { locale } = await searchParams;
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 + handles live edits
244
+ // app/[locale]/page-client.tsx — Client Component: renders + auto-syncs
256
245
  'use client';
257
246
 
258
- import { useEffect, useState } from "react";
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 [pageData, setPageData] = useState(initialContent.data);
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(pageData)}>
277
- {pageData?.body?.map(item => (
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={initialContent.links}
282
- references={initialContent.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.20260408170501",
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.20260408170501",
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",