@udixio/ui-react 2.10.10 → 2.10.12

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.
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
1
+ import React, { useEffect, useLayoutEffect, useRef, useState, useCallback } from 'react';
2
2
  import { animate } from 'motion/react';
3
3
  import { CarouselInterface, CarouselItemInterface } from '../interfaces';
4
4
 
@@ -7,10 +7,6 @@ import { CustomScroll } from '../effects';
7
7
  import { ReactProps } from '../utils';
8
8
  import { CarouselItem, normalize } from './CarouselItem';
9
9
 
10
- function clamp(v: number, min: number, max: number) {
11
- return Math.max(min, Math.min(max, v));
12
- }
13
-
14
10
  /**
15
11
  * Carousels show a collection of items that can be scrolled on and off the screen
16
12
  *
@@ -39,11 +35,9 @@ export const Carousel = ({
39
35
  scrollSensitivity = 1.25,
40
36
  ...restProps
41
37
  }: ReactProps<CarouselInterface>) => {
42
- const defaultRef = useRef(null);
38
+ const defaultRef = useRef<HTMLDivElement>(null);
43
39
  const ref = optionalRef || defaultRef;
44
40
 
45
- const [translateX, setTranslateX] = useState(0);
46
-
47
41
  const styles = useCarouselStyle({
48
42
  index,
49
43
  className,
@@ -63,78 +57,64 @@ export const Carousel = ({
63
57
  );
64
58
 
65
59
  const trackRef = useRef<HTMLDivElement>(null);
66
- const [itemsWidth, setItemsWidth] = useState<Record<number, number | null>>(
67
- {},
68
- );
69
- const [scroll, setScroll] = useState<{
70
- scrollProgress: number;
71
- scrollTotal: number;
72
- scrollVisible: number;
73
- scroll: number;
74
- } | null>(null);
60
+
61
+ // OPTIMIZATION: We no longer store width and translate in React state to avoid laggy 60fps re-renders.
62
+ // We use refs instead.
63
+ const getScrollState = useRef({
64
+ scrollProgress: 0,
65
+ scrollTotal: 0,
66
+ scrollVisible: 0,
67
+ scroll: 0,
68
+ });
69
+
75
70
  // Smoothed scroll progress using framer-motion animate()
76
71
  const smoothedProgressRef = useRef(0);
77
72
  const scrollAnimationRef = useRef<ReturnType<typeof animate> | null>(null);
78
73
 
79
- const calculatePercentages = () => {
80
- if (
81
- !trackRef.current ||
82
- !ref.current ||
83
- scroll?.scrollProgress === undefined
84
- )
85
- return [];
86
-
87
- const scrollVisible =
88
- scroll?.scrollVisible ?? (ref.current as any)?.clientWidth ?? 0;
89
-
90
- function assignRelativeIndexes(
91
- values: number[],
92
- progressScroll: number,
93
- ): {
94
- itemScrollXCenter: number;
95
- relativeIndex: number;
96
- index: number;
97
- width: number;
98
- }[] {
99
- return values.map((value, index) => {
100
- const relativeIndex =
101
- (value - progressScroll) / Math.abs(values[1] - values[0]);
102
- return {
103
- itemScrollXCenter: value,
104
- relativeIndex,
105
- index: index,
106
- width: 0,
107
- };
108
- });
109
- }
110
-
111
- const itemsScrollXCenter = items.map((_, index) => {
112
- const itemRef = itemRefs[index];
74
+ const itemRefs = useRef<React.RefObject<HTMLDivElement | null>[]>([]).current;
75
+ const [selectedItem, setSelectedItem] = useState(0);
113
76
 
114
- if (!itemRef.current || !trackRef.current) return 0;
77
+ if (itemRefs.length !== items.length) {
78
+ itemRefs.length = 0; // reset
79
+ items.forEach((_, i) => {
80
+ itemRefs[i] = React.createRef<HTMLDivElement>();
81
+ });
82
+ }
115
83
 
116
- const itemScrollXCenter = index / (items.length - 1);
84
+ // Pure mathematical logic - kept from original
85
+ const updateLayoutFromCalculations = useCallback(() => {
86
+ if (!trackRef.current || !ref.current) return;
87
+ const currentScrollProgress = smoothedProgressRef.current;
88
+
89
+ // Need dimensions
90
+ const scrollVisible = getScrollState.current.scrollVisible || (ref.current as any).clientWidth || 0;
91
+
92
+ function assignRelativeIndexes(values: number[], progressScroll: number) {
93
+ return values.map((value, idx) => {
94
+ const relativeIndex = (value - progressScroll) / Math.abs(values[1] - values[0]);
95
+ return { itemScrollXCenter: value, relativeIndex, index: idx, width: 0 };
96
+ });
97
+ }
117
98
 
99
+ const itemsScrollXCenter = items.map((_, idx) => {
100
+ // Calculate original center normalized
101
+ const itemScrollXCenter = idx / Math.max(1, items.length - 1);
118
102
  return normalize(itemScrollXCenter, [0, 1], [0, 1]);
119
103
  });
120
104
 
121
105
  const itemValues = assignRelativeIndexes(
122
106
  itemsScrollXCenter,
123
- scroll?.scrollProgress ?? 0,
107
+ currentScrollProgress,
124
108
  ).sort((a, b) => a.index - b.index);
125
109
 
126
- let widthLeft =
127
- (ref.current?.clientWidth ?? scrollVisible) + gap + outputRange[0] + gap;
110
+ let widthLeft = scrollVisible + gap + outputRange[0] + gap;
128
111
 
112
+ let localSelected = selectedItem;
129
113
  const visibleItemValues = itemValues
130
114
  .sort((a, b) => Math.abs(a.relativeIndex) - Math.abs(b.relativeIndex))
131
- .map((item, index) => {
132
- if (widthLeft <= 0) {
133
- return undefined;
134
- }
135
- if (index == 0) {
136
- setSelectedItem(item.index);
137
- }
115
+ .map((item, idx) => {
116
+ if (widthLeft <= 0) return undefined;
117
+ if (idx === 0) localSelected = item.index;
138
118
 
139
119
  item.width = normalize(
140
120
  widthLeft - gap,
@@ -144,48 +124,30 @@ export const Carousel = ({
144
124
 
145
125
  widthLeft -= item.width + gap;
146
126
 
147
- if (widthLeft != 0 && widthLeft < (outputRange[0] + gap) * 2) {
148
- const newWidth =
149
- item.width - ((outputRange[0] + gap) * 2 - widthLeft);
150
-
127
+ if (widthLeft !== 0 && widthLeft < (outputRange[0] + gap) * 2) {
128
+ const newWidth = item.width - ((outputRange[0] + gap) * 2 - widthLeft);
151
129
  widthLeft += item.width;
152
130
  item.width = newWidth;
153
131
  widthLeft -= item.width;
154
- } else if (widthLeft == 0 && item.width >= outputRange[0] * 2 + gap) {
132
+ } else if (widthLeft === 0 && item.width >= outputRange[0] * 2 + gap) {
155
133
  const newWidth = item.width - (outputRange[0] + gap - widthLeft);
156
-
157
134
  widthLeft += item.width;
158
135
  item.width = newWidth;
159
136
  widthLeft -= item.width;
160
137
  }
161
138
  return item;
162
139
  })
163
- .filter(Boolean) as unknown as {
164
- itemScrollXCenter: number;
165
- relativeIndex: number;
166
- index: number;
167
- width: number;
168
- }[];
169
-
170
- const reverseItemsVisible = visibleItemValues.reverse();
171
- const itemsVisibleByIndex = [...visibleItemValues].sort(
172
- (a, b) => Math.abs(a.index) - Math.abs(b.index),
173
- );
140
+ .filter(Boolean) as { itemScrollXCenter: number; relativeIndex: number; index: number; width: number; }[];
174
141
 
175
- //dynamic items
142
+ const reverseItemsVisible = [...visibleItemValues].reverse();
143
+ const itemsVisibleByIndex = [...visibleItemValues].sort((a, b) => Math.abs(a.index) - Math.abs(b.index));
176
144
 
177
- reverseItemsVisible.forEach((item, index) => {
178
- const nextItem = reverseItemsVisible[index + 1];
145
+ reverseItemsVisible.forEach((item, idx) => {
146
+ const nextItem = reverseItemsVisible[idx + 1];
179
147
  if (!nextItem) return;
180
148
 
181
- const test =
182
- 1 - (Math.abs(item.relativeIndex) - Math.abs(nextItem?.relativeIndex));
183
-
184
- const newWidth = normalize(
185
- test,
186
- [0, 2],
187
- [item.width + widthLeft, nextItem.width],
188
- );
149
+ const test = 1 - (Math.abs(item.relativeIndex) - Math.abs(nextItem.relativeIndex));
150
+ const newWidth = normalize(test, [0, 2], [item.width + widthLeft, nextItem.width]);
189
151
 
190
152
  widthLeft += item.width;
191
153
  item.width = newWidth;
@@ -195,71 +157,69 @@ export const Carousel = ({
195
157
  const percentMax = visibleItemValues.length / 2;
196
158
  const percent = normalize(
197
159
  Math.abs(itemsVisibleByIndex[0].relativeIndex),
198
- [itemsVisibleByIndex[0].index == 0 ? 0 : percentMax - 1, percentMax],
160
+ [itemsVisibleByIndex[0].index === 0 ? 0 : percentMax - 1, percentMax],
199
161
  [0, 1],
200
162
  );
201
163
 
202
- const translate =
203
- normalize(percent, [0, 1], [0, 1]) * -(outputRange[0] + gap);
164
+ const translate = normalize(percent, [0, 1], [0, 1]) * -(outputRange[0] + gap);
165
+
166
+ // ===================================
167
+ // DOM INJECTION OPTIMIZATION
168
+ // ===================================
169
+
170
+ // Apply width to each visible item using DOM instead of setItemWidths React state
171
+ // First, fallback everything to outputRange[0] (or hide them)
172
+ itemRefs.forEach((refItem, i) => {
173
+ if (refItem.current) {
174
+ const match = visibleItemValues.find(v => v.index === i);
175
+ if (match) {
176
+ refItem.current.style.setProperty('--carousel-item-width', `${match.width}px`);
177
+ refItem.current.style.display = 'block';
178
+ } else {
179
+ refItem.current.style.setProperty('--carousel-item-width', `${outputRange[0]}px`);
180
+ refItem.current.style.display = 'none';
181
+ }
182
+ }
183
+ });
204
184
 
205
- setTranslateX(translate);
185
+ // Apply track translate directly via DOM
186
+ trackRef.current.style.transform = `translateX(${translate}px)`;
187
+
188
+ if (localSelected !== selectedItem) {
189
+ setSelectedItem(localSelected);
190
+ }
191
+ }, [items.length, outputRange, gap, selectedItem]);
192
+
193
+ useLayoutEffect(() => {
194
+ updateLayoutFromCalculations();
195
+ }, [updateLayoutFromCalculations, items.length]);
206
196
 
207
- return Object.fromEntries(
208
- visibleItemValues.map((item) => [item.index, item.width]),
209
- );
210
- };
211
- const itemRefs = useRef<React.RefObject<HTMLDivElement | null>[]>([]).current;
212
- const [selectedItem, setSelectedItem] = useState(0);
213
197
 
214
198
  useEffect(() => {
215
199
  if (onChange) onChange(selectedItem);
216
- }, [selectedItem]);
200
+ }, [selectedItem, onChange]);
217
201
 
218
- // Sync controlled index prop to internal state/position
219
- useEffect(() => {
220
- if (
221
- typeof index === 'number' &&
222
- items.length > 0 &&
223
- index !== selectedItem
224
- ) {
225
- centerOnIndex(index);
226
- }
227
- }, [index, items.length]);
202
+ // accessibility and interaction states
203
+ const [focusedIndex, setFocusedIndex] = useState(0);
228
204
 
229
- // keep focused index aligned with selected when selection changes through scroll
230
205
  useEffect(() => {
231
206
  setFocusedIndex(selectedItem);
232
207
  }, [selectedItem]);
233
208
 
234
- if (itemRefs.length !== items.length) {
235
- items.forEach((_, i) => {
236
- if (!itemRefs[i]) {
237
- itemRefs[i] = React.createRef<HTMLDivElement>(); // Crée une nouvelle ref si manquante
238
- }
239
- });
240
- }
241
-
242
- // accessibility and interaction states
243
- const [focusedIndex, setFocusedIndex] = useState(0);
244
209
 
245
- const centerOnIndex = (index: number, opts: { animate?: boolean } = {}) => {
246
- // Guard: need valid refs and at least one item
210
+ const centerOnIndex = (idx: number, opts: { animate?: boolean } = {}) => {
247
211
  if (!items.length) return 0;
248
- const itemRef = itemRefs[index];
212
+ const itemRef = itemRefs[idx];
249
213
  if (!itemRef || !itemRef.current || !trackRef.current) return 0;
250
214
 
251
- // Compute progress (0..1) for the target item center within the track
252
215
  const itemScrollXCenter = normalize(
253
- index / Math.max(1, items.length - 1),
216
+ idx / Math.max(1, items.length - 1),
254
217
  [0, 1],
255
218
  [0, 1],
256
219
  );
257
220
 
258
- // Update selection/focus hint
259
- setFocusedIndex(index);
221
+ setFocusedIndex(idx);
260
222
 
261
- // Ask CustomScroll to move to the computed progress. This will trigger onScroll,
262
- // which in turn drives the smoothed animation via handleScroll().
263
223
  const track = trackRef.current as HTMLElement;
264
224
  track.dispatchEvent(
265
225
  new CustomEvent('udx:customScroll:set', {
@@ -275,32 +235,11 @@ export const Carousel = ({
275
235
  return itemScrollXCenter;
276
236
  };
277
237
 
278
- const renderItems = items.map((child, index) => {
279
- const existingOnClick = (child as any).props?.onClick as
280
- | ((e: any) => void)
281
- | undefined;
282
- const handleClick = (e: any) => {
283
- existingOnClick?.(e);
284
- // centerOnIndex(index);
285
- };
286
- const handleFocus = () => setFocusedIndex(index);
287
-
288
- return React.cloneElement(
289
- child as React.ReactElement<ReactProps<CarouselItemInterface>>,
290
- {
291
- width: itemsWidth[index],
292
- outputRange,
293
- ref: itemRefs[index],
294
- key: index,
295
- index,
296
- role: 'option',
297
- 'aria-selected': selectedItem === index,
298
- tabIndex: selectedItem === index ? 0 : -1,
299
- onClick: handleClick,
300
- onFocus: handleFocus,
301
- } as any,
302
- );
303
- });
238
+ useEffect(() => {
239
+ if (typeof index === 'number' && items.length > 0 && index !== selectedItem) {
240
+ centerOnIndex(index);
241
+ }
242
+ }, [index, items.length]);
304
243
 
305
244
  const handleScroll = (args: {
306
245
  scrollProgress: number;
@@ -308,15 +247,14 @@ export const Carousel = ({
308
247
  scrollVisible: number;
309
248
  scroll: number;
310
249
  }) => {
250
+ getScrollState.current = args;
251
+
311
252
  if (args.scrollTotal > 0) {
312
- // Smooth and inertial transition of scrollProgress using framer-motion animate()
313
- // Stop any previous animation to avoid stacking
314
253
  scrollAnimationRef.current?.stop();
315
254
  const from = smoothedProgressRef.current ?? 0;
316
255
  const to = args.scrollProgress ?? 0;
317
256
 
318
257
  scrollAnimationRef.current = animate(from, to, {
319
- // Spring tuning to add a bit of inertia and smoothness
320
258
  type: 'spring',
321
259
  stiffness: 260,
322
260
  damping: 32,
@@ -324,46 +262,33 @@ export const Carousel = ({
324
262
  restDelta: 0.0005,
325
263
  onUpdate: (v) => {
326
264
  smoothedProgressRef.current = v;
327
- setScroll({ ...args, scrollProgress: v });
265
+ requestAnimationFrame(() => {
266
+ updateLayoutFromCalculations(); // Apply DOM updates synchronously to animation
267
+ });
328
268
  },
329
269
  });
330
270
  }
331
271
  };
332
272
 
333
- useEffect(() => {
334
- const updatedPercentages = calculatePercentages();
335
- setItemsWidth(updatedPercentages);
336
- }, [scroll]);
337
-
338
273
  // Keep latest onMetricsChange in a ref to avoid effect dependency loops
339
274
  const onMetricsChangeRef = useRef(onMetricsChange);
340
275
  useEffect(() => {
341
276
  onMetricsChangeRef.current = onMetricsChange;
342
277
  }, [onMetricsChange]);
343
278
 
344
- // Cache last emitted metrics to prevent redundant calls
345
279
  const lastMetricsRef = useRef<any>(null);
346
280
 
347
- // Compute and emit live metrics for external control
348
281
  useEffect(() => {
349
282
  const cb = onMetricsChangeRef.current;
350
- if (!cb) return;
351
- if (!ref?.current) return;
283
+ if (!cb || !ref?.current || items.length <= 0) return;
352
284
  const total = items.length;
353
- if (total <= 0) return;
354
- const viewportWidth = (ref.current as any)?.clientWidth ?? 0;
285
+ const viewportWidth = (ref.current as any).clientWidth || 0;
355
286
  const itemMaxWidth = outputRange[1];
356
- const sProgress =
357
- smoothedProgressRef.current ?? scroll?.scrollProgress ?? 0;
287
+ const sProgress = smoothedProgressRef.current;
358
288
  const visibleApprox = (viewportWidth + gap) / (itemMaxWidth + gap);
359
289
  const visibleFull = Math.max(1, Math.floor(visibleApprox));
360
290
  const stepHalf = Math.max(1, Math.round(visibleFull * (2 / 3)));
361
- const selectedIndexSafe = Math.min(
362
- Math.max(0, selectedItem),
363
- Math.max(0, total - 1),
364
- );
365
- const canPrev = selectedIndexSafe > 0;
366
- const canNext = selectedIndexSafe < total - 1;
291
+ const selectedIndexSafe = Math.min(Math.max(0, selectedItem), Math.max(0, total - 1));
367
292
 
368
293
  const metrics = {
369
294
  total,
@@ -371,20 +296,19 @@ export const Carousel = ({
371
296
  visibleApprox,
372
297
  visibleFull,
373
298
  stepHalf,
374
- canPrev,
375
- canNext,
299
+ canPrev: selectedIndexSafe > 0,
300
+ canNext: selectedIndexSafe < total - 1,
376
301
  scrollProgress: sProgress,
377
302
  viewportWidth,
378
303
  itemMaxWidth,
379
304
  gap,
380
- } as any;
305
+ };
381
306
 
382
- // Shallow compare with last metrics to avoid spamming parent and loops
383
307
  const last = lastMetricsRef.current;
384
308
  let changed = !last;
385
309
  if (!changed) {
386
310
  for (const k in metrics) {
387
- if (metrics[k] !== last[k]) {
311
+ if ((metrics as any)[k] !== last[k]) {
388
312
  changed = true;
389
313
  break;
390
314
  }
@@ -395,34 +319,8 @@ export const Carousel = ({
395
319
  lastMetricsRef.current = metrics;
396
320
  cb(metrics);
397
321
  }
398
- }, [ref, items.length, selectedItem, scroll, gap, outputRange]);
399
-
400
- // // Recalculate on scrollMV changes (e.g., programmatic animations)
401
- // useEffect(() => {
402
- // const unsubscribe = scrollMV.on('change', (p) => {
403
- // // Keep CustomScroll container in sync by dispatching a bubbling control event
404
- // const track = trackRef.current as HTMLElement | null;
405
- // if (track) {
406
- // track.dispatchEvent(
407
- // new CustomEvent('udx:customScroll:set', {
408
- // bubbles: true,
409
- // detail: { progress: p, orientation: 'horizontal' },
410
- // }),
411
- // );
412
- // }
413
- // const updated = calculatePercentages();
414
- // if (updated.length) setItemsWidth(updated);
415
- // });
416
- // return () => unsubscribe();
417
- // }, [scrollMV, trackRef]);
418
-
419
- // Initial compute on mount and when items count changes
420
- // useLayoutEffect(() => {
421
- // const updated = calculatePercentages();
422
- // if (updated.length) setItemsWidth(updated);
423
- // }, [items.length]);
424
-
425
- // Cleanup any pending animation on unmount
322
+ }, [ref, items.length, selectedItem, gap, outputRange]);
323
+
426
324
  useEffect(() => {
427
325
  return () => {
428
326
  scrollAnimationRef.current?.stop();
@@ -432,40 +330,14 @@ export const Carousel = ({
432
330
  const [scrollSize, setScrollSize] = useState(0);
433
331
  useLayoutEffect(() => {
434
332
  let maxWidth = outputRange[1];
435
- if (scroll && maxWidth > scroll.scrollVisible) {
436
- maxWidth = scroll.scrollVisible;
333
+ const scrollState = getScrollState.current;
334
+ if (scrollState && maxWidth > scrollState.scrollVisible && scrollState.scrollVisible > 0) {
335
+ maxWidth = scrollState.scrollVisible;
437
336
  }
438
- const result = ((maxWidth + gap) * renderItems.length) / scrollSensitivity;
439
- setScrollSize(result);
440
- }, [ref, itemRefs, scroll]);
441
-
442
- // Recompute sizes on container/track resize
443
- // useEffect(() => {
444
- // const root = ref.current as unknown as HTMLElement | null;
445
- // const track = trackRef.current as unknown as HTMLElement | null;
446
- // if (!root || !track) return;
447
- // const ro = new ResizeObserver(() => {
448
- // const updated = calculatePercentages();
449
- // if (updated.length) setItemsWidth(updated);
450
- // let maxWidth = outputRange[1];
451
- // const visible = scroll?.scrollVisible ?? root.clientWidth;
452
- // if (maxWidth > visible) maxWidth = visible;
453
- // const result =
454
- // ((maxWidth + gap) * renderItems.length) / scrollSensitivity;
455
- // setScrollSize(result);
456
- // });
457
- // ro.observe(root);
458
- // ro.observe(track);
459
- // return () => ro.disconnect();
460
- // }, [
461
- // ref,
462
- // trackRef,
463
- // renderItems.length,
464
- // gap,
465
- // outputRange,
466
- // scrollSensitivity,
467
- // scroll,
468
- // ]);
337
+ const result = ((maxWidth + gap) * items.length) / scrollSensitivity;
338
+ setScrollSize(result || 400); // Fail-safe
339
+ }, [ref, items.length, gap, outputRange, scrollSensitivity]);
340
+
469
341
 
470
342
  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
471
343
  if (!items.length) return;
@@ -488,34 +360,52 @@ export const Carousel = ({
488
360
  centerOnIndex(items.length - 1);
489
361
  break;
490
362
  case 'Enter':
491
- case ' ': // Space
363
+ case ' ':
492
364
  e.preventDefault();
493
365
  centerOnIndex(idx);
494
366
  break;
495
367
  }
496
368
  };
497
369
 
498
- // External control via CustomEvent on root element
499
370
  useEffect(() => {
500
371
  const root = ref.current as any;
501
372
  if (!root) return;
502
373
  const handler = (ev: Event) => {
503
- const detail = (ev as CustomEvent).detail as
504
- | { index?: number }
505
- | undefined;
374
+ const detail = (ev as CustomEvent).detail;
506
375
  if (detail && typeof detail.index === 'number') {
507
376
  centerOnIndex(detail.index);
508
377
  }
509
378
  };
510
- root.addEventListener('udx:carousel:centerIndex', handler as EventListener);
379
+ root.addEventListener('udx:carousel:centerIndex', handler);
511
380
  return () => {
512
- root.removeEventListener(
513
- 'udx:carousel:centerIndex',
514
- handler as EventListener,
515
- );
381
+ root.removeEventListener('udx:carousel:centerIndex', handler);
516
382
  };
517
383
  }, [ref, items.length]);
518
384
 
385
+ const renderItems = items.map((child, idx) => {
386
+ const existingOnClick = (child as any).props?.onClick;
387
+ const handleClick = (e: any) => {
388
+ existingOnClick?.(e);
389
+ // centerOnIndex(idx);
390
+ };
391
+
392
+ return React.cloneElement(
393
+ child as React.ReactElement<ReactProps<CarouselItemInterface>>,
394
+ {
395
+ outputRange,
396
+ ref: itemRefs[idx],
397
+ key: idx,
398
+ index: idx,
399
+ role: 'option',
400
+ 'aria-selected': selectedItem === idx,
401
+ tabIndex: selectedItem === idx ? 0 : -1,
402
+ onClick: handleClick,
403
+ onFocus: () => setFocusedIndex(idx),
404
+ // NOTE: We REMOVED the 'width' prop from here!
405
+ } as any,
406
+ );
407
+ });
408
+
519
409
  return (
520
410
  <div
521
411
  className={styles.carousel}
@@ -536,8 +426,7 @@ export const Carousel = ({
536
426
  ref={trackRef}
537
427
  style={{
538
428
  gap: `${gap}px`,
539
- translate: translateX,
540
- willChange: 'translate',
429
+ willChange: 'transform',
541
430
  }}
542
431
  >
543
432
  {renderItems}
@@ -41,23 +41,16 @@ export const CarouselItem = ({
41
41
  const styles = useCarouselItemStyle({
42
42
  className,
43
43
  index,
44
- width,
45
44
  children,
46
- outputRange,
47
45
  });
48
46
 
49
47
  return (
50
48
  <div
51
49
  ref={ref}
52
50
  style={{
53
- width: width + 'px',
54
- maxWidth: outputRange[1] + 'px',
55
- minWidth: outputRange[0] + 'px',
56
- willChange: 'width',
57
- }}
58
- transition={{
59
- duration: 0.5,
60
- ease: 'linear',
51
+ width: 'var(--carousel-item-width, 100%)',
52
+ maxWidth: outputRange ? outputRange[1] + 'px' : undefined,
53
+ minWidth: outputRange ? outputRange[0] + 'px' : undefined,
61
54
  }}
62
55
  className={styles.carouselItem}
63
56
  {...restProps}