Compare commits
13 Commits
ec76d2cfc9
...
develop_si
| Author | SHA1 | Date | |
|---|---|---|---|
| daac18afa8 | |||
| adfede9b44 | |||
| 1fae88878f | |||
| 4ecb03002f | |||
| 27e1e3bb6a | |||
| b9d52d452c | |||
| fd55c04c83 | |||
| 30472bfe17 | |||
|
|
51d587a1a1 | ||
|
|
349688092c | ||
| d933ca6bb7 | |||
| e86b56e14e | |||
|
|
eee8e73b97 |
@@ -779,6 +779,8 @@
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.times {
|
||||
|
||||
@@ -639,7 +639,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
{/* Overlay */}
|
||||
<Overlay bottomControlsVisible={controlsVisible} onClick={handleVideoClick}>
|
||||
{/* Loading + Thumbnail */}
|
||||
{loading && thumbnailUrl && (
|
||||
{/* {loading && thumbnailUrl && (
|
||||
<>
|
||||
<p className={classNames(css.thumbnail, isModal && css.smallThumbnail)}>
|
||||
<img src={thumbnailUrl} alt="" />
|
||||
@@ -648,7 +648,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => {
|
||||
<Loader />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{/* Controls with MediaSlider */}
|
||||
{controlsVisible && !isModal && (
|
||||
|
||||
@@ -695,6 +695,7 @@
|
||||
height: 70px;
|
||||
width:1800px;
|
||||
margin-left:60px;
|
||||
margin-right: 59px;
|
||||
bottom:92px;
|
||||
> *:first-child {
|
||||
text-align: right;
|
||||
|
||||
@@ -2467,9 +2467,9 @@ const VideoPlayerBase = class extends React.Component {
|
||||
</p>
|
||||
}
|
||||
|
||||
<div className={css.loaderWrap}>
|
||||
{/* <div className={css.loaderWrap}>
|
||||
<Loader />
|
||||
</div>
|
||||
</div> */}
|
||||
</>
|
||||
) : null}
|
||||
{this.state.mediaControlsVisible && (
|
||||
@@ -2534,7 +2534,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
videoVerticalVisible && css.videoVertical
|
||||
)}
|
||||
>
|
||||
{this.state.mediaSliderVisible && type ? (
|
||||
{this.state.mediaSliderVisible && type && !panelInfo.modal ? (
|
||||
<Times
|
||||
noCurrentTime
|
||||
total={type === 'LIVE' ? liveTotalTime : this.state.duration}
|
||||
@@ -2542,7 +2542,7 @@ const VideoPlayerBase = class extends React.Component {
|
||||
type={type}
|
||||
/>
|
||||
) : null}
|
||||
{this.state.mediaSliderVisible && type ? (
|
||||
{this.state.mediaSliderVisible && type && !panelInfo.modal ? (
|
||||
<Times
|
||||
noTotalTime
|
||||
current={type === 'LIVE' ? currentLiveTimeSeconds : this.state.currentTime}
|
||||
|
||||
@@ -718,7 +718,7 @@
|
||||
}
|
||||
|
||||
.sliderContainer {
|
||||
// display: flex;
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
margin-left: 60px;
|
||||
@@ -727,6 +727,10 @@
|
||||
bottom: -20px;
|
||||
> *:first-child {
|
||||
text-align: right;
|
||||
margin-right: 12px;
|
||||
}
|
||||
> *:nth-child(2) {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.enact-locale-rtl({
|
||||
@@ -787,79 +791,4 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ========== MediaPlayer.v2 Controls ==========
|
||||
.controlsContainer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20px 40px 30px;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.7) 60%, transparent 100%);
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.sliderContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.times {
|
||||
min-width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.controlsButtons {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.playPauseBtn {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
font-size: 24px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 2px solid rgba(255, 255, 255, 0.6);
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.backBtn {
|
||||
padding: 12px 24px;
|
||||
font-size: 18px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 2px solid rgba(255, 255, 255, 0.6);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
.position(@position: absolute, @top: 0, @left: 0, @right: 0, @bottom: 0);
|
||||
z-index: 19;
|
||||
border: 4px solid @PRIMARY_COLOR_RED;
|
||||
box-shadow: 0 0 @boxShadow 0 rgba(0, 0, 0, 0.5);
|
||||
border-radius: @borderRadius;
|
||||
content: "";
|
||||
}
|
||||
|
||||
@@ -183,6 +183,57 @@ const ButtonStackContainer = SpotlightContainerDecorator(
|
||||
|
||||
const SpottableComponent = Spottable('div');
|
||||
|
||||
// 🔽 [251217] ProductAllSection 스크롤 컨테이너 위치 저장/검증 함수
|
||||
// PlayerPanel의 배너 위치 검증 패턴을 ProductAllSection에 적용
|
||||
|
||||
/**
|
||||
* ProductAllSection의 스크롤 컨테이너 위치 수집 함수
|
||||
* @returns {Object|null} 스크롤 컨테이너의 위치 정보 (top, left) 또는 null
|
||||
*/
|
||||
const collectProductScrollPosition = () => {
|
||||
const scrollContainer = document.querySelector('[data-spotlight-id="main-content-scroller"]');
|
||||
|
||||
if (scrollContainer) {
|
||||
const { top, left } = scrollContainer.getBoundingClientRect();
|
||||
const position = {
|
||||
top: Math.round(top),
|
||||
left: Math.round(left)
|
||||
};
|
||||
console.log('[ProductAllSection] 스크롤 컨테이너 위치 수집:', position);
|
||||
return position;
|
||||
}
|
||||
|
||||
console.warn('[ProductAllSection] 스크롤 컨테이너를 찾을 수 없음');
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* ProductAllSection 스크롤 컨테이너 위치 검증 함수
|
||||
* @param {Object} savedPosition - 저장된 초기 위치
|
||||
* @param {Object} currentPosition - 현재 위치
|
||||
* @returns {boolean} 위치가 일치하는지 여부
|
||||
*/
|
||||
const isProductScrollPositionValid = (savedPosition, currentPosition) => {
|
||||
if (!savedPosition || !currentPosition) {
|
||||
console.warn('[ProductAllSection] 저장된 위치 또는 현재 위치가 없음');
|
||||
return false;
|
||||
}
|
||||
|
||||
const tolerance = 1; // 1px 오차 범위
|
||||
const isMatching =
|
||||
Math.abs(currentPosition.top - savedPosition.top) <= tolerance &&
|
||||
Math.abs(currentPosition.left - savedPosition.left) <= tolerance;
|
||||
|
||||
console.log('[ProductAllSection] 스크롤 위치 검증:', {
|
||||
expected: savedPosition,
|
||||
current: currentPosition,
|
||||
matching: isMatching,
|
||||
tolerance
|
||||
});
|
||||
|
||||
return isMatching;
|
||||
};
|
||||
|
||||
const getProductData = curry(
|
||||
(productType, themeProductInfo, themeProducts, selectedIndex, productInfo) =>
|
||||
pipe(
|
||||
@@ -304,6 +355,9 @@ export default function ProductAllSection({
|
||||
// handleScrollToImages의 timeout을 추적하기 위한 ref
|
||||
const scrollToImagesTimeoutRef = useRef(null);
|
||||
|
||||
// 🔽 [251217] 스크롤 컨테이너 초기 위치 저장 ref (VideoPlayer modal 전환 시 위치 검증용)
|
||||
const scrollPositionOnMountRef = useRef(null);
|
||||
|
||||
// ProductAllSection 초기 로딩 시 Skeleton 표시를 위한 상태
|
||||
const [isInitialLoading, setIsInitialLoading] = useState(true);
|
||||
|
||||
@@ -344,6 +398,19 @@ export default function ProductAllSection({
|
||||
);
|
||||
}, [selectedPatnrId, selectedPrdtId, userNumber, dispatch]);
|
||||
|
||||
// 🔽 [251217] ProductAllSection 마운트 시 스크롤 컨테이너 위치 저장
|
||||
useEffect(() => {
|
||||
// 초기 렌더링 후 스크롤 컨테이너의 위치를 저장 (1회만 실행)
|
||||
// 이 위치는 나중에 VideoPlayer의 modal 전환 시 유효성 검증에 사용됨
|
||||
const savedPosition = collectProductScrollPosition();
|
||||
if (savedPosition) {
|
||||
scrollPositionOnMountRef.current = savedPosition;
|
||||
// 🔽 window 객체에도 저장하여 MediaPanel.v3.jsx에서 접근 가능하게 함
|
||||
window.productScrollPositionOnMount = savedPosition;
|
||||
console.log('[ProductAllSection] ✅ 초기 스크롤 위치 저장 완료:', savedPosition);
|
||||
}
|
||||
}, []); // 마운트 시 1회만 실행
|
||||
|
||||
useEffect(() => {
|
||||
// 필수 값이 모두 있을 때만 호출
|
||||
if (selectedPatnrId && selectedPrdtId) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
@@ -26,17 +27,35 @@ export default function UserReviewDetail({
|
||||
onNext,
|
||||
className,
|
||||
}) {
|
||||
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||||
|
||||
// 새로운 리뷰가 로드될 때 이미지 인덱스 초기화
|
||||
useEffect(() => {
|
||||
setCurrentImageIndex(0);
|
||||
}, [currentReview]);
|
||||
|
||||
const reviewImages = currentReview?.reviewImageList || [];
|
||||
const hasMultipleImages = reviewImages.length > 1;
|
||||
|
||||
const handlePrevious = useCallback(() => {
|
||||
if (onPrevious && currentIndex > 0) {
|
||||
// 이미지가 여러 개이고 현재 이미지가 첫 번째가 아니면 이미지만 변경
|
||||
if (hasMultipleImages && currentImageIndex > 0) {
|
||||
setCurrentImageIndex(prev => prev - 1);
|
||||
} else if (onPrevious && currentIndex > 0) {
|
||||
// 이미지가 첫 번째이면 이전 리뷰로 이동
|
||||
onPrevious();
|
||||
}
|
||||
}, [onPrevious, currentIndex]);
|
||||
}, [onPrevious, currentIndex, hasMultipleImages, currentImageIndex]);
|
||||
|
||||
const handleNext = useCallback(() => {
|
||||
if (onNext && currentIndex < totalReviews - 1) {
|
||||
// 이미지가 여러 개이고 현재 이미지가 마지막이 아니면 이미지만 변경
|
||||
if (hasMultipleImages && currentImageIndex < reviewImages.length - 1) {
|
||||
setCurrentImageIndex(prev => prev + 1);
|
||||
} else if (onNext && currentIndex < totalReviews - 1) {
|
||||
// 이미지가 마지막이면 다음 리뷰로 이동
|
||||
onNext();
|
||||
}
|
||||
}, [onNext, currentIndex, totalReviews]);
|
||||
}, [onNext, currentIndex, totalReviews, hasMultipleImages, currentImageIndex, reviewImages.length]);
|
||||
|
||||
// 리뷰 데이터가 없을 때 처리
|
||||
if (!currentReview) {
|
||||
@@ -47,9 +66,7 @@ export default function UserReviewDetail({
|
||||
);
|
||||
}
|
||||
|
||||
const reviewImage =
|
||||
currentReview.reviewImageList && currentReview.reviewImageList[0];
|
||||
const hasMultipleReviews = totalReviews > 1;
|
||||
const reviewImage = reviewImages[currentImageIndex];
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
const [year, month, day] = dateStr.split("-");
|
||||
@@ -59,7 +76,7 @@ export default function UserReviewDetail({
|
||||
return (
|
||||
<>
|
||||
{/* Left Arrow - 이전 리뷰가 있을 때만 표시 */}
|
||||
{hasMultipleReviews && currentIndex > 0 && (
|
||||
{(hasMultipleImages || currentIndex > 0) && (
|
||||
<SpottableButton
|
||||
className={css.leftArrow}
|
||||
onClick={handlePrevious}
|
||||
@@ -128,7 +145,7 @@ export default function UserReviewDetail({
|
||||
</div>
|
||||
</div>
|
||||
{/* Right Arrow - 다음 리뷰가 있을 때만 표시 */}
|
||||
{hasMultipleReviews && currentIndex < totalReviews - 1 && (
|
||||
{(hasMultipleImages || currentIndex < totalReviews - 1) && (
|
||||
<SpottableButton
|
||||
className={css.rightArrow}
|
||||
onClick={handleNext}
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
.enrgLbImg {
|
||||
width:62px;
|
||||
width:70px;
|
||||
border:4px solid transparent;
|
||||
&:focus {
|
||||
border: 4px solid @PRIMARY_COLOR_RED;
|
||||
|
||||
@@ -248,6 +248,7 @@ export default function ThemeContents({
|
||||
handleItemFocus?.(index);
|
||||
}}
|
||||
onMouseEnter={() => Spotlight.focus(spotlightItemId)}
|
||||
euEnrgLblInfos={euEnrgLblInfos}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -51,6 +51,7 @@ export default function ThemeItemCard({
|
||||
spotlightId,
|
||||
dataSpotlightDefault,
|
||||
onFocused,
|
||||
euEnrgLblInfos
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
@@ -211,9 +212,9 @@ export default function ThemeItemCard({
|
||||
))}
|
||||
</div>
|
||||
)} */}
|
||||
{/* {mockEnergyLabel && mockEnergyLabel.length > 0 && (
|
||||
{euEnrgLblInfos && euEnrgLblInfos.length > 0 && (
|
||||
<div className={css.energyLabels}>
|
||||
{mockEnergyLabel.map((label, labelIndex) => (
|
||||
{euEnrgLblInfos.map((label, labelIndex) => (
|
||||
<SpottableTemp
|
||||
key={labelIndex}
|
||||
spotlightDisabled={Boolean(!cursorVisible)}
|
||||
@@ -228,7 +229,7 @@ export default function ThemeItemCard({
|
||||
</SpottableTemp>
|
||||
))}
|
||||
</div>
|
||||
)} */}
|
||||
)}
|
||||
</div>
|
||||
</SpottableDiv>
|
||||
{(() => {
|
||||
|
||||
@@ -220,3 +220,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.energyImage {
|
||||
max-height:800px;
|
||||
}
|
||||
@@ -93,6 +93,36 @@ const findSelector = (selector, maxAttempts = 5, currentAttempts = 0) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 🔽 [251217] ProductAllSection 스크롤 컨테이너 위치 수집 함수
|
||||
const collectProductScrollPosition = () => {
|
||||
const scrollContainer = document.querySelector('[data-spotlight-id="main-content-scroller"]');
|
||||
|
||||
if (scrollContainer) {
|
||||
const { top, left } = scrollContainer.getBoundingClientRect();
|
||||
const position = {
|
||||
top: Math.round(top),
|
||||
left: Math.round(left)
|
||||
};
|
||||
return position;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// 🔽 [251217] ProductAllSection 스크롤 컨테이너 위치 검증 함수
|
||||
const isProductScrollPositionValid = (savedPosition, currentPosition) => {
|
||||
if (!savedPosition || !currentPosition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tolerance = 1; // 1px 오차 범위
|
||||
const isMatching =
|
||||
Math.abs(currentPosition.top - savedPosition.top) <= tolerance &&
|
||||
Math.abs(currentPosition.left - savedPosition.left) <= tolerance;
|
||||
|
||||
return isMatching;
|
||||
};
|
||||
|
||||
const getLogTpNo = (type, nowMenu) => {
|
||||
if (type === 'LIVE') {
|
||||
switch (nowMenu) {
|
||||
@@ -1107,6 +1137,26 @@ const MediaPanel = React.forwardRef(
|
||||
}, [dispatch]);
|
||||
|
||||
const enterFullscreen = useCallback(() => {
|
||||
// 🔽 [251217] ProductAllSection 스크롤 위치 검증
|
||||
// DetailPanel에서 ProductAllSection의 초기 위치와 현재 위치를 비교하여 검증
|
||||
const savedPosition = window.productScrollPositionOnMount;
|
||||
const currentPosition = collectProductScrollPosition();
|
||||
|
||||
if (savedPosition && currentPosition) {
|
||||
const isValid = isProductScrollPositionValid(savedPosition, currentPosition);
|
||||
if (!isValid) {
|
||||
console.warn('[MediaPanel] ⚠️ ProductAllSection 스크롤 위치 검증 실패 - 전체화면 재생 가능하지만 경고 표시', {
|
||||
savedPosition,
|
||||
currentPosition,
|
||||
});
|
||||
// 사용자 피드백: 위치가 일치하지 않음을 콘솔에 기록 (비디오는 계속 재생)
|
||||
} else {
|
||||
console.log('[MediaPanel] ✅ ProductAllSection 스크롤 위치 검증 성공');
|
||||
}
|
||||
} else {
|
||||
console.warn('[MediaPanel] ⚠️ ProductAllSection 위치 정보 없음 - 검증 스킵');
|
||||
}
|
||||
|
||||
isTransitioningToFullscreen.current = true;
|
||||
dispatch(switchMediaToFullscreen());
|
||||
}, [dispatch]);
|
||||
|
||||
@@ -167,8 +167,8 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 4px solid @PRIMARY_COLOR_RED;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 0 22px @PRIMARY_COLOR_RED;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -298,8 +298,8 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 4px solid @PRIMARY_COLOR_RED;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 0 22px @PRIMARY_COLOR_RED;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,464 @@
|
||||
import React from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
|
||||
import PlayerOverlayContents from './PlayerOverlayContents';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
|
||||
function MediaOverlayContents(props) {
|
||||
return <PlayerOverlayContents {...props} forceShowMediaOverlay />;
|
||||
import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
import Marquee from '@enact/ui/Marquee';
|
||||
|
||||
import defaultLogoImg
|
||||
from '../../../../assets/images/ic-tab-partners-default@3x.png';
|
||||
import { setShowPopup } from '../../../actions/commonActions';
|
||||
import CustomImage from '../../../components/CustomImage/CustomImage';
|
||||
import { ACTIVE_POPUP } from '../../../utils/Config';
|
||||
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||
import PlayerTabButton from '../PlayerTabContents/TabButton/PlayerTabButton';
|
||||
import css from './MediaOverlayContents.module.less';
|
||||
|
||||
const SpottableBtn = Spottable('button');
|
||||
|
||||
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||
|
||||
function MediaOverlayContents({
|
||||
type,
|
||||
onClick,
|
||||
panelInfo,
|
||||
disclaimer,
|
||||
playListInfo,
|
||||
captionEnable,
|
||||
selectedIndex,
|
||||
setIsSubtitleActive,
|
||||
videoVerticalVisible,
|
||||
sideContentsVisible,
|
||||
setSideContentsVisible,
|
||||
belowContentsVisible,
|
||||
handleIndicatorUpClick,
|
||||
handleIndicatorDownClick,
|
||||
tabContainerVersion,
|
||||
tabIndexV2,
|
||||
}) {
|
||||
const cntry_cd = useSelector((state) => state.common.httpHeader?.cntry_cd);
|
||||
const dispatch = useDispatch();
|
||||
const onClickBack = (ev) => {
|
||||
// TabContainerV2가 표시된 상태에서 백버튼 클릭 시 이벤트 버블링 방지
|
||||
// (Overlay의 onClick으로 전파되어 toggleControls()가 호출되는 것을 막음)
|
||||
if (tabContainerVersion === 2 && belowContentsVisible) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
export default MediaOverlayContents;
|
||||
if (onClick) {
|
||||
onClick(ev);
|
||||
}
|
||||
};
|
||||
const backBtnRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (type === 'MEDIA' && !panelInfo.modal && backBtnRef.current) {
|
||||
Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
|
||||
}
|
||||
}, [type, panelInfo.modal, backBtnRef]);
|
||||
|
||||
const handleSubtitleOnClick = useCallback(() => {
|
||||
if (!captionEnable) {
|
||||
return dispatch(setShowPopup(ACTIVE_POPUP.alertPopup));
|
||||
}
|
||||
|
||||
setIsSubtitleActive((prev) => !prev);
|
||||
}, [dispatch, captionEnable, setIsSubtitleActive]);
|
||||
|
||||
const patncLogoPath = useMemo(() => {
|
||||
let logo = playListInfo[selectedIndex]?.patncLogoPath;
|
||||
if (type === 'MEDIA') {
|
||||
logo = panelInfo?.patncLogoPath;
|
||||
}
|
||||
|
||||
return logo;
|
||||
}, [playListInfo, selectedIndex, panelInfo, type]);
|
||||
|
||||
const partnerName = useMemo(() => {
|
||||
let name = playListInfo[selectedIndex]?.patncNm;
|
||||
if (type === 'MEDIA') {
|
||||
name = panelInfo?.patncNm;
|
||||
}
|
||||
|
||||
return name;
|
||||
}, [playListInfo, selectedIndex, panelInfo, type]);
|
||||
|
||||
const showName = useMemo(() => {
|
||||
let name = playListInfo[selectedIndex]?.showNm;
|
||||
if (type === 'MEDIA') {
|
||||
name = panelInfo?.showNm;
|
||||
}
|
||||
|
||||
return name ? name.replace(/<br\s*\/?>/gi, ' ') : '';
|
||||
}, [playListInfo, selectedIndex, panelInfo, type]);
|
||||
|
||||
const onSpotlightMoveTabButton = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON);
|
||||
}, []);
|
||||
|
||||
const onSpotlightMoveMediaButton = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// TabContainerV2의 tabIndex=2일 때 하단 버튼들로 포커스 이동
|
||||
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||
if (Spotlight.focus('live-channel-next-button')) return;
|
||||
if (Spotlight.focus('below-tab-shop-now-button')) return;
|
||||
}
|
||||
|
||||
if (type === 'LIVE') {
|
||||
return Spotlight.focus('videoIndicator-down-button');
|
||||
}
|
||||
return Spotlight.focus(SpotlightIds.PLAYER_PLAY_BUTTON);
|
||||
},
|
||||
[type, tabContainerVersion, tabIndexV2]
|
||||
);
|
||||
|
||||
const onSpotlightMoveSubtitleButton = useCallback(
|
||||
() => {
|
||||
// 1. 먼저 자막 버튼으로 포커스 시도
|
||||
if (Spotlight.focus('player-subtitlebutton')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TabContainerV2의 tabIndex=2일 때 TabContainerV2 버튼들로 포커스 이동
|
||||
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||
let focusSuccessful = false;
|
||||
|
||||
// 먼저 LiveChannelNext 버튼으로 시도
|
||||
if (Spotlight.focus('live-channel-next-button')) {
|
||||
focusSuccessful = true;
|
||||
}
|
||||
// 실패하면 ShopNowButton으로 시도
|
||||
else if (Spotlight.focus('below-tab-shop-now-button')) {
|
||||
focusSuccessful = true;
|
||||
}
|
||||
|
||||
if (focusSuccessful) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 기본 동작: 자막 버튼으로 포커스
|
||||
return Spotlight.focus('player-subtitlebutton');
|
||||
},
|
||||
[tabContainerVersion, tabIndexV2]
|
||||
);
|
||||
|
||||
const onSpotlightMoveSlider = useCallback(
|
||||
(e) => {
|
||||
if (type === 'VOD') {
|
||||
e.stopPropagation();
|
||||
|
||||
Spotlight.focus(SpotlightIds.PLAYER_SLIDER);
|
||||
}
|
||||
},
|
||||
[type]
|
||||
);
|
||||
|
||||
const onSpotlightMoveSideTab = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
Spotlight.focus('tab-0');
|
||||
}, []);
|
||||
|
||||
const onSpotlightMoveBelowTab = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
console.log(`[onSpotlightMoveBelowTab] tabIndexV2: ${tabIndexV2}`);
|
||||
|
||||
// tabIndexV2에 따라 다른 버튼으로 포커스 이동
|
||||
if (tabIndexV2 === 0) {
|
||||
// ShopNow 탭: Close 버튼으로
|
||||
// Spotlight.focus('below-tab-close-button');
|
||||
const result = Spotlight.focus('shownow_close_button');
|
||||
console.log(`[onSpotlightMoveBelowTab] tabIndexV2=0, focus result:`, result);
|
||||
} else if (tabIndexV2 === 1) {
|
||||
// LIVE CHANNEL 탭: LIVE CHANNEL 버튼으로
|
||||
const result = Spotlight.focus('below-tab-live-channel-button');
|
||||
console.log(`[onSpotlightMoveBelowTab] tabIndexV2=1, focus result:`, result);
|
||||
} else if (tabIndexV2 === 2) {
|
||||
// ShopNowButton: ShopNowButton으로
|
||||
const result = Spotlight.focus('below-tab-shop-now-button');
|
||||
console.log(`[onSpotlightMoveBelowTab] tabIndexV2=2, focus result:`, result);
|
||||
}
|
||||
},
|
||||
[tabIndexV2]
|
||||
);
|
||||
|
||||
// Back Button arrow down 전용 핸들러 - tabIndex에 따라 다른 포커스
|
||||
const handleBackButtonDown = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (tabContainerVersion === 2 && belowContentsVisible) {
|
||||
if (tabIndexV2 === 0) {
|
||||
// tabIndexV2가 0일 때 ShopNow 닫기 버튼으로 포커스
|
||||
const result = Spotlight.focus('shownow_close_button');
|
||||
} else if (tabIndexV2 === 1) {
|
||||
// tabIndexV2가 1일 때 below-tab-live-channel-button으로 포커스
|
||||
Spotlight.focus('below-tab-live-channel-button');
|
||||
} else if (tabIndexV2 === 2) {
|
||||
// tabIndexV2가 2일 때 LiveChannelNext로 포커스
|
||||
Spotlight.focus('live-channel-next-button');
|
||||
} else {
|
||||
// 그 외에는 기존 로직 사용
|
||||
onSpotlightMoveMediaButton(e);
|
||||
}
|
||||
} else {
|
||||
onSpotlightMoveMediaButton(e);
|
||||
}
|
||||
},
|
||||
[tabContainerVersion, belowContentsVisible, tabIndexV2, onSpotlightMoveMediaButton]
|
||||
);
|
||||
|
||||
const onSpotlightMoveBackButton = useCallback(() => {
|
||||
return Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
|
||||
}, []);
|
||||
|
||||
const handleOverlayKeyDownCapture = useCallback(
|
||||
(ev) => {
|
||||
const currentId = Spotlight.getCurrent()?.getAttribute('data-spotlight-id');
|
||||
if (ev.keyCode === 40 && currentId === SpotlightIds.PLAYER_BACK_BUTTON) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return handleBackButtonDown(ev);
|
||||
}
|
||||
if (ev.keyCode === 39 && currentId === SpotlightIds.PLAYER_BACK_BUTTON) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return onSpotlightMoveSubtitleButton(ev);
|
||||
}
|
||||
if (ev.keyCode === 37 && currentId === SpotlightIds.PLAYER_SUBTITLE_BUTTON) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return onSpotlightMoveBackButton(ev);
|
||||
}
|
||||
if (ev.keyCode === 38 && currentId === SpotlightIds.PLAYER_PLAY_BUTTON) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return onSpotlightMoveBackButton();
|
||||
}
|
||||
},
|
||||
[onSpotlightMoveBackButton, onSpotlightMoveMediaButton, onSpotlightMoveSubtitleButton, handleBackButtonDown]
|
||||
);
|
||||
|
||||
const currentSideButtonStatus = useMemo(() => {
|
||||
if (
|
||||
!panelInfo?.modal &&
|
||||
!sideContentsVisible &&
|
||||
tabContainerVersion === 1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [panelInfo, sideContentsVisible, tabContainerVersion]);
|
||||
|
||||
const noLiveContentsVisible = useMemo(() => {
|
||||
if (!Array.isArray(playListInfo) || playListInfo.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const noShowIdCount = playListInfo.filter((item) => !item.showId).length;
|
||||
if (playListInfo.length - 1 === noShowIdCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [playListInfo]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container className={css.overlayContainer} onKeyDownCapture={handleOverlayKeyDownCapture}>
|
||||
{/* 251118 임시로 unvisible */}
|
||||
{/* {playListInfo.length > 1 && noLiveContentsVisible && (
|
||||
<>
|
||||
<div className={css.indicatorUpButton}>
|
||||
<SpottableBtn
|
||||
onClick={handleIndicatorUpClick}
|
||||
spotlightId="videoIndicator-up-button"
|
||||
onSpotlightRight={
|
||||
videoVerticalVisible
|
||||
? onSpotlightMoveSideTab
|
||||
: tabContainerVersion === 1
|
||||
? onSpotlightMoveTabButton
|
||||
: undefined
|
||||
}
|
||||
onSpotlightDown={
|
||||
tabContainerVersion === 2 && belowContentsVisible
|
||||
? onSpotlightMoveBelowTab
|
||||
: onSpotlightMoveSlider
|
||||
}
|
||||
aria-label="Previous channel"
|
||||
/>
|
||||
</div>
|
||||
<div className={css.indicatorDownButton}>
|
||||
<SpottableBtn
|
||||
onClick={handleIndicatorDownClick}
|
||||
spotlightId="videoIndicator-down-button"
|
||||
onSpotlightLeft={onSpotlightMoveSlider}
|
||||
onSpotlightUp={onSpotlightMoveSlider}
|
||||
onSpotlightRight={
|
||||
videoVerticalVisible
|
||||
? onSpotlightMoveSideTab
|
||||
: tabContainerVersion === 1
|
||||
? onSpotlightMoveTabButton
|
||||
: undefined
|
||||
}
|
||||
onSpotlightDown={
|
||||
tabContainerVersion === 2 && belowContentsVisible
|
||||
? onSpotlightMoveBelowTab
|
||||
: undefined
|
||||
}
|
||||
aria-label="Next channel"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)} */}
|
||||
|
||||
{currentSideButtonStatus && !videoVerticalVisible && (
|
||||
<PlayerTabButton
|
||||
setSideContentsVisible={setSideContentsVisible}
|
||||
sideContentsVisible={sideContentsVisible}
|
||||
onSpotlightLeft={
|
||||
playListInfo.length < 2 && onSpotlightMoveBackButton
|
||||
}
|
||||
videoType={type}
|
||||
/>
|
||||
)}
|
||||
|
||||
{cntry_cd === 'US' && (
|
||||
<div className={css.videoButtonContainer}>
|
||||
<SpottableBtn
|
||||
className={classNames(
|
||||
css.subtitleButton,
|
||||
videoVerticalVisible && css.videoVericalSubtitleButton
|
||||
)}
|
||||
onClick={handleSubtitleOnClick}
|
||||
spotlightId="player-subtitlebutton"
|
||||
onSpotlightUp={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
// tabIndexV2가 2일 때만 ShopNowButton으로 포커스
|
||||
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||
Spotlight.focus('below-tab-shop-now-button');
|
||||
} else {
|
||||
onSpotlightMoveBackButton();
|
||||
}
|
||||
}}
|
||||
onSpotlightLeft={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
// tabIndexV2가 2일 때만 LiveChannelNext로 포커스
|
||||
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||
Spotlight.focus('live-channel-next-button');
|
||||
}
|
||||
}}
|
||||
onSpotlightRight={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
// tabIndexV2가 2일 때만 LiveChannelNext로 포커스
|
||||
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||
Spotlight.focus('below-tab-shop-now-button');
|
||||
}
|
||||
}}
|
||||
onSpotlightDown={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
// tabIndexV2가 2일 때만 ShopNowButton으로 포커스
|
||||
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||
Spotlight.focus('live-channel-next-button');
|
||||
}
|
||||
}}
|
||||
aria-label="Caption"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={css.overlayHeader}>
|
||||
<SpottableBtn
|
||||
onClick={onClickBack}
|
||||
className={css.backIcon}
|
||||
spotlightId="player-back-button"
|
||||
onSpotlightDown={handleBackButtonDown}
|
||||
onSpotlightRight={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
// tabIndexV2가 2일 때만 ShopNowButton으로 포커스
|
||||
if (tabContainerVersion === 2 && tabIndexV2 === 2) {
|
||||
Spotlight.focus('below-tab-shop-now-button');
|
||||
}
|
||||
}}
|
||||
onSpotlightUp={onSpotlightMoveSubtitleButton}
|
||||
aria-label="Video Player Close"
|
||||
ref={backBtnRef}
|
||||
/>
|
||||
<div
|
||||
className={classNames(type === 'LIVE' && css.liveIcon)}
|
||||
aria-label={type === 'LIVE' && 'Live Icon'}
|
||||
/>
|
||||
|
||||
{partnerName && (
|
||||
<CustomImage
|
||||
src={patncLogoPath}
|
||||
fallbackSrc={defaultLogoImg}
|
||||
alt={partnerName}
|
||||
aria-label={partnerName}
|
||||
/>
|
||||
)}
|
||||
|
||||
<h2 className={css.patnerName}>{partnerName}</h2>
|
||||
|
||||
{!panelInfo?.modal && (
|
||||
<Marquee
|
||||
className={classNames(css.title, videoVerticalVisible && css.videoVerticalMarquee)}
|
||||
marqueeOn="render"
|
||||
>
|
||||
{showName}
|
||||
</Marquee>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
{type === 'VOD' && disclaimer && (
|
||||
<div className={css.disclaimer}>
|
||||
<span className={css.icon} />
|
||||
<h3 aria-label={disclaimer}>{disclaimer}</h3>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const propsAreEqual = (prev, next) => {
|
||||
return (
|
||||
prev.type === next.type &&
|
||||
prev.panelInfo?.showId === next.panelInfo?.showId &&
|
||||
prev.disclaimer === next.disclaimer &&
|
||||
prev.playListInfo === next.playListInfo &&
|
||||
prev.captionEnable === next.captionEnable &&
|
||||
prev.selectedIndex === next.selectedIndex &&
|
||||
prev.videoVerticalVisible === next.videoVerticalVisible &&
|
||||
prev.sideContentsVisible === next.sideContentsVisible &&
|
||||
prev.belowContentsVisible === next.belowContentsVisible &&
|
||||
prev.tabContainerVersion === next.tabContainerVersion &&
|
||||
prev.tabIndexV2 === next.tabIndexV2
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(MediaOverlayContents, propsAreEqual);
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
@import "../../../style/CommonStyle.module.less";
|
||||
@import "../../../style/utils.module.less";
|
||||
|
||||
.overlayContainer {
|
||||
.position(@position: absolute, @top: 77px, @right: 0, @bottom: 0, @left: 60px);
|
||||
display: flex;
|
||||
z-index: 5;
|
||||
|
||||
.videoButtonContainer {
|
||||
.subtitleButton {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-image: url("../../../../assets/images/btn/btn-video-cc-nor@3x.png");
|
||||
background-size: cover;
|
||||
position: absolute;
|
||||
right: 300px;
|
||||
top: 680px;
|
||||
z-index: 10;
|
||||
|
||||
&.videoVericalSubtitleButton {
|
||||
position: absolute;
|
||||
right: 680px;
|
||||
top: 850px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: rgba(199, 8, 80, 0.5);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.indicatorUpButton {
|
||||
position: absolute;
|
||||
top: -77px;
|
||||
left: 880px;
|
||||
z-index: 10;
|
||||
.size(@w: 144px, @h: 48px);
|
||||
> button {
|
||||
.size(@w: 144px, @h: 48px);
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("../../../../assets/images/btn/btn-wh-arrow-top-nor.svg");
|
||||
&:focus {
|
||||
background-color: @PRIMARY_COLOR_RED;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.indicatorDownButton {
|
||||
position: absolute;
|
||||
bottom: -986px;
|
||||
left: 880px;
|
||||
z-index: 10;
|
||||
.size(@w: 144px, @h: 48px);
|
||||
> button {
|
||||
.size(@w: 144px, @h: 48px);
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("../../../../assets/images/btn/btn-wh-arrow-down-nor.svg");
|
||||
&:focus {
|
||||
background-color: @PRIMARY_COLOR_RED;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overlayHeader {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
|
||||
.backIcon {
|
||||
.size(@w: 60px, @h: 60px);
|
||||
background-size: 60px 60px;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("../../../../assets/images/btn/btn-60-wh-back-nor@3x.png");
|
||||
|
||||
&:focus {
|
||||
background-image: url("../../../../assets/images/btn/btn-60-wh-back-foc@3x.png");
|
||||
}
|
||||
}
|
||||
.liveIcon {
|
||||
.size(@w: 108px, @h: 48px);
|
||||
margin: 6px 0 6px 30px;
|
||||
background-size: 108px 48px;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("../../../../assets/images/tag-liveshow.png");
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
> img {
|
||||
.size(@w: auto, @h: 60px);
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.patnerName {
|
||||
font-size: 44px;
|
||||
font-weight: bold;
|
||||
color: #fcfcfc;
|
||||
margin-left: 14px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 1200px;
|
||||
align-items: center;
|
||||
font-size: 44px;
|
||||
color: #fcfcfc;
|
||||
margin-left: 35px;
|
||||
|
||||
> div {
|
||||
> div {
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
&.videoVerticalTitle {
|
||||
.size(@w: 950px , @h: 60px);
|
||||
> div {
|
||||
> div {
|
||||
padding-top: 10px;
|
||||
.size(@w: 940px , @h: 60px);
|
||||
}
|
||||
}
|
||||
}
|
||||
// .marquee {
|
||||
// &.videoVerticalMarquee {
|
||||
// width: 950px;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
.size(@w: 1800px , @h: 54px);
|
||||
display: flex;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
position: absolute;
|
||||
top: 140px;
|
||||
left: 60px;
|
||||
align-items: center;
|
||||
|
||||
> span {
|
||||
.size(@w: 18px , @h: 18px);
|
||||
background-image: url("../../../../assets/images/icons/ic-alert-20@3x.png");
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
margin: 0 12px 0 20px;
|
||||
}
|
||||
> h3 {
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ export default function LiveChannelContents({
|
||||
// currentVideoShowId와 일치하는 배너의 인덱스 찾기
|
||||
const index = liveInfos.findIndex((item) => item.showId === currentVideoShowId);
|
||||
if (index !== -1) {
|
||||
scrollToRef.current({ index, animate: true, focus: false });
|
||||
scrollToRef.current({ index, animate: true, focus: false, alignToStart: true });
|
||||
}
|
||||
}
|
||||
}, [currentVideoShowId, liveInfos]);
|
||||
|
||||
@@ -82,28 +82,34 @@ export default function TScrollerLiveChannel({
|
||||
if (!container || !itemsRef.current[index]) return;
|
||||
|
||||
const item = itemsRef.current[index];
|
||||
const { animate = true } = options;
|
||||
const { animate = true, alignToStart = false } = options;
|
||||
|
||||
if (direction === 'horizontal') {
|
||||
// 수평 스크롤: 현재 아이템 + 다음 아이템까지 보이도록
|
||||
// 수평 스크롤: 스크롤 가능 여부 판단
|
||||
const isScrollable = container.scrollWidth > container.clientWidth;
|
||||
|
||||
let scrollLeft = 0;
|
||||
|
||||
if (alignToStart && isScrollable) {
|
||||
// 첫 번째 위치로 스크롤
|
||||
scrollLeft = item.offsetLeft - scaleW(spacing);
|
||||
} else if (!alignToStart) {
|
||||
// 기존 로직: 현재 아이템 + 다음 아이템까지 보이도록
|
||||
const itemLeft = item.offsetLeft;
|
||||
const itemWidth = item.offsetWidth;
|
||||
const containerWidth = container.clientWidth;
|
||||
|
||||
// 다음 아이템도 일부 보일 수 있도록 스크롤
|
||||
// 현재 아이템 + 다음 아이템의 일부가 보이는 위치로 스크롤
|
||||
const nextItem = itemsRef.current[index + 1];
|
||||
let scrollLeft = itemLeft - scaleW(spacing);
|
||||
scrollLeft = itemLeft - scaleW(spacing);
|
||||
|
||||
if (nextItem) {
|
||||
// 다음 아이템의 왼쪽 끝이 컨테이너의 오른쪽 끝과 같은 위치가 되도록
|
||||
const nextItemLeft = nextItem.offsetLeft;
|
||||
const nextItemWidth = nextItem.offsetWidth;
|
||||
const targetScrollLeft = nextItemLeft + nextItemWidth - containerWidth + scaleW(spacing);
|
||||
|
||||
// 두 가지 중에서 현재 아이템이 더 잘 보이는 쪽 선택
|
||||
scrollLeft = Math.min(scrollLeft, targetScrollLeft);
|
||||
}
|
||||
}
|
||||
|
||||
// 음수 스크롤 방지
|
||||
scrollLeft = Math.max(0, scrollLeft);
|
||||
@@ -117,24 +123,31 @@ export default function TScrollerLiveChannel({
|
||||
container.scrollLeft = scrollLeft;
|
||||
}
|
||||
} else {
|
||||
// 수직 스크롤: 현재 아이템 + 다음 아이템까지 보이도록
|
||||
// 수직 스크롤: 스크롤 가능 여부 판단
|
||||
const isScrollable = container.scrollHeight > container.clientHeight;
|
||||
|
||||
let scrollTop = 0;
|
||||
|
||||
if (alignToStart && isScrollable) {
|
||||
// 첫 번째 위치로 스크롤
|
||||
scrollTop = item.offsetTop - scaleH(spacing);
|
||||
} else if (!alignToStart) {
|
||||
// 기존 로직: 현재 아이템 + 다음 아이템까지 보이도록
|
||||
const itemTop = item.offsetTop;
|
||||
const itemHeight = item.offsetHeight;
|
||||
const containerHeight = container.clientHeight;
|
||||
|
||||
// 다음 아이템도 일부 보일 수 있도록 스크롤
|
||||
const nextItem = itemsRef.current[index + 1];
|
||||
let scrollTop = itemTop - scaleH(spacing);
|
||||
scrollTop = itemTop - scaleH(spacing);
|
||||
|
||||
if (nextItem) {
|
||||
// 다음 아이템의 위쪽 끝이 컨테이너의 아래쪽 끝과 같은 위치가 되도록
|
||||
const nextItemTop = nextItem.offsetTop;
|
||||
const nextItemHeight = nextItem.offsetHeight;
|
||||
const targetScrollTop = nextItemTop + nextItemHeight - containerHeight + scaleH(spacing);
|
||||
|
||||
// 두 가지 중에서 현재 아이템이 더 잘 보이는 쪽 선택
|
||||
scrollTop = Math.min(scrollTop, targetScrollTop);
|
||||
}
|
||||
}
|
||||
|
||||
// 음수 스크롤 방지
|
||||
scrollTop = Math.max(0, scrollTop);
|
||||
@@ -156,9 +169,9 @@ export default function TScrollerLiveChannel({
|
||||
useEffect(() => {
|
||||
if (cbScrollTo) {
|
||||
cbScrollTo((options) => {
|
||||
const { index, animate = true, focus = true } = options;
|
||||
const { index, animate = true, focus = true, alignToStart = false } = options;
|
||||
if (typeof index === 'number' && index >= 0 && index < dataSize) {
|
||||
scrollToIndex(index, { animate });
|
||||
scrollToIndex(index, { animate, alignToStart });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user