diff --git a/Input/InputFieldSpotlightDecorator.js b/Input/InputFieldSpotlightDecorator.js
index 9160e15f18..ba42fe3ea4 100644
--- a/Input/InputFieldSpotlightDecorator.js
+++ b/Input/InputFieldSpotlightDecorator.js
@@ -1,11 +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} from 'react';
+import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {lockPointer, releasePointer} from './pointer';
@@ -22,11 +22,6 @@ const isSelectionAtLocation = (target, location) => {
}
};
-const handleKeyDown = handle(
- forwardWithPrevent('onKeyDown'),
- call('onKeyDown')
-);
-
/**
* Default config for {@link sandstone/Input.InputSpotlightDecorator|InputSpotlightDecorator}
*
@@ -59,225 +54,165 @@ const InputSpotlightDecorator = hoc(defaultConfig, (config, Wrapped) => {
const forwardBlur = forward('onBlur');
const forwardMouseDown = forward('onMouseDown');
const forwardFocus = forward('onFocus');
+ const forwardKeyDown = forwardWithPrevent('onKeyDown');
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 {onSpotlightDisappear} = props;
+ const paused = useMemo(() => new Pause('InputSpotlightDecorator'), []);
+ const [downTarget, setDownTarget] = useState();
+ const [fromMouse, setFromMouse] = useState(false);
+ const focused = useRef(null);
+ const node = useRef(null);
+ const prevStatus = useRef({
+ focused: null,
+ node: null
+ });
+
+ useEffect(() => {
+ return () => {
+ paused.resume();
+
+ if (focused.current === 'input') {
+ if (onSpotlightDisappear) {
+ onSpotlightDisappear();
+ }
- if (!noLockPointer) {
- releasePointer(this.node);
+ if (!noLockPointer) {
+ releasePointer(node.current);
+ }
}
- }
- }
+ };
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
- 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.current &&
+ Spotlight.getCurrent() !== node.current &&
+ (paused.isPaused() || !Spotlight.isPaused())
) {
- if (this.fromMouse) {
- this.node.focus({preventScroll: true});
+ if (fromMouse) {
+ node.current.focus({preventScroll: true});
} else {
- this.node.focus();
+ node.current.focus();
}
}
- const focusChanged = this.focused !== this.prevStatus.focused;
+ const focusChanged = focused.current !== prevStatus.current.focused;
if (focusChanged) {
- if (this.focused === 'input') {
- forwardCustom('onActivate')(null, this.props);
+ if (focused.current === 'input') {
+ forwardCustom('onActivate')(null, props);
if (!noLockPointer) {
- lockPointer(this.node);
+ lockPointer(node.current);
}
- this.paused.pause();
- } else if (this.prevStatus.focused === 'input') {
- forwardCustom('onDeactivate')(null, this.props);
+ paused.pause();
+ } else if (prevStatus.current.focused === 'input') {
+ forwardCustom('onDeactivate')(null, props);
if (!noLockPointer) {
- releasePointer(this.prevStatus.node);
+ releasePointer(prevStatus.current.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();
+ prevStatus.current = {focused: focused.current, node: node.current};
+ }, [fromMouse, paused, props]);
+
+ const focus = useCallback((focusedValue, nodeValue, fromMouseValue) => {
+ focused.current = focusedValue;
+ setFromMouse(fromMouseValue);
+ node.current = nodeValue;
+ updateFocus();
+ }, [updateFocus]);
+
+ const blur = useCallback(() => {
+ if (focused.current || node.current) {
+ focused.current = null;
+ node.current = null;
+ updateFocus();
}
- };
+ }, [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;
+ focused.current = 'decorator';
+ setFromMouse(false);
+ node.current = 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.current === 'input' && node.current === 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, 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.current === null && !Spotlight.getPointerMode())) {
+ focusInput(ev.currentTarget, false);
ev.stopPropagation();
}
- };
+ }, [focusInput, props]);
+
+ const onKeyDown = useCallback((ev) => {
+ forwardKeyDown(ev, props);
- 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.current === 'input') {
const isDown = is('down', keyCode);
const isLeft = is('left', keyCode);
const isRight = is('right', keyCode);
@@ -311,72 +246,129 @@ 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);
}
}
- }
+ }, [blur, focusDecorator, paused, props]);
- 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.current === '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.current !== '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, props]);
+
+ const componentProps = Object.assign({}, props);
+ delete componentProps.autoFocus;
+ delete componentProps.onActivate;
+ delete componentProps.onDeactivate;
+
+ 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;