From dffdbdb47b77f1c2f4d2b05ffebe25559ad21bcc Mon Sep 17 00:00:00 2001 From: alexandru-morariu-lgp Date: Wed, 9 Oct 2024 16:05:16 +0300 Subject: [PATCH 1/4] Initial commit --- Input/InputFieldSpotlightDecorator.js | 760 +++++++++++++++++++------- 1 file changed, 550 insertions(+), 210 deletions(-) diff --git a/Input/InputFieldSpotlightDecorator.js b/Input/InputFieldSpotlightDecorator.js index 9160e15f18..8f070e6f0a 100644 --- a/Input/InputFieldSpotlightDecorator.js +++ b/Input/InputFieldSpotlightDecorator.js @@ -1,11 +1,15 @@ -import {call, forward, forwardCustom, forwardWithPrevent, handle, stopImmediate} from '@enact/core/handle'; +import { + call, + forward, forwardCustom, forwardWithPrevent, handle, stopImmediate} from '@enact/core/handle'; import hoc from '@enact/core/hoc'; import {is} from '@enact/core/keymap'; import {getDirection, Spotlight} from '@enact/spotlight'; import Pause from '@enact/spotlight/Pause'; import Spottable from '@enact/spotlight/Spottable'; import PropTypes from 'prop-types'; -import {Component as ReactComponent} from 'react'; +import { + // Component as ReactComponent, + useCallback, useEffect, useMemo, useState} from 'react'; import {lockPointer, releasePointer} from './pointer'; @@ -22,10 +26,10 @@ const isSelectionAtLocation = (target, location) => { } }; -const handleKeyDown = handle( - forwardWithPrevent('onKeyDown'), - call('onKeyDown') -); +// const handleKeyDown = handle( +// forwardWithPrevent('onKeyDown'), +// call('onKeyDown') +// ); /** * Default config for {@link sandstone/Input.InputSpotlightDecorator|InputSpotlightDecorator} @@ -53,7 +57,341 @@ const defaultConfig = { * @hoc * @private */ -const InputSpotlightDecorator = hoc(defaultConfig, (config, Wrapped) => { +// const InputSpotlightDecorator = hoc(defaultConfig, (config, Wrapped) => { +// const {noLockPointer} = config; +// const Component = Spottable({emulateMouse: false}, Wrapped); +// const forwardBlur = forward('onBlur'); +// const forwardMouseDown = forward('onMouseDown'); +// const forwardFocus = forward('onFocus'); +// const forwardKeyUp = forward('onKeyUp'); +// +// return class extends ReactComponent { +// static displayName = 'InputSpotlightDecorator'; +// +// static propTypes = /** @lends sandstone/Input/InputSpotlightDecorator.InputSpotlightDecorator.prototype */ { +// /** +// * Focuses the when the decorator is focused via 5-way. +// * +// * @type {Boolean} +// * @default false +// * @public +// */ +// autoFocus: PropTypes.bool, +// +// /** +// * Applies a disabled style and the control becomes non-interactive. +// * +// * @type {Boolean} +// * @default false +// * @public +// */ +// disabled: PropTypes.bool, +// +// /** +// * Blurs the input when the "enter" key is pressed. +// * +// * @type {Boolean} +// * @default false +// * @public +// */ +// dismissOnEnter: PropTypes.bool, +// +// /** +// * Called when the internal is focused. +// * +// * @type {Function} +// * @param {Object} event +// * @public +// */ +// onActivate: PropTypes.func, +// +// /** +// * Called when the internal loses focus. +// * +// * @type {Function} +// * @param {Object} event +// * @public +// */ +// onDeactivate: PropTypes.func, +// +// /** +// * Called when the component is removed while retaining focus. +// * +// * @type {Function} +// * @param {Object} event +// * @public +// */ +// onSpotlightDisappear: PropTypes.func, +// +// /** +// * Disables spotlight navigation into the component. +// * +// * @type {Boolean} +// * @default false +// * @public +// */ +// spotlightDisabled: PropTypes.bool +// }; +// +// constructor (props) { +// super(props); +// +// this.focused = null; +// this.node = null; +// this.fromMouse = false; +// this.paused = new Pause('InputSpotlightDecorator'); +// this.handleKeyDown = handleKeyDown.bind(this); +// this.prevStatus = { +// focused: null, +// node: null +// }; +// } +// +// componentWillUnmount () { +// this.paused.resume(); +// +// if (this.focused === 'input') { +// const {onSpotlightDisappear} = this.props; +// +// if (onSpotlightDisappear) { +// onSpotlightDisappear(); +// } +// +// if (!noLockPointer) { +// releasePointer(this.node); +// } +// } +// } +// +// updateFocus = () => { +// // focus node if `InputSpotlightDecorator` is pausing Spotlight or if Spotlight is paused +// if ( +// this.node && +// Spotlight.getCurrent() !== this.node && +// (this.paused.isPaused() || !Spotlight.isPaused()) +// ) { +// if (this.fromMouse) { +// this.node.focus({preventScroll: true}); +// } else { +// this.node.focus(); +// } +// } +// +// const focusChanged = this.focused !== this.prevStatus.focused; +// if (focusChanged) { +// if (this.focused === 'input') { +// forwardCustom('onActivate')(null, this.props); +// if (!noLockPointer) { +// lockPointer(this.node); +// } +// this.paused.pause(); +// } else if (this.prevStatus.focused === 'input') { +// forwardCustom('onDeactivate')(null, this.props); +// if (!noLockPointer) { +// releasePointer(this.prevStatus.node); +// } +// this.paused.resume(); +// } +// } +// +// this.prevStatus.focused = this.focused; +// this.prevStatus.node = this.node; +// }; +// +// focus = (focused, node, fromMouse) => { +// this.focused = focused; +// this.node = node; +// this.fromMouse = fromMouse; +// this.updateFocus(); +// }; +// +// blur = () => { +// if (this.focused || this.node) { +// this.focused = null; +// this.node = null; +// this.updateFocus(); +// } +// }; +// +// focusDecorator = (decorator) => { +// this.focus('decorator', decorator, false); +// }; +// +// focusInput = (decorator, fromMouse) => { +// this.focus('input', decorator.querySelector('input'), fromMouse); +// }; +// +// onBlur = (ev) => { +// if (!this.props.autoFocus) { +// if (isBubbling(ev)) { +// if (Spotlight.getPointerMode()) { +// this.blur(); +// forwardBlur(ev, this.props); +// } else { +// this.focused = 'decorator'; +// this.node = ev.currentTarget; +// this.fromMouse = false; +// ev.stopPropagation(); +// } +// } else if (!ev.currentTarget.contains(ev.relatedTarget)) { +// // Blurring decorator but not focusing input +// forwardBlur(ev, this.props); +// this.blur(); +// } +// } else if (isBubbling(ev)) { +// if (this.focused === 'input' && this.node === ev.target && ev.currentTarget !== ev.relatedTarget) { +// this.blur(); +// forwardBlur(ev, this.props); +// } else { +// this.focusDecorator(ev.currentTarget); +// ev.stopPropagation(); +// this.blur(); +// } +// } +// }; +// +// onMouseDown = (ev) => { +// const {disabled, spotlightDisabled} = this.props; +// +// this.setDownTarget(ev); +// // focus the whenever clicking on any part of the component to ensure both that +// // the has focus and Spotlight is paused. +// if (!disabled && !spotlightDisabled) { +// this.focusInput(ev.currentTarget, true); +// } +// +// forwardMouseDown(ev, this.props); +// }; +// +// onFocus = (ev) => { +// forwardFocus(ev, this.props); +// +// // when in autoFocus mode, focusing the decorator directly will cause it to +// // forward the focus onto the +// if (!isBubbling(ev) && (this.props.autoFocus && this.focused === null && !Spotlight.getPointerMode())) { +// this.focusInput(ev.currentTarget, false); +// ev.stopPropagation(); +// } +// }; +// +// onKeyDown (ev) { +// const {currentTarget, keyCode, target} = ev; +// +// // cache the target if this is the first keyDown event to ensure the component had focus +// // when the key interaction started +// this.setDownTarget(ev); +// +// if (this.focused === 'input') { +// const isDown = is('down', keyCode); +// const isLeft = is('left', keyCode); +// const isRight = is('right', keyCode); +// const isUp = is('up', keyCode); +// +// // move spotlight +// const shouldSpotlightMove = ( +// // No value exists! (Can happen when disabled) +// target.value == null || +// // on left + at beginning of selection +// (isLeft && isSelectionAtLocation(target, 0)) || +// // on right + at end of selection (note: fails on non-selectable types usually) +// (isRight && isSelectionAtLocation(target, target.value.length)) || +// // on up +// isUp || +// // on down +// isDown +// ); +// +// // prevent modifying the value via 5-way for numeric fields +// if ((isUp || isDown) && target.type === 'number') { +// ev.preventDefault(); +// } +// +// if (shouldSpotlightMove) { +// const direction = getDirection(keyCode); +// const {getPointerMode, move, setPointerMode} = Spotlight; +// +// if (getPointerMode()) { +// setPointerMode(false); +// } +// +// stopImmediate(ev); +// this.paused.resume(); +// +// // Move spotlight in the keypress direction +// if (move(direction)) { +// // if successful, reset the internal state +// this.blur(); +// } else { +// // if there is no other spottable elements, focus `InputDecorator` instead +// this.focusDecorator(currentTarget); +// } +// } else if (isLeft || isRight) { +// // prevent 5-way nav for left/right keys within the +// stopImmediate(ev); +// } +// } +// } +// +// onKeyUp = (ev) => { +// const {dismissOnEnter} = this.props; +// const {currentTarget, keyCode, target} = ev; +// +// // verify that we have a matching pair of key down/up events to avoid adjusting focus +// // when the component received focus mid-press +// if (target === this.downTarget) { +// this.downTarget = null; +// +// if (!this.props.disabled) { +// if (this.focused === 'input' && dismissOnEnter && is('enter', keyCode)) { +// this.focusDecorator(currentTarget); +// // prevent Enter onKeyPress which triggers an onMouseDown via Spotlight +// ev.preventDefault(); +// } else if (this.focused !== 'input' && is('enter', keyCode)) { +// this.focusInput(currentTarget, false); +// } +// } +// } +// +// forwardKeyUp(ev, this.props); +// }; +// +// setDownTarget (ev) { +// const {repeat, target} = ev; +// +// if (!repeat) { +// this.downTarget = target; +// } +// } +// +// render () { +// const props = Object.assign({}, this.props); +// delete props.autoFocus; +// delete props.onActivate; +// delete props.onDeactivate; +// +// return ( +// +// ); +// } +// }; +// }); + +/** + * A higher-order component that manages the + * spotlight behavior for an {@link sandstone/Input.Input} + * + * @class InputSpotlightDecorator + * @memberof sandstone/Input/InputSpotlightDecorator + * @hoc + * @private + */const InputSpotlightDecorator = hoc(defaultConfig, (config, Wrapped) => { const {noLockPointer} = config; const Component = Spottable({emulateMouse: false}, Wrapped); const forwardBlur = forward('onBlur'); @@ -61,223 +399,162 @@ const InputSpotlightDecorator = hoc(defaultConfig, (config, Wrapped) => { const forwardFocus = forward('onFocus'); const forwardKeyUp = forward('onKeyUp'); - return class extends ReactComponent { - static displayName = 'InputSpotlightDecorator'; - - static propTypes = /** @lends sandstone/Input/InputSpotlightDecorator.InputSpotlightDecorator.prototype */ { - /** - * Focuses the when the decorator is focused via 5-way. - * - * @type {Boolean} - * @default false - * @public - */ - autoFocus: PropTypes.bool, - - /** - * Applies a disabled style and the control becomes non-interactive. - * - * @type {Boolean} - * @default false - * @public - */ - disabled: PropTypes.bool, - - /** - * Blurs the input when the "enter" key is pressed. - * - * @type {Boolean} - * @default false - * @public - */ - dismissOnEnter: PropTypes.bool, - - /** - * Called when the internal is focused. - * - * @type {Function} - * @param {Object} event - * @public - */ - onActivate: PropTypes.func, - - /** - * Called when the internal loses focus. - * - * @type {Function} - * @param {Object} event - * @public - */ - onDeactivate: PropTypes.func, - - /** - * Called when the component is removed while retaining focus. - * - * @type {Function} - * @param {Object} event - * @public - */ - onSpotlightDisappear: PropTypes.func, - - /** - * Disables spotlight navigation into the component. - * - * @type {Boolean} - * @default false - * @public - */ - spotlightDisabled: PropTypes.bool - }; - - constructor (props) { - super(props); - - this.focused = null; - this.node = null; - this.fromMouse = false; - this.paused = new Pause('InputSpotlightDecorator'); - this.handleKeyDown = handleKeyDown.bind(this); - this.prevStatus = { - focused: null, - node: null - }; - } - - componentWillUnmount () { - this.paused.resume(); - - if (this.focused === 'input') { - const {onSpotlightDisappear} = this.props; - - if (onSpotlightDisappear) { - onSpotlightDisappear(); - } + const InputSpotlight = ({...props}) => { + const paused = useMemo(() => new Pause('InputSpotlightDecorator'), []); + const [downTarget, setDownTarget] = useState(); + const [focused, setFocused] = useState(null); + const [fromMouse, setFromMouse] = useState(false); + const [node, setNode] = useState(null); + const [prevStatus, setPrevStatus] = useState({ + focused: null, + node: null + }); + + useEffect(() => { + return () => { + paused.resume(); + + if (focused === 'input') { + const {onSpotlightDisappear} = props; + + if (onSpotlightDisappear) { + onSpotlightDisappear(); + } - if (!noLockPointer) { - releasePointer(this.node); + if (!noLockPointer) { + releasePointer(node); + } } - } - } + }; + // }, [focused, node, paused, props]); + }, []); - updateFocus = () => { + const updateFocus = useCallback(() => { // focus node if `InputSpotlightDecorator` is pausing Spotlight or if Spotlight is paused if ( - this.node && - Spotlight.getCurrent() !== this.node && - (this.paused.isPaused() || !Spotlight.isPaused()) + node && + Spotlight.getCurrent() !== node && + (paused.isPaused() || !Spotlight.isPaused()) ) { - if (this.fromMouse) { - this.node.focus({preventScroll: true}); + if (fromMouse) { + node.focus({preventScroll: true}); } else { - this.node.focus(); + node.focus(); } } - const focusChanged = this.focused !== this.prevStatus.focused; + const focusChanged = focused !== prevStatus.focused; if (focusChanged) { - if (this.focused === 'input') { - forwardCustom('onActivate')(null, this.props); + if (focused === 'input') { + forwardCustom('onActivate')(null, props); if (!noLockPointer) { - lockPointer(this.node); + lockPointer(node); } - this.paused.pause(); - } else if (this.prevStatus.focused === 'input') { - forwardCustom('onDeactivate')(null, this.props); + paused.pause(); + } else if (prevStatus.focused === 'input') { + forwardCustom('onDeactivate')(null, props); if (!noLockPointer) { - releasePointer(this.prevStatus.node); + releasePointer(prevStatus.node); } - this.paused.resume(); + paused.resume(); } } - this.prevStatus.focused = this.focused; - this.prevStatus.node = this.node; - }; - - focus = (focused, node, fromMouse) => { - this.focused = focused; - this.node = node; - this.fromMouse = fromMouse; - this.updateFocus(); - }; - - blur = () => { - if (this.focused || this.node) { - this.focused = null; - this.node = null; - this.updateFocus(); + setPrevStatus({focused: focused, node: node}); + }, [focused, fromMouse, node, paused, prevStatus, props]); + + const focus = useCallback((focusedValue, nodeValue, fromMouseValue) => { + setFocused(focusedValue); + setFromMouse(fromMouseValue); + setNode(nodeValue); + updateFocus(); + }, [updateFocus]); + + const blur = useCallback(() => { + if (focused || node) { + setFocused(null); + setNode(null); + updateFocus(); } - }; + }, [focused, node, updateFocus]); - focusDecorator = (decorator) => { - this.focus('decorator', decorator, false); - }; + const focusDecorator = useCallback((decorator) => { + focus('decorator', decorator, false); + }, [focus]); - focusInput = (decorator, fromMouse) => { - this.focus('input', decorator.querySelector('input'), fromMouse); - }; + const focusInput = useCallback((decorator, fromMouseValue) => { + focus('input', decorator.querySelector('input'), fromMouseValue); + }, [focus]); - onBlur = (ev) => { - if (!this.props.autoFocus) { + const onBlur = useCallback((ev) => { + if (!props.autoFocus) { if (isBubbling(ev)) { if (Spotlight.getPointerMode()) { - this.blur(); - forwardBlur(ev, this.props); + blur(); + forwardBlur(ev, props); } else { - this.focused = 'decorator'; - this.node = ev.currentTarget; - this.fromMouse = false; + setFocused('decorator'); + setFromMouse(false); + setNode(ev.currentTarget); ev.stopPropagation(); } } else if (!ev.currentTarget.contains(ev.relatedTarget)) { // Blurring decorator but not focusing input - forwardBlur(ev, this.props); - this.blur(); + forwardBlur(ev, props); + blur(); } } else if (isBubbling(ev)) { - if (this.focused === 'input' && this.node === ev.target && ev.currentTarget !== ev.relatedTarget) { - this.blur(); - forwardBlur(ev, this.props); + if (focused === 'input' && node === ev.target && ev.currentTarget !== ev.relatedTarget) { + blur(); + forwardBlur(ev, props); } else { - this.focusDecorator(ev.currentTarget); + focusDecorator(ev.currentTarget); ev.stopPropagation(); - this.blur(); + blur(); } } + }, [blur, focusDecorator, focused, node, props]); + + const updateDownTarget = (ev) => { + const {repeat, target} = ev; + + if (!repeat) { + setDownTarget(target); + } }; - onMouseDown = (ev) => { - const {disabled, spotlightDisabled} = this.props; + const onMouseDown = useCallback((ev) => { + const {disabled, spotlightDisabled} = props; - this.setDownTarget(ev); + updateDownTarget(ev); // focus the whenever clicking on any part of the component to ensure both that // the has focus and Spotlight is paused. if (!disabled && !spotlightDisabled) { - this.focusInput(ev.currentTarget, true); + focusInput(ev.currentTarget, true); } - forwardMouseDown(ev, this.props); - }; + forwardMouseDown(ev, props); + }, [focusInput, props]); - onFocus = (ev) => { - forwardFocus(ev, this.props); + const onFocus = useCallback((ev) => { + forwardFocus(ev, props); // when in autoFocus mode, focusing the decorator directly will cause it to // forward the focus onto the - if (!isBubbling(ev) && (this.props.autoFocus && this.focused === null && !Spotlight.getPointerMode())) { - this.focusInput(ev.currentTarget, false); + if (!isBubbling(ev) && (props.autoFocus && focused === null && !Spotlight.getPointerMode())) { + focusInput(ev.currentTarget, false); ev.stopPropagation(); } - }; + }, [focusInput, focused, props]); - onKeyDown (ev) { + const onKeyDown = (ev) => { const {currentTarget, keyCode, target} = ev; // cache the target if this is the first keyDown event to ensure the component had focus // when the key interaction started - this.setDownTarget(ev); + updateDownTarget(ev); - if (this.focused === 'input') { + if (focused === 'input') { const isDown = is('down', keyCode); const isLeft = is('left', keyCode); const isRight = is('right', keyCode); @@ -311,72 +588,135 @@ const InputSpotlightDecorator = hoc(defaultConfig, (config, Wrapped) => { } stopImmediate(ev); - this.paused.resume(); + paused.resume(); // Move spotlight in the keypress direction if (move(direction)) { // if successful, reset the internal state - this.blur(); + blur(); } else { // if there is no other spottable elements, focus `InputDecorator` instead - this.focusDecorator(currentTarget); + focusDecorator(currentTarget); } } else if (isLeft || isRight) { // prevent 5-way nav for left/right keys within the stopImmediate(ev); } } - } + }; + + const handleKeyDown = handle( + forwardWithPrevent('onKeyDown'), + (ev) => onKeyDown(ev) + ); - onKeyUp = (ev) => { - const {dismissOnEnter} = this.props; + const onKeyUp = useCallback((ev) => { + const {dismissOnEnter} = props; const {currentTarget, keyCode, target} = ev; // verify that we have a matching pair of key down/up events to avoid adjusting focus // when the component received focus mid-press - if (target === this.downTarget) { - this.downTarget = null; + if (target === downTarget) { + setDownTarget(null); - if (!this.props.disabled) { - if (this.focused === 'input' && dismissOnEnter && is('enter', keyCode)) { - this.focusDecorator(currentTarget); + if (!props.disabled) { + if (focused === 'input' && dismissOnEnter && is('enter', keyCode)) { + focusDecorator(currentTarget); // prevent Enter onKeyPress which triggers an onMouseDown via Spotlight ev.preventDefault(); - } else if (this.focused !== 'input' && is('enter', keyCode)) { - this.focusInput(currentTarget, false); + } else if (focused !== 'input' && is('enter', keyCode)) { + focusInput(currentTarget, false); } } } - forwardKeyUp(ev, this.props); - }; - - setDownTarget (ev) { - const {repeat, target} = ev; - - if (!repeat) { - this.downTarget = target; - } - } - - render () { - const props = Object.assign({}, this.props); - delete props.autoFocus; - delete props.onActivate; - delete props.onDeactivate; - - return ( - - ); - } + forwardKeyUp(ev, props); + }, [downTarget, focusDecorator, focusInput, focused, props]); + + delete props.autoFocus; + delete props.onActivate; + delete props.onDeactivate; + + console.log(focused); + + return ( + + ); }; + InputSpotlight.displayName = 'InputSpotlightDecorator'; + InputSpotlight.propTypes = /** @lends sandstone/Input/InputSpotlightDecorator.InputSpotlightDecorator.prototype */ { + /** + * Focuses the when the decorator is focused via 5-way. + * + * @type {Boolean} + * @default false + * @public + */ + autoFocus: PropTypes.bool, + + /** + * Applies a disabled style and the control becomes non-interactive. + * + * @type {Boolean} + * @default false + * @public + */ + disabled: PropTypes.bool, + + /** + * Blurs the input when the "enter" key is pressed. + * + * @type {Boolean} + * @default false + * @public + */ + dismissOnEnter: PropTypes.bool, + + /** + * Called when the internal is focused. + * + * @type {Function} + * @param {Object} event + * @public + */ + onActivate: PropTypes.func, + + /** + * Called when the internal loses focus. + * + * @type {Function} + * @param {Object} event + * @public + */ + onDeactivate: PropTypes.func, + + /** + * Called when the component is removed while retaining focus. + * + * @type {Function} + * @param {Object} event + * @public + */ + onSpotlightDisappear: PropTypes.func, + + /** + * Disables spotlight navigation into the component. + * + * @type {Boolean} + * @default false + * @public + */ + spotlightDisabled: PropTypes.bool + }; + + return InputSpotlight; }); export default InputSpotlightDecorator; From b12121fc857bdc957743d6c6928c672ac5fa802d Mon Sep 17 00:00:00 2001 From: alexandru-morariu-lgp Date: Mon, 21 Oct 2024 10:45:02 +0300 Subject: [PATCH 2/4] Fixed a problem with spotlight --- Input/InputFieldSpotlightDecorator.js | 91 +++++++++++++++------------ 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/Input/InputFieldSpotlightDecorator.js b/Input/InputFieldSpotlightDecorator.js index 8f070e6f0a..206385a3d7 100644 --- a/Input/InputFieldSpotlightDecorator.js +++ b/Input/InputFieldSpotlightDecorator.js @@ -9,7 +9,8 @@ import Spottable from '@enact/spotlight/Spottable'; import PropTypes from 'prop-types'; import { // Component as ReactComponent, - useCallback, useEffect, useMemo, useState} from 'react'; + useCallback, useEffect, useMemo, useRef, useState +} from 'react'; import {lockPointer, releasePointer} from './pointer'; @@ -400,12 +401,20 @@ const defaultConfig = { const forwardKeyUp = forward('onKeyUp'); const InputSpotlight = ({...props}) => { + const {onSpotlightDisappear} = props; const paused = useMemo(() => new Pause('InputSpotlightDecorator'), []); const [downTarget, setDownTarget] = useState(); - const [focused, setFocused] = useState(null); + // const [focused, setFocused] = useState(null); + const focused = useRef(null); const [fromMouse, setFromMouse] = useState(false); - const [node, setNode] = useState(null); - const [prevStatus, setPrevStatus] = useState({ + // const [node, setNode] = useState(null); + const node = useRef(null); + // const [prevStatus, setPrevStatus] = useState({ + // focused: null, + // node: null + // }); + + const prevStatus = useRef({ focused: null, node: null }); @@ -414,69 +423,67 @@ const defaultConfig = { return () => { paused.resume(); - if (focused === 'input') { - const {onSpotlightDisappear} = props; - + if (focused.current === 'input') { if (onSpotlightDisappear) { onSpotlightDisappear(); } if (!noLockPointer) { - releasePointer(node); + releasePointer(node.current); } } }; - // }, [focused, node, paused, props]); - }, []); + }, [paused, onSpotlightDisappear]); + // }, []); const updateFocus = useCallback(() => { // focus node if `InputSpotlightDecorator` is pausing Spotlight or if Spotlight is paused if ( - node && - Spotlight.getCurrent() !== node && + node.current && + Spotlight.getCurrent() !== node.current && (paused.isPaused() || !Spotlight.isPaused()) ) { if (fromMouse) { - node.focus({preventScroll: true}); + node.current.focus({preventScroll: true}); } else { - node.focus(); + node.current.focus(); } } - const focusChanged = focused !== prevStatus.focused; + const focusChanged = focused.current !== prevStatus.current.focused; if (focusChanged) { - if (focused === 'input') { + if (focused.current === 'input') { forwardCustom('onActivate')(null, props); if (!noLockPointer) { - lockPointer(node); + lockPointer(node.current); } paused.pause(); - } else if (prevStatus.focused === 'input') { + } else if (prevStatus.current.focused === 'input') { forwardCustom('onDeactivate')(null, props); if (!noLockPointer) { - releasePointer(prevStatus.node); + releasePointer(prevStatus.current.node); } paused.resume(); } } - setPrevStatus({focused: focused, node: node}); - }, [focused, fromMouse, node, paused, prevStatus, props]); + prevStatus.current = {focused: focused.current, node: node.current}; + }, [fromMouse, paused, props]); const focus = useCallback((focusedValue, nodeValue, fromMouseValue) => { - setFocused(focusedValue); + focused.current = focusedValue; setFromMouse(fromMouseValue); - setNode(nodeValue); + node.current = nodeValue; updateFocus(); }, [updateFocus]); const blur = useCallback(() => { - if (focused || node) { - setFocused(null); - setNode(null); + if (focused.current || node.current) { + focused.current = null; + node.current = null; updateFocus(); } - }, [focused, node, updateFocus]); + }, [updateFocus]); const focusDecorator = useCallback((decorator) => { focus('decorator', decorator, false); @@ -493,9 +500,9 @@ const defaultConfig = { blur(); forwardBlur(ev, props); } else { - setFocused('decorator'); + focused.current = 'decorator'; setFromMouse(false); - setNode(ev.currentTarget); + node.current = ev.currentTarget; ev.stopPropagation(); } } else if (!ev.currentTarget.contains(ev.relatedTarget)) { @@ -504,7 +511,7 @@ const defaultConfig = { blur(); } } else if (isBubbling(ev)) { - if (focused === 'input' && node === ev.target && ev.currentTarget !== ev.relatedTarget) { + if (focused.current === 'input' && node.current === ev.target && ev.currentTarget !== ev.relatedTarget) { blur(); forwardBlur(ev, props); } else { @@ -513,7 +520,7 @@ const defaultConfig = { blur(); } } - }, [blur, focusDecorator, focused, node, props]); + }, [blur, focusDecorator, props]); const updateDownTarget = (ev) => { const {repeat, target} = ev; @@ -541,11 +548,11 @@ const defaultConfig = { // when in autoFocus mode, focusing the decorator directly will cause it to // forward the focus onto the - if (!isBubbling(ev) && (props.autoFocus && focused === null && !Spotlight.getPointerMode())) { + if (!isBubbling(ev) && (props.autoFocus && focused.current === null && !Spotlight.getPointerMode())) { focusInput(ev.currentTarget, false); ev.stopPropagation(); } - }, [focusInput, focused, props]); + }, [focusInput, props]); const onKeyDown = (ev) => { const {currentTarget, keyCode, target} = ev; @@ -554,7 +561,7 @@ const defaultConfig = { // when the key interaction started updateDownTarget(ev); - if (focused === 'input') { + if (focused.current === 'input') { const isDown = is('down', keyCode); const isLeft = is('left', keyCode); const isRight = is('right', keyCode); @@ -607,6 +614,7 @@ const defaultConfig = { const handleKeyDown = handle( forwardWithPrevent('onKeyDown'), + call('onKeyDown'), (ev) => onKeyDown(ev) ); @@ -620,28 +628,29 @@ const defaultConfig = { setDownTarget(null); if (!props.disabled) { - if (focused === 'input' && dismissOnEnter && is('enter', keyCode)) { + if (focused.current === 'input' && dismissOnEnter && is('enter', keyCode)) { focusDecorator(currentTarget); // prevent Enter onKeyPress which triggers an onMouseDown via Spotlight ev.preventDefault(); - } else if (focused !== 'input' && is('enter', keyCode)) { + } else if (focused.current !== 'input' && is('enter', keyCode)) { focusInput(currentTarget, false); } } } forwardKeyUp(ev, props); - }, [downTarget, focusDecorator, focusInput, focused, props]); + }, [downTarget, focusDecorator, focusInput, props]); - delete props.autoFocus; - delete props.onActivate; - delete props.onDeactivate; + const newProps = Object.assign({}, props); + delete newProps.autoFocus; + delete newProps.onActivate; + delete newProps.onDeactivate; console.log(focused); return ( Date: Tue, 22 Oct 2024 11:20:14 +0300 Subject: [PATCH 3/4] Fixed a problem with the onKeyDown function. --- Input/InputFieldSpotlightDecorator.js | 380 +------------------------- 1 file changed, 12 insertions(+), 368 deletions(-) diff --git a/Input/InputFieldSpotlightDecorator.js b/Input/InputFieldSpotlightDecorator.js index 206385a3d7..f04f84dc57 100644 --- a/Input/InputFieldSpotlightDecorator.js +++ b/Input/InputFieldSpotlightDecorator.js @@ -1,16 +1,11 @@ -import { - call, - forward, forwardCustom, forwardWithPrevent, handle, stopImmediate} from '@enact/core/handle'; +import {forward, forwardCustom, forwardWithPrevent, stopImmediate} from '@enact/core/handle'; import hoc from '@enact/core/hoc'; import {is} from '@enact/core/keymap'; import {getDirection, Spotlight} from '@enact/spotlight'; import Pause from '@enact/spotlight/Pause'; import Spottable from '@enact/spotlight/Spottable'; import PropTypes from 'prop-types'; -import { - // Component as ReactComponent, - useCallback, useEffect, useMemo, useRef, useState -} from 'react'; +import {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {lockPointer, releasePointer} from './pointer'; @@ -27,11 +22,6 @@ const isSelectionAtLocation = (target, location) => { } }; -// const handleKeyDown = handle( -// forwardWithPrevent('onKeyDown'), -// call('onKeyDown') -// ); - /** * Default config for {@link sandstone/Input.InputSpotlightDecorator|InputSpotlightDecorator} * @@ -49,340 +39,6 @@ const defaultConfig = { noLockPointer: false }; -/** - * A higher-order component that manages the - * spotlight behavior for an {@link sandstone/Input.Input} - * - * @class InputSpotlightDecorator - * @memberof sandstone/Input/InputSpotlightDecorator - * @hoc - * @private - */ -// const InputSpotlightDecorator = hoc(defaultConfig, (config, Wrapped) => { -// const {noLockPointer} = config; -// const Component = Spottable({emulateMouse: false}, Wrapped); -// const forwardBlur = forward('onBlur'); -// const forwardMouseDown = forward('onMouseDown'); -// const forwardFocus = forward('onFocus'); -// const forwardKeyUp = forward('onKeyUp'); -// -// return class extends ReactComponent { -// static displayName = 'InputSpotlightDecorator'; -// -// static propTypes = /** @lends sandstone/Input/InputSpotlightDecorator.InputSpotlightDecorator.prototype */ { -// /** -// * Focuses the when the decorator is focused via 5-way. -// * -// * @type {Boolean} -// * @default false -// * @public -// */ -// autoFocus: PropTypes.bool, -// -// /** -// * Applies a disabled style and the control becomes non-interactive. -// * -// * @type {Boolean} -// * @default false -// * @public -// */ -// disabled: PropTypes.bool, -// -// /** -// * Blurs the input when the "enter" key is pressed. -// * -// * @type {Boolean} -// * @default false -// * @public -// */ -// dismissOnEnter: PropTypes.bool, -// -// /** -// * Called when the internal is focused. -// * -// * @type {Function} -// * @param {Object} event -// * @public -// */ -// onActivate: PropTypes.func, -// -// /** -// * Called when the internal loses focus. -// * -// * @type {Function} -// * @param {Object} event -// * @public -// */ -// onDeactivate: PropTypes.func, -// -// /** -// * Called when the component is removed while retaining focus. -// * -// * @type {Function} -// * @param {Object} event -// * @public -// */ -// onSpotlightDisappear: PropTypes.func, -// -// /** -// * Disables spotlight navigation into the component. -// * -// * @type {Boolean} -// * @default false -// * @public -// */ -// spotlightDisabled: PropTypes.bool -// }; -// -// constructor (props) { -// super(props); -// -// this.focused = null; -// this.node = null; -// this.fromMouse = false; -// this.paused = new Pause('InputSpotlightDecorator'); -// this.handleKeyDown = handleKeyDown.bind(this); -// this.prevStatus = { -// focused: null, -// node: null -// }; -// } -// -// componentWillUnmount () { -// this.paused.resume(); -// -// if (this.focused === 'input') { -// const {onSpotlightDisappear} = this.props; -// -// if (onSpotlightDisappear) { -// onSpotlightDisappear(); -// } -// -// if (!noLockPointer) { -// releasePointer(this.node); -// } -// } -// } -// -// updateFocus = () => { -// // focus node if `InputSpotlightDecorator` is pausing Spotlight or if Spotlight is paused -// if ( -// this.node && -// Spotlight.getCurrent() !== this.node && -// (this.paused.isPaused() || !Spotlight.isPaused()) -// ) { -// if (this.fromMouse) { -// this.node.focus({preventScroll: true}); -// } else { -// this.node.focus(); -// } -// } -// -// const focusChanged = this.focused !== this.prevStatus.focused; -// if (focusChanged) { -// if (this.focused === 'input') { -// forwardCustom('onActivate')(null, this.props); -// if (!noLockPointer) { -// lockPointer(this.node); -// } -// this.paused.pause(); -// } else if (this.prevStatus.focused === 'input') { -// forwardCustom('onDeactivate')(null, this.props); -// if (!noLockPointer) { -// releasePointer(this.prevStatus.node); -// } -// this.paused.resume(); -// } -// } -// -// this.prevStatus.focused = this.focused; -// this.prevStatus.node = this.node; -// }; -// -// focus = (focused, node, fromMouse) => { -// this.focused = focused; -// this.node = node; -// this.fromMouse = fromMouse; -// this.updateFocus(); -// }; -// -// blur = () => { -// if (this.focused || this.node) { -// this.focused = null; -// this.node = null; -// this.updateFocus(); -// } -// }; -// -// focusDecorator = (decorator) => { -// this.focus('decorator', decorator, false); -// }; -// -// focusInput = (decorator, fromMouse) => { -// this.focus('input', decorator.querySelector('input'), fromMouse); -// }; -// -// onBlur = (ev) => { -// if (!this.props.autoFocus) { -// if (isBubbling(ev)) { -// if (Spotlight.getPointerMode()) { -// this.blur(); -// forwardBlur(ev, this.props); -// } else { -// this.focused = 'decorator'; -// this.node = ev.currentTarget; -// this.fromMouse = false; -// ev.stopPropagation(); -// } -// } else if (!ev.currentTarget.contains(ev.relatedTarget)) { -// // Blurring decorator but not focusing input -// forwardBlur(ev, this.props); -// this.blur(); -// } -// } else if (isBubbling(ev)) { -// if (this.focused === 'input' && this.node === ev.target && ev.currentTarget !== ev.relatedTarget) { -// this.blur(); -// forwardBlur(ev, this.props); -// } else { -// this.focusDecorator(ev.currentTarget); -// ev.stopPropagation(); -// this.blur(); -// } -// } -// }; -// -// onMouseDown = (ev) => { -// const {disabled, spotlightDisabled} = this.props; -// -// this.setDownTarget(ev); -// // focus the whenever clicking on any part of the component to ensure both that -// // the has focus and Spotlight is paused. -// if (!disabled && !spotlightDisabled) { -// this.focusInput(ev.currentTarget, true); -// } -// -// forwardMouseDown(ev, this.props); -// }; -// -// onFocus = (ev) => { -// forwardFocus(ev, this.props); -// -// // when in autoFocus mode, focusing the decorator directly will cause it to -// // forward the focus onto the -// if (!isBubbling(ev) && (this.props.autoFocus && this.focused === null && !Spotlight.getPointerMode())) { -// this.focusInput(ev.currentTarget, false); -// ev.stopPropagation(); -// } -// }; -// -// onKeyDown (ev) { -// const {currentTarget, keyCode, target} = ev; -// -// // cache the target if this is the first keyDown event to ensure the component had focus -// // when the key interaction started -// this.setDownTarget(ev); -// -// if (this.focused === 'input') { -// const isDown = is('down', keyCode); -// const isLeft = is('left', keyCode); -// const isRight = is('right', keyCode); -// const isUp = is('up', keyCode); -// -// // move spotlight -// const shouldSpotlightMove = ( -// // No value exists! (Can happen when disabled) -// target.value == null || -// // on left + at beginning of selection -// (isLeft && isSelectionAtLocation(target, 0)) || -// // on right + at end of selection (note: fails on non-selectable types usually) -// (isRight && isSelectionAtLocation(target, target.value.length)) || -// // on up -// isUp || -// // on down -// isDown -// ); -// -// // prevent modifying the value via 5-way for numeric fields -// if ((isUp || isDown) && target.type === 'number') { -// ev.preventDefault(); -// } -// -// if (shouldSpotlightMove) { -// const direction = getDirection(keyCode); -// const {getPointerMode, move, setPointerMode} = Spotlight; -// -// if (getPointerMode()) { -// setPointerMode(false); -// } -// -// stopImmediate(ev); -// this.paused.resume(); -// -// // Move spotlight in the keypress direction -// if (move(direction)) { -// // if successful, reset the internal state -// this.blur(); -// } else { -// // if there is no other spottable elements, focus `InputDecorator` instead -// this.focusDecorator(currentTarget); -// } -// } else if (isLeft || isRight) { -// // prevent 5-way nav for left/right keys within the -// stopImmediate(ev); -// } -// } -// } -// -// onKeyUp = (ev) => { -// const {dismissOnEnter} = this.props; -// const {currentTarget, keyCode, target} = ev; -// -// // verify that we have a matching pair of key down/up events to avoid adjusting focus -// // when the component received focus mid-press -// if (target === this.downTarget) { -// this.downTarget = null; -// -// if (!this.props.disabled) { -// if (this.focused === 'input' && dismissOnEnter && is('enter', keyCode)) { -// this.focusDecorator(currentTarget); -// // prevent Enter onKeyPress which triggers an onMouseDown via Spotlight -// ev.preventDefault(); -// } else if (this.focused !== 'input' && is('enter', keyCode)) { -// this.focusInput(currentTarget, false); -// } -// } -// } -// -// forwardKeyUp(ev, this.props); -// }; -// -// setDownTarget (ev) { -// const {repeat, target} = ev; -// -// if (!repeat) { -// this.downTarget = target; -// } -// } -// -// render () { -// const props = Object.assign({}, this.props); -// delete props.autoFocus; -// delete props.onActivate; -// delete props.onDeactivate; -// -// return ( -// -// ); -// } -// }; -// }); /** * A higher-order component that manages the @@ -392,28 +48,23 @@ const defaultConfig = { * @memberof sandstone/Input/InputSpotlightDecorator * @hoc * @private - */const InputSpotlightDecorator = hoc(defaultConfig, (config, Wrapped) => { + */ +const InputSpotlightDecorator = hoc(defaultConfig, (config, Wrapped) => { const {noLockPointer} = config; const Component = Spottable({emulateMouse: false}, Wrapped); const forwardBlur = forward('onBlur'); const forwardMouseDown = forward('onMouseDown'); const forwardFocus = forward('onFocus'); + const forwardKeyDown = forwardWithPrevent('onKeyDown'); const forwardKeyUp = forward('onKeyUp'); - const InputSpotlight = ({...props}) => { + const InputSpotlight = (props) => { const {onSpotlightDisappear} = props; const paused = useMemo(() => new Pause('InputSpotlightDecorator'), []); const [downTarget, setDownTarget] = useState(); - // const [focused, setFocused] = useState(null); const focused = useRef(null); const [fromMouse, setFromMouse] = useState(false); - // const [node, setNode] = useState(null); const node = useRef(null); - // const [prevStatus, setPrevStatus] = useState({ - // focused: null, - // node: null - // }); - const prevStatus = useRef({ focused: null, node: null @@ -433,8 +84,7 @@ const defaultConfig = { } } }; - }, [paused, onSpotlightDisappear]); - // }, []); + }, []); // eslint-disable-line react-hooks/exhaustive-deps const updateFocus = useCallback(() => { // focus node if `InputSpotlightDecorator` is pausing Spotlight or if Spotlight is paused @@ -554,7 +204,9 @@ const defaultConfig = { } }, [focusInput, props]); - const onKeyDown = (ev) => { + const onKeyDown = useCallback((ev) => { + forwardKeyDown(ev, props); + const {currentTarget, keyCode, target} = ev; // cache the target if this is the first keyDown event to ensure the component had focus @@ -610,13 +262,7 @@ const defaultConfig = { stopImmediate(ev); } } - }; - - const handleKeyDown = handle( - forwardWithPrevent('onKeyDown'), - call('onKeyDown'), - (ev) => onKeyDown(ev) - ); + }, [blur, focusDecorator, paused, props]); const onKeyUp = useCallback((ev) => { const {dismissOnEnter} = props; @@ -646,15 +292,13 @@ const defaultConfig = { delete newProps.onActivate; delete newProps.onDeactivate; - console.log(focused); - return ( ); From 7e853cff9c13ad8faf2c3096a4c6aea7f1ef9fef Mon Sep 17 00:00:00 2001 From: alexandru-morariu-lgp Date: Tue, 19 Nov 2024 15:59:05 +0200 Subject: [PATCH 4/4] Small fixes --- Input/InputFieldSpotlightDecorator.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Input/InputFieldSpotlightDecorator.js b/Input/InputFieldSpotlightDecorator.js index f04f84dc57..ba42fe3ea4 100644 --- a/Input/InputFieldSpotlightDecorator.js +++ b/Input/InputFieldSpotlightDecorator.js @@ -39,7 +39,6 @@ const defaultConfig = { noLockPointer: false }; - /** * A higher-order component that manages the * spotlight behavior for an {@link sandstone/Input.Input} @@ -62,8 +61,8 @@ const InputSpotlightDecorator = hoc(defaultConfig, (config, Wrapped) => { const {onSpotlightDisappear} = props; const paused = useMemo(() => new Pause('InputSpotlightDecorator'), []); const [downTarget, setDownTarget] = useState(); - const focused = useRef(null); const [fromMouse, setFromMouse] = useState(false); + const focused = useRef(null); const node = useRef(null); const prevStatus = useRef({ focused: null, @@ -287,14 +286,14 @@ const InputSpotlightDecorator = hoc(defaultConfig, (config, Wrapped) => { forwardKeyUp(ev, props); }, [downTarget, focusDecorator, focusInput, props]); - const newProps = Object.assign({}, props); - delete newProps.autoFocus; - delete newProps.onActivate; - delete newProps.onDeactivate; + const componentProps = Object.assign({}, props); + delete componentProps.autoFocus; + delete componentProps.onActivate; + delete componentProps.onDeactivate; return (