@cntrl-site/sdk-nextjs 1.8.30-alpha.1 → 1.8.31

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.
Binary file
@@ -24,15 +24,18 @@ const useRegisterResize_1 = require("../../../common/useRegisterResize");
24
24
  const getStyleFromItemStateAndParams_1 = require("../../../utils/getStyleFromItemStateAndParams");
25
25
  const useEmbedVideoItem_1 = require("./useEmbedVideoItem");
26
26
  const LinkWrapper_1 = require("../LinkWrapper");
27
+ const useLayoutContext_1 = require("../../useLayoutContext");
27
28
  const VimeoEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
28
29
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
29
30
  const id = (0, react_1.useId)();
30
31
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
31
32
  const { radius: itemRadius, blur: itemBlur, opacity: itemOpacity } = (0, useEmbedVideoItem_1.useEmbedVideoItem)(item, sectionId);
33
+ const layoutId = (0, useLayoutContext_1.useLayoutContext)();
32
34
  const [iframeRef, setIframeRef] = (0, react_1.useState)(null);
33
35
  const vimeoPlayer = (0, react_1.useMemo)(() => iframeRef ? new player_1.default(iframeRef) : undefined, [iframeRef]);
34
36
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
35
- const { play, controls, loop, muted, pictureInPicture, url } = item.commonParams;
37
+ const { url } = item.commonParams;
38
+ const layoutParams = layoutId ? item.layoutParams[layoutId] : null;
36
39
  const [ref, setRef] = (0, react_1.useState)(null);
37
40
  const [imgRef, setImgRef] = (0, react_1.useState)(null);
38
41
  const [isCoverVisible, setIsCoverVisible] = (0, react_1.useState)(false);
@@ -44,24 +47,25 @@ const VimeoEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibili
44
47
  const opacity = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_c = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.styles) === null || _c === void 0 ? void 0 : _c.opacity, itemOpacity);
45
48
  const radius = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_d = frameStateParams === null || frameStateParams === void 0 ? void 0 : frameStateParams.styles) === null || _d === void 0 ? void 0 : _d.radius, itemRadius);
46
49
  (0, useRegisterResize_1.useRegisterResize)(ref, onResize);
47
- const getValidVimeoUrl = (url) => {
50
+ const validUrl = (0, react_1.useMemo)(() => {
51
+ if (!layoutParams)
52
+ return url;
48
53
  const validURL = new URL(url);
49
- validURL.searchParams.append('controls', String(controls));
50
- validURL.searchParams.append('autoplay', String(play === 'auto'));
51
- validURL.searchParams.append('muted', String(muted));
52
- validURL.searchParams.append('loop', String(loop));
53
- validURL.searchParams.append('pip', String(pictureInPicture));
54
+ validURL.searchParams.append('controls', String(layoutParams.controls));
55
+ validURL.searchParams.append('autoplay', String(layoutParams.play === 'auto'));
56
+ validURL.searchParams.append('muted', String(layoutParams.muted));
57
+ validURL.searchParams.append('loop', String(layoutParams.loop));
58
+ validURL.searchParams.append('pip', String(layoutParams.pictureInPicture));
54
59
  validURL.searchParams.append('title', '0');
55
60
  validURL.searchParams.append('byline', '0');
56
61
  validURL.searchParams.append('portrait', '0');
57
62
  validURL.searchParams.append('autopause', 'false');
58
63
  return validURL.href;
59
- };
60
- const validUrl = getValidVimeoUrl(url);
64
+ }, [url, layoutParams]);
61
65
  (0, react_1.useEffect)(() => {
62
- if (!vimeoPlayer || !imgRef)
66
+ if (!vimeoPlayer || !imgRef || !layoutParams)
63
67
  return;
64
- if (play === 'on-click') {
68
+ if (layoutParams.play === 'on-click') {
65
69
  setIsCoverVisible(true);
66
70
  }
67
71
  vimeoPlayer.on('pause', (e) => {
@@ -113,11 +117,11 @@ const VimeoEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibili
113
117
  });
114
118
  }, [interactionCtrl, vimeoPlayer]);
115
119
  return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, { url: (_e = item.link) === null || _e === void 0 ? void 0 : _e.url, target: (_f = item.link) === null || _f === void 0 ? void 0 : _f.target, children: [(0, jsx_runtime_1.jsxs)("div", { className: `embed-video-wrapper-${item.id}`, ref: setRef, style: Object.assign(Object.assign(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg) translateZ(0)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { transition: (_g = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _g !== void 0 ? _g : 'none' }), onMouseEnter: () => {
116
- if (!vimeoPlayer || play !== 'on-hover')
120
+ if (!vimeoPlayer || !layoutParams || layoutParams.play !== 'on-hover')
117
121
  return;
118
122
  vimeoPlayer.play();
119
123
  }, onMouseLeave: () => {
120
- if (!vimeoPlayer || play !== 'on-hover')
124
+ if (!vimeoPlayer || !layoutParams || layoutParams.play !== 'on-hover')
121
125
  return;
122
126
  vimeoPlayer.pause();
123
127
  }, children: [item.commonParams.coverUrl && ((0, jsx_runtime_1.jsx)("img", { ref: setImgRef, onClick: () => onCoverClick(), src: (_h = item.commonParams.coverUrl) !== null && _h !== void 0 ? _h : '', style: {
@@ -129,7 +133,7 @@ const VimeoEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibili
129
133
  width: '100%',
130
134
  top: '0',
131
135
  left: '0'
132
- }, alt: "Cover img" })), (!item.commonParams.controls && (play === 'on-click' || play === 'auto')) && ((0, jsx_runtime_1.jsx)("div", { onClick: handleClick, style: {
136
+ }, alt: "Cover img" })), layoutParams && (!layoutParams.controls && (layoutParams.play === 'on-click' || layoutParams.play === 'auto')) && ((0, jsx_runtime_1.jsx)("div", { onClick: handleClick, style: {
133
137
  position: 'absolute',
134
138
  height: '100%',
135
139
  width: '100%',
@@ -16,11 +16,14 @@ const useCntrlContext_1 = require("../../../provider/useCntrlContext");
16
16
  const useYouTubeIframeApi_1 = require("../../../utils/Youtube/useYouTubeIframeApi");
17
17
  const useRegisterResize_1 = require("../../../common/useRegisterResize");
18
18
  const getStyleFromItemStateAndParams_1 = require("../../../utils/getStyleFromItemStateAndParams");
19
+ const useLayoutContext_1 = require("../../useLayoutContext");
19
20
  const YoutubeEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
20
21
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
21
22
  const id = (0, react_1.useId)();
22
23
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
23
- const { play, controls, url } = item.commonParams;
24
+ const { url } = item.commonParams;
25
+ const layoutId = (0, useLayoutContext_1.useLayoutContext)();
26
+ const layoutParams = layoutId ? item.layoutParams[layoutId] : null;
24
27
  const { radius: itemRadius, blur: itemBlur, opacity: itemOpacity } = (0, useEmbedVideoItem_1.useEmbedVideoItem)(item, sectionId);
25
28
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
26
29
  const YT = (0, useYouTubeIframeApi_1.useYouTubeIframeApi)();
@@ -43,8 +46,9 @@ const YoutubeEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibi
43
46
  (0, react_1.useEffect)(() => {
44
47
  const newUrl = new URL(url);
45
48
  const videoId = (0, getValidYoutubeUrl_1.getYoutubeId)(newUrl);
46
- if (!YT || !videoId || !div)
49
+ if (!YT || !videoId || !div || !layoutParams)
47
50
  return;
51
+ const { play, controls } = layoutParams;
48
52
  const placeholder = document.createElement('div');
49
53
  div.appendChild(placeholder);
50
54
  const player = new YT.Player(placeholder, {
@@ -78,7 +82,7 @@ const YoutubeEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibi
78
82
  player.destroy();
79
83
  (_a = placeholder.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(placeholder);
80
84
  };
81
- }, [YT, div]);
85
+ }, [YT, div, layoutParams]);
82
86
  const onCoverClick = () => {
83
87
  if (!player || !imgRef)
84
88
  return;
@@ -86,10 +90,10 @@ const YoutubeEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibi
86
90
  setIsCoverVisible(false);
87
91
  };
88
92
  (0, react_1.useEffect)(() => {
89
- if (play === 'on-click' && !controls) {
93
+ if (layoutParams && layoutParams.play === 'on-click' && !layoutParams.controls) {
90
94
  setIsCoverVisible(true);
91
95
  }
92
- }, []);
96
+ }, [layoutParams]);
93
97
  (0, react_1.useEffect)(() => {
94
98
  onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isInteractive);
95
99
  if (!isInteractive && player) {
@@ -111,11 +115,11 @@ const YoutubeEmbedItem = ({ item, sectionId, onResize, interactionCtrl, onVisibi
111
115
  });
112
116
  }, [interactionCtrl, player]);
113
117
  return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, { url: (_e = item.link) === null || _e === void 0 ? void 0 : _e.url, target: (_f = item.link) === null || _f === void 0 ? void 0 : _f.target, children: [(0, jsx_runtime_1.jsxs)("div", { className: `embed-youtube-video-wrapper-${item.id}`, onMouseEnter: () => {
114
- if (!player || play !== 'on-hover')
118
+ if (!player || !layoutParams || layoutParams.play !== 'on-hover')
115
119
  return;
116
120
  player.playVideo();
117
121
  }, onMouseLeave: () => {
118
- if (!player || play !== 'on-hover')
122
+ if (!player || !layoutParams || layoutParams.play !== 'on-hover')
119
123
  return;
120
124
  player.pauseVideo();
121
125
  }, style: Object.assign(Object.assign(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg) translateZ(0)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { transition: (_g = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _g !== void 0 ? _g : 'none' }), children: [item.commonParams.coverUrl && ((0, jsx_runtime_1.jsx)("img", { ref: setImgRef, onClick: () => onCoverClick(), src: (_h = item.commonParams.coverUrl) !== null && _h !== void 0 ? _h : '', style: {
@@ -21,18 +21,23 @@ const useVideoFx_1 = require("../../../utils/effects/useVideoFx");
21
21
  const useElementRect_1 = require("../../../utils/useElementRect");
22
22
  const useItemFXData_1 = require("../../../common/useItemFXData");
23
23
  const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
24
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
24
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
25
25
  const id = (0, react_1.useId)();
26
26
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
27
27
  const layoutId = (0, useLayoutContext_1.useLayoutContext)();
28
28
  const { radius: itemRadius, strokeWidth: itemStrokeWidth, strokeColor: itemStrokeColor, opacity: itemOpacity, blur: itemBlur } = (0, useFileItem_1.useFileItem)(item, sectionId);
29
+ const [isVideoPlaying, setIsVideoPlaying] = (0, react_1.useState)(false);
30
+ const isScrollPausedRef = (0, react_1.useRef)(false);
31
+ const [userPaused, setUserPaused] = (0, react_1.useState)(false);
32
+ const [isVideoInteracted, setIsVideoInteracted] = (0, react_1.useState)(false);
29
33
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
30
34
  const [ref, setRef] = (0, react_1.useState)(null);
31
- const videoRef = (0, react_1.useRef)(null);
35
+ const [videoRef, setVideoRef] = (0, react_1.useState)(null);
32
36
  const fxCanvas = (0, react_1.useRef)(null);
33
37
  const { url, hasGLEffect } = item.commonParams;
34
38
  const isInitialRef = (0, react_1.useRef)(true);
35
39
  const area = layoutId ? item.area[layoutId] : null;
40
+ const layoutParams = layoutId ? item.layoutParams[layoutId] : null;
36
41
  const exemplary = (_a = layouts === null || layouts === void 0 ? void 0 : layouts.find(l => l.id === layoutId)) === null || _a === void 0 ? void 0 : _a.exemplary;
37
42
  const width = area && exemplary ? area.width * exemplary : 0;
38
43
  const height = area && exemplary ? area.height * exemplary : 0;
@@ -40,7 +45,7 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
40
45
  const rect = (0, useElementRect_1.useElementRect)(ref);
41
46
  const rectWidth = Math.floor((_b = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _b !== void 0 ? _b : 0);
42
47
  const rectHeight = Math.floor((_c = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _c !== void 0 ? _c : 0);
43
- const scrollPlayback = layoutId ? item.layoutParams[layoutId].scrollPlayback : null;
48
+ const scrollPlayback = layoutParams ? layoutParams.scrollPlayback : null;
44
49
  const layoutValues = [item.area, item.layoutParams];
45
50
  const hasScrollPlayback = scrollPlayback !== null;
46
51
  const wrapperStateParams = interactionCtrl === null || interactionCtrl === void 0 ? void 0 : interactionCtrl.getState(['angle', 'opacity', 'blur']);
@@ -69,11 +74,63 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
69
74
  (0, react_1.useEffect)(() => {
70
75
  onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isInteractive);
71
76
  }, [isInteractive, onVisibilityChange]);
72
- return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, { url: (_l = item.link) === null || _l === void 0 ? void 0 : _l.url, target: (_m = item.link) === null || _m === void 0 ? void 0 : _m.target, children: [(0, jsx_runtime_1.jsx)("div", { className: `video-wrapper-${item.id}`, ref: setRef, style: Object.assign(Object.assign(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg) translateZ(0)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { transition: (_o = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _o !== void 0 ? _o : 'none' }), children: hasScrollPlayback
73
- ? ((0, jsx_runtime_1.jsx)(ScrollPlaybackVideo_1.ScrollPlaybackVideo, { sectionId: sectionId, src: item.commonParams.url, playbackParams: scrollPlayback, style: inlineStyles, className: `video video-playback-wrapper video-${item.id}` }))
74
- : ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: hasGLEffect && isFXAllowed
75
- ? ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: `video-canvas video-${item.id}`, width: rectWidth, height: rectHeight }))
76
- : ((0, jsx_runtime_1.jsx)("video", { poster: (_p = item.commonParams.coverUrl) !== null && _p !== void 0 ? _p : '', ref: videoRef, autoPlay: true, muted: true, loop: true, playsInline: true, className: `video video-${item.id}`, style: inlineStyles, children: (0, jsx_runtime_1.jsx)("source", { src: item.commonParams.url }) })) })) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
77
+ (0, react_1.useEffect)(() => {
78
+ if (!layoutParams || !videoRef || layoutParams.play !== 'on-click' || !ref)
79
+ return;
80
+ const observer = new IntersectionObserver(([entry]) => {
81
+ if (userPaused || !isVideoInteracted)
82
+ return;
83
+ if (entry.isIntersecting) {
84
+ isScrollPausedRef.current = false;
85
+ videoRef.play();
86
+ }
87
+ else {
88
+ isScrollPausedRef.current = true;
89
+ videoRef.pause();
90
+ }
91
+ });
92
+ observer.observe(ref);
93
+ return () => observer.disconnect();
94
+ }, [layoutParams, videoRef, ref, userPaused, isVideoInteracted]);
95
+ return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, { url: (_l = item.link) === null || _l === void 0 ? void 0 : _l.url, target: (_m = item.link) === null || _m === void 0 ? void 0 : _m.target, children: [(0, jsx_runtime_1.jsxs)("div", { className: `video-wrapper-${item.id}`, ref: setRef, style: Object.assign(Object.assign(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg) translateZ(0)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { transition: (_o = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _o !== void 0 ? _o : 'none' }), children: [hasScrollPlayback && ((0, jsx_runtime_1.jsx)(ScrollPlaybackVideo_1.ScrollPlaybackVideo, { sectionId: sectionId, src: item.commonParams.url, playbackParams: scrollPlayback, style: inlineStyles, className: `video video-playback-wrapper video-${item.id}` })), hasGLEffect && isFXAllowed && ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: `video-canvas video-${item.id}`, width: rectWidth, height: rectHeight })), !hasScrollPlayback && !hasGLEffect && layoutParams && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("video", { poster: (_p = item.commonParams.coverUrl) !== null && _p !== void 0 ? _p : '', ref: setVideoRef, autoPlay: layoutParams.play === 'auto', preload: "auto", onClick: () => {
96
+ setIsVideoInteracted(true);
97
+ }, muted: layoutParams.muted, onPlay: () => {
98
+ setIsVideoPlaying(true);
99
+ setUserPaused(false);
100
+ }, onPause: () => {
101
+ if (!isScrollPausedRef.current) {
102
+ setUserPaused(true);
103
+ }
104
+ setIsVideoPlaying(false);
105
+ }, onMouseEnter: () => {
106
+ if (!videoRef || layoutParams.play !== 'on-hover')
107
+ return;
108
+ videoRef.play();
109
+ }, onMouseLeave: () => {
110
+ if (!videoRef || layoutParams.play !== 'on-hover')
111
+ return;
112
+ videoRef.pause();
113
+ }, loop: true, controls: layoutParams.controls, playsInline: true, className: `video video-${item.id}`, style: inlineStyles, children: (0, jsx_runtime_1.jsx)("source", { src: item.commonParams.url }) }), (layoutParams.play === 'on-click' || layoutParams.play === 'on-hover') && item.commonParams.coverUrl && !isVideoInteracted && ((0, jsx_runtime_1.jsx)("img", { onMouseEnter: () => {
114
+ if (!videoRef || layoutParams.play !== 'on-hover')
115
+ return;
116
+ setIsVideoInteracted(true);
117
+ videoRef.play();
118
+ }, src: (_q = item.commonParams.coverUrl) !== null && _q !== void 0 ? _q : '', className: `video-cover-${item.id}`, onClick: () => {
119
+ if (!videoRef)
120
+ return;
121
+ setIsVideoInteracted(true);
122
+ videoRef.play();
123
+ } })), (layoutParams.play === 'on-click' && !layoutParams.controls && ((0, jsx_runtime_1.jsx)("div", { className: `video-overlay-${item.id}`, onClick: () => {
124
+ if (!videoRef)
125
+ return;
126
+ setIsVideoInteracted(true);
127
+ if (isVideoPlaying) {
128
+ videoRef.pause();
129
+ }
130
+ else {
131
+ videoRef.play();
132
+ }
133
+ } })))] }))] }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
77
134
  .video-wrapper-${item.id} {
78
135
  position: absolute;
79
136
  overflow: hidden;
@@ -82,6 +139,23 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
82
139
  box-sizing: border-box;
83
140
  opacity: ${opacity};
84
141
  }
142
+ .video-overlay-${item.id} {
143
+ position: absolute;
144
+ top: 0;
145
+ left: 0;
146
+ cursor: pointer;
147
+ width: 100%;
148
+ height: 100%;
149
+ }
150
+ .video-cover-${item.id} {
151
+ position: absolute;
152
+ top: 0;
153
+ left: 0;
154
+ cursor: pointer;
155
+ width: 100%;
156
+ height: 100%;
157
+ object-fit: cover;
158
+ }
85
159
  .video {
86
160
  width: 100%;
87
161
  height: 100%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cntrl-site/sdk-nextjs",
3
- "version": "1.8.30-alpha.1",
3
+ "version": "1.8.31",
4
4
  "description": "SDK for Next.js",
5
5
  "author": "arsen@momdesign.nyc",
6
6
  "license": "MIT",
@@ -31,7 +31,7 @@
31
31
  "@antfu/eslint-config": "^3.8.0",
32
32
  "@cntrl-site/color": "^1.0.0",
33
33
  "@cntrl-site/effects": "^1.4.0",
34
- "@cntrl-site/sdk": "^1.22.21-alpha.1",
34
+ "@cntrl-site/sdk": "^1.22.19",
35
35
  "@types/vimeo__player": "^2.18.0",
36
36
  "@vimeo/player": "^2.25.0",
37
37
  "html-react-parser": "^3.0.1",
@@ -7,10 +7,11 @@ import { useEffect, useId, useMemo, useState } from 'react';
7
7
  import JSXStyle from 'styled-jsx/style';
8
8
  import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../../provider/useCntrlContext';
10
- import { useRegisterResize } from "../../../common/useRegisterResize";
10
+ import { useRegisterResize } from '../../../common/useRegisterResize';
11
11
  import { getStyleFromItemStateAndParams } from '../../../utils/getStyleFromItemStateAndParams';
12
12
  import { useEmbedVideoItem } from './useEmbedVideoItem';
13
13
  import { LinkWrapper } from '../LinkWrapper';
14
+ import { useLayoutContext } from '../../useLayoutContext';
14
15
 
15
16
  export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
16
17
  const id = useId();
@@ -20,10 +21,12 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
20
21
  blur: itemBlur,
21
22
  opacity: itemOpacity
22
23
  } = useEmbedVideoItem(item, sectionId);
24
+ const layoutId = useLayoutContext();
23
25
  const [iframeRef, setIframeRef] = useState<HTMLIFrameElement | null>(null);
24
26
  const vimeoPlayer = useMemo(() => iframeRef ? new Player(iframeRef) : undefined, [iframeRef]);
25
27
  const itemAngle = useItemAngle(item, sectionId);
26
- const { play, controls, loop, muted, pictureInPicture, url } = item.commonParams;
28
+ const { url } = item.commonParams;
29
+ const layoutParams = layoutId ? item.layoutParams[layoutId] : null;
27
30
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
28
31
  const [imgRef, setImgRef] = useState<HTMLImageElement | null>(null);
29
32
  const [isCoverVisible, setIsCoverVisible] = useState(false);
@@ -35,25 +38,24 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
35
38
  const opacity = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.opacity, itemOpacity);
36
39
  const radius = getStyleFromItemStateAndParams(frameStateParams?.styles?.radius, itemRadius);
37
40
  useRegisterResize(ref, onResize);
38
- const getValidVimeoUrl = (url: string): string => {
41
+ const validUrl = useMemo(() => {
42
+ if (!layoutParams) return url;
39
43
  const validURL = new URL(url);
40
- validURL.searchParams.append('controls', String(controls));
41
- validURL.searchParams.append('autoplay', String(play === 'auto'));
42
- validURL.searchParams.append('muted', String(muted));
43
- validURL.searchParams.append('loop', String(loop));
44
- validURL.searchParams.append('pip', String(pictureInPicture));
44
+ validURL.searchParams.append('controls', String(layoutParams.controls));
45
+ validURL.searchParams.append('autoplay', String(layoutParams.play === 'auto'));
46
+ validURL.searchParams.append('muted', String(layoutParams.muted));
47
+ validURL.searchParams.append('loop', String(layoutParams.loop));
48
+ validURL.searchParams.append('pip', String(layoutParams.pictureInPicture));
45
49
  validURL.searchParams.append('title', '0');
46
50
  validURL.searchParams.append('byline', '0');
47
51
  validURL.searchParams.append('portrait', '0');
48
52
  validURL.searchParams.append('autopause', 'false');
49
-
50
53
  return validURL.href;
51
- };
52
- const validUrl = getValidVimeoUrl(url);
54
+ }, [url, layoutParams]);
53
55
 
54
56
  useEffect(() => {
55
- if (!vimeoPlayer || !imgRef) return;
56
- if (play === 'on-click') {
57
+ if (!vimeoPlayer || !imgRef || !layoutParams) return;
58
+ if (layoutParams.play === 'on-click') {
57
59
  setIsCoverVisible(true);
58
60
  }
59
61
  vimeoPlayer.on('pause', (e) => {
@@ -95,7 +97,7 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
95
97
  useEffect(() => {
96
98
  if (!vimeoPlayer || !interactionCtrl) return;
97
99
  interactionCtrl.setActionReceiver((type) => {
98
- switch(type) {
100
+ switch (type) {
99
101
  case 'play':
100
102
  vimeoPlayer.play();
101
103
  break;
@@ -118,11 +120,11 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
118
120
  transition: wrapperStateParams?.transition ?? 'none'
119
121
  }}
120
122
  onMouseEnter={() => {
121
- if (!vimeoPlayer || play !== 'on-hover') return;
123
+ if (!vimeoPlayer || !layoutParams || layoutParams.play !== 'on-hover') return;
122
124
  vimeoPlayer.play();
123
125
  }}
124
126
  onMouseLeave={() => {
125
- if (!vimeoPlayer || play !== 'on-hover') return;
127
+ if (!vimeoPlayer || !layoutParams || layoutParams.play !== 'on-hover') return;
126
128
  vimeoPlayer.pause();
127
129
  }}
128
130
  >
@@ -145,7 +147,7 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
145
147
  />
146
148
  )}
147
149
  {/* ↓ This is necessary to track clicks on an iframe. */}
148
- {(!item.commonParams.controls && (play === 'on-click' || play === 'auto')) && (
150
+ {layoutParams && (!layoutParams.controls && (layoutParams.play === 'on-click' || layoutParams.play === 'auto')) && (
149
151
  <div
150
152
  onClick={handleClick}
151
153
  style={{
@@ -9,13 +9,16 @@ import { getLayoutStyles, YoutubeEmbedItem as TYoutubeEmbedItem } from '@cntrl-s
9
9
  import { useCntrlContext } from '../../../provider/useCntrlContext';
10
10
  import { useYouTubeIframeApi } from '../../../utils/Youtube/useYouTubeIframeApi';
11
11
  import { YTPlayer } from '../../../utils/Youtube/YoutubeIframeApi';
12
- import { useRegisterResize } from "../../../common/useRegisterResize";
12
+ import { useRegisterResize } from '../../../common/useRegisterResize';
13
13
  import { getStyleFromItemStateAndParams } from '../../../utils/getStyleFromItemStateAndParams';
14
+ import { useLayoutContext } from '../../useLayoutContext';
14
15
 
15
16
  export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
16
17
  const id = useId();
17
18
  const { layouts } = useCntrlContext();
18
- const { play, controls, url } = item.commonParams;
19
+ const { url } = item.commonParams;
20
+ const layoutId = useLayoutContext();
21
+ const layoutParams = layoutId ? item.layoutParams[layoutId] : null;
19
22
  const { radius: itemRadius, blur: itemBlur, opacity: itemOpacity } = useEmbedVideoItem(item, sectionId);
20
23
  const itemAngle = useItemAngle(item, sectionId);
21
24
  const YT = useYouTubeIframeApi();
@@ -38,7 +41,8 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
38
41
  useEffect(() => {
39
42
  const newUrl = new URL(url);
40
43
  const videoId = getYoutubeId(newUrl);
41
- if (!YT || !videoId || !div) return;
44
+ if (!YT || !videoId || !div || !layoutParams) return;
45
+ const { play, controls } = layoutParams;
42
46
  const placeholder = document.createElement('div');
43
47
  div.appendChild(placeholder);
44
48
  const player = new YT.Player(placeholder, {
@@ -53,7 +57,7 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
53
57
  if (event.data === 1) {
54
58
  setIsCoverVisible(false);
55
59
  }
56
- if (event.data === 2 || event.data === -1 ) {
60
+ if (event.data === 2 || event.data === -1) {
57
61
  setIsCoverVisible(true);
58
62
  }
59
63
  },
@@ -70,7 +74,7 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
70
74
  player.destroy();
71
75
  placeholder.parentElement?.removeChild(placeholder);
72
76
  };
73
- }, [YT, div]);
77
+ }, [YT, div, layoutParams]);
74
78
 
75
79
  const onCoverClick = () => {
76
80
  if (!player || !imgRef) return;
@@ -79,10 +83,10 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
79
83
  };
80
84
 
81
85
  useEffect(() => {
82
- if (play === 'on-click' && !controls) {
86
+ if (layoutParams && layoutParams.play === 'on-click' && !layoutParams.controls) {
83
87
  setIsCoverVisible(true);
84
88
  }
85
- }, []);
89
+ }, [layoutParams]);
86
90
 
87
91
  useEffect(() => {
88
92
  onVisibilityChange?.(isInteractive);
@@ -94,7 +98,7 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
94
98
  useEffect(() => {
95
99
  if (!player || !interactionCtrl) return;
96
100
  interactionCtrl.setActionReceiver((type) => {
97
- switch(type) {
101
+ switch (type) {
98
102
  case 'play':
99
103
  player.playVideo();
100
104
  break;
@@ -110,11 +114,11 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
110
114
  <div
111
115
  className={`embed-youtube-video-wrapper-${item.id}`}
112
116
  onMouseEnter={() => {
113
- if (!player || play !== 'on-hover') return;
117
+ if (!player || !layoutParams || layoutParams.play !== 'on-hover') return;
114
118
  player.playVideo();
115
119
  }}
116
120
  onMouseLeave={() => {
117
- if (!player || play !== 'on-hover') return;
121
+ if (!player || !layoutParams || layoutParams.play !== 'on-hover') return;
118
122
  player.pauseVideo();
119
123
  }}
120
124
  style={{
@@ -179,9 +183,9 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
179
183
  border-radius: ${layoutParams.radius * 100}vw;
180
184
  }
181
185
  `);
182
- })}
183
- `}</JSXStyle>
186
+ })}
187
+ `}
188
+ </JSXStyle>
184
189
  </LinkWrapper>
185
- )
190
+ );
186
191
  };
187
-
@@ -26,13 +26,18 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
26
26
  opacity: itemOpacity,
27
27
  blur: itemBlur
28
28
  } = useFileItem(item, sectionId);
29
+ const [isVideoPlaying, setIsVideoPlaying] = useState(false);
30
+ const isScrollPausedRef = useRef(false);
31
+ const [userPaused, setUserPaused] = useState(false);
32
+ const [isVideoInteracted, setIsVideoInteracted] = useState(false);
29
33
  const itemAngle = useItemAngle(item, sectionId);
30
34
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
31
- const videoRef = useRef<HTMLVideoElement | null>(null);
35
+ const [videoRef, setVideoRef] = useState<HTMLVideoElement | null>(null);
32
36
  const fxCanvas = useRef<HTMLCanvasElement | null>(null);
33
37
  const { url, hasGLEffect } = item.commonParams;
34
38
  const isInitialRef = useRef(true);
35
39
  const area = layoutId ? item.area[layoutId] : null;
40
+ const layoutParams = layoutId ? item.layoutParams[layoutId] : null;
36
41
  const exemplary = layouts?.find(l => l.id === layoutId)?.exemplary;
37
42
  const width = area && exemplary ? area.width * exemplary : 0;
38
43
  const height = area && exemplary ? area.height * exemplary : 0;
@@ -40,7 +45,7 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
40
45
  const rect = useElementRect(ref);
41
46
  const rectWidth = Math.floor(rect?.width ?? 0);
42
47
  const rectHeight = Math.floor(rect?.height ?? 0);
43
- const scrollPlayback = layoutId ? item.layoutParams[layoutId].scrollPlayback : null;
48
+ const scrollPlayback = layoutParams ? layoutParams.scrollPlayback : null;
44
49
  const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
45
50
  const hasScrollPlayback = scrollPlayback !== null;
46
51
  const wrapperStateParams = interactionCtrl?.getState(['angle', 'opacity', 'blur']);
@@ -80,6 +85,24 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
80
85
  onVisibilityChange?.(isInteractive);
81
86
  }, [isInteractive, onVisibilityChange]);
82
87
 
88
+ useEffect(() => {
89
+ if (!layoutParams || !videoRef || layoutParams.play !== 'on-click' || !ref) return;
90
+ const observer = new IntersectionObserver(
91
+ ([entry]) => {
92
+ if (userPaused || !isVideoInteracted) return;
93
+ if (entry.isIntersecting) {
94
+ isScrollPausedRef.current = false;
95
+ videoRef.play();
96
+ } else {
97
+ isScrollPausedRef.current = true;
98
+ videoRef.pause();
99
+ }
100
+ }
101
+ );
102
+ observer.observe(ref);
103
+ return () => observer.disconnect();
104
+ }, [layoutParams, videoRef, ref, userPaused, isVideoInteracted]);
105
+
83
106
  return (
84
107
  <LinkWrapper url={item.link?.url} target={item.link?.target}>
85
108
  <div
@@ -92,45 +115,93 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
92
115
  transition: wrapperStateParams?.transition ?? 'none'
93
116
  }}
94
117
  >
95
- {hasScrollPlayback
96
- ? (
97
- <ScrollPlaybackVideo
98
- sectionId={sectionId}
99
- src={item.commonParams.url}
100
- playbackParams={scrollPlayback}
101
- style={inlineStyles}
102
- className={`video video-playback-wrapper video-${item.id}`}
118
+ {hasScrollPlayback && (
119
+ <ScrollPlaybackVideo
120
+ sectionId={sectionId}
121
+ src={item.commonParams.url}
122
+ playbackParams={scrollPlayback}
123
+ style={inlineStyles}
124
+ className={`video video-playback-wrapper video-${item.id}`}
125
+ />
126
+ )}
127
+ {hasGLEffect && isFXAllowed && (
128
+ <canvas
129
+ style={inlineStyles}
130
+ ref={fxCanvas}
131
+ className={`video-canvas video-${item.id}`}
132
+ width={rectWidth}
133
+ height={rectHeight}
134
+ />
135
+ )}
136
+ {!hasScrollPlayback && !hasGLEffect && layoutParams && (
137
+ <>
138
+ <video
139
+ poster={item.commonParams.coverUrl ?? ''}
140
+ ref={setVideoRef}
141
+ autoPlay={layoutParams.play === 'auto'}
142
+ preload="auto"
143
+ onClick={() => {
144
+ setIsVideoInteracted(true);
145
+ }}
146
+ muted={layoutParams.muted}
147
+ onPlay={() => {
148
+ setIsVideoPlaying(true);
149
+ setUserPaused(false);
150
+ }}
151
+ onPause={() => {
152
+ if (!isScrollPausedRef.current) {
153
+ setUserPaused(true);
154
+ }
155
+ setIsVideoPlaying(false);
156
+ }}
157
+ onMouseEnter={() => {
158
+ if (!videoRef || layoutParams.play !== 'on-hover') return;
159
+ videoRef.play();
160
+ }}
161
+ onMouseLeave={() => {
162
+ if (!videoRef || layoutParams.play !== 'on-hover') return;
163
+ videoRef.pause();
164
+ }}
165
+ loop
166
+ controls={layoutParams.controls}
167
+ playsInline
168
+ className={`video video-${item.id}`}
169
+ style={inlineStyles}
170
+ >
171
+ <source src={item.commonParams.url} />
172
+ </video>
173
+ {(layoutParams.play === 'on-click' || layoutParams.play === 'on-hover') && item.commonParams.coverUrl && !isVideoInteracted && (
174
+ <img
175
+ onMouseEnter={() => {
176
+ if (!videoRef || layoutParams.play !== 'on-hover') return;
177
+ setIsVideoInteracted(true);
178
+ videoRef.play();
179
+ }}
180
+ src={item.commonParams.coverUrl ?? ''}
181
+ className={`video-cover-${item.id}`}
182
+ onClick={() => {
183
+ if (!videoRef) return;
184
+ setIsVideoInteracted(true);
185
+ videoRef.play();
186
+ }}
103
187
  />
104
- )
105
- : (
106
- <>
107
- {hasGLEffect && isFXAllowed
108
- ? (
109
- <canvas
110
- style={inlineStyles}
111
- ref={fxCanvas}
112
- className={`video-canvas video-${item.id}`}
113
- width={rectWidth}
114
- height={rectHeight}
115
- />
116
- )
117
- : (
118
- <video
119
- poster={item.commonParams.coverUrl ?? ''}
120
- ref={videoRef}
121
- autoPlay
122
- muted
123
- loop
124
- playsInline
125
- className={`video video-${item.id}`}
126
- style={inlineStyles}
127
- >
128
- <source src={item.commonParams.url} />
129
- </video>
130
- )}
131
-
132
- </>
133
188
  )}
189
+ {(layoutParams.play === 'on-click' && !layoutParams.controls && (
190
+ <div
191
+ className={`video-overlay-${item.id}`}
192
+ onClick={() => {
193
+ if (!videoRef) return;
194
+ setIsVideoInteracted(true);
195
+ if (isVideoPlaying) {
196
+ videoRef.pause();
197
+ } else {
198
+ videoRef.play();
199
+ }
200
+ }}
201
+ />
202
+ ))}
203
+ </>
204
+ )}
134
205
  </div>
135
206
  <JSXStyle id={id}>{`
136
207
  .video-wrapper-${item.id} {
@@ -141,6 +212,23 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
141
212
  box-sizing: border-box;
142
213
  opacity: ${opacity};
143
214
  }
215
+ .video-overlay-${item.id} {
216
+ position: absolute;
217
+ top: 0;
218
+ left: 0;
219
+ cursor: pointer;
220
+ width: 100%;
221
+ height: 100%;
222
+ }
223
+ .video-cover-${item.id} {
224
+ position: absolute;
225
+ top: 0;
226
+ left: 0;
227
+ cursor: pointer;
228
+ width: 100%;
229
+ height: 100%;
230
+ object-fit: cover;
231
+ }
144
232
  .video {
145
233
  width: 100%;
146
234
  height: 100%;