react-fit-list 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marin Heđeš
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,259 @@
1
+ # react-fit-list
2
+
3
+ ![Resizable FitList demo](./docs/demo.gif)
4
+
5
+ `react-fit-list` is a small headless React utility for rendering a **single-line list that never wraps**.
6
+ When the available width is too small, the list collapses extra items into an overflow affordance such as `+3`.
7
+
8
+ It ships with:
9
+
10
+ - a ready-to-use `<FitList />` component
11
+ - a headless `useFitList()` hook for custom renderers
12
+ - TypeScript types for component and hook APIs
13
+
14
+ ## Use cases
15
+
16
+ `react-fit-list` works well for interfaces where wrapping would break the layout, such as:
17
+
18
+ - tag and chip rows
19
+ - recipient lists
20
+ - breadcrumbs / metadata rows
21
+ - inline filters
22
+ - compact table or card cells
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install react-fit-list
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```tsx
33
+ import { FitList } from 'react-fit-list'
34
+
35
+ const items = [
36
+ { id: 1, label: 'Security' },
37
+ { id: 2, label: 'Startups' },
38
+ { id: 3, label: 'Fintech' },
39
+ { id: 4, label: 'B2B SaaS' },
40
+ ]
41
+
42
+ export function Example() {
43
+ return (
44
+ <div style={{ width: 240 }}>
45
+ <FitList
46
+ items={items}
47
+ getKey={(item) => item.id}
48
+ renderItem={(item) => (
49
+ <span
50
+ style={{
51
+ display: 'inline-flex',
52
+ alignItems: 'center',
53
+ border: '1px solid #d0d7de',
54
+ borderRadius: 999,
55
+ padding: '4px 10px',
56
+ fontSize: 12,
57
+ whiteSpace: 'nowrap',
58
+ }}
59
+ >
60
+ {item.label}
61
+ </span>
62
+ )}
63
+ />
64
+ </div>
65
+ )
66
+ }
67
+ ```
68
+
69
+ ## How it works
70
+
71
+ The package measures the container width and item widths, then determines how many items can fit in a single row.
72
+ If not all items fit, the remainder is collapsed into an overflow element.
73
+
74
+ By default:
75
+
76
+ - the component keeps items on one row
77
+ - overflow collapses from the end
78
+ - the overflow trigger renders as `+N`
79
+ - item widths are measured from the DOM in `live` mode
80
+
81
+ ## Component API
82
+
83
+ ### `<FitList />`
84
+
85
+ ```tsx
86
+ import { FitList } from 'react-fit-list'
87
+ ```
88
+
89
+ #### Required props
90
+
91
+ | Prop | Type | Description |
92
+ | --- | --- | --- |
93
+ | `items` | `readonly T[]` | Items to render. |
94
+ | `getKey` | `(item: T, index: number) => React.Key` | Returns a stable key for each item. |
95
+ | `renderItem` | `(item: T, index: number) => React.ReactNode` | Renders one item. |
96
+
97
+ #### Optional props
98
+
99
+ | Prop | Type | Default | Description |
100
+ | --- | --- | --- | --- |
101
+ | `renderOverflow` | `(args) => React.ReactNode` | renders `+N` | Custom overflow renderer. Receives `{ hiddenCount, hiddenItems, visibleItems, isExpanded, setExpanded, toggle }`. |
102
+ | `className` | `string` | — | Class for the root container. |
103
+ | `listClassName` | `string` | — | Class for the visible-items wrapper. |
104
+ | `itemClassName` | `string` | — | Class for each item wrapper. |
105
+ | `overflowClassName` | `string` | — | Class for the overflow trigger wrapper. |
106
+ | `measureClassName` | `string` | — | Class for hidden measurement nodes. Use when sizing depends on CSS classes. |
107
+ | `emptyFallback` | `React.ReactNode` | `null` | Rendered when `items` is empty. |
108
+ | `gap` | `number` | `8` | Pixel gap between items. |
109
+ | `collapseFrom` | `'end' \| 'start'` | `'end'` | Collapse from the end or start of the list. |
110
+ | `reserveOverflowSpace` | `boolean` | `false` | Reserve room for the overflow element even when everything currently fits. |
111
+ | `overflowWidth` | `number` | auto | Fixed overflow width in pixels. Useful when the trigger width is known. |
112
+ | `estimatedItemWidth` | `number \| ((item, index) => number)` | fallback `96` | Used in `estimate` mode or before live measurements are available. |
113
+ | `measurementMode` | `'live' \| 'estimate'` | `'live'` | `live` measures DOM nodes, `estimate` uses `estimatedItemWidth`. |
114
+ | `expanded` | `boolean` | uncontrolled | Controlled expanded state. |
115
+ | `defaultExpanded` | `boolean` | `false` | Initial expanded state for uncontrolled usage. |
116
+ | `onExpandedChange` | `(expanded: boolean) => void` | — | Called when expanded state changes. |
117
+ | `as` | `keyof React.JSX.IntrinsicElements` | `'div'` | Root element tag name. |
118
+ | `overflowAs` | `keyof React.JSX.IntrinsicElements` | `'button'` | Overflow element tag name. |
119
+
120
+ ## Hook API
121
+
122
+ ### `useFitList()`
123
+
124
+ Use the hook when you want to own the markup but reuse the fitting logic.
125
+
126
+ ```tsx
127
+ import { useFitList } from 'react-fit-list'
128
+ ```
129
+
130
+ #### Hook options
131
+
132
+ The hook accepts the same fitting-related options used by the component:
133
+
134
+ - `items`
135
+ - `getKey`
136
+ - `reserveOverflowSpace`
137
+ - `overflowWidth`
138
+ - `gap`
139
+ - `collapseFrom`
140
+ - `estimatedItemWidth`
141
+ - `measurementMode`
142
+ - `expanded`
143
+ - `defaultExpanded`
144
+ - `onExpandedChange`
145
+ - `measureOverflowWidth`
146
+
147
+ #### Return value
148
+
149
+ | Field | Type | Description |
150
+ | --- | --- | --- |
151
+ | `containerRef` | `RefObject<HTMLDivElement \| null>` | Attach to the outer container whose width should be measured. |
152
+ | `registerItem(key)` | `(node) => void` | Attach to each visible item wrapper. |
153
+ | `registerMeasureItem(key)` | `(node) => void` | Attach to hidden measurement nodes for accurate width calculation. |
154
+ | `registerOverflow(node)` | `(node) => void` | Attach to the overflow element. |
155
+ | `visibleItems` | `T[]` | Items currently visible. |
156
+ | `hiddenItems` | `T[]` | Items currently hidden. |
157
+ | `hiddenCount` | `number` | Count of hidden items. |
158
+ | `isExpanded` | `boolean` | Whether the list is expanded. |
159
+ | `setExpanded` | `(expanded: boolean) => void` | Manually set expanded state. |
160
+ | `toggleExpanded` | `() => void` | Toggle expanded state. |
161
+ | `recompute` | `() => void` | Force a recalculation. |
162
+
163
+ ## Examples
164
+
165
+ ### Custom overflow label
166
+
167
+ ```tsx
168
+ <FitList
169
+ items={items}
170
+ getKey={(item) => item.id}
171
+ renderItem={(item) => <Tag>{item.label}</Tag>}
172
+ renderOverflow={({ hiddenCount }) => (
173
+ <button type="button">+{hiddenCount}</button>
174
+ )}
175
+ />
176
+ ```
177
+
178
+ ### Collapse from the start
179
+
180
+ Useful when the most recent or most important items are at the end.
181
+
182
+ ```tsx
183
+ <FitList
184
+ items={items}
185
+ getKey={(item) => item.id}
186
+ renderItem={(item) => <Tag>{item.label}</Tag>}
187
+ collapseFrom="start"
188
+ />
189
+ ```
190
+
191
+ ### Estimate mode
192
+
193
+ Estimate mode avoids relying on live measurement for every item and can be useful when item widths are predictable.
194
+
195
+ ```tsx
196
+ <FitList
197
+ items={items}
198
+ getKey={(item) => item.id}
199
+ renderItem={(item) => <Tag>{item.label}</Tag>}
200
+ measurementMode="estimate"
201
+ estimatedItemWidth={(item) => Math.max(72, item.label.length * 8)}
202
+ />
203
+ ```
204
+
205
+ ### Controlled expanded state
206
+
207
+ `FitList` does not impose any default click behavior for the overflow trigger. Use `renderOverflow` to define interactions such as opening a popover, modal, or expanding the list.
208
+
209
+ Use `expanded` / `onExpandedChange` only when you intentionally want to control expansion behavior.
210
+
211
+ ```tsx
212
+ function ControlledExample() {
213
+ const [expanded, setExpanded] = useState(false)
214
+
215
+ return (
216
+ <FitList
217
+ items={items}
218
+ getKey={(item) => item.id}
219
+ renderItem={(item) => <Tag>{item.label}</Tag>}
220
+ expanded={expanded}
221
+ onExpandedChange={setExpanded}
222
+ />
223
+ )
224
+ }
225
+ ```
226
+
227
+ ## Styling guidance
228
+
229
+ The package is intentionally headless. You control the appearance of the item contents.
230
+
231
+ For best results:
232
+
233
+ - keep each rendered item visually compact
234
+ - ensure item content does not wrap internally (`white-space: nowrap` is usually correct)
235
+ - use `measureClassName` when your measurement nodes need the same CSS as your visible nodes
236
+ - provide `overflowWidth` when you know the trigger width and want more predictable calculations
237
+
238
+ ## Accessibility notes
239
+
240
+ - By default the overflow trigger renders as a `<button>`.
241
+ - When using a custom `renderOverflow`, make sure the resulting UI still communicates its action clearly.
242
+ - If you switch `overflowAs` away from `button`, you are responsible for semantics and interaction behavior.
243
+
244
+ ## SSR / browser behavior
245
+
246
+ - the package is safe to render during SSR
247
+ - measurements are applied on the client
248
+ - `ResizeObserver` is used when available
249
+ - a `window.resize` listener is also attached as a fallback
250
+
251
+ ## Local development
252
+
253
+ A Vite demo app is included in the `example/` directory. The demo uses a native resize handle instead of a slider so you can drag the container width directly.
254
+
255
+ ```bash
256
+ cd example
257
+ npm install
258
+ npm run dev
259
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,435 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ function _interopNamespace(e) {
7
+ if (e && e.__esModule) return e;
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+
24
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
25
+
26
+ // src/components/FitList.tsx
27
+ function useControllableState({
28
+ value,
29
+ defaultValue,
30
+ onChange
31
+ }) {
32
+ const [internal, setInternal] = React.useState(defaultValue);
33
+ const isControlled = value !== void 0;
34
+ const current = isControlled ? value : internal;
35
+ const setValue = React.useCallback(
36
+ (next) => {
37
+ if (!isControlled) {
38
+ setInternal(next);
39
+ }
40
+ onChange?.(next);
41
+ },
42
+ [isControlled, onChange]
43
+ );
44
+ return [current, setValue];
45
+ }
46
+
47
+ // src/hooks/useFitList.tsx
48
+ var useIsoLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
49
+ function getEstimatedWidth(item, index, estimatedItemWidth, fallback) {
50
+ if (typeof estimatedItemWidth === "function")
51
+ return estimatedItemWidth(item, index);
52
+ if (typeof estimatedItemWidth === "number") return estimatedItemWidth;
53
+ return fallback;
54
+ }
55
+ function useFitList({
56
+ items,
57
+ getKey,
58
+ reserveOverflowSpace = false,
59
+ overflowWidth,
60
+ gap = 8,
61
+ collapseFrom = "end",
62
+ estimatedItemWidth,
63
+ measurementMode = "live",
64
+ expanded,
65
+ defaultExpanded = false,
66
+ onExpandedChange,
67
+ measureOverflowWidth
68
+ }) {
69
+ const containerRef = React.useRef(null);
70
+ const overflowRef = React.useRef(null);
71
+ const itemNodeMap = React.useRef(/* @__PURE__ */ new Map());
72
+ const measureNodeMap = React.useRef(/* @__PURE__ */ new Map());
73
+ const [visibleCount, setVisibleCount] = React.useState(items.length);
74
+ const [isExpanded, setExpanded] = useControllableState({
75
+ value: expanded,
76
+ defaultValue: defaultExpanded,
77
+ onChange: onExpandedChange
78
+ });
79
+ const compute = React.useCallback(() => {
80
+ if (isExpanded) {
81
+ setVisibleCount(items.length);
82
+ return;
83
+ }
84
+ const container = containerRef.current;
85
+ if (!container) {
86
+ setVisibleCount(items.length);
87
+ return;
88
+ }
89
+ const containerWidth = container.clientWidth;
90
+ if (!containerWidth) {
91
+ setVisibleCount(items.length);
92
+ return;
93
+ }
94
+ const keys = items.map(getKey);
95
+ const itemWidths = items.map((item, index) => {
96
+ const key = keys[index];
97
+ const measureNode = measureNodeMap.current.get(key);
98
+ const liveNode = itemNodeMap.current.get(key);
99
+ if (measurementMode === "live") {
100
+ if (measureNode) return measureNode.offsetWidth;
101
+ if (liveNode) return liveNode.offsetWidth;
102
+ }
103
+ return getEstimatedWidth(item, index, estimatedItemWidth, 96);
104
+ });
105
+ let nextVisible = items.length;
106
+ for (let count = items.length; count >= 0; count -= 1) {
107
+ const hiddenCount = items.length - count;
108
+ const visibleWidths = collapseFrom === "end" ? itemWidths.slice(0, count) : itemWidths.slice(items.length - count);
109
+ const itemsWidth = visibleWidths.reduce((sum, width) => sum + width, 0);
110
+ const itemsGap = count > 1 ? gap * (count - 1) : 0;
111
+ let currentOverflowWidth = 0;
112
+ if (hiddenCount > 0) {
113
+ if (typeof overflowWidth === "number") {
114
+ currentOverflowWidth = overflowWidth;
115
+ } else if (measureOverflowWidth) {
116
+ currentOverflowWidth = measureOverflowWidth(hiddenCount);
117
+ } else {
118
+ currentOverflowWidth = overflowRef.current?.offsetWidth ?? 44;
119
+ }
120
+ } else if (reserveOverflowSpace) {
121
+ if (typeof overflowWidth === "number") {
122
+ currentOverflowWidth = overflowWidth;
123
+ } else {
124
+ currentOverflowWidth = overflowRef.current?.offsetWidth ?? 44;
125
+ }
126
+ }
127
+ const overflowGap = (hiddenCount > 0 || reserveOverflowSpace) && count > 0 ? gap : 0;
128
+ const total = itemsWidth + itemsGap + overflowGap + currentOverflowWidth;
129
+ if (total <= containerWidth) {
130
+ nextVisible = count;
131
+ break;
132
+ }
133
+ }
134
+ setVisibleCount((prev) => prev === nextVisible ? prev : nextVisible);
135
+ }, [
136
+ collapseFrom,
137
+ estimatedItemWidth,
138
+ gap,
139
+ getKey,
140
+ isExpanded,
141
+ items,
142
+ measurementMode,
143
+ measureOverflowWidth,
144
+ overflowWidth,
145
+ reserveOverflowSpace
146
+ ]);
147
+ useIsoLayoutEffect(() => {
148
+ compute();
149
+ }, [compute]);
150
+ useIsoLayoutEffect(() => {
151
+ const container = containerRef.current;
152
+ if (!container || typeof ResizeObserver === "undefined") return;
153
+ const observer = new ResizeObserver(() => {
154
+ requestAnimationFrame(compute);
155
+ });
156
+ observer.observe(container);
157
+ return () => observer.disconnect();
158
+ }, [compute]);
159
+ React.useEffect(() => {
160
+ if (typeof window === "undefined") return;
161
+ const onResize = () => compute();
162
+ window.addEventListener("resize", onResize);
163
+ return () => window.removeEventListener("resize", onResize);
164
+ }, [compute]);
165
+ const registerItem = React.useCallback(
166
+ (key) => (node) => {
167
+ if (node) {
168
+ itemNodeMap.current.set(key, node);
169
+ } else {
170
+ itemNodeMap.current.delete(key);
171
+ }
172
+ },
173
+ []
174
+ );
175
+ const registerMeasureItem = React.useCallback(
176
+ (key) => (node) => {
177
+ if (node) {
178
+ measureNodeMap.current.set(key, node);
179
+ } else {
180
+ measureNodeMap.current.delete(key);
181
+ }
182
+ },
183
+ []
184
+ );
185
+ const registerOverflow = React.useCallback((node) => {
186
+ overflowRef.current = node;
187
+ }, []);
188
+ const clampedVisibleCount = Math.max(0, Math.min(visibleCount, items.length));
189
+ const visibleItems = React.useMemo(() => {
190
+ if (isExpanded) return [...items];
191
+ if (collapseFrom === "end") return items.slice(0, clampedVisibleCount);
192
+ return items.slice(items.length - clampedVisibleCount);
193
+ }, [clampedVisibleCount, collapseFrom, isExpanded, items]);
194
+ const hiddenItems = React.useMemo(() => {
195
+ if (isExpanded) return [];
196
+ if (collapseFrom === "end") return items.slice(clampedVisibleCount);
197
+ return items.slice(0, items.length - clampedVisibleCount);
198
+ }, [clampedVisibleCount, collapseFrom, isExpanded, items]);
199
+ const toggleExpanded = React.useCallback(() => {
200
+ setExpanded(!isExpanded);
201
+ }, [isExpanded, setExpanded]);
202
+ return {
203
+ containerRef,
204
+ registerItem,
205
+ registerMeasureItem,
206
+ registerOverflow,
207
+ visibleItems,
208
+ hiddenItems,
209
+ hiddenCount: hiddenItems.length,
210
+ isExpanded,
211
+ setExpanded,
212
+ toggleExpanded,
213
+ recompute: compute
214
+ };
215
+ }
216
+ function defaultOverflow({ hiddenCount }) {
217
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
218
+ "+",
219
+ hiddenCount
220
+ ] });
221
+ }
222
+ function FitList({
223
+ items,
224
+ getKey,
225
+ renderItem,
226
+ renderOverflow = defaultOverflow,
227
+ className,
228
+ listClassName,
229
+ itemClassName,
230
+ overflowClassName,
231
+ measureClassName,
232
+ emptyFallback = null,
233
+ gap = 8,
234
+ collapseFrom = "end",
235
+ reserveOverflowSpace = false,
236
+ overflowWidth,
237
+ estimatedItemWidth,
238
+ measurementMode = "live",
239
+ expanded,
240
+ defaultExpanded = false,
241
+ onExpandedChange,
242
+ as = "div",
243
+ onOverflowClick,
244
+ overflowAs = "button"
245
+ }) {
246
+ const Component = as;
247
+ const OverflowComponent = overflowAs;
248
+ const overflowMeasureRef = React__namespace.useRef(null);
249
+ const isDefaultOverflowRenderer = renderOverflow === defaultOverflow;
250
+ const measureOverflowWidth = React__namespace.useCallback(
251
+ (hiddenCount2) => {
252
+ if (typeof overflowWidth === "number") return overflowWidth;
253
+ const node = overflowMeasureRef.current;
254
+ if (!node) return 44;
255
+ if (isDefaultOverflowRenderer) {
256
+ const previous = node.textContent;
257
+ node.textContent = `+${hiddenCount2}`;
258
+ const width = node.offsetWidth;
259
+ node.textContent = previous;
260
+ return width;
261
+ }
262
+ return node.offsetWidth;
263
+ },
264
+ [isDefaultOverflowRenderer, overflowWidth]
265
+ );
266
+ const {
267
+ containerRef,
268
+ registerItem,
269
+ registerMeasureItem,
270
+ registerOverflow,
271
+ visibleItems,
272
+ hiddenItems,
273
+ hiddenCount,
274
+ isExpanded,
275
+ setExpanded,
276
+ toggleExpanded
277
+ } = useFitList({
278
+ items,
279
+ getKey,
280
+ gap,
281
+ collapseFrom,
282
+ reserveOverflowSpace,
283
+ overflowWidth,
284
+ estimatedItemWidth,
285
+ measurementMode,
286
+ expanded,
287
+ defaultExpanded,
288
+ onExpandedChange,
289
+ measureOverflowWidth
290
+ });
291
+ const visibleEntries = React__namespace.useMemo(() => {
292
+ if (isExpanded) {
293
+ return items.map((item, index) => ({ item, index }));
294
+ }
295
+ if (collapseFrom === "end") {
296
+ return items.slice(0, visibleItems.length).map((item, index) => ({ item, index }));
297
+ }
298
+ const startIndex = items.length - visibleItems.length;
299
+ return items.slice(startIndex).map((item, index) => ({ item, index: startIndex + index }));
300
+ }, [collapseFrom, isExpanded, items, visibleItems.length]);
301
+ if (items.length === 0) {
302
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: emptyFallback });
303
+ }
304
+ const overflowArgs = {
305
+ hiddenCount,
306
+ hiddenItems: [...hiddenItems],
307
+ visibleItems: [...visibleItems],
308
+ isExpanded,
309
+ setExpanded,
310
+ toggle: toggleExpanded
311
+ };
312
+ const overflowChildren = renderOverflow(overflowArgs);
313
+ const overflowButtonProps = {
314
+ className: overflowClassName,
315
+ type: "button",
316
+ onClick: (event) => onOverflowClick?.(overflowArgs, event),
317
+ "aria-expanded": isExpanded,
318
+ children: overflowChildren
319
+ };
320
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
321
+ /* @__PURE__ */ jsxRuntime.jsx(
322
+ "div",
323
+ {
324
+ className: listClassName,
325
+ style: {
326
+ display: "flex",
327
+ alignItems: "center",
328
+ gap,
329
+ minWidth: 0,
330
+ flex: "1 1 auto",
331
+ overflow: "hidden"
332
+ },
333
+ children: visibleEntries.map(({ item, index }) => {
334
+ const key = getKey(item, index);
335
+ return /* @__PURE__ */ jsxRuntime.jsx(
336
+ "div",
337
+ {
338
+ ref: registerItem(key),
339
+ className: itemClassName,
340
+ style: {
341
+ minWidth: 0,
342
+ flex: "0 0 auto",
343
+ whiteSpace: "nowrap"
344
+ },
345
+ children: renderItem(item, index)
346
+ },
347
+ key
348
+ );
349
+ })
350
+ }
351
+ ),
352
+ (hiddenCount > 0 || reserveOverflowSpace) && /* @__PURE__ */ jsxRuntime.jsx(
353
+ "div",
354
+ {
355
+ ref: registerOverflow,
356
+ style: {
357
+ visibility: hiddenCount > 0 ? "visible" : "hidden",
358
+ flex: "0 0 auto",
359
+ whiteSpace: "nowrap",
360
+ display: "block"
361
+ },
362
+ children: hiddenCount > 0 ? overflowAs === "button" ? /* @__PURE__ */ jsxRuntime.jsx("button", { ...overflowButtonProps }) : React__namespace.createElement(
363
+ OverflowComponent,
364
+ { className: overflowClassName },
365
+ overflowChildren
366
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "+0" })
367
+ }
368
+ )
369
+ ] });
370
+ const root = React__namespace.createElement(
371
+ Component,
372
+ {
373
+ ref: containerRef,
374
+ className,
375
+ style: {
376
+ display: "flex",
377
+ alignItems: "center",
378
+ gap,
379
+ minWidth: 0,
380
+ whiteSpace: "nowrap"
381
+ }
382
+ },
383
+ content
384
+ );
385
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
386
+ root,
387
+ /* @__PURE__ */ jsxRuntime.jsx(
388
+ "div",
389
+ {
390
+ "aria-hidden": "true",
391
+ style: {
392
+ pointerEvents: "none",
393
+ position: "fixed",
394
+ top: 0,
395
+ left: 0,
396
+ zIndex: -1,
397
+ overflow: "hidden",
398
+ opacity: 0
399
+ },
400
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap }, children: [
401
+ items.map((item, index) => {
402
+ const key = getKey(item, index);
403
+ return /* @__PURE__ */ jsxRuntime.jsx(
404
+ "span",
405
+ {
406
+ ref: registerMeasureItem(key),
407
+ className: measureClassName ?? itemClassName,
408
+ style: {
409
+ display: "inline-flex",
410
+ whiteSpace: "nowrap"
411
+ },
412
+ children: renderItem(item, index)
413
+ },
414
+ `measure:${String(key)}`
415
+ );
416
+ }),
417
+ /* @__PURE__ */ jsxRuntime.jsx(
418
+ "span",
419
+ {
420
+ ref: overflowMeasureRef,
421
+ className: overflowClassName,
422
+ style: { display: "inline-flex", whiteSpace: "nowrap" },
423
+ children: overflowChildren
424
+ }
425
+ )
426
+ ] })
427
+ }
428
+ )
429
+ ] });
430
+ }
431
+
432
+ exports.FitList = FitList;
433
+ exports.useFitList = useFitList;
434
+ //# sourceMappingURL=index.cjs.map
435
+ //# sourceMappingURL=index.cjs.map