search panel focus

This commit is contained in:
yonghyon
2024-06-13 20:49:57 +09:00
parent b5e0a650d1
commit 665e56ab24
10 changed files with 142 additions and 89 deletions

View File

@@ -213,12 +213,6 @@ const TVerticalPagenator = forwardRef(
focus: focus || !currentSpot, focus: focus || !currentSpot,
align: "top", align: "top",
}); });
// [2024.06.04] 베너 탑인 경우에는, LIVE,VOD 로 포커스 가도록 수정
if (node.getAttribute("data-spotlight-id") !== "DSP00101") {
if (focus || !currentSpot) {
Spotlight.focus(node);
}
}
if (onFocusedContainerIdRef.current) { if (onFocusedContainerIdRef.current) {
if (!node.getAttribute("data-spotlight-id")) { if (!node.getAttribute("data-spotlight-id")) {
console.warn("TVerticalPagenator should have spotlight id"); console.warn("TVerticalPagenator should have spotlight id");

View File

@@ -16,8 +16,10 @@ import {
} from '../../utils/helperMethods'; } from '../../utils/helperMethods';
import AutoScrollArea, { POSITION } from '../AutoScrollArea/AutoScrollArea'; import AutoScrollArea, { POSITION } from '../AutoScrollArea/AutoScrollArea';
import css from './TVirtualGridList.module.less'; import css from './TVirtualGridList.module.less';
import Spotlight from '@enact/spotlight';
export default function TVirtualGridList({ export default function TVirtualGridList({
defaultSpotlightId,
dataSize, dataSize,
direction = "vertical", direction = "vertical",
className, className,
@@ -43,6 +45,7 @@ export default function TVirtualGridList({
const scrollPosition = useRef("top"); const scrollPosition = useRef("top");
const scrollToRef = useRef(null); const scrollToRef = useRef(null);
const defaultFocusTargetRef = useRef(null);
const scrollHorizontalPos = useRef(0); const scrollHorizontalPos = useRef(0);
const scrollVerticalPos = useRef(0); const scrollVerticalPos = useRef(0);
const visibleIndexRef = useRef({firstVisibleIndex:0, lastVisibleIndex:0}); const visibleIndexRef = useRef({firstVisibleIndex:0, lastVisibleIndex:0});
@@ -58,6 +61,13 @@ export default function TVirtualGridList({
useEffect(() => { useEffect(() => {
const listRef = gridlistParentRef.current.childNodes[0]; const listRef = gridlistParentRef.current.childNodes[0];
if(spotlightId &&defaultSpotlightId && defaultSpotlightId.indexOf(spotlightId) === 0){
let matched = defaultSpotlightId.match(/\d+$/); //find index
if(matched){
defaultFocusTargetRef.current = {spotlightId: defaultSpotlightId, index: matched[0]};
scrollToRef.current({index: matched[0], animate: false, focus: true});
}
}
if(direction === 'vertical'){ if(direction === 'vertical'){
listRef.addEventListener("wheel", handleWheel, true); listRef.addEventListener("wheel", handleWheel, true);
} }
@@ -86,9 +96,14 @@ export default function TVirtualGridList({
if (onScrollStop) { if (onScrollStop) {
onScrollStop(e); onScrollStop(e);
} }
isScrolling.current = false; isScrolling.current = false;
if(defaultFocusTargetRef.current){
if(defaultFocusTargetRef.current.index >= e.moreInfo.firstVisibleIndex
&& defaultFocusTargetRef.current.index <= e.moreInfo.lastVisibleIndex){
Spotlight.focus(defaultFocusTargetRef.current.spotlightId);
defaultFocusTargetRef.current = null;
}
}
if (e.reachedEdgeInfo) { if (e.reachedEdgeInfo) {
if (e.reachedEdgeInfo.top) { if (e.reachedEdgeInfo.top) {
scrollPosition.current = "top"; scrollPosition.current = "top";

View File

@@ -21,6 +21,12 @@ export const SpotlightIds = {
LIST_PLAYER: "list_player", LIST_PLAYER: "list_player",
LIST_PLAYER2: "list_player2", LIST_PLAYER2: "list_player2",
// SearchPanel
SEARCH_THEME: "search_theme",
SEARCH_SHOW: "search_show",
SEARCH_ITEM: "search_item",
SEARCH_BESTSELLER: "search_bestseller",
//pin Code Popup //pin Code Popup
PINCODE_CONTAINER: "pincodeContainer", PINCODE_CONTAINER: "pincodeContainer",
}; };

View File

@@ -72,7 +72,7 @@ export default function HomeBanner({
} }
return "banner" + targetIndex; return "banner" + targetIndex;
} }
return "banner0"; return null;
}, [bannerDataList]); }, [bannerDataList]);
useEffect(() => { useEffect(() => {
@@ -87,7 +87,7 @@ export default function HomeBanner({
(index, isHorizontal) => { (index, isHorizontal) => {
const data = bannerDataList?.[index] ?? {}; const data = bannerDataList?.[index] ?? {};
return ( return (
<div className={!isHorizontal && css.imgBox}> <div className={!isHorizontal ? css.imgBox: undefined}>
{data.shptmDspyTpNm === "Rolling" ? ( {data.shptmDspyTpNm === "Rolling" ? (
<Rolling <Rolling
bannerData={data} bannerData={data}

View File

@@ -14,6 +14,7 @@ import { LOG_MENU, panel_names } from "../../../utils/Config";
import { $L } from "../../../utils/helperMethods"; import { $L } from "../../../utils/helperMethods";
import css from "./NoSearchResults.module.less"; import css from "./NoSearchResults.module.less";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import { SpotlightIds } from "../../../utils/SpotlightIds";
const ContainerBasic = SpotlightContainerDecorator( const ContainerBasic = SpotlightContainerDecorator(
{ enterTo: null }, { enterTo: null },
"div" "div"
@@ -84,7 +85,7 @@ export default function NoSearchResults({ handleItemFocus }) {
</div> </div>
<p>{$L("SORRY, NO RESULTS MATCHING YOUR SEARCH")}</p> <p>{$L("SORRY, NO RESULTS MATCHING YOUR SEARCH")}</p>
</div> </div>
<ContainerBasic className={css.bestSellerWrap} data-wheel-point={true}> <ContainerBasic className={css.bestSellerWrap} data-wheel-point={true} spotlightId={SpotlightIds.SEARCH_BESTSELLER}>
<SectionTitle title={$L("BEST SELLER")} /> <SectionTitle title={$L("BEST SELLER")} />
<TGrid> <TGrid>
{bestSellerDatas && {bestSellerDatas &&

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef, useState } from "react"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@@ -9,7 +9,6 @@ import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDeco
import { popPanel, updatePanel } from "../../actions/panelActions"; import { popPanel, updatePanel } from "../../actions/panelActions";
import { getSearch, resetSearch } from "../../actions/searchActions"; import { getSearch, resetSearch } from "../../actions/searchActions";
import TBody from "../../components/TBody/TBody"; import TBody from "../../components/TBody/TBody";
import TButton from "../../components/TButton/TButton";
import TInput, { ICONS, KINDS } from "../../components/TInput/TInput"; import TInput, { ICONS, KINDS } from "../../components/TInput/TInput";
import TPanel from "../../components/TPanel/TPanel"; import TPanel from "../../components/TPanel/TPanel";
import TVerticalPagenator from "../../components/TVerticalPagenator/TVerticalPagenator"; import TVerticalPagenator from "../../components/TVerticalPagenator/TVerticalPagenator";
@@ -20,6 +19,8 @@ import NoSearchResults from "./NoSearchResults/NoSearchResults";
import RecommendedKeywords from "./RecommendedKeywords/RecommendedKeywords"; import RecommendedKeywords from "./RecommendedKeywords/RecommendedKeywords";
import css from "./SearchPanel.module.less"; import css from "./SearchPanel.module.less";
import SearchResults from "./SearchResults/SearchResults"; import SearchResults from "./SearchResults/SearchResults";
import { setContainerLastFocusedElement } from "@enact/spotlight/src/container";
import { SpotlightIds } from "../../utils/SpotlightIds";
const ContainerBasic = SpotlightContainerDecorator( const ContainerBasic = SpotlightContainerDecorator(
{ enterTo: "last-focused" }, { enterTo: "last-focused" },
@@ -30,7 +31,7 @@ const ITEMS_PER_PAGE = 9;
export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
const { sendLogGNB } = useLogService(); const { sendLogGNB } = useLogService();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [firstSpot, setFirstSpot] = useState(false);
const recommandedKeywords = useSelector( const recommandedKeywords = useSelector(
(state) => state.myPage.recommandedKeywordData.data?.keywords (state) => state.myPage.recommandedKeywordData.data?.keywords
); );
@@ -96,10 +97,17 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
} }
}, []); }, []);
const clearAllContainerHistory = useCallback(()=>{
setContainerLastFocusedElement(null, [SpotlightIds.SEARCH_THEME]);
setContainerLastFocusedElement(null, [SpotlightIds.SEARCH_SHOW]);
setContainerLastFocusedElement(null, [SpotlightIds.SEARCH_ITEM]);
setContainerLastFocusedElement(null, [SpotlightIds.SEARCH_BESTSELLER]);
},[]);
const handleSearchSubmit = useCallback( const handleSearchSubmit = useCallback(
(query) => { (query) => {
clearAllContainerHistory();
if (!searchPerformed && !query) return; if (!searchPerformed && !query) return;
if (query.trim()) { if (query.trim()) {
dispatch( dispatch(
getSearch({ getSearch({
@@ -137,14 +145,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
currentPage * ITEMS_PER_PAGE < recommandedKeywords?.length; currentPage * ITEMS_PER_PAGE < recommandedKeywords?.length;
useEffect(() => { useEffect(() => {
const focusJobValue = focusJob.current;
if (panelInfo && isOnTop) { if (panelInfo && isOnTop) {
if (panelInfo.currentSpot) { if (panelInfo.currentSpot && firstSpot) {
console.log("chw", panelInfo.currentSpot); Spotlight.focus(panel_names.SEARCH_PANEL);
Spotlight.focus(panelInfo.currentSpot);
focusJobValue.start(() => Spotlight.focus(panelInfo.currentSpot));
const currentFocusedItem = Spotlight.getCurrent();
} }
} }
}, [panelInfo, isOnTop]); }, [panelInfo, isOnTop]);
@@ -194,7 +197,25 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
); );
const onFocusedContainerId = useCallback((containerId) => { const onFocusedContainerId = useCallback((containerId) => {
setFocusedContainerId(containerId); setFocusedContainerId(containerId);
}, []); if (!firstSpot) {
setTimeout(() => {
Spotlight.resume();
setFirstSpot(true);
if (panelInfo.currentSpot) {
Spotlight.focus(panelInfo.currentSpot);
}
}, 0);
}
}, [panelInfo, firstSpot]);
const panelInfoFall = useMemo(()=>{
const newPanelInfo = {...panelInfo};
if(firstSpot){
newPanelInfo.currentSpot = null;
}
return newPanelInfo;
},[panelInfo, firstSpot]);
return ( return (
<TPanel <TPanel
className={css.container} className={css.container}
@@ -206,6 +227,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
scrollable={false} scrollable={false}
spotlightDisabled={!isOnTop} spotlightDisabled={!isOnTop}
> >
<ContainerBasic>
<TVerticalPagenator <TVerticalPagenator
className={css.tVerticalPagenator} className={css.tVerticalPagenator}
spotlightId={"search_verticalPagenator"} spotlightId={"search_verticalPagenator"}
@@ -216,47 +238,49 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
cbChangePageRef={cbChangePageRef} cbChangePageRef={cbChangePageRef}
topMargin={36} topMargin={36}
> >
<ContainerBasic <ContainerBasic
className={css.inputContainer} className={css.inputContainer}
data-wheel-point={true} data-wheel-point={true}
> spotlightId={'search-input-layer'}
<TInput >
className={css.inputBox} <TInput
kind={KINDS.withIcon} className={css.inputBox}
icon={ICONS.search} kind={KINDS.withIcon}
value={searchQuery} icon={ICONS.search}
onChange={handleSearchChange} value={searchQuery}
onIconClick={() => handleSearchSubmit(searchQuery)} onChange={handleSearchChange}
onKeyDown={handleKeydown} onIconClick={() => handleSearchSubmit(searchQuery)}
forcedSpotlight="first-keyword-button" onKeyDown={handleKeydown}
spotlightId={"search-input-box"} forcedSpotlight="first-keyword-button"
/> spotlightId={"search-input-box"}
</ContainerBasic>
{searchPerformed && searchQuery !== null ? (
Object.keys(searchDatas).length > 0 ? (
<SearchResults
panelInfo={panelInfo}
searchQueryRef={searchQueryRef}
isRecommendedSearchRef={isRecommendedSearchRef}
/>
) : (
<NoSearchResults handleItemFocus={handleItemFocus} />
)
) : (
<ContainerBasic>
<RecommendedKeywords
keywords={paginatedKeywords}
onPrev={handlePrev}
onNext={handleNext}
hasPrevPage={hasPrevPage}
hasNextPage={hasNextPage}
handleSearchSubmit={handleSearchSubmit}
isRecommendedSearchRef={isRecommendedSearchRef}
/> />
</ContainerBasic> </ContainerBasic>
)}
</TVerticalPagenator> {searchPerformed && searchQuery !== null ? (
Object.keys(searchDatas).length > 0 ? (
<SearchResults
panelInfo={panelInfoFall}
searchQueryRef={searchQueryRef}
isRecommendedSearchRef={isRecommendedSearchRef}
/>
) : (
<NoSearchResults handleItemFocus={handleItemFocus} />
)
) : (
<ContainerBasic>
<RecommendedKeywords
keywords={paginatedKeywords}
onPrev={handlePrev}
onNext={handleNext}
hasPrevPage={hasPrevPage}
hasNextPage={hasNextPage}
handleSearchSubmit={handleSearchSubmit}
isRecommendedSearchRef={isRecommendedSearchRef}
/>
</ContainerBasic>
)}
</TVerticalPagenator>
</ContainerBasic>
</TBody> </TBody>
</TPanel> </TPanel>
); );

View File

@@ -6,6 +6,10 @@
.tBody { .tBody {
height: 100%; height: 100%;
>div{
width: 100%;
height: 100%;
}
.focusedContainerId{ .focusedContainerId{
height: 100%; height: 100%;
} }

View File

@@ -10,6 +10,7 @@ import useLogService from "../../../hooks/useLogService";
import { $L } from "../../../utils/helperMethods"; import { $L } from "../../../utils/helperMethods";
import css from "./SearchResults.module.less"; import css from "./SearchResults.module.less";
import SearchResultsType from "./SearchResultsType"; import SearchResultsType from "./SearchResultsType";
import { SpotlightIds } from "../../../utils/SpotlightIds";
const Container = SpotlightContainerDecorator( const Container = SpotlightContainerDecorator(
{ enterTo: "last-focused" }, { enterTo: "last-focused" },
@@ -55,11 +56,11 @@ export default memo(function SearchResults({
spotJobValue.start(() => { spotJobValue.start(() => {
if (searchDatas) { if (searchDatas) {
if (searchDatas?.theme?.length > 0) { if (searchDatas?.theme?.length > 0) {
Spotlight.focus("searchThemeItem0"); Spotlight.focus(SpotlightIds.SEARCH_THEME);
} else if (searchDatas?.show?.length > 0) { } else if (searchDatas?.show?.length > 0) {
Spotlight.focus("searchShowItem0"); Spotlight.focus(SpotlightIds.SEARCH_SHOW);
} else { } else {
Spotlight.focus("searchItem0"); Spotlight.focus(SpotlightIds.SEARCH_ITEM);
} }
} }
}); });
@@ -99,7 +100,7 @@ export default memo(function SearchResults({
count={count} count={count}
data={data} data={data}
idx={index} idx={index}
key={title + index} key={category + "searchResult"}
panelInfo={panelInfo} panelInfo={panelInfo}
searchQueryRef={searchQueryRef} searchQueryRef={searchQueryRef}
sectionTitle={title} sectionTitle={title}

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef } from "react"; import React, { useCallback, useEffect, useMemo, useRef } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@@ -16,6 +16,7 @@ import SearchItemCard from "./SearchCard/SearchItemCard";
import SearchShowCard from "./SearchCard/SearchShowCard"; import SearchShowCard from "./SearchCard/SearchShowCard";
import SearchThemeCard from "./SearchCard/SearchThemeCard"; import SearchThemeCard from "./SearchCard/SearchThemeCard";
import css from "./SearchResultsType.module.less"; import css from "./SearchResultsType.module.less";
import { SpotlightIds } from "../../../utils/SpotlightIds";
const Container = SpotlightContainerDecorator( const Container = SpotlightContainerDecorator(
{ leaveFor: { right: "" }, enterTo: "last-focused" }, { leaveFor: { right: "" }, enterTo: "last-focused" },
@@ -59,10 +60,10 @@ export default function SearchResultsType({
const dataRef = usePrevious(data); const dataRef = usePrevious(data);
useEffect(() => { useEffect(() => {
if (initPerformed) { if(data && !panelInfo.currentSpot){
scrollLeft(); scrollLeft();
} }
}, [initPerformed]); }, [data]);
useEffect(() => { useEffect(() => {
const scrollLeftJobValue = scrollLeftJob.current; const scrollLeftJobValue = scrollLeftJob.current;
@@ -83,7 +84,7 @@ export default function SearchResultsType({
const onScroll = useCallback( const onScroll = useCallback(
(ev) => { (ev) => {
const { lastVisibleIndex } = ev.moreInfo; const { lastVisibleIndex } = ev.moreInfo;
if (dataRef.current.length >= SEARCH_DATA_MAX_RESULTS_LIMIT) { if (dataRef.current && dataRef.current.length >= SEARCH_DATA_MAX_RESULTS_LIMIT) {
const nextSearchIndex = const nextSearchIndex =
(Math.floor(lastVisibleIndex / SEARCH_DATA_MAX_RESULTS_LIMIT) + 1) * (Math.floor(lastVisibleIndex / SEARCH_DATA_MAX_RESULTS_LIMIT) + 1) *
SEARCH_DATA_MAX_RESULTS_LIMIT + SEARCH_DATA_MAX_RESULTS_LIMIT +
@@ -117,11 +118,11 @@ export default function SearchResultsType({
<SearchThemeCard <SearchThemeCard
contentId={contentId} contentId={contentId}
idx={idx} idx={idx}
key={"theme" + index} key={category + index}
keyword={keyword} keyword={keyword}
patncNm={partnerName} patncNm={partnerName}
searchQueryRef={searchQueryRef} searchQueryRef={searchQueryRef}
spotlightId={"searchThemeItem" + index} spotlightId={SpotlightIds.SEARCH_THEME+ index}
title={title} title={title}
type={"theme"} type={"theme"}
{...rest} {...rest}
@@ -146,11 +147,11 @@ export default function SearchResultsType({
<SearchShowCard <SearchShowCard
contentId={contentId} contentId={contentId}
idx={idx} idx={idx}
key={"show" + index} key={category + index}
liveFlag={liveFlag} liveFlag={liveFlag}
patncNm={partnerName} patncNm={partnerName}
searchQueryRef={searchQueryRef} searchQueryRef={searchQueryRef}
spotlightId={"searchShowItem" + index} spotlightId={SpotlightIds.SEARCH_SHOW+ index}
thumbnail={thumbnail} thumbnail={thumbnail}
title={title} title={title}
{...rest} {...rest}
@@ -176,11 +177,11 @@ export default function SearchResultsType({
contentId={contentId} contentId={contentId}
dcPrice={dcPrice} dcPrice={dcPrice}
idx={idx} idx={idx}
key={"item" + index} key={category + index}
patncNm={partnerName} patncNm={partnerName}
price={price} price={price}
searchQueryRef={searchQueryRef} searchQueryRef={searchQueryRef}
spotlightId={"searchItem" + index} spotlightId={SpotlightIds.SEARCH_ITEM+ index}
thumbnail={thumbnail} thumbnail={thumbnail}
title={title} title={title}
length={dataSize} length={dataSize}
@@ -196,23 +197,33 @@ export default function SearchResultsType({
[category, data, idx, searchQueryRef] [category, data, idx, searchQueryRef]
); );
const gridListId = useMemo(()=>{
switch(category){
case 'theme':
return SpotlightIds.SEARCH_THEME;
case 'show':
return SpotlightIds.SEARCH_SHOW;
case 'item':
return SpotlightIds.SEARCH_ITEM;
}
},[category]);
const { itemWidth, itemHeight, spacing } = ITEM_SIZE[category] || {}; const { itemWidth, itemHeight, spacing } = ITEM_SIZE[category] || {};
return ( return (
<Container data-wheel-point={true}> <Container data-wheel-point={true} spotlightId={'searchresult_section_'+category}>
<SectionTitle <SectionTitle
className={css.sectionTitle} className={css.sectionTitle}
title={sectionTitle} title={sectionTitle}
itemCount={count ? count : null} itemCount={count ? count : null}
label={sectionTitle + ", Heading 2"} label={sectionTitle + ", Heading 2"}
/> />
<Container
className={classNames(css.container, category && css[category])}
>
<TVirtualGridList <TVirtualGridList
defaultSpotlightId={panelInfo.currentSpot}
key={sectionTitle} key={sectionTitle}
spotlightId={gridListId}
cbScrollTo={getScrollTo} cbScrollTo={getScrollTo}
className={css.grid} className={classNames(css.container, category && css[category], css.grid)}
dataSize={count ? count : 0} dataSize={count ? count : 0}
renderItem={renderItem} renderItem={renderItem}
direction="horizontal" direction="horizontal"
@@ -223,6 +234,5 @@ export default function SearchResultsType({
onScroll={onScroll} onScroll={onScroll}
/> />
</Container> </Container>
</Container>
); );
} }

View File

@@ -5,18 +5,16 @@
padding-left: 60px; padding-left: 60px;
} }
.container { .container {
> div { .flex();
.flex();
margin: 30px 0 50px; margin: 30px 0 50px;
width: 100%; width: 100%;
} &.grid {
.grid {
padding-right: 60px; padding-right: 60px;
padding-left: 60px; padding-left: 60px;
} }
&.theme { &.theme {
.grid { &.grid {
> div { > div {
height: 141px; height: 141px;
} }
@@ -24,7 +22,7 @@
} }
&.show { &.show {
.grid { &.grid {
> div { > div {
height: 240px; height: 240px;
} }
@@ -32,7 +30,7 @@
} }
&.item { &.item {
.grid { &.grid {
> div { > div {
height: 150px; height: 150px;
} }