Skip to content

Commit 01aa254

Browse files
chore: overflow utility initial commit (#18644)
* chore: overflow utility initial commit * refactor: added optional callbacks for custom styling, and tests * refactor: tests to work with heights, and added interface docs * refactor: overflow handler, separate functions
1 parent ed20be6 commit 01aa254

File tree

4 files changed

+440
-1
lines changed

4 files changed

+440
-1
lines changed

packages/utilities/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/**
2-
* Copyright IBM Corp. 2024
2+
* Copyright IBM Corp. 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
77

88
export * from './dateTimeFormat';
9+
export * from './overflowHandler';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Copyright IBM Corp. 2025
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
export * from './overflowHandler';
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/**
2+
* Copyright IBM Corp. 2025
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import { createOverflowHandler } from './overflowHandler';
9+
10+
// Helper to create items with a given size in the specified dimension ('width' or 'height')
11+
const createItems = (sizes, dimension) => {
12+
return sizes.map((size) => {
13+
const item = document.createElement('div');
14+
Object.defineProperty(item, 'getBoundingClientRect', {
15+
value: () => ({ [dimension]: size }),
16+
});
17+
return item;
18+
});
19+
};
20+
21+
// Helper to set container dimension (clientWidth or clientHeight)
22+
const setContainerDimension = (container, dimension, value) => {
23+
const prop = dimension === 'width' ? 'clientWidth' : 'clientHeight';
24+
Object.defineProperty(container, prop, {
25+
value,
26+
configurable: true,
27+
writable: true,
28+
});
29+
};
30+
31+
describe('createOverflowHandler (width)', () => {
32+
let container, handler, mockOnChange;
33+
34+
beforeEach(() => {
35+
container = document.createElement('div');
36+
mockOnChange = jest.fn();
37+
});
38+
39+
afterEach(() => {
40+
if (handler) {
41+
handler.disconnect();
42+
handler = null;
43+
}
44+
});
45+
46+
describe('Equal sized items (40px each)', () => {
47+
beforeEach(() => {
48+
const items = createItems(Array(10).fill(40), 'width');
49+
container.append(...items);
50+
});
51+
52+
test.each([
53+
[500, 10, 0], // All items fit
54+
[400, 10, 0], // All items fit perfectly
55+
[200, 5, 5], // 4 items fit, 6 hidden
56+
[80, 2, 8], // 1 item fits, 9 hidden
57+
[0, 0, 10], // No items fit, all hidden
58+
])(
59+
'When container width is %ipx, expect %i visible and %i hidden',
60+
(width, expectedVisible, expectedHidden) => {
61+
setContainerDimension(container, 'width', width);
62+
handler = createOverflowHandler({
63+
container,
64+
onChange: mockOnChange,
65+
});
66+
if (expectedHidden === 0) {
67+
expect(mockOnChange).not.toHaveBeenCalled();
68+
} else {
69+
expect(mockOnChange).toHaveBeenCalled();
70+
const [visible, hidden] = mockOnChange.mock.calls[0];
71+
expect(visible.length).toBe(expectedVisible);
72+
expect(hidden.length).toBe(expectedHidden);
73+
}
74+
}
75+
);
76+
77+
test('Respects maxVisibleItems option', () => {
78+
setContainerDimension(container, 'width', 500);
79+
handler = createOverflowHandler({
80+
container,
81+
maxVisibleItems: 3,
82+
onChange: mockOnChange,
83+
});
84+
expect(mockOnChange).toHaveBeenCalled();
85+
const [visible, hidden] = mockOnChange.mock.calls[0];
86+
expect(visible.length).toBe(3);
87+
expect(hidden.length).toBe(7);
88+
});
89+
});
90+
91+
describe('Varying sized items (40-60px)', () => {
92+
beforeEach(() => {
93+
const items = createItems(
94+
[40, 50, 60, 40, 50, 60, 40, 50, 60, 40],
95+
'width'
96+
);
97+
container.append(...items);
98+
});
99+
100+
test.each([
101+
[500, 10, 0], // All items fit
102+
[400, 8, 2], // Fits first 7 items
103+
[200, 4, 6], // Fits first 3 items
104+
[80, 2, 8],
105+
[0, 0, 10],
106+
])(
107+
'When container width is %ipx, expect %i visible and %i hidden',
108+
(width, expectedVisible, expectedHidden) => {
109+
setContainerDimension(container, 'width', width);
110+
handler = createOverflowHandler({
111+
container,
112+
onChange: mockOnChange,
113+
});
114+
115+
if (expectedHidden === 0) {
116+
expect(mockOnChange).not.toHaveBeenCalled();
117+
} else {
118+
expect(mockOnChange).toHaveBeenCalled();
119+
const [visible, hidden] = mockOnChange.mock.calls[0];
120+
expect(visible.length).toBe(expectedVisible);
121+
expect(hidden.length).toBe(expectedHidden);
122+
}
123+
}
124+
);
125+
126+
test('Respects maxVisibleItems option', () => {
127+
setContainerDimension(container, 'width', 500);
128+
handler = createOverflowHandler({
129+
container,
130+
maxVisibleItems: 3,
131+
onChange: mockOnChange,
132+
});
133+
expect(mockOnChange).toHaveBeenCalled();
134+
const [visible, hidden] = mockOnChange.mock.calls[0];
135+
expect(visible.length).toBe(3);
136+
expect(hidden.length).toBe(7);
137+
});
138+
});
139+
});
140+
141+
describe('createOverflowHandler (height)', () => {
142+
let container, handler, mockOnChange;
143+
144+
beforeEach(() => {
145+
container = document.createElement('div');
146+
mockOnChange = jest.fn();
147+
});
148+
149+
afterEach(() => {
150+
if (handler) {
151+
handler.disconnect();
152+
handler = null;
153+
}
154+
});
155+
156+
describe('Varying sized items (40-60px)', () => {
157+
beforeEach(() => {
158+
const items = createItems(
159+
[40, 50, 60, 40, 50, 60, 40, 50, 60, 40],
160+
'height'
161+
);
162+
container.append(...items);
163+
});
164+
165+
test.each([
166+
[500, 10, 0],
167+
[400, 8, 2],
168+
[200, 4, 6],
169+
[80, 2, 8],
170+
[0, 0, 10],
171+
])(
172+
'When container height is %ipx, expect %i visible and %i hidden',
173+
(height, expectedVisible, expectedHidden) => {
174+
setContainerDimension(container, 'height', height);
175+
handler = createOverflowHandler({
176+
container,
177+
dimension: 'height',
178+
onChange: mockOnChange,
179+
});
180+
if (expectedHidden === 0) {
181+
expect(mockOnChange).not.toHaveBeenCalled();
182+
} else {
183+
expect(mockOnChange).toHaveBeenCalled();
184+
const [visible, hidden] = mockOnChange.mock.calls[0];
185+
expect(visible.length).toBe(expectedVisible);
186+
expect(hidden.length).toBe(expectedHidden);
187+
}
188+
}
189+
);
190+
191+
test('Respects maxVisibleItems option with height dimension', () => {
192+
setContainerDimension(container, 'height', 500);
193+
handler = createOverflowHandler({
194+
container,
195+
maxVisibleItems: 3,
196+
dimension: 'height',
197+
onChange: mockOnChange,
198+
});
199+
expect(mockOnChange).toHaveBeenCalled();
200+
const [visible, hidden] = mockOnChange.mock.calls[0];
201+
expect(visible.length).toBe(3);
202+
expect(hidden.length).toBe(7);
203+
});
204+
});
205+
});

0 commit comments

Comments
 (0)