@cntrl-site/sdk-nextjs 1.0.20 → 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 +7 -11
  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,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
  })}
@@ -3,7 +3,11 @@ import { useKeyframeValue } from '../common/useKeyframeValue';
3
3
  import { getItemTopStyle } from '../utils/getItemTopStyle';
4
4
  import { useLayoutContext } from './useLayoutContext';
5
5
 
6
- export const useItemPosition = (item: ItemAny, sectionId: string) => {
6
+ export const useItemPosition = (
7
+ item: ItemAny,
8
+ sectionId: string,
9
+ stateValues: { top?: number; left?: number; }
10
+ ) => {
7
11
  const layoutId = useLayoutContext();
8
12
  const position = useKeyframeValue<{ top: number; left: number } | undefined>(
9
13
  item,
@@ -18,12 +22,15 @@ export const useItemPosition = (item: ItemAny, sectionId: string) => {
18
22
  );
19
23
  const anchorSide = layoutId ? item.area[layoutId].anchorSide : AnchorSide.Top;
20
24
  const positionType = layoutId ? item.area[layoutId].positionType : PositionType.ScreenBased;
21
- // tp prevent fixed item (with anchor point bottom) to jump when scroll in safari on mobile
25
+ // to prevent fixed item (with anchor point bottom) to jump when scroll in safari on mobile
22
26
  const isScreenBasedBottom = positionType === PositionType.ScreenBased && anchorSide === AnchorSide.Bottom;
27
+ if (!position) return undefined;
28
+ const top = stateValues?.top ?? position.top;
29
+ const left = stateValues?.left ?? position.left;
23
30
  return position ? {
24
- bottom: isScreenBasedBottom ? `${-position.top * 100}vw` : 'unset',
25
- top: isScreenBasedBottom ? 'unset' : getItemTopStyle(position.top, anchorSide),
26
- left: `${position.left * 100}vw`
31
+ bottom: isScreenBasedBottom ? `${-top * 100}vw` : 'unset',
32
+ top: isScreenBasedBottom ? 'unset' : getItemTopStyle(top, anchorSide),
33
+ left: `${left * 100}vw`
27
34
  } : undefined;
28
35
  };
29
36
 
@@ -0,0 +1,38 @@
1
+ import { ArticleItemType, ItemState } from '@cntrl-site/sdk';
2
+
3
+ export const CSSPropertyNameMap: Record<keyof ItemState<ArticleItemType>, string> = {
4
+ 'width': 'width',
5
+ 'height': 'height',
6
+ 'top': 'top',
7
+ 'left': 'left',
8
+ 'scale': 'transform',
9
+ 'angle': 'transform',
10
+ 'opacity': 'opacity',
11
+ 'radius': 'border-radius',
12
+ 'strokeWidth': 'border-width',
13
+ 'strokeColor': 'border-color',
14
+ 'fillColor': 'background-color',
15
+ 'blur': 'filter',
16
+ 'backdropBlur': 'backdrop-filter',
17
+ 'letterSpacing': 'letter-spacing',
18
+ 'wordSpacing': 'word-spacing',
19
+ 'color': 'color'
20
+ };
21
+
22
+ const PropertyNameCSSMap: Record<string, string[]> = {
23
+ 'transform': ['angle', 'scale'],
24
+ 'border-radius': ['radius'],
25
+ 'border-width': ['strokeWidth'],
26
+ 'border-color': ['strokeColor'],
27
+ 'background-color': ['fillColor'],
28
+ 'filter': ['blur'],
29
+ 'backdrop-filter': ['backdrop-blur'],
30
+ 'letter-spacing': ['letterSpacing'],
31
+ 'word-spacing': ['wordSpacing'],
32
+ };
33
+
34
+
35
+ export function getStyleKeysFromCSSProperty(cssProp: string): string[] {
36
+ const key = PropertyNameCSSMap[cssProp] ?? [cssProp];
37
+ return key;
38
+ }