@cntrl-site/sdk-nextjs 1.9.53 → 1.9.55-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.
Files changed (37) hide show
  1. package/lib/components/ScrollPlaybackVideo.js +17 -6
  2. package/lib/components/Section/Section.js +2 -1
  3. package/lib/components/items/CodeEmbedItem/CodeEmbedItem.js +2 -1
  4. package/lib/components/items/ComponentItem/ComponentItem.js +13 -6
  5. package/lib/components/items/CompoundItem/CompoundChild.js +2 -1
  6. package/lib/components/items/CompoundItem/CompoundItem.js +2 -1
  7. package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +2 -1
  8. package/lib/components/items/EmbedVideoItem/YoutubeEmbed.js +2 -1
  9. package/lib/components/items/FileItem/ImageItem.js +2 -1
  10. package/lib/components/items/FileItem/VideoItem.js +5 -4
  11. package/lib/components/items/GroupItem/GroupItem.js +2 -1
  12. package/lib/components/items/Item.js +2 -1
  13. package/lib/components/items/RectangleItem/RectangleItem.js +2 -1
  14. package/lib/components/items/RichTextItem/RichTextItem.js +2 -1
  15. package/lib/index.js +4 -1
  16. package/lib/provider/CntrlSdkContext.js +26 -1
  17. package/lib/utils/evaluateComponentBundle.js +92 -0
  18. package/package.json +4 -3
  19. package/src/components/Page.tsx +6 -0
  20. package/src/components/ScrollPlaybackVideo.tsx +21 -4
  21. package/src/components/Section/Section.tsx +2 -1
  22. package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +2 -1
  23. package/src/components/items/ComponentItem/ComponentItem.tsx +12 -2
  24. package/src/components/items/CompoundItem/CompoundChild.tsx +2 -1
  25. package/src/components/items/CompoundItem/CompoundItem.tsx +2 -1
  26. package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +2 -1
  27. package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +2 -1
  28. package/src/components/items/FileItem/ImageItem.tsx +2 -1
  29. package/src/components/items/FileItem/VideoItem.tsx +4 -1
  30. package/src/components/items/GroupItem/GroupItem.tsx +2 -1
  31. package/src/components/items/Item.tsx +2 -1
  32. package/src/components/items/RectangleItem/RectangleItem.tsx +2 -1
  33. package/src/components/items/RichTextItem/RichTextItem.tsx +2 -1
  34. package/src/index.ts +2 -1
  35. package/src/provider/CntrlSdkContext.ts +31 -2
  36. package/src/types/components-utils.d.ts +7 -0
  37. package/src/utils/evaluateComponentBundle.ts +83 -0
@@ -6,10 +6,16 @@ const react_1 = require("react");
6
6
  const rangeMap_1 = require("../utils/rangeMap");
7
7
  const ArticleRectContext_1 = require("../provider/ArticleRectContext");
8
8
  const sdk_1 = require("@cntrl-site/sdk");
9
- const ScrollPlaybackVideo = ({ sectionId, src, playbackParams, style, className }) => {
9
+ const ScrollPlaybackVideo = ({ sectionId, src, playbackParams, style, className, scrollPlaybackFrameData, frameBaseUrl }) => {
10
10
  const [containerElement, setContainerElement] = (0, react_1.useState)(null);
11
11
  const [time, setTime] = (0, react_1.useState)(0);
12
12
  const articleRectObserver = (0, react_1.useContext)(ArticleRectContext_1.ArticleRectContext);
13
+ const hasServerFrames = (scrollPlaybackFrameData === null || scrollPlaybackFrameData === void 0 ? void 0 : scrollPlaybackFrameData.status) === 'ready'
14
+ && scrollPlaybackFrameData.batchId
15
+ && scrollPlaybackFrameData.frameCount
16
+ && scrollPlaybackFrameData.frameRate
17
+ && scrollPlaybackFrameData.frameFormat
18
+ && frameBaseUrl;
13
19
  (0, react_1.useEffect)(() => {
14
20
  if (!playbackParams || !articleRectObserver)
15
21
  return;
@@ -24,12 +30,17 @@ const ScrollPlaybackVideo = ({ sectionId, src, playbackParams, style, className
24
30
  const scrollVideoManager = (0, react_1.useMemo)(() => {
25
31
  if (!containerElement)
26
32
  return null;
27
- const manager = new sdk_1.ScrollPlaybackVideoManager({
28
- src,
29
- videoContainer: containerElement
30
- });
33
+ const manager = new sdk_1.ScrollPlaybackVideoManager(Object.assign({ src, videoContainer: containerElement }, (hasServerFrames ? {
34
+ frameData: {
35
+ batchId: scrollPlaybackFrameData.batchId,
36
+ frameCount: scrollPlaybackFrameData.frameCount,
37
+ frameRate: scrollPlaybackFrameData.frameRate,
38
+ frameFormat: scrollPlaybackFrameData.frameFormat
39
+ },
40
+ frameBaseUrl
41
+ } : {})));
31
42
  return manager;
32
- }, [containerElement, src]);
43
+ }, [containerElement, src, hasServerFrames, frameBaseUrl]);
33
44
  (0, react_1.useEffect)(() => {
34
45
  return () => {
35
46
  scrollVideoManager === null || scrollVideoManager === void 0 ? void 0 : scrollVideoManager.destroy();
@@ -18,7 +18,8 @@ const checkOverflowClipSupport_1 = require("../../utils/checkOverflowClipSupport
18
18
  const DEFAULT_COLOR = 'rgba(0, 0, 0, 0)';
19
19
  const Section = ({ section, data, children, zIndex }) => {
20
20
  var _a;
21
- const id = (0, react_1.useId)();
21
+ const reactId = (0, react_1.useId)();
22
+ const id = `${reactId}-section-${section.id}`;
22
23
  const sectionRef = (0, react_1.useRef)(null);
23
24
  const { layouts, customSections } = (0, useCntrlContext_1.useCntrlContext)();
24
25
  const layout = (0, useLayoutContext_1.useLayoutContext)();
@@ -27,7 +27,8 @@ const stylesMap = {
27
27
  };
28
28
  const CodeEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
29
29
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
30
- const id = (0, react_1.useId)();
30
+ const reactId = (0, react_1.useId)();
31
+ const id = `${reactId}-codeembed-${item.id}`;
31
32
  const { layouts, fonts } = (0, useCntrlContext_1.useCntrlContext)();
32
33
  const fontGoogleTags = fonts === null || fonts === void 0 ? void 0 : fonts.google;
33
34
  const fontAdobeTags = fonts === null || fonts === void 0 ? void 0 : fonts.adobe;
@@ -16,11 +16,13 @@ const getStyleFromItemStateAndParams_1 = require("../../../utils/getStyleFromIte
16
16
  const useLayoutContext_1 = require("../../useLayoutContext");
17
17
  const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
18
18
  const ComponentItem = ({ item, sectionId, onResize, interactionCtrl }) => {
19
- var _a, _b, _c, _d;
19
+ var _a, _b, _c, _d, _e, _f;
20
20
  const sdk = (0, useCntrlContext_1.useCntrlContext)();
21
21
  const { layouts } = sdk;
22
22
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
23
23
  const layout = (0, useLayoutContext_1.useLayoutContext)();
24
+ const fallbackLayout = (_a = layouts[0]) === null || _a === void 0 ? void 0 : _a.id;
25
+ const effectiveLayout = layout !== null && layout !== void 0 ? layout : fallbackLayout;
24
26
  const layoutValues = [item.area, item.layoutParams];
25
27
  const component = sdk.getComponent(item.commonParams.componentId);
26
28
  const [ref, setRef] = (0, react_1.useState)(null);
@@ -28,12 +30,17 @@ const ComponentItem = ({ item, sectionId, onResize, interactionCtrl }) => {
28
30
  (0, useRegisterResize_1.useRegisterResize)(ref, onResize);
29
31
  (0, useItemGeometry_1.useItemGeometry)(item.id, ref);
30
32
  const stateParams = interactionCtrl === null || interactionCtrl === void 0 ? void 0 : interactionCtrl.getState(['opacity', 'angle', 'blur']);
31
- const angle = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_a = stateParams === null || stateParams === void 0 ? void 0 : stateParams.styles) === null || _a === void 0 ? void 0 : _a.angle, itemAngle);
32
- const opacity = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_b = stateParams === null || stateParams === void 0 ? void 0 : stateParams.styles) === null || _b === void 0 ? void 0 : _b.opacity, itemOpacity);
33
- const blur = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_c = stateParams === null || stateParams === void 0 ? void 0 : stateParams.styles) === null || _c === void 0 ? void 0 : _c.blur, itemBlur);
33
+ const angle = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_b = stateParams === null || stateParams === void 0 ? void 0 : stateParams.styles) === null || _b === void 0 ? void 0 : _b.angle, itemAngle);
34
+ const opacity = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_c = stateParams === null || stateParams === void 0 ? void 0 : stateParams.styles) === null || _c === void 0 ? void 0 : _c.opacity, itemOpacity);
35
+ const blur = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_d = stateParams === null || stateParams === void 0 ? void 0 : stateParams.styles) === null || _d === void 0 ? void 0 : _d.blur, itemBlur);
34
36
  const Element = component ? component.element : undefined;
35
- const parameters = layout ? item.layoutParams[layout].parameters : undefined;
36
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `custom-component-${item.id}`, ref: setRef, style: Object.assign(Object.assign(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset', transition: (_d = stateParams === null || stateParams === void 0 ? void 0 : stateParams.transition) !== null && _d !== void 0 ? _d : 'none' }), children: parameters && Element && ((0, jsx_runtime_1.jsx)(Element, Object.assign({ metadata: { itemId: item.id }, portalId: "component-portal", content: item.commonParams.content }, parameters))) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: item.id, children: `
37
+ const layoutParameters = effectiveLayout ? (_e = item.layoutParams[effectiveLayout]) === null || _e === void 0 ? void 0 : _e.parameters : undefined;
38
+ const commonParameters = item.commonParams.parameters;
39
+ const parameters = layoutParameters ? Object.assign(Object.assign({}, layoutParameters), { settings: Object.assign(Object.assign({}, layoutParameters.settings), commonParameters === null || commonParameters === void 0 ? void 0 : commonParameters.settings) }) : undefined;
40
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `custom-component-${item.id}`, ref: setRef, style: Object.assign(Object.assign(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset', transition: (_f = stateParams === null || stateParams === void 0 ? void 0 : stateParams.transition) !== null && _f !== void 0 ? _f : 'none' }), children: parameters && Element && ((0, jsx_runtime_1.jsx)(Element, Object.assign({ metadata: {
41
+ itemId: item.id,
42
+ submitUrl: sdk.getSubmitUrl(commonParameters === null || commonParameters === void 0 ? void 0 : commonParameters.pluginConfigId)
43
+ }, portalId: "component-portal", content: item.commonParams.content }, parameters))) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: item.id, children: `
37
44
  .custom-component-${item.id} {
38
45
  width: 100%;
39
46
  height: 100%;
@@ -25,7 +25,8 @@ const useItemArea_1 = require("../useItemArea");
25
25
  const noop = () => null;
26
26
  const CompoundChild = ({ item, sectionId, isParentVisible = true }) => {
27
27
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
28
- const id = (0, react_1.useId)();
28
+ const reactId = (0, react_1.useId)();
29
+ const id = `${reactId}-compound-${item.id}`;
29
30
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
30
31
  const layout = (0, useLayoutContext_1.useLayoutContext)();
31
32
  const exemplary = (0, useExemplary_1.useExemplary)();
@@ -19,7 +19,8 @@ const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
19
19
  const ItemGeometryContext_1 = require("../../../ItemGeometry/ItemGeometryContext");
20
20
  const CompoundItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
21
21
  var _a, _b, _c, _d, _e;
22
- const id = (0, react_1.useId)();
22
+ const reactId = (0, react_1.useId)();
23
+ const id = `${reactId}-compounditem-${item.id}`;
23
24
  const { items } = item;
24
25
  const itemIds = items ? items.map(item => item.id) : [];
25
26
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
@@ -28,7 +28,8 @@ const useLayoutContext_1 = require("../../useLayoutContext");
28
28
  const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
29
29
  const VimeoEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
30
30
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
31
- const id = (0, react_1.useId)();
31
+ const reactId = (0, react_1.useId)();
32
+ const id = `${reactId}-vimeo-${item.id}`;
32
33
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
33
34
  const { radius: itemRadius, blur: itemBlur, opacity: itemOpacity } = (0, useEmbedVideoItem_1.useEmbedVideoItem)(item, sectionId);
34
35
  const layoutId = (0, useLayoutContext_1.useLayoutContext)();
@@ -20,7 +20,8 @@ const useLayoutContext_1 = require("../../useLayoutContext");
20
20
  const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
21
21
  const YoutubeEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
22
22
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
23
- const id = (0, react_1.useId)();
23
+ const reactId = (0, react_1.useId)();
24
+ const id = `${reactId}-youtube-${item.id}`;
24
25
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
25
26
  const { url } = item.commonParams;
26
27
  const layoutId = (0, useLayoutContext_1.useLayoutContext)();
@@ -22,7 +22,8 @@ const getFill_1 = require("../../../utils/getFill");
22
22
  const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
23
23
  const ImageItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
24
24
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
25
- const id = (0, react_1.useId)();
25
+ const reactId = (0, react_1.useId)();
26
+ const id = `${reactId}-image-${item.id}`;
26
27
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
27
28
  const layoutId = (0, useLayoutContext_1.useLayoutContext)();
28
29
  const { radius: itemRadius, strokeWidth: itemStrokeWidth, opacity: itemOpacity, strokeFill: itemStrokeFill, blur: itemBlur } = (0, useFileItem_1.useFileItem)(item, sectionId);
@@ -22,8 +22,9 @@ const useItemFXData_1 = require("../../../common/useItemFXData");
22
22
  const getFill_1 = require("../../../utils/getFill");
23
23
  const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
24
24
  const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
25
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
26
- const id = (0, react_1.useId)();
25
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
26
+ const reactId = (0, react_1.useId)();
27
+ const id = `${reactId}-video-${item.id}`;
27
28
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
28
29
  const layoutId = (0, useLayoutContext_1.useLayoutContext)();
29
30
  const { radius: itemRadius, strokeWidth: itemStrokeWidth, strokeFill: itemStrokeFill, opacity: itemOpacity, blur: itemBlur } = (0, useFileItem_1.useFileItem)(item, sectionId);
@@ -99,7 +100,7 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
99
100
  observer.observe(ref);
100
101
  return () => observer.disconnect();
101
102
  }, [layoutParams, videoRef, ref, userPaused, isVideoInteracted]);
102
- return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, { url: (_q = item.link) === null || _q === void 0 ? void 0 : _q.url, target: (_r = item.link) === null || _r === void 0 ? void 0 : _r.target, children: [(0, jsx_runtime_1.jsxs)("div", { className: `video-wrapper-${item.id}`, ref: setRef, style: Object.assign(Object.assign(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset', transition: (_s = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _s !== void 0 ? _s : 'none' }), children: [hasScrollPlayback && ((0, jsx_runtime_1.jsx)(ScrollPlaybackVideo_1.ScrollPlaybackVideo, { sectionId: sectionId, src: item.commonParams.url, playbackParams: scrollPlayback, style: inlineStyles, className: `video video-playback-wrapper video-${item.id}` })), hasGLEffect && isFXAllowed && ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: `video-canvas video-${item.id}`, width: rectWidth, height: rectHeight })), !hasScrollPlayback && !hasGLEffect && layoutParams && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("video", { poster: (_t = item.commonParams.coverUrl) !== null && _t !== void 0 ? _t : '', ref: setVideoRef, autoPlay: layoutParams.play === 'auto', preload: "auto", onClick: () => {
103
+ return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, { url: (_q = item.link) === null || _q === void 0 ? void 0 : _q.url, target: (_r = item.link) === null || _r === void 0 ? void 0 : _r.target, children: [(0, jsx_runtime_1.jsxs)("div", { className: `video-wrapper-${item.id}`, ref: setRef, style: Object.assign(Object.assign(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset', transition: (_s = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _s !== void 0 ? _s : 'none' }), children: [hasScrollPlayback && ((0, jsx_runtime_1.jsx)(ScrollPlaybackVideo_1.ScrollPlaybackVideo, { sectionId: sectionId, src: item.commonParams.url, playbackParams: scrollPlayback, style: inlineStyles, className: `video video-playback-wrapper video-${item.id}`, scrollPlaybackFrameData: item.commonParams.scrollPlaybackFrameData, frameBaseUrl: (_t = item.commonParams.scrollPlaybackFrameData) === null || _t === void 0 ? void 0 : _t.framesUrl })), hasGLEffect && isFXAllowed && ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: `video-canvas video-${item.id}`, width: rectWidth, height: rectHeight })), !hasScrollPlayback && !hasGLEffect && layoutParams && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("video", { poster: (_u = item.commonParams.coverUrl) !== null && _u !== void 0 ? _u : '', ref: setVideoRef, autoPlay: layoutParams.play === 'auto', preload: "auto", onClick: () => {
103
104
  setIsVideoInteracted(true);
104
105
  }, muted: layoutParams.muted, onPlay: () => {
105
106
  setIsVideoPlaying(true);
@@ -122,7 +123,7 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
122
123
  return;
123
124
  setIsVideoInteracted(true);
124
125
  videoRef.play();
125
- }, src: (_u = item.commonParams.coverUrl) !== null && _u !== void 0 ? _u : '', className: `video-cover-${item.id}`, onClick: () => {
126
+ }, src: (_v = item.commonParams.coverUrl) !== null && _v !== void 0 ? _v : '', className: `video-cover-${item.id}`, onClick: () => {
126
127
  if (!videoRef)
127
128
  return;
128
129
  setIsVideoInteracted(true);
@@ -20,7 +20,8 @@ const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
20
20
  const ItemGeometryContext_1 = require("../../../ItemGeometry/ItemGeometryContext");
21
21
  const GroupItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange, isInCompound }) => {
22
22
  var _a, _b, _c, _d, _e, _f;
23
- const id = (0, react_1.useId)();
23
+ const reactId = (0, react_1.useId)();
24
+ const id = `${reactId}-group-${item.id}`;
24
25
  const { items } = item;
25
26
  const itemIds = items ? items.map(item => item.id) : [];
26
27
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
@@ -40,7 +40,8 @@ const Item = ({ item, sectionId, articleHeight, isParentVisible = true, isInGrou
40
40
  const itemInnerRef = (0, react_1.useRef)(null);
41
41
  const rectObserver = (0, react_1.useContext)(ArticleRectContext_1.ArticleRectContext);
42
42
  const keyframesRepo = (0, react_1.useContext)(KeyframesContext_1.KeyframesContext);
43
- const id = (0, react_1.useId)();
43
+ const reactId = (0, react_1.useId)();
44
+ const id = `${reactId}-item-${item.id}`;
44
45
  const keyframes = (0, react_1.useMemo)(() => keyframesRepo.getItemKeyframes(item.id), [keyframesRepo, item.id]);
45
46
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
46
47
  const layout = (0, useLayoutContext_1.useLayoutContext)();
@@ -20,7 +20,8 @@ const areFillsVisible_1 = require("../../../utils/areFillsVisible/areFillsVisibl
20
20
  const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
21
21
  const RectangleItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
22
22
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
23
- const id = (0, react_1.useId)();
23
+ const reactId = (0, react_1.useId)();
24
+ const id = `${reactId}-rect-${item.id}`;
24
25
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
25
26
  const { fill: itemFill, radius: itemRadius, strokeWidth: itemStrokeWidth, strokeFill: itemStrokeFill, blur: itemBlur, backdropBlur: itemBackdropBlur } = (0, useRectangleItem_1.useRectangleItem)(item, sectionId);
26
27
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
@@ -22,7 +22,8 @@ const useItemGeometry_1 = require("../../../ItemGeometry/useItemGeometry");
22
22
  const RichTextGeometryController_1 = require("../../../ItemGeometry/RichTextGeometryController");
23
23
  const RichTextItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
24
24
  var _a, _b, _c, _d;
25
- const id = (0, react_1.useId)();
25
+ const reactId = (0, react_1.useId)();
26
+ const id = `${reactId}-richtext-${item.id}`;
26
27
  const [ref, setRef] = (0, react_1.useState)(null);
27
28
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
28
29
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
package/lib/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.cntrlSdkContext = exports.customSections = exports.customItems = exports.useCntrlContext = exports.CntrlProvider = exports.YoutubeEmbedItem = exports.VimeoEmbedItem = exports.LayoutStyle = exports.VideoItem = exports.RichTextItem = exports.RectangleItem = exports.ImageItem = exports.Item = exports.Section = exports.Article = exports.Head = exports.Page = exports.RichTextConverter = void 0;
17
+ exports.cntrlSdkContext = exports.customSections = exports.customItems = exports.resolveCustomComponents = exports.evaluateComponentBundle = exports.useCntrlContext = exports.CntrlProvider = exports.YoutubeEmbedItem = exports.VimeoEmbedItem = exports.LayoutStyle = exports.VideoItem = exports.RichTextItem = exports.RectangleItem = exports.ImageItem = exports.Item = exports.Section = exports.Article = exports.Head = exports.Page = exports.RichTextConverter = void 0;
18
18
  const defaultContext_1 = require("./provider/defaultContext");
19
19
  __exportStar(require("@cntrl-site/sdk"), exports);
20
20
  var RichTextConverter_1 = require("./utils/RichTextConverter/RichTextConverter");
@@ -47,6 +47,9 @@ var CntrlProvider_1 = require("./provider/CntrlProvider");
47
47
  Object.defineProperty(exports, "CntrlProvider", { enumerable: true, get: function () { return CntrlProvider_1.CntrlProvider; } });
48
48
  var useCntrlContext_1 = require("./provider/useCntrlContext");
49
49
  Object.defineProperty(exports, "useCntrlContext", { enumerable: true, get: function () { return useCntrlContext_1.useCntrlContext; } });
50
+ var evaluateComponentBundle_1 = require("./utils/evaluateComponentBundle");
51
+ Object.defineProperty(exports, "evaluateComponentBundle", { enumerable: true, get: function () { return evaluateComponentBundle_1.evaluateComponentBundle; } });
52
+ Object.defineProperty(exports, "resolveCustomComponents", { enumerable: true, get: function () { return evaluateComponentBundle_1.resolveCustomComponents; } });
50
53
  exports.customItems = defaultContext_1.cntrlSdkContext.customItems;
51
54
  exports.customSections = defaultContext_1.cntrlSdkContext.customSections;
52
55
  exports.cntrlSdkContext = defaultContext_1.cntrlSdkContext;
@@ -17,6 +17,8 @@ class CntrlSdkContext {
17
17
  this.customSections = customSections;
18
18
  this._layouts = [];
19
19
  this._fonts = undefined;
20
+ this._projectId = undefined;
21
+ this._publicApiBase = undefined;
20
22
  this.sectionHeightMap = new Map();
21
23
  this.components = new Map();
22
24
  }
@@ -34,12 +36,30 @@ class CntrlSdkContext {
34
36
  return Object.fromEntries(yield Promise.all(resolvers.map(({ name, resolver }) => __awaiter(this, void 0, void 0, function* () { return [name, yield resolver()]; }))));
35
37
  });
36
38
  }
37
- init({ project, article }) {
39
+ init({ project, article, customComponents }) {
40
+ this._projectId = project.id;
38
41
  this.setLayouts(project.layouts);
39
42
  this.setComponents(components_1.components);
43
+ if (customComponents) {
44
+ this.setCustomComponents(customComponents);
45
+ }
40
46
  this.setFonts(project.fonts);
41
47
  this.setSectionsHeight(article.sections);
42
48
  }
49
+ setPublicApiBase(url) {
50
+ this._publicApiBase = url;
51
+ }
52
+ get projectId() {
53
+ return this._projectId;
54
+ }
55
+ get publicApiBase() {
56
+ return this._publicApiBase;
57
+ }
58
+ getSubmitUrl(pluginConfigId) {
59
+ if (!this._publicApiBase || !this._projectId || !pluginConfigId)
60
+ return undefined;
61
+ return `${this._publicApiBase}/projects/${this._projectId}/forms/${pluginConfigId}/submit`;
62
+ }
43
63
  setLayouts(layouts) {
44
64
  this._layouts = layouts;
45
65
  }
@@ -48,6 +68,11 @@ class CntrlSdkContext {
48
68
  this.components.set(component.id, component);
49
69
  }
50
70
  }
71
+ setCustomComponents(customComponents) {
72
+ for (const [id, component] of customComponents) {
73
+ this.components.set(id, component);
74
+ }
75
+ }
51
76
  setFonts(fonts) {
52
77
  this._fonts = fonts;
53
78
  }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.resolveCustomComponents = exports.evaluateComponentBundle = void 0;
30
+ const react_1 = __importDefault(require("react"));
31
+ const react_dom_1 = __importDefault(require("react-dom"));
32
+ const jsxRuntime = __importStar(require("react/jsx-runtime"));
33
+ const componentUtils = __importStar(require("@cntrl-site/components/utils"));
34
+ let globalsInitialized = false;
35
+ function ensureGlobals() {
36
+ if (globalsInitialized)
37
+ return;
38
+ globalThis.__CNTRL_REACT__ = react_1.default;
39
+ globalThis.__CNTRL_REACT_DOM__ = react_dom_1.default;
40
+ globalThis.__CNTRL_JSX_RUNTIME__ = jsxRuntime;
41
+ globalThis.__CNTRL_COMPONENT_UTILS__ = componentUtils;
42
+ globalsInitialized = true;
43
+ }
44
+ function transformEsmToEvaluatable(code) {
45
+ let transformed = code;
46
+ let hasDefault = false;
47
+ transformed = transformed.replace(/export\s*\{\s*(\w+)\s+as\s+default\s*\}\s*;?/g, (_, name) => { hasDefault = true; return `var __cntrl_default_export__ = ${name};`; });
48
+ transformed = transformed.replace(/export\s+default\s+/g, () => { hasDefault = true; return 'var __cntrl_default_export__ = '; });
49
+ transformed = transformed.replace(/export\s*\{\s*([^}]*)\}\s*;?/g, (_, names) => {
50
+ if (!hasDefault) {
51
+ const firstName = names.split(',')[0].trim().split(/\s+/)[0];
52
+ if (firstName) {
53
+ hasDefault = true;
54
+ return `var __cntrl_default_export__ = ${firstName};`;
55
+ }
56
+ }
57
+ return '';
58
+ });
59
+ return `${transformed}\nreturn __cntrl_default_export__;`;
60
+ }
61
+ function evaluateComponentBundle(bundleCode) {
62
+ ensureGlobals();
63
+ const evaluatable = transformEsmToEvaluatable(bundleCode);
64
+ const fn = new Function(evaluatable);
65
+ return fn();
66
+ }
67
+ exports.evaluateComponentBundle = evaluateComponentBundle;
68
+ const resolvedCache = new Map();
69
+ function resolveCustomComponents(bundles, schemas) {
70
+ var _a;
71
+ const result = new Map();
72
+ for (const [id, code] of Object.entries(bundles)) {
73
+ try {
74
+ let element = resolvedCache.get(id);
75
+ if (!element) {
76
+ element = evaluateComponentBundle(code);
77
+ resolvedCache.set(id, element);
78
+ }
79
+ result.set(id, {
80
+ id,
81
+ name: id,
82
+ element,
83
+ schema: (_a = schemas === null || schemas === void 0 ? void 0 : schemas[id]) !== null && _a !== void 0 ? _a : {}
84
+ });
85
+ }
86
+ catch (err) {
87
+ console.error(`Failed to evaluate custom component bundle "${id}":`, err);
88
+ }
89
+ }
90
+ return result;
91
+ }
92
+ exports.resolveCustomComponents = resolveCustomComponents;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cntrl-site/sdk-nextjs",
3
- "version": "1.9.53",
3
+ "version": "1.9.55-1",
4
4
  "description": "SDK for Next.js",
5
5
  "author": "arsen@momdesign.nyc",
6
6
  "license": "MIT",
@@ -31,9 +31,9 @@
31
31
  "dependencies": {
32
32
  "@antfu/eslint-config": "^3.8.0",
33
33
  "@cntrl-site/color": "^1.0.0",
34
- "@cntrl-site/components": "^0.1.34",
34
+ "@cntrl-site/components": "^1.0.1-alpha.15",
35
35
  "@cntrl-site/effects": "^1.4.0",
36
- "@cntrl-site/sdk": "^1.25.11",
36
+ "@cntrl-site/sdk": "^1.26.1",
37
37
  "@types/vimeo__player": "^2.18.0",
38
38
  "@vimeo/player": "^2.25.0",
39
39
  "html-react-parser": "^3.0.1",
@@ -48,6 +48,7 @@
48
48
  "@types/jest": "^29.5.12",
49
49
  "@types/node": "^18.11.7",
50
50
  "@types/react": "^18.0.15",
51
+ "@types/react-dom": "^18.3.7",
51
52
  "cross-env": "^10.1.0",
52
53
  "jest": "^29.7.0",
53
54
  "jest-environment-jsdom": "^29.7.0",
@@ -8,12 +8,18 @@ import { Keyframes } from '../provider/Keyframes';
8
8
  import { ItemGeometryContext } from '../ItemGeometry/ItemGeometryContext';
9
9
  import { ItemGeometryService } from '../ItemGeometry/ItemGeometryService';
10
10
 
11
+ export interface CustomComponentBundlesData {
12
+ bundles: Record<string, string>;
13
+ schemas: Record<string, Record<string, unknown>>;
14
+ }
15
+
11
16
  export interface PageProps {
12
17
  article: TArticle;
13
18
  project: Project;
14
19
  meta: Meta;
15
20
  keyframes: KeyframeAny[];
16
21
  sectionData: Record<SectionName, any>;
22
+ customComponentBundles?: CustomComponentBundlesData;
17
23
  }
18
24
 
19
25
  export const Page: FC<PageProps> = ({ article, project, meta, keyframes, sectionData }) => {
@@ -1,7 +1,7 @@
1
1
  import React, { FC, useContext, useEffect, useMemo, useState } from 'react';
2
2
  import { rangeMap } from '../utils/rangeMap';
3
3
  import { ArticleRectContext } from '../provider/ArticleRectContext';
4
- import { ScrollPlaybackVideoManager } from '@cntrl-site/sdk';
4
+ import { ScrollPlaybackVideoManager, ScrollPlaybackFrameData } from '@cntrl-site/sdk';
5
5
 
6
6
  type PlaybackParams = { from: number, to: number };
7
7
 
@@ -11,12 +11,20 @@ interface Props {
11
11
  playbackParams: PlaybackParams | null;
12
12
  style?: React.CSSProperties;
13
13
  className: string;
14
+ scrollPlaybackFrameData?: ScrollPlaybackFrameData | null;
15
+ frameBaseUrl?: string;
14
16
  }
15
17
 
16
- export const ScrollPlaybackVideo: FC<Props> = ({ sectionId, src, playbackParams, style, className }) => {
18
+ export const ScrollPlaybackVideo: FC<Props> = ({ sectionId, src, playbackParams, style, className, scrollPlaybackFrameData, frameBaseUrl }) => {
17
19
  const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null);
18
20
  const [time, setTime] = useState(0);
19
21
  const articleRectObserver = useContext(ArticleRectContext);
22
+ const hasServerFrames = scrollPlaybackFrameData?.status === 'ready'
23
+ && scrollPlaybackFrameData.batchId
24
+ && scrollPlaybackFrameData.frameCount
25
+ && scrollPlaybackFrameData.frameRate
26
+ && scrollPlaybackFrameData.frameFormat
27
+ && frameBaseUrl;
20
28
 
21
29
  useEffect(() => {
22
30
  if (!playbackParams || !articleRectObserver) return;
@@ -33,10 +41,19 @@ export const ScrollPlaybackVideo: FC<Props> = ({ sectionId, src, playbackParams,
33
41
  if (!containerElement) return null;
34
42
  const manager = new ScrollPlaybackVideoManager({
35
43
  src,
36
- videoContainer: containerElement
44
+ videoContainer: containerElement,
45
+ ...(hasServerFrames ? {
46
+ frameData: {
47
+ batchId: scrollPlaybackFrameData.batchId!,
48
+ frameCount: scrollPlaybackFrameData.frameCount!,
49
+ frameRate: scrollPlaybackFrameData.frameRate!,
50
+ frameFormat: scrollPlaybackFrameData.frameFormat!
51
+ },
52
+ frameBaseUrl
53
+ } : {})
37
54
  });
38
55
  return manager;
39
- }, [containerElement, src]);
56
+ }, [containerElement, src, hasServerFrames, frameBaseUrl]);
40
57
 
41
58
  useEffect(() => {
42
59
  return () => {
@@ -26,7 +26,8 @@ interface Props {
26
26
  }
27
27
 
28
28
  export const Section: FC<Props> = ({ section, data, children, zIndex }) => {
29
- const id = useId();
29
+ const reactId = useId();
30
+ const id = `${reactId}-section-${section.id}`;
30
31
  const sectionRef = useRef<HTMLDivElement | null>(null);
31
32
  const { layouts, customSections } = useCntrlContext();
32
33
  const layout = useLayoutContext();
@@ -22,7 +22,8 @@ const stylesMap = {
22
22
  };
23
23
 
24
24
  export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
25
- const id = useId();
25
+ const reactId = useId();
26
+ const id = `${reactId}-codeembed-${item.id}`;
26
27
  const { layouts, fonts } = useCntrlContext();
27
28
  const fontGoogleTags = fonts?.google;
28
29
  const fontAdobeTags = fonts?.adobe;
@@ -15,6 +15,8 @@ export const ComponentItem: FC<ItemProps<TComponentItem>> = ({ item, sectionId,
15
15
  const { layouts } = sdk;
16
16
  const itemAngle = useItemAngle(item, sectionId);
17
17
  const layout = useLayoutContext();
18
+ const fallbackLayout = layouts[0]?.id;
19
+ const effectiveLayout = layout ?? fallbackLayout;
18
20
  const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
19
21
  const component = sdk.getComponent(item.commonParams.componentId);
20
22
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
@@ -26,7 +28,12 @@ export const ComponentItem: FC<ItemProps<TComponentItem>> = ({ item, sectionId,
26
28
  const opacity = getStyleFromItemStateAndParams(stateParams?.styles?.opacity, itemOpacity);
27
29
  const blur = getStyleFromItemStateAndParams(stateParams?.styles?.blur, itemBlur);
28
30
  const Element = component ? component.element : undefined;
29
- const parameters = layout ? item.layoutParams[layout].parameters : undefined;
31
+ const layoutParameters = effectiveLayout ? item.layoutParams[effectiveLayout]?.parameters : undefined;
32
+ const commonParameters = (item.commonParams as any).parameters;
33
+ const parameters = layoutParameters ? {
34
+ ...layoutParameters,
35
+ settings: { ...layoutParameters.settings, ...commonParameters?.settings }
36
+ } : undefined;
30
37
 
31
38
  return (
32
39
  <>
@@ -43,7 +50,10 @@ export const ComponentItem: FC<ItemProps<TComponentItem>> = ({ item, sectionId,
43
50
  >
44
51
  {parameters && Element && (
45
52
  <Element
46
- metadata={{ itemId: item.id }}
53
+ metadata={{
54
+ itemId: item.id,
55
+ submitUrl: sdk.getSubmitUrl(commonParameters?.pluginConfigId)
56
+ }}
47
57
  portalId="component-portal"
48
58
  content={item.commonParams.content}
49
59
  {...parameters}
@@ -36,7 +36,8 @@ interface ChildItemProps {
36
36
  const noop = () => null;
37
37
 
38
38
  export const CompoundChild: FC<ChildItemProps> = ({ item, sectionId, isParentVisible = true }) => {
39
- const id = useId();
39
+ const reactId = useId();
40
+ const id = `${reactId}-compound-${item.id}`;
40
41
  const { layouts } = useCntrlContext();
41
42
  const layout = useLayoutContext();
42
43
  const exemplary = useExemplary();
@@ -13,7 +13,8 @@ import { useItemGeometry } from '../../../ItemGeometry/useItemGeometry';
13
13
  import { ItemGeometryContext } from '../../../ItemGeometry/ItemGeometryContext';
14
14
 
15
15
  export const CompoundItem: FC<ItemProps<TCompoundItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
16
- const id = useId();
16
+ const reactId = useId();
17
+ const id = `${reactId}-compounditem-${item.id}`;
17
18
  const { items } = item;
18
19
  const itemIds = items ? items.map(item => item.id) : [];
19
20
  const itemAngle = useItemAngle(item, sectionId);
@@ -15,7 +15,8 @@ import { useLayoutContext } from '../../useLayoutContext';
15
15
  import { useItemGeometry } from '../../../ItemGeometry/useItemGeometry';
16
16
 
17
17
  export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
18
- const id = useId();
18
+ const reactId = useId();
19
+ const id = `${reactId}-vimeo-${item.id}`;
19
20
  const { layouts } = useCntrlContext();
20
21
  const {
21
22
  radius: itemRadius,
@@ -15,7 +15,8 @@ import { useLayoutContext } from '../../useLayoutContext';
15
15
  import { useItemGeometry } from '../../../ItemGeometry/useItemGeometry';
16
16
 
17
17
  export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
18
- const id = useId();
18
+ const reactId = useId();
19
+ const id = `${reactId}-youtube-${item.id}`;
19
20
  const { layouts } = useCntrlContext();
20
21
  const { url } = item.commonParams;
21
22
  const layoutId = useLayoutContext();
@@ -16,7 +16,8 @@ import { getFill } from '../../../utils/getFill';
16
16
  import { useItemGeometry } from '../../../ItemGeometry/useItemGeometry';
17
17
 
18
18
  export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
19
- const id = useId();
19
+ const reactId = useId();
20
+ const id = `${reactId}-image-${item.id}`;
20
21
  const { layouts } = useCntrlContext();
21
22
  const layoutId = useLayoutContext();
22
23
  const {
@@ -17,7 +17,8 @@ import { getFill } from '../../../utils/getFill';
17
17
  import { useItemGeometry } from '../../../ItemGeometry/useItemGeometry';
18
18
 
19
19
  export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
20
- const id = useId();
20
+ const reactId = useId();
21
+ const id = `${reactId}-video-${item.id}`;
21
22
  const { layouts } = useCntrlContext();
22
23
  const layoutId = useLayoutContext();
23
24
  const {
@@ -130,6 +131,8 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
130
131
  playbackParams={scrollPlayback}
131
132
  style={inlineStyles}
132
133
  className={`video video-playback-wrapper video-${item.id}`}
134
+ scrollPlaybackFrameData={item.commonParams.scrollPlaybackFrameData}
135
+ frameBaseUrl={item.commonParams.scrollPlaybackFrameData?.framesUrl}
133
136
  />
134
137
  )}
135
138
  {hasGLEffect && isFXAllowed && (
@@ -13,7 +13,8 @@ import { useItemGeometry } from '../../../ItemGeometry/useItemGeometry';
13
13
  import { ItemGeometryContext } from '../../../ItemGeometry/ItemGeometryContext';
14
14
 
15
15
  export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange, isInCompound }) => {
16
- const id = useId();
16
+ const reactId = useId();
17
+ const id = `${reactId}-group-${item.id}`;
17
18
  const { items } = item;
18
19
  const itemIds = items ? items.map(item => item.id) : [];
19
20
  const itemAngle = useItemAngle(item, sectionId);
@@ -68,7 +68,8 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isP
68
68
  const itemInnerRef = useRef<HTMLDivElement | null>(null);
69
69
  const rectObserver = useContext(ArticleRectContext);
70
70
  const keyframesRepo = useContext(KeyframesContext);
71
- const id = useId();
71
+ const reactId = useId();
72
+ const id = `${reactId}-item-${item.id}`;
72
73
  const keyframes = useMemo(() => keyframesRepo.getItemKeyframes(item.id), [keyframesRepo, item.id]);
73
74
  const { layouts } = useCntrlContext();
74
75
  const layout = useLayoutContext();
@@ -14,7 +14,8 @@ import { areFillsVisible } from '../../../utils/areFillsVisible/areFillsVisible'
14
14
  import { useItemGeometry } from '../../../ItemGeometry/useItemGeometry';
15
15
 
16
16
  export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
17
- const id = useId();
17
+ const reactId = useId();
18
+ const id = `${reactId}-rect-${item.id}`;
18
19
  const { layouts } = useCntrlContext();
19
20
  const {
20
21
  fill: itemFill,
@@ -16,7 +16,8 @@ import { useItemGeometry } from '../../../ItemGeometry/useItemGeometry';
16
16
  import { RichTextGeometryController } from '../../../ItemGeometry/RichTextGeometryController';
17
17
 
18
18
  export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
19
- const id = useId();
19
+ const reactId = useId();
20
+ const id = `${reactId}-richtext-${item.id}`;
20
21
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
21
22
  const { layouts } = useCntrlContext();
22
23
  const itemAngle = useItemAngle(item, sectionId);
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ import { cntrlSdkContext as sdk } from './provider/defaultContext';
3
3
  export * from '@cntrl-site/sdk';
4
4
  export { RichTextConverter } from './utils/RichTextConverter/RichTextConverter';
5
5
  export { Page } from './components/Page';
6
- export type { PageProps } from './components/Page';
6
+ export type { PageProps, CustomComponentBundlesData } from './components/Page';
7
7
  export { CNTRLHead as Head } from './components/Head';
8
8
  export { Article } from './components/Article';
9
9
  export { Section } from './components/Section/Section';
@@ -19,6 +19,7 @@ export { YoutubeEmbedItem } from './components/items/EmbedVideoItem/YoutubeEmbed
19
19
  export { CntrlProvider } from './provider/CntrlProvider';
20
20
  export type { CustomItemComponent } from './provider/CustomItemTypes';
21
21
  export { useCntrlContext } from './provider/useCntrlContext';
22
+ export { evaluateComponentBundle, resolveCustomComponents } from './utils/evaluateComponentBundle';
22
23
  export const customItems = sdk.customItems;
23
24
  export const customSections = sdk.customSections;
24
25
  export const cntrlSdkContext = sdk;
@@ -2,16 +2,18 @@ import { CustomItemRegistry } from './CustomItemRegistry';
2
2
  import { Article, Section, Layout, Project, SectionHeight } from '@cntrl-site/sdk';
3
3
  import { components, Component as TComponent } from '@cntrl-site/components';
4
4
  import { CustomSectionRegistry } from './CustomSectionRegistry';
5
- import { Component } from 'react';
6
5
 
7
6
  interface SdkContextInitProps {
8
7
  project: Project;
9
8
  article: Article;
9
+ customComponents?: Map<string, TComponent>;
10
10
  }
11
11
 
12
12
  export class CntrlSdkContext {
13
13
  private _layouts: Layout[] = [];
14
14
  private _fonts?: Project['fonts'] = undefined;
15
+ private _projectId?: string = undefined;
16
+ private _publicApiBase?: string = undefined;
15
17
  private sectionHeightMap: Map<string, Record<string, SectionHeight>> = new Map();
16
18
  private components: Map<string, TComponent> = new Map();
17
19
 
@@ -34,13 +36,34 @@ export class CntrlSdkContext {
34
36
  );
35
37
  }
36
38
 
37
- init({ project, article }: SdkContextInitProps) {
39
+ init({ project, article, customComponents }: SdkContextInitProps) {
40
+ this._projectId = project.id;
38
41
  this.setLayouts(project.layouts);
39
42
  this.setComponents(components);
43
+ if (customComponents) {
44
+ this.setCustomComponents(customComponents);
45
+ }
40
46
  this.setFonts(project.fonts);
41
47
  this.setSectionsHeight(article.sections);
42
48
  }
43
49
 
50
+ setPublicApiBase(url: string) {
51
+ this._publicApiBase = url;
52
+ }
53
+
54
+ get projectId(): string | undefined {
55
+ return this._projectId;
56
+ }
57
+
58
+ get publicApiBase(): string | undefined {
59
+ return this._publicApiBase;
60
+ }
61
+
62
+ getSubmitUrl(pluginConfigId: string | undefined): string | undefined {
63
+ if (!this._publicApiBase || !this._projectId || !pluginConfigId) return undefined;
64
+ return `${this._publicApiBase}/projects/${this._projectId}/forms/${pluginConfigId}/submit`;
65
+ }
66
+
44
67
  setLayouts(layouts: Layout[]) {
45
68
  this._layouts = layouts;
46
69
  }
@@ -51,6 +74,12 @@ export class CntrlSdkContext {
51
74
  }
52
75
  }
53
76
 
77
+ setCustomComponents(customComponents: Map<string, TComponent>) {
78
+ for (const [id, component] of customComponents) {
79
+ this.components.set(id, component);
80
+ }
81
+ }
82
+
54
83
  private setFonts(fonts: Project['fonts']) {
55
84
  this._fonts = fonts;
56
85
  }
@@ -0,0 +1,7 @@
1
+ declare module '@cntrl-site/components/utils' {
2
+ export function scalingValue(value: number, isEditor?: boolean): string;
3
+ export function useScopedStyles(): { prefix: string; cls: (name: string) => string };
4
+ export function getColorAlpha(color: string): number;
5
+ export function getPositionStyles(position: any): Record<string, any>;
6
+ export function getImageRect(rect: any): Record<string, any>;
7
+ }
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import * as jsxRuntime from 'react/jsx-runtime';
4
+ import { Component as TComponent } from '@cntrl-site/components';
5
+ import * as componentUtils from '@cntrl-site/components/utils';
6
+
7
+ let globalsInitialized = false;
8
+
9
+ function ensureGlobals() {
10
+ if (globalsInitialized) return;
11
+ (globalThis as any).__CNTRL_REACT__ = React;
12
+ (globalThis as any).__CNTRL_REACT_DOM__ = ReactDOM;
13
+ (globalThis as any).__CNTRL_JSX_RUNTIME__ = jsxRuntime;
14
+ (globalThis as any).__CNTRL_COMPONENT_UTILS__ = componentUtils;
15
+ globalsInitialized = true;
16
+ }
17
+
18
+ function transformEsmToEvaluatable(code: string): string {
19
+ let transformed = code;
20
+ let hasDefault = false;
21
+
22
+ transformed = transformed.replace(
23
+ /export\s*\{\s*(\w+)\s+as\s+default\s*\}\s*;?/g,
24
+ (_, name) => { hasDefault = true; return `var __cntrl_default_export__ = ${name};`; }
25
+ );
26
+
27
+ transformed = transformed.replace(
28
+ /export\s+default\s+/g,
29
+ () => { hasDefault = true; return 'var __cntrl_default_export__ = '; }
30
+ );
31
+
32
+ transformed = transformed.replace(
33
+ /export\s*\{\s*([^}]*)\}\s*;?/g,
34
+ (_, names) => {
35
+ if (!hasDefault) {
36
+ const firstName = names.split(',')[0].trim().split(/\s+/)[0];
37
+ if (firstName) {
38
+ hasDefault = true;
39
+ return `var __cntrl_default_export__ = ${firstName};`;
40
+ }
41
+ }
42
+ return '';
43
+ }
44
+ );
45
+
46
+ return `${transformed}\nreturn __cntrl_default_export__;`;
47
+ }
48
+
49
+ export function evaluateComponentBundle(
50
+ bundleCode: string
51
+ ): (props: any) => React.ReactElement {
52
+ ensureGlobals();
53
+ const evaluatable = transformEsmToEvaluatable(bundleCode);
54
+ const fn = new Function(evaluatable);
55
+ return fn();
56
+ }
57
+
58
+ const resolvedCache = new Map<string, (props: any) => React.ReactElement>();
59
+
60
+ export function resolveCustomComponents(
61
+ bundles: Record<string, string>,
62
+ schemas?: Record<string, Record<string, unknown>>
63
+ ): Map<string, TComponent> {
64
+ const result = new Map<string, TComponent>();
65
+ for (const [id, code] of Object.entries(bundles)) {
66
+ try {
67
+ let element = resolvedCache.get(id);
68
+ if (!element) {
69
+ element = evaluateComponentBundle(code);
70
+ resolvedCache.set(id, element);
71
+ }
72
+ result.set(id, {
73
+ id,
74
+ name: id,
75
+ element,
76
+ schema: schemas?.[id] ?? {}
77
+ } as TComponent);
78
+ } catch (err) {
79
+ console.error(`Failed to evaluate custom component bundle "${id}":`, err);
80
+ }
81
+ }
82
+ return result;
83
+ }