diff --git a/ColorPicker/ColorPicker.js b/ColorPicker/ColorPicker.js
index 531f6c250..e618db408 100644
--- a/ColorPicker/ColorPicker.js
+++ b/ColorPicker/ColorPicker.js
@@ -24,7 +24,7 @@ import {Cell, Column, Row} from '@enact/ui/Layout';
import Toggleable from '@enact/ui/Toggleable';
import PropTypes from 'prop-types';
import compose from 'ramda/src/compose';
-import {useCallback, useEffect, useState} from 'react';
+import {useCallback, useEffect, useReducer} from 'react';
import BodyText from '../BodyText';
import Button, {ButtonBase} from '../Button';
@@ -52,28 +52,38 @@ const PopupContent = (props) => {
checkPropTypes(PopupContent, props);
const {color, colorHandler, css, presetColors} = props;
- const [hue, setHue] = useState(0);
- const [saturation, setSaturation] = useState(0);
- const [lightness, setLightness] = useState(0);
+
+ const reducer = (reducerState, payload) => {
+ return {...reducerState, ...payload};
+ };
+
+ const createInitialState = () => {
+ return {
+ hue: 0,
+ saturation: 0,
+ lightness: 0
+ };
+ };
+
+ const [state, dispatch] = useReducer(reducer, null, createInitialState);
+ const {hue, saturation, lightness} = state;
useEffect(() => {
- let {h, s, l} = hexToHSL(color);
+ const {h, s, l} = hexToHSL(color);
- setHue(h);
- setSaturation(s);
- setLightness(l);
+ dispatch({hue: h, saturation: s, lightness: l});
}, [color]);
const changeHue = useCallback((ev) => {
- setHue(ev.value);
+ dispatch({hue: ev.value});
}, []);
const changeLightness = useCallback((ev) => {
- setLightness(ev.value);
+ dispatch({lightness: ev.value});
}, []);
const changeSaturation = useCallback((ev) => {
- setSaturation(ev.value);
+ dispatch({saturation: ev.value});
}, []);
const handleClick = useCallback((ev) => {
diff --git a/Popup/Popup.js b/Popup/Popup.js
index 80c6e6368..6bd1434c3 100644
--- a/Popup/Popup.js
+++ b/Popup/Popup.js
@@ -21,7 +21,7 @@ import {getLastContainer} from '@enact/spotlight/src/container';
import FloatingLayer from '@enact/ui/FloatingLayer';
import Transition from '@enact/ui/Transition';
import PropTypes from 'prop-types';
-import {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react';
+import {useCallback, useEffect, useLayoutEffect, useReducer, useRef, useState} from 'react';
import warning from 'warning';
import Skinnable from '../Skinnable';
@@ -308,55 +308,66 @@ const Popup = (props) => {
// Assign the needed props to the rest object for the child component
Object.assign(rest, {noAnimation, position, spotlightRestrict});
- const [activator, setActivator] = useState(null);
- const [addKeyDownListener, setAddKeyDownListener] = useState(false);
- const [floatLayerOpen, setFloatLayerOpen] = useState(open);
- const [popupOpen, setPopupOpen] = useState(open ? OpenState.OPEN : OpenState.CLOSED);
- const [prevOpen, setPrevOpen] = useState(open);
+ const reducer = (reducerState, payload) => {
+ return {...reducerState, ...payload};
+ };
+
+ const createInitialState = () => {
+ return {
+ activator: null,
+ addKeyDownListener: false,
+ floatLayerOpen: open,
+ popupOpen: open ? OpenState.OPEN : OpenState.CLOSED,
+ prevOpen: open
+ };
+ };
- const containerIdRef = useRef(null);
- const pausedRef = useRef(new Pause('Popup'));
+ const [state, dispatch] = useReducer(reducer, null, createInitialState);
+ const {activator, addKeyDownListener, floatLayerOpen, popupOpen, prevOpen} = state;
- if (!containerIdRef.current) {
- containerIdRef.current = Spotlight.add();
- }
+ const [containerId] = useState(() => Spotlight.add());
+ const pausedRef = useRef(new Pause('Popup'));
const getDerivedStateFromProps = useCallback(() => {
if (open !== prevOpen) {
if (open) {
- setPopupOpen(noAnimation || floatLayerOpen ? OpenState.OPEN : OpenState.CLOSED);
- setFloatLayerOpen(true);
- setActivator(Spotlight.getCurrent());
- setPrevOpen(open);
+ dispatch({
+ activator: Spotlight.getCurrent(),
+ floatLayerOpen: true,
+ popupOpen: noAnimation || floatLayerOpen ? OpenState.OPEN : OpenState.CLOSED,
+ prevOpen: open
+ });
} else {
// Disables the spotlight container of popup when `noAnimation` set
if (noAnimation) {
- const node = getContainerNode(containerIdRef.current);
+ const node = getContainerNode(containerId);
if (node) {
node.dataset['spotlightContainerDisabled'] = true;
}
}
- setPopupOpen(OpenState.CLOSED);
- setFloatLayerOpen(popupOpen === OpenState.OPEN ? !noAnimation : false);
- setActivator(noAnimation ? null : activator);
- setPrevOpen(open);
+ dispatch({
+ activator: noAnimation ? null : activator,
+ floatLayerOpen: popupOpen === OpenState.OPEN ? !noAnimation : false,
+ popupOpen: OpenState.CLOSED,
+ prevOpen: open
+ });
}
}
return null;
- }, [activator, floatLayerOpen, noAnimation, open, popupOpen, prevOpen]);
+ }, [activator, containerId, floatLayerOpen, noAnimation, open, popupOpen, prevOpen]);
const handleKeyDown = useCallback((ev) => {
if (no5WayClose) return;
const keyCode = ev.keyCode;
const direction = getDirection(keyCode);
- const spottables = Spotlight.getSpottableDescendants(containerIdRef.current).length;
+ const spottables = Spotlight.getSpottableDescendants(containerId).length;
const current = Spotlight.getCurrent();
- if (direction && (!spottables || current && getContainerNode(containerIdRef.current).contains(current))) {
+ if (direction && (!spottables || current && getContainerNode(containerId).contains(current))) {
// explicitly restrict navigation in order to manage focus state when attempting to leave the popup
- Spotlight.set(containerIdRef.current, {restrict: 'self-only'});
+ Spotlight.set(containerId, {restrict: 'self-only'});
if (onClose) {
let focusChanged;
@@ -387,7 +398,7 @@ const Popup = (props) => {
}
}
}
- }, [componentProps, no5WayClose, onClose, position, spotlightRestrict]);
+ }, [componentProps, containerId, no5WayClose, onClose, position, spotlightRestrict]);
const spotActivator = useCallback(() => {
pausedRef.current.resume();
@@ -396,10 +407,10 @@ const Popup = (props) => {
if (open) return;
const current = Spotlight.getCurrent();
- const containerNode = getContainerNode(containerIdRef.current);
+ const containerNode = getContainerNode(containerId);
const lastContainerId = getLastContainer();
- setAddKeyDownListener(false);
+ dispatch({addKeyDownListener: false});
// if there is no currently-spotted control, or it is wrapped by the popup's container, we
// know it's safe to change focus
@@ -417,7 +428,7 @@ const Popup = (props) => {
}
}
}
- }, [activator, open]);
+ }, [activator, containerId, open]);
const spotPopupContent = useCallback(() => {
pausedRef.current.resume();
@@ -425,9 +436,9 @@ const Popup = (props) => {
// only spot the activator if the popup is open
if (!open) return;
- setAddKeyDownListener(true);
+ dispatch({addKeyDownListener: true});
- if (!Spotlight.isPaused() && !Spotlight.focus(containerIdRef.current)) {
+ if (!Spotlight.isPaused() && !Spotlight.focus(containerId)) {
const current = Spotlight.getCurrent();
// In cases where the container contains no spottable controls, or we're in pointer-mode, focus
@@ -436,13 +447,13 @@ const Popup = (props) => {
if (current) {
current.blur();
}
- Spotlight.setActiveContainer(containerIdRef.current);
+ Spotlight.setActiveContainer(containerId);
}
- }, [open]);
+ }, [containerId, open]);
const handleFloatingLayerOpen = useCallback(() => {
if (!noAnimation && popupOpen !== OpenState.OPEN) {
- setPopupOpen(OpenState.OPENING);
+ dispatch({popupOpen: OpenState.OPENING});
} else if (popupOpen === OpenState.OPEN && open) {
spotPopupContent();
}
@@ -455,25 +466,22 @@ const Popup = (props) => {
const handlePopupHide = useCallback((ev) => {
forwardHide(ev, componentProps);
- setFloatLayerOpen(() => {
- if (!ev.currentTarget || ev.currentTarget.getAttribute('data-spotlight-id') === containerIdRef.current) {
- spotActivator();
- }
- setActivator(null);
+ if (!ev.currentTarget || ev.currentTarget.getAttribute('data-spotlight-id') === containerId) {
+ spotActivator();
+ }
- return false;
- });
- }, [componentProps, spotActivator]);
+ dispatch({activator: null, floatLayerOpen: false});
+ }, [componentProps, containerId, spotActivator]);
const handlePopupShow = useCallback((ev) => {
forwardShow(ev, componentProps);
- setPopupOpen(OpenState.OPEN);
+ dispatch({popupOpen: OpenState.OPEN});
- if (!ev.currentTarget || ev.currentTarget.getAttribute('data-spotlight-id') === containerIdRef.current) {
+ if (!ev.currentTarget || ev.currentTarget.getAttribute('data-spotlight-id') === containerId) {
spotPopupContent();
}
- }, [componentProps, spotPopupContent]);
+ }, [componentProps, containerId, spotPopupContent]);
useLayoutEffect(() => {
getDerivedStateFromProps();
@@ -511,7 +519,7 @@ const Popup = (props) => {
// If the popup is open on mount, we need to pause spotlight so nothing steals focus
// while the popup is rendering.
pausedRef.current.pause();
- if (getContainerNode(containerIdRef.current)) {
+ if (getContainerNode(containerId)) {
spotPopupContent();
}
@@ -521,7 +529,7 @@ const Popup = (props) => {
if (open) {
off('keydown', handleKeyDown);
}
- Spotlight.remove(containerIdRef.current);
+ Spotlight.remove(containerId);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -552,7 +560,7 @@ const Popup = (props) => {
onHide={handlePopupHide}
onShow={handlePopupShow}
open={popupOpen >= OpenState.OPENING}
- spotlightId={containerIdRef.current}
+ spotlightId={containerId}
/>
);
diff --git a/Scroller/EditableWrapper.js b/Scroller/EditableWrapper.js
index 8747c76bf..4558f5aff 100644
--- a/Scroller/EditableWrapper.js
+++ b/Scroller/EditableWrapper.js
@@ -344,7 +344,7 @@ const EditableWrapper = (props) => {
// Add rearranged items
const addRearrangedItems = useCallback(({currentMoveDirection, toIndex}) => {
// Set the currentMoveDirection to css variable
- const {rtl} = scrollContainerHandle.current;
+ const {current: {rtl}} = scrollContainerHandle;
wrapperRef.current.style.setProperty('--move-direction', currentMoveDirection * (rtl ? -1 : 1));
const {fromIndex, rearrangedItems, selectedItem} = mutableRef.current;
@@ -392,7 +392,7 @@ const EditableWrapper = (props) => {
// Move items
const moveItems = useCallback((toIndex) => {
const {selectedItem} = mutableRef.current;
- const {rtl} = scrollContainerHandle.current;
+ const {current: {rtl}} = scrollContainerHandle;
if (selectedItem && !selectedItem.hasAttribute('data-is-hiding')) {
// Bail out when index is out of scope
@@ -433,7 +433,7 @@ const EditableWrapper = (props) => {
const {keyCode} = ev;
const container = scrollContentRef.current;
const {itemWidth, prevToIndex} = mutableRef.current;
- const {rtl} = scrollContainerHandle.current;
+ const {current: {rtl}} = scrollContainerHandle;
const toIndex = (!rtl ^ !is('left', keyCode)) ? prevToIndex - 1 : prevToIndex + 1;
const scrollLeft = container.scrollLeft * (rtl ? -1 : 1);
@@ -538,7 +538,7 @@ const EditableWrapper = (props) => {
const getNextIndexFromPosition = useCallback((x, tolerance) => {
const {centeredOffset, itemWidth, prevToIndex} = mutableRef.current;
- const {rtl} = scrollContainerHandle.current;
+ const {current: {rtl}} = scrollContainerHandle;
const bodyWidth = document.body.getBoundingClientRect().width;
// Determine toIndex with mouse client x position
@@ -575,7 +575,7 @@ const EditableWrapper = (props) => {
const handleMouseLeave = useCallback(() => {
const {focusedItem, itemWidth, lastInputType, lastMouseClientX, selectedItem} = mutableRef.current;
- const {rtl} = scrollContainerHandle.current;
+ const {current: {rtl}} = scrollContainerHandle;
const scrollContentNode = scrollContentRef.current;
const scrollContentCenter = scrollContentNode.getBoundingClientRect().width / 2;
@@ -788,7 +788,7 @@ const EditableWrapper = (props) => {
const handleTouchEnd = useCallback((ev) => {
const {itemWidth, lastInputType, lastMouseClientX, selectedItem} = mutableRef.current;
- const {rtl} = scrollContainerHandle.current;
+ const {current: {rtl}} = scrollContainerHandle;
const scrollContentNode = scrollContentRef.current;
const scrollContentCenter = scrollContentNode.getBoundingClientRect().width / 2;
diff --git a/tests/screenshot/apps/Limestone-View.js b/tests/screenshot/apps/Limestone-View.js
index 21985ac3c..021a88abc 100644
--- a/tests/screenshot/apps/Limestone-View.js
+++ b/tests/screenshot/apps/Limestone-View.js
@@ -135,6 +135,10 @@ class App extends ReactComponent {
}
}
+// Declared at module scope so, the components are not created during render (react-hooks/static-components).
+const WrappedAppAutoFocus = ThemeDecorator({noAutoFocus: false}, App);
+const WrappedAppNoAutoFocus = ThemeDecorator({noAutoFocus: true}, App);
+
const ExportedApp = (props) => {
// Common test parameters
let skin = url.searchParams.get('skin');
@@ -178,14 +182,14 @@ const ExportedApp = (props) => {
}
}
- const WrappedApp = ThemeDecorator({noAutoFocus}, App);
+ const WrappedApp = noAutoFocus ? WrappedAppNoAutoFocus : WrappedAppAutoFocus;
useEffect(() => {
document.querySelector('#root').classList.add('spotlight-input-key');
}, []);
return (
-
+
);
};