(
event: KeyboardEvent,
binding: string,
pendingRef: { current: number },
timerRef: { current?: ReturnType<typeof setTimeout> },
onMatch: () => void,
consume: () => void,
timeoutMs = 500,
)
| 1494 | } |
| 1495 | |
| 1496 | export function advanceSequence( |
| 1497 | event: KeyboardEvent, |
| 1498 | binding: string, |
| 1499 | pendingRef: { current: number }, |
| 1500 | timerRef: { current?: ReturnType<typeof setTimeout> }, |
| 1501 | onMatch: () => void, |
| 1502 | consume: () => void, |
| 1503 | timeoutMs = 500, |
| 1504 | ): boolean { |
| 1505 | const tokens = binding |
| 1506 | .split(/\s+/) |
| 1507 | .map((token) => token.trim()) |
| 1508 | .filter(Boolean); |
| 1509 | if (tokens.length === 0) return false; |
| 1510 | const token = sequenceTokenFromEvent(event); |
| 1511 | if (!token) return false; |
| 1512 | |
| 1513 | const reset = (): void => { |
| 1514 | pendingRef.current = 0; |
| 1515 | if (timerRef.current) clearTimeout(timerRef.current); |
| 1516 | timerRef.current = undefined; |
| 1517 | }; |
| 1518 | |
| 1519 | if (pendingRef.current > 0) { |
| 1520 | if (token === tokens[pendingRef.current]) { |
| 1521 | consume(); |
| 1522 | pendingRef.current += 1; |
| 1523 | if (pendingRef.current >= tokens.length) { |
| 1524 | reset(); |
| 1525 | onMatch(); |
| 1526 | } else { |
| 1527 | if (timerRef.current) clearTimeout(timerRef.current); |
| 1528 | timerRef.current = setTimeout(reset, timeoutMs); |
| 1529 | } |
| 1530 | return true; |
| 1531 | } |
| 1532 | reset(); |
| 1533 | } |
| 1534 | |
| 1535 | if (token !== tokens[0]) return false; |
| 1536 | consume(); |
| 1537 | if (tokens.length === 1) { |
| 1538 | onMatch(); |
| 1539 | return true; |
| 1540 | } |
| 1541 | pendingRef.current = 1; |
| 1542 | if (timerRef.current) clearTimeout(timerRef.current); |
| 1543 | timerRef.current = setTimeout(reset, timeoutMs); |
| 1544 | return true; |
| 1545 | } |
no test coverage detected