@cntrl-site/sdk-nextjs 0.12.1 → 0.12.2
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/lib/components/Item.js +44 -13
- package/lib/components/Section.js +2 -1
- package/lib/components/items/RichTextItem.js +16 -2
- package/lib/components/useItemPosition.js +20 -2
- package/lib/components/useSectionHeightMap.js +20 -0
- package/lib/provider/CntrlSdkContext.js +15 -0
- package/package.json +1 -1
- package/src/components/Item.tsx +64 -22
- package/src/components/Section.tsx +1 -1
- package/src/components/items/RichTextItem.tsx +17 -2
- package/src/components/useItemPosition.ts +19 -2
- package/src/components/useSectionHeightMap.ts +19 -0
- package/src/provider/CntrlSdkContext.ts +25 -1
- package/lib/components/items/useItemSticky.js +0 -51
- package/lib/utils/StickyManager/StickyManager.js +0 -25
- package/src/components/items/useItemSticky.ts +0 -51
package/lib/components/Item.js
CHANGED
|
@@ -18,10 +18,10 @@ const CustomItem_1 = require("./items/CustomItem");
|
|
|
18
18
|
const useCntrlContext_1 = require("../provider/useCntrlContext");
|
|
19
19
|
const useItemPosition_1 = require("./useItemPosition");
|
|
20
20
|
const useItemDimensions_1 = require("./useItemDimensions");
|
|
21
|
-
const useItemSticky_1 = require("./items/useItemSticky");
|
|
22
21
|
const useCurrentLayout_1 = require("../common/useCurrentLayout");
|
|
23
22
|
const useItemScale_1 = require("./useItemScale");
|
|
24
23
|
const ScaleAnchorMap_1 = require("../utils/ScaleAnchorMap");
|
|
24
|
+
const useSectionHeightMap_1 = require("./useSectionHeightMap");
|
|
25
25
|
const itemsMap = {
|
|
26
26
|
[sdk_1.ArticleItemType.Rectangle]: RectangleItem_1.RectangleItem,
|
|
27
27
|
[sdk_1.ArticleItemType.Image]: ImageItem_1.ImageItem,
|
|
@@ -35,13 +35,16 @@ const noop = () => null;
|
|
|
35
35
|
const Item = ({ item, sectionId }) => {
|
|
36
36
|
const id = (0, react_1.useId)();
|
|
37
37
|
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
38
|
+
const [wrapperHeight, setWrapperHeight] = (0, react_1.useState)(undefined);
|
|
38
39
|
const { scale, scaleAnchor } = (0, useItemScale_1.useItemScale)(item, sectionId);
|
|
39
|
-
const
|
|
40
|
+
const { top, left } = (0, useItemPosition_1.useItemPosition)(item, sectionId);
|
|
41
|
+
const sectionHeight = (0, useSectionHeightMap_1.useSectionHeightData)(sectionId);
|
|
40
42
|
const layout = (0, useCurrentLayout_1.useCurrentLayout)();
|
|
41
|
-
const { top, isFixed } = (0, useItemSticky_1.useItemSticky)(position.top, item, sectionId);
|
|
42
43
|
const { width, height } = (0, useItemDimensions_1.useItemDimensions)(item, sectionId);
|
|
43
44
|
const layoutValues = [item.area];
|
|
44
45
|
const isInitialRef = (0, react_1.useRef)(true);
|
|
46
|
+
layoutValues.push(item.sticky);
|
|
47
|
+
layoutValues.push(sectionHeight);
|
|
45
48
|
if (item.layoutParams) {
|
|
46
49
|
layoutValues.push(item.layoutParams);
|
|
47
50
|
}
|
|
@@ -50,37 +53,65 @@ const Item = ({ item, sectionId }) => {
|
|
|
50
53
|
: undefined;
|
|
51
54
|
const sizingAxis = parseSizing(sizing);
|
|
52
55
|
const ItemComponent = itemsMap[item.type] || noop;
|
|
56
|
+
const handleItemResize = (height) => {
|
|
57
|
+
const sticky = item.sticky[layout];
|
|
58
|
+
if (!sticky) {
|
|
59
|
+
setWrapperHeight(undefined);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const wrapperHeight = getStickyItemWrapperHeight(sticky, height);
|
|
63
|
+
setWrapperHeight(wrapperHeight);
|
|
64
|
+
};
|
|
53
65
|
(0, react_1.useEffect)(() => {
|
|
54
66
|
isInitialRef.current = false;
|
|
55
67
|
}, []);
|
|
56
68
|
const styles = {
|
|
57
69
|
transform: `scale(${scale})`,
|
|
58
70
|
transformOrigin: ScaleAnchorMap_1.ScaleAnchorMap[scaleAnchor],
|
|
59
|
-
left: `${position.left * 100}vw`,
|
|
60
71
|
width: `${sizingAxis.x === sdk_1.ArticleItemSizingType.Manual ? `${width * 100}vw` : 'max-content'}`,
|
|
61
72
|
height: `${sizingAxis.y === sdk_1.ArticleItemSizingType.Manual ? `${height * 100}vw` : 'unset'}`,
|
|
62
|
-
top
|
|
63
73
|
};
|
|
64
|
-
return ((0, jsx_runtime_1.jsxs)("div", {
|
|
65
|
-
${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([area, layoutParams]) => {
|
|
74
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `item-wrapper-${item.id}`, style: { top, left, ...(wrapperHeight ? { height: `${wrapperHeight * 100}vw` } : {}) }, children: [(0, jsx_runtime_1.jsx)("div", { suppressHydrationWarning: true, className: `item-${item.id}`, style: isInitialRef.current ? {} : styles, children: (0, jsx_runtime_1.jsx)(ItemComponent, { item: item, sectionId: sectionId, onResize: handleItemResize }) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
|
|
75
|
+
${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([area, sticky, sectionHeight, layoutParams]) => {
|
|
66
76
|
const sizingAxis = parseSizing(layoutParams.sizing);
|
|
67
77
|
return (`
|
|
68
78
|
.item-${item.id} {
|
|
69
|
-
position: absolute;
|
|
70
|
-
z-index: ${area.zIndex};
|
|
71
|
-
top: ${(0, useItemSticky_1.getItemTopStyle)(area.top, area.anchorSide)};
|
|
72
|
-
left: ${area.left * 100}vw;
|
|
79
|
+
position: ${sticky ? 'sticky' : 'absolute'};
|
|
73
80
|
width: ${sizingAxis.x === sdk_1.ArticleItemSizingType.Manual ? `${area.width * 100}vw` : 'max-content'};
|
|
74
|
-
height: ${sizingAxis.y === sdk_1.ArticleItemSizingType.Manual ?
|
|
75
|
-
z-index: ${area.zIndex};
|
|
81
|
+
height: ${sizingAxis.y === sdk_1.ArticleItemSizingType.Manual ? `w${area.height * 100}vw` : 'unset'};
|
|
76
82
|
transform: scale(${scale});
|
|
83
|
+
top: ${sticky ? `${getAnchoredItemTop(area.top - sticky.from, sectionHeight, area.anchorSide)}` : 0};
|
|
77
84
|
transform-origin: ${ScaleAnchorMap_1.ScaleAnchorMap[scaleAnchor]};
|
|
85
|
+
pointer-events: auto;
|
|
86
|
+
--webkit-backface-visibility: hidden;
|
|
87
|
+
}
|
|
88
|
+
.item-wrapper-${item.id} {
|
|
89
|
+
position: absolute;
|
|
90
|
+
z-index: ${area.zIndex};
|
|
91
|
+
pointer-events: none;
|
|
92
|
+
top: ${(0, useItemPosition_1.getItemTopStyle)(area.top, area.anchorSide)};
|
|
93
|
+
left: ${area.left * 100}vw;
|
|
94
|
+
height: ${sticky ? `${getStickyItemWrapperHeight(sticky, area.height) * 100}vw` : 'unset'};
|
|
78
95
|
}
|
|
79
96
|
`);
|
|
80
97
|
})}
|
|
81
98
|
` })] }));
|
|
82
99
|
};
|
|
83
100
|
exports.Item = Item;
|
|
101
|
+
function getAnchoredItemTop(top, sectionHeight, anchorSide) {
|
|
102
|
+
const styleTop = `${top * 100}vw`;
|
|
103
|
+
switch (anchorSide) {
|
|
104
|
+
case sdk_1.AnchorSide.Center: return `calc(${styleTop} + ${sectionHeight} / 2)`;
|
|
105
|
+
case sdk_1.AnchorSide.Bottom: return `calc(${styleTop} + ${sectionHeight})`;
|
|
106
|
+
case sdk_1.AnchorSide.Top:
|
|
107
|
+
default:
|
|
108
|
+
return styleTop;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function getStickyItemWrapperHeight(sticky, itemHeight) {
|
|
112
|
+
const end = sticky.to ?? 100;
|
|
113
|
+
return end - sticky.from + itemHeight;
|
|
114
|
+
}
|
|
84
115
|
function parseSizing(sizing = 'manual') {
|
|
85
116
|
const axisSizing = sizing.split(' ');
|
|
86
117
|
return {
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.Section = void 0;
|
|
6
|
+
exports.getSectionHeight = exports.Section = void 0;
|
|
7
7
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
8
|
const react_1 = require("react");
|
|
9
9
|
const style_1 = __importDefault(require("styled-jsx/style"));
|
|
@@ -54,3 +54,4 @@ function getSectionHeight(heightData) {
|
|
|
54
54
|
return `${units * 100}vw`;
|
|
55
55
|
return '0';
|
|
56
56
|
}
|
|
57
|
+
exports.getSectionHeight = getSectionHeight;
|
|
@@ -5,14 +5,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.RichTextItem = void 0;
|
|
7
7
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const react_1 = require("react");
|
|
8
9
|
const style_1 = __importDefault(require("styled-jsx/style"));
|
|
9
10
|
const useRichTextItem_1 = require("./useRichTextItem");
|
|
10
11
|
const useItemAngle_1 = require("../useItemAngle");
|
|
11
|
-
const RichTextItem = ({ item, sectionId }) => {
|
|
12
|
+
const RichTextItem = ({ item, sectionId, onResize }) => {
|
|
12
13
|
const [content, styles, preset] = (0, useRichTextItem_1.useRichTextItem)(item);
|
|
14
|
+
const [ref, setRef] = (0, react_1.useState)(null);
|
|
13
15
|
const angle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
|
|
14
16
|
const className = preset ? `cntrl-preset-${preset.id}` : undefined;
|
|
15
|
-
|
|
17
|
+
(0, react_1.useEffect)(() => {
|
|
18
|
+
if (!ref || !onResize)
|
|
19
|
+
return;
|
|
20
|
+
const observer = new ResizeObserver((entries) => {
|
|
21
|
+
const [entry] = entries;
|
|
22
|
+
onResize(entry.target.getBoundingClientRect().height / window.innerWidth);
|
|
23
|
+
});
|
|
24
|
+
observer.observe(ref);
|
|
25
|
+
return () => {
|
|
26
|
+
observer.unobserve(ref);
|
|
27
|
+
};
|
|
28
|
+
}, [ref, onResize]);
|
|
29
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { ref: setRef, className: className, style: {
|
|
16
30
|
transform: `rotate(${angle}deg)`
|
|
17
31
|
}, children: content }), (0, jsx_runtime_1.jsx)(style_1.default, { id: item.id, children: styles })] }));
|
|
18
32
|
};
|
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useItemPosition = void 0;
|
|
3
|
+
exports.getItemTopStyle = exports.useItemPosition = void 0;
|
|
4
|
+
const sdk_1 = require("@cntrl-site/sdk");
|
|
4
5
|
const useKeyframeValue_1 = require("../common/useKeyframeValue");
|
|
5
6
|
const useCurrentLayout_1 = require("../common/useCurrentLayout");
|
|
6
7
|
const useItemPosition = (item, sectionId) => {
|
|
7
8
|
const layoutId = (0, useCurrentLayout_1.useCurrentLayout)();
|
|
8
9
|
const { top, left } = (0, useKeyframeValue_1.useKeyframeValue)(item, (item, layoutId) => item.area[layoutId], (animator, scroll, value) => animator.getPositions(value, scroll), sectionId, [layoutId]);
|
|
9
|
-
return {
|
|
10
|
+
return {
|
|
11
|
+
top: getItemTopStyle(top, item.area[layoutId].anchorSide),
|
|
12
|
+
left: `${left * 100}vw`
|
|
13
|
+
};
|
|
10
14
|
};
|
|
11
15
|
exports.useItemPosition = useItemPosition;
|
|
16
|
+
function getItemTopStyle(top, anchorSide) {
|
|
17
|
+
const defaultValue = `${top * 100}vw`;
|
|
18
|
+
if (!anchorSide)
|
|
19
|
+
return defaultValue;
|
|
20
|
+
switch (anchorSide) {
|
|
21
|
+
case sdk_1.AnchorSide.Top:
|
|
22
|
+
return defaultValue;
|
|
23
|
+
case sdk_1.AnchorSide.Center:
|
|
24
|
+
return `calc(50% + ${top * 100}vw)`;
|
|
25
|
+
case sdk_1.AnchorSide.Bottom:
|
|
26
|
+
return `calc(100% + ${top * 100}vw)`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.getItemTopStyle = getItemTopStyle;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSectionHeightMap = exports.useSectionHeightData = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const CntrlContext_1 = require("../provider/CntrlContext");
|
|
6
|
+
const Section_1 = require("./Section");
|
|
7
|
+
const useSectionHeightData = (sectionId) => {
|
|
8
|
+
const sectionHeightContext = (0, react_1.useContext)(CntrlContext_1.CntrlContext);
|
|
9
|
+
const layouts = sectionHeightContext.layouts;
|
|
10
|
+
const sectionHeightData = sectionHeightContext.getSectionHeightData(sectionId);
|
|
11
|
+
return sectionHeightData ? getSectionHeightMap(sectionHeightData) : getDefaultHeightData(layouts);
|
|
12
|
+
};
|
|
13
|
+
exports.useSectionHeightData = useSectionHeightData;
|
|
14
|
+
function getSectionHeightMap(sectionHeight) {
|
|
15
|
+
return Object.fromEntries(Object.entries(sectionHeight).map(([sectionId, heightData]) => [sectionId, (0, Section_1.getSectionHeight)(heightData)]));
|
|
16
|
+
}
|
|
17
|
+
exports.getSectionHeightMap = getSectionHeightMap;
|
|
18
|
+
function getDefaultHeightData(layouts) {
|
|
19
|
+
return layouts.reduce((acc, layout) => ({ ...acc, [layout.id]: '0' }), {});
|
|
20
|
+
}
|
|
@@ -6,6 +6,7 @@ class CntrlSdkContext {
|
|
|
6
6
|
this.customItems = customItems;
|
|
7
7
|
this.customSections = customSections;
|
|
8
8
|
this._layouts = [];
|
|
9
|
+
this.sectionHeightMap = new Map();
|
|
9
10
|
}
|
|
10
11
|
async resolveSectionData(sections) {
|
|
11
12
|
const resolvers = sections.map(section => {
|
|
@@ -19,12 +20,26 @@ class CntrlSdkContext {
|
|
|
19
20
|
}).filter(isDefined);
|
|
20
21
|
return Object.fromEntries(await Promise.all(resolvers.map(async ({ name, resolver }) => [name, await resolver()])));
|
|
21
22
|
}
|
|
23
|
+
init({ project, typePresets, article }) {
|
|
24
|
+
this.setTypePresets(typePresets);
|
|
25
|
+
this.setLayouts(project.layouts);
|
|
26
|
+
this.setSectionsHeight(article.sections);
|
|
27
|
+
}
|
|
22
28
|
setTypePresets(typePresets) {
|
|
23
29
|
this._typePresets = typePresets;
|
|
24
30
|
}
|
|
25
31
|
setLayouts(layouts) {
|
|
26
32
|
this._layouts = layouts;
|
|
27
33
|
}
|
|
34
|
+
setSectionsHeight(sections) {
|
|
35
|
+
for (const section of sections) {
|
|
36
|
+
this.sectionHeightMap.set(section.id, section.height);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
getSectionHeightData(sectionId) {
|
|
40
|
+
const sectionHeightData = this.sectionHeightMap.get(sectionId);
|
|
41
|
+
return sectionHeightData;
|
|
42
|
+
}
|
|
28
43
|
get layouts() {
|
|
29
44
|
return this._layouts;
|
|
30
45
|
}
|
package/package.json
CHANGED
package/src/components/Item.tsx
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { ComponentType, FC, useEffect, useId, useRef } from 'react';
|
|
1
|
+
import { ComponentType, FC, useEffect, useId, useRef, useState } from 'react';
|
|
2
2
|
import JSXStyle from 'styled-jsx/style';
|
|
3
3
|
import {
|
|
4
|
+
AnchorSide,
|
|
4
5
|
ArticleItemSizingType as SizingType,
|
|
5
6
|
ArticleItemType,
|
|
6
7
|
getLayoutStyles,
|
|
7
8
|
TArticleItem,
|
|
8
|
-
|
|
9
|
+
TStickyParams,
|
|
10
|
+
TArticleItemAny,
|
|
11
|
+
|
|
9
12
|
} from '@cntrl-site/sdk';
|
|
10
13
|
import { RectangleItem } from './items/RectangleItem';
|
|
11
14
|
import { ImageItem } from './items/ImageItem';
|
|
@@ -15,16 +18,17 @@ import { VimeoEmbedItem } from './items/VimeoEmbed';
|
|
|
15
18
|
import { YoutubeEmbedItem } from './items/YoutubeEmbed';
|
|
16
19
|
import { CustomItem } from './items/CustomItem';
|
|
17
20
|
import { useCntrlContext } from '../provider/useCntrlContext';
|
|
18
|
-
import { useItemPosition } from './useItemPosition';
|
|
21
|
+
import { getItemTopStyle, useItemPosition } from './useItemPosition';
|
|
19
22
|
import { useItemDimensions } from './useItemDimensions';
|
|
20
|
-
import { getItemTopStyle, useItemSticky } from './items/useItemSticky';
|
|
21
23
|
import { useCurrentLayout } from '../common/useCurrentLayout';
|
|
22
24
|
import { useItemScale } from './useItemScale';
|
|
23
25
|
import { ScaleAnchorMap } from '../utils/ScaleAnchorMap';
|
|
26
|
+
import { useSectionHeightData } from './useSectionHeightMap';
|
|
24
27
|
|
|
25
28
|
export interface ItemProps<I extends TArticleItemAny> {
|
|
26
29
|
item: I;
|
|
27
30
|
sectionId: string;
|
|
31
|
+
onResize?: (height: number) => void;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
const itemsMap: Record<ArticleItemType, ComponentType<ItemProps<any>>> = {
|
|
@@ -42,13 +46,16 @@ const noop = () => null;
|
|
|
42
46
|
export const Item: FC<ItemProps<TArticleItemAny>> = ({ item, sectionId}) => {
|
|
43
47
|
const id = useId();
|
|
44
48
|
const { layouts } = useCntrlContext();
|
|
49
|
+
const [wrapperHeight, setWrapperHeight] = useState<undefined | number>(undefined);
|
|
45
50
|
const { scale, scaleAnchor } = useItemScale(item, sectionId);
|
|
46
|
-
const
|
|
51
|
+
const { top, left } = useItemPosition(item, sectionId);
|
|
52
|
+
const sectionHeight = useSectionHeightData(sectionId);
|
|
47
53
|
const layout = useCurrentLayout();
|
|
48
|
-
const { top, isFixed } = useItemSticky(position.top, item, sectionId);
|
|
49
54
|
const { width, height } = useItemDimensions(item, sectionId);
|
|
50
55
|
const layoutValues: Record<string, any>[] = [item.area];
|
|
51
56
|
const isInitialRef = useRef(true);
|
|
57
|
+
layoutValues.push(item.sticky);
|
|
58
|
+
layoutValues.push(sectionHeight);
|
|
52
59
|
if (item.layoutParams) {
|
|
53
60
|
layoutValues.push(item.layoutParams);
|
|
54
61
|
}
|
|
@@ -59,6 +66,16 @@ export const Item: FC<ItemProps<TArticleItemAny>> = ({ item, sectionId}) => {
|
|
|
59
66
|
const sizingAxis = parseSizing(sizing);
|
|
60
67
|
const ItemComponent = itemsMap[item.type] || noop;
|
|
61
68
|
|
|
69
|
+
const handleItemResize = (height: number) => {
|
|
70
|
+
const sticky = item.sticky[layout];
|
|
71
|
+
if (!sticky) {
|
|
72
|
+
setWrapperHeight(undefined);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const wrapperHeight = getStickyItemWrapperHeight(sticky, height)
|
|
76
|
+
setWrapperHeight(wrapperHeight);
|
|
77
|
+
};
|
|
78
|
+
|
|
62
79
|
useEffect(() => {
|
|
63
80
|
isInitialRef.current = false;
|
|
64
81
|
}, []);
|
|
@@ -66,41 +83,66 @@ export const Item: FC<ItemProps<TArticleItemAny>> = ({ item, sectionId}) => {
|
|
|
66
83
|
const styles = {
|
|
67
84
|
transform: `scale(${scale})`,
|
|
68
85
|
transformOrigin: ScaleAnchorMap[scaleAnchor],
|
|
69
|
-
left: `${position.left * 100}vw`,
|
|
70
86
|
width: `${sizingAxis.x === SizingType.Manual ? `${width * 100}vw` : 'max-content'}`,
|
|
71
87
|
height: `${sizingAxis.y === SizingType.Manual ? `${height * 100}vw` : 'unset'}`,
|
|
72
|
-
top
|
|
73
88
|
};
|
|
74
89
|
|
|
75
90
|
return (
|
|
76
|
-
<div
|
|
77
|
-
|
|
78
|
-
className={`item-${item.id}`}
|
|
79
|
-
style={isInitialRef.current ? {} : { ...styles, position: isFixed ? 'fixed': 'absolute' } }
|
|
91
|
+
<div className={`item-wrapper-${item.id}`}
|
|
92
|
+
style={{ top, left, ...(wrapperHeight ? { height: `${wrapperHeight * 100}vw` } : {}) }}
|
|
80
93
|
>
|
|
81
|
-
<
|
|
94
|
+
<div
|
|
95
|
+
suppressHydrationWarning={true}
|
|
96
|
+
className={`item-${item.id}`}
|
|
97
|
+
style={isInitialRef.current ? {} : styles }
|
|
98
|
+
>
|
|
99
|
+
<ItemComponent item={item} sectionId={sectionId} onResize={handleItemResize} />
|
|
100
|
+
</div>
|
|
82
101
|
<JSXStyle id={id}>{`
|
|
83
|
-
${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
|
|
84
|
-
|
|
85
|
-
|
|
102
|
+
${getLayoutStyles(layouts, layoutValues, ([area, sticky, sectionHeight, layoutParams]) => {
|
|
103
|
+
const sizingAxis = parseSizing(layoutParams.sizing);
|
|
104
|
+
return (`
|
|
86
105
|
.item-${item.id} {
|
|
106
|
+
position: ${sticky ? 'sticky' : 'absolute'};
|
|
107
|
+
width: ${sizingAxis.x === SizingType.Manual ? `${area.width * 100}vw` : 'max-content'};
|
|
108
|
+
height: ${sizingAxis.y === SizingType.Manual ? `w${area.height * 100}vw` : 'unset'};
|
|
109
|
+
transform: scale(${scale});
|
|
110
|
+
top: ${sticky ? `${getAnchoredItemTop(area.top - sticky.from, sectionHeight, area.anchorSide)}` : 0};
|
|
111
|
+
transform-origin: ${ScaleAnchorMap[scaleAnchor]};
|
|
112
|
+
pointer-events: auto;
|
|
113
|
+
--webkit-backface-visibility: hidden;
|
|
114
|
+
}
|
|
115
|
+
.item-wrapper-${item.id} {
|
|
87
116
|
position: absolute;
|
|
88
117
|
z-index: ${area.zIndex};
|
|
118
|
+
pointer-events: none;
|
|
89
119
|
top: ${getItemTopStyle(area.top, area.anchorSide)};
|
|
90
120
|
left: ${area.left * 100}vw;
|
|
91
|
-
|
|
92
|
-
height: ${sizingAxis.y === SizingType.Manual ? `${area.height * 100}vw` : 'unset'};
|
|
93
|
-
z-index: ${area.zIndex};
|
|
94
|
-
transform: scale(${scale});
|
|
95
|
-
transform-origin: ${ScaleAnchorMap[scaleAnchor]};
|
|
121
|
+
height: ${sticky ? `${getStickyItemWrapperHeight(sticky, area.height) * 100}vw` : 'unset'};
|
|
96
122
|
}
|
|
97
123
|
`);
|
|
98
|
-
|
|
124
|
+
})}
|
|
99
125
|
`}</JSXStyle>
|
|
100
126
|
</div>
|
|
101
127
|
);
|
|
102
128
|
};
|
|
103
129
|
|
|
130
|
+
function getAnchoredItemTop(top: number, sectionHeight: string, anchorSide: AnchorSide) {
|
|
131
|
+
const styleTop = `${top * 100}vw`;
|
|
132
|
+
switch (anchorSide) {
|
|
133
|
+
case AnchorSide.Center: return `calc(${styleTop} + ${sectionHeight} / 2)`;
|
|
134
|
+
case AnchorSide.Bottom: return `calc(${styleTop} + ${sectionHeight})`;
|
|
135
|
+
case AnchorSide.Top:
|
|
136
|
+
default:
|
|
137
|
+
return styleTop;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getStickyItemWrapperHeight(sticky: TStickyParams, itemHeight: number): number {
|
|
142
|
+
const end = sticky.to ?? 100;
|
|
143
|
+
return end - sticky.from + itemHeight;
|
|
144
|
+
}
|
|
145
|
+
|
|
104
146
|
function parseSizing(sizing: string = 'manual'): Axis {
|
|
105
147
|
const axisSizing = sizing.split(' ');
|
|
106
148
|
return {
|
|
@@ -70,7 +70,7 @@ export const Section: FC<Props> = ({ section, data, children }) => {
|
|
|
70
70
|
);
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
function getSectionHeight(heightData: TSectionHeight): string {
|
|
73
|
+
export function getSectionHeight(heightData: TSectionHeight): string {
|
|
74
74
|
const { units, vhUnits, mode } = heightData;
|
|
75
75
|
if (mode === SectionHeightMode.ViewportHeightUnits) return `${vhUnits}vh`;
|
|
76
76
|
if (mode === SectionHeightMode.ControlUnits) return `${units * 100}vw`;
|
|
@@ -1,17 +1,32 @@
|
|
|
1
|
-
import { FC } from 'react';
|
|
1
|
+
import { FC, useEffect, useState } from 'react';
|
|
2
2
|
import { TRichTextItem } from '@cntrl-site/sdk';
|
|
3
3
|
import JSXStyle from 'styled-jsx/style';
|
|
4
4
|
import { ItemProps } from '../Item';
|
|
5
5
|
import { useRichTextItem } from './useRichTextItem';
|
|
6
6
|
import { useItemAngle } from '../useItemAngle';
|
|
7
7
|
|
|
8
|
-
export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId }) => {
|
|
8
|
+
export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, onResize }) => {
|
|
9
9
|
const [content, styles, preset] = useRichTextItem(item);
|
|
10
|
+
const [ref, setRef] = useState<HTMLDivElement | null>(null);
|
|
10
11
|
const angle = useItemAngle(item, sectionId);
|
|
11
12
|
const className = preset ? `cntrl-preset-${preset.id}` : undefined;
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!ref || !onResize) return;
|
|
16
|
+
const observer = new ResizeObserver((entries) => {
|
|
17
|
+
const [entry] = entries;
|
|
18
|
+
onResize(entry.target.getBoundingClientRect().height / window.innerWidth);
|
|
19
|
+
});
|
|
20
|
+
observer.observe(ref);
|
|
21
|
+
return () => {
|
|
22
|
+
observer.unobserve(ref);
|
|
23
|
+
};
|
|
24
|
+
}, [ref, onResize]);
|
|
25
|
+
|
|
12
26
|
return (
|
|
13
27
|
<>
|
|
14
28
|
<div
|
|
29
|
+
ref={setRef}
|
|
15
30
|
className={className}
|
|
16
31
|
style={{
|
|
17
32
|
transform: `rotate(${angle}deg)`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TArticleItemAny } from '@cntrl-site/sdk';
|
|
1
|
+
import { AnchorSide, TArticleItemAny } from '@cntrl-site/sdk';
|
|
2
2
|
import { useKeyframeValue } from '../common/useKeyframeValue';
|
|
3
3
|
import { useCurrentLayout } from '../common/useCurrentLayout';
|
|
4
4
|
|
|
@@ -11,6 +11,23 @@ export const useItemPosition = (item: TArticleItemAny, sectionId: string) => {
|
|
|
11
11
|
sectionId,
|
|
12
12
|
[layoutId]
|
|
13
13
|
);
|
|
14
|
-
return {
|
|
14
|
+
return {
|
|
15
|
+
top: getItemTopStyle(top, item.area[layoutId].anchorSide),
|
|
16
|
+
left: `${left * 100}vw`
|
|
17
|
+
};
|
|
15
18
|
};
|
|
16
19
|
|
|
20
|
+
export function getItemTopStyle(top: number, anchorSide?: AnchorSide) {
|
|
21
|
+
const defaultValue = `${top * 100}vw`;
|
|
22
|
+
if (!anchorSide) return defaultValue;
|
|
23
|
+
switch (anchorSide) {
|
|
24
|
+
case AnchorSide.Top:
|
|
25
|
+
return defaultValue;
|
|
26
|
+
case AnchorSide.Center:
|
|
27
|
+
return `calc(50% + ${top * 100}vw)`;
|
|
28
|
+
case AnchorSide.Bottom:
|
|
29
|
+
return `calc(100% + ${top * 100}vw)`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { CntrlContext } from '../provider/CntrlContext';
|
|
3
|
+
import { TLayout, TSectionHeight } from '@cntrl-site/sdk';
|
|
4
|
+
import { getSectionHeight } from './Section';
|
|
5
|
+
|
|
6
|
+
export const useSectionHeightData = (sectionId: string): Record<string, string> => {
|
|
7
|
+
const sectionHeightContext = useContext(CntrlContext);
|
|
8
|
+
const layouts = sectionHeightContext.layouts;
|
|
9
|
+
const sectionHeightData = sectionHeightContext.getSectionHeightData(sectionId);
|
|
10
|
+
return sectionHeightData ? getSectionHeightMap(sectionHeightData) : getDefaultHeightData(layouts);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function getSectionHeightMap(sectionHeight: Record<string, TSectionHeight>): Record<string, string> {
|
|
14
|
+
return Object.fromEntries(Object.entries(sectionHeight).map(([sectionId, heightData]) => [sectionId, getSectionHeight(heightData)]));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getDefaultHeightData(layouts: TLayout[]) {
|
|
18
|
+
return layouts.reduce((acc, layout) => ({...acc, [layout.id]: '0'}), {});
|
|
19
|
+
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { CustomItemRegistry } from './CustomItemRegistry';
|
|
2
|
-
import { TArticleSection, TLayout, TTypePresets } from '@cntrl-site/sdk';
|
|
2
|
+
import { TArticle, TArticleSection, TLayout, TProject, TSectionHeight, TTypePresets } from '@cntrl-site/sdk';
|
|
3
3
|
import { CustomSectionRegistry } from './CustomSectionRegistry';
|
|
4
4
|
|
|
5
|
+
interface SdkContextInitProps {
|
|
6
|
+
typePresets: TTypePresets;
|
|
7
|
+
project: TProject;
|
|
8
|
+
article: TArticle;
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
export class CntrlSdkContext {
|
|
6
12
|
private _typePresets?: TTypePresets;
|
|
7
13
|
private _layouts: TLayout[] = [];
|
|
14
|
+
private sectionHeightMap: Map<string, Record<string, TSectionHeight>> = new Map();
|
|
8
15
|
constructor(
|
|
9
16
|
public readonly customItems: CustomItemRegistry,
|
|
10
17
|
public readonly customSections: CustomSectionRegistry
|
|
@@ -24,6 +31,12 @@ export class CntrlSdkContext {
|
|
|
24
31
|
);
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
init({ project, typePresets, article }: SdkContextInitProps) {
|
|
35
|
+
this.setTypePresets(typePresets);
|
|
36
|
+
this.setLayouts(project.layouts);
|
|
37
|
+
this.setSectionsHeight(article.sections);
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
setTypePresets(typePresets: TTypePresets) {
|
|
28
41
|
this._typePresets = typePresets;
|
|
29
42
|
}
|
|
@@ -32,6 +45,17 @@ export class CntrlSdkContext {
|
|
|
32
45
|
this._layouts = layouts;
|
|
33
46
|
}
|
|
34
47
|
|
|
48
|
+
setSectionsHeight(sections: TArticleSection[]) {
|
|
49
|
+
for (const section of sections) {
|
|
50
|
+
this.sectionHeightMap.set(section.id, section.height)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getSectionHeightData(sectionId: string) {
|
|
55
|
+
const sectionHeightData = this.sectionHeightMap.get(sectionId);
|
|
56
|
+
return sectionHeightData;
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
get layouts(): TLayout[] {
|
|
36
60
|
return this._layouts;
|
|
37
61
|
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getItemTopStyle = exports.useItemSticky = void 0;
|
|
4
|
-
const react_1 = require("react");
|
|
5
|
-
const sdk_1 = require("@cntrl-site/sdk");
|
|
6
|
-
const StickyManager_1 = require("../../utils/StickyManager/StickyManager");
|
|
7
|
-
const useCurrentLayout_1 = require("../../common/useCurrentLayout");
|
|
8
|
-
const ArticleRectContext_1 = require("../../provider/ArticleRectContext");
|
|
9
|
-
const useItemSticky = (top, item, sectionId) => {
|
|
10
|
-
const [isFixed, setIsFixed] = (0, react_1.useState)(false);
|
|
11
|
-
const [adjustedTop, setAdjustedTop] = (0, react_1.useState)(top);
|
|
12
|
-
const articleRectObserver = (0, react_1.useContext)(ArticleRectContext_1.ArticleRectContext);
|
|
13
|
-
const layoutId = (0, useCurrentLayout_1.useCurrentLayout)();
|
|
14
|
-
const sticky = (0, react_1.useMemo)(() => item.sticky[layoutId], [layoutId]);
|
|
15
|
-
const stickyManager = (0, react_1.useMemo)(() => new StickyManager_1.StickyManager(sticky), [sticky]);
|
|
16
|
-
const handleSticky = (0, react_1.useCallback)((scroll) => {
|
|
17
|
-
setIsFixed(stickyManager.getIsSticky(scroll));
|
|
18
|
-
setAdjustedTop(stickyManager.getPosition(scroll, top));
|
|
19
|
-
}, [top, stickyManager]);
|
|
20
|
-
(0, react_1.useEffect)(() => {
|
|
21
|
-
if (!articleRectObserver)
|
|
22
|
-
return;
|
|
23
|
-
handleSticky(articleRectObserver.getSectionScroll(sectionId));
|
|
24
|
-
}, [handleSticky, articleRectObserver]);
|
|
25
|
-
(0, react_1.useEffect)(() => {
|
|
26
|
-
if (!articleRectObserver || !sticky)
|
|
27
|
-
return;
|
|
28
|
-
return articleRectObserver.on('scroll', () => {
|
|
29
|
-
handleSticky(articleRectObserver.getSectionScroll(sectionId));
|
|
30
|
-
});
|
|
31
|
-
}, [handleSticky, articleRectObserver, sticky]);
|
|
32
|
-
return {
|
|
33
|
-
isFixed,
|
|
34
|
-
top: sticky ? `${adjustedTop * 100}vw` : getItemTopStyle(adjustedTop, layoutId ? item.area[layoutId].anchorSide : sdk_1.AnchorSide.Top)
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
exports.useItemSticky = useItemSticky;
|
|
38
|
-
function getItemTopStyle(top, anchorSide) {
|
|
39
|
-
const defaultValue = `${top * 100}vw`;
|
|
40
|
-
if (!anchorSide)
|
|
41
|
-
return defaultValue;
|
|
42
|
-
switch (anchorSide) {
|
|
43
|
-
case sdk_1.AnchorSide.Top:
|
|
44
|
-
return defaultValue;
|
|
45
|
-
case sdk_1.AnchorSide.Center:
|
|
46
|
-
return `calc(50% + ${top * 100}vw)`;
|
|
47
|
-
case sdk_1.AnchorSide.Bottom:
|
|
48
|
-
return `calc(100% + ${top * 100}vw)`;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
exports.getItemTopStyle = getItemTopStyle;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.StickyManager = void 0;
|
|
4
|
-
class StickyManager {
|
|
5
|
-
constructor(sticky) {
|
|
6
|
-
this.sticky = sticky;
|
|
7
|
-
}
|
|
8
|
-
getIsSticky(scroll) {
|
|
9
|
-
if (!this.sticky)
|
|
10
|
-
return false;
|
|
11
|
-
return (this.sticky.from <= scroll) && (!this.sticky.to || this.sticky.to >= scroll);
|
|
12
|
-
}
|
|
13
|
-
getPosition(scroll, top) {
|
|
14
|
-
if (!this.sticky)
|
|
15
|
-
return top;
|
|
16
|
-
if (this.getIsSticky(scroll)) {
|
|
17
|
-
return top - this.sticky.from;
|
|
18
|
-
}
|
|
19
|
-
if (this.sticky.to !== undefined && this.sticky.to <= scroll) {
|
|
20
|
-
return top + this.sticky.to - this.sticky.from;
|
|
21
|
-
}
|
|
22
|
-
return top;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
exports.StickyManager = StickyManager;
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { AnchorSide, TArticleItemAny } from '@cntrl-site/sdk';
|
|
3
|
-
import { StickyManager } from '../../utils/StickyManager/StickyManager';
|
|
4
|
-
import { useCurrentLayout } from '../../common/useCurrentLayout';
|
|
5
|
-
import { ArticleRectContext } from '../../provider/ArticleRectContext';
|
|
6
|
-
|
|
7
|
-
export const useItemSticky = (top: number, item: TArticleItemAny, sectionId: string) => {
|
|
8
|
-
const [isFixed, setIsFixed] = useState(false);
|
|
9
|
-
const [adjustedTop, setAdjustedTop] = useState(top);
|
|
10
|
-
const articleRectObserver = useContext(ArticleRectContext);
|
|
11
|
-
const layoutId = useCurrentLayout();
|
|
12
|
-
const sticky = useMemo(() => item.sticky[layoutId], [layoutId]);
|
|
13
|
-
const stickyManager = useMemo(() => new StickyManager(sticky), [sticky]);
|
|
14
|
-
|
|
15
|
-
const handleSticky = useCallback((scroll: number) => {
|
|
16
|
-
setIsFixed(stickyManager.getIsSticky(scroll));
|
|
17
|
-
setAdjustedTop(stickyManager.getPosition(
|
|
18
|
-
scroll,
|
|
19
|
-
top
|
|
20
|
-
));
|
|
21
|
-
}, [top, stickyManager]);
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
if (!articleRectObserver) return;
|
|
25
|
-
handleSticky(articleRectObserver.getSectionScroll(sectionId));
|
|
26
|
-
}, [handleSticky, articleRectObserver]);
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
if (!articleRectObserver || !sticky) return;
|
|
30
|
-
return articleRectObserver.on('scroll', () => {
|
|
31
|
-
handleSticky(articleRectObserver.getSectionScroll(sectionId));
|
|
32
|
-
});
|
|
33
|
-
}, [handleSticky, articleRectObserver, sticky]);
|
|
34
|
-
return {
|
|
35
|
-
isFixed,
|
|
36
|
-
top: sticky ? `${adjustedTop * 100}vw` : getItemTopStyle(adjustedTop, layoutId ? item.area[layoutId].anchorSide : AnchorSide.Top)
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export function getItemTopStyle(top: number, anchorSide?: AnchorSide) {
|
|
41
|
-
const defaultValue = `${top * 100}vw`;
|
|
42
|
-
if (!anchorSide) return defaultValue;
|
|
43
|
-
switch (anchorSide) {
|
|
44
|
-
case AnchorSide.Top:
|
|
45
|
-
return defaultValue;
|
|
46
|
-
case AnchorSide.Center:
|
|
47
|
-
return `calc(50% + ${top * 100}vw)`;
|
|
48
|
-
case AnchorSide.Bottom:
|
|
49
|
-
return `calc(100% + ${top * 100}vw)`;
|
|
50
|
-
}
|
|
51
|
-
}
|