@cntrl-site/sdk-nextjs 1.0.20 → 1.1.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 (47) hide show
  1. package/jest.config.js +2 -2
  2. package/lib/components/Article.js +9 -8
  3. package/lib/components/Item.js +39 -21
  4. package/lib/components/items/CodeEmbedItem.js +15 -12
  5. package/lib/components/items/CustomItem.js +7 -9
  6. package/lib/components/items/GroupItem.js +15 -12
  7. package/lib/components/items/ImageItem.js +28 -21
  8. package/lib/components/items/RectangleItem.js +28 -15
  9. package/lib/components/items/RichTextItem.js +33 -12
  10. package/lib/components/items/VideoItem.js +26 -19
  11. package/lib/components/items/VimeoEmbed.js +22 -20
  12. package/lib/components/items/YoutubeEmbed.js +20 -18
  13. package/lib/components/useItemPosition.js +10 -5
  14. package/lib/interactions/CSSPropertyNameMap.js +38 -0
  15. package/lib/interactions/InteractionsRegistry.js +220 -0
  16. package/lib/interactions/ItemInteractionCtrl.js +61 -0
  17. package/lib/interactions/getTransition.js +21 -0
  18. package/lib/interactions/types.js +2 -0
  19. package/lib/interactions/useItemInteractionCtrl.js +17 -0
  20. package/lib/provider/InteractionsContext.js +23 -0
  21. package/lib/utils/getStyleFromItemStateAndParams.js +9 -0
  22. package/package.json +7 -4
  23. package/src/components/Article.tsx +30 -27
  24. package/src/components/ArticleWrapper.tsx +1 -2
  25. package/src/components/Item.tsx +75 -29
  26. package/src/components/items/CodeEmbedItem.tsx +15 -11
  27. package/src/components/items/CustomItem.tsx +9 -11
  28. package/src/components/items/GroupItem.tsx +17 -13
  29. package/src/components/items/ImageItem.tsx +35 -20
  30. package/src/components/items/RectangleItem.tsx +35 -14
  31. package/src/components/items/RichTextItem.tsx +41 -14
  32. package/src/components/items/VideoItem.tsx +34 -20
  33. package/src/components/items/VimeoEmbed.tsx +27 -19
  34. package/src/components/items/YoutubeEmbed.tsx +20 -18
  35. package/src/components/useItemPosition.ts +12 -5
  36. package/src/interactions/CSSPropertyNameMap.ts +38 -0
  37. package/src/interactions/InteractionsRegistry.ts +244 -0
  38. package/src/interactions/ItemInteractionCtrl.ts +62 -0
  39. package/src/interactions/getTransition.ts +27 -0
  40. package/src/interactions/types.ts +32 -0
  41. package/src/interactions/useItemInteractionCtrl.ts +18 -0
  42. package/src/provider/InteractionsContext.old.tsx +65 -0
  43. package/src/provider/InteractionsContext.test.tsx +97 -0
  44. package/src/provider/InteractionsContext.tsx +28 -0
  45. package/src/utils/getStyleFromItemStateAndParams.ts +8 -0
  46. package/lib/utils/HoverStyles/HoverStyles.js +0 -77
  47. package/src/utils/HoverStyles/HoverStyles.ts +0 -85
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTransition = void 0;
4
+ const CSSPropertyNameMap_1 = require("./CSSPropertyNameMap");
5
+ function getTransition(state, keys) {
6
+ if (Object.keys(state).length === 0)
7
+ return 'none';
8
+ const transitions = [];
9
+ for (const [key, params] of Object.entries(state)) {
10
+ if (!keys.includes(key) || !params.transition)
11
+ continue;
12
+ const { transition: { duration, delay, timing } } = params;
13
+ const cssKey = CSSPropertyNameMap_1.CSSPropertyNameMap[key];
14
+ const nonZeroDuration = Math.max(duration, 0.01);
15
+ transitions.push(`${cssKey} ${nonZeroDuration}ms ${timing} ${delay}ms`);
16
+ }
17
+ return transitions.length > 0
18
+ ? transitions.join(', ')
19
+ : 'none';
20
+ }
21
+ exports.getTransition = getTransition;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useItemInteractionCtrl = void 0;
4
+ const react_1 = require("react");
5
+ const ItemInteractionCtrl_1 = require("./ItemInteractionCtrl");
6
+ const InteractionsContext_1 = require("../provider/InteractionsContext");
7
+ function useItemInteractionCtrl(itemId) {
8
+ const [_, triggerRender] = (0, react_1.useState)(0);
9
+ const registry = (0, InteractionsContext_1.useInteractionsRegistry)();
10
+ const ctrl = (0, react_1.useMemo)(() => {
11
+ if (!registry)
12
+ return;
13
+ return new ItemInteractionCtrl_1.ItemInteractionController(itemId, registry, () => triggerRender(prev => prev + 1));
14
+ }, [itemId, registry]);
15
+ return ctrl;
16
+ }
17
+ exports.useItemInteractionCtrl = useItemInteractionCtrl;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useInteractionsRegistry = exports.InteractionsProvider = exports.InteractionsContext = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const InteractionsRegistry_1 = require("../interactions/InteractionsRegistry");
7
+ const useCurrentLayout_1 = require("../common/useCurrentLayout");
8
+ exports.InteractionsContext = (0, react_1.createContext)(undefined);
9
+ const InteractionsProvider = ({ article, children }) => {
10
+ const { layoutId } = (0, useCurrentLayout_1.useCurrentLayout)();
11
+ const registry = (0, react_1.useMemo)(() => {
12
+ if (!layoutId)
13
+ return;
14
+ return new InteractionsRegistry_1.InteractionsRegistry(article, layoutId);
15
+ }, [layoutId]);
16
+ return ((0, jsx_runtime_1.jsx)(exports.InteractionsContext.Provider, { value: registry, children: children }));
17
+ };
18
+ exports.InteractionsProvider = InteractionsProvider;
19
+ function useInteractionsRegistry() {
20
+ const registry = (0, react_1.useContext)(exports.InteractionsContext);
21
+ return registry;
22
+ }
23
+ exports.useInteractionsRegistry = useInteractionsRegistry;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getStyleFromItemStateAndParams = void 0;
4
+ function getStyleFromItemStateAndParams(stateValue, paramsValue) {
5
+ return stateValue !== undefined
6
+ ? stateValue
7
+ : paramsValue;
8
+ }
9
+ exports.getStyleFromItemStateAndParams = getStyleFromItemStateAndParams;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cntrl-site/sdk-nextjs",
3
- "version": "1.0.20",
3
+ "version": "1.1.1",
4
4
  "description": "SDK for Next.js",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/index.ts",
@@ -23,7 +23,7 @@
23
23
  "dependencies": {
24
24
  "@cntrl-site/color": "^1.0.0",
25
25
  "@cntrl-site/effects": "^1.2.0",
26
- "@cntrl-site/sdk": "^1.12.3",
26
+ "@cntrl-site/sdk": "^1.13.0",
27
27
  "@types/vimeo__player": "^2.18.0",
28
28
  "@vimeo/player": "^2.20.1",
29
29
  "html-react-parser": "^3.0.1",
@@ -38,12 +38,15 @@
38
38
  "react-dom": "^18.3.1"
39
39
  },
40
40
  "devDependencies": {
41
+ "@testing-library/jest-dom": "^6.4.8",
42
+ "@testing-library/react": "^16.0.0",
41
43
  "@tsconfig/node16": "^1.0.3",
42
44
  "@tsconfig/recommended": "^1.0.1",
43
- "@types/jest": "^29.2.5",
45
+ "@types/jest": "^29.5.12",
44
46
  "@types/node": "^18.11.7",
45
47
  "@types/react": "^18.0.15",
46
- "jest": "^29.3.1",
48
+ "jest": "^29.7.0",
49
+ "jest-environment-jsdom": "^29.7.0",
47
50
  "ts-jest": "^29.0.3",
48
51
  "typescript": "^5.2.2"
49
52
  },
@@ -6,6 +6,7 @@ import { Item } from './Item';
6
6
  import { useArticleRectObserver } from '../utils/ArticleRectManager/useArticleRectObserver';
7
7
  import { ArticleRectContext } from '../provider/ArticleRectContext';
8
8
  import { ArticleWrapper } from './ArticleWrapper';
9
+ import { InteractionsProvider } from '../provider/InteractionsContext';
9
10
 
10
11
  interface Props {
11
12
  article: TArticle;
@@ -27,35 +28,37 @@ export const Article: FC<Props> = ({ article, sectionData }) => {
27
28
 
28
29
  return (
29
30
  <ArticleRectContext.Provider value={articleRectObserver}>
30
- <ArticleWrapper>
31
- <div className="article" ref={articleRef}>
32
- {article.sections.map((section, i) => {
33
- const data = section.name ? sectionData[section.name] : {};
34
- return (
35
- <Section
36
- section={section}
37
- key={section.id}
38
- data={data}
39
- >
40
- {article.sections[i].items.map(item => (
41
- <Item
42
- item={item}
43
- key={item.id}
44
- sectionId={section.id}
45
- articleHeight={articleHeight}
46
- />
47
- ))}
48
- </Section>
49
- );
50
- })}
51
- </div>
52
- </ArticleWrapper>
53
- <JSXStyle id={id}>{`
31
+ <InteractionsProvider article={article}>
32
+ <ArticleWrapper>
33
+ <div className="article" ref={articleRef}>
34
+ {article.sections.map((section, i) => {
35
+ const data = section.name ? sectionData[section.name] : {};
36
+ return (
37
+ <Section
38
+ section={section}
39
+ key={section.id}
40
+ data={data}
41
+ >
42
+ {article.sections[i].items.map(item => (
43
+ <Item
44
+ item={item}
45
+ key={item.id}
46
+ sectionId={section.id}
47
+ articleHeight={articleHeight}
48
+ />
49
+ ))}
50
+ </Section>
51
+ );
52
+ })}
53
+ </div>
54
+ </ArticleWrapper>
55
+ <JSXStyle id={id}>{`
54
56
  .article {
55
- position: relative;
56
- overflow: clip;
57
- }
57
+ position: relative;
58
+ overflow: clip;
59
+ }
58
60
  `}</JSXStyle>
61
+ </InteractionsProvider>
59
62
  </ArticleRectContext.Provider>
60
63
  );
61
64
  };
@@ -1,5 +1,4 @@
1
- import React, { CSSProperties, FC, PropsWithChildren, useEffect, useId, useState } from 'react';
2
- import JSXStyle from 'styled-jsx/style';
1
+ import React, { CSSProperties, FC, PropsWithChildren } from 'react';
3
2
  import { useCurrentLayout } from '../common/useCurrentLayout';
4
3
  import { LayoutContext } from '../provider/LayoutContext';
5
4
 
@@ -1,4 +1,15 @@
1
- import { ComponentType, FC, PropsWithChildren, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';
1
+ import {
2
+ ComponentType,
3
+ FC,
4
+ PropsWithChildren,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useId,
9
+ useMemo,
10
+ useRef,
11
+ useState
12
+ } from 'react';
2
13
  import JSXStyle from 'styled-jsx/style';
3
14
  import {
4
15
  AnchorSide,
@@ -21,7 +32,6 @@ import { useItemDimensions } from './useItemDimensions';
21
32
  import { useItemScale } from './useItemScale';
22
33
  import { ScaleAnchorMap } from '../utils/ScaleAnchorMap';
23
34
  import { useSectionHeightData } from './useSectionHeightMap';
24
- import { getHoverStyles, getTransitions } from '../utils/HoverStyles/HoverStyles';
25
35
  import { getItemTopStyle } from '../utils/getItemTopStyle';
26
36
  import { useStickyItemTop } from './items/useStickyItemTop';
27
37
  import { getAnchoredItemTop } from '../utils/getAnchoredItemTop';
@@ -32,17 +42,23 @@ import { GroupItem } from './items/GroupItem';
32
42
  import { CodeEmbedItem } from './items/CodeEmbedItem';
33
43
  import { AreaAnchor } from '@cntrl-site/sdk/src/types/article/ItemArea';
34
44
  import { KeyframesContext } from '../provider/KeyframesContext';
45
+ import { useItemInteractionCtrl } from '../interactions/useItemInteractionCtrl';
35
46
 
36
47
  export interface ItemProps<I extends ItemAny> {
37
48
  item: I;
38
49
  sectionId: string;
39
- onResize?: (height: number) => void;
40
50
  articleHeight: number;
51
+ onResize?: (height: number) => void;
52
+ interactionCtrl?: ReturnType<typeof useItemInteractionCtrl>;
53
+ onVisibilityChange: (isVisible: boolean) => void;
41
54
  }
42
55
 
43
- export interface ItemWrapperProps extends ItemProps<ItemAny> {
56
+ export interface ItemWrapperProps {
57
+ item: ItemAny;
58
+ sectionId: string;
44
59
  articleHeight: number;
45
60
  isInGroup?: boolean;
61
+ isParentVisible?: boolean;
46
62
  }
47
63
 
48
64
  const itemsMap: Record<ArticleItemType, ComponentType<ItemProps<any>>> = {
@@ -77,8 +93,9 @@ const RichTextWrapper: FC<PropsWithChildren<RTWrapperProps>> = ({ isRichText, ch
77
93
 
78
94
  const noop = () => null;
79
95
 
80
- export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isInGroup = false }) => {
96
+ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isParentVisible = true, isInGroup = false }) => {
81
97
  const itemWrapperRef = useRef<HTMLDivElement | null>(null);
98
+ const itemInnerRef = useRef<HTMLDivElement | null>(null);
82
99
  const rectObserver = useContext(ArticleRectContext);
83
100
  const keyframesRepo = useContext(KeyframesContext);
84
101
  const id = useId();
@@ -86,21 +103,27 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
86
103
  const { layouts } = useCntrlContext();
87
104
  const layout = useLayoutContext();
88
105
  const exemplary = useExemplary();
106
+ const [allowPointerEvents, setAllowPointerEvents] = useState<boolean>(isParentVisible);
89
107
  const [wrapperHeight, setWrapperHeight] = useState<undefined | number>(undefined);
90
108
  const [itemHeight, setItemHeight] = useState<undefined | number>(undefined);
91
- const scale = useItemScale(item, sectionId);
92
- const position = useItemPosition(item, sectionId);
109
+ const itemScale = useItemScale(item, sectionId);
110
+ const interactionCtrl = useItemInteractionCtrl(item.id);
111
+ const wrapperStateProps = interactionCtrl?.getState(['top', 'left']);
112
+ const innerStateProps = interactionCtrl?.getState(['width', 'height', 'scale']);
113
+ const position = useItemPosition(item, sectionId, {
114
+ top: wrapperStateProps?.styles?.top as number,
115
+ left: wrapperStateProps?.styles?.left as number,
116
+ });
93
117
  const sectionHeight = useSectionHeightData(sectionId);
94
118
  const stickyTop = useStickyItemTop(item, sectionHeight, sectionId);
95
119
  const dimensions = useItemDimensions(item, sectionId);
96
- const layoutValues: Record<string, any>[] = [item.area, item.hidden, item.state.hover];
120
+ const layoutValues: Record<string, any>[] = [item.area, item.hidden];
97
121
  const isInitialRef = useRef(true);
98
122
  layoutValues.push(item.sticky);
99
123
  layoutValues.push(sectionHeight);
100
124
  if (item.layoutParams) {
101
125
  layoutValues.push(item.layoutParams);
102
126
  }
103
-
104
127
  const sizing = layout && isItemType(item, ArticleItemType.RichText)
105
128
  ? item.layoutParams[layout].sizing
106
129
  : undefined;
@@ -125,20 +148,33 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
125
148
  setWrapperHeight(wrapperHeight);
126
149
  };
127
150
 
151
+ const handleVisibilityChange = useCallback((isVisible: boolean) => {
152
+ if (!isParentVisible) return;
153
+ setAllowPointerEvents(isVisible);
154
+ }, [isParentVisible]);
155
+
128
156
  useEffect(() => {
129
157
  isInitialRef.current = false;
130
158
  }, []);
131
159
 
132
160
  const isRichText = isItemType(item, ArticleItemType.RichText);
161
+ const width = (innerStateProps?.styles?.width ?? dimensions?.width) as number | undefined;
162
+ const height = (innerStateProps?.styles?.height ?? dimensions?.height) as number | undefined;
163
+ const scale = innerStateProps?.styles?.scale ?? itemScale;
133
164
  return (
134
165
  <div
135
166
  className={`item-wrapper-${item.id}`}
136
167
  ref={itemWrapperRef}
168
+ onTransitionEnd={(e) => {
169
+ e.stopPropagation();
170
+ interactionCtrl?.handleTransitionEnd?.(e.propertyName);
171
+ }}
137
172
  style={{
138
173
  ...(position ? { top: position.top } : {}),
139
174
  ...(position ? { left: position.left } : {}),
140
175
  ...(position ? { bottom: position.bottom } : {}),
141
- ...(wrapperHeight !== undefined ? { height: `${wrapperHeight * 100}vw` } : {})
176
+ ...(wrapperHeight !== undefined ? { height: `${wrapperHeight * 100}vw` } : {}),
177
+ transition: wrapperStateProps?.transition ?? 'none'
142
178
  }}
143
179
  >
144
180
  <div
@@ -153,23 +189,42 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
153
189
  <RichTextWrapper isRichText={isRichText}>
154
190
  <div
155
191
  className={`item-${item.id}-inner`}
192
+ ref={itemInnerRef}
193
+ onClick={() => {
194
+ interactionCtrl?.sendTrigger('click');
195
+ }}
196
+ onMouseEnter={() => {
197
+ interactionCtrl?.sendTrigger('hover-in');
198
+ }}
199
+ onMouseLeave={() => {
200
+ interactionCtrl?.sendTrigger('hover-out');
201
+ }}
156
202
  style={{
157
- ...(dimensions ? {
203
+ ...((width && height) ? {
158
204
  width: `${sizingAxis.x === 'manual'
159
205
  ? isRichText
160
- ? `${dimensions.width * exemplary}px`
161
- : `${dimensions.width * 100}vw`
206
+ ? `${width * exemplary}px`
207
+ : `${width * 100}vw`
162
208
  : 'max-content'}`,
163
- height: `${sizingAxis.y === 'manual' ? `${dimensions.height * 100}vw` : 'unset'}` } : {}),
209
+ height: `${sizingAxis.y === 'manual' ? `${height * 100}vw` : 'unset'}` } : {}),
164
210
  ...(scale !== undefined ? { transform: `scale(${scale})`, 'WebkitTransform': `scale(${scale})` } : {}),
211
+ transition: innerStateProps?.transition ?? 'none',
212
+ pointerEvents: allowPointerEvents ? 'auto' : 'none'
165
213
  }}
166
214
  >
167
- <ItemComponent item={item} sectionId={sectionId} onResize={handleItemResize} articleHeight={articleHeight} />
215
+ <ItemComponent
216
+ item={item}
217
+ sectionId={sectionId}
218
+ onResize={handleItemResize}
219
+ articleHeight={articleHeight}
220
+ interactionCtrl={interactionCtrl}
221
+ onVisibilityChange={handleVisibilityChange}
222
+ />
168
223
  </div>
169
224
  </RichTextWrapper>
170
225
  </div>
171
226
  <JSXStyle id={id}>{`
172
- ${getLayoutStyles(layouts, layoutValues, ([area, hidden, hoverParams, sticky, sectionHeight, layoutParams], exemplary) => {
227
+ ${getLayoutStyles(layouts, layoutValues, ([area, hidden, sticky, sectionHeight, layoutParams], exemplary) => {
173
228
  const sizingAxis = parseSizing(layoutParams.sizing);
174
229
  const isScreenBasedBottom = area.positionType === PositionType.ScreenBased && area.anchorSide === AnchorSide.Bottom;
175
230
  const scaleAnchor = area.scaleAnchor as AreaAnchor;
@@ -183,8 +238,6 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
183
238
  height: fit-content;
184
239
  }
185
240
  .item-${item.id}-inner {
186
- transition: ${getTransitions(['width', 'height', 'scale'], hoverParams)};
187
- pointer-events: auto;
188
241
  width: ${sizingAxis.x === 'manual'
189
242
  ? `${area.width * 100}vw`
190
243
  : 'max-content'};
@@ -200,13 +253,6 @@ export const Item: FC<ItemWrapperProps> = ({ item, sectionId, articleHeight, isI
200
253
  bottom: ${isScreenBasedBottom ? `${-area.top * 100}vw` : 'unset'};
201
254
  top: ${isScreenBasedBottom ? 'unset' : getItemTopStyle(area.top, area.anchorSide)};
202
255
  left: ${area.left * 100}vw;
203
- transition: ${getTransitions(['left', 'top'], hoverParams)};
204
- }
205
- .item-${item.id}-inner:hover {
206
- ${getHoverStyles(['width', 'height', 'scale'], hoverParams)};
207
- }
208
- .item-wrapper-${item.id}:hover {
209
- ${getHoverStyles(['left', 'top'], hoverParams, area.anchorSide)};
210
256
  }
211
257
  `);
212
258
  })}
@@ -223,11 +269,11 @@ function parseSizing(sizing: string = 'manual'): Axis {
223
269
  } as Axis;
224
270
  }
225
271
 
272
+ export function isItemType<T extends ArticleItemType>(item: ItemAny, itemType: T): item is TItem<T> {
273
+ return item.type === itemType;
274
+ }
275
+
226
276
  interface Axis {
227
277
  x: 'manual' | 'auto';
228
278
  y: 'manual' | 'auto';
229
279
  }
230
-
231
- export function isItemType<T extends ArticleItemType>(item: ItemAny, itemType: T): item is TItem<T> {
232
- return item.type === itemType;
233
- }
@@ -1,8 +1,7 @@
1
- import { ArticleItemType, getLayoutStyles, CodeEmbedItem as TCodeEmbedItem, AreaAnchor } from '@cntrl-site/sdk';
1
+ import { getLayoutStyles, CodeEmbedItem as TCodeEmbedItem, AreaAnchor } from '@cntrl-site/sdk';
2
2
  import { FC, useEffect, useId, useState } from 'react';
3
3
  import { useCntrlContext } from '../../provider/useCntrlContext';
4
4
  import { ItemProps } from '../Item';
5
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
6
5
  import JSXStyle from 'styled-jsx/style';
7
6
  import { useRegisterResize } from "../../common/useRegisterResize";
8
7
  import { useItemAngle } from '../useItemAngle';
@@ -21,16 +20,20 @@ const stylesMap = {
21
20
  [AreaAnchor.BottomRight]: { justifyContent: 'flex-end', alignItems: 'flex-end' }
22
21
  };
23
22
 
24
- export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId, onResize }) => {
23
+ export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
25
24
  const id = useId();
26
25
  const { layouts } = useCntrlContext();
27
- const { anchor, blur, opacity } = useCodeEmbedItem(item, sectionId);
28
- const angle = useItemAngle(item, sectionId);
26
+ const { anchor, blur: itemBlur, opacity: itemOpacity } = useCodeEmbedItem(item, sectionId);
27
+ const itemAngle = useItemAngle(item, sectionId);
29
28
  const { html } = item.commonParams;
30
29
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
31
30
  useRegisterResize(ref, onResize);
32
31
  const pos = stylesMap[anchor];
33
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
32
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
33
+ const stateParams = interactionCtrl?.getState(['angle', 'blur', 'opacity']);
34
+ const blur = (stateParams?.styles?.blur ?? itemBlur) as number;
35
+ const opacity = stateParams?.styles?.opacity ?? itemOpacity;
36
+ const angle = stateParams?.styles?.angle ?? itemAngle;
34
37
 
35
38
  useEffect(() => {
36
39
  if (!ref) return;
@@ -52,6 +55,10 @@ export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId,
52
55
  if (!iframe) return;
53
56
  iframe.srcdoc = item.commonParams.html;
54
57
  }, [item.commonParams.html, item.commonParams.iframe, ref]);
58
+ const isInteractive = opacity !== 0;
59
+ useEffect(() => {
60
+ onVisibilityChange?.(isInteractive);
61
+ }, [isInteractive, onVisibilityChange]);
55
62
 
56
63
  return (
57
64
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
@@ -61,6 +68,7 @@ export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId,
61
68
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
62
69
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
63
70
  ...(opacity !== undefined ? { opacity } : {}),
71
+ transition: stateParams?.transition ?? 'none'
64
72
  }}
65
73
  ref={setRef}
66
74
  >
@@ -93,21 +101,17 @@ export const CodeEmbedItem: FC<ItemProps<TCodeEmbedItem>> = ({ item, sectionId,
93
101
  z-index: 1;
94
102
  border: none;
95
103
  }
96
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams], exemplary) => {
104
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams], exemplary) => {
97
105
  return (`
98
106
  .embed-wrapper-${item.id} {
99
107
  opacity: ${layoutParams.opacity};
100
108
  transform: rotate(${area.angle}deg);
101
109
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
102
- transition: ${getTransitions<ArticleItemType.CodeEmbed>(['angle', 'blur', 'opacity'], hoverParams)};
103
110
  }
104
111
  .embed-${item.id} {
105
112
  width: ${item.commonParams.scale ? `${area.width * exemplary}px` : '100%'};
106
113
  height: ${item.commonParams.scale ? `${area.height * exemplary}px` : '100%'};
107
114
  }
108
- .embed-wrapper-${item.id}:hover {
109
- ${getHoverStyles<ArticleItemType.CodeEmbed>(['angle', 'blur', 'opacity'], hoverParams)}
110
- }
111
115
  `);
112
116
  })}
113
117
  `}</JSXStyle>
@@ -1,20 +1,21 @@
1
- import { ArticleItemType, getLayoutStyles, CustomItem as TCustomItem } from '@cntrl-site/sdk';
1
+ import { getLayoutStyles, CustomItem as TCustomItem } from '@cntrl-site/sdk';
2
2
  import { FC, useState } from 'react';
3
3
  import { useCntrlContext } from '../../provider/useCntrlContext';
4
4
  import { ItemProps } from '../Item';
5
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
6
5
  import JSXStyle from 'styled-jsx/style';
7
- import { useRegisterResize } from "../../common/useRegisterResize";
6
+ import { useRegisterResize } from '../../common/useRegisterResize';
8
7
  import { useItemAngle } from '../useItemAngle';
9
8
 
10
- export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item, onResize, sectionId }) => {
9
+ export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item, onResize, sectionId, interactionCtrl }) => {
11
10
  const sdk = useCntrlContext();
12
11
  const { layouts } = useCntrlContext();
13
- const angle = useItemAngle(item, sectionId);
12
+ const itemAngle = useItemAngle(item, sectionId);
14
13
  const component = sdk.customItems.get(item.commonParams.name);
15
- const layoutValues: Record<string, any>[] = [item.area, item.state.hover];
14
+ const layoutValues: Record<string, any>[] = [item.area];
16
15
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
17
16
  useRegisterResize(ref, onResize);
17
+ const stateParams = interactionCtrl?.getState(['angle']);
18
+ const angle = stateParams?.styles?.angle ?? itemAngle;
18
19
  if (!component) return null;
19
20
  return (
20
21
  <>
@@ -23,25 +24,22 @@ export const CustomItem: FC<ItemProps<TCustomItem>> = ({ item, onResize, section
23
24
  ref={setRef}
24
25
  style={{
25
26
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
27
+ transition: stateParams?.transition ?? 'none'
26
28
  }}
27
29
  >
28
30
  {component({})}
29
31
  </div>
30
32
  <JSXStyle id={item.id}>
31
- {`${getLayoutStyles(layouts, layoutValues, ([area, hoverParams]) => {
33
+ {`${getLayoutStyles(layouts, layoutValues, ([area]) => {
32
34
  return (`
33
35
  .custom-component-${item.id} {
34
36
  transform: rotate(${area.angle}deg);
35
- transition: ${getTransitions<ArticleItemType.Custom>(['angle'], hoverParams)};
36
37
  height: 100%;
37
38
  width: 100%;
38
39
  position: absolute;
39
40
  left: 0;
40
41
  top: 0;
41
42
  }
42
- .custom-component-${item.id}:hover {
43
- ${getHoverStyles<ArticleItemType.Custom>(['angle'], hoverParams)};
44
- }
45
43
  `);
46
44
  })}`}
47
45
  </JSXStyle>
@@ -1,24 +1,30 @@
1
- import React, { FC, useId, useState } from 'react';
1
+ import React, { FC, useEffect, useId, useState } from 'react';
2
2
  import { Item, ItemProps } from '../Item';
3
3
  import JSXStyle from 'styled-jsx/style';
4
- import { ArticleItemType, getLayoutStyles, GroupItem as TGroupItem } from '@cntrl-site/sdk';
5
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
4
+ import { getLayoutStyles, GroupItem as TGroupItem } from '@cntrl-site/sdk';
6
5
  import { LinkWrapper } from '../LinkWrapper';
7
6
  import { useRegisterResize } from '../../common/useRegisterResize';
8
7
  import { useCntrlContext } from '../../provider/useCntrlContext';
9
8
  import { useItemAngle } from '../useItemAngle';
10
9
  import { useGroupItem } from './useGroupItem';
10
+ import { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
11
11
 
12
- export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize, articleHeight }) => {
12
+ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize, articleHeight, interactionCtrl, onVisibilityChange }) => {
13
13
  const id = useId();
14
14
  const { items } = item;
15
- const angle = useItemAngle(item, sectionId);
15
+ const itemAngle = useItemAngle(item, sectionId);
16
16
  const { layouts } = useCntrlContext();
17
- const { opacity } = useGroupItem(item, sectionId);
18
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
17
+ const { opacity: itemOpacity } = useGroupItem(item, sectionId);
18
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
19
19
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
20
20
  useRegisterResize(ref, onResize);
21
-
21
+ const stateParams = interactionCtrl?.getState(['opacity', 'angle']);
22
+ const angle = getStyleFromItemStateAndParams(stateParams?.styles?.angle, itemAngle);
23
+ const opacity = getStyleFromItemStateAndParams(stateParams?.styles?.opacity, itemOpacity);
24
+ const isInteractive = opacity !== 0 && opacity !== undefined;
25
+ useEffect(() => {
26
+ onVisibilityChange?.(isInteractive);
27
+ }, [isInteractive, onVisibilityChange]);
22
28
  return (
23
29
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
24
30
  <>
@@ -28,6 +34,7 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
28
34
  style={{
29
35
  ...(opacity !== undefined ? { opacity } : {}),
30
36
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
37
+ transition: stateParams?.transition ?? 'none'
31
38
  }}
32
39
  >
33
40
  {items && items.map(item => (
@@ -36,6 +43,7 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
36
43
  key={item.id}
37
44
  sectionId={sectionId}
38
45
  articleHeight={articleHeight}
46
+ isParentVisible={isInteractive}
39
47
  isInGroup
40
48
  />
41
49
  ))}
@@ -47,15 +55,11 @@ export const GroupItem: FC<ItemProps<TGroupItem>> = ({ item, sectionId, onResize
47
55
  height: 100%;
48
56
  box-sizing: border-box;
49
57
  }
50
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
58
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
51
59
  return (`
52
60
  .group-${item.id} {
53
61
  opacity: ${layoutParams.opacity};
54
62
  transform: rotate(${area.angle}deg);
55
- transition: ${getTransitions<ArticleItemType.Group>(['opacity', 'angle'], hoverParams)};
56
- }
57
- .group-${item.id}:hover {
58
- ${getHoverStyles<ArticleItemType.Group>(['opacity', 'angle'], hoverParams)};
59
63
  }
60
64
  `);
61
65
  })}