@devfellowship/components 3.5.1 → 3.5.3

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
@@ -4707,7 +4707,20 @@ var React23 = __toESM(require("react"), 1);
4707
4707
  var SelectPrimitive = __toESM(require("@radix-ui/react-select"), 1);
4708
4708
  var import_lucide_react14 = require("lucide-react");
4709
4709
  var import_jsx_runtime35 = require("react/jsx-runtime");
4710
- var Select = SelectPrimitive.Root;
4710
+ var Select = ({ allowEmptyValue = false, onValueChange, value, ...props }) => {
4711
+ const latest = React23.useRef({ value, onValueChange, allowEmptyValue });
4712
+ latest.current = { value, onValueChange, allowEmptyValue };
4713
+ const handleValueChange = React23.useCallback((next) => {
4714
+ const current = latest.current;
4715
+ if (!current.onValueChange) return;
4716
+ const isControlled = current.value !== void 0;
4717
+ const isEmptyEcho = next === "" && isControlled && current.value !== "";
4718
+ if (isEmptyEcho && !current.allowEmptyValue) return;
4719
+ current.onValueChange(next);
4720
+ }, []);
4721
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SelectPrimitive.Root, { value, onValueChange: handleValueChange, ...props });
4722
+ };
4723
+ Select.displayName = "Select";
4711
4724
  var SelectGroup = SelectPrimitive.Group;
4712
4725
  var SelectValue = SelectPrimitive.Value;
4713
4726
  var SelectTrigger = React23.forwardRef(({ className, children, error, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
package/dist/index.d.cts CHANGED
@@ -1109,7 +1109,78 @@ interface ScrollAreaProps extends React$1.ComponentPropsWithoutRef<typeof Scroll
1109
1109
  declare const ScrollArea: React$1.ForwardRefExoticComponent<ScrollAreaProps & React$1.RefAttributes<HTMLDivElement>>;
1110
1110
  declare const ScrollBar: React$1.ForwardRefExoticComponent<Omit<ScrollAreaPrimitive.ScrollAreaScrollbarProps & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
1111
1111
 
1112
- declare const Select: React$1.FC<SelectPrimitive.SelectProps>;
1112
+ /**
1113
+ * Select — DFL Design System
1114
+ *
1115
+ * A thin wrapper over `SelectPrimitive.Root` that adds ONE guard: it drops the
1116
+ * empty-string echo the hidden native `<select>` sends back before its
1117
+ * `<option>` list has registered.
1118
+ *
1119
+ * ## Why the guard exists
1120
+ *
1121
+ * Radix keeps a hidden native `<select>` (`SelectBubbleInput`) so the component
1122
+ * participates in form submission. Radix renders it whenever the Select is a
1123
+ * form control. When the Radix value changes, that input does two things in an
1124
+ * effect:
1125
+ *
1126
+ * ```ts
1127
+ * setValue.call(select, nextValue); // assign select.value
1128
+ * select.dispatchEvent(new Event("change", ...)) // and dispatch a REAL change
1129
+ * ```
1130
+ *
1131
+ * and its own `onChange` reports `event.target.value` back through
1132
+ * `onValueChange`.
1133
+ *
1134
+ * The `<option>` elements come from the `SelectItem` children. While the menu
1135
+ * is closed, Radix mounts those children into a detached `DocumentFragment`
1136
+ * that only exists after a layout effect. So for the first renders after mount
1137
+ * the native `<select>` has NO options. The browser then resolves
1138
+ * `select.value = "plan-b"` to `""`, and that empty string arrives at the
1139
+ * consumer as `onValueChange("")`.
1140
+ *
1141
+ * A controlled consumer that sets its value asynchronously — a fetch, a
1142
+ * hydration effect, an edit dialog that loads the current record — receives
1143
+ * that echo AFTER it set the real value, and the echo WIPES it. The user sees
1144
+ * the field reset itself to the placeholder.
1145
+ *
1146
+ * Found in production on 2026-08-21: the student plan picker in
1147
+ * `marques-boxing-monorepo` cleared the plan every time the edit dialog opened.
1148
+ * The app patched it locally (commit `df55d95`). This guard replaces that
1149
+ * app-side patch, so every consumer of `@devfellowship/components` gets it.
1150
+ *
1151
+ * ## What the guard does
1152
+ *
1153
+ * It drops an `onValueChange("")` call when ALL of these hold:
1154
+ *
1155
+ * 1. The component is CONTROLLED (`value` is not `undefined`).
1156
+ * 2. The current `value` is NOT the empty string.
1157
+ * 3. `allowEmptyValue` is not set.
1158
+ *
1159
+ * A user selection can never produce `""`: Radix requires every `SelectItem` to
1160
+ * carry a non-empty `value`. So an empty string on a controlled, non-empty
1161
+ * Select can only come from the bubble-input echo.
1162
+ *
1163
+ * Uncontrolled usage (`defaultValue`, or no value at all) is UNCHANGED — the
1164
+ * guard never runs there.
1165
+ *
1166
+ * ## Escape hatch
1167
+ *
1168
+ * Pass `allowEmptyValue` to opt out and receive every `onValueChange`,
1169
+ * including `""`. Use it for a Select that genuinely clears itself to an empty
1170
+ * value through the Radix value channel.
1171
+ */
1172
+ interface SelectProps extends React$1.ComponentPropsWithoutRef<typeof SelectPrimitive.Root> {
1173
+ /**
1174
+ * Let an empty-string `onValueChange` through while the Select is controlled
1175
+ * with a non-empty `value`. Default `false`, which drops the empty echo the
1176
+ * hidden native `<select>` sends before its options register.
1177
+ */
1178
+ allowEmptyValue?: boolean;
1179
+ }
1180
+ declare const Select: {
1181
+ ({ allowEmptyValue, onValueChange, value, ...props }: SelectProps): React$1.JSX.Element;
1182
+ displayName: string;
1183
+ };
1113
1184
  declare const SelectGroup: React$1.ForwardRefExoticComponent<SelectPrimitive.SelectGroupProps & React$1.RefAttributes<HTMLDivElement>>;
1114
1185
  declare const SelectValue: React$1.ForwardRefExoticComponent<SelectPrimitive.SelectValueProps & React$1.RefAttributes<HTMLSpanElement>>;
1115
1186
  /**
@@ -3278,4 +3349,4 @@ interface RoadmapProps {
3278
3349
  /** Dark-only, ordinary document scroll. The JSON owns placement; CSS owns the pixels. */
3279
3350
  declare function Roadmap({ document, state, renderNode, onNodeClick, onAction, testIdPrefix, className, ariaLabel, debugPerf, collapseEmptyColumns }: RoadmapProps): React__default.JSX.Element;
3280
3351
 
3281
- export { ALLOWED_ORIGINS, AVATAR_MEMBER_PALETTE_SIZE, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, type AlertDialogActionProps, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppNavbar, type AppNavbarProps, AppSidebar, type AppSidebarProps, AspectRatio, Avatar, AvatarFallback, AvatarGroup, AvatarImage, type AvatarSize, AvatarStatus, type AvatarStatusValue, type AvatarTone, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, type BreadcrumbEntry, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleHeader, CollapsibleItem, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DEFAULT_EDGE_ARROW, DEFAULT_EDGE_ROUTE, DEFAULT_EDGE_STYLE, DEFAULT_GROUP_TONE, DEFAULT_LEGEND_PLACEMENT, DEFAULT_NAME_COL_WIDTH, DEFAULT_NODE_SPAN, DEFAULT_NODE_STATE, type DflIframeMessage, type DflNavigateMessage, type DflReadyMessage, DflRemote, type DflRemoteProps, type DflResizeMessage, type DflSetTokenMessage, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Gantt, type GanttDependency, type GanttMilestone, type GanttProps, type GanttStage, HoverCard, HoverCardContent, HoverCardTrigger, IconButton, type IconButtonProps, IframeAware, type IframeAwareProps, IframeContext, type IframeContextValue, type IframeMessageType, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Kbd, type KbdProps, Label, LoginPage, type LoginPageProps, LoginScreen, type LoginScreenProps, MEMBER_PALETTE_SIZE, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NODE_KIND_DEFAULT_TONE, type NavGroup, type NavItem, type NavbarUserInfo, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, type OTPStatus, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PasswordInput, type PasswordInputProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, type ProgressProps, ProtectedRoute, type ProtectedRouteProps, PublishDrawer, type PublishDrawerProps, type PublishDrawerSupabase, type PublishResult, type PublishStatus, type PublisherAccount, ROADMAP_ARROWS, ROADMAP_COLUMNS, ROADMAP_EDGE_LABEL_MAX, ROADMAP_EDGE_ROUTES, ROADMAP_EDGE_STYLES, ROADMAP_LABEL_MAX, ROADMAP_LEGEND_PLACEMENTS, ROADMAP_NODE_ID_PATTERN, ROADMAP_NODE_KINDS, ROADMAP_NODE_STATES, ROADMAP_TONES, RadioGroup, RadioGroupItem, RadioGroupRow, ResizableHandle, ResizablePanel, ResizablePanelGroup, Roadmap, type RoadmapAction, type RoadmapArrow, type RoadmapColumn, type RoadmapDocument, type RoadmapEdge, type RoadmapEdgeRoute, type RoadmapEdgeStyle, type RoadmapGroup, type RoadmapGroupRange, type RoadmapIcon, type RoadmapLegend, type RoadmapLegendEntry, type RoadmapLegendPlacement, type RoadmapLink, type RoadmapNode, type RoadmapNodeKind, type RoadmapNodeState, type RoadmapOverlap, type RoadmapPlacement, type RoadmapProps, type RoadmapStateOverlay, type RoadmapTone, ScrollArea, type ScrollAreaScrollbars, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeparatorWithLabel, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, SonnerToaster, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, Toast, ToastAction, type ToastActionElement, ToastClose, type ToastComponentProps, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserAvatar, type UserAvatarProps, type UserInfo, UserMenu, type UserMenuItem, type UserMenuProps, avatarMemberColor, badgeVariants, barGridColumn, buttonVariants, clampPct, columnIndex, columnsCovered, columnsUsed, filterPublishableAccounts, firstFreeRow, getInitials, gridColumnFor, groupColumnRuns, groupRanges, iconButtonVariants, isAllowedOrigin, kbdVariants, labelVariants, maxRowOf, memberHueIndex, memberHueVar, navigationMenuTriggerStyle, nodesByRow, parseRoadmapDocument, parseRoadmapStateOverlay, parseTags, placeNodes, resolveEdgeArrow, resolveEdgeRoute, resolveEdgeStyle, resolveGroupColumns, resolveGroupTone, resolveLegendPlacement, resolveNameColWidth, resolveNodeSpan, resolveNodeState, resolveNodeTone, resolveWeekCount, resolveWeekLabels, roadmapActionSchema, roadmapDocumentSchema, roadmapEdgeSchema, roadmapGroupSchema, roadmapIconSchema, roadmapLegendEntrySchema, roadmapLegendSchema, roadmapLinkSchema, roadmapNodeSchema, roadmapNodeStateSchema, roadmapStateOverlaySchema, roadmapStateSchema, rowsOf, safeParseRoadmapDocument, safeParseRoadmapStateOverlay, stageProgress, toggleVariants, useFormField, useSidebar, validateNoOverlap, validatePublishForm };
3352
+ export { ALLOWED_ORIGINS, AVATAR_MEMBER_PALETTE_SIZE, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, type AlertDialogActionProps, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppNavbar, type AppNavbarProps, AppSidebar, type AppSidebarProps, AspectRatio, Avatar, AvatarFallback, AvatarGroup, AvatarImage, type AvatarSize, AvatarStatus, type AvatarStatusValue, type AvatarTone, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, type BreadcrumbEntry, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleHeader, CollapsibleItem, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DEFAULT_EDGE_ARROW, DEFAULT_EDGE_ROUTE, DEFAULT_EDGE_STYLE, DEFAULT_GROUP_TONE, DEFAULT_LEGEND_PLACEMENT, DEFAULT_NAME_COL_WIDTH, DEFAULT_NODE_SPAN, DEFAULT_NODE_STATE, type DflIframeMessage, type DflNavigateMessage, type DflReadyMessage, DflRemote, type DflRemoteProps, type DflResizeMessage, type DflSetTokenMessage, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Gantt, type GanttDependency, type GanttMilestone, type GanttProps, type GanttStage, HoverCard, HoverCardContent, HoverCardTrigger, IconButton, type IconButtonProps, IframeAware, type IframeAwareProps, IframeContext, type IframeContextValue, type IframeMessageType, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Kbd, type KbdProps, Label, LoginPage, type LoginPageProps, LoginScreen, type LoginScreenProps, MEMBER_PALETTE_SIZE, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NODE_KIND_DEFAULT_TONE, type NavGroup, type NavItem, type NavbarUserInfo, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, type OTPStatus, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PasswordInput, type PasswordInputProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, type ProgressProps, ProtectedRoute, type ProtectedRouteProps, PublishDrawer, type PublishDrawerProps, type PublishDrawerSupabase, type PublishResult, type PublishStatus, type PublisherAccount, ROADMAP_ARROWS, ROADMAP_COLUMNS, ROADMAP_EDGE_LABEL_MAX, ROADMAP_EDGE_ROUTES, ROADMAP_EDGE_STYLES, ROADMAP_LABEL_MAX, ROADMAP_LEGEND_PLACEMENTS, ROADMAP_NODE_ID_PATTERN, ROADMAP_NODE_KINDS, ROADMAP_NODE_STATES, ROADMAP_TONES, RadioGroup, RadioGroupItem, RadioGroupRow, ResizableHandle, ResizablePanel, ResizablePanelGroup, Roadmap, type RoadmapAction, type RoadmapArrow, type RoadmapColumn, type RoadmapDocument, type RoadmapEdge, type RoadmapEdgeRoute, type RoadmapEdgeStyle, type RoadmapGroup, type RoadmapGroupRange, type RoadmapIcon, type RoadmapLegend, type RoadmapLegendEntry, type RoadmapLegendPlacement, type RoadmapLink, type RoadmapNode, type RoadmapNodeKind, type RoadmapNodeState, type RoadmapOverlap, type RoadmapPlacement, type RoadmapProps, type RoadmapStateOverlay, type RoadmapTone, ScrollArea, type ScrollAreaScrollbars, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, type SelectProps, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, type SelectTriggerProps, SelectValue, Separator, SeparatorWithLabel, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, SonnerToaster, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, Toast, ToastAction, type ToastActionElement, ToastClose, type ToastComponentProps, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserAvatar, type UserAvatarProps, type UserInfo, UserMenu, type UserMenuItem, type UserMenuProps, avatarMemberColor, badgeVariants, barGridColumn, buttonVariants, clampPct, columnIndex, columnsCovered, columnsUsed, filterPublishableAccounts, firstFreeRow, getInitials, gridColumnFor, groupColumnRuns, groupRanges, iconButtonVariants, isAllowedOrigin, kbdVariants, labelVariants, maxRowOf, memberHueIndex, memberHueVar, navigationMenuTriggerStyle, nodesByRow, parseRoadmapDocument, parseRoadmapStateOverlay, parseTags, placeNodes, resolveEdgeArrow, resolveEdgeRoute, resolveEdgeStyle, resolveGroupColumns, resolveGroupTone, resolveLegendPlacement, resolveNameColWidth, resolveNodeSpan, resolveNodeState, resolveNodeTone, resolveWeekCount, resolveWeekLabels, roadmapActionSchema, roadmapDocumentSchema, roadmapEdgeSchema, roadmapGroupSchema, roadmapIconSchema, roadmapLegendEntrySchema, roadmapLegendSchema, roadmapLinkSchema, roadmapNodeSchema, roadmapNodeStateSchema, roadmapStateOverlaySchema, roadmapStateSchema, rowsOf, safeParseRoadmapDocument, safeParseRoadmapStateOverlay, stageProgress, toggleVariants, useFormField, useSidebar, validateNoOverlap, validatePublishForm };
package/dist/index.d.ts CHANGED
@@ -1109,7 +1109,78 @@ interface ScrollAreaProps extends React$1.ComponentPropsWithoutRef<typeof Scroll
1109
1109
  declare const ScrollArea: React$1.ForwardRefExoticComponent<ScrollAreaProps & React$1.RefAttributes<HTMLDivElement>>;
1110
1110
  declare const ScrollBar: React$1.ForwardRefExoticComponent<Omit<ScrollAreaPrimitive.ScrollAreaScrollbarProps & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
1111
1111
 
1112
- declare const Select: React$1.FC<SelectPrimitive.SelectProps>;
1112
+ /**
1113
+ * Select — DFL Design System
1114
+ *
1115
+ * A thin wrapper over `SelectPrimitive.Root` that adds ONE guard: it drops the
1116
+ * empty-string echo the hidden native `<select>` sends back before its
1117
+ * `<option>` list has registered.
1118
+ *
1119
+ * ## Why the guard exists
1120
+ *
1121
+ * Radix keeps a hidden native `<select>` (`SelectBubbleInput`) so the component
1122
+ * participates in form submission. Radix renders it whenever the Select is a
1123
+ * form control. When the Radix value changes, that input does two things in an
1124
+ * effect:
1125
+ *
1126
+ * ```ts
1127
+ * setValue.call(select, nextValue); // assign select.value
1128
+ * select.dispatchEvent(new Event("change", ...)) // and dispatch a REAL change
1129
+ * ```
1130
+ *
1131
+ * and its own `onChange` reports `event.target.value` back through
1132
+ * `onValueChange`.
1133
+ *
1134
+ * The `<option>` elements come from the `SelectItem` children. While the menu
1135
+ * is closed, Radix mounts those children into a detached `DocumentFragment`
1136
+ * that only exists after a layout effect. So for the first renders after mount
1137
+ * the native `<select>` has NO options. The browser then resolves
1138
+ * `select.value = "plan-b"` to `""`, and that empty string arrives at the
1139
+ * consumer as `onValueChange("")`.
1140
+ *
1141
+ * A controlled consumer that sets its value asynchronously — a fetch, a
1142
+ * hydration effect, an edit dialog that loads the current record — receives
1143
+ * that echo AFTER it set the real value, and the echo WIPES it. The user sees
1144
+ * the field reset itself to the placeholder.
1145
+ *
1146
+ * Found in production on 2026-08-21: the student plan picker in
1147
+ * `marques-boxing-monorepo` cleared the plan every time the edit dialog opened.
1148
+ * The app patched it locally (commit `df55d95`). This guard replaces that
1149
+ * app-side patch, so every consumer of `@devfellowship/components` gets it.
1150
+ *
1151
+ * ## What the guard does
1152
+ *
1153
+ * It drops an `onValueChange("")` call when ALL of these hold:
1154
+ *
1155
+ * 1. The component is CONTROLLED (`value` is not `undefined`).
1156
+ * 2. The current `value` is NOT the empty string.
1157
+ * 3. `allowEmptyValue` is not set.
1158
+ *
1159
+ * A user selection can never produce `""`: Radix requires every `SelectItem` to
1160
+ * carry a non-empty `value`. So an empty string on a controlled, non-empty
1161
+ * Select can only come from the bubble-input echo.
1162
+ *
1163
+ * Uncontrolled usage (`defaultValue`, or no value at all) is UNCHANGED — the
1164
+ * guard never runs there.
1165
+ *
1166
+ * ## Escape hatch
1167
+ *
1168
+ * Pass `allowEmptyValue` to opt out and receive every `onValueChange`,
1169
+ * including `""`. Use it for a Select that genuinely clears itself to an empty
1170
+ * value through the Radix value channel.
1171
+ */
1172
+ interface SelectProps extends React$1.ComponentPropsWithoutRef<typeof SelectPrimitive.Root> {
1173
+ /**
1174
+ * Let an empty-string `onValueChange` through while the Select is controlled
1175
+ * with a non-empty `value`. Default `false`, which drops the empty echo the
1176
+ * hidden native `<select>` sends before its options register.
1177
+ */
1178
+ allowEmptyValue?: boolean;
1179
+ }
1180
+ declare const Select: {
1181
+ ({ allowEmptyValue, onValueChange, value, ...props }: SelectProps): React$1.JSX.Element;
1182
+ displayName: string;
1183
+ };
1113
1184
  declare const SelectGroup: React$1.ForwardRefExoticComponent<SelectPrimitive.SelectGroupProps & React$1.RefAttributes<HTMLDivElement>>;
1114
1185
  declare const SelectValue: React$1.ForwardRefExoticComponent<SelectPrimitive.SelectValueProps & React$1.RefAttributes<HTMLSpanElement>>;
1115
1186
  /**
@@ -3278,4 +3349,4 @@ interface RoadmapProps {
3278
3349
  /** Dark-only, ordinary document scroll. The JSON owns placement; CSS owns the pixels. */
3279
3350
  declare function Roadmap({ document, state, renderNode, onNodeClick, onAction, testIdPrefix, className, ariaLabel, debugPerf, collapseEmptyColumns }: RoadmapProps): React__default.JSX.Element;
3280
3351
 
3281
- export { ALLOWED_ORIGINS, AVATAR_MEMBER_PALETTE_SIZE, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, type AlertDialogActionProps, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppNavbar, type AppNavbarProps, AppSidebar, type AppSidebarProps, AspectRatio, Avatar, AvatarFallback, AvatarGroup, AvatarImage, type AvatarSize, AvatarStatus, type AvatarStatusValue, type AvatarTone, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, type BreadcrumbEntry, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleHeader, CollapsibleItem, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DEFAULT_EDGE_ARROW, DEFAULT_EDGE_ROUTE, DEFAULT_EDGE_STYLE, DEFAULT_GROUP_TONE, DEFAULT_LEGEND_PLACEMENT, DEFAULT_NAME_COL_WIDTH, DEFAULT_NODE_SPAN, DEFAULT_NODE_STATE, type DflIframeMessage, type DflNavigateMessage, type DflReadyMessage, DflRemote, type DflRemoteProps, type DflResizeMessage, type DflSetTokenMessage, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Gantt, type GanttDependency, type GanttMilestone, type GanttProps, type GanttStage, HoverCard, HoverCardContent, HoverCardTrigger, IconButton, type IconButtonProps, IframeAware, type IframeAwareProps, IframeContext, type IframeContextValue, type IframeMessageType, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Kbd, type KbdProps, Label, LoginPage, type LoginPageProps, LoginScreen, type LoginScreenProps, MEMBER_PALETTE_SIZE, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NODE_KIND_DEFAULT_TONE, type NavGroup, type NavItem, type NavbarUserInfo, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, type OTPStatus, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PasswordInput, type PasswordInputProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, type ProgressProps, ProtectedRoute, type ProtectedRouteProps, PublishDrawer, type PublishDrawerProps, type PublishDrawerSupabase, type PublishResult, type PublishStatus, type PublisherAccount, ROADMAP_ARROWS, ROADMAP_COLUMNS, ROADMAP_EDGE_LABEL_MAX, ROADMAP_EDGE_ROUTES, ROADMAP_EDGE_STYLES, ROADMAP_LABEL_MAX, ROADMAP_LEGEND_PLACEMENTS, ROADMAP_NODE_ID_PATTERN, ROADMAP_NODE_KINDS, ROADMAP_NODE_STATES, ROADMAP_TONES, RadioGroup, RadioGroupItem, RadioGroupRow, ResizableHandle, ResizablePanel, ResizablePanelGroup, Roadmap, type RoadmapAction, type RoadmapArrow, type RoadmapColumn, type RoadmapDocument, type RoadmapEdge, type RoadmapEdgeRoute, type RoadmapEdgeStyle, type RoadmapGroup, type RoadmapGroupRange, type RoadmapIcon, type RoadmapLegend, type RoadmapLegendEntry, type RoadmapLegendPlacement, type RoadmapLink, type RoadmapNode, type RoadmapNodeKind, type RoadmapNodeState, type RoadmapOverlap, type RoadmapPlacement, type RoadmapProps, type RoadmapStateOverlay, type RoadmapTone, ScrollArea, type ScrollAreaScrollbars, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeparatorWithLabel, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, SonnerToaster, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, Toast, ToastAction, type ToastActionElement, ToastClose, type ToastComponentProps, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserAvatar, type UserAvatarProps, type UserInfo, UserMenu, type UserMenuItem, type UserMenuProps, avatarMemberColor, badgeVariants, barGridColumn, buttonVariants, clampPct, columnIndex, columnsCovered, columnsUsed, filterPublishableAccounts, firstFreeRow, getInitials, gridColumnFor, groupColumnRuns, groupRanges, iconButtonVariants, isAllowedOrigin, kbdVariants, labelVariants, maxRowOf, memberHueIndex, memberHueVar, navigationMenuTriggerStyle, nodesByRow, parseRoadmapDocument, parseRoadmapStateOverlay, parseTags, placeNodes, resolveEdgeArrow, resolveEdgeRoute, resolveEdgeStyle, resolveGroupColumns, resolveGroupTone, resolveLegendPlacement, resolveNameColWidth, resolveNodeSpan, resolveNodeState, resolveNodeTone, resolveWeekCount, resolveWeekLabels, roadmapActionSchema, roadmapDocumentSchema, roadmapEdgeSchema, roadmapGroupSchema, roadmapIconSchema, roadmapLegendEntrySchema, roadmapLegendSchema, roadmapLinkSchema, roadmapNodeSchema, roadmapNodeStateSchema, roadmapStateOverlaySchema, roadmapStateSchema, rowsOf, safeParseRoadmapDocument, safeParseRoadmapStateOverlay, stageProgress, toggleVariants, useFormField, useSidebar, validateNoOverlap, validatePublishForm };
3352
+ export { ALLOWED_ORIGINS, AVATAR_MEMBER_PALETTE_SIZE, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, type AlertDialogActionProps, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppNavbar, type AppNavbarProps, AppSidebar, type AppSidebarProps, AspectRatio, Avatar, AvatarFallback, AvatarGroup, AvatarImage, type AvatarSize, AvatarStatus, type AvatarStatusValue, type AvatarTone, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, type BreadcrumbEntry, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleHeader, CollapsibleItem, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DEFAULT_EDGE_ARROW, DEFAULT_EDGE_ROUTE, DEFAULT_EDGE_STYLE, DEFAULT_GROUP_TONE, DEFAULT_LEGEND_PLACEMENT, DEFAULT_NAME_COL_WIDTH, DEFAULT_NODE_SPAN, DEFAULT_NODE_STATE, type DflIframeMessage, type DflNavigateMessage, type DflReadyMessage, DflRemote, type DflRemoteProps, type DflResizeMessage, type DflSetTokenMessage, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Gantt, type GanttDependency, type GanttMilestone, type GanttProps, type GanttStage, HoverCard, HoverCardContent, HoverCardTrigger, IconButton, type IconButtonProps, IframeAware, type IframeAwareProps, IframeContext, type IframeContextValue, type IframeMessageType, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Kbd, type KbdProps, Label, LoginPage, type LoginPageProps, LoginScreen, type LoginScreenProps, MEMBER_PALETTE_SIZE, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, NODE_KIND_DEFAULT_TONE, type NavGroup, type NavItem, type NavbarUserInfo, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, type OTPStatus, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PasswordInput, type PasswordInputProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, type ProgressProps, ProtectedRoute, type ProtectedRouteProps, PublishDrawer, type PublishDrawerProps, type PublishDrawerSupabase, type PublishResult, type PublishStatus, type PublisherAccount, ROADMAP_ARROWS, ROADMAP_COLUMNS, ROADMAP_EDGE_LABEL_MAX, ROADMAP_EDGE_ROUTES, ROADMAP_EDGE_STYLES, ROADMAP_LABEL_MAX, ROADMAP_LEGEND_PLACEMENTS, ROADMAP_NODE_ID_PATTERN, ROADMAP_NODE_KINDS, ROADMAP_NODE_STATES, ROADMAP_TONES, RadioGroup, RadioGroupItem, RadioGroupRow, ResizableHandle, ResizablePanel, ResizablePanelGroup, Roadmap, type RoadmapAction, type RoadmapArrow, type RoadmapColumn, type RoadmapDocument, type RoadmapEdge, type RoadmapEdgeRoute, type RoadmapEdgeStyle, type RoadmapGroup, type RoadmapGroupRange, type RoadmapIcon, type RoadmapLegend, type RoadmapLegendEntry, type RoadmapLegendPlacement, type RoadmapLink, type RoadmapNode, type RoadmapNodeKind, type RoadmapNodeState, type RoadmapOverlap, type RoadmapPlacement, type RoadmapProps, type RoadmapStateOverlay, type RoadmapTone, ScrollArea, type ScrollAreaScrollbars, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, type SelectProps, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, type SelectTriggerProps, SelectValue, Separator, SeparatorWithLabel, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, Skeleton, Slider, SonnerToaster, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, Toast, ToastAction, type ToastActionElement, ToastClose, type ToastComponentProps, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserAvatar, type UserAvatarProps, type UserInfo, UserMenu, type UserMenuItem, type UserMenuProps, avatarMemberColor, badgeVariants, barGridColumn, buttonVariants, clampPct, columnIndex, columnsCovered, columnsUsed, filterPublishableAccounts, firstFreeRow, getInitials, gridColumnFor, groupColumnRuns, groupRanges, iconButtonVariants, isAllowedOrigin, kbdVariants, labelVariants, maxRowOf, memberHueIndex, memberHueVar, navigationMenuTriggerStyle, nodesByRow, parseRoadmapDocument, parseRoadmapStateOverlay, parseTags, placeNodes, resolveEdgeArrow, resolveEdgeRoute, resolveEdgeStyle, resolveGroupColumns, resolveGroupTone, resolveLegendPlacement, resolveNameColWidth, resolveNodeSpan, resolveNodeState, resolveNodeTone, resolveWeekCount, resolveWeekLabels, roadmapActionSchema, roadmapDocumentSchema, roadmapEdgeSchema, roadmapGroupSchema, roadmapIconSchema, roadmapLegendEntrySchema, roadmapLegendSchema, roadmapLinkSchema, roadmapNodeSchema, roadmapNodeStateSchema, roadmapStateOverlaySchema, roadmapStateSchema, rowsOf, safeParseRoadmapDocument, safeParseRoadmapStateOverlay, stageProgress, toggleVariants, useFormField, useSidebar, validateNoOverlap, validatePublishForm };
package/dist/index.js CHANGED
@@ -4310,7 +4310,20 @@ import * as React23 from "react";
4310
4310
  import * as SelectPrimitive from "@radix-ui/react-select";
4311
4311
  import { Check as Check3, ChevronDown as ChevronDown2, ChevronUp } from "lucide-react";
4312
4312
  import { jsx as jsx35, jsxs as jsxs21 } from "react/jsx-runtime";
4313
- var Select = SelectPrimitive.Root;
4313
+ var Select = ({ allowEmptyValue = false, onValueChange, value, ...props }) => {
4314
+ const latest = React23.useRef({ value, onValueChange, allowEmptyValue });
4315
+ latest.current = { value, onValueChange, allowEmptyValue };
4316
+ const handleValueChange = React23.useCallback((next) => {
4317
+ const current = latest.current;
4318
+ if (!current.onValueChange) return;
4319
+ const isControlled = current.value !== void 0;
4320
+ const isEmptyEcho = next === "" && isControlled && current.value !== "";
4321
+ if (isEmptyEcho && !current.allowEmptyValue) return;
4322
+ current.onValueChange(next);
4323
+ }, []);
4324
+ return /* @__PURE__ */ jsx35(SelectPrimitive.Root, { value, onValueChange: handleValueChange, ...props });
4325
+ };
4326
+ Select.displayName = "Select";
4314
4327
  var SelectGroup = SelectPrimitive.Group;
4315
4328
  var SelectValue = SelectPrimitive.Value;
4316
4329
  var SelectTrigger = React23.forwardRef(({ className, children, error, ...props }, ref) => /* @__PURE__ */ jsxs21(
@@ -6195,9 +6208,9 @@ import {
6195
6208
  useContext as useContext8,
6196
6209
  useState as useState5,
6197
6210
  useEffect as useEffect5,
6198
- useCallback as useCallback3,
6211
+ useCallback as useCallback4,
6199
6212
  useMemo as useMemo4,
6200
- useRef
6213
+ useRef as useRef2
6201
6214
  } from "react";
6202
6215
  import { jsx as jsx51 } from "react/jsx-runtime";
6203
6216
  var AuthContext = createContext8(void 0);
@@ -6211,12 +6224,12 @@ var AuthProvider = ({
6211
6224
  const [profile, setProfile] = useState5(null);
6212
6225
  const [profileLoading, setProfileLoading] = useState5(false);
6213
6226
  const [authLoading, setAuthLoading] = useState5(true);
6214
- const userRef = useRef(null);
6227
+ const userRef = useRef2(null);
6215
6228
  const isLoading = authLoading || profileLoading;
6216
6229
  useEffect5(() => {
6217
6230
  userRef.current = user;
6218
6231
  }, [user]);
6219
- const fetchProfile = useCallback3(
6232
+ const fetchProfile = useCallback4(
6220
6233
  async (userId) => {
6221
6234
  try {
6222
6235
  const { data, error } = await supabase.from(profilesTable).select("*").eq("id", userId).single();
@@ -6228,7 +6241,7 @@ var AuthProvider = ({
6228
6241
  },
6229
6242
  [supabase, profilesTable]
6230
6243
  );
6231
- const refreshProfile = useCallback3(async () => {
6244
+ const refreshProfile = useCallback4(async () => {
6232
6245
  if (userRef.current) {
6233
6246
  await fetchProfile(userRef.current.id);
6234
6247
  }
@@ -6255,21 +6268,21 @@ var AuthProvider = ({
6255
6268
  setProfile(null);
6256
6269
  }
6257
6270
  }, [user?.id, fetchProfile]);
6258
- const login = useCallback3(
6271
+ const login = useCallback4(
6259
6272
  async (email, password) => {
6260
6273
  const { error } = await supabase.auth.signInWithPassword({ email, password });
6261
6274
  return { error };
6262
6275
  },
6263
6276
  [supabase]
6264
6277
  );
6265
- const signup = useCallback3(
6278
+ const signup = useCallback4(
6266
6279
  async (email, password) => {
6267
6280
  const { error } = await supabase.auth.signUp({ email, password });
6268
6281
  return { error };
6269
6282
  },
6270
6283
  [supabase]
6271
6284
  );
6272
- const logout = useCallback3(async () => {
6285
+ const logout = useCallback4(async () => {
6273
6286
  await supabase.auth.signOut();
6274
6287
  }, [supabase]);
6275
6288
  const value = useMemo4(
@@ -6311,7 +6324,7 @@ var ProtectedRoute = ({
6311
6324
  };
6312
6325
 
6313
6326
  // src/components/iframe/dfl-remote.tsx
6314
- import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef2, useState as useState6 } from "react";
6327
+ import { useCallback as useCallback5, useEffect as useEffect6, useRef as useRef3, useState as useState6 } from "react";
6315
6328
 
6316
6329
  // src/components/iframe/types.ts
6317
6330
  var ALLOWED_ORIGINS = [
@@ -6337,12 +6350,12 @@ function DflRemote({
6337
6350
  style,
6338
6351
  allow = "clipboard-read; clipboard-write; fullscreen"
6339
6352
  }) {
6340
- const iframeRef = useRef2(null);
6353
+ const iframeRef = useRef3(null);
6341
6354
  const [ready, setReady] = useState6(false);
6342
6355
  const [height, setHeight] = useState6(void 0);
6343
6356
  const token = authToken ?? supabaseSession?.access_token ?? null;
6344
6357
  const userId = supabaseSession?.user?.id;
6345
- const sendToken = useCallback4(() => {
6358
+ const sendToken = useCallback5(() => {
6346
6359
  const iframe = iframeRef.current;
6347
6360
  if (!iframe?.contentWindow || !token) return;
6348
6361
  const targetOrigin = new URL(src).origin;
@@ -6397,7 +6410,7 @@ function DflRemote({
6397
6410
  }
6398
6411
 
6399
6412
  // src/components/iframe/iframe-aware.tsx
6400
- import { useCallback as useCallback5, useEffect as useEffect7, useRef as useRef3, useState as useState7 } from "react";
6413
+ import { useCallback as useCallback6, useEffect as useEffect7, useRef as useRef4, useState as useState7 } from "react";
6401
6414
 
6402
6415
  // src/components/iframe/iframe-context.tsx
6403
6416
  import { createContext as createContext9 } from "react";
@@ -6413,8 +6426,8 @@ function IframeAware({ children, allowedOrigins }) {
6413
6426
  const [token, setToken] = useState7(null);
6414
6427
  const [userId, setUserId] = useState7(null);
6415
6428
  const [ready, setReady] = useState7(false);
6416
- const containerRef = useRef3(null);
6417
- const checkOrigin = useCallback5(
6429
+ const containerRef = useRef4(null);
6430
+ const checkOrigin = useCallback6(
6418
6431
  (origin) => {
6419
6432
  if (allowedOrigins) {
6420
6433
  return allowedOrigins.some((p) => p.test(origin));
@@ -8757,7 +8770,7 @@ function routeEdge(source, target, edge, obstacles = []) {
8757
8770
  }
8758
8771
 
8759
8772
  // src/components/organisms/roadmap/Roadmap.tsx
8760
- import { useMemo as useMemo5, useRef as useRef6 } from "react";
8773
+ import { useMemo as useMemo5, useRef as useRef7 } from "react";
8761
8774
 
8762
8775
  // src/components/organisms/roadmap/RoadmapNode.tsx
8763
8776
  import { cva as cva14 } from "class-variance-authority";
@@ -8839,10 +8852,10 @@ function RoadmapGroupView({ group, testIdPrefix }) {
8839
8852
  }
8840
8853
 
8841
8854
  // src/components/organisms/roadmap/RoadmapEdgeLayer.tsx
8842
- import { useId as useId4, useEffect as useEffect10, useLayoutEffect as useLayoutEffect2, useRef as useRef5, useState as useState13 } from "react";
8855
+ import { useId as useId4, useEffect as useEffect10, useLayoutEffect as useLayoutEffect2, useRef as useRef6, useState as useState13 } from "react";
8843
8856
  import { jsx as jsx68, jsxs as jsxs43 } from "react/jsx-runtime";
8844
8857
  function Edge({ edge, path, instance, prefix }) {
8845
- const pathRef = useRef5(null);
8858
+ const pathRef = useRef6(null);
8846
8859
  const [midpoint, setMidpoint] = useState13();
8847
8860
  useLayoutEffect2(() => {
8848
8861
  const element = pathRef.current;
@@ -8953,7 +8966,7 @@ function RoadmapEdgeLayer({ containerRef, document: documentModel, testIdPrefix,
8953
8966
  // src/components/organisms/roadmap/Roadmap.tsx
8954
8967
  import { jsx as jsx69, jsxs as jsxs44 } from "react/jsx-runtime";
8955
8968
  function Roadmap({ document: document2, state, renderNode, onNodeClick, onAction, testIdPrefix = "roadmap", className = "", ariaLabel, debugPerf, collapseEmptyColumns = true }) {
8956
- const gridRef = useRef6(null);
8969
+ const gridRef = useRef7(null);
8957
8970
  const { placements } = useMemo5(() => placeNodes(document2), [document2]);
8958
8971
  const used = useMemo5(() => columnsUsed({ nodes: document2.nodes.filter((node) => !((node.span ?? 1) === 3 && (node.kind === "title" || node.kind === "label"))) }), [document2]);
8959
8972
  const weights = ["left", "center", "right"].map((column, index) => !collapseEmptyColumns || used.length === 0 || used.includes(column) ? index === 1 ? 1.25 : 1 : 0);
@@ -8999,9 +9012,9 @@ function useIframeAuth() {
8999
9012
  }
9000
9013
 
9001
9014
  // src/hooks/use-iframe-navigate.ts
9002
- import { useCallback as useCallback8 } from "react";
9015
+ import { useCallback as useCallback9 } from "react";
9003
9016
  function useIframeNavigate() {
9004
- return useCallback8((path) => {
9017
+ return useCallback9((path) => {
9005
9018
  if (!window.parent || window.parent === window) return;
9006
9019
  window.parent.postMessage(
9007
9020
  { type: "DFL_NAVIGATE", path },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devfellowship/components",
3
- "version": "3.5.1",
3
+ "version": "3.5.3",
4
4
  "description": "DFL Design System — UI components, hooks, utils and providers",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -58,14 +58,15 @@
58
58
  "build:lib": "tsup",
59
59
  "build:cli": "tsup --config tsup.cli.config.ts",
60
60
  "build:watch": "tsup --watch",
61
- "typecheck": "tsc --noEmit",
61
+ "typecheck": "tsc --noEmit && tsc --project tsconfig.e2e.json",
62
62
  "test": "vitest run",
63
63
  "prepublishOnly": "npm run build",
64
64
  "storybook": "storybook dev -p 6006",
65
65
  "build-storybook": "storybook build",
66
66
  "lint:no-docs": "node scripts/check-no-storybook-docs.mjs",
67
67
  "check:cli-offline": "node scripts/check-cli-bundle-offline.mjs",
68
- "check:canvas-isolated": "node scripts/check-canvas-entry-isolated.mjs"
68
+ "check:canvas-isolated": "node scripts/check-canvas-entry-isolated.mjs",
69
+ "test:e2e": "playwright test"
69
70
  },
70
71
  "peerDependencies": {
71
72
  "@dagrejs/dagre": ">=3.0.0",
@@ -94,6 +95,7 @@
94
95
  "@changesets/cli": "^2.31.0",
95
96
  "@dagrejs/dagre": "^3.1.1",
96
97
  "@devfellowship/ux-paths-capture": "^0.1.1",
98
+ "@playwright/test": "^1.63.0",
97
99
  "@storybook/addon-a11y": "^9.1.20",
98
100
  "@storybook/addon-themes": "^9.1.20",
99
101
  "@storybook/react": "^9.1.20",
@@ -102,6 +104,7 @@
102
104
  "@tailwindcss/vite": "^4.2.2",
103
105
  "@testing-library/jest-dom": "^6.9.1",
104
106
  "@testing-library/react": "^16.3.2",
107
+ "@testing-library/user-event": "^14.6.7",
105
108
  "@types/node": "^20.0.0",
106
109
  "@types/react": "^18.3.28",
107
110
  "@types/react-dom": "^18.3.7",