Skip to content

Commit

Permalink
Merge branch 'develop' of https://github.com/enactjs/sandstone into f…
Browse files Browse the repository at this point in the history
…eature/WRR-13728
  • Loading branch information
daniel-stoian-lgp committed Dec 12, 2024
2 parents 27008c3 + 5b666e4 commit 6e1dae5
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 102 deletions.
198 changes: 100 additions & 98 deletions Dropdown/DropdownList.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import IdProvider from '@enact/ui/internal/IdProvider';
import ri from '@enact/ui/resolution';
import PropTypes from 'prop-types';
import compose from 'ramda/src/compose';
import {Component, createRef} from 'react';
import {useCallback, useEffect, useRef, useState} from 'react';

import $L from '../internal/$L';
import Icon from '../Icon';
Expand Down Expand Up @@ -180,142 +180,144 @@ const ReadyState = {
const DropdownListSpotlightDecorator = hoc((config, Wrapped) => {
const WrappedWithRef = WithRef(Wrapped);

return class extends Component {
static displayName = 'DropdownListSpotlightDecorator';

static propTypes = {
/*
* Passed by DropdownBase to resume Spotlight
*
* @type {Function}
*/
handleSpotlightPause: PropTypes.func,

/*
* Called when an item receives focus.
*
* @type {Function}
*/
onFocus: PropTypes.func,

/*
* Index of the selected item.
*
* @type {Number}
*/
selected: PropTypes.number
};

constructor (props) {
super(props);

this.clientSiblingRef = createRef(null);
this.state = {
prevChildren: props.children,
prevFocused: null,
prevSelected: this.props.selected,
prevSelectedKey: getKey(props),
ready: isSelectedValid(props) ? ReadyState.INIT : ReadyState.DONE
};
}

componentDidMount () {
if (this.props.handleSpotlightPause) {
this.props.handleSpotlightPause(false);
// eslint-disable-next-line no-shadow
const DropdownListSpotlightDecorator = (props) => {
const clientSiblingRef = useRef(null);
const [state, setState] = useState({
prevChildren: props.children,
prevFocused: null,
prevSelected: props.selected,
prevSelectedKey: getKey(props),
ready: isSelectedValid(props) ? ReadyState.INIT : ReadyState.DONE
});
const scrollToRef = useRef(() => {});
const lastFocusedKey = useRef(null);

useEffect(() => {
if (props.handleSpotlightPause) {
props.handleSpotlightPause(false);
}
}

componentDidUpdate () {
if (this.state.ready === ReadyState.INIT) {
this.scrollIntoView();
} else if (this.state.ready === ReadyState.SCROLLED) {
this.focusSelected();
} else {
const key = getKey(this.props);
const keysDiffer = key && this.state.prevSelectedKey && key !== this.state.prevSelectedKey;
}, []); // eslint-disable-line react-hooks/exhaustive-deps

if (keysDiffer ||
((!key || !this.state.prevSelectedKey) && this.state.prevSelected !== this.props.selected) ||
!compareChildren(this.state.prevChildren, this.props.children)
) {
this.resetFocus(keysDiffer);
}
}
}

setScrollTo = (scrollTo) => {
this.scrollTo = scrollTo;
const focusSelected = () => {
setState(value => {
return {...value, ready: ReadyState.DONE};
});
};

resetFocus (keysDiffer) {
const resetFocus = useCallback((keysDiffer) => {
let adjustedFocusIndex;

if (!keysDiffer && this.lastFocusedKey) {
const targetIndex = indexFromKey(this.props.children, this.lastFocusedKey);
if (!keysDiffer && lastFocusedKey.current) {
const targetIndex = indexFromKey(props.children, lastFocusedKey.current);
if (targetIndex >= 0) {
adjustedFocusIndex = targetIndex;
}
}

this.setState({
prevChildren: this.props.children,
setState({
prevChildren: props.children,
prevFocused: adjustedFocusIndex,
prevSelected: this.props.selected,
prevSelectedKey: getKey(this.props),
prevSelected: props.selected,
prevSelectedKey: getKey(props),
ready: ReadyState.INIT
});
}
}, [props]);

scrollIntoView = () => {
let {selected} = this.props;
const scrollIntoView = useCallback(() => {
let {selected} = props;

if (this.state.prevFocused == null && !isSelectedValid(this.props)) {
if (state.prevFocused == null && !isSelectedValid(props)) {
selected = 0;
} else if (this.state.prevFocused != null) {
selected = this.state.prevFocused;
} else if (state.prevFocused != null) {
selected = state.prevFocused;
}

this.scrollTo({
scrollToRef.current({
animate: false,
focus: true,
index: selected,
offset: ri.scale(126 * 2), // @sand-item-small-height * 2 (TODO: large text mode not supported!)
stickTo: 'start' // offset from the top of the dropdown
});

this.setState({ready: ReadyState.SCROLLED});
};
setState(value => {
return {...value, ready: ReadyState.SCROLLED};
});
}, [props, state.prevFocused]);

focusSelected () {
this.setState({ready: ReadyState.DONE});
}
useEffect(() => {
if (state.ready === ReadyState.INIT) {
scrollIntoView();
} else if (state.ready === ReadyState.SCROLLED) {
focusSelected();
} else {
const key = getKey(props);
const keysDiffer = key && state.prevSelectedKey && key !== state.prevSelectedKey;

if (keysDiffer ||
((!key || !state.prevSelectedKey) && state.prevSelected !== props.selected) ||
!compareChildren(state.prevChildren, props.children)
) {
resetFocus(keysDiffer);
}
}
}, [props, resetFocus, scrollIntoView, state]);

handleFocus = (ev) => {
const setScrollTo = useCallback((scrollTo) => {
scrollToRef.current = scrollTo;
}, []);

const handleFocus = useCallback((ev) => {
const current = ev.target;
const dropdownListNode = this.clientSiblingRef?.current;
const dropdownListNode = clientSiblingRef?.current;

if (this.state.ready === ReadyState.DONE && !Spotlight.getPointerMode() &&
if (state.ready === ReadyState.DONE && !Spotlight.getPointerMode() &&
current.dataset['index'] != null && dropdownListNode.contains(current)
) {
const focusedIndex = Number(current.dataset['index']);
const lastFocusedKey = getKey({children: this.props.children, selected: focusedIndex});
this.lastFocusedKey = lastFocusedKey;
lastFocusedKey.current = getKey({children: props.children, selected: focusedIndex});
}

if (this.props.onFocus) {
this.props.onFocus(ev);
if (props.onFocus) {
props.onFocus(ev);
}
};
}, [props, state.ready]);

render () {
const props = {...this.props};
delete props.handleSpotlightPause;
const wrappedComponentProps = Object.assign({}, props);
delete wrappedComponentProps.handleSpotlightPause;

return (
<WrappedWithRef {...props} onFocus={this.handleFocus} outermostRef={this.clientSiblingRef} referrerName="DropdownList" scrollTo={this.setScrollTo} />
);
}
return (
<WrappedWithRef {...wrappedComponentProps} onFocus={handleFocus} outermostRef={clientSiblingRef} referrerName="DropdownList" scrollTo={setScrollTo} />
);
};

DropdownListSpotlightDecorator.displayName = 'DropdownListSpotlightDecorator';

DropdownListSpotlightDecorator.propTypes = {
/*
* Passed by DropdownBase to resume Spotlight
*
* @type {Function}
*/
handleSpotlightPause: PropTypes.func,

/*
* Called when an item receives focus.
*
* @type {Function}
*/
onFocus: PropTypes.func,

/*
* Index of the selected item.
*
* @type {Number}
*/
selected: PropTypes.number
};

return DropdownListSpotlightDecorator;
});

const DropdownListDecorator = compose(
Expand Down
23 changes: 23 additions & 0 deletions samples/sampler/stories/helper/icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,27 @@ export const drawingIcons = [
'pen'
].sort();

export const tabIcons = [
'appscontents',
'bookmark',
'browser',
'closedcaption',
'folder',
'guide',
'heart',
'help',
'info',
'location',
'movies',
'music',
'network',
'notification',
'power',
'sound',
'speaker',
'star',
'support',
'timer'
].sort();

export default Object.keys(icons).sort();
8 changes: 4 additions & 4 deletions samples/sampler/stories/qa/TabLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {mergeComponentMetadata} from '@enact/storybook-utils';
import {range, select} from '@enact/storybook-utils/addons/controls';
import {Component, useState} from 'react';

import icons from '../helper/icons';
import {tabIcons} from '../helper/icons';

TabLayout.displayName = 'TabLayout';
const Config = mergeComponentMetadata('TabLayout', TabLayoutBase, TabLayout);
Expand Down Expand Up @@ -81,7 +81,7 @@ export const WithVariableNumberOfTabs = (args) => {
orientation={args['orientation']}
>
{Array.from({length: tabs}, (v, i) => (
<TabLayout.Tab title={`Tab ${i}`} icon={icons[i % icons.length]} key={`tab${i}`}>
<TabLayout.Tab title={`Tab ${i}`} icon={tabIcons[i % tabIcons.length]} key={`tab${i}`}>
<Scroller key={'view' + i}>
<Button>Tab {i} Top</Button>
<BodyText>
Expand Down Expand Up @@ -245,7 +245,7 @@ export const WithDisabledTabs = (args) => {
{Array.from({length: tabs}, (v, i) => (
<TabLayout.Tab
disabled={i % 2 === 1}
icon={icons[i % icons.length]}
icon={tabIcons[i % tabIcons.length]}
title={`Tab ${i}`}
key={`tab${i}`}
>
Expand Down Expand Up @@ -329,7 +329,7 @@ export const WithAllDisabledTabs = (args) => {
orientation={args['orientation']}
>
{Array.from({length: tabs}, (v, i) => (
<TabLayout.Tab disabled icon={icons[i % icons.length]} title={`Tab ${i}`} key={`tab${i}`}>
<TabLayout.Tab disabled icon={tabIcons[i % tabIcons.length]} title={`Tab ${i}`} key={`tab${i}`}>
<Scroller key={'view' + i}>
<Button>Tab {i} Top</Button>
<BodyText>
Expand Down

0 comments on commit 6e1dae5

Please sign in to comment.