@ship-it-ui/ui 0.0.10 → 0.0.11
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 +128 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -2
- package/dist/index.d.ts +31 -2
- package/dist/index.js +129 -38
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1218,7 +1218,8 @@ declare const Crumb: react.ForwardRefExoticComponent<CrumbProps & react.RefAttri
|
|
|
1218
1218
|
* behavior; no library.
|
|
1219
1219
|
*
|
|
1220
1220
|
* Pass an array of `items` and a `renderItem` function — the carousel
|
|
1221
|
-
* handles snapping, active-index tracking, and keyboard nav.
|
|
1221
|
+
* handles snapping, active-index tracking, and keyboard nav. Set
|
|
1222
|
+
* `loop` to make arrows / dots / native swipe wrap continuously.
|
|
1222
1223
|
*/
|
|
1223
1224
|
interface CarouselProps<T = unknown> extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
1224
1225
|
/** Slide data. */
|
|
@@ -1239,6 +1240,13 @@ interface CarouselProps<T = unknown> extends Omit<HTMLAttributes<HTMLDivElement>
|
|
|
1239
1240
|
showDots?: boolean;
|
|
1240
1241
|
/** When false, hides the prev/next arrows. Default `true`. */
|
|
1241
1242
|
showArrows?: boolean;
|
|
1243
|
+
/**
|
|
1244
|
+
* Wrap arrows / dots / native swipe past the boundaries. Default `false`.
|
|
1245
|
+
* When `true`, hidden clones of the first / last slide bracket the real
|
|
1246
|
+
* items so swipe past the end jumps invisibly to the other side.
|
|
1247
|
+
* `onIndexChange` still only emits real indices in `0..items.length - 1`.
|
|
1248
|
+
*/
|
|
1249
|
+
loop?: boolean;
|
|
1242
1250
|
/** Accessible label for the carousel region. */
|
|
1243
1251
|
'aria-label'?: string;
|
|
1244
1252
|
}
|
|
@@ -1495,7 +1503,8 @@ declare const DateRangePicker: react.ForwardRefExoticComponent<DateRangePickerPr
|
|
|
1495
1503
|
/**
|
|
1496
1504
|
* Lightbox — fullscreen photo viewer. Built on Radix Dialog (reuses the
|
|
1497
1505
|
* focus trap, portal, and escape-to-close). Adds keyboard ←/→ navigation
|
|
1498
|
-
* between items and a counter overlay.
|
|
1506
|
+
* between items and a counter overlay. Set `loop` to wrap navigation
|
|
1507
|
+
* past the boundaries.
|
|
1499
1508
|
*/
|
|
1500
1509
|
interface LightboxProps {
|
|
1501
1510
|
open?: boolean;
|
|
@@ -1511,6 +1520,13 @@ interface LightboxProps {
|
|
|
1511
1520
|
defaultIndex?: number;
|
|
1512
1521
|
/** Fires when the index changes. */
|
|
1513
1522
|
onIndexChange?: (index: number) => void;
|
|
1523
|
+
/**
|
|
1524
|
+
* Wrap prev / next (buttons and ←/→ keys) past the boundaries. Default
|
|
1525
|
+
* `false`. When `true`, "next" on the last item goes to the first and
|
|
1526
|
+
* vice versa, and the arrow buttons never disable while there's more
|
|
1527
|
+
* than one item.
|
|
1528
|
+
*/
|
|
1529
|
+
loop?: boolean;
|
|
1514
1530
|
/** Accessible title (visually hidden). */
|
|
1515
1531
|
title?: ReactNode;
|
|
1516
1532
|
}
|
|
@@ -1561,6 +1577,12 @@ interface ListingCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'childre
|
|
|
1561
1577
|
* placeholders that follow `currentColor`) or non-image slides.
|
|
1562
1578
|
*/
|
|
1563
1579
|
renderPhoto?: (src: string, index: number) => ReactNode;
|
|
1580
|
+
/**
|
|
1581
|
+
* Wrap the photo carousel past the boundaries (next from the last
|
|
1582
|
+
* photo goes to the first). Default `true` — marketplace photo
|
|
1583
|
+
* browsing expects looping. Pass `false` to restore stop-at-end.
|
|
1584
|
+
*/
|
|
1585
|
+
loop?: boolean;
|
|
1564
1586
|
/** Listing title — e.g. "2023 Tesla Model 3". */
|
|
1565
1587
|
title: ReactNode;
|
|
1566
1588
|
/** Optional eyebrow text above the title (location, vehicle type). */
|
|
@@ -1715,6 +1737,13 @@ interface ListingDetailProps {
|
|
|
1715
1737
|
* placeholders or non-image slides.
|
|
1716
1738
|
*/
|
|
1717
1739
|
renderPhoto?: (src: string, index: number, mode: 'gallery' | 'lightbox') => ReactNode;
|
|
1740
|
+
/**
|
|
1741
|
+
* Wrap the gallery carousel and the fullscreen lightbox past the
|
|
1742
|
+
* boundaries (next from the last photo goes to the first). Default
|
|
1743
|
+
* `true` — marketplace photo browsing expects looping. One prop
|
|
1744
|
+
* drives both surfaces.
|
|
1745
|
+
*/
|
|
1746
|
+
loop?: boolean;
|
|
1718
1747
|
/** Listing title — e.g. "2023 Tesla Model 3". */
|
|
1719
1748
|
title: ReactNode;
|
|
1720
1749
|
/** Optional eyebrow above the title — vehicle type, location. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1218,7 +1218,8 @@ declare const Crumb: react.ForwardRefExoticComponent<CrumbProps & react.RefAttri
|
|
|
1218
1218
|
* behavior; no library.
|
|
1219
1219
|
*
|
|
1220
1220
|
* Pass an array of `items` and a `renderItem` function — the carousel
|
|
1221
|
-
* handles snapping, active-index tracking, and keyboard nav.
|
|
1221
|
+
* handles snapping, active-index tracking, and keyboard nav. Set
|
|
1222
|
+
* `loop` to make arrows / dots / native swipe wrap continuously.
|
|
1222
1223
|
*/
|
|
1223
1224
|
interface CarouselProps<T = unknown> extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
1224
1225
|
/** Slide data. */
|
|
@@ -1239,6 +1240,13 @@ interface CarouselProps<T = unknown> extends Omit<HTMLAttributes<HTMLDivElement>
|
|
|
1239
1240
|
showDots?: boolean;
|
|
1240
1241
|
/** When false, hides the prev/next arrows. Default `true`. */
|
|
1241
1242
|
showArrows?: boolean;
|
|
1243
|
+
/**
|
|
1244
|
+
* Wrap arrows / dots / native swipe past the boundaries. Default `false`.
|
|
1245
|
+
* When `true`, hidden clones of the first / last slide bracket the real
|
|
1246
|
+
* items so swipe past the end jumps invisibly to the other side.
|
|
1247
|
+
* `onIndexChange` still only emits real indices in `0..items.length - 1`.
|
|
1248
|
+
*/
|
|
1249
|
+
loop?: boolean;
|
|
1242
1250
|
/** Accessible label for the carousel region. */
|
|
1243
1251
|
'aria-label'?: string;
|
|
1244
1252
|
}
|
|
@@ -1495,7 +1503,8 @@ declare const DateRangePicker: react.ForwardRefExoticComponent<DateRangePickerPr
|
|
|
1495
1503
|
/**
|
|
1496
1504
|
* Lightbox — fullscreen photo viewer. Built on Radix Dialog (reuses the
|
|
1497
1505
|
* focus trap, portal, and escape-to-close). Adds keyboard ←/→ navigation
|
|
1498
|
-
* between items and a counter overlay.
|
|
1506
|
+
* between items and a counter overlay. Set `loop` to wrap navigation
|
|
1507
|
+
* past the boundaries.
|
|
1499
1508
|
*/
|
|
1500
1509
|
interface LightboxProps {
|
|
1501
1510
|
open?: boolean;
|
|
@@ -1511,6 +1520,13 @@ interface LightboxProps {
|
|
|
1511
1520
|
defaultIndex?: number;
|
|
1512
1521
|
/** Fires when the index changes. */
|
|
1513
1522
|
onIndexChange?: (index: number) => void;
|
|
1523
|
+
/**
|
|
1524
|
+
* Wrap prev / next (buttons and ←/→ keys) past the boundaries. Default
|
|
1525
|
+
* `false`. When `true`, "next" on the last item goes to the first and
|
|
1526
|
+
* vice versa, and the arrow buttons never disable while there's more
|
|
1527
|
+
* than one item.
|
|
1528
|
+
*/
|
|
1529
|
+
loop?: boolean;
|
|
1514
1530
|
/** Accessible title (visually hidden). */
|
|
1515
1531
|
title?: ReactNode;
|
|
1516
1532
|
}
|
|
@@ -1561,6 +1577,12 @@ interface ListingCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'childre
|
|
|
1561
1577
|
* placeholders that follow `currentColor`) or non-image slides.
|
|
1562
1578
|
*/
|
|
1563
1579
|
renderPhoto?: (src: string, index: number) => ReactNode;
|
|
1580
|
+
/**
|
|
1581
|
+
* Wrap the photo carousel past the boundaries (next from the last
|
|
1582
|
+
* photo goes to the first). Default `true` — marketplace photo
|
|
1583
|
+
* browsing expects looping. Pass `false` to restore stop-at-end.
|
|
1584
|
+
*/
|
|
1585
|
+
loop?: boolean;
|
|
1564
1586
|
/** Listing title — e.g. "2023 Tesla Model 3". */
|
|
1565
1587
|
title: ReactNode;
|
|
1566
1588
|
/** Optional eyebrow text above the title (location, vehicle type). */
|
|
@@ -1715,6 +1737,13 @@ interface ListingDetailProps {
|
|
|
1715
1737
|
* placeholders or non-image slides.
|
|
1716
1738
|
*/
|
|
1717
1739
|
renderPhoto?: (src: string, index: number, mode: 'gallery' | 'lightbox') => ReactNode;
|
|
1740
|
+
/**
|
|
1741
|
+
* Wrap the gallery carousel and the fullscreen lightbox past the
|
|
1742
|
+
* boundaries (next from the last photo goes to the first). Default
|
|
1743
|
+
* `true` — marketplace photo browsing expects looping. One prop
|
|
1744
|
+
* drives both surfaces.
|
|
1745
|
+
*/
|
|
1746
|
+
loop?: boolean;
|
|
1718
1747
|
/** Listing title — e.g. "2023 Tesla Model 3". */
|
|
1719
1748
|
title: ReactNode;
|
|
1720
1749
|
/** Optional eyebrow above the title — vehicle type, location. */
|
package/dist/index.js
CHANGED
|
@@ -3080,6 +3080,7 @@ import {
|
|
|
3080
3080
|
forwardRef as forwardRef44,
|
|
3081
3081
|
useCallback as useCallback9,
|
|
3082
3082
|
useEffect as useEffect8,
|
|
3083
|
+
useLayoutEffect as useLayoutEffect2,
|
|
3083
3084
|
useRef as useRef8
|
|
3084
3085
|
} from "react";
|
|
3085
3086
|
import { Fragment, jsx as jsx45, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
@@ -3093,27 +3094,36 @@ var Carousel = forwardRef44(function Carousel2({
|
|
|
3093
3094
|
aspectRatio = 16 / 10,
|
|
3094
3095
|
showDots = true,
|
|
3095
3096
|
showArrows = true,
|
|
3097
|
+
loop = false,
|
|
3096
3098
|
className,
|
|
3097
3099
|
"aria-label": ariaLabel = "Carousel",
|
|
3098
3100
|
...props
|
|
3099
3101
|
}, ref) {
|
|
3102
|
+
const N = items.length;
|
|
3103
|
+
const isLooping = loop && N > 1;
|
|
3100
3104
|
const [active, setActive] = useControllableState({
|
|
3101
3105
|
value: indexProp,
|
|
3102
3106
|
defaultValue: defaultIndex ?? 0,
|
|
3103
3107
|
onChange: onIndexChange
|
|
3104
3108
|
});
|
|
3105
3109
|
const viewportRef = useRef8(null);
|
|
3110
|
+
const internalScrollRef = useRef8(false);
|
|
3111
|
+
const activeIdx = active ?? 0;
|
|
3112
|
+
const domIndexFor = useCallback9((real) => isLooping ? real + 1 : real, [isLooping]);
|
|
3106
3113
|
const goTo = useCallback9(
|
|
3107
3114
|
(i) => {
|
|
3108
|
-
const
|
|
3109
|
-
setActive(
|
|
3115
|
+
const next = isLooping ? (i % N + N) % N : Math.max(0, Math.min(N - 1, i));
|
|
3116
|
+
setActive(next);
|
|
3110
3117
|
const node = viewportRef.current;
|
|
3111
3118
|
if (node) {
|
|
3112
|
-
const slide = node.children[
|
|
3113
|
-
slide
|
|
3119
|
+
const slide = node.children[domIndexFor(next)];
|
|
3120
|
+
if (slide) {
|
|
3121
|
+
internalScrollRef.current = true;
|
|
3122
|
+
slide.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start" });
|
|
3123
|
+
}
|
|
3114
3124
|
}
|
|
3115
3125
|
},
|
|
3116
|
-
[
|
|
3126
|
+
[N, isLooping, domIndexFor, setActive]
|
|
3117
3127
|
);
|
|
3118
3128
|
useEffect8(() => {
|
|
3119
3129
|
const node = viewportRef.current;
|
|
@@ -3121,13 +3131,59 @@ var Carousel = forwardRef44(function Carousel2({
|
|
|
3121
3131
|
const onScroll = () => {
|
|
3122
3132
|
const width = node.clientWidth;
|
|
3123
3133
|
if (width === 0) return;
|
|
3124
|
-
const
|
|
3125
|
-
if (
|
|
3134
|
+
const domIdx = Math.round(node.scrollLeft / width);
|
|
3135
|
+
if (!isLooping) {
|
|
3136
|
+
if (domIdx !== activeIdx) setActive(domIdx);
|
|
3137
|
+
return;
|
|
3138
|
+
}
|
|
3139
|
+
if (domIdx === 0) {
|
|
3140
|
+
const realTwin = node.children[N];
|
|
3141
|
+
if (realTwin) {
|
|
3142
|
+
internalScrollRef.current = true;
|
|
3143
|
+
realTwin.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
|
|
3144
|
+
}
|
|
3145
|
+
if (activeIdx !== N - 1) setActive(N - 1);
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
if (domIdx === N + 1) {
|
|
3149
|
+
const realTwin = node.children[1];
|
|
3150
|
+
if (realTwin) {
|
|
3151
|
+
internalScrollRef.current = true;
|
|
3152
|
+
realTwin.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
|
|
3153
|
+
}
|
|
3154
|
+
if (activeIdx !== 0) setActive(0);
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
const realIdx = domIdx - 1;
|
|
3158
|
+
if (realIdx !== activeIdx) setActive(realIdx);
|
|
3126
3159
|
};
|
|
3127
3160
|
node.addEventListener("scroll", onScroll, { passive: true });
|
|
3128
3161
|
return () => node.removeEventListener("scroll", onScroll);
|
|
3129
|
-
}, [
|
|
3130
|
-
|
|
3162
|
+
}, [activeIdx, isLooping, N, setActive]);
|
|
3163
|
+
useEffect8(() => {
|
|
3164
|
+
if (internalScrollRef.current) {
|
|
3165
|
+
internalScrollRef.current = false;
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
const node = viewportRef.current;
|
|
3169
|
+
if (!node) return;
|
|
3170
|
+
const width = node.clientWidth;
|
|
3171
|
+
if (width === 0) return;
|
|
3172
|
+
const targetDom = domIndexFor(activeIdx);
|
|
3173
|
+
const currentDom = Math.round(node.scrollLeft / width);
|
|
3174
|
+
if (currentDom === targetDom) return;
|
|
3175
|
+
const slide = node.children[targetDom];
|
|
3176
|
+
slide?.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
|
|
3177
|
+
}, [activeIdx, domIndexFor]);
|
|
3178
|
+
useLayoutEffect2(() => {
|
|
3179
|
+
if (!isLooping) return;
|
|
3180
|
+
const node = viewportRef.current;
|
|
3181
|
+
if (!node) return;
|
|
3182
|
+
const slide = node.children[domIndexFor(activeIdx)];
|
|
3183
|
+
if (!slide) return;
|
|
3184
|
+
internalScrollRef.current = true;
|
|
3185
|
+
slide.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
|
|
3186
|
+
}, [isLooping]);
|
|
3131
3187
|
return /* @__PURE__ */ jsxs38(
|
|
3132
3188
|
"div",
|
|
3133
3189
|
{
|
|
@@ -3139,34 +3195,58 @@ var Carousel = forwardRef44(function Carousel2({
|
|
|
3139
3195
|
...props,
|
|
3140
3196
|
children: [
|
|
3141
3197
|
/* @__PURE__ */ jsxs38("div", { className: "relative overflow-hidden rounded-md", children: [
|
|
3142
|
-
/* @__PURE__ */
|
|
3198
|
+
/* @__PURE__ */ jsxs38(
|
|
3143
3199
|
"div",
|
|
3144
3200
|
{
|
|
3145
3201
|
ref: viewportRef,
|
|
3146
3202
|
className: "flex w-full snap-x snap-mandatory overflow-x-auto scroll-smooth [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
3147
3203
|
"aria-live": "polite",
|
|
3148
|
-
children:
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3204
|
+
children: [
|
|
3205
|
+
isLooping && /* @__PURE__ */ jsx45(
|
|
3206
|
+
"div",
|
|
3207
|
+
{
|
|
3208
|
+
"aria-hidden": "true",
|
|
3209
|
+
tabIndex: -1,
|
|
3210
|
+
className: "w-full shrink-0 snap-start",
|
|
3211
|
+
style: { aspectRatio: String(aspectRatio) },
|
|
3212
|
+
children: renderItem(items[N - 1], N - 1)
|
|
3213
|
+
},
|
|
3214
|
+
"clone-start"
|
|
3215
|
+
),
|
|
3216
|
+
items.map((item, i) => /* @__PURE__ */ jsx45(
|
|
3217
|
+
"div",
|
|
3218
|
+
{
|
|
3219
|
+
className: "w-full shrink-0 snap-start",
|
|
3220
|
+
style: { aspectRatio: String(aspectRatio) },
|
|
3221
|
+
role: "group",
|
|
3222
|
+
"aria-roledescription": "slide",
|
|
3223
|
+
"aria-label": `${i + 1} of ${N}`,
|
|
3224
|
+
children: renderItem(item, i)
|
|
3225
|
+
},
|
|
3226
|
+
i
|
|
3227
|
+
)),
|
|
3228
|
+
isLooping && /* @__PURE__ */ jsx45(
|
|
3229
|
+
"div",
|
|
3230
|
+
{
|
|
3231
|
+
"aria-hidden": "true",
|
|
3232
|
+
tabIndex: -1,
|
|
3233
|
+
className: "w-full shrink-0 snap-start",
|
|
3234
|
+
style: { aspectRatio: String(aspectRatio) },
|
|
3235
|
+
children: renderItem(items[0], 0)
|
|
3236
|
+
},
|
|
3237
|
+
"clone-end"
|
|
3238
|
+
)
|
|
3239
|
+
]
|
|
3160
3240
|
}
|
|
3161
3241
|
),
|
|
3162
|
-
showArrows &&
|
|
3242
|
+
showArrows && N > 1 && /* @__PURE__ */ jsxs38(Fragment, { children: [
|
|
3163
3243
|
/* @__PURE__ */ jsx45(
|
|
3164
3244
|
"button",
|
|
3165
3245
|
{
|
|
3166
3246
|
type: "button",
|
|
3167
3247
|
"aria-label": "Previous slide",
|
|
3168
3248
|
onClick: () => goTo(activeIdx - 1),
|
|
3169
|
-
disabled: activeIdx === 0,
|
|
3249
|
+
disabled: !isLooping && activeIdx === 0,
|
|
3170
3250
|
className: "bg-panel/85 border-border text-text hover:bg-panel absolute top-1/2 left-2 inline-grid h-9 w-9 -translate-y-1/2 cursor-pointer place-items-center rounded-full border shadow-md backdrop-blur disabled:cursor-not-allowed disabled:opacity-40",
|
|
3171
3251
|
children: /* @__PURE__ */ jsx45(IconGlyph4, { name: "caretLeft", size: 16 })
|
|
3172
3252
|
}
|
|
@@ -3177,13 +3257,13 @@ var Carousel = forwardRef44(function Carousel2({
|
|
|
3177
3257
|
type: "button",
|
|
3178
3258
|
"aria-label": "Next slide",
|
|
3179
3259
|
onClick: () => goTo(activeIdx + 1),
|
|
3180
|
-
disabled: activeIdx ===
|
|
3260
|
+
disabled: !isLooping && activeIdx === N - 1,
|
|
3181
3261
|
className: "bg-panel/85 border-border text-text hover:bg-panel absolute top-1/2 right-2 inline-grid h-9 w-9 -translate-y-1/2 cursor-pointer place-items-center rounded-full border shadow-md backdrop-blur disabled:cursor-not-allowed disabled:opacity-40",
|
|
3182
3262
|
children: /* @__PURE__ */ jsx45(IconGlyph4, { name: "caretRight", size: 16 })
|
|
3183
3263
|
}
|
|
3184
3264
|
)
|
|
3185
3265
|
] }),
|
|
3186
|
-
showDots &&
|
|
3266
|
+
showDots && N > 1 && /*
|
|
3187
3267
|
* Plain `<button>` + `aria-current` rather than the tabs pattern
|
|
3188
3268
|
* (`role="tablist" / "tab"`). The APG carousel pattern recommends
|
|
3189
3269
|
* this lighter semantic; the viewport's `aria-live="polite"`
|
|
@@ -4256,19 +4336,28 @@ var Lightbox = forwardRef50(function Lightbox2({
|
|
|
4256
4336
|
index,
|
|
4257
4337
|
defaultIndex,
|
|
4258
4338
|
onIndexChange,
|
|
4339
|
+
loop = false,
|
|
4259
4340
|
title = "Photo viewer"
|
|
4260
4341
|
}, ref) {
|
|
4342
|
+
const N = items.length;
|
|
4343
|
+
const isLooping = loop && N > 1;
|
|
4261
4344
|
const [active, setActive] = useControllableState({
|
|
4262
4345
|
value: index,
|
|
4263
4346
|
defaultValue: defaultIndex ?? 0,
|
|
4264
4347
|
onChange: onIndexChange
|
|
4265
4348
|
});
|
|
4266
4349
|
const goPrev = useCallback11(() => {
|
|
4267
|
-
setActive((prev) =>
|
|
4268
|
-
|
|
4350
|
+
setActive((prev) => {
|
|
4351
|
+
const p = prev ?? 0;
|
|
4352
|
+
return isLooping ? (p - 1 + N) % N : Math.max(0, p - 1);
|
|
4353
|
+
});
|
|
4354
|
+
}, [setActive, isLooping, N]);
|
|
4269
4355
|
const goNext = useCallback11(() => {
|
|
4270
|
-
setActive((prev) =>
|
|
4271
|
-
|
|
4356
|
+
setActive((prev) => {
|
|
4357
|
+
const p = prev ?? 0;
|
|
4358
|
+
return isLooping ? (p + 1) % N : Math.min(N - 1, p + 1);
|
|
4359
|
+
});
|
|
4360
|
+
}, [setActive, isLooping, N]);
|
|
4272
4361
|
const onKey = useCallback11(
|
|
4273
4362
|
(e) => {
|
|
4274
4363
|
if (e.key === "ArrowLeft") {
|
|
@@ -4308,7 +4397,7 @@ var Lightbox = forwardRef50(function Lightbox2({
|
|
|
4308
4397
|
type: "button",
|
|
4309
4398
|
"aria-label": "Previous photo",
|
|
4310
4399
|
onClick: goPrev,
|
|
4311
|
-
disabled: activeIdx === 0,
|
|
4400
|
+
disabled: !isLooping && activeIdx === 0,
|
|
4312
4401
|
className: "absolute top-1/2 left-4 inline-grid h-11 w-11 -translate-y-1/2 cursor-pointer place-items-center rounded-full bg-white/10 text-white hover:bg-white/20 disabled:cursor-not-allowed disabled:opacity-40",
|
|
4313
4402
|
children: /* @__PURE__ */ jsx52(IconGlyph6, { name: "caretLeft", size: 20 })
|
|
4314
4403
|
}
|
|
@@ -4319,7 +4408,7 @@ var Lightbox = forwardRef50(function Lightbox2({
|
|
|
4319
4408
|
type: "button",
|
|
4320
4409
|
"aria-label": "Next photo",
|
|
4321
4410
|
onClick: goNext,
|
|
4322
|
-
disabled: activeIdx ===
|
|
4411
|
+
disabled: !isLooping && activeIdx === N - 1,
|
|
4323
4412
|
className: "absolute top-1/2 right-4 inline-grid h-11 w-11 -translate-y-1/2 cursor-pointer place-items-center rounded-full bg-white/10 text-white hover:bg-white/20 disabled:cursor-not-allowed disabled:opacity-40",
|
|
4324
4413
|
children: /* @__PURE__ */ jsx52(IconGlyph6, { name: "caretRight", size: 20 })
|
|
4325
4414
|
}
|
|
@@ -4372,6 +4461,7 @@ var ListingCard = forwardRef51(function ListingCard2({
|
|
|
4372
4461
|
variant = "default",
|
|
4373
4462
|
photos,
|
|
4374
4463
|
renderPhoto,
|
|
4464
|
+
loop = true,
|
|
4375
4465
|
onClick,
|
|
4376
4466
|
hoverEffect,
|
|
4377
4467
|
title,
|
|
@@ -4418,6 +4508,7 @@ var ListingCard = forwardRef51(function ListingCard2({
|
|
|
4418
4508
|
Carousel,
|
|
4419
4509
|
{
|
|
4420
4510
|
items: photos,
|
|
4511
|
+
loop,
|
|
4421
4512
|
...isSpec ? {
|
|
4422
4513
|
index: photoIndex,
|
|
4423
4514
|
onIndexChange: setPhotoIndex,
|
|
@@ -4658,6 +4749,7 @@ var ListingDetail = forwardRef52(function ListingDetail2({
|
|
|
4658
4749
|
onOpenChange,
|
|
4659
4750
|
photos,
|
|
4660
4751
|
renderPhoto,
|
|
4752
|
+
loop = true,
|
|
4661
4753
|
title,
|
|
4662
4754
|
eyebrow,
|
|
4663
4755
|
description,
|
|
@@ -4727,6 +4819,7 @@ var ListingDetail = forwardRef52(function ListingDetail2({
|
|
|
4727
4819
|
items: photos,
|
|
4728
4820
|
index: galleryIndex,
|
|
4729
4821
|
onIndexChange: setGalleryIndex,
|
|
4822
|
+
loop,
|
|
4730
4823
|
...isSpec ? { showDots: false } : {},
|
|
4731
4824
|
"aria-label": typeof title === "string" ? `${title} photos` : "Listing photos",
|
|
4732
4825
|
renderItem: (src, i) => renderPhoto ? renderPhoto(src, i, "gallery") : /* @__PURE__ */ jsx54(
|
|
@@ -4972,6 +5065,7 @@ var ListingDetail = forwardRef52(function ListingDetail2({
|
|
|
4972
5065
|
items: photos,
|
|
4973
5066
|
index: galleryIndex,
|
|
4974
5067
|
onIndexChange: setGalleryIndex,
|
|
5068
|
+
loop,
|
|
4975
5069
|
title: lightboxTitle,
|
|
4976
5070
|
renderItem: (src, i) => renderPhoto ? renderPhoto(src, i, "lightbox") : /* @__PURE__ */ jsx54("img", { src, alt: "", className: "max-h-[88vh] max-w-[92vw] object-contain" })
|
|
4977
5071
|
}
|
|
@@ -7241,7 +7335,6 @@ Topbar.displayName = "Topbar";
|
|
|
7241
7335
|
import {
|
|
7242
7336
|
forwardRef as forwardRef79,
|
|
7243
7337
|
useCallback as useCallback16,
|
|
7244
|
-
useEffect as useEffect16,
|
|
7245
7338
|
useMemo as useMemo7,
|
|
7246
7339
|
useRef as useRef14,
|
|
7247
7340
|
useState as useState21
|
|
@@ -7287,11 +7380,9 @@ var Tree = forwardRef79(function Tree2({
|
|
|
7287
7380
|
return out;
|
|
7288
7381
|
}, [items, expandedSet]);
|
|
7289
7382
|
const [activeId, setActiveId] = useState21(null);
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
}
|
|
7294
|
-
}, [activeId, flatVisible]);
|
|
7383
|
+
if (activeId && !flatVisible.some((f) => f.id === activeId)) {
|
|
7384
|
+
setActiveId(null);
|
|
7385
|
+
}
|
|
7295
7386
|
const tabStopId = useMemo7(() => {
|
|
7296
7387
|
if (activeId && flatVisible.some((f) => f.id === activeId)) return activeId;
|
|
7297
7388
|
if (value && flatVisible.some((f) => f.id === value)) return value;
|