@music-vine/cadence 2.5.1 → 2.6.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/dist/components/index.js
CHANGED
|
@@ -94,6 +94,11 @@ import {
|
|
|
94
94
|
import { PriceTag } from "./price-tag";
|
|
95
95
|
import { RadioGroup, RadioGroupItem } from "./radio-group";
|
|
96
96
|
import { FadeAway, ScrollArea, ScrollBar } from "./scroll-area";
|
|
97
|
+
import {
|
|
98
|
+
ScrollDrum,
|
|
99
|
+
ScrollDrumColumn,
|
|
100
|
+
ScrollDrumGroup
|
|
101
|
+
} from "./scroll-drum";
|
|
97
102
|
import {
|
|
98
103
|
Select,
|
|
99
104
|
SelectContent,
|
|
@@ -233,6 +238,9 @@ export {
|
|
|
233
238
|
RadioGroupItem,
|
|
234
239
|
ScrollArea,
|
|
235
240
|
ScrollBar,
|
|
241
|
+
ScrollDrum,
|
|
242
|
+
ScrollDrumColumn,
|
|
243
|
+
ScrollDrumGroup,
|
|
236
244
|
Select,
|
|
237
245
|
SelectContent,
|
|
238
246
|
SelectContentPopper,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/components/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Cadence UI Components\n *\n * Import components from this module:\n * @example\n * import { Button, Input } from '@music-vine/cadence/ui';\n */\n\n// Accordion\nexport {\n Accordion,\n AccordionContent,\n type AccordionContentProps,\n AccordionItem,\n type AccordionItemProps,\n type AccordionProps,\n AccordionTrigger,\n type AccordionTriggerProps,\n type AccordionVariant,\n} from \"./accordion\";\n\n// Badge\nexport { Badge, type BadgeProps, badgeVariants } from \"./badge\";\n\n// Breadcrumb\nexport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"./breadcrumb\";\n\n// Button\nexport {\n Button,\n type ButtonFontSize,\n type ButtonProps,\n type ButtonSize,\n buttonVariants,\n Loading,\n loadingVariants,\n type ResponsiveButtonFontSize,\n type ResponsiveButtonSize,\n} from \"./button\";\n\n// Card\nexport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"./card\";\n\n// Carousel\nexport {\n Carousel,\n type CarouselApi,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n useCarousel,\n} from \"./carousel\";\nexport { CarouselDots } from \"./carousel-dots\";\n\n// Checkbox\nexport { Checkbox, DummyCheckbox } from \"./checkbox\";\n\n// Context Menu\nexport {\n ContextMenu,\n ContextMenuCheckboxItem,\n ContextMenuContent,\n ContextMenuGroup,\n ContextMenuItem,\n ContextMenuLabel,\n ContextMenuPortal,\n ContextMenuRadioGroup,\n ContextMenuRadioItem,\n ContextMenuSeparator,\n ContextMenuShortcut,\n ContextMenuSub,\n ContextMenuSubContent,\n ContextMenuSubTrigger,\n ContextMenuTrigger,\n} from \"./context-menu\";\n\n// Dialog\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n} from \"./dialog\";\n\n// Drawer\nexport {\n Drawer,\n DrawerClose,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerOverlay,\n DrawerPortal,\n DrawerTitle,\n DrawerTrigger,\n} from \"./drawer\";\n\n// Input\nexport {\n ClearInputButton,\n Input,\n type InputProps,\n inputVariants,\n} from \"./input\";\n\n// Label\nexport { Label } from \"./label\";\n\n// Popover\nexport {\n Popover,\n PopoverAnchor,\n PopoverContent,\n PopoverTrigger,\n} from \"./popover\";\n\n// Price Tag\nexport { PriceTag, type PriceTagProps } from \"./price-tag\";\n\n// Radio Group\nexport { RadioGroup, RadioGroupItem } from \"./radio-group\";\n\n// Scroll Area\nexport { FadeAway, ScrollArea, ScrollBar } from \"./scroll-area\";\n\n// Select\nexport {\n Select,\n SelectContent,\n SelectContentPopper,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n} from \"./select\";\n\n// Separator\nexport { Separator } from \"./separator\";\n\n// Skeleton\nexport { Skeleton, SkeletonFragment } from \"./skeleton\";\n\n// Slider\nexport { Slider, type SliderProps, sliderVariants } from \"./slider\";\n// Stacking Card\nexport {\n StackingCard,\n StackingCardCheck,\n StackingCardContent,\n StackingCardDescription,\n StackingCardGroup,\n StackingCardHeader,\n StackingCardList,\n StackingCardListItem,\n StackingCardTitle,\n} from \"./stacking-card\";\n// Tabs\nexport { Tabs, TabsContent, TabsList, TabsTrigger } from \"./tabs\";\n// Textarea\nexport { Textarea, type TextareaProps, textareaVariants } from \"./textarea\";\n// Toast\nexport {\n type GlobalToastOptions,\n Toaster,\n type ToasterProps,\n toast,\n} from \"./toast\";\n// Toggle Button\nexport { ToggleButton, type ToggleButtonProps } from \"./toggle-button\";\n\n// Typography\nexport {\n Alpha,\n Bravo,\n Charlie,\n Delta,\n Echo,\n Foxtrot,\n Heading,\n type HeadingProps,\n headingVariants,\n List,\n ListItem,\n type ListItemProps,\n ListItemTick,\n type ListProps,\n listVariants,\n Prose,\n Text,\n type TextProps,\n textVariants,\n} from \"./typography\";\n"],
|
|
5
|
-
"mappings": "AASA;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,EAGA;AAAA,OAGK;AAGP,SAAS,OAAwB,qBAAqB;AAGtD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;AAG7B,SAAS,UAAU,qBAAqB;AAGxC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AAGP,SAAS,aAAa;AAGtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,gBAAoC;AAG7C,SAAS,YAAY,sBAAsB;AAG3C,SAAS,UAAU,YAAY,iBAAiB;AAGhD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,iBAAiB;AAG1B,SAAS,UAAU,wBAAwB;AAG3C,SAAS,QAA0B,sBAAsB;AAEzD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,MAAM,aAAa,UAAU,mBAAmB;AAEzD,SAAS,UAA8B,wBAAwB;AAE/D;AAAA,EAEE;AAAA,EAEA;AAAA,OACK;AAEP,SAAS,oBAA4C;AAGrD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;",
|
|
4
|
+
"sourcesContent": ["/**\n * Cadence UI Components\n *\n * Import components from this module:\n * @example\n * import { Button, Input } from '@music-vine/cadence/ui';\n */\n\n// Accordion\nexport {\n Accordion,\n AccordionContent,\n type AccordionContentProps,\n AccordionItem,\n type AccordionItemProps,\n type AccordionProps,\n AccordionTrigger,\n type AccordionTriggerProps,\n type AccordionVariant,\n} from \"./accordion\";\n\n// Badge\nexport { Badge, type BadgeProps, badgeVariants } from \"./badge\";\n\n// Breadcrumb\nexport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"./breadcrumb\";\n\n// Button\nexport {\n Button,\n type ButtonFontSize,\n type ButtonProps,\n type ButtonSize,\n buttonVariants,\n Loading,\n loadingVariants,\n type ResponsiveButtonFontSize,\n type ResponsiveButtonSize,\n} from \"./button\";\n\n// Card\nexport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from \"./card\";\n\n// Carousel\nexport {\n Carousel,\n type CarouselApi,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n useCarousel,\n} from \"./carousel\";\nexport { CarouselDots } from \"./carousel-dots\";\n\n// Checkbox\nexport { Checkbox, DummyCheckbox } from \"./checkbox\";\n\n// Context Menu\nexport {\n ContextMenu,\n ContextMenuCheckboxItem,\n ContextMenuContent,\n ContextMenuGroup,\n ContextMenuItem,\n ContextMenuLabel,\n ContextMenuPortal,\n ContextMenuRadioGroup,\n ContextMenuRadioItem,\n ContextMenuSeparator,\n ContextMenuShortcut,\n ContextMenuSub,\n ContextMenuSubContent,\n ContextMenuSubTrigger,\n ContextMenuTrigger,\n} from \"./context-menu\";\n\n// Dialog\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n} from \"./dialog\";\n\n// Drawer\nexport {\n Drawer,\n DrawerClose,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerOverlay,\n DrawerPortal,\n DrawerTitle,\n DrawerTrigger,\n} from \"./drawer\";\n\n// Input\nexport {\n ClearInputButton,\n Input,\n type InputProps,\n inputVariants,\n} from \"./input\";\n\n// Label\nexport { Label } from \"./label\";\n\n// Popover\nexport {\n Popover,\n PopoverAnchor,\n PopoverContent,\n PopoverTrigger,\n} from \"./popover\";\n\n// Price Tag\nexport { PriceTag, type PriceTagProps } from \"./price-tag\";\n\n// Radio Group\nexport { RadioGroup, RadioGroupItem } from \"./radio-group\";\n\n// Scroll Area\nexport { FadeAway, ScrollArea, ScrollBar } from \"./scroll-area\";\n\n// Scroll Drum\nexport {\n ScrollDrum,\n ScrollDrumColumn,\n type ScrollDrumColumnProps,\n ScrollDrumGroup,\n type ScrollDrumGroupProps,\n type ScrollDrumProps,\n} from \"./scroll-drum\";\n\n// Select\nexport {\n Select,\n SelectContent,\n SelectContentPopper,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n} from \"./select\";\n\n// Separator\nexport { Separator } from \"./separator\";\n\n// Skeleton\nexport { Skeleton, SkeletonFragment } from \"./skeleton\";\n\n// Slider\nexport { Slider, type SliderProps, sliderVariants } from \"./slider\";\n// Stacking Card\nexport {\n StackingCard,\n StackingCardCheck,\n StackingCardContent,\n StackingCardDescription,\n StackingCardGroup,\n StackingCardHeader,\n StackingCardList,\n StackingCardListItem,\n StackingCardTitle,\n} from \"./stacking-card\";\n// Tabs\nexport { Tabs, TabsContent, TabsList, TabsTrigger } from \"./tabs\";\n// Textarea\nexport { Textarea, type TextareaProps, textareaVariants } from \"./textarea\";\n// Toast\nexport {\n type GlobalToastOptions,\n Toaster,\n type ToasterProps,\n toast,\n} from \"./toast\";\n// Toggle Button\nexport { ToggleButton, type ToggleButtonProps } from \"./toggle-button\";\n\n// Typography\nexport {\n Alpha,\n Bravo,\n Charlie,\n Delta,\n Echo,\n Foxtrot,\n Heading,\n type HeadingProps,\n headingVariants,\n List,\n ListItem,\n type ListItemProps,\n ListItemTick,\n type ListProps,\n listVariants,\n Prose,\n Text,\n type TextProps,\n textVariants,\n} from \"./typography\";\n"],
|
|
5
|
+
"mappings": "AASA;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,EAGA;AAAA,OAGK;AAGP,SAAS,OAAwB,qBAAqB;AAGtD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;AAG7B,SAAS,UAAU,qBAAqB;AAGxC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AAGP,SAAS,aAAa;AAGtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,gBAAoC;AAG7C,SAAS,YAAY,sBAAsB;AAG3C,SAAS,UAAU,YAAY,iBAAiB;AAGhD;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,iBAAiB;AAG1B,SAAS,UAAU,wBAAwB;AAG3C,SAAS,QAA0B,sBAAsB;AAEzD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,MAAM,aAAa,UAAU,mBAAmB;AAEzD,SAAS,UAA8B,wBAAwB;AAE/D;AAAA,EAEE;AAAA,EAEA;AAAA,OACK;AAEP,SAAS,oBAA4C;AAGrD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import {
|
|
3
|
+
Children,
|
|
4
|
+
Fragment,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useId,
|
|
8
|
+
useRef,
|
|
9
|
+
useState
|
|
10
|
+
} from "react";
|
|
11
|
+
import { cn } from "../lib/utils";
|
|
12
|
+
const DEFAULT_ITEM_HEIGHT = 52;
|
|
13
|
+
const DEFAULT_VISIBLE = 3;
|
|
14
|
+
const DEFAULT_MAX_CHARS = 4;
|
|
15
|
+
const SNAP_DURATION_MS = 280;
|
|
16
|
+
const WHEEL_SETTLE_MS = 110;
|
|
17
|
+
const COAST_FACTOR = 220 * 0.55;
|
|
18
|
+
const clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
|
|
19
|
+
const pmod = (n, m) => (n % m + m) % m;
|
|
20
|
+
const ScrollDrum = ({
|
|
21
|
+
items,
|
|
22
|
+
value,
|
|
23
|
+
onChange,
|
|
24
|
+
itemHeight = DEFAULT_ITEM_HEIGHT,
|
|
25
|
+
visibleCount = DEFAULT_VISIBLE,
|
|
26
|
+
loop: loopProp = true,
|
|
27
|
+
maxChars = DEFAULT_MAX_CHARS,
|
|
28
|
+
ariaLabel,
|
|
29
|
+
className
|
|
30
|
+
}) => {
|
|
31
|
+
const uid = useId();
|
|
32
|
+
const optionId = (idx) => `${uid}-opt-${idx}`;
|
|
33
|
+
const containerRef = useRef(null);
|
|
34
|
+
const animRef = useRef(0);
|
|
35
|
+
const wheelTimer = useRef(null);
|
|
36
|
+
const dragRef = useRef({
|
|
37
|
+
active: false,
|
|
38
|
+
startY: 0,
|
|
39
|
+
startIndex: 0,
|
|
40
|
+
lastY: 0,
|
|
41
|
+
lastT: 0,
|
|
42
|
+
vy: 0,
|
|
43
|
+
moved: false
|
|
44
|
+
});
|
|
45
|
+
const N = items.length;
|
|
46
|
+
const loop = loopProp && N > 1;
|
|
47
|
+
const truncate = useCallback(
|
|
48
|
+
(label) => label.length > maxChars ? label.slice(0, maxChars) : label,
|
|
49
|
+
[maxChars]
|
|
50
|
+
);
|
|
51
|
+
const initialIndex = Math.max(0, items.indexOf(value));
|
|
52
|
+
const [floatIndex, setFloatIndex] = useState(initialIndex);
|
|
53
|
+
const floatIndexRef = useRef(floatIndex);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
floatIndexRef.current = floatIndex;
|
|
56
|
+
}, [floatIndex]);
|
|
57
|
+
const resolveIndex = useCallback(
|
|
58
|
+
(f) => {
|
|
59
|
+
if (loop) return pmod(Math.round(f), N);
|
|
60
|
+
return clamp(Math.round(f), 0, N - 1);
|
|
61
|
+
},
|
|
62
|
+
[loop, N]
|
|
63
|
+
);
|
|
64
|
+
const animateTo = useCallback(
|
|
65
|
+
(targetFloat) => {
|
|
66
|
+
cancelAnimationFrame(animRef.current);
|
|
67
|
+
const start = performance.now();
|
|
68
|
+
const from = floatIndexRef.current;
|
|
69
|
+
const ease = (t) => 1 - (1 - t) ** 3;
|
|
70
|
+
const step = () => {
|
|
71
|
+
const t = clamp((performance.now() - start) / SNAP_DURATION_MS, 0, 1);
|
|
72
|
+
const v = from + (targetFloat - from) * ease(t);
|
|
73
|
+
setFloatIndex(v);
|
|
74
|
+
if (t < 1) {
|
|
75
|
+
animRef.current = requestAnimationFrame(step);
|
|
76
|
+
} else {
|
|
77
|
+
const final = loop ? pmod(targetFloat, N) : clamp(targetFloat, 0, N - 1);
|
|
78
|
+
setFloatIndex(final);
|
|
79
|
+
const idx = resolveIndex(final);
|
|
80
|
+
const next = items[idx];
|
|
81
|
+
if (next !== void 0 && next !== value) onChange?.(next, idx);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
animRef.current = requestAnimationFrame(step);
|
|
85
|
+
},
|
|
86
|
+
[items, loop, N, onChange, resolveIndex, value]
|
|
87
|
+
);
|
|
88
|
+
const targetIdx = N > 0 ? Math.max(0, items.indexOf(value)) : 0;
|
|
89
|
+
const lastSyncedTargetIdx = useRef(targetIdx);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (targetIdx === lastSyncedTargetIdx.current) return;
|
|
92
|
+
lastSyncedTargetIdx.current = targetIdx;
|
|
93
|
+
if (N === 0) return;
|
|
94
|
+
const cur = floatIndexRef.current;
|
|
95
|
+
let target = targetIdx;
|
|
96
|
+
if (loop) {
|
|
97
|
+
const cycles = Math.round((cur - targetIdx) / N);
|
|
98
|
+
target = targetIdx + cycles * N;
|
|
99
|
+
}
|
|
100
|
+
if (Math.abs(target - cur) > 1e-3) animateTo(target);
|
|
101
|
+
}, [targetIdx]);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
const el = containerRef.current;
|
|
104
|
+
if (!el) return;
|
|
105
|
+
const onWheel = (e) => {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
cancelAnimationFrame(animRef.current);
|
|
108
|
+
const delta = e.deltaY / itemHeight;
|
|
109
|
+
const raw = floatIndexRef.current + delta;
|
|
110
|
+
setFloatIndex(loop ? raw : clamp(raw, 0, N - 1));
|
|
111
|
+
if (wheelTimer.current) clearTimeout(wheelTimer.current);
|
|
112
|
+
wheelTimer.current = setTimeout(() => {
|
|
113
|
+
const cur = floatIndexRef.current;
|
|
114
|
+
const target = loop ? Math.round(cur) : clamp(Math.round(cur), 0, N - 1);
|
|
115
|
+
animateTo(target);
|
|
116
|
+
}, WHEEL_SETTLE_MS);
|
|
117
|
+
};
|
|
118
|
+
el.addEventListener("wheel", onWheel, { passive: false });
|
|
119
|
+
return () => {
|
|
120
|
+
el.removeEventListener("wheel", onWheel);
|
|
121
|
+
if (wheelTimer.current) clearTimeout(wheelTimer.current);
|
|
122
|
+
};
|
|
123
|
+
}, [animateTo, N, itemHeight, loop]);
|
|
124
|
+
useEffect(
|
|
125
|
+
() => () => {
|
|
126
|
+
cancelAnimationFrame(animRef.current);
|
|
127
|
+
},
|
|
128
|
+
[]
|
|
129
|
+
);
|
|
130
|
+
const onPointerDown = (e) => {
|
|
131
|
+
if (e.pointerType === "mouse" && e.button !== 0) return;
|
|
132
|
+
cancelAnimationFrame(animRef.current);
|
|
133
|
+
containerRef.current?.setPointerCapture(e.pointerId);
|
|
134
|
+
dragRef.current = {
|
|
135
|
+
active: true,
|
|
136
|
+
startY: e.clientY,
|
|
137
|
+
startIndex: floatIndexRef.current,
|
|
138
|
+
lastY: e.clientY,
|
|
139
|
+
lastT: performance.now(),
|
|
140
|
+
vy: 0,
|
|
141
|
+
moved: false
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
const onPointerMove = (e) => {
|
|
145
|
+
const d = dragRef.current;
|
|
146
|
+
if (!d.active) return;
|
|
147
|
+
const dy = e.clientY - d.startY;
|
|
148
|
+
if (Math.abs(dy) > 3) d.moved = true;
|
|
149
|
+
const raw = d.startIndex - dy / itemHeight;
|
|
150
|
+
setFloatIndex(loop ? raw : clamp(raw, 0, N - 1));
|
|
151
|
+
const now = performance.now();
|
|
152
|
+
const dt = Math.max(1, now - d.lastT);
|
|
153
|
+
d.vy = (e.clientY - d.lastY) / dt;
|
|
154
|
+
d.lastY = e.clientY;
|
|
155
|
+
d.lastT = now;
|
|
156
|
+
};
|
|
157
|
+
const onPointerUp = (e) => {
|
|
158
|
+
const d = dragRef.current;
|
|
159
|
+
if (!d.active) return;
|
|
160
|
+
d.active = false;
|
|
161
|
+
try {
|
|
162
|
+
containerRef.current?.releasePointerCapture(e.pointerId);
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
const coastPx = d.vy * COAST_FACTOR;
|
|
166
|
+
const projected = floatIndexRef.current - coastPx / itemHeight;
|
|
167
|
+
const target = loop ? Math.round(projected) : clamp(Math.round(projected), 0, N - 1);
|
|
168
|
+
animateTo(target);
|
|
169
|
+
};
|
|
170
|
+
const onKeyDown = (e) => {
|
|
171
|
+
let target = Math.round(floatIndexRef.current);
|
|
172
|
+
if (e.key === "ArrowUp") target -= 1;
|
|
173
|
+
else if (e.key === "ArrowDown") target += 1;
|
|
174
|
+
else if (e.key === "PageUp") target -= 5;
|
|
175
|
+
else if (e.key === "PageDown") target += 5;
|
|
176
|
+
else if (e.key === "Home") {
|
|
177
|
+
const cur = Math.round(floatIndexRef.current);
|
|
178
|
+
target = loop ? cur - pmod(cur, N) : 0;
|
|
179
|
+
} else if (e.key === "End") {
|
|
180
|
+
const cur = Math.round(floatIndexRef.current);
|
|
181
|
+
target = loop ? cur - pmod(cur, N) + N - 1 : N - 1;
|
|
182
|
+
} else return;
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
if (!loop) target = clamp(target, 0, N - 1);
|
|
185
|
+
animateTo(target);
|
|
186
|
+
};
|
|
187
|
+
const halfVisible = Math.floor(visibleCount / 2);
|
|
188
|
+
const windowRadius = halfVisible + 2;
|
|
189
|
+
const centerInt = Math.round(floatIndex);
|
|
190
|
+
const visibleItems = [];
|
|
191
|
+
for (let off = -windowRadius; off <= windowRadius; off++) {
|
|
192
|
+
const rawIdx = centerInt + off;
|
|
193
|
+
if (!loop && (rawIdx < 0 || rawIdx >= N)) continue;
|
|
194
|
+
const itemIdx = pmod(rawIdx, N);
|
|
195
|
+
const label = items[itemIdx];
|
|
196
|
+
if (label === void 0) continue;
|
|
197
|
+
visibleItems.push({
|
|
198
|
+
key: String(rawIdx),
|
|
199
|
+
itemIdx,
|
|
200
|
+
offsetSlot: rawIdx,
|
|
201
|
+
label
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const rowOffset = (slot) => (slot - floatIndex) * itemHeight;
|
|
205
|
+
const selectedIndex = N > 0 ? resolveIndex(floatIndex) : 0;
|
|
206
|
+
if (N === 0) {
|
|
207
|
+
return /* @__PURE__ */ jsx(
|
|
208
|
+
"div",
|
|
209
|
+
{
|
|
210
|
+
"aria-label": ariaLabel,
|
|
211
|
+
className: cn(
|
|
212
|
+
"relative h-full w-full overflow-hidden bg-white dark:bg-gray-900",
|
|
213
|
+
className
|
|
214
|
+
),
|
|
215
|
+
role: "listbox"
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return /* @__PURE__ */ jsxs(
|
|
220
|
+
"div",
|
|
221
|
+
{
|
|
222
|
+
"aria-activedescendant": optionId(selectedIndex),
|
|
223
|
+
"aria-label": ariaLabel,
|
|
224
|
+
"aria-orientation": "vertical",
|
|
225
|
+
className: cn(
|
|
226
|
+
"relative h-full w-full cursor-grab touch-none select-none overflow-hidden bg-white outline-none active:cursor-grabbing focus-visible:[box-shadow:inset_0_0_0_2px_var(--brand-primary)] dark:bg-gray-900",
|
|
227
|
+
className
|
|
228
|
+
),
|
|
229
|
+
onKeyDown,
|
|
230
|
+
onPointerCancel: onPointerUp,
|
|
231
|
+
onPointerDown,
|
|
232
|
+
onPointerMove,
|
|
233
|
+
onPointerUp,
|
|
234
|
+
ref: containerRef,
|
|
235
|
+
role: "listbox",
|
|
236
|
+
style: { "--sd-item-h": `${itemHeight}px` },
|
|
237
|
+
tabIndex: 0,
|
|
238
|
+
children: [
|
|
239
|
+
/* @__PURE__ */ jsx(
|
|
240
|
+
"div",
|
|
241
|
+
{
|
|
242
|
+
"aria-hidden": "true",
|
|
243
|
+
className: "pointer-events-none absolute inset-x-0 top-[-1px] z-[2] h-9 bg-gradient-to-b from-white from-[12%] to-transparent dark:from-gray-900"
|
|
244
|
+
}
|
|
245
|
+
),
|
|
246
|
+
/* @__PURE__ */ jsx(
|
|
247
|
+
"div",
|
|
248
|
+
{
|
|
249
|
+
"aria-hidden": "true",
|
|
250
|
+
className: "pointer-events-none absolute inset-x-0 bottom-[-1px] z-[2] h-9 bg-gradient-to-t from-white from-[12%] to-transparent dark:from-gray-900"
|
|
251
|
+
}
|
|
252
|
+
),
|
|
253
|
+
/* @__PURE__ */ jsx(
|
|
254
|
+
"div",
|
|
255
|
+
{
|
|
256
|
+
"aria-hidden": "true",
|
|
257
|
+
className: "pointer-events-none absolute inset-x-2 top-1/2 z-0 h-[41px] -translate-y-1/2 rounded-sm border border-brand-primary bg-brand-secondary"
|
|
258
|
+
}
|
|
259
|
+
),
|
|
260
|
+
/* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0", children: visibleItems.map(({ key, itemIdx, offsetSlot, label }) => {
|
|
261
|
+
const isSelected = itemIdx === selectedIndex && Math.abs(offsetSlot - floatIndex) <= 0.5;
|
|
262
|
+
return /* @__PURE__ */ jsx(
|
|
263
|
+
"button",
|
|
264
|
+
{
|
|
265
|
+
"aria-selected": isSelected,
|
|
266
|
+
className: cn(
|
|
267
|
+
"pointer-events-auto absolute top-1/2 left-1/2 z-[1] m-0 box-border flex h-[var(--sd-item-h,52px)] w-full cursor-pointer items-center justify-center overflow-hidden border-0 bg-transparent px-1.5 py-0 transition-colors duration-150",
|
|
268
|
+
isSelected ? "text-brand-primary-hover dark:text-white" : "text-gray-600 dark:text-gray-300"
|
|
269
|
+
),
|
|
270
|
+
id: isSelected ? optionId(itemIdx) : void 0,
|
|
271
|
+
onClick: () => {
|
|
272
|
+
if (dragRef.current.moved) return;
|
|
273
|
+
animateTo(offsetSlot);
|
|
274
|
+
},
|
|
275
|
+
role: "option",
|
|
276
|
+
style: {
|
|
277
|
+
transform: `translate3d(-50%, calc(-50% + ${rowOffset(offsetSlot)}px), 0)`
|
|
278
|
+
},
|
|
279
|
+
tabIndex: -1,
|
|
280
|
+
type: "button",
|
|
281
|
+
children: /* @__PURE__ */ jsx("span", { className: "inline-block whitespace-nowrap text-center font-sans font-semibold text-[30px] leading-9 tracking-[-1px] [font-feature-settings:'ss03']", children: truncate(label) })
|
|
282
|
+
},
|
|
283
|
+
key
|
|
284
|
+
);
|
|
285
|
+
}) })
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
};
|
|
290
|
+
const ScrollDrumGroup = ({
|
|
291
|
+
children,
|
|
292
|
+
separator = ":",
|
|
293
|
+
showSeparators = true,
|
|
294
|
+
className
|
|
295
|
+
}) => {
|
|
296
|
+
const cols = Children.toArray(children);
|
|
297
|
+
return /* @__PURE__ */ jsx(
|
|
298
|
+
"div",
|
|
299
|
+
{
|
|
300
|
+
className: cn(
|
|
301
|
+
"inline-flex flex-row items-center justify-center rounded-lg bg-gray-100 px-8 pt-6 pb-4 dark:bg-gray-800",
|
|
302
|
+
className
|
|
303
|
+
),
|
|
304
|
+
children: cols.map((col, idx) => (
|
|
305
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: columns are static and not reordered
|
|
306
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
307
|
+
col,
|
|
308
|
+
idx < cols.length - 1 && /* @__PURE__ */ jsx(
|
|
309
|
+
"div",
|
|
310
|
+
{
|
|
311
|
+
"aria-hidden": "true",
|
|
312
|
+
className: "flex h-[120px] w-8 flex-none items-center justify-center self-start",
|
|
313
|
+
children: showSeparators && /* @__PURE__ */ jsx("span", { className: "text-center font-sans font-semibold text-[36px] text-gray-600 leading-10 tracking-[-1.5px] [font-feature-settings:'ss03'] dark:text-gray-400", children: separator })
|
|
314
|
+
}
|
|
315
|
+
)
|
|
316
|
+
] }, idx)
|
|
317
|
+
))
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
const ScrollDrumColumn = ({
|
|
322
|
+
label,
|
|
323
|
+
width,
|
|
324
|
+
maxChars = DEFAULT_MAX_CHARS,
|
|
325
|
+
children,
|
|
326
|
+
className
|
|
327
|
+
}) => {
|
|
328
|
+
const computed = width ?? Math.max(72, 24 + maxChars * 16);
|
|
329
|
+
return /* @__PURE__ */ jsxs(
|
|
330
|
+
"div",
|
|
331
|
+
{
|
|
332
|
+
className: cn(
|
|
333
|
+
"flex flex-none flex-col gap-2 p-0",
|
|
334
|
+
className
|
|
335
|
+
),
|
|
336
|
+
style: { width: computed },
|
|
337
|
+
children: [
|
|
338
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-[120px] flex-none items-center justify-center bg-white dark:bg-gray-900", children }),
|
|
339
|
+
label !== void 0 && /* @__PURE__ */ jsx(
|
|
340
|
+
"div",
|
|
341
|
+
{
|
|
342
|
+
className: "h-6 text-center font-sans font-semibold text-base text-gray-600 leading-6 [font-feature-settings:'ss03'] dark:text-gray-300",
|
|
343
|
+
children: label || "\xA0"
|
|
344
|
+
}
|
|
345
|
+
)
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
};
|
|
350
|
+
export {
|
|
351
|
+
ScrollDrum,
|
|
352
|
+
ScrollDrumColumn,
|
|
353
|
+
ScrollDrumGroup
|
|
354
|
+
};
|
|
355
|
+
//# sourceMappingURL=scroll-drum.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/components/scroll-drum.tsx"],
|
|
4
|
+
"sourcesContent": ["/**\n * @module ScrollDrum\n *\n * iOS-style picker columns for selecting a value from a constrained list \u2014\n * durations, time, dates, BPM, etc. Supports wheel/trackpad scrolling, mouse\n * and touch drag with momentum, click-to-center, full keyboard control, and\n * optional infinite looping.\n *\n * @example\n * // Single drum: count-in bars\n * const [val, setVal] = useState(\"8\");\n * const items = Array.from({ length: 31 }, (_, i) => String(i));\n *\n * <ScrollDrumGroup>\n * <ScrollDrumColumn label=\"bars\">\n * <ScrollDrum items={items} value={val} onChange={setVal} ariaLabel=\"Bars\" />\n * </ScrollDrumColumn>\n * </ScrollDrumGroup>\n *\n * @example\n * // 12-hour time picker with separator\n * <ScrollDrumGroup separator=\":\">\n * <ScrollDrumColumn label=\"hour\">\n * <ScrollDrum items={hours} value={h} onChange={setH} ariaLabel=\"Hour\" />\n * </ScrollDrumColumn>\n * <ScrollDrumColumn label=\"min\">\n * <ScrollDrum items={minutes} value={m} onChange={setM} ariaLabel=\"Minute\" />\n * </ScrollDrumColumn>\n * <ScrollDrumColumn label=\"\">\n * <ScrollDrum items={[\"AM\", \"PM\"]} value={p} onChange={setP} loop={false} />\n * </ScrollDrumColumn>\n * </ScrollDrumGroup>\n */\nimport {\n Children,\n Fragment,\n type PointerEvent as ReactPointerEvent,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\";\n\nimport { cn } from \"../lib/utils\";\n\nconst DEFAULT_ITEM_HEIGHT = 52;\nconst DEFAULT_VISIBLE = 3;\nconst DEFAULT_MAX_CHARS = 4;\n\nconst SNAP_DURATION_MS = 280;\nconst WHEEL_SETTLE_MS = 110;\nconst COAST_FACTOR = 220 * 0.55;\n\nconst clamp = (n: number, lo: number, hi: number): number =>\n Math.max(lo, Math.min(hi, n));\n\n// Positive modulo (handles negatives) \u2014 required for stable wrap math.\nconst pmod = (n: number, m: number): number => ((n % m) + m) % m;\n\ninterface ScrollDrumProps {\n /** Ordered list of selectable strings. Order is the visual order. */\n items: string[];\n /** Currently selected item. Must exist in `items`. */\n value: string;\n /** Fires with `(newValue, newIndex)` when the user lands on a new row. */\n onChange?: (value: string, index: number) => void;\n /** Vertical pixel size of one row slot. Defaults to 52. */\n itemHeight?: number;\n /** How many rows are visible at once (odd numbers center cleanly). */\n visibleCount?: number;\n /** When `true` (default), scrolling past the end wraps to the start. */\n loop?: boolean;\n /** Hard character cap per item. Long labels are truncated for layout. */\n maxChars?: number;\n /** Accessible name for the listbox. */\n ariaLabel?: string;\n /** Additional class names merged onto the drum element. */\n className?: string;\n}\n\n/**\n * A single scrollable column. Designed to be wrapped in a {@link ScrollDrumColumn}\n * and grouped via {@link ScrollDrumGroup}.\n */\nconst ScrollDrum = ({\n items,\n value,\n onChange,\n itemHeight = DEFAULT_ITEM_HEIGHT,\n visibleCount = DEFAULT_VISIBLE,\n loop: loopProp = true,\n maxChars = DEFAULT_MAX_CHARS,\n ariaLabel,\n className,\n}: ScrollDrumProps): ReactNode => {\n const uid = useId();\n const optionId = (idx: number): string => `${uid}-opt-${idx}`;\n const containerRef = useRef<HTMLDivElement | null>(null);\n const animRef = useRef<number>(0);\n const wheelTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const dragRef = useRef({\n active: false,\n startY: 0,\n startIndex: 0,\n lastY: 0,\n lastT: 0,\n vy: 0,\n moved: false,\n });\n\n const N = items.length;\n // Looping past zero items would call pmod(n, 0) and yield NaN, breaking the\n // render math. Force non-looping internally when there's nothing to loop\n // through; the render bails on N === 0.\n const loop = loopProp && N > 1;\n\n const truncate = useCallback(\n (label: string): string =>\n label.length > maxChars ? label.slice(0, maxChars) : label,\n [maxChars]\n );\n\n const initialIndex = Math.max(0, items.indexOf(value));\n const [floatIndex, setFloatIndex] = useState<number>(initialIndex);\n const floatIndexRef = useRef<number>(floatIndex);\n useEffect(() => {\n floatIndexRef.current = floatIndex;\n }, [floatIndex]);\n\n const resolveIndex = useCallback(\n (f: number): number => {\n if (loop) return pmod(Math.round(f), N);\n return clamp(Math.round(f), 0, N - 1);\n },\n [loop, N]\n );\n\n const animateTo = useCallback(\n (targetFloat: number): void => {\n cancelAnimationFrame(animRef.current);\n const start = performance.now();\n const from = floatIndexRef.current;\n const ease = (t: number): number => 1 - (1 - t) ** 3;\n const step = (): void => {\n const t = clamp((performance.now() - start) / SNAP_DURATION_MS, 0, 1);\n const v = from + (targetFloat - from) * ease(t);\n setFloatIndex(v);\n if (t < 1) {\n animRef.current = requestAnimationFrame(step);\n } else {\n const final = loop\n ? pmod(targetFloat, N)\n : clamp(targetFloat, 0, N - 1);\n setFloatIndex(final);\n const idx = resolveIndex(final);\n const next = items[idx];\n if (next !== undefined && next !== value) onChange?.(next, idx);\n }\n };\n animRef.current = requestAnimationFrame(step);\n },\n [items, loop, N, onChange, resolveIndex, value]\n );\n\n // External value -> internal float, animated on the shortest path. Tracked\n // by value's resolved index rather than the items reference, so an unstable\n // `items` array (fresh ref every render) doesn't cancel an in-flight\n // animation, but a real reorder (where `value` lands at a different index)\n // still resyncs the drum.\n const targetIdx = N > 0 ? Math.max(0, items.indexOf(value)) : 0;\n const lastSyncedTargetIdx = useRef<number>(targetIdx);\n useEffect(() => {\n if (targetIdx === lastSyncedTargetIdx.current) return;\n lastSyncedTargetIdx.current = targetIdx;\n if (N === 0) return;\n const cur = floatIndexRef.current;\n let target = targetIdx;\n if (loop) {\n const cycles = Math.round((cur - targetIdx) / N);\n target = targetIdx + cycles * N;\n }\n if (Math.abs(target - cur) > 0.001) animateTo(target);\n // animateTo intentionally omitted to avoid retriggering on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [targetIdx]);\n\n // Wheel: native non-passive listener so we can preventDefault.\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const onWheel = (e: WheelEvent): void => {\n e.preventDefault();\n cancelAnimationFrame(animRef.current);\n const delta = e.deltaY / itemHeight;\n const raw = floatIndexRef.current + delta;\n setFloatIndex(loop ? raw : clamp(raw, 0, N - 1));\n if (wheelTimer.current) clearTimeout(wheelTimer.current);\n wheelTimer.current = setTimeout(() => {\n const cur = floatIndexRef.current;\n const target = loop\n ? Math.round(cur)\n : clamp(Math.round(cur), 0, N - 1);\n animateTo(target);\n }, WHEEL_SETTLE_MS);\n };\n el.addEventListener(\"wheel\", onWheel, { passive: false });\n return () => {\n el.removeEventListener(\"wheel\", onWheel);\n if (wheelTimer.current) clearTimeout(wheelTimer.current);\n };\n }, [animateTo, N, itemHeight, loop]);\n\n useEffect(\n () => () => {\n cancelAnimationFrame(animRef.current);\n },\n []\n );\n\n const onPointerDown = (e: ReactPointerEvent<HTMLDivElement>): void => {\n if (e.pointerType === \"mouse\" && e.button !== 0) return;\n cancelAnimationFrame(animRef.current);\n containerRef.current?.setPointerCapture(e.pointerId);\n dragRef.current = {\n active: true,\n startY: e.clientY,\n startIndex: floatIndexRef.current,\n lastY: e.clientY,\n lastT: performance.now(),\n vy: 0,\n moved: false,\n };\n };\n\n const onPointerMove = (e: ReactPointerEvent<HTMLDivElement>): void => {\n const d = dragRef.current;\n if (!d.active) return;\n const dy = e.clientY - d.startY;\n if (Math.abs(dy) > 3) d.moved = true;\n const raw = d.startIndex - dy / itemHeight;\n setFloatIndex(loop ? raw : clamp(raw, 0, N - 1));\n const now = performance.now();\n const dt = Math.max(1, now - d.lastT);\n d.vy = (e.clientY - d.lastY) / dt;\n d.lastY = e.clientY;\n d.lastT = now;\n };\n\n const onPointerUp = (e: ReactPointerEvent<HTMLDivElement>): void => {\n const d = dragRef.current;\n if (!d.active) return;\n d.active = false;\n try {\n containerRef.current?.releasePointerCapture(e.pointerId);\n } catch {\n /* pointer may already be released */\n }\n const coastPx = d.vy * COAST_FACTOR;\n const projected = floatIndexRef.current - coastPx / itemHeight;\n const target = loop\n ? Math.round(projected)\n : clamp(Math.round(projected), 0, N - 1);\n animateTo(target);\n };\n\n const onKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>): void => {\n let target = Math.round(floatIndexRef.current);\n if (e.key === \"ArrowUp\") target -= 1;\n else if (e.key === \"ArrowDown\") target += 1;\n else if (e.key === \"PageUp\") target -= 5;\n else if (e.key === \"PageDown\") target += 5;\n else if (e.key === \"Home\") {\n const cur = Math.round(floatIndexRef.current);\n target = loop ? cur - pmod(cur, N) : 0;\n } else if (e.key === \"End\") {\n const cur = Math.round(floatIndexRef.current);\n target = loop ? cur - pmod(cur, N) + N - 1 : N - 1;\n } else return;\n e.preventDefault();\n if (!loop) target = clamp(target, 0, N - 1);\n animateTo(target);\n };\n\n const halfVisible = Math.floor(visibleCount / 2);\n const windowRadius = halfVisible + 2;\n const centerInt = Math.round(floatIndex);\n\n const visibleItems: Array<{\n key: string;\n itemIdx: number;\n offsetSlot: number;\n label: string;\n }> = [];\n for (let off = -windowRadius; off <= windowRadius; off++) {\n const rawIdx = centerInt + off;\n if (!loop && (rawIdx < 0 || rawIdx >= N)) continue;\n const itemIdx = pmod(rawIdx, N);\n const label = items[itemIdx];\n if (label === undefined) continue;\n visibleItems.push({\n key: String(rawIdx),\n itemIdx,\n offsetSlot: rawIdx,\n label,\n });\n }\n\n const rowOffset = (slot: number): number => (slot - floatIndex) * itemHeight;\n const selectedIndex = N > 0 ? resolveIndex(floatIndex) : 0;\n\n if (N === 0) {\n return (\n <div\n aria-label={ariaLabel}\n className={cn(\n \"relative h-full w-full overflow-hidden bg-white dark:bg-gray-900\",\n className\n )}\n role=\"listbox\"\n />\n );\n }\n\n return (\n <div\n aria-activedescendant={optionId(selectedIndex)}\n aria-label={ariaLabel}\n aria-orientation=\"vertical\"\n className={cn(\n \"relative h-full w-full cursor-grab touch-none select-none overflow-hidden bg-white outline-none active:cursor-grabbing focus-visible:[box-shadow:inset_0_0_0_2px_var(--brand-primary)] dark:bg-gray-900\",\n className\n )}\n onKeyDown={onKeyDown}\n onPointerCancel={onPointerUp}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n ref={containerRef}\n role=\"listbox\"\n style={{ \"--sd-item-h\": `${itemHeight}px` } as React.CSSProperties}\n tabIndex={0}\n >\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-x-0 top-[-1px] z-[2] h-9 bg-gradient-to-b from-white from-[12%] to-transparent dark:from-gray-900\"\n />\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-x-0 bottom-[-1px] z-[2] h-9 bg-gradient-to-t from-white from-[12%] to-transparent dark:from-gray-900\"\n />\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-x-2 top-1/2 z-0 h-[41px] -translate-y-1/2 rounded-sm border border-brand-primary bg-brand-secondary\"\n />\n\n <div className=\"pointer-events-none absolute inset-0\">\n {visibleItems.map(({ key, itemIdx, offsetSlot, label }) => {\n const isSelected =\n itemIdx === selectedIndex &&\n Math.abs(offsetSlot - floatIndex) <= 0.5;\n return (\n <button\n aria-selected={isSelected}\n className={cn(\n \"pointer-events-auto absolute top-1/2 left-1/2 z-[1] m-0 box-border flex h-[var(--sd-item-h,52px)] w-full cursor-pointer items-center justify-center overflow-hidden border-0 bg-transparent px-1.5 py-0 transition-colors duration-150\",\n isSelected\n ? \"text-brand-primary-hover dark:text-white\"\n : \"text-gray-600 dark:text-gray-300\"\n )}\n id={isSelected ? optionId(itemIdx) : undefined}\n key={key}\n onClick={() => {\n if (dragRef.current.moved) return;\n animateTo(offsetSlot);\n }}\n role=\"option\"\n style={{\n transform: `translate3d(-50%, calc(-50% + ${rowOffset(offsetSlot)}px), 0)`,\n }}\n tabIndex={-1}\n type=\"button\"\n >\n <span className=\"inline-block whitespace-nowrap text-center font-sans font-semibold text-[30px] leading-9 tracking-[-1px] [font-feature-settings:'ss03']\">\n {truncate(label)}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n );\n};\n\ninterface ScrollDrumGroupProps {\n /** One or more {@link ScrollDrumColumn} children. */\n children: ReactNode;\n /** Glyph rendered between adjacent columns. Ignored when `showSeparators` is false. */\n separator?: string;\n /** When `false`, the gutter between columns is preserved but no glyph is rendered. */\n showSeparators?: boolean;\n /** Additional class names merged onto the group container. */\n className?: string;\n}\n\n/**\n * Gray container that holds one or more {@link ScrollDrumColumn} children.\n * Optionally renders a glyph between columns (e.g. `:` for time, `/` for date).\n */\nconst ScrollDrumGroup = ({\n children,\n separator = \":\",\n showSeparators = true,\n className,\n}: ScrollDrumGroupProps): ReactNode => {\n const cols = Children.toArray(children);\n return (\n <div\n className={cn(\n \"inline-flex flex-row items-center justify-center rounded-lg bg-gray-100 px-8 pt-6 pb-4 dark:bg-gray-800\",\n className\n )}\n >\n {cols.map((col, idx) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: columns are static and not reordered\n <Fragment key={idx}>\n {col}\n {idx < cols.length - 1 && (\n <div\n aria-hidden=\"true\"\n className=\"flex h-[120px] w-8 flex-none items-center justify-center self-start\"\n >\n {showSeparators && (\n <span className=\"text-center font-sans font-semibold text-[36px] text-gray-600 leading-10 tracking-[-1.5px] [font-feature-settings:'ss03'] dark:text-gray-400\">\n {separator}\n </span>\n )}\n </div>\n )}\n </Fragment>\n ))}\n </div>\n );\n};\n\ninterface ScrollDrumColumnProps {\n /** Caption rendered below the drum slab. Pass `\"\"` for an invisible spacer. */\n label?: string;\n /** Explicit pixel width override. When omitted, scales with `maxChars`. */\n width?: number;\n /** Hard character cap that drives the auto-computed column width. */\n maxChars?: number;\n /** Should be a single {@link ScrollDrum}. */\n children: ReactNode;\n /** Additional class names merged onto the column wrapper. */\n className?: string;\n}\n\n/**\n * Pairs a {@link ScrollDrum} with an optional caption underneath. When `width`\n * is omitted the column auto-sizes via `Math.max(72, 24 + maxChars * 16)`, so\n * a 4-char drum (e.g. `\"2026\"`) lands at 88px and a 2-char drum stays at the\n * 72px floor.\n */\nconst ScrollDrumColumn = ({\n label,\n width,\n maxChars = DEFAULT_MAX_CHARS,\n children,\n className,\n}: ScrollDrumColumnProps): ReactNode => {\n const computed = width ?? Math.max(72, 24 + maxChars * 16);\n return (\n <div\n className={cn(\n \"flex flex-none flex-col gap-2 p-0\",\n className\n )}\n style={{ width: computed }}\n >\n <div className=\"flex h-[120px] flex-none items-center justify-center bg-white dark:bg-gray-900\">\n {children}\n </div>\n {label !== undefined && (\n <div\n className=\"h-6 text-center font-sans font-semibold text-base text-gray-600 leading-6 [font-feature-settings:'ss03'] dark:text-gray-300\"\n >\n {label || \"\u00A0\"}\n </div>\n )}\n </div>\n );\n};\n\nexport { ScrollDrum, ScrollDrumGroup, ScrollDrumColumn };\nexport type { ScrollDrumProps, ScrollDrumGroupProps, ScrollDrumColumnProps };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@music-vine/cadence",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
"tailwindcss-v3": "npm:tailwindcss@^3.4.18",
|
|
140
140
|
"tsx": "^4.21.0",
|
|
141
141
|
"typescript": "^5.2.2",
|
|
142
|
-
"vite": "^
|
|
142
|
+
"vite": "^8.0.7",
|
|
143
143
|
"vitest": "^3.2.4"
|
|
144
144
|
},
|
|
145
145
|
"scripts": {
|