Skip to content

Commit

Permalink
Add shiftInRange helper
Browse files Browse the repository at this point in the history
  • Loading branch information
connor-baer committed Oct 25, 2024
1 parent 8158aa3 commit 1bd13c1
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 12 deletions.
14 changes: 3 additions & 11 deletions packages/circuit-ui/hooks/useFocusList/useFocusList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import { useCallback, useId, type KeyboardEvent } from 'react';

import { isArrowDown, isArrowUp } from '../../util/key-codes.js';
import { shiftInRange } from '../../util/helpers.js';

export type FocusProps = {
'data-focus-list': string;
Expand All @@ -42,9 +43,8 @@ export function useFocusList(): FocusProps {
);
const currentEl = event.target as HTMLElement;
const currentIndex = Array.from(items).indexOf(currentEl);
const newIndex = isArrowUp(event)
? getPrevIndex(currentIndex, items.length)
: getNextIndex(currentIndex, items.length);
const offset = isArrowUp(event) ? -1 : 1;
const newIndex = shiftInRange(currentIndex, offset, 0, items.length - 1);
const newEl = items.item(newIndex);

newEl.focus();
Expand All @@ -54,11 +54,3 @@ export function useFocusList(): FocusProps {

return { 'data-focus-list': name, onKeyDown };
}

function getPrevIndex(currentIndex: number, length: number): number {
return currentIndex - 1 < 0 ? length - 1 : currentIndex - 1;
}

function getNextIndex(currentIndex: number, length: number): number {
return currentIndex + 1 >= length ? 0 : currentIndex + 1;
}
42 changes: 41 additions & 1 deletion packages/circuit-ui/util/helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@

import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';

import { chunk, clamp, eachFn, isEmpty, last, throttle } from './helpers.js';
import {
chunk,
clamp,
eachFn,
isEmpty,
last,
shiftInRange,
throttle,
} from './helpers.js';

describe('helpers', () => {
describe('clamp', () => {
Expand Down Expand Up @@ -231,4 +239,36 @@ describe('helpers', () => {
expect(actual).toBeUndefined();
});
});

describe('shiftInRange', () => {
it('should increase a value within a range', () => {
const actual = shiftInRange(4, 2, 1, 12);
expect(actual).toBe(6);
});

it('should decrease a value within a range', () => {
const actual = shiftInRange(4, -2, 1, 12);
expect(actual).toBe(2);
});

it('should loop around to the end', () => {
const actual = shiftInRange(4, -6, 1, 12);
expect(actual).toBe(10);
});

it('should loop around to the start', () => {
const actual = shiftInRange(7, 10, 4, 12);
expect(actual).toBe(8);
});

it('should loop around to the start', () => {
const actual = shiftInRange(4, 10, 2, 12);
expect(actual).toBe(3);
});

it('should loop around multiple times', () => {
const actual = shiftInRange(4, -9, 2, 5);
expect(actual).toBe(3);
});
});
});
17 changes: 17 additions & 0 deletions packages/circuit-ui/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export function isEmpty(value: unknown): boolean {
return false;
}

/**
* Clamps a value within a range of values between a minimum and maximum limit.
*/
export function clamp(value: number, min: number, max: number): number {
if (process.env.NODE_ENV !== 'production' && min >= max) {
throw new RangeError(
Expand Down Expand Up @@ -96,3 +99,17 @@ export function last<T>(array: T[]): T;
export function last<T>(array: T[] | undefined | null): T | undefined {
return isArray(array) ? array[array.length - 1] : undefined;
}

/**
* Increases or decreases a value by an offset and loops back around to stay
* within a given range.
*/
export function shiftInRange(
value: number, // must be in range
offset: number, // positive or negative
min: number, // inclusive
max: number, // inclusive
) {
const modulus = max - min + 1;
return ((value - min + (offset % modulus) + modulus) % modulus) + min;
}

0 comments on commit 1bd13c1

Please sign in to comment.