@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.
@@ -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 position = (0, useItemPosition_1.useItemPosition)(item, sectionId);
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", { suppressHydrationWarning: true, className: `item-${item.id}`, style: isInitialRef.current ? {} : { ...styles, position: isFixed ? 'fixed' : 'absolute' }, children: [(0, jsx_runtime_1.jsx)(ItemComponent, { item: item, sectionId: sectionId }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
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 ? `${area.height * 100}vw` : 'unset'};
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
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: className, style: {
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 { top, left };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cntrl-site/sdk-nextjs",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "description": "SDK for Next.js",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/index.ts",
@@ -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
- TArticleItemAny
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 position = useItemPosition(item, sectionId);
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
- suppressHydrationWarning={true}
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
- <ItemComponent item={item} sectionId={sectionId} />
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
- const sizingAxis = parseSizing(layoutParams.sizing);
85
- return (`
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
- width: ${sizingAxis.x === SizingType.Manual ? `${area.width * 100}vw` : 'max-content'};
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 { top, left };
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
- }