How to prevent react-dates SingleDatePicker to open on focus? - javascript

I'm using react-dates SingleDatePicker. The visibility of the calendar is controlled by the focused prop. When using tabs on the website, I don't want the calendar to open, I want it to open on enter key. Is there a way to achieve this? Thanks!

You should to control "focused" property and onFocusChange method. Also set the listener to container. For example
.....
const [focusedInput, setFocusedInput] = useState(null);
const handleUserKeyPress = (evt) => {
if (evt.keyCode === 13 && !evt.shiftKey) setFocusedInput(true);
};
....
<div onKeyUp={handleUserKeyPress}>
<SingleDatePicker
focused={focusedInput}
onFocusChange={({ focused }) => {
const focus = focusedInput && focused ? true : false;
setFocusedInput(focus);
}}
.....
/>
</div>
.....

Related

How can i transfer focus to the next focusable element inside an onFocus handler of another element?

I have a react app, and i am trying to build a focus trapper element, that lets the user tab through elements normally but won't let you focus outside their container.
What works
I am doing so by rendering a first and last "bounder" to sandwich the actual content between two focusable divs that should pass the focus forwards or backwards based on the direction they received it from.
the code for the container:
export class QKeyBinder
extends ComponentSync<QKeyBinder_Props, State> {
private firstTabBinder: React.RefObject<HTMLDivElement> = React.createRef();
private lastTabBinder: React.RefObject<HTMLDivElement> = React.createRef();
protected deriveStateFromProps(nextProps: QKeyBinder_Props): State {
return {};
}
private renderFirstTabBounder() {
return <div
tabIndex={0}
ref={this.firstTabBinder}
className={'q-key-binder__tab-binder'}
role={'tab-binder'}
onKeyDown={(e) => {
if (e.key === 'Tab' && e.shiftKey) {
e.preventDefault();
stopPropagation(e);
return this.lastTabBinder.current!.focus();
}
}}/>;
}
private renderLastTabBounder() {
return <div
tabIndex={0}
ref={this.lastTabBinder}
className={'q-key-binder__tab-binder'}
role={'tab-binder'}
onKeyDown={(e) => {
if (e.key === 'Tab' && !e.shiftKey) {
e.preventDefault();
stopPropagation(e);
return this.firstTabBinder.current!.focus();
}
}}/>;
}
render() {
const className = _className('q-key-binder', this.props.className);
return <div className={className}>
{this.renderFirstTabBounder()}
{this.props.children}
{this.renderLastTabBounder()}
</div>;
}
}
As you can see, i have it working by pressing tab again.
I want the bounders to have a onFocus handler to pass the focus along once they get it.
What didn't work
Since i can't know beforehand who the next focusable element is, I tried dispatching a keyboard event, e.g:
onFocus={(e}=>{
document.body.dispatchEvent(new KeyboardEvent('keypress',{key:'Tab'}))
}}
Dispatching the event on the body.document, the e.target, the body, the window, none of these work.
Just can't seem to simulate another tab press, or find a way to focus the next element without depending on a selector, or a wrapper, which causes extra complexity.
Any help would be much appreciated!

Check if a div element can receive paste event i.e if it is focused

I am capturing a paste event in a div element.
<div onPaste={(e)=>console.log(e.clipboardData.getData("Text"))}}>
</div>
This, however only works if the element has been clicked into (and the user hasn't clicked outside of it)
How do I indicate to the user that the element is able to receive a paste (i.e is focused, I suppose)
document.activeElement remains focused on body I suppose because the div is not an input
You should use react hook useState for that:
import { useState } from 'react'
const App = () => {
const [focused, setFocused] = useState(false)
const onFocus = () => {
setFocused(true)
}
const onBlur = () => {
setFocused(false)
}
return (
<div>
<div onFocus={onFocus} onBlur={onBlur}>{focused ? 'focused' : 'not focused'}</div>
</div>
)
}
export default App
And now if the focused variable is true, the element is focused and if it's false the element is blurred.

Touch support for arrow clicks on React app

I am working on a React web app where I have a div of a 4x4 grid which has 16 blocks rendered in them. Where I have arrow click events for right, left, up, and down arrow. I need to add the mobile touch support for those arrow clicks when it's accessed only on mobile devices. I have never implemented these touch/swipe mobile features hence it seems very confusing. Any help would be appreciated.
Component:
const Grid = () => {
const [grid, setGrid] = useState(new Grid());
const arrowLeftKey = 37;
const arrowDownKey = 40;
const handleKeyDown = (event) => {
if (grid.hasWon()) {
return;
}
if (event.keyCode >= arrowLeftKey && event.keyCode <= arrowDownKey) {
let direction = event.keyCode - arrowLeftKey;
let gridClone = Object.assign(
Object.create(Object.getPrototypeOf(grid)),
grid
);
let newGrid = gridClone.move(direction);
setGrid(newGrid);
}
};
useArrowKeyEvent('keydown',handleKeyDown); //hook
const displayBlocks = grid.cells.map((row, rowIndex) => {
return (
<div key={rowIndex}>
{row.map((col, colIndex) => {
return <Block key={rowIndex + colIndex} />;
})}
</div>
);
});
return (
<div className="grid" id="gridId">
{displayBlocks}
</div>
);
I came to know from googling that I would need to use Touch Events, such as touchStart, touchMove, touchEnd. Looking at the touchevents documentation I added the following piece of code to my component. I changed the MouseEvents to ´KeyBoardevent´. Since it's a arrow key click/keydown event. But this is not working. Not sure where am I doing wrong.
const onTouch = (evt) => {
evt.preventDefault();
if (evt.touches.length > 1 || (evt.type === "touchend" && evt.touches.length > 0))
return;
var newEvt = document.createEvent("KeyboardEvent");
var type = null;
var touch = null;
// eslint-disable-next-line default-case
switch (evt.type) {
case "touchstart":
type = "keydown";
touch = evt.changedTouches[0];
break;
case "touchmove":
type = "keydown";
touch = evt.changedTouches[0];
break;
case "touchend":
type = "keydown";
touch = evt.changedTouches[0];
break;
}
newEvt.initEvent(type, true, true, evt.originalTarget.ownerDocument.defaultView, 0,
touch.screenX, touch.screenY, touch.clientX, touch.clientY,
evt.keyCode('37'), evt.keyCode('39'), evt.keyCode('38'), evt.keyCode('40'), 0, null);
evt.originalTarget.dispatchEvent(newEvt);
}
document.addEventListener("touchstart", onTouch, true);
document.addEventListener("touchmove", onTouch, true);
document.addEventListener("touchend", onTouch, true);
I get the following error when I swipe right and expect for right arrow click:
TypeError: Cannot read property 'ownerDocument' of undefined
On the following line of code:
newEvt.initEvent(type, true, true, evt.originalTarget.ownerDocument.defaultView, 0,
touch.screenX, touch.screenY, touch.clientX, touch.clientY,
evt.keyCode('37'), evt.keyCode('39'), evt.keyCode('38'), evt.keyCode('40'), 0, null);
Version 2
Edit: : used react-swipeable after #sschwei1 suggested
I have added the following piece in the component :
const swipeHandlers = useSwipeable({
onSwipedLeft: useArrowKeyEvent('keydown',handleKeyDown),<<<<<problem
onSwipedRight: eventData => console.log("swiped right"),
onSwipedUp: eventData => console.log("swiped up"),
onSwipedDown: eventData => console.log("swiped down")
});
and the return statement:
<div className="grid" {...swipeHandlers}>
{displayBlocks}
</div>
Problem: Can't use the hook as callback function.
React-swipeable is a library which handles swipes for you, it enables you to create handlers for different swipe directions, e.g onSwipedLeft or onSwipedUp and pretty much all other cases you can think of like onTap, onSwiping, onSwiped, and many more.
In these handlers you can just re-use the logic of your arrow keys.
The first solution I would think of (not the prettiest solution, but easy to use and understand) to create a wrapper function for swipes and call the according keyHandler function to it
Here is an example of how these functions could look like:
const handleTouch = (key) => {
handleKeyDown({keyCode:key});
}
And in your touch handlers you can call this function with the according key
const swipeHandlers = useSwipeable({
onSwipedLeft: () => handleTouch('37'),
onSwipedUp: () => handleTouch('38'),
onSwipedRight: () => handleTouch('39'),
onSwipedDown: () => handleTouch('40')
});
Since you are only using the keyCode in your handleKeyDown function, you can just pass an object with the keyCode property to the function and 'simulate' the key press

React Dropdown acessibility onClick

at the current point, this code works, but when the user clicks to hide the menu, the useClickOutside fires too, the menu toggles off and on again... would there any way to fix that so when clicks outside it closes but when clicks the button it toggles on/off ?
const useClickOutside = (ref, handler) => {
useEffect(() => {
const clickHandler = (event) => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener('mousedown', clickHandler);
return () => {
document.removeEventListener('mousedown', clickHandler);
};
});
};
const Settings = () => {
const ref = useRef();
const [toggle, setToggle] = useState(false);
useClickOutside(ref, () => setToggle(false));
return (
<div className='settings'>
<button onClick={() => setToggle(!toggle)} className='settings__button'>
Menu
</button>
{toggle && (
<div ref={ref} className='settings__panel'>
<Link className='settings__links' to='/user/settings'>
Your Profile
</Link>
<Link className='settings__links' to='/user/settings'>
Todos history
</Link>
<Link className='settings__links' to='/user/settings'>
Settings
</Link>
<Link className='settings__links' value={'Logout'} to='/user/login'>
Logout
</Link>
</div>
)}
</div>
);
};
You might consider adding a onBlur event on the .settings div with a tabIndex=0.
You can then then capture blurs of the div and test if the event came from within the div or not.
const onBlur = (e: FocusEvent < HTMLElement > ) => {
if (opened?) {
const element = e.relatedTarget;
if (element == null) {
// dropdown was blured because window lost focused. probably close.
} else if (element != e.currentTarget) {
if (!e.currentTarget.contains(element as Node)) {
// blured element is not in .settings. close
}
}
}
};
If you want to get fancy you can also add a keydown and close on escape.
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
// close!
}
);
Here is a code sandbox that implements these items.
You could make use of event stopPropagation. Add the call event.stopPropagation() to your onClick handler function that hides the menu.
<button
onClick={(e) => {
e.stopPropagation();
setToggle(!toggle);
}}
className='settings__button'
>
Menu
</button>
This will prevent that the onClick event bubbles upwards to the next event listener which would be your onClickOutside listener.
UPDATE:
This will only work if your event listener is listening for onClick events. Your inline onClick event listener will stop the propagation of the event of type click only.
document.addEventListener('click', clickHandler);
return () => {
document.removeEventListener('click', clickHandler);
};

React Semantic UI Input not firing function on backspace pressed

I am building a form using semantic UI.
I am disabling the submit button if one of my buttons is not fulfilled.
<Button
disabled={!merchantId || !conduit || !region || !acquirer || !processingTimezone}
content={`Associate to Domain ${DomainData.domain}`}
onClick={() => this.submitNewMerchant()}
/>
and I am changing the state of each field using a function
// other inputs
<Form.Input
name="processingTimezone"
onKeyDown={() => console.log('backspace')}
onChange={e => this.inputChange(e)}
label="processingTimezone"
defaultValue={processingTimezone}
/>
inputChange = (event) => {
const { name, value } = event.target;
this.setState({ [name]: value });
}
As you can see I am trying to track the user input both using onChange and onKeyDown, but the problem happens if the user focus an input, select all (ctrl + a) and press Backspace.
The state is not being updated to empty or '' or any other value.
How can I manage this ?
Try adding a value to the input text :
<Form.Input
name="processingTimezone"
onKeyDown={() => console.log('backspace')}
onChange={e => this.inputChange(e)}
label="processingTimezone"
defaultValue="CST"
value={this.state.processingTimezone}
/>
at least the input won't budge if the state doesn't change.

Categories

Resources