4

I have a PIN code field component that is just a list of inputs wrapped in a React.Fragment. Each time a key is pressed, it focuses the next input. When I reach the last input, I would like to trigger a tabulation to focus the next controller outside of the component (input, button, link etc). I tried to dispatch a KeyboardEvent, but doesn't work:

const evt = new KeyboardEvent("keydown", {
  ...
  code: "Tab",
  key: "Tab",
})

lastInput.dispatchEvent(evt)

Any idea?

Source code: https://github.com/soywod/react-pin-field
Demo : https://react-pin-field.soywod.me/

soywod
  • 4,377
  • 3
  • 26
  • 47

8 Answers8

6

According to this answer the browser security model stops you simulating the pressing of the Tab Key.

As an alternative perhaps you could add a nextFocusId attribute to your component and use that to move the curser with document.getElementById().focus() instead.

David Bradshaw
  • 11,859
  • 3
  • 41
  • 70
  • 1
    This is the solution I kept. My component `PinField` has a [`onComplete`](https://github.com/soywod/react-pin-field/blob/571f7c73c6e18a9189ee6ee8b2efedda5781aa20/src/pin-field.types.ts#L14) prop. I save the ref of the next controller with `const nextField = useRef()`, and I set up `onComplete` this way: `onComplete={() => nextField.current && nextField.current.focus()}`. – soywod Jan 03 '20 at 09:38
0

I guess this would solve your problem - triggering mouse click event of the input element outside the component you want to focus, when the component is being re-rendered.

Vivian
  • 46
  • 4
  • I will try and see how it behaves. – soywod Dec 31 '19 at 16:14
  • Please make sure you trigger click after the re-render is done, I faced the same issue, then figured out it was because of re-render. You can either use a state to find when to focus or use ComponentDidUpdate method – Vivian Jan 02 '20 at 07:13
0

Thought about this a bit more, if you can not do tab, how about setting data-focused on the current element and then gathering all the input elements on the page, finding the currently focused one via the data attribute, and then if we are not the last element, focusing the next item.

const inputs = document.querySelectorAll("button, input, select, textarea")
const len = inputs.length - 1  // ignore last item in array
let i = 0

while( i < len ){
  if (inputs[i++].dataSet.focused) {
    inputs[i].focus()
    break
  }
}
David Bradshaw
  • 11,859
  • 3
  • 41
  • 70
0

define listener for keypress event on last input

private onLastInputChange = e => {
    const target = e.target;
    const keyboardEvent = new KeyboardEvent("keypress", {
        bubbles : true,
        cancelable : true,
        key : "Tab",
        shiftKey : false,
        keyCode : 13
    });
    target.dispatchEvent(keyboardEvent);
}

the keyCode 13 represent tab ASCII code

and in your render method

<>
   ...
   <input .../>
   <input ... onChange={this.onLastInputChange} />    //<---- the last pin input
</>
Ali Faris
  • 17,754
  • 10
  • 45
  • 70
0

Let's say there are three major components here, one is PIN, another is Next, and also a parent component Parent holds PIN and Next.

  1. Parent passes a prop onLast which is a function to PIN, PIN executes onLast while the last input reached.
  2. Parent forwards a ref to Next, Next attaches the ref to its focusable element(input, button, link, etc).

Have the above conditions in mind, now we have the ability to 'trigger' focus to Next by conditions. e.g. When onLast is called(the last input is reached in PIN). By using traditional dom API.

https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/focus

ps. It's ok to trigger a keyboard event by programing, and it will trigger an appropriate event handler being executed. And that's all, you should never expect it works more, like producing text, moving focus(tab key), they are totally user operation that programming cannot reach.

Pengson
  • 845
  • 5
  • 10
0

you can use a parent component as mediator to help children components communicate with each other, just as a sample that i draft below: enter link description here

wang eason
  • 176
  • 4
0

you can use a parent component as mediator to help children components communicate with each other, just as a sample that i draft below, just same as what you did: sample

wang eason
  • 176
  • 4
-3

you can try using tabindex on your elements