@ship-it-ui/ui 0.0.11 → 0.0.13

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/dist/index.cjs CHANGED
@@ -3257,7 +3257,8 @@ var Carousel = (0, import_react52.forwardRef)(function Carousel2({
3257
3257
  ...props
3258
3258
  }, ref) {
3259
3259
  const N = items.length;
3260
- const isLooping = loop && N > 1;
3260
+ const loopMode = !loop ? null : loop === true ? "circular" : loop;
3261
+ const isLooping = loopMode !== null && N > 1;
3261
3262
  const [active, setActive] = useControllableState({
3262
3263
  value: indexProp,
3263
3264
  defaultValue: defaultIndex ?? 0,
@@ -3265,6 +3266,8 @@ var Carousel = (0, import_react52.forwardRef)(function Carousel2({
3265
3266
  });
3266
3267
  const viewportRef = (0, import_react52.useRef)(null);
3267
3268
  const internalScrollRef = (0, import_react52.useRef)(false);
3269
+ const goToInProgressRef = (0, import_react52.useRef)(false);
3270
+ const wrapInFlightRef = (0, import_react52.useRef)(null);
3268
3271
  const activeIdx = active ?? 0;
3269
3272
  const domIndexFor = (0, import_react52.useCallback)((real) => isLooping ? real + 1 : real, [isLooping]);
3270
3273
  const goTo = (0, import_react52.useCallback)(
@@ -3273,14 +3276,36 @@ var Carousel = (0, import_react52.forwardRef)(function Carousel2({
3273
3276
  setActive(next);
3274
3277
  const node = viewportRef.current;
3275
3278
  if (node) {
3276
- const slide = node.children[domIndexFor(next)];
3279
+ const width = node.clientWidth;
3280
+ if (isLooping && wrapInFlightRef.current !== null && width > 0) {
3281
+ const rebaseTarget = wrapInFlightRef.current === N + 1 ? 0 : wrapInFlightRef.current === 0 ? N + 1 : null;
3282
+ if (rebaseTarget !== null) {
3283
+ const rebaseSlide = node.children[rebaseTarget];
3284
+ if (rebaseSlide) {
3285
+ internalScrollRef.current = true;
3286
+ rebaseSlide.scrollIntoView({
3287
+ behavior: "instant",
3288
+ block: "nearest",
3289
+ inline: "start"
3290
+ });
3291
+ }
3292
+ }
3293
+ wrapInFlightRef.current = null;
3294
+ }
3295
+ const isNextWrap = loopMode === "circular" && activeIdx === N - 1 && i === activeIdx + 1;
3296
+ const isPrevWrap = loopMode === "circular" && activeIdx === 0 && i === activeIdx - 1;
3297
+ const targetDom = isNextWrap ? N + 1 : isPrevWrap ? 0 : domIndexFor(next);
3298
+ const slide = node.children[targetDom];
3277
3299
  if (slide) {
3278
3300
  internalScrollRef.current = true;
3301
+ goToInProgressRef.current = true;
3302
+ if (isNextWrap) wrapInFlightRef.current = N + 1;
3303
+ else if (isPrevWrap) wrapInFlightRef.current = 0;
3279
3304
  slide.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start" });
3280
3305
  }
3281
3306
  }
3282
3307
  },
3283
- [N, isLooping, domIndexFor, setActive]
3308
+ [N, isLooping, loopMode, domIndexFor, setActive, activeIdx]
3284
3309
  );
3285
3310
  (0, import_react52.useEffect)(() => {
3286
3311
  const node = viewportRef.current;
@@ -3290,32 +3315,54 @@ var Carousel = (0, import_react52.forwardRef)(function Carousel2({
3290
3315
  if (width === 0) return;
3291
3316
  const domIdx = Math.round(node.scrollLeft / width);
3292
3317
  if (!isLooping) {
3318
+ if (goToInProgressRef.current) {
3319
+ if (domIdx === activeIdx) goToInProgressRef.current = false;
3320
+ return;
3321
+ }
3293
3322
  if (domIdx !== activeIdx) setActive(domIdx);
3294
3323
  return;
3295
3324
  }
3296
3325
  if (domIdx === 0) {
3326
+ if (goToInProgressRef.current && node.scrollLeft > 1) return;
3297
3327
  const realTwin = node.children[N];
3298
3328
  if (realTwin) {
3299
3329
  internalScrollRef.current = true;
3300
3330
  realTwin.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
3301
3331
  }
3302
3332
  if (activeIdx !== N - 1) setActive(N - 1);
3333
+ goToInProgressRef.current = false;
3334
+ wrapInFlightRef.current = null;
3303
3335
  return;
3304
3336
  }
3305
3337
  if (domIdx === N + 1) {
3338
+ if (goToInProgressRef.current && node.scrollLeft < (N + 1) * width - 1) return;
3306
3339
  const realTwin = node.children[1];
3307
3340
  if (realTwin) {
3308
3341
  internalScrollRef.current = true;
3309
3342
  realTwin.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
3310
3343
  }
3311
3344
  if (activeIdx !== 0) setActive(0);
3345
+ goToInProgressRef.current = false;
3346
+ wrapInFlightRef.current = null;
3312
3347
  return;
3313
3348
  }
3314
3349
  const realIdx = domIdx - 1;
3350
+ if (goToInProgressRef.current) {
3351
+ if (realIdx === activeIdx) goToInProgressRef.current = false;
3352
+ return;
3353
+ }
3315
3354
  if (realIdx !== activeIdx) setActive(realIdx);
3316
3355
  };
3356
+ const onPointerDown = () => {
3357
+ goToInProgressRef.current = false;
3358
+ wrapInFlightRef.current = null;
3359
+ };
3317
3360
  node.addEventListener("scroll", onScroll, { passive: true });
3318
- return () => node.removeEventListener("scroll", onScroll);
3361
+ node.addEventListener("pointerdown", onPointerDown, { passive: true });
3362
+ return () => {
3363
+ node.removeEventListener("scroll", onScroll);
3364
+ node.removeEventListener("pointerdown", onPointerDown);
3365
+ };
3319
3366
  }, [activeIdx, isLooping, N, setActive]);
3320
3367
  (0, import_react52.useEffect)(() => {
3321
3368
  if (internalScrollRef.current) {
@@ -3451,19 +3498,32 @@ var Carousel = (0, import_react52.forwardRef)(function Carousel2({
3451
3498
  }
3452
3499
  )
3453
3500
  ] }),
3454
- renderThumbnail && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "mt-2 flex gap-2 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3455
- "button",
3456
- {
3457
- type: "button",
3458
- "aria-label": `Show slide ${i + 1}`,
3459
- onClick: () => goTo(i),
3460
- className: cn(
3461
- "shrink-0 cursor-pointer overflow-hidden rounded transition-opacity",
3462
- i === activeIdx ? "ring-accent opacity-100 ring-2" : "opacity-60 hover:opacity-100"
3463
- ),
3464
- children: renderThumbnail(item, i)
3465
- },
3466
- i
3501
+ renderThumbnail && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "-mx-0.5 mt-1.5 flex gap-2 overflow-x-auto p-0.5 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", children: items.map((item, i) => (
3502
+ // The active ring is applied to the rendered thumbnail (the
3503
+ // button's first child) rather than the button itself, so it
3504
+ // traces whatever border-radius the consumer's thumbnail
3505
+ // already has. Picking a fixed radius here would always be
3506
+ // wrong for one consumer or another — `rounded-lg` thumbs got
3507
+ // a 4px-radius ring; future thumbs could be circular or
3508
+ // square. `box-shadow` (what `ring-2` compiles to) follows
3509
+ // the child's `border-radius` automatically, so this is
3510
+ // self-adjusting.
3511
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3512
+ "button",
3513
+ {
3514
+ type: "button",
3515
+ "aria-label": `Show slide ${i + 1}`,
3516
+ onClick: () => goTo(i),
3517
+ "data-active": i === activeIdx ? "true" : void 0,
3518
+ className: cn(
3519
+ "shrink-0 cursor-pointer transition-opacity",
3520
+ "[&[data-active]>*]:ring-accent [&[data-active]>*]:ring-2",
3521
+ i === activeIdx ? "opacity-100" : "opacity-60 hover:opacity-100"
3522
+ ),
3523
+ children: renderThumbnail(item, i)
3524
+ },
3525
+ i
3526
+ )
3467
3527
  )) })
3468
3528
  ]
3469
3529
  }