Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions ColorPicker/ColorPicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) => {
Expand Down
102 changes: 55 additions & 47 deletions Popup/Popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -387,7 +398,7 @@ const Popup = (props) => {
}
}
}
}, [componentProps, no5WayClose, onClose, position, spotlightRestrict]);
}, [componentProps, containerId, no5WayClose, onClose, position, spotlightRestrict]);

const spotActivator = useCallback(() => {
pausedRef.current.resume();
Expand All @@ -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
Expand All @@ -417,17 +428,17 @@ const Popup = (props) => {
}
}
}
}, [activator, open]);
}, [activator, containerId, open]);

const spotPopupContent = useCallback(() => {
pausedRef.current.resume();

// 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
Expand All @@ -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();
}
Expand All @@ -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();
Expand Down Expand Up @@ -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();

}
Expand All @@ -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
}, []);
Expand Down Expand Up @@ -552,7 +560,7 @@ const Popup = (props) => {
onHide={handlePopupHide}
onShow={handlePopupShow}
open={popupOpen >= OpenState.OPENING}
spotlightId={containerIdRef.current}
spotlightId={containerId}
/>
</FloatingLayer>
);
Expand Down
12 changes: 6 additions & 6 deletions Scroller/EditableWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down
8 changes: 6 additions & 2 deletions tests/screenshot/apps/Limestone-View.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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 (
<WrappedApp {...props} skin={skin} highContrast={highContrast} locale={locale} textSize={textSize} focusRing={focusRing} />
<WrappedApp {...props} focusRing={focusRing} highContrast={highContrast} locale={locale} skin={skin} textSize={textSize} />
);
};

Expand Down
Loading