Skip to content

Commit 1bd13c1

Browse files
committed
Add shiftInRange helper
1 parent 8158aa3 commit 1bd13c1

File tree

3 files changed

+61
-12
lines changed

3 files changed

+61
-12
lines changed

packages/circuit-ui/hooks/useFocusList/useFocusList.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import { useCallback, useId, type KeyboardEvent } from 'react';
1717

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

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

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

5555
return { 'data-focus-list': name, onKeyDown };
5656
}
57-
58-
function getPrevIndex(currentIndex: number, length: number): number {
59-
return currentIndex - 1 < 0 ? length - 1 : currentIndex - 1;
60-
}
61-
62-
function getNextIndex(currentIndex: number, length: number): number {
63-
return currentIndex + 1 >= length ? 0 : currentIndex + 1;
64-
}

packages/circuit-ui/util/helpers.spec.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@
1515

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

18-
import { chunk, clamp, eachFn, isEmpty, last, throttle } from './helpers.js';
18+
import {
19+
chunk,
20+
clamp,
21+
eachFn,
22+
isEmpty,
23+
last,
24+
shiftInRange,
25+
throttle,
26+
} from './helpers.js';
1927

2028
describe('helpers', () => {
2129
describe('clamp', () => {
@@ -231,4 +239,36 @@ describe('helpers', () => {
231239
expect(actual).toBeUndefined();
232240
});
233241
});
242+
243+
describe('shiftInRange', () => {
244+
it('should increase a value within a range', () => {
245+
const actual = shiftInRange(4, 2, 1, 12);
246+
expect(actual).toBe(6);
247+
});
248+
249+
it('should decrease a value within a range', () => {
250+
const actual = shiftInRange(4, -2, 1, 12);
251+
expect(actual).toBe(2);
252+
});
253+
254+
it('should loop around to the end', () => {
255+
const actual = shiftInRange(4, -6, 1, 12);
256+
expect(actual).toBe(10);
257+
});
258+
259+
it('should loop around to the start', () => {
260+
const actual = shiftInRange(7, 10, 4, 12);
261+
expect(actual).toBe(8);
262+
});
263+
264+
it('should loop around to the start', () => {
265+
const actual = shiftInRange(4, 10, 2, 12);
266+
expect(actual).toBe(3);
267+
});
268+
269+
it('should loop around multiple times', () => {
270+
const actual = shiftInRange(4, -9, 2, 5);
271+
expect(actual).toBe(3);
272+
});
273+
});
234274
});

packages/circuit-ui/util/helpers.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export function isEmpty(value: unknown): boolean {
4646
return false;
4747
}
4848

49+
/**
50+
* Clamps a value within a range of values between a minimum and maximum limit.
51+
*/
4952
export function clamp(value: number, min: number, max: number): number {
5053
if (process.env.NODE_ENV !== 'production' && min >= max) {
5154
throw new RangeError(
@@ -96,3 +99,17 @@ export function last<T>(array: T[]): T;
9699
export function last<T>(array: T[] | undefined | null): T | undefined {
97100
return isArray(array) ? array[array.length - 1] : undefined;
98101
}
102+
103+
/**
104+
* Increases or decreases a value by an offset and loops back around to stay
105+
* within a given range.
106+
*/
107+
export function shiftInRange(
108+
value: number, // must be in range
109+
offset: number, // positive or negative
110+
min: number, // inclusive
111+
max: number, // inclusive
112+
) {
113+
const modulus = max - min + 1;
114+
return ((value - min + (offset % modulus) + modulus) % modulus) + min;
115+
}

0 commit comments

Comments
 (0)