@dotcms/react 26.9.18-1 → 26.9.23-1
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 +55 -8
- package/lib/next/components/Column/Column.module.css.esm.js +13 -6
- package/lib/next/components/Contentlet/Contentlet.esm.js +16 -8
- package/lib/next/components/DotCMSEditableText/DotCMSEditableText.esm.js +22 -11
- package/lib/next/components/DotCMSEditableText/TinyMCEEditor.esm.js +35 -0
- package/lib/next/components/DotCMSLayoutBody/DotCMSLayoutBody.esm.js +9 -2
- package/lib/next/components/DotCMSLayoutBody/DotCMSPageProvider.esm.js +19 -6
- package/lib/next/components/Row/Row.module.css.esm.js +13 -6
- package/lib/next/contexts/DotCMSPageContext.esm.js +3 -1
- package/lib/next/hooks/useCheckVisibleContent.esm.js +11 -2
- package/lib/next/hooks/useIsDevMode.esm.js +48 -17
- package/package.json +13 -8
- package/src/lib/next/components/DotCMSEditableText/TinyMCEEditor.d.ts +35 -0
- package/src/lib/next/components/DotCMSEditableText/utils.d.ts +1 -1
- package/src/lib/next/components/DotCMSLayoutBody/DotCMSLayoutBody.d.ts +0 -17
- package/src/lib/next/components/DotCMSLayoutBody/DotCMSPageProvider.d.ts +5 -0
- package/src/lib/next/contexts/DotCMSPageContext.d.ts +11 -0
- package/src/lib/next/hooks/useCheckVisibleContent.d.ts +7 -1
- package/src/lib/next/hooks/useIsDevMode.d.ts +32 -2
- package/src/lib/next/shared/types.d.ts +1 -1
- package/_virtual/_style-inject.esm.js +0 -30
package/README.md
CHANGED
|
@@ -308,23 +308,70 @@ The layout renders the pre-rendered slot node for a contentlet if one exists, ot
|
|
|
308
308
|
|
|
309
309
|
#### Component Mapping
|
|
310
310
|
|
|
311
|
-
The `DotCMSLayoutBody` component uses a `components` prop to map content type variable names to React components. This allows you to render different components for different content types.
|
|
311
|
+
The `DotCMSLayoutBody` component uses a `components` prop to map content type variable names to React components. This allows you to render different components for different content types.
|
|
312
312
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
313
|
+
**Load the mapped components dynamically.** A static map makes every component reachable from the client entry, so a visitor downloads all of them on every route — even the ones that page never renders. Wrapping each in a dynamic import gives each component its own chunk, fetched only when a page actually contains that content type.
|
|
314
|
+
|
|
315
|
+
In Next.js, use `next/dynamic`:
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
import dynamic from 'next/dynamic';
|
|
319
|
+
|
|
320
|
+
import { CustomNoComponent } from './Empty';
|
|
321
|
+
|
|
322
|
+
export const pageComponents = {
|
|
323
|
+
Blog: dynamic(() => import('./MyBlogCard')),
|
|
324
|
+
Product: dynamic(() => import('./DotCMSProductComponent')),
|
|
325
|
+
// The fallback for unmapped content types stays eager: it should render without
|
|
326
|
+
// waiting on a network round-trip, and it is small.
|
|
327
|
+
CustomNoComponent
|
|
328
|
+
};
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Anywhere else — Astro, Vite, plain React — use `React.lazy`:
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
import { lazy } from 'react';
|
|
335
|
+
|
|
336
|
+
export const pageComponents = {
|
|
337
|
+
Blog: lazy(() => import('./MyBlogCard')),
|
|
338
|
+
Product: lazy(() => import('./DotCMSProductComponent'))
|
|
317
339
|
};
|
|
318
340
|
```
|
|
319
341
|
|
|
342
|
+
`next/dynamic` brings its own Suspense boundary. `React.lazy` does not, but you do not need to add one: `DotCMSLayoutBody` wraps every contentlet in a Suspense boundary, so a lazy component can be mapped directly.
|
|
343
|
+
|
|
320
344
|
- Keys (e.g., `Blog`, `Product`): Match your [content type variable names](https://dev.dotcms.com/docs/content-types#VariableNames) in dotCMS
|
|
321
|
-
- Values:
|
|
322
|
-
- Supports lazy loading through dynamic imports
|
|
323
|
-
- Components must be standalone or declared in a module
|
|
345
|
+
- Values: any React component type — including the result of `next/dynamic` or `React.lazy`
|
|
324
346
|
|
|
325
347
|
> [!TIP]
|
|
326
348
|
> Always use the exact content type variable name from dotCMS as the key. You can find this in the Content Types section of your dotCMS admin panel.
|
|
327
349
|
|
|
350
|
+
> [!WARNING]
|
|
351
|
+
> Avoid re-exporting your content-type components from a barrel (`export * from './Banner'`). Anything importing that barrel makes every component statically reachable, which puts them all back in the initial bundle no matter how the map loads them.
|
|
352
|
+
|
|
353
|
+
#### Migrating an existing app to dynamic mapping
|
|
354
|
+
|
|
355
|
+
Earlier versions of our examples showed a static component map, so an app built from them ships **every** mapped component on **every** route. If you have dozens of content types, that is the single biggest thing you can fix — and upgrading the SDK does not fix it for you, because the map lives in your code.
|
|
356
|
+
|
|
357
|
+
Measured on the Next.js example with 135 mapped content types, one realistic component each:
|
|
358
|
+
|
|
359
|
+
| Component map | Initial route JS | Mapped components in the initial bundle |
|
|
360
|
+
| --- | --- | --- |
|
|
361
|
+
| Static imports | 892.7 KB raw / 250.4 KB gzip | **135 of 135** |
|
|
362
|
+
| `next/dynamic` | 792.6 KB raw / 238.1 KB gzip | **0 of 135** |
|
|
363
|
+
|
|
364
|
+
Every mapped component leaves the initial route and is fetched only when a page contains that content type. How much weight that removes depends on your components: the 100 KB above is what 135 modest ones cost, and real component libraries are usually heavier.
|
|
365
|
+
|
|
366
|
+
To migrate:
|
|
367
|
+
|
|
368
|
+
1. Wrap each entry in the map with `next/dynamic` (or `React.lazy` outside Next.js). Keep the unmatched-type fallback eager.
|
|
369
|
+
2. Delete any barrel that re-exports your content-type components, and import them directly where you need them elsewhere. A single `export * from './Banner'` re-exports every component from one module and undoes the whole change.
|
|
370
|
+
3. Do the same for the `customRenderers` map you pass to `DotCMSBlockEditorRenderer` if it maps more than a handful of components.
|
|
371
|
+
4. Rebuild and confirm. The quickest check is to map a component no page uses, give it a unique string, and grep the production client chunks for that string — it should appear only in its own chunk. `examples/scripts/check-initial-bundle.mjs` in this repository does exactly that for the Next.js and Astro examples and can be pointed at your build.
|
|
372
|
+
|
|
373
|
+
`next/dynamic` keeps server rendering on by default, so this does not change what the crawler sees.
|
|
374
|
+
|
|
328
375
|
|
|
329
376
|
### DotCMSEditableText
|
|
330
377
|
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var
|
|
1
|
+
var css_248z = ".Column-module_col-start-1__ZjXIT {\n grid-column-start: 1;\n}\n\n.Column-module_col-start-2__pKXk4 {\n grid-column-start: 2;\n}\n\n.Column-module_col-start-3__9jDkG {\n grid-column-start: 3;\n}\n\n.Column-module_col-start-4__nKEgb {\n grid-column-start: 4;\n}\n\n.Column-module_col-start-5__ybSmQ {\n grid-column-start: 5;\n}\n\n.Column-module_col-start-6__oMag- {\n grid-column-start: 6;\n}\n\n.Column-module_col-start-7__B3Gyy {\n grid-column-start: 7;\n}\n\n.Column-module_col-start-8__jLDTT {\n grid-column-start: 8;\n}\n\n.Column-module_col-start-9__CtwzK {\n grid-column-start: 9;\n}\n\n.Column-module_col-start-10__7wGuq {\n grid-column-start: 10;\n}\n\n.Column-module_col-start-11__hFAC7 {\n grid-column-start: 11;\n}\n\n.Column-module_col-start-12__yl9H8 {\n grid-column-start: 12;\n}\n\n.Column-module_col-end-1__8NXpa {\n grid-column-end: 1;\n}\n\n.Column-module_col-end-2__PNIHf {\n grid-column-end: 2;\n}\n\n.Column-module_col-end-3__tYgN- {\n grid-column-end: 3;\n}\n\n.Column-module_col-end-4__kfbI3 {\n grid-column-end: 4;\n}\n\n.Column-module_col-end-5__E8Gsl {\n grid-column-end: 5;\n}\n\n.Column-module_col-end-6__SpIXw {\n grid-column-end: 6;\n}\n\n.Column-module_col-end-7__0nOh1 {\n grid-column-end: 7;\n}\n\n.Column-module_col-end-8__iOzUU {\n grid-column-end: 8;\n}\n\n.Column-module_col-end-9__xSt6O {\n grid-column-end: 9;\n}\n\n.Column-module_col-end-10__naTtw {\n grid-column-end: 10;\n}\n\n.Column-module_col-end-11__b6utR {\n grid-column-end: 11;\n}\n\n.Column-module_col-end-12__D18ty {\n grid-column-end: 12;\n}\n\n.Column-module_col-end-13__5rTLe {\n grid-column-end: 13;\n}\n";
|
|
2
|
+
var styles = {"col-start-1":"Column-module_col-start-1__ZjXIT","col-start-2":"Column-module_col-start-2__pKXk4","col-start-3":"Column-module_col-start-3__9jDkG","col-start-4":"Column-module_col-start-4__nKEgb","col-start-5":"Column-module_col-start-5__ybSmQ","col-start-6":"Column-module_col-start-6__oMag-","col-start-7":"Column-module_col-start-7__B3Gyy","col-start-8":"Column-module_col-start-8__jLDTT","col-start-9":"Column-module_col-start-9__CtwzK","col-start-10":"Column-module_col-start-10__7wGuq","col-start-11":"Column-module_col-start-11__hFAC7","col-start-12":"Column-module_col-start-12__yl9H8","col-end-1":"Column-module_col-end-1__8NXpa","col-end-2":"Column-module_col-end-2__PNIHf","col-end-3":"Column-module_col-end-3__tYgN-","col-end-4":"Column-module_col-end-4__kfbI3","col-end-5":"Column-module_col-end-5__E8Gsl","col-end-6":"Column-module_col-end-6__SpIXw","col-end-7":"Column-module_col-end-7__0nOh1","col-end-8":"Column-module_col-end-8__iOzUU","col-end-9":"Column-module_col-end-9__xSt6O","col-end-10":"Column-module_col-end-10__naTtw","col-end-11":"Column-module_col-end-11__b6utR","col-end-12":"Column-module_col-end-12__D18ty","col-end-13":"Column-module_col-end-13__5rTLe"};
|
|
3
|
+
(function () {
|
|
4
|
+
if (typeof document === 'undefined') return;
|
|
5
|
+
var existing = document.head.querySelectorAll('style[data-dotcms-style]');
|
|
6
|
+
for (var i = 0; i < existing.length; i++) {
|
|
7
|
+
if (existing[i].textContent === css_248z) return;
|
|
8
|
+
}
|
|
9
|
+
var style = document.createElement('style');
|
|
10
|
+
style.setAttribute('data-dotcms-style', '');
|
|
11
|
+
style.appendChild(document.createTextNode(css_248z));
|
|
12
|
+
document.head.appendChild(style);
|
|
13
|
+
})();
|
|
7
14
|
|
|
8
15
|
export { styles as default };
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
3
|
-
import { useRef, useMemo,
|
|
3
|
+
import { useRef, useContext, useMemo, Suspense } from 'react';
|
|
4
4
|
import { getDotContentletAttributes, getAnalyticsContentletAttributes, CUSTOM_NO_COMPONENT } from '@dotcms/uve/internal';
|
|
5
5
|
import { DotCMSPageContext } from '../../contexts/DotCMSPageContext.esm.js';
|
|
6
6
|
import { useCheckVisibleContent } from '../../hooks/useCheckVisibleContent.esm.js';
|
|
7
|
-
import { useIsAnalyticsActive } from '../../hooks/useIsAnalyticsActive.esm.js';
|
|
8
|
-
import { useIsDevMode } from '../../hooks/useIsDevMode.esm.js';
|
|
9
7
|
import { FallbackComponent } from '../FallbackComponent/FallbackComponent.esm.js';
|
|
10
8
|
|
|
11
9
|
/**
|
|
@@ -34,9 +32,16 @@ function Contentlet({
|
|
|
34
32
|
container
|
|
35
33
|
}) {
|
|
36
34
|
const ref = useRef(null);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
// Both flags are resolved once per layout tree by DotCMSPageProvider. Reading them from
|
|
36
|
+
// context keeps this component free of its own UVE lookup and analytics listener, which
|
|
37
|
+
// previously ran once per contentlet on the page.
|
|
38
|
+
const {
|
|
39
|
+
isDevMode,
|
|
40
|
+
isAnalyticsActive
|
|
41
|
+
} = useContext(DotCMSPageContext);
|
|
42
|
+
// The measurement only feeds the editor's empty-contentlet placeholder, so skip the
|
|
43
|
+
// forced layout entirely outside development mode.
|
|
44
|
+
const haveContent = useCheckVisibleContent(ref, isDevMode);
|
|
40
45
|
const style = useMemo(() => isDevMode ? {
|
|
41
46
|
minHeight: haveContent ? undefined : '4rem'
|
|
42
47
|
} : {}, [isDevMode, haveContent]);
|
|
@@ -58,8 +63,11 @@ function Contentlet({
|
|
|
58
63
|
className: CONTENTLET_CLASS,
|
|
59
64
|
ref: ref,
|
|
60
65
|
style: style,
|
|
61
|
-
children: jsx(
|
|
62
|
-
|
|
66
|
+
children: jsx(Suspense, {
|
|
67
|
+
fallback: null,
|
|
68
|
+
children: jsx(CustomComponent, {
|
|
69
|
+
contentlet: contentlet
|
|
70
|
+
})
|
|
63
71
|
})
|
|
64
72
|
}));
|
|
65
73
|
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import {
|
|
3
|
-
import { useRef, useState, useEffect } from 'react';
|
|
2
|
+
import { useRef, useState, useEffect, Suspense, lazy } from 'react';
|
|
4
3
|
import { UVE_MODE, DotCMSUVEAction } from '@dotcms/types';
|
|
5
4
|
import { __DOTCMS_UVE_EVENT__ } from '@dotcms/types/internal';
|
|
6
5
|
import { getUVEState, sendMessageToUVE } from '@dotcms/uve';
|
|
7
6
|
import { __TINYMCE_PATH_ON_DOTCMS__ } from '@dotcms/uve/internal';
|
|
8
|
-
import { TINYMCE_CONFIG } from './utils.esm.js';
|
|
9
7
|
|
|
8
|
+
/**
|
|
9
|
+
* TinyMCE is only ever rendered once the UVE is in edit mode, so the integration is split
|
|
10
|
+
* into its own chunk and fetched at that point. Importing it statically put the whole editor
|
|
11
|
+
* — and the TinyMCE React wrapper — into every consumer bundle, including live-mode pages
|
|
12
|
+
* that can never show it.
|
|
13
|
+
*/
|
|
14
|
+
const TinyMCEEditor = /*#__PURE__*/lazy(() => import('./TinyMCEEditor.esm.js'));
|
|
10
15
|
/**
|
|
11
16
|
* Allows inline edit content pulled from dotCMS API using TinyMCE editor
|
|
12
17
|
*
|
|
@@ -171,14 +176,20 @@ function DotCMSEditableText({
|
|
|
171
176
|
outline: '2px solid #006ce7',
|
|
172
177
|
borderRadius: '4px'
|
|
173
178
|
},
|
|
174
|
-
children: jsx(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
children: jsx(Suspense, {
|
|
180
|
+
fallback: jsx("span", {
|
|
181
|
+
dangerouslySetInnerHTML: {
|
|
182
|
+
__html: content
|
|
183
|
+
}
|
|
184
|
+
}),
|
|
185
|
+
children: jsx(TinyMCEEditor, {
|
|
186
|
+
scriptSrc: scriptSrc,
|
|
187
|
+
mode: mode,
|
|
188
|
+
initialValue: content,
|
|
189
|
+
onEditorInit: editor => editorRef.current = editor,
|
|
190
|
+
onMouseDown: onMouseDown,
|
|
191
|
+
onFocusOut: onFocusOut
|
|
192
|
+
})
|
|
182
193
|
})
|
|
183
194
|
});
|
|
184
195
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { Editor } from '@tinymce/tinymce-react';
|
|
4
|
+
import { TINYMCE_CONFIG } from './utils.esm.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @internal
|
|
8
|
+
*
|
|
9
|
+
* Thin wrapper around `@tinymce/tinymce-react`, kept in its own module so it is the *only*
|
|
10
|
+
* place that imports the TinyMCE integration. `DotCMSEditableText` pulls it in with a dynamic
|
|
11
|
+
* import once the UVE actually enters edit mode, so live-mode consumers never download it.
|
|
12
|
+
*
|
|
13
|
+
* Do not import this module statically from anywhere else — doing so puts TinyMCE back in
|
|
14
|
+
* the main bundle and undoes the split.
|
|
15
|
+
*/
|
|
16
|
+
function TinyMCEEditor({
|
|
17
|
+
scriptSrc,
|
|
18
|
+
mode,
|
|
19
|
+
initialValue,
|
|
20
|
+
onEditorInit,
|
|
21
|
+
onMouseDown,
|
|
22
|
+
onFocusOut
|
|
23
|
+
}) {
|
|
24
|
+
return jsx(Editor, {
|
|
25
|
+
tinymceScriptSrc: scriptSrc,
|
|
26
|
+
inline: true,
|
|
27
|
+
onInit: (_, editor) => onEditorInit(editor),
|
|
28
|
+
init: TINYMCE_CONFIG[mode],
|
|
29
|
+
initialValue: initialValue,
|
|
30
|
+
onMouseDown: onMouseDown,
|
|
31
|
+
onFocusOut: onFocusOut
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { TinyMCEEditor, TinyMCEEditor as default };
|
|
@@ -20,11 +20,18 @@ import { Row } from '../Row/Row.esm.js';
|
|
|
20
20
|
* @returns {JSX.Element} The rendered DotCMS page body or an error message if the layout body is missing.
|
|
21
21
|
*
|
|
22
22
|
*/
|
|
23
|
+
/**
|
|
24
|
+
* Hoisted so the defaults keep a stable identity across renders. As inline `= {}` defaults
|
|
25
|
+
* they produced a fresh object on every render, which invalidated the page context's useMemo
|
|
26
|
+
* and re-rendered every container and contentlet in the tree.
|
|
27
|
+
*/
|
|
28
|
+
const NO_COMPONENTS = {};
|
|
29
|
+
const NO_SLOTS = {};
|
|
23
30
|
const DotCMSLayoutBody = ({
|
|
24
31
|
page,
|
|
25
|
-
components: _components =
|
|
32
|
+
components: _components = NO_COMPONENTS,
|
|
26
33
|
mode: _mode = 'production',
|
|
27
|
-
slots: _slots =
|
|
34
|
+
slots: _slots = NO_SLOTS
|
|
28
35
|
}) => {
|
|
29
36
|
var _page$layout;
|
|
30
37
|
const dotCMSPageBody = page == null || (_page$layout = page.layout) == null ? void 0 : _page$layout.body;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useMemo } from 'react';
|
|
3
4
|
import { DotCMSPageContext } from '../../contexts/DotCMSPageContext.esm.js';
|
|
5
|
+
import { useIsAnalyticsActive } from '../../hooks/useIsAnalyticsActive.esm.js';
|
|
6
|
+
import { useResolvedDevMode } from '../../hooks/useIsDevMode.esm.js';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* @internal
|
|
@@ -8,6 +11,11 @@ import { DotCMSPageContext } from '../../contexts/DotCMSPageContext.esm.js';
|
|
|
8
11
|
* Client boundary that provides the DotCMS page context to the layout tree.
|
|
9
12
|
* Keeping this separate from DotCMSLayoutBody allows the layout to remain
|
|
10
13
|
* a server component while only the context provider runs on the client.
|
|
14
|
+
*
|
|
15
|
+
* Development mode and the Analytics-active flag are resolved here, once per layout tree,
|
|
16
|
+
* and shared through the context. Resolving them per contentlet meant one
|
|
17
|
+
* `dotcms:analytics:ready` window listener and one UVE-state lookup for every piece of
|
|
18
|
+
* content on the page.
|
|
11
19
|
*/
|
|
12
20
|
function DotCMSPageProvider({
|
|
13
21
|
page,
|
|
@@ -16,13 +24,18 @@ function DotCMSPageProvider({
|
|
|
16
24
|
slots,
|
|
17
25
|
children
|
|
18
26
|
}) {
|
|
27
|
+
const isDevMode = useResolvedDevMode(mode);
|
|
28
|
+
const isAnalyticsActive = useIsAnalyticsActive();
|
|
29
|
+
const value = useMemo(() => ({
|
|
30
|
+
pageAsset: page,
|
|
31
|
+
userComponents: components,
|
|
32
|
+
mode,
|
|
33
|
+
slots,
|
|
34
|
+
isDevMode,
|
|
35
|
+
isAnalyticsActive
|
|
36
|
+
}), [page, components, mode, slots, isDevMode, isAnalyticsActive]);
|
|
19
37
|
return jsx(DotCMSPageContext.Provider, {
|
|
20
|
-
value:
|
|
21
|
-
pageAsset: page,
|
|
22
|
-
userComponents: components,
|
|
23
|
-
mode,
|
|
24
|
-
slots
|
|
25
|
-
},
|
|
38
|
+
value: value,
|
|
26
39
|
children: children
|
|
27
40
|
});
|
|
28
41
|
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var
|
|
1
|
+
var css_248z = ".Row-module_row__fJF2K {\n display: grid;\n grid-template-columns: repeat(12, 1fr);\n gap: 1rem;\n}\n";
|
|
2
|
+
var styles = {"row":"Row-module_row__fJF2K"};
|
|
3
|
+
(function () {
|
|
4
|
+
if (typeof document === 'undefined') return;
|
|
5
|
+
var existing = document.head.querySelectorAll('style[data-dotcms-style]');
|
|
6
|
+
for (var i = 0; i < existing.length; i++) {
|
|
7
|
+
if (existing[i].textContent === css_248z) return;
|
|
8
|
+
}
|
|
9
|
+
var style = document.createElement('style');
|
|
10
|
+
style.setAttribute('data-dotcms-style', '');
|
|
11
|
+
style.appendChild(document.createTextNode(css_248z));
|
|
12
|
+
document.head.appendChild(style);
|
|
13
|
+
})();
|
|
7
14
|
|
|
8
15
|
export { styles as default };
|
|
@@ -4,7 +4,13 @@ import { useState, useLayoutEffect } from 'react';
|
|
|
4
4
|
* @internal
|
|
5
5
|
* A custom React hook that checks whether a referenced HTMLDivElement has visible content based on its height.
|
|
6
6
|
*
|
|
7
|
+
* The measurement is only used to reserve space for the editor's empty-contentlet placeholder,
|
|
8
|
+
* so it is gated behind `enabled`. `getBoundingClientRect()` forces a synchronous layout, and
|
|
9
|
+
* running it from a layout effect on every contentlet of a page is expensive — in production
|
|
10
|
+
* the result was measured and then discarded.
|
|
11
|
+
*
|
|
7
12
|
* @param {RefObject<HTMLDivElement>} ref - A React ref object pointing to an HTMLDivElement.
|
|
13
|
+
* @param {boolean} [enabled=true] - When false the element is never measured and the hook returns false.
|
|
8
14
|
* @returns {boolean} - Returns true if the element's height is greater than zero (indicating visible content), otherwise false.
|
|
9
15
|
*
|
|
10
16
|
* @example
|
|
@@ -22,9 +28,12 @@ import { useState, useLayoutEffect } from 'react';
|
|
|
22
28
|
* );
|
|
23
29
|
* }
|
|
24
30
|
*/
|
|
25
|
-
const useCheckVisibleContent = ref => {
|
|
31
|
+
const useCheckVisibleContent = (ref, enabled = true) => {
|
|
26
32
|
const [haveContent, setHaveContent] = useState(false);
|
|
27
33
|
useLayoutEffect(() => {
|
|
34
|
+
if (!enabled) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
28
37
|
if (!ref.current) {
|
|
29
38
|
setHaveContent(false);
|
|
30
39
|
return;
|
|
@@ -33,7 +42,7 @@ const useCheckVisibleContent = ref => {
|
|
|
33
42
|
height
|
|
34
43
|
} = ref.current.getBoundingClientRect();
|
|
35
44
|
setHaveContent(height > 0);
|
|
36
|
-
}, [ref]);
|
|
45
|
+
}, [ref, enabled]);
|
|
37
46
|
return haveContent;
|
|
38
47
|
};
|
|
39
48
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useEffect, useContext } from 'react';
|
|
2
3
|
import { UVE_MODE } from '@dotcms/types';
|
|
3
4
|
import { getUVEState } from '@dotcms/uve';
|
|
4
5
|
import { DEVELOPMENT_MODE } from '@dotcms/uve/internal';
|
|
@@ -6,30 +7,60 @@ import { DotCMSPageContext } from '../contexts/DotCMSPageContext.esm.js';
|
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @internal
|
|
10
|
+
*
|
|
11
|
+
* Resolve whether we are rendering in "development" mode — i.e. whether editor metadata
|
|
12
|
+
* (`data-dot-*` attributes, empty-state placeholders, fallback components) should be emitted.
|
|
13
|
+
* Inside the UVE it follows the UVE state (dev when the mode is EDIT); otherwise it follows
|
|
14
|
+
* the renderer `mode` passed to `DotCMSLayoutBody`.
|
|
15
|
+
*
|
|
16
|
+
* @param mode the renderer mode from the page context
|
|
17
|
+
* @returns `true` when editor metadata should be emitted
|
|
18
|
+
*/
|
|
19
|
+
const resolveDevMode = mode => {
|
|
20
|
+
var _getUVEState;
|
|
21
|
+
const uveMode = (_getUVEState = getUVEState()) == null ? void 0 : _getUVEState.mode;
|
|
22
|
+
if (uveMode) {
|
|
23
|
+
return uveMode === UVE_MODE.EDIT;
|
|
24
|
+
}
|
|
25
|
+
return mode === DEVELOPMENT_MODE;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* @internal
|
|
29
|
+
*
|
|
30
|
+
* Owns the development-mode state for one layout tree. Called once by `DotCMSPageProvider`;
|
|
31
|
+
* the result travels down through the page context.
|
|
32
|
+
*
|
|
33
|
+
* `getUVEState()` reads the browser, so it is deliberately resolved in an effect rather than
|
|
34
|
+
* during render: on the server it would always report "not in the editor", and resolving it
|
|
35
|
+
* synchronously on the client would make the first render disagree with the server-rendered
|
|
36
|
+
* markup and trip React's hydration check inside the UVE.
|
|
37
|
+
*
|
|
38
|
+
* @param mode the renderer mode passed to `DotCMSLayoutBody`
|
|
39
|
+
* @returns `true` when editor metadata should be emitted
|
|
40
|
+
*/
|
|
41
|
+
const useResolvedDevMode = mode => {
|
|
42
|
+
const [isDevMode, setIsDevMode] = useState(mode === DEVELOPMENT_MODE);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
setIsDevMode(resolveDevMode(mode));
|
|
45
|
+
}, [mode]);
|
|
46
|
+
return isDevMode;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* @internal
|
|
50
|
+
*
|
|
9
51
|
* A React hook that determines if the current environment is in development mode.
|
|
10
52
|
*
|
|
11
|
-
* The
|
|
12
|
-
*
|
|
53
|
+
* The value is resolved once per layout tree by `DotCMSPageProvider` and shared through the
|
|
54
|
+
* page context, so a page with many containers and contentlets resolves it a single time
|
|
55
|
+
* instead of once per component.
|
|
13
56
|
*
|
|
14
57
|
* @returns {boolean} - `true` if in development mode or inside the editor; otherwise, `false`.
|
|
15
58
|
*/
|
|
16
59
|
const useIsDevMode = () => {
|
|
17
60
|
const {
|
|
18
|
-
|
|
61
|
+
isDevMode
|
|
19
62
|
} = useContext(DotCMSPageContext);
|
|
20
|
-
const [isDevMode, setIsDevMode] = useState(mode === 'development');
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
var _getUVEState;
|
|
23
|
-
// Inside UVE we rely on the UVE state to determine if we are in development mode
|
|
24
|
-
if ((_getUVEState = getUVEState()) != null && _getUVEState.mode) {
|
|
25
|
-
var _getUVEState2;
|
|
26
|
-
const isUVEInEditor = ((_getUVEState2 = getUVEState()) == null ? void 0 : _getUVEState2.mode) === UVE_MODE.EDIT;
|
|
27
|
-
setIsDevMode(isUVEInEditor);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
setIsDevMode(mode === DEVELOPMENT_MODE);
|
|
31
|
-
}, [mode]);
|
|
32
63
|
return isDevMode;
|
|
33
64
|
};
|
|
34
65
|
|
|
35
|
-
export { useIsDevMode };
|
|
66
|
+
export { resolveDevMode, useIsDevMode, useResolvedDevMode };
|
package/package.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotcms/react",
|
|
3
|
-
"version": "26.9.
|
|
3
|
+
"version": "26.9.23-1",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"react": ">=18",
|
|
6
6
|
"react-dom": ">=18",
|
|
7
|
-
"@dotcms/uve": "26.9.
|
|
8
|
-
"@dotcms/client": "26.9.
|
|
7
|
+
"@dotcms/uve": "26.9.23-1",
|
|
8
|
+
"@dotcms/client": "26.9.23-1",
|
|
9
|
+
"@dotcms/types": "26.9.23-1"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
12
|
"@tinymce/tinymce-react": "6.2.1"
|
|
12
13
|
},
|
|
13
14
|
"devDependencies": {
|
|
14
|
-
"@dotcms/types": "26.9.
|
|
15
|
+
"@dotcms/types": "26.9.23-1"
|
|
15
16
|
},
|
|
16
17
|
"description": "Official React Components library to render a dotCMS page.",
|
|
17
18
|
"repository": {
|
|
@@ -30,15 +31,15 @@
|
|
|
30
31
|
"exports": {
|
|
31
32
|
"./package.json": "./package.json",
|
|
32
33
|
".": {
|
|
34
|
+
"types": "./index.d.ts",
|
|
33
35
|
"react-server": "./index.server.esm.js",
|
|
34
|
-
"import": "./index.esm.js"
|
|
35
|
-
"types": "./index.d.ts"
|
|
36
|
+
"import": "./index.esm.js"
|
|
36
37
|
}
|
|
37
38
|
},
|
|
38
39
|
"typesVersions": {
|
|
39
40
|
"*": {
|
|
40
41
|
".": [
|
|
41
|
-
"./
|
|
42
|
+
"./index.d.ts"
|
|
42
43
|
]
|
|
43
44
|
}
|
|
44
45
|
},
|
|
@@ -48,8 +49,12 @@
|
|
|
48
49
|
"url": "https://github.com/dotCMS/core/issues"
|
|
49
50
|
},
|
|
50
51
|
"homepage": "https://github.com/dotCMS/core/tree/main/core-web/libs/sdk/react/README.md",
|
|
52
|
+
"sideEffects": [
|
|
53
|
+
"**/*.css",
|
|
54
|
+
"**/*.css.esm.js"
|
|
55
|
+
],
|
|
51
56
|
"module": "./index.esm.js",
|
|
52
57
|
"type": "module",
|
|
53
58
|
"main": "./index.esm.js",
|
|
54
59
|
"types": "./index.d.ts"
|
|
55
|
-
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Editor } from '@tinymce/tinymce-react';
|
|
2
|
+
import { DOT_EDITABLE_TEXT_MODE } from './utils';
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*
|
|
6
|
+
* The TinyMCE editor instance handed back by `onInit`.
|
|
7
|
+
*/
|
|
8
|
+
export type TinyMCEEditorInstance = NonNullable<Editor['editor']>;
|
|
9
|
+
/**
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export interface TinyMCEEditorProps {
|
|
13
|
+
/** URL of the TinyMCE script served by the dotCMS host. */
|
|
14
|
+
scriptSrc: string;
|
|
15
|
+
/** Toolbar preset to load: `plain`, `minimal` or `full`. */
|
|
16
|
+
mode: DOT_EDITABLE_TEXT_MODE;
|
|
17
|
+
/** Field content to seed the editor with. */
|
|
18
|
+
initialValue: string;
|
|
19
|
+
/** Called once the editor is ready, with the live editor instance. */
|
|
20
|
+
onEditorInit: (editor: TinyMCEEditorInstance) => void;
|
|
21
|
+
onMouseDown: (event: MouseEvent) => void;
|
|
22
|
+
onFocusOut: () => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
*
|
|
27
|
+
* Thin wrapper around `@tinymce/tinymce-react`, kept in its own module so it is the *only*
|
|
28
|
+
* place that imports the TinyMCE integration. `DotCMSEditableText` pulls it in with a dynamic
|
|
29
|
+
* import once the UVE actually enters edit mode, so live-mode consumers never download it.
|
|
30
|
+
*
|
|
31
|
+
* Do not import this module statically from anywhere else — doing so puts TinyMCE back in
|
|
32
|
+
* the main bundle and undoes the split.
|
|
33
|
+
*/
|
|
34
|
+
export declare function TinyMCEEditor({ scriptSrc, mode, initialValue, onEditorInit, onMouseDown, onFocusOut }: Readonly<TinyMCEEditorProps>): import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
export default TinyMCEEditor;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { IAllProps } from '@tinymce/tinymce-react';
|
|
2
1
|
import { DotCMSBasicContentlet } from '@dotcms/types';
|
|
2
|
+
import type { IAllProps } from '@tinymce/tinymce-react';
|
|
3
3
|
export type DOT_EDITABLE_TEXT_FORMAT = 'html' | 'text';
|
|
4
4
|
export type DOT_EDITABLE_TEXT_MODE = 'minimal' | 'full' | 'plain';
|
|
5
5
|
export interface DotCMSEditableTextProps<T extends DotCMSBasicContentlet> {
|
|
@@ -29,21 +29,4 @@ export interface DotCMSLayoutBodyProps<TContentlet extends DotCMSBasicContentlet
|
|
|
29
29
|
*/
|
|
30
30
|
slots?: Record<string, ReactNode>;
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
33
|
-
* DotCMSLayoutBody component renders the layout body for a DotCMS page.
|
|
34
|
-
*
|
|
35
|
-
* It utilizes the dotCMS page asset's layout body to render the page body.
|
|
36
|
-
* If the layout body does not exist, it renders an error message in the mode is `development`.
|
|
37
|
-
*
|
|
38
|
-
* @public
|
|
39
|
-
* @component
|
|
40
|
-
* @param {Object} props - Component properties.
|
|
41
|
-
* @param {DotCMSPageAsset} props.page - The DotCMS page asset containing the layout information.
|
|
42
|
-
* @param {Record<string, React.ComponentType<DotCMSContentlet>>} [props.components] - mapping of custom components for content rendering.
|
|
43
|
-
* @param {DotCMSPageRendererMode} [props.mode='production'] - The renderer mode; defaults to 'production'. Alternate modes might trigger different behaviors.
|
|
44
|
-
* @param {Record<string, ReactNode>} [props.slots] - Pre-rendered server component nodes keyed by contentlet identifier.
|
|
45
|
-
*
|
|
46
|
-
* @returns {JSX.Element} The rendered DotCMS page body or an error message if the layout body is missing.
|
|
47
|
-
*
|
|
48
|
-
*/
|
|
49
32
|
export declare const DotCMSLayoutBody: ({ page, components, mode, slots }: DotCMSLayoutBodyProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -13,6 +13,11 @@ interface DotCMSPageProviderProps {
|
|
|
13
13
|
* Client boundary that provides the DotCMS page context to the layout tree.
|
|
14
14
|
* Keeping this separate from DotCMSLayoutBody allows the layout to remain
|
|
15
15
|
* a server component while only the context provider runs on the client.
|
|
16
|
+
*
|
|
17
|
+
* Development mode and the Analytics-active flag are resolved here, once per layout tree,
|
|
18
|
+
* and shared through the context. Resolving them per contentlet meant one
|
|
19
|
+
* `dotcms:analytics:ready` window listener and one UVE-state lookup for every piece of
|
|
20
|
+
* content on the page.
|
|
16
21
|
*/
|
|
17
22
|
export declare function DotCMSPageProvider({ page, components, mode, slots, children }: DotCMSPageProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
18
23
|
export {};
|
|
@@ -19,6 +19,17 @@ export interface DotCMSPageContextProps {
|
|
|
19
19
|
mode: DotCMSPageRendererMode;
|
|
20
20
|
userComponents: Record<string, React.ComponentType<DotCMSBasicContentlet>>;
|
|
21
21
|
slots?: Record<string, ReactNode>;
|
|
22
|
+
/**
|
|
23
|
+
* Whether editor metadata (`data-dot-*` attributes, placeholders, fallbacks) should be
|
|
24
|
+
* emitted. Resolved once at the layout root and shared with the whole tree so it isn't
|
|
25
|
+
* recomputed by every container and contentlet.
|
|
26
|
+
*/
|
|
27
|
+
isDevMode: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Whether dotCMS Analytics is active. Resolved once at the layout root — a single
|
|
30
|
+
* `dotcms:analytics:ready` listener for the tree instead of one per contentlet.
|
|
31
|
+
*/
|
|
32
|
+
isAnalyticsActive: boolean;
|
|
22
33
|
}
|
|
23
34
|
/**
|
|
24
35
|
* The `PageContext` is a React context that provides access to the DotCMS page context.
|
|
@@ -3,7 +3,13 @@ import { RefObject } from 'react';
|
|
|
3
3
|
* @internal
|
|
4
4
|
* A custom React hook that checks whether a referenced HTMLDivElement has visible content based on its height.
|
|
5
5
|
*
|
|
6
|
+
* The measurement is only used to reserve space for the editor's empty-contentlet placeholder,
|
|
7
|
+
* so it is gated behind `enabled`. `getBoundingClientRect()` forces a synchronous layout, and
|
|
8
|
+
* running it from a layout effect on every contentlet of a page is expensive — in production
|
|
9
|
+
* the result was measured and then discarded.
|
|
10
|
+
*
|
|
6
11
|
* @param {RefObject<HTMLDivElement>} ref - A React ref object pointing to an HTMLDivElement.
|
|
12
|
+
* @param {boolean} [enabled=true] - When false the element is never measured and the hook returns false.
|
|
7
13
|
* @returns {boolean} - Returns true if the element's height is greater than zero (indicating visible content), otherwise false.
|
|
8
14
|
*
|
|
9
15
|
* @example
|
|
@@ -21,4 +27,4 @@ import { RefObject } from 'react';
|
|
|
21
27
|
* );
|
|
22
28
|
* }
|
|
23
29
|
*/
|
|
24
|
-
export declare const useCheckVisibleContent: (ref: RefObject<HTMLDivElement
|
|
30
|
+
export declare const useCheckVisibleContent: (ref: RefObject<HTMLDivElement>, enabled?: boolean) => boolean;
|
|
@@ -1,9 +1,39 @@
|
|
|
1
|
+
import { DotCMSPageRendererMode } from '@dotcms/types';
|
|
1
2
|
/**
|
|
2
3
|
* @internal
|
|
4
|
+
*
|
|
5
|
+
* Resolve whether we are rendering in "development" mode — i.e. whether editor metadata
|
|
6
|
+
* (`data-dot-*` attributes, empty-state placeholders, fallback components) should be emitted.
|
|
7
|
+
* Inside the UVE it follows the UVE state (dev when the mode is EDIT); otherwise it follows
|
|
8
|
+
* the renderer `mode` passed to `DotCMSLayoutBody`.
|
|
9
|
+
*
|
|
10
|
+
* @param mode the renderer mode from the page context
|
|
11
|
+
* @returns `true` when editor metadata should be emitted
|
|
12
|
+
*/
|
|
13
|
+
export declare const resolveDevMode: (mode: DotCMSPageRendererMode | undefined) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*
|
|
17
|
+
* Owns the development-mode state for one layout tree. Called once by `DotCMSPageProvider`;
|
|
18
|
+
* the result travels down through the page context.
|
|
19
|
+
*
|
|
20
|
+
* `getUVEState()` reads the browser, so it is deliberately resolved in an effect rather than
|
|
21
|
+
* during render: on the server it would always report "not in the editor", and resolving it
|
|
22
|
+
* synchronously on the client would make the first render disagree with the server-rendered
|
|
23
|
+
* markup and trip React's hydration check inside the UVE.
|
|
24
|
+
*
|
|
25
|
+
* @param mode the renderer mode passed to `DotCMSLayoutBody`
|
|
26
|
+
* @returns `true` when editor metadata should be emitted
|
|
27
|
+
*/
|
|
28
|
+
export declare const useResolvedDevMode: (mode: DotCMSPageRendererMode | undefined) => boolean;
|
|
29
|
+
/**
|
|
30
|
+
* @internal
|
|
31
|
+
*
|
|
3
32
|
* A React hook that determines if the current environment is in development mode.
|
|
4
33
|
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
34
|
+
* The value is resolved once per layout tree by `DotCMSPageProvider` and shared through the
|
|
35
|
+
* page context, so a page with many containers and contentlets resolves it a single time
|
|
36
|
+
* instead of once per component.
|
|
7
37
|
*
|
|
8
38
|
* @returns {boolean} - `true` if in development mode or inside the editor; otherwise, `false`.
|
|
9
39
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createDotCMSClient } from '@dotcms/client';
|
|
1
|
+
import type { createDotCMSClient } from '@dotcms/client';
|
|
2
2
|
import { DotCMSAISearchContentletData, DotCMSAISearchParams, DotCMSAISearchResponse, DotCMSBasicContentlet, DotCMSEntityStatus } from '@dotcms/types';
|
|
3
3
|
/**
|
|
4
4
|
* Return type of the AI Search context
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
function styleInject(css, ref) {
|
|
2
|
-
if (ref === void 0) ref = {};
|
|
3
|
-
var insertAt = ref.insertAt;
|
|
4
|
-
|
|
5
|
-
if (typeof document === 'undefined') {
|
|
6
|
-
return;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
var head = document.head || document.getElementsByTagName('head')[0];
|
|
10
|
-
var style = document.createElement('style');
|
|
11
|
-
style.type = 'text/css';
|
|
12
|
-
|
|
13
|
-
if (insertAt === 'top') {
|
|
14
|
-
if (head.firstChild) {
|
|
15
|
-
head.insertBefore(style, head.firstChild);
|
|
16
|
-
} else {
|
|
17
|
-
head.appendChild(style);
|
|
18
|
-
}
|
|
19
|
-
} else {
|
|
20
|
-
head.appendChild(style);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (style.styleSheet) {
|
|
24
|
-
style.styleSheet.cssText = css;
|
|
25
|
-
} else {
|
|
26
|
-
style.appendChild(document.createTextNode(css));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export { styleInject as default, styleInject };
|