@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
@@ -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
  })}
@@ -1,28 +1,58 @@
1
- import { FC, useId, useMemo, useState } from 'react';
1
+ import { FC, useEffect, useId, useMemo, useState } from 'react';
2
2
  import { CntrlColor } from '@cntrl-site/color';
3
- import { ArticleItemType, getLayoutStyles, RichTextItem as TRichTextItem } from '@cntrl-site/sdk';
3
+ import { getLayoutStyles, RichTextItem as TRichTextItem } from '@cntrl-site/sdk';
4
4
  import JSXStyle from 'styled-jsx/style';
5
5
  import { ItemProps } from '../Item';
6
6
  import { useRichTextItem } from './useRichTextItem';
7
7
  import { useCntrlContext } from '../../provider/useCntrlContext';
8
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
9
8
  import { useRichTextItemValues } from './useRichTextItemValues';
10
9
  import { useRegisterResize } from "../../common/useRegisterResize";
11
10
  import { getFontFamilyValue } from '../../utils/getFontFamilyValue';
12
11
  import { useExemplary } from '../../common/useExemplary';
13
12
  import { useItemAngle } from '../useItemAngle';
13
+ import { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
14
+ import { useCurrentLayout } from '../../common/useCurrentLayout';
14
15
 
15
- export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, onResize }) => {
16
- const [content, styles] = useRichTextItem(item);
16
+ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
17
17
  const id = useId();
18
18
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
19
19
  const { layouts } = useCntrlContext();
20
- const angle = useItemAngle(item, sectionId);
21
- const { blur, wordSpacing, letterSpacing, color, fontSize, lineHeight } = useRichTextItemValues(item, sectionId);
22
- const textColor = useMemo(() => color ? CntrlColor.parse(color) : undefined, [color]);
23
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
20
+ const itemAngle = useItemAngle(item, sectionId);
21
+ const {
22
+ blur: itemBlur,
23
+ wordSpacing: itemWordSpacing,
24
+ letterSpacing: itemLetterSpacing,
25
+ color: itemColor,
26
+ fontSize,
27
+ lineHeight
28
+ } = useRichTextItemValues(item, sectionId);
29
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
24
30
  const exemplary = useExemplary();
31
+ const { layoutId } = useCurrentLayout();
25
32
  useRegisterResize(ref, onResize);
33
+ const stateParams = interactionCtrl?.getState(['angle', 'blur', 'letterSpacing', 'wordSpacing', 'color']);
34
+ const stateStyles = stateParams?.styles ?? {};
35
+ const transition = stateParams?.transition ?? 'none';
36
+ const textColor = useMemo(() => {
37
+ const color = getStyleFromItemStateAndParams(stateParams?.styles?.color, itemColor);
38
+ return color ? CntrlColor.parse(color) : undefined;
39
+ }, [itemColor, stateStyles.color]);
40
+ const angle = getStyleFromItemStateAndParams(stateStyles.angle, itemAngle);
41
+ const blur = getStyleFromItemStateAndParams(stateStyles.blur, itemBlur);
42
+ const letterSpacing = getStyleFromItemStateAndParams(stateStyles.letterSpacing, itemLetterSpacing);
43
+ const wordSpacing = getStyleFromItemStateAndParams(stateStyles.wordSpacing, itemWordSpacing);
44
+ const colorAlpha = textColor?.getAlpha();
45
+ const rangeStyles = layoutId ? item.layoutParams[layoutId]?.rangeStyles ?? [] : [];
46
+ const rangeColors = rangeStyles.filter((style) => style.style === 'COLOR');
47
+ const hasVisibleRangeColors = rangeColors.some((color) => {
48
+ const alpha = CntrlColor.parse(color.value!).getAlpha();
49
+ return alpha > 0;
50
+ });
51
+ const isInteractive = colorAlpha !== 0 || hasVisibleRangeColors;
52
+ const [content, styles] = useRichTextItem(item);
53
+ useEffect(() => {
54
+ onVisibilityChange?.(isInteractive);
55
+ }, [isInteractive, onVisibilityChange]);
26
56
 
27
57
  return (
28
58
  <>
@@ -37,13 +67,14 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
37
67
  ...(wordSpacing !== undefined ? { wordSpacing: `${wordSpacing * exemplary}px` } : {}),
38
68
  ...(fontSize !== undefined ? { fontSize: `${Math.round(fontSize * exemplary)}px` } : {}),
39
69
  ...(lineHeight !== undefined ? { lineHeight: `${lineHeight * exemplary}px` } : {}),
70
+ transition
40
71
  }}
41
72
  >
42
73
  {content}
43
74
  </div>
44
75
  <JSXStyle id={id}>
45
76
  {styles}
46
- {`${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
77
+ {`${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
47
78
  const color = CntrlColor.parse(layoutParams.color);
48
79
  return (`
49
80
  .rich-text-wrapper-${item.id} {
@@ -60,10 +91,6 @@ export const RichTextItem: FC<ItemProps<TRichTextItem>> = ({ item, sectionId, on
60
91
  transform: rotate(${area.angle}deg);
61
92
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
62
93
  text-transform: ${layoutParams.textTransform};
63
- transition: ${getTransitions<ArticleItemType.RichText>(['angle', 'blur', 'letterSpacing', 'wordSpacing', 'color'], hoverParams)};
64
- }
65
- .rich-text-wrapper-${item.id}:hover {
66
- ${getHoverStyles<ArticleItemType.RichText>(['angle', 'blur', 'letterSpacing', 'wordSpacing', 'color'], hoverParams)};
67
94
  }
68
95
  @supports not (color: oklch(42% 0.3 90 / 1)) {
69
96
  .rich-text-wrapper-${item.id} {
@@ -1,35 +1,56 @@
1
- import { FC, useId, useMemo, useRef, useState } from 'react';
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, VideoItem as TVideoItem } from '@cntrl-site/sdk';
4
+ import { getLayoutStyles, VideoItem as TVideoItem } 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 { useLayoutContext } from '../useLayoutContext';
13
12
  import { ScrollPlaybackVideo } from '../ScrollPlaybackVideo';
13
+ import { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
14
14
 
15
- export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize }) => {
15
+ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
16
16
  const id = useId();
17
17
  const { layouts } = useCntrlContext();
18
- const { radius, strokeWidth, strokeColor, opacity, blur } = useFileItem(item, sectionId);
19
- const angle = useItemAngle(item, sectionId);
20
- const borderColor = useMemo(() => strokeColor ? CntrlColor.parse(strokeColor) : undefined, [strokeColor]);
18
+ const {
19
+ radius: itemRadius,
20
+ strokeWidth: itemStrokeWidth,
21
+ strokeColor: itemStrokeColor,
22
+ opacity: itemOpacity,
23
+ blur: itemBlur
24
+ } = useFileItem(item, sectionId);
25
+ const itemAngle = useItemAngle(item, sectionId);
21
26
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
22
27
  const videoRef = useRef<HTMLVideoElement | null>(null);
23
28
  const layoutId = useLayoutContext();
24
29
  const scrollPlayback = layoutId ? item.layoutParams[layoutId].scrollPlayback : null;
25
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
30
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
26
31
  const hasScrollPlayback = scrollPlayback !== null;
32
+ const wrapperStateParams = interactionCtrl?.getState(['angle', 'opacity', 'blur']);
33
+ const videoStateParams = interactionCtrl?.getState(['strokeWidth', 'radius', 'strokeColor']);
34
+ const borderColor = useMemo(() => {
35
+ const strokeColor = getStyleFromItemStateAndParams(videoStateParams?.styles?.color, itemStrokeColor);
36
+ return strokeColor ? CntrlColor.parse(strokeColor) : undefined;
37
+ }, [videoStateParams?.styles?.color, itemStrokeColor]);
38
+ const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
39
+ const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
40
+ const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
41
+ const strokeWidth = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.strokeWidth, itemStrokeWidth);
42
+ const radius = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.radius, itemRadius);
27
43
  useRegisterResize(ref, onResize);
28
44
  const inlineStyles = {
29
45
  ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
30
46
  ...(strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {}),
31
- ...(borderColor ? { borderColor: `${borderColor.toCss()}` } : {})
47
+ ...(borderColor ? { borderColor: `${borderColor.toCss()}` } : {}),
48
+ transition: videoStateParams?.transition ?? 'none'
32
49
  };
50
+ const isInteractive = opacity !== 0;
51
+ useEffect(() => {
52
+ onVisibilityChange?.(isInteractive);
53
+ }, [isInteractive, onVisibilityChange]);
33
54
 
34
55
  return (
35
56
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
@@ -39,7 +60,8 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
39
60
  style={{
40
61
  ...(opacity !== undefined ? { opacity } : {}),
41
62
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
42
- ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})
63
+ ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
64
+ transition: wrapperStateParams?.transition ?? 'none'
43
65
  }}
44
66
  >
45
67
  {hasScrollPlayback ? (
@@ -86,31 +108,23 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
86
108
  pointer-events: auto;
87
109
  }
88
110
  .video-${item.id} {
89
- border-color: ${strokeColor};
111
+ border-color: ${itemStrokeColor};
90
112
  }
91
113
  .video-playback-wrapper {
92
114
  display: flex;
93
115
  justify-content: center;
94
116
  }
95
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
117
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
96
118
  return (`
97
119
  .video-wrapper-${item.id} {
98
120
  opacity: ${layoutParams.opacity};
99
121
  transform: rotate(${area.angle}deg);
100
122
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
101
- transition: ${getTransitions<ArticleItemType.Video>(['angle', 'opacity', 'blur'], hoverParams)};
102
- }
103
- .video-wrapper-${item.id}:hover {
104
- ${getHoverStyles<ArticleItemType.Video>(['angle', 'opacity', 'blur'], hoverParams)};
105
123
  }
106
124
  .video-${item.id} {
107
125
  border-color: ${CntrlColor.parse(layoutParams.strokeColor).fmt('rgba')};
108
126
  border-radius: ${layoutParams.radius * 100}vw;
109
127
  border-width: ${layoutParams.strokeWidth * 100}vw;
110
- transition: ${getTransitions<ArticleItemType.Video>(['strokeWidth', 'radius', 'strokeColor'], hoverParams)};
111
- }
112
- .video-wrapper-${item.id}:hover .video {
113
- ${getHoverStyles<ArticleItemType.Video>(['strokeWidth', 'radius', 'strokeColor'], hoverParams)};
114
128
  }
115
129
  `);
116
130
  })}
@@ -5,23 +5,33 @@ import { ItemProps } from '../Item';
5
5
  import { LinkWrapper } from '../LinkWrapper';
6
6
  import { useEmbedVideoItem } from './useEmbedVideoItem';
7
7
  import { useItemAngle } from '../useItemAngle';
8
- import { ArticleItemType, getLayoutStyles, VimeoEmbedItem as TVimeoEmbedItem } from '@cntrl-site/sdk';
8
+ import { getLayoutStyles, VimeoEmbedItem as TVimeoEmbedItem } from '@cntrl-site/sdk';
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 VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId, onResize }) => {
13
+ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
14
14
  const id = useId();
15
15
  const { layouts } = useCntrlContext();
16
- const { radius, blur, opacity } = useEmbedVideoItem(item, sectionId);
16
+ const {
17
+ radius: itemRadius,
18
+ blur: itemBlur,
19
+ opacity: itemOpacity
20
+ } = useEmbedVideoItem(item, sectionId);
17
21
  const [iframeRef, setIframeRef] = useState<HTMLIFrameElement | null>(null);
18
22
  const vimeoPlayer = useMemo(() => iframeRef ? new Player(iframeRef) : undefined, [iframeRef]);
19
- const angle = useItemAngle(item, sectionId);
23
+ const itemAngle = useItemAngle(item, sectionId);
20
24
  const { play, controls, loop, muted, pictureInPicture, url } = item.commonParams;
21
25
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
22
26
  const [imgRef, setImgRef] = useState<HTMLImageElement | null>(null);
23
27
  const [isCoverVisible, setIsCoverVisible] = useState(false);
24
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
28
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
29
+ const wrapperStateParams = interactionCtrl?.getState(['angle', 'blur', 'opacity']);
30
+ const frameStateParams = interactionCtrl?.getState(['radius']);
31
+ const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
32
+ const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
33
+ const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
34
+ const radius = getStyleFromItemStateAndParams(frameStateParams?.styles?.radius, itemRadius);
25
35
  useRegisterResize(ref, onResize);
26
36
  const getValidVimeoUrl = (url: string): string => {
27
37
  const validURL = new URL(url);
@@ -56,6 +66,10 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
56
66
  vimeoPlayer!.play();
57
67
  setIsCoverVisible(false);
58
68
  };
69
+ const isInteractive = opacity !== 0;
70
+ useEffect(() => {
71
+ onVisibilityChange?.(isInteractive);
72
+ }, [isInteractive, onVisibilityChange]);
59
73
 
60
74
  return (
61
75
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
@@ -66,6 +80,7 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
66
80
  ...(opacity !== undefined ? { opacity } : {}),
67
81
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
68
82
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
83
+ transition: wrapperStateParams?.transition ?? 'none'
69
84
  }}
70
85
  onMouseEnter={() => {
71
86
  if (!vimeoPlayer || play !== 'on-hover') return;
@@ -96,12 +111,13 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
96
111
  )}
97
112
  <iframe
98
113
  ref={setIframeRef}
99
- className="embedVideo"
114
+ className={`embed-video`}
100
115
  src={validUrl || ''}
101
116
  allow="autoplay; fullscreen; picture-in-picture;"
102
117
  allowFullScreen
103
118
  style={{
104
- ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {})
119
+ ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
120
+ transition: frameStateParams?.transition ?? 'none'
105
121
  }}
106
122
  />
107
123
  </div>
@@ -111,30 +127,22 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
111
127
  width: 100%;
112
128
  height: 100%;
113
129
  }
114
- .embedVideo {
130
+ .embed-video {
115
131
  width: 100%;
116
132
  height: 100%;
117
133
  z-index: 1;
118
134
  border: none;
119
135
  overflow: hidden;
120
136
  }
121
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
137
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
122
138
  return (`
123
139
  .embed-video-wrapper-${item.id} {
124
140
  opacity: ${layoutParams.opacity};
125
141
  transform: rotate(${area.angle}deg);
126
142
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
127
- transition: ${getTransitions<ArticleItemType.VimeoEmbed>(['angle', 'blur', 'opacity'], hoverParams)};
128
- }
129
- .embed-video-wrapper-${item.id}:hover {
130
- ${getHoverStyles<ArticleItemType.VimeoEmbed>(['angle', 'blur', 'opacity'], hoverParams)}
131
143
  }
132
- .embed-video-wrapper-${item.id} .embedVideo {
144
+ .embed-video-wrapper-${item.id} .embed-video {
133
145
  border-radius: ${layoutParams.radius * 100}vw;
134
- transition: ${getTransitions<ArticleItemType.VimeoEmbed>(['radius'], hoverParams)};
135
- }
136
- .embed-video-wrapper-${item.id}:hover .embedVideo {
137
- ${getHoverStyles<ArticleItemType.VimeoEmbed>(['radius'], hoverParams)};
138
146
  }
139
147
  `);
140
148
  })}
@@ -5,27 +5,36 @@ import { LinkWrapper } from '../LinkWrapper';
5
5
  import { getYoutubeId } from '../../utils/getValidYoutubeUrl';
6
6
  import { useEmbedVideoItem } from './useEmbedVideoItem';
7
7
  import { useItemAngle } from '../useItemAngle';
8
- import { ArticleItemType, getLayoutStyles, YoutubeEmbedItem as TYoutubeEmbedItem } from '@cntrl-site/sdk';
9
- import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
8
+ import { getLayoutStyles, YoutubeEmbedItem as TYoutubeEmbedItem } from '@cntrl-site/sdk';
10
9
  import { useCntrlContext } from '../../provider/useCntrlContext';
11
10
  import { useYouTubeIframeApi } from '../../utils/Youtube/useYouTubeIframeApi';
12
11
  import { YTPlayer } from '../../utils/Youtube/YoutubeIframeApi';
13
12
  import { useRegisterResize } from "../../common/useRegisterResize";
13
+ import { getStyleFromItemStateAndParams } from '../../utils/getStyleFromItemStateAndParams';
14
14
 
15
- export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, sectionId, onResize }) => {
15
+ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
16
16
  const id = useId();
17
17
  const { layouts } = useCntrlContext();
18
18
  const { play, controls, url } = item.commonParams;
19
- const { radius, blur, opacity } = useEmbedVideoItem(item, sectionId);
20
- const angle = useItemAngle(item, sectionId);
19
+ const { radius: itemRadius, blur: itemBlur, opacity: itemOpacity } = useEmbedVideoItem(item, sectionId);
20
+ const itemAngle = useItemAngle(item, sectionId);
21
21
  const YT = useYouTubeIframeApi();
22
22
  const [div, setDiv] = useState<HTMLDivElement | null>(null);
23
23
  const [player, setPlayer] = useState<YTPlayer | undefined>(undefined);
24
24
  const [isCoverVisible, setIsCoverVisible] = useState(false);
25
25
  const [imgRef, setImgRef] = useState<HTMLImageElement | null>(null);
26
- const layoutValues: Record<string, any>[] = [item.area, item.layoutParams, item.state.hover];
26
+ const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
27
+ const wrapperStateParams = interactionCtrl?.getState(['angle', 'blur', 'opacity']);
28
+ const frameStateParams = interactionCtrl?.getState(['radius']);
29
+ const angle = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.angle, itemAngle);
30
+ const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
31
+ const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
32
+ const radius = getStyleFromItemStateAndParams(frameStateParams?.styles?.radius, itemRadius);
33
+ const isInteractive = opacity !== 0;
34
+ useEffect(() => {
35
+ onVisibilityChange?.(isInteractive);
36
+ }, [isInteractive, onVisibilityChange]);
27
37
  useRegisterResize(div, onResize);
28
-
29
38
  useEffect(() => {
30
39
  const newUrl = new URL(url);
31
40
  const videoId = getYoutubeId(newUrl);
@@ -91,6 +100,7 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
91
100
  ...(opacity !== undefined ? { opacity } : {}),
92
101
  ...(angle !== undefined ? { transform: `rotate(${angle}deg)` } : {}),
93
102
  ...(blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {}),
103
+ transition: wrapperStateParams?.transition ?? 'none'
94
104
  }}
95
105
  >
96
106
  {item.commonParams.coverUrl && (
@@ -116,8 +126,8 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
116
126
  className={`embed-${item.id}`}
117
127
  ref={setDiv}
118
128
  style={{
119
- ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
120
-
129
+ ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
130
+ transition: frameStateParams?.transition ?? 'none'
121
131
  }}
122
132
  />
123
133
  </div>
@@ -137,23 +147,15 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
137
147
  z-index: 1;
138
148
  border: none;
139
149
  }
140
- ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams, hoverParams]) => {
150
+ ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
141
151
  return (`
142
152
  .embed-youtube-video-wrapper-${item.id} {
143
153
  opacity: ${layoutParams.opacity};
144
154
  transform: rotate(${area.angle}deg);
145
155
  filter: ${layoutParams.blur !== 0 ? `blur(${layoutParams.blur * 100}vw)` : 'unset'};
146
- transition: ${getTransitions<ArticleItemType.YoutubeEmbed>(['angle', 'blur', 'opacity'], hoverParams)};
147
156
  }
148
157
  .embed-youtube-video-wrapper-${item.id} .embed-${item.id} {
149
158
  border-radius: ${layoutParams.radius * 100}vw;
150
- transition: ${getTransitions<ArticleItemType.YoutubeEmbed>(['radius'], hoverParams)};
151
- }
152
- .embed-youtube-video-wrapper-${item.id}:hover {
153
- ${getHoverStyles<ArticleItemType.YoutubeEmbed>(['angle', 'blur', 'opacity'], hoverParams)};
154
- }
155
- .embed-youtube-video-wrapper-${item.id}:hover .embed-${item.id} {
156
- ${getHoverStyles<ArticleItemType.YoutubeEmbed>(['radius'], hoverParams)};
157
159
  }
158
160
  `);
159
161
  })}