@cntrl-site/sdk-nextjs 1.0.19 → 1.1.0

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 (49) hide show
  1. package/jest.config.js +2 -2
  2. package/lib/components/Article.js +9 -8
  3. package/lib/components/Head.js +1 -1
  4. package/lib/components/Item.js +39 -21
  5. package/lib/components/items/CodeEmbedItem.js +15 -12
  6. package/lib/components/items/CustomItem.js +7 -9
  7. package/lib/components/items/GroupItem.js +15 -12
  8. package/lib/components/items/ImageItem.js +28 -21
  9. package/lib/components/items/RectangleItem.js +28 -15
  10. package/lib/components/items/RichTextItem.js +33 -12
  11. package/lib/components/items/VideoItem.js +26 -19
  12. package/lib/components/items/VimeoEmbed.js +22 -20
  13. package/lib/components/items/YoutubeEmbed.js +20 -18
  14. package/lib/components/useItemPosition.js +10 -5
  15. package/lib/interactions/CSSPropertyNameMap.js +38 -0
  16. package/lib/interactions/InteractionsRegistry.js +220 -0
  17. package/lib/interactions/ItemInteractionCtrl.js +61 -0
  18. package/lib/interactions/getTransition.js +21 -0
  19. package/lib/interactions/types.js +2 -0
  20. package/lib/interactions/useItemInteractionCtrl.js +17 -0
  21. package/lib/provider/InteractionsContext.js +23 -0
  22. package/lib/utils/getStyleFromItemStateAndParams.js +9 -0
  23. package/package.json +7 -4
  24. package/src/components/Article.tsx +30 -27
  25. package/src/components/ArticleWrapper.tsx +1 -2
  26. package/src/components/Head.tsx +0 -1
  27. package/src/components/Item.tsx +75 -29
  28. package/src/components/items/CodeEmbedItem.tsx +15 -11
  29. package/src/components/items/CustomItem.tsx +9 -11
  30. package/src/components/items/GroupItem.tsx +17 -13
  31. package/src/components/items/ImageItem.tsx +35 -20
  32. package/src/components/items/RectangleItem.tsx +35 -14
  33. package/src/components/items/RichTextItem.tsx +41 -14
  34. package/src/components/items/VideoItem.tsx +34 -20
  35. package/src/components/items/VimeoEmbed.tsx +27 -19
  36. package/src/components/items/YoutubeEmbed.tsx +20 -18
  37. package/src/components/useItemPosition.ts +12 -5
  38. package/src/interactions/CSSPropertyNameMap.ts +38 -0
  39. package/src/interactions/InteractionsRegistry.ts +244 -0
  40. package/src/interactions/ItemInteractionCtrl.ts +62 -0
  41. package/src/interactions/getTransition.ts +27 -0
  42. package/src/interactions/types.ts +32 -0
  43. package/src/interactions/useItemInteractionCtrl.ts +18 -0
  44. package/src/provider/InteractionsContext.old.tsx +65 -0
  45. package/src/provider/InteractionsContext.test.tsx +97 -0
  46. package/src/provider/InteractionsContext.tsx +28 -0
  47. package/src/utils/getStyleFromItemStateAndParams.ts +8 -0
  48. package/lib/utils/HoverStyles/HoverStyles.js +0 -77
  49. package/src/utils/HoverStyles/HoverStyles.ts +0 -85
@@ -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
  })}
@@ -1,17 +1,17 @@
1
1
  import { FC, useEffect, useId, useMemo, useRef, useState } from 'react';
2
2
  import JSXStyle from 'styled-jsx/style';
3
3
  import { CntrlColor } from '@cntrl-site/color';
4
- import { ArticleItemType, getLayoutStyles, ImageItem as TImageItem } from '@cntrl-site/sdk';
4
+ import { getLayoutStyles, ImageItem as TImageItem } from '@cntrl-site/sdk';
5
5
  import { ItemProps } from '../Item';
6
6
  import { LinkWrapper } from '../LinkWrapper';
7
7
  import { useFileItem } from './useFileItem';
8
8
  import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../provider/useCntrlContext';
10
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
11
10
  import { useRegisterResize } from "../../common/useRegisterResize";
12
11
  import { useImageFx } from '../../utils/effects/useImageFx';
13
12
  import { useElementRect } from '../../utils/useElementRect';
14
13
  import { useLayoutContext } from '../useLayoutContext';
14
+ import { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
15
15
 
16
16
  const baseVariables = `precision mediump float;
17
17
  uniform sampler2D u_image;
@@ -22,15 +22,20 @@ uniform float u_time;
22
22
  uniform vec2 u_cursor;
23
23
  varying vec2 v_texCoord;`;
24
24
 
25
- export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize }) => {
25
+ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
26
26
  const id = useId();
27
27
  const { layouts } = useCntrlContext();
28
28
  const layoutId = useLayoutContext();
29
- const { radius, strokeWidth, opacity, strokeColor, blur } = useFileItem(item, sectionId);
30
- const angle = useItemAngle(item, sectionId);
31
- const borderColor = useMemo(() => strokeColor ? CntrlColor.parse(strokeColor) : undefined, [strokeColor]);
32
- const [ref, setRef] = useState<HTMLDivElement | null>(null);
33
- useRegisterResize(ref, onResize);
29
+ const {
30
+ radius: itemRadius,
31
+ strokeWidth: itemStrokeWidth,
32
+ opacity: itemOpacity,
33
+ strokeColor: itemStrokeColor,
34
+ blur: itemBlur
35
+ } = useFileItem(item, sectionId);
36
+ const itemAngle = useItemAngle(item, sectionId);
37
+ const [wrapperRef, setWrapperRef] = useState<HTMLDivElement | null>(null);
38
+ useRegisterResize(wrapperRef, onResize);
34
39
  const { url, hasGLEffect, fragmentShader, FXControls, FXCursor } = item.commonParams;
35
40
  const fxCanvas = useRef<HTMLCanvasElement | null>(null);
36
41
  const isInitialRef = useRef(true);
@@ -41,12 +46,15 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
41
46
  acc[control.shaderParam] = control.value;
42
47
  return acc;
43
48
  }, {});
44
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
49
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
45
50
  const fullShaderCode = `${baseVariables}\n${controlsVariables}\n${fragmentShader}`;
46
51
  const area = layoutId ? item.area[layoutId] : null;
47
52
  const exemplary = layouts?.find(l => l.id === layoutId)?.exemplary;
48
53
  const width = area && exemplary ? area.width * exemplary : 0;
49
54
  const height = area && exemplary ? area.height * exemplary : 0;
55
+ const wrapperStateParams = interactionCtrl?.getState(['angle', 'opacity', 'blur']);
56
+ const imgStateParams = interactionCtrl?.getState(['strokeWidth', 'radius', 'strokeColor']);
57
+
50
58
  useEffect(() => {
51
59
  isInitialRef.current = false;
52
60
  }, []);
@@ -62,24 +70,39 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
62
70
  width,
63
71
  height
64
72
  );
65
- const rect = useElementRect(ref);
73
+ const rect = useElementRect(wrapperRef);
66
74
  const rectWidth = Math.floor(rect?.width ?? 0);
67
75
  const rectHeight = Math.floor(rect?.height ?? 0);
76
+ const borderColor = useMemo(() => {
77
+ const borderColor = getStyleFromItemStateAndParams(imgStateParams?.styles?.strokeColor, itemStrokeColor)
78
+ return borderColor ? CntrlColor.parse(borderColor) : undefined;
79
+ }, [itemStrokeColor, imgStateParams?.styles?.strokeColor]);
80
+ const radius = getStyleFromItemStateAndParams(imgStateParams?.styles?.radius, itemRadius);
81
+ const strokeWidth = getStyleFromItemStateAndParams(imgStateParams?.styles?.strokeWidth, itemStrokeWidth);
82
+ const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
83
+ const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
84
+ const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
68
85
  const inlineStyles = {
69
86
  ...(borderColor ? { borderColor: `${borderColor.fmt('rgba')}` } : {}),
70
87
  ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
71
88
  ...(strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {}),
89
+ transition: imgStateParams?.transition ?? 'none'
72
90
  };
91
+ const isInteractive = opacity !== 0;
92
+ useEffect(() => {
93
+ onVisibilityChange?.(isInteractive);
94
+ }, [isInteractive, onVisibilityChange]);
73
95
  return (
74
96
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
75
97
  <>
76
98
  <div
77
99
  className={`image-wrapper-${item.id}`}
78
- ref={setRef}
100
+ ref={setWrapperRef}
79
101
  style={{
80
102
  ...(opacity !== undefined ? { opacity } : {}),
81
103
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
82
104
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
105
+ transition: wrapperStateParams?.transition ?? 'none'
83
106
  }}
84
107
  >
85
108
  {hasGLEffect && isFXAllowed ? (
@@ -125,25 +148,17 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
125
148
  border-width: 0;
126
149
  box-sizing: border-box;
127
150
  }
128
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
151
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
129
152
  return (`
130
153
  .image-wrapper-${item.id} {
131
154
  opacity: ${layoutParams.opacity};
132
155
  transform: rotate(${area.angle}deg);
133
156
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
134
- transition: ${getTransitions<ArticleItemType.Image>(['angle', 'opacity', 'blur'], hoverParams)};
135
- }
136
- .image-wrapper-${item.id}:hover {
137
- ${getHoverStyles<ArticleItemType.Image>(['angle', 'opacity', 'blur'], hoverParams)};
138
157
  }
139
158
  .image-${item.id} {
140
159
  border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
141
160
  border-radius: ${layoutParams.radius * 100}vw;
142
161
  border-width: ${layoutParams.strokeWidth * 100}vw;
143
- transition: ${getTransitions<ArticleItemType.Image>(['strokeWidth', 'radius', 'strokeColor'], hoverParams)};
144
- }
145
- .image-wrapper-${item.id}:hover .image, .image-wrapper-${item.id}:hover .img-canvas {
146
- ${getHoverStyles<ArticleItemType.Image>(['strokeWidth', 'radius', 'strokeColor'], hoverParams)};
147
162
  }
148
163
  `);
149
164
  })}
@@ -1,26 +1,50 @@
1
- import { FC, useEffect, useId, useMemo, useRef, useState } from 'react';
1
+ import { FC, useEffect, useId, useMemo, useState } from 'react';
2
2
  import JSXStyle from 'styled-jsx/style';
3
3
  import { CntrlColor } from '@cntrl-site/color';
4
- import { RectangleItem as TRectangleItem, getLayoutStyles, ArticleItemType } from '@cntrl-site/sdk';
4
+ import { RectangleItem as TRectangleItem, getLayoutStyles } from '@cntrl-site/sdk';
5
5
  import { ItemProps } from '../Item';
6
6
  import { LinkWrapper } from '../LinkWrapper';
7
7
  import { useRectangleItem } from './useRectangleItem';
8
8
  import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../provider/useCntrlContext';
10
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
11
10
  import { useRegisterResize } from "../../common/useRegisterResize";
11
+ import { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
12
12
 
13
- export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId, onResize }) => {
13
+ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
14
14
  const id = useId();
15
15
  const { layouts } = useCntrlContext();
16
- const { fillColor, radius, strokeWidth, strokeColor, blur, backdropBlur } = useRectangleItem(item, sectionId);
17
- const angle = useItemAngle(item, sectionId);
18
- const backgroundColor = useMemo(() => fillColor ? CntrlColor.parse(fillColor) : undefined, [fillColor]);
19
- const borderColor = useMemo(() => strokeColor ? CntrlColor.parse(strokeColor) : undefined, [strokeColor]);
20
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
16
+ const {
17
+ fillColor: itemFillColor,
18
+ radius: itemRadius,
19
+ strokeWidth: itemStrokeWidth,
20
+ strokeColor: itemStrokeColor,
21
+ blur: itemBlur,
22
+ backdropBlur: itemBackdropBlur
23
+ } = useRectangleItem(item, sectionId);
24
+ const itemAngle = useItemAngle(item, sectionId);
25
+ const stateParams = interactionCtrl?.getState(['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor', 'blur', 'backdropBlur']);
26
+ const styles = stateParams?.styles ?? {};
27
+ const backgroundColor = useMemo(() => {
28
+ const fillColor = getStyleFromItemStateAndParams(styles?.fillColor, itemFillColor);
29
+ return fillColor ? CntrlColor.parse(fillColor) : undefined;
30
+ }, [itemFillColor, styles?.fillColor]);
31
+ const borderColor = useMemo(() => {
32
+ const strokeColor = getStyleFromItemStateAndParams(styles?.strokeColor, itemStrokeColor);
33
+ return strokeColor ? CntrlColor.parse(strokeColor) : undefined;
34
+ }, [itemStrokeColor, styles?.strokeColor]);
35
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
21
36
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
22
37
  useRegisterResize(ref, onResize);
38
+ const backdropBlur = getStyleFromItemStateAndParams(styles?.backdropBlur, itemBackdropBlur);
39
+ const radius = getStyleFromItemStateAndParams(styles?.radius, itemRadius);
40
+ const strokeWidth = getStyleFromItemStateAndParams(styles?.strokeWidth, itemStrokeWidth);
41
+ const angle = getStyleFromItemStateAndParams(styles?.angle, itemAngle);
42
+ const blur = getStyleFromItemStateAndParams(styles?.blur, itemBlur);
23
43
  const backdropFilterValue = backdropBlur ? `blur(${backdropBlur * 100}vw)`: undefined;
44
+ const isInteractive = backgroundColor?.getAlpha() !== 0 || (strokeWidth !== 0 && borderColor?.getAlpha() !== 0);
45
+ useEffect(() => {
46
+ onVisibilityChange?.(isInteractive);
47
+ }, [isInteractive, onVisibilityChange]);
24
48
 
25
49
  return (
26
50
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
@@ -39,6 +63,7 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
39
63
  ? { backdropFilter: backdropFilterValue, WebkitBackdropFilter: backdropFilterValue }
40
64
  : {}
41
65
  ),
66
+ transition: stateParams?.transition ?? 'none'
42
67
  }}
43
68
  />
44
69
  <JSXStyle id={id}>{`
@@ -49,7 +74,7 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
49
74
  border-style: solid;
50
75
  box-sizing: border-box;
51
76
  }
52
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
77
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
53
78
  return (`
54
79
  .rectangle-${item.id} {
55
80
  background-color: ${CntrlColor.parse(layoutParams.fillColor).fmt('rgba')};
@@ -60,10 +85,6 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
60
85
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
61
86
  backdrop-filter: ${layoutParams.backdropFilter !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
62
87
  -webkit-backdrop-filter: ${layoutParams.backdropFilter !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
63
- transition: ${getTransitions<ArticleItemType.Rectangle>(['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor', 'blur', 'backdropBlur'], hoverParams)};
64
- }
65
- .rectangle-${item.id}:hover {
66
- ${getHoverStyles<ArticleItemType.Rectangle>(['angle', 'fillColor', 'strokeWidth', 'radius', 'strokeColor', 'blur', 'backdropBlur'], hoverParams)};
67
88
  }
68
89
  `);
69
90
  })}