on master help sign in
activate-ux: auto-advance focus to next key field
782bb34tomholford
src/form/Inputs.tsx+47−18
@@ -1,4 +1,4 @@
1−import React from 'react';
1+import React, { useCallback } from 'react';
2import * as ob from 'urbit-ob';
2import * as ob from 'urbit-ob';
3import { Input, AccessoryIcon } from 'indigo-react';
3import { Input, AccessoryIcon } from 'indigo-react';
4import { useField, useForm } from 'react-final-form';
4import { useField, useForm } from 'react-final-form';
@@ -224,30 +224,59 @@ export function TicketSegmentInput({ name, ...rest }: TicketSegmentInputProps) {
224 const { input } = useField(name);
224 const { input } = useField(name);
225 const { change } = useForm();
225 const { change } = useForm();
226
226
227+ const onPaste = useCallback(
227− const onPaste = (event: React.ClipboardEvent<HTMLInputElement>) => {
228+ (event: React.ClipboardEvent<HTMLInputElement>) => {
228− try {
229+ try {
229− const pasteBuffer = event.clipboardData.getData('text');
230+ const pasteBuffer = event.clipboardData.getData('text');
230− const patp = ensurePatFormat(pasteBuffer);
231+ const patp = ensurePatFormat(pasteBuffer);
231− if (!ob.isValidPatp(patp)) {
232+ if (!ob.isValidPatp(patp)) {
232− throw new Error('invalid patp');
233+ throw new Error('invalid patp');
234+ }
235+
236+ // prevent pasting the entire clipboard text into the field
237+ event.preventDefault();
238+
239+ const segments = ticketToSegments(pasteBuffer);
240+ segments.forEach((s, i) => {
241+ change(`ticket${i}`, s);
242+ });
243+ } catch (error) {
244+ console.log(`skipping ticket autofill: ${error}`);
233 }
245 }
246+ },
247+ [change]
248+ );
234
249
235− // prevent pasting the entire clipboard text into the field
250+ // Advance the focused field after typing 6 characters
236− event.preventDefault();
251+ const onChange = useCallback(
252+ (event: React.ChangeEvent<HTMLInputElement>) => {
253+ const currentIndex = Number(name.replace('ticket', ''));
254+ if (currentIndex < 3 && event.target.value.length === 6) {
255+ const nextField = document.querySelector(
256+ `input[name=ticket${currentIndex + 1}]`
257+ );
237
258
238− const segments = ticketToSegments(pasteBuffer);
259+ if (nextField !== null) {
239− change('ticket0', segments[0]);
260+ nextField.focus();
240− change('ticket1', segments[1]);
261+ }
241− change('ticket2', segments[2]);
262+ }
242− change('ticket3', segments[3]);
263+ },
243− } catch (error) {
264+ [name]
244− console.log(`skipping ticket autofill: ${error}`);
265+ );
266+
267+ // Run the react-final-form onChange handler,
268+ // and our own custom one to advance the form cursor
269+ const mergedInput = {
270+ ...input,
271+ onChange: e => {
272+ input.onChange(e);
273+ onChange(e);
245− }
274+ },
246 };
275 };
247
276
248 return (
277 return (
249 <StatelessTextInput
278 <StatelessTextInput
250− {...input}
279+ {...mergedInput}
251 autoCapitalize="none"
280 autoCapitalize="none"
252 autoCorrect="off"
281 autoCorrect="off"
253 type="text"
282 type="text"