@open-mercato/ui 0.6.8-develop.7100.1.fbf66fca35 → 0.7.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.
Files changed (71) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/ai/records/ActivityCard.js +3 -4
  3. package/dist/ai/records/ActivityCard.js.map +2 -2
  4. package/dist/ai/records/DealCard.js +3 -4
  5. package/dist/ai/records/DealCard.js.map +2 -2
  6. package/dist/ai/records/ProductCard.js +2 -3
  7. package/dist/ai/records/ProductCard.js.map +2 -2
  8. package/dist/backend/AppShell.js +106 -129
  9. package/dist/backend/AppShell.js.map +2 -2
  10. package/dist/backend/DataTable.js +9 -22
  11. package/dist/backend/DataTable.js.map +2 -2
  12. package/dist/backend/detail/NotesSection.js +20 -26
  13. package/dist/backend/detail/NotesSection.js.map +2 -2
  14. package/dist/backend/icons/lucideRegistry.generated.js +0 -4
  15. package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
  16. package/dist/backend/injection/useRegisteredComponent.js +6 -24
  17. package/dist/backend/injection/useRegisteredComponent.js.map +2 -2
  18. package/dist/backend/inputs/ComboboxInput.js +4 -8
  19. package/dist/backend/inputs/ComboboxInput.js.map +2 -2
  20. package/dist/backend/inputs/LookupSelect.js +9 -9
  21. package/dist/backend/inputs/LookupSelect.js.map +2 -2
  22. package/dist/backend/sidebar/SidebarCustomizationEditor.js +30 -36
  23. package/dist/backend/sidebar/SidebarCustomizationEditor.js.map +2 -2
  24. package/dist/backend/utils/crud.js.map +2 -2
  25. package/dist/backend/utils/useAutoDiscoveredFields.js +8 -4
  26. package/dist/backend/utils/useAutoDiscoveredFields.js.map +2 -2
  27. package/dist/primitives/alert.js +1 -1
  28. package/dist/primitives/alert.js.map +2 -2
  29. package/dist/primitives/icon-button.js +1 -20
  30. package/dist/primitives/icon-button.js.map +2 -2
  31. package/dist/primitives/pagination.js +9 -14
  32. package/dist/primitives/pagination.js.map +2 -2
  33. package/dist/primitives/rich-editor.js +7 -14
  34. package/dist/primitives/rich-editor.js.map +2 -2
  35. package/dist/utils/format.js +5 -5
  36. package/dist/utils/format.js.map +2 -2
  37. package/package.json +4 -5
  38. package/src/ai/parts/__tests__/ConfirmationCard.test.tsx +0 -2
  39. package/src/ai/records/ActivityCard.tsx +3 -4
  40. package/src/ai/records/DealCard.tsx +3 -4
  41. package/src/ai/records/ProductCard.tsx +2 -3
  42. package/src/backend/AppShell.tsx +58 -89
  43. package/src/backend/DataTable.tsx +12 -46
  44. package/src/backend/__tests__/AppShell.test.tsx +1 -162
  45. package/src/backend/__tests__/DataTable.render.test.tsx +0 -37
  46. package/src/backend/__tests__/NotesSection.test.tsx +0 -89
  47. package/src/backend/detail/NotesSection.tsx +67 -92
  48. package/src/backend/icons/lucideRegistry.generated.tsx +0 -4
  49. package/src/backend/injection/useRegisteredComponent.tsx +8 -49
  50. package/src/backend/inputs/ComboboxInput.tsx +5 -21
  51. package/src/backend/inputs/LookupSelect.tsx +14 -10
  52. package/src/backend/inputs/__tests__/ComboboxInput.test.tsx +0 -142
  53. package/src/backend/inputs/__tests__/LookupSelect.test.tsx +40 -63
  54. package/src/backend/sidebar/SidebarCustomizationEditor.tsx +11 -14
  55. package/src/backend/utils/__tests__/useAutoDiscoveredFields.groupLabels.test.tsx +59 -0
  56. package/src/backend/utils/crud.ts +0 -2
  57. package/src/backend/utils/useAutoDiscoveredFields.ts +10 -4
  58. package/src/primitives/__tests__/alert.test.tsx +1 -41
  59. package/src/primitives/__tests__/icon-button.test.tsx +0 -70
  60. package/src/primitives/__tests__/notification.test.tsx +1 -1
  61. package/src/primitives/__tests__/pagination.test.tsx +0 -54
  62. package/src/primitives/__tests__/rich-editor.test.tsx +0 -43
  63. package/src/primitives/alert.tsx +2 -5
  64. package/src/primitives/icon-button.tsx +1 -20
  65. package/src/primitives/pagination.tsx +9 -40
  66. package/src/primitives/rich-editor.tsx +4 -9
  67. package/src/utils/__tests__/format.test.ts +14 -27
  68. package/src/utils/format.ts +5 -10
  69. package/src/backend/__tests__/DataTable.cappedPagination.test.tsx +0 -122
  70. package/src/backend/__tests__/DataTable.headerWrap.test.tsx +0 -111
  71. package/src/backend/__tests__/useRegisteredComponent.identity.test.tsx +0 -305
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/icons/lucideRegistry.generated.tsx"],
4
- "sourcesContent": ["/* eslint-disable */\n// AUTO-GENERATED by @open-mercato/ui build. Do not edit by hand.\n// Generated from discovered icon string usages across the repo.\n\nimport type { LucideIcon } from 'lucide-react';\nimport {\n Activity,\n AlarmClock,\n AlertCircle,\n AlertOctagon,\n AlertTriangle,\n Apple,\n Archive,\n AtSign,\n Award,\n BadgeCheck,\n Ban,\n Banknote,\n BarChart2,\n BarChart3,\n Bell,\n Bolt,\n Book,\n Bookmark,\n Bot,\n Box,\n Boxes,\n Briefcase,\n BriefcaseBusiness,\n Building,\n Building2,\n Calendar,\n CalendarCheck,\n CalendarClock,\n CalendarCog,\n CalendarMinus,\n CalendarOff,\n CalendarX,\n Check,\n CheckCircle,\n CheckCircle2,\n CheckSquare,\n Circle,\n ClipboardCheck,\n ClipboardList,\n Clock,\n Clock3,\n Coins,\n Copy,\n CopyX,\n CreditCard,\n Crown,\n Database,\n DollarSign,\n Download,\n ExternalLink,\n Eye,\n FileCheck,\n FileMinus,\n FilePenLine,\n Files,\n FileSpreadsheet,\n FileText,\n FilterX,\n Flag,\n Flame,\n FolderTree,\n Gauge,\n GitBranch,\n GitCompareArrows,\n GitPullRequestArrow,\n Globe,\n GraduationCap,\n Hammer,\n Hand,\n Handshake,\n Headphones,\n Heart,\n HelpCircle,\n Hourglass,\n Inbox,\n Key,\n KeyRound,\n Layers,\n Leaf,\n Lightbulb,\n LineChart,\n Link,\n List,\n ListX,\n Loader,\n Loader2,\n Lock,\n Mail,\n MailOpen,\n Map,\n MapPin,\n MapPinned,\n Megaphone,\n MessageCircle,\n MessageSquareReply,\n MinusCircle,\n Notebook,\n Package,\n PackageCheck,\n PackagePlus,\n PackageSearch,\n PackageX,\n PauseCircle,\n Percent,\n Phone,\n PhoneCall,\n PieChart,\n PlusSquare,\n Receipt,\n ReceiptText,\n RefreshCcw,\n RefreshCw,\n Repeat2,\n Reply,\n RotateCcw,\n Ruler,\n Send,\n Settings,\n Shapes,\n Share2,\n Shield,\n ShieldAlert,\n ShieldCheck,\n ShoppingBag,\n ShoppingCart,\n Shuffle,\n Sliders,\n Smartphone,\n Snowflake,\n Sparkles,\n Star,\n StickyNote,\n Store,\n Tag,\n Target,\n ThumbsUp,\n Ticket,\n Timer,\n Trash2,\n TrendingUp,\n TriangleAlert,\n Trophy,\n Truck,\n Undo,\n Undo2,\n Unlock,\n User,\n UserCheck,\n UserMinus,\n UserPlus,\n UserRound,\n Users,\n UserX,\n Wallet,\n Warehouse,\n Webhook,\n Workflow,\n Wrench,\n X,\n XCircle,\n ZapOff,\n} from 'lucide-react';\n\nexport const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {\n 'activity': Activity,\n 'alarm-clock': AlarmClock,\n 'alert-circle': AlertCircle,\n 'alert-octagon': AlertOctagon,\n 'alert-triangle': AlertTriangle,\n 'apple': Apple,\n 'archive': Archive,\n 'at-sign': AtSign,\n 'award': Award,\n 'badge-check': BadgeCheck,\n 'ban': Ban,\n 'banknote': Banknote,\n 'bar-chart-2': BarChart2,\n 'bar-chart-3': BarChart3,\n 'bell': Bell,\n 'bolt': Bolt,\n 'book': Book,\n 'bookmark': Bookmark,\n 'bot': Bot,\n 'box': Box,\n 'boxes': Boxes,\n 'briefcase': Briefcase,\n 'briefcase-business': BriefcaseBusiness,\n 'building': Building,\n 'building2': Building2,\n 'calendar': Calendar,\n 'calendar-check': CalendarCheck,\n 'calendar-clock': CalendarClock,\n 'calendar-cog': CalendarCog,\n 'calendar-minus': CalendarMinus,\n 'calendar-off': CalendarOff,\n 'calendar-x': CalendarX,\n 'check': Check,\n 'check-circle': CheckCircle,\n 'check-circle-2': CheckCircle2,\n 'check-square': CheckSquare,\n 'circle': Circle,\n 'clipboard-check': ClipboardCheck,\n 'clipboard-list': ClipboardList,\n 'clock': Clock,\n 'clock-3': Clock3,\n 'coins': Coins,\n 'copy': Copy,\n 'copy-x': CopyX,\n 'credit-card': CreditCard,\n 'crown': Crown,\n 'database': Database,\n 'dollar-sign': DollarSign,\n 'download': Download,\n 'external-link': ExternalLink,\n 'eye': Eye,\n 'file-check': FileCheck,\n 'file-minus': FileMinus,\n 'file-pen-line': FilePenLine,\n 'file-spreadsheet': FileSpreadsheet,\n 'file-text': FileText,\n 'files': Files,\n 'filter-x': FilterX,\n 'flag': Flag,\n 'flame': Flame,\n 'folder-tree': FolderTree,\n 'gauge': Gauge,\n 'git-branch': GitBranch,\n 'git-compare-arrows': GitCompareArrows,\n 'git-pull-request-arrow': GitPullRequestArrow,\n 'globe': Globe,\n 'graduation-cap': GraduationCap,\n 'hammer': Hammer,\n 'hand': Hand,\n 'handshake': Handshake,\n 'headphones': Headphones,\n 'heart': Heart,\n 'help-circle': HelpCircle,\n 'hourglass': Hourglass,\n 'inbox': Inbox,\n 'key': Key,\n 'key-round': KeyRound,\n 'layers': Layers,\n 'leaf': Leaf,\n 'lightbulb': Lightbulb,\n 'line-chart': LineChart,\n 'link': Link,\n 'list': List,\n 'list-x': ListX,\n 'loader': Loader,\n 'loader-2': Loader2,\n 'lock': Lock,\n 'mail': Mail,\n 'mail-open': MailOpen,\n 'map': Map,\n 'map-pin': MapPin,\n 'map-pinned': MapPinned,\n 'megaphone': Megaphone,\n 'message-circle': MessageCircle,\n 'message-square-reply': MessageSquareReply,\n 'minus-circle': MinusCircle,\n 'notebook': Notebook,\n 'package': Package,\n 'package-check': PackageCheck,\n 'package-plus': PackagePlus,\n 'package-search': PackageSearch,\n 'package-x': PackageX,\n 'pause-circle': PauseCircle,\n 'percent': Percent,\n 'phone': Phone,\n 'phone-call': PhoneCall,\n 'pie-chart': PieChart,\n 'plus-square': PlusSquare,\n 'receipt': Receipt,\n 'receipt-text': ReceiptText,\n 'refresh-ccw': RefreshCcw,\n 'refresh-cw': RefreshCw,\n 'repeat-2': Repeat2,\n 'reply': Reply,\n 'rotate-ccw': RotateCcw,\n 'ruler': Ruler,\n 'send': Send,\n 'settings': Settings,\n 'shapes': Shapes,\n 'share-2': Share2,\n 'shield': Shield,\n 'shield-alert': ShieldAlert,\n 'shield-check': ShieldCheck,\n 'shopping-bag': ShoppingBag,\n 'shopping-cart': ShoppingCart,\n 'shuffle': Shuffle,\n 'sliders': Sliders,\n 'smartphone': Smartphone,\n 'snowflake': Snowflake,\n 'sparkles': Sparkles,\n 'star': Star,\n 'sticky-note': StickyNote,\n 'store': Store,\n 'tag': Tag,\n 'target': Target,\n 'thumbs-up': ThumbsUp,\n 'ticket': Ticket,\n 'timer': Timer,\n 'trash-2': Trash2,\n 'trending-up': TrendingUp,\n 'triangle-alert': TriangleAlert,\n 'trophy': Trophy,\n 'truck': Truck,\n 'undo': Undo,\n 'undo-2': Undo2,\n 'unlock': Unlock,\n 'user': User,\n 'user-check': UserCheck,\n 'user-minus': UserMinus,\n 'user-plus': UserPlus,\n 'user-round': UserRound,\n 'user-x': UserX,\n 'users': Users,\n 'wallet': Wallet,\n 'warehouse': Warehouse,\n 'webhook': Webhook,\n 'workflow': Workflow,\n 'wrench': Wrench,\n 'x': X,\n 'x-circle': XCircle,\n 'zap-off': ZapOff,\n};\n"],
5
- "mappings": "AAKA;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,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,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,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,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,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,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,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,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,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",
4
+ "sourcesContent": ["/* eslint-disable */\n// AUTO-GENERATED by @open-mercato/ui build. Do not edit by hand.\n// Generated from discovered icon string usages across the repo.\n\nimport type { LucideIcon } from 'lucide-react';\nimport {\n Activity,\n AlarmClock,\n AlertCircle,\n AlertOctagon,\n AlertTriangle,\n Apple,\n Archive,\n Award,\n BadgeCheck,\n Ban,\n Banknote,\n BarChart2,\n BarChart3,\n Bell,\n Bolt,\n Book,\n Bookmark,\n Box,\n Boxes,\n Briefcase,\n BriefcaseBusiness,\n Building,\n Building2,\n Calendar,\n CalendarCheck,\n CalendarClock,\n CalendarCog,\n CalendarMinus,\n CalendarOff,\n CalendarX,\n Check,\n CheckCircle,\n CheckCircle2,\n CheckSquare,\n Circle,\n ClipboardCheck,\n ClipboardList,\n Clock,\n Clock3,\n Coins,\n Copy,\n CopyX,\n CreditCard,\n Crown,\n Database,\n DollarSign,\n Download,\n ExternalLink,\n Eye,\n FileCheck,\n FileMinus,\n FilePenLine,\n Files,\n FileSpreadsheet,\n FileText,\n FilterX,\n Flag,\n Flame,\n FolderTree,\n Gauge,\n GitBranch,\n GitCompareArrows,\n GitPullRequestArrow,\n Globe,\n GraduationCap,\n Hammer,\n Hand,\n Handshake,\n Headphones,\n Heart,\n HelpCircle,\n Hourglass,\n Inbox,\n Key,\n KeyRound,\n Layers,\n Leaf,\n Lightbulb,\n LineChart,\n Link,\n List,\n ListX,\n Loader,\n Loader2,\n Lock,\n Mail,\n MailOpen,\n Map,\n MapPin,\n MapPinned,\n Megaphone,\n MessageCircle,\n MessageSquareReply,\n MinusCircle,\n Notebook,\n Package,\n PackageCheck,\n PackagePlus,\n PackageSearch,\n PackageX,\n PauseCircle,\n Percent,\n Phone,\n PhoneCall,\n PieChart,\n PlusSquare,\n Receipt,\n ReceiptText,\n RefreshCcw,\n RefreshCw,\n Repeat2,\n Reply,\n RotateCcw,\n Ruler,\n Send,\n Settings,\n Shapes,\n Share2,\n Shield,\n ShieldAlert,\n ShieldCheck,\n ShoppingBag,\n ShoppingCart,\n Shuffle,\n Sliders,\n Smartphone,\n Snowflake,\n Sparkles,\n Star,\n StickyNote,\n Store,\n Tag,\n Target,\n ThumbsUp,\n Ticket,\n Timer,\n Trash2,\n TrendingUp,\n TriangleAlert,\n Trophy,\n Truck,\n Undo,\n Undo2,\n Unlock,\n User,\n UserCheck,\n UserMinus,\n UserPlus,\n UserRound,\n Users,\n UserX,\n Wallet,\n Warehouse,\n Webhook,\n Workflow,\n Wrench,\n X,\n XCircle,\n ZapOff,\n} from 'lucide-react';\n\nexport const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {\n 'activity': Activity,\n 'alarm-clock': AlarmClock,\n 'alert-circle': AlertCircle,\n 'alert-octagon': AlertOctagon,\n 'alert-triangle': AlertTriangle,\n 'apple': Apple,\n 'archive': Archive,\n 'award': Award,\n 'badge-check': BadgeCheck,\n 'ban': Ban,\n 'banknote': Banknote,\n 'bar-chart-2': BarChart2,\n 'bar-chart-3': BarChart3,\n 'bell': Bell,\n 'bolt': Bolt,\n 'book': Book,\n 'bookmark': Bookmark,\n 'box': Box,\n 'boxes': Boxes,\n 'briefcase': Briefcase,\n 'briefcase-business': BriefcaseBusiness,\n 'building': Building,\n 'building2': Building2,\n 'calendar': Calendar,\n 'calendar-check': CalendarCheck,\n 'calendar-clock': CalendarClock,\n 'calendar-cog': CalendarCog,\n 'calendar-minus': CalendarMinus,\n 'calendar-off': CalendarOff,\n 'calendar-x': CalendarX,\n 'check': Check,\n 'check-circle': CheckCircle,\n 'check-circle-2': CheckCircle2,\n 'check-square': CheckSquare,\n 'circle': Circle,\n 'clipboard-check': ClipboardCheck,\n 'clipboard-list': ClipboardList,\n 'clock': Clock,\n 'clock-3': Clock3,\n 'coins': Coins,\n 'copy': Copy,\n 'copy-x': CopyX,\n 'credit-card': CreditCard,\n 'crown': Crown,\n 'database': Database,\n 'dollar-sign': DollarSign,\n 'download': Download,\n 'external-link': ExternalLink,\n 'eye': Eye,\n 'file-check': FileCheck,\n 'file-minus': FileMinus,\n 'file-pen-line': FilePenLine,\n 'file-spreadsheet': FileSpreadsheet,\n 'file-text': FileText,\n 'files': Files,\n 'filter-x': FilterX,\n 'flag': Flag,\n 'flame': Flame,\n 'folder-tree': FolderTree,\n 'gauge': Gauge,\n 'git-branch': GitBranch,\n 'git-compare-arrows': GitCompareArrows,\n 'git-pull-request-arrow': GitPullRequestArrow,\n 'globe': Globe,\n 'graduation-cap': GraduationCap,\n 'hammer': Hammer,\n 'hand': Hand,\n 'handshake': Handshake,\n 'headphones': Headphones,\n 'heart': Heart,\n 'help-circle': HelpCircle,\n 'hourglass': Hourglass,\n 'inbox': Inbox,\n 'key': Key,\n 'key-round': KeyRound,\n 'layers': Layers,\n 'leaf': Leaf,\n 'lightbulb': Lightbulb,\n 'line-chart': LineChart,\n 'link': Link,\n 'list': List,\n 'list-x': ListX,\n 'loader': Loader,\n 'loader-2': Loader2,\n 'lock': Lock,\n 'mail': Mail,\n 'mail-open': MailOpen,\n 'map': Map,\n 'map-pin': MapPin,\n 'map-pinned': MapPinned,\n 'megaphone': Megaphone,\n 'message-circle': MessageCircle,\n 'message-square-reply': MessageSquareReply,\n 'minus-circle': MinusCircle,\n 'notebook': Notebook,\n 'package': Package,\n 'package-check': PackageCheck,\n 'package-plus': PackagePlus,\n 'package-search': PackageSearch,\n 'package-x': PackageX,\n 'pause-circle': PauseCircle,\n 'percent': Percent,\n 'phone': Phone,\n 'phone-call': PhoneCall,\n 'pie-chart': PieChart,\n 'plus-square': PlusSquare,\n 'receipt': Receipt,\n 'receipt-text': ReceiptText,\n 'refresh-ccw': RefreshCcw,\n 'refresh-cw': RefreshCw,\n 'repeat-2': Repeat2,\n 'reply': Reply,\n 'rotate-ccw': RotateCcw,\n 'ruler': Ruler,\n 'send': Send,\n 'settings': Settings,\n 'shapes': Shapes,\n 'share-2': Share2,\n 'shield': Shield,\n 'shield-alert': ShieldAlert,\n 'shield-check': ShieldCheck,\n 'shopping-bag': ShoppingBag,\n 'shopping-cart': ShoppingCart,\n 'shuffle': Shuffle,\n 'sliders': Sliders,\n 'smartphone': Smartphone,\n 'snowflake': Snowflake,\n 'sparkles': Sparkles,\n 'star': Star,\n 'sticky-note': StickyNote,\n 'store': Store,\n 'tag': Tag,\n 'target': Target,\n 'thumbs-up': ThumbsUp,\n 'ticket': Ticket,\n 'timer': Timer,\n 'trash-2': Trash2,\n 'trending-up': TrendingUp,\n 'triangle-alert': TriangleAlert,\n 'trophy': Trophy,\n 'truck': Truck,\n 'undo': Undo,\n 'undo-2': Undo2,\n 'unlock': Unlock,\n 'user': User,\n 'user-check': UserCheck,\n 'user-minus': UserMinus,\n 'user-plus': UserPlus,\n 'user-round': UserRound,\n 'user-x': UserX,\n 'users': Users,\n 'wallet': Wallet,\n 'warehouse': Warehouse,\n 'webhook': Webhook,\n 'workflow': Workflow,\n 'wrench': Wrench,\n 'x': X,\n 'x-circle': XCircle,\n 'zap-off': ZapOff,\n};\n"],
5
+ "mappings": "AAKA;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,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,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,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,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,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,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,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,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,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",
6
6
  "names": []
7
7
  }
@@ -21,19 +21,6 @@ class ReplacementErrorBoundary extends React.Component {
21
21
  return this.props.children;
22
22
  }
23
23
  }
24
- const composedWrappers = /* @__PURE__ */ new WeakMap();
25
- function applyWrapper(wrapper, Base) {
26
- let byBase = composedWrappers.get(wrapper);
27
- if (!byBase) {
28
- byBase = /* @__PURE__ */ new WeakMap();
29
- composedWrappers.set(wrapper, byBase);
30
- }
31
- const cached = byBase.get(Base);
32
- if (cached) return cached;
33
- const composed = wrapper(Base);
34
- byBase.set(Base, composed);
35
- return composed;
36
- }
37
24
  function resolveComponent(componentId, fallback, userFeatures) {
38
25
  const entry = getComponentEntry(componentId);
39
26
  const original = entry?.component ?? fallback ?? null;
@@ -61,7 +48,7 @@ function resolveComponent(componentId, fallback, userFeatures) {
61
48
  if ("propsTransform" in override) transforms.push(override.propsTransform);
62
49
  }
63
50
  const base = replacement ?? original;
64
- const wrapped = wrappers.reduce((acc, wrapper) => applyWrapper(wrapper, acc), base);
51
+ const wrapped = wrappers.reduce((acc, wrapper) => wrapper(acc), base);
65
52
  return {
66
53
  original,
67
54
  wrapped,
@@ -71,17 +58,13 @@ function resolveComponent(componentId, fallback, userFeatures) {
71
58
  };
72
59
  }
73
60
  function useRegisteredComponent(componentId, fallback) {
74
- const fallbackRef = React.useRef(fallback);
75
- fallbackRef.current = fallback;
76
- const registered = React.useRef(null);
77
- if (!registered.current || registered.current.componentId !== componentId) {
61
+ return React.useMemo(() => {
78
62
  const Registered = (props) => {
79
63
  const userFeatures = useOverrideUserFeatures();
80
64
  const overrideRevision = useOverrideRegistryRevision();
81
- const currentFallback = fallbackRef.current;
82
65
  const { original, wrapped, transforms, replacementOverride, replacementModule } = React.useMemo(
83
- () => resolveComponent(componentId, currentFallback, userFeatures),
84
- [currentFallback, overrideRevision, userFeatures]
66
+ () => resolveComponent(componentId, fallback, userFeatures),
67
+ [overrideRevision, userFeatures]
85
68
  );
86
69
  if (!original || !wrapped) return null;
87
70
  const transformed = transforms.reduce((current, transform) => transform(current), props);
@@ -105,9 +88,8 @@ function useRegisteredComponent(componentId, fallback) {
105
88
  );
106
89
  };
107
90
  Registered.displayName = `RegisteredComponent(${componentId})`;
108
- registered.current = { componentId, Component: Registered };
109
- }
110
- return registered.current.Component;
91
+ return Registered;
92
+ }, [componentId, fallback]);
111
93
  }
112
94
  var useRegisteredComponent_default = useRegisteredComponent;
113
95
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/injection/useRegisteredComponent.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport type { ComponentType } from 'react'\nimport type { ComponentOverride } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { getComponentEntry, getComponentOverrides } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useOverrideRegistryRevision, useOverrideUserFeatures } from './ComponentOverrideProvider'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui').child({ component: 'useRegisteredComponent' })\n\nclass ReplacementErrorBoundary extends React.Component<\n { fallback: React.ReactNode; onError: (error: unknown) => void; children: React.ReactNode },\n { hasError: boolean }\n> {\n constructor(props: { fallback: React.ReactNode; onError: (error: unknown) => void; children: React.ReactNode }) {\n super(props)\n this.state = { hasError: false }\n }\n\n static getDerivedStateFromError(): { hasError: boolean } {\n return { hasError: true }\n }\n\n componentDidCatch(error: unknown): void {\n this.props.onError(error)\n }\n\n render() {\n if (this.state.hasError) return this.props.fallback\n return this.props.children\n }\n}\n\ntype Resolution<TProps> = {\n original: ComponentType<TProps> | null\n wrapped: ComponentType<TProps> | null\n transforms: Array<(props: TProps) => TProps>\n replacementOverride: ComponentOverride | null\n replacementModule: string\n}\n\n/**\n * Calling a wrapper override returns a fresh component every time, which would\n * reintroduce the identity churn this hook exists to avoid. Memoizing per\n * (wrapper, wrapped component) pair keeps the composed component referentially\n * stable for as long as both inputs are.\n */\nconst composedWrappers = new WeakMap<object, WeakMap<object, unknown>>()\n\nfunction applyWrapper<TProps>(\n wrapper: (Original: ComponentType<TProps>) => ComponentType<TProps>,\n Base: ComponentType<TProps>,\n): ComponentType<TProps> {\n let byBase = composedWrappers.get(wrapper)\n if (!byBase) {\n byBase = new WeakMap<object, unknown>()\n composedWrappers.set(wrapper, byBase)\n }\n const cached = byBase.get(Base)\n if (cached) return cached as ComponentType<TProps>\n const composed = wrapper(Base)\n byBase.set(Base, composed)\n return composed\n}\n\nfunction resolveComponent<TProps>(\n componentId: string,\n fallback: ComponentType<TProps> | undefined,\n userFeatures: readonly string[],\n): Resolution<TProps> {\n const entry = getComponentEntry(componentId)\n const original = (entry?.component as ComponentType<TProps> | undefined) ?? fallback ?? null\n if (!original) {\n if (process.env.NODE_ENV !== 'production' && !fallback) {\n logger.warn('Component is not registered', { componentId })\n }\n return { original: null, wrapped: null, transforms: [], replacementOverride: null, replacementModule: 'unknown' }\n }\n\n const overrides = getComponentOverrides(componentId, userFeatures)\n const replacementOverrides = overrides.filter((override) => 'replacement' in override)\n if (process.env.NODE_ENV !== 'production' && replacementOverrides.length > 1) {\n logger.warn('Multiple replacements registered; highest-priority replacement is applied', { componentId })\n }\n\n let replacement: ComponentType<TProps> | null = null\n let replacementOverride: ComponentOverride | null = null\n const wrappers: Array<(Original: ComponentType<TProps>) => ComponentType<TProps>> = []\n const transforms: Array<(props: TProps) => TProps> = []\n\n for (const override of overrides) {\n if ('replacement' in override) {\n replacement = override.replacement as ComponentType<TProps>\n replacementOverride = override\n }\n if ('wrapper' in override) wrappers.push(override.wrapper as (Original: ComponentType<TProps>) => ComponentType<TProps>)\n if ('propsTransform' in override) transforms.push(override.propsTransform as (props: TProps) => TProps)\n }\n\n const base = replacement ?? original\n const wrapped = wrappers.reduce<ComponentType<TProps>>((acc, wrapper) => applyWrapper(wrapper, acc), base)\n\n return {\n original,\n wrapped,\n transforms,\n replacementOverride,\n replacementModule: replacementOverride?.metadata?.module ?? 'unknown',\n }\n}\n\n/**\n * The returned component's identity must depend only on `componentId` \u2014 never\n * on the override registry, and never on the `fallback` a caller happens to\n * pass on this render.\n *\n * Overrides arrive asynchronously: `ComponentOverridesBootstrap` dynamically\n * imports the generated override module and hands the provider a fresh array,\n * which bumps the registry revision some time after first paint. Resolving the\n * component in a `useMemo` keyed on that revision handed callers a brand-new\n * function on every bump, so React saw a different element type at that\n * position and unmounted the whole subtree \u2014 discarding its DOM and state. On\n * the login form that threw away credentials the user had already typed\n * (#5037), and the same hazard applied to every host of a registered section.\n *\n * Resolution therefore happens *inside* a stable component. When the revision\n * carries no override for this id, `wrapped` keeps its previous identity and\n * React reconciles in place; only a genuine replacement or wrapper swaps the\n * rendered type, where a remount is the correct behaviour.\n *\n * The identity is held in a ref rather than a `useMemo`, because `useMemo` is a\n * performance hint React is free to discard, and identity here is a correctness\n * requirement rather than an optimisation. `fallback` is read through a ref for\n * the same reason: a host that builds its fallback inline would otherwise swap\n * the component this hook hands back on every render. That still cannot save a\n * subtree rendered *through* such a fallback \u2014 the fallback itself is then the\n * element type, and only the host can stabilise it \u2014 but it keeps the churn\n * from spreading to hosts whose id does resolve to a registered component.\n */\nexport function useRegisteredComponent<TProps>(\n componentId: string,\n fallback?: ComponentType<TProps>,\n): ComponentType<TProps> {\n const fallbackRef = React.useRef<ComponentType<TProps> | undefined>(fallback)\n fallbackRef.current = fallback\n\n const registered = React.useRef<{ componentId: string; Component: ComponentType<TProps> } | null>(null)\n if (!registered.current || registered.current.componentId !== componentId) {\n const Registered = (props: TProps) => {\n const userFeatures = useOverrideUserFeatures()\n const overrideRevision = useOverrideRegistryRevision()\n const currentFallback = fallbackRef.current\n const { original, wrapped, transforms, replacementOverride, replacementModule } = React.useMemo(\n () => resolveComponent<TProps>(componentId, currentFallback, userFeatures),\n [currentFallback, overrideRevision, userFeatures],\n )\n\n if (!original || !wrapped) return null\n\n const transformed = transforms.reduce((current, transform) => transform(current), props)\n const Fallback = React.createElement(original as React.ComponentType<Record<string, unknown>>, transformed as Record<string, unknown>)\n if (\n process.env.NODE_ENV !== 'production'\n && replacementOverride\n && 'replacement' in replacementOverride\n ) {\n const validation = replacementOverride.propsSchema.safeParse(transformed)\n if (!validation.success) {\n logger.error('Props schema validation failed for replacement', { componentId, module: replacementModule, issues: validation.error.format() })\n return Fallback\n }\n }\n return (\n <ReplacementErrorBoundary\n fallback={Fallback}\n onError={(error) => {\n logger.error('Component replacement failed', { componentId, module: replacementModule, err: error })\n }}\n >\n {React.createElement(wrapped as React.ComponentType<Record<string, unknown>>, transformed as Record<string, unknown>)}\n </ReplacementErrorBoundary>\n )\n }\n\n Registered.displayName = `RegisteredComponent(${componentId})`\n registered.current = { componentId, Component: Registered }\n }\n\n return registered.current.Component\n}\n\nexport default useRegisteredComponent\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport type { ComponentType } from 'react'\nimport type { ComponentOverride } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { getComponentEntry, getComponentOverrides } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useOverrideRegistryRevision, useOverrideUserFeatures } from './ComponentOverrideProvider'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui').child({ component: 'useRegisteredComponent' })\n\nclass ReplacementErrorBoundary extends React.Component<\n { fallback: React.ReactNode; onError: (error: unknown) => void; children: React.ReactNode },\n { hasError: boolean }\n> {\n constructor(props: { fallback: React.ReactNode; onError: (error: unknown) => void; children: React.ReactNode }) {\n super(props)\n this.state = { hasError: false }\n }\n\n static getDerivedStateFromError(): { hasError: boolean } {\n return { hasError: true }\n }\n\n componentDidCatch(error: unknown): void {\n this.props.onError(error)\n }\n\n render() {\n if (this.state.hasError) return this.props.fallback\n return this.props.children\n }\n}\n\ntype Resolution<TProps> = {\n original: ComponentType<TProps> | null\n wrapped: ComponentType<TProps> | null\n transforms: Array<(props: TProps) => TProps>\n replacementOverride: ComponentOverride | null\n replacementModule: string\n}\n\nfunction resolveComponent<TProps>(\n componentId: string,\n fallback: ComponentType<TProps> | undefined,\n userFeatures: readonly string[],\n): Resolution<TProps> {\n const entry = getComponentEntry(componentId)\n const original = (entry?.component as ComponentType<TProps> | undefined) ?? fallback ?? null\n if (!original) {\n if (process.env.NODE_ENV !== 'production' && !fallback) {\n logger.warn('Component is not registered', { componentId })\n }\n return { original: null, wrapped: null, transforms: [], replacementOverride: null, replacementModule: 'unknown' }\n }\n\n const overrides = getComponentOverrides(componentId, userFeatures)\n const replacementOverrides = overrides.filter((override) => 'replacement' in override)\n if (process.env.NODE_ENV !== 'production' && replacementOverrides.length > 1) {\n logger.warn('Multiple replacements registered; highest-priority replacement is applied', { componentId })\n }\n\n let replacement: ComponentType<TProps> | null = null\n let replacementOverride: ComponentOverride | null = null\n const wrappers: Array<(Original: ComponentType<TProps>) => ComponentType<TProps>> = []\n const transforms: Array<(props: TProps) => TProps> = []\n\n for (const override of overrides) {\n if ('replacement' in override) {\n replacement = override.replacement as ComponentType<TProps>\n replacementOverride = override\n }\n if ('wrapper' in override) wrappers.push(override.wrapper as (Original: ComponentType<TProps>) => ComponentType<TProps>)\n if ('propsTransform' in override) transforms.push(override.propsTransform as (props: TProps) => TProps)\n }\n\n const base = replacement ?? original\n const wrapped = wrappers.reduce<ComponentType<TProps>>((acc, wrapper) => wrapper(acc), base)\n\n return {\n original,\n wrapped,\n transforms,\n replacementOverride,\n replacementModule: replacementOverride?.metadata?.module ?? 'unknown',\n }\n}\n\n/**\n * The returned component's identity must depend only on `componentId` and\n * `fallback` \u2014 never on the override registry.\n *\n * Overrides arrive asynchronously: `ComponentOverridesBootstrap` dynamically\n * imports the generated override module and hands the provider a fresh array,\n * which bumps the registry revision some time after first paint. Resolving the\n * component in a `useMemo` keyed on that revision handed callers a brand-new\n * function on every bump, so React saw a different element type at that\n * position and unmounted the whole subtree \u2014 discarding its DOM and state. On\n * the login form that threw away credentials the user had already typed\n * (#5037), and the same hazard applied to every host of a registered section.\n *\n * Resolution therefore happens *inside* a stable component. When the revision\n * carries no override for this id, `wrapped` keeps its previous identity and\n * React reconciles in place; only a genuine replacement or wrapper swaps the\n * rendered type, where a remount is the correct behaviour.\n */\nexport function useRegisteredComponent<TProps>(\n componentId: string,\n fallback?: ComponentType<TProps>,\n): ComponentType<TProps> {\n return React.useMemo(() => {\n const Registered = (props: TProps) => {\n const userFeatures = useOverrideUserFeatures()\n const overrideRevision = useOverrideRegistryRevision()\n const { original, wrapped, transforms, replacementOverride, replacementModule } = React.useMemo(\n () => resolveComponent<TProps>(componentId, fallback, userFeatures),\n [overrideRevision, userFeatures],\n )\n\n if (!original || !wrapped) return null\n\n const transformed = transforms.reduce((current, transform) => transform(current), props)\n const Fallback = React.createElement(original as React.ComponentType<Record<string, unknown>>, transformed as Record<string, unknown>)\n if (\n process.env.NODE_ENV !== 'production'\n && replacementOverride\n && 'replacement' in replacementOverride\n ) {\n const validation = replacementOverride.propsSchema.safeParse(transformed)\n if (!validation.success) {\n logger.error('Props schema validation failed for replacement', { componentId, module: replacementModule, issues: validation.error.format() })\n return Fallback\n }\n }\n return (\n <ReplacementErrorBoundary\n fallback={Fallback}\n onError={(error) => {\n logger.error('Component replacement failed', { componentId, module: replacementModule, err: error })\n }}\n >\n {React.createElement(wrapped as React.ComponentType<Record<string, unknown>>, transformed as Record<string, unknown>)}\n </ReplacementErrorBoundary>\n )\n }\n\n Registered.displayName = `RegisteredComponent(${componentId})`\n return Registered\n }, [componentId, fallback])\n}\n\nexport default useRegisteredComponent\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -65,7 +65,6 @@ function ComboboxInput({
65
65
  const blurClosePendingRef = React.useRef(false);
66
66
  const suppressOpenOnFocusRef = React.useRef(Boolean(autoFocus && !disabled));
67
67
  const eagerFallbackLoadedValueRef = React.useRef(null);
68
- const userTypedRef = React.useRef(false);
69
68
  const staticOptions = React.useMemo(
70
69
  () => normalizeOptions([...seedOptions ?? [], ...suggestions ?? []]),
71
70
  [seedOptions, suggestions]
@@ -187,10 +186,10 @@ function ComboboxInput({
187
186
  };
188
187
  }, [value, disabled, knownLabelValues, coveredOptionValues, eagerResolveLabel, loadSuggestions]);
189
188
  React.useEffect(() => {
190
- const option = optionMap.get(value);
191
- const hasRealLabel = Boolean(option && option.label !== option.value);
192
- if (document.activeElement === inputRef.current && (userTypedRef.current || !hasRealLabel)) return;
193
- setInput(option?.label ?? value ?? "");
189
+ if (document.activeElement !== inputRef.current) {
190
+ const option = optionMap.get(value);
191
+ setInput(option?.label ?? value ?? "");
192
+ }
194
193
  }, [value, optionMap]);
195
194
  const selectValue = React.useCallback(
196
195
  (nextValue) => {
@@ -202,7 +201,6 @@ function ComboboxInput({
202
201
  setInput(option?.label ?? trimmed);
203
202
  setShowSuggestions(false);
204
203
  setSelectedIndex(-1);
205
- userTypedRef.current = false;
206
204
  },
207
205
  [disabled, onChange, optionMap, resetBlurCloseState]
208
206
  );
@@ -350,14 +348,12 @@ function ComboboxInput({
350
348
  },
351
349
  onChange: (event) => {
352
350
  setTouched(true);
353
- userTypedRef.current = true;
354
351
  setInput(event.target.value);
355
352
  setShowSuggestions(true);
356
353
  setSelectedIndex(-1);
357
354
  },
358
355
  onKeyDown: handleKeyDown,
359
356
  onBlur: () => {
360
- userTypedRef.current = false;
361
357
  blurClosePendingRef.current = true;
362
358
  clearBlurCloseTimer();
363
359
  if (loadingRef.current) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/ComboboxInput.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { X } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\n\nexport type ComboboxOption = {\n value: string\n label: string\n description?: string | null\n}\n\nexport type ComboboxInputProps = {\n value: string\n onChange: (next: string) => void\n placeholder?: string\n suggestions?: Array<string | ComboboxOption>\n // Options to hydrate the option map up front (typically the linked entity's\n // display fields, already present in a record-detail payload). Merged with\n // `suggestions` so a pre-selected value renders its label without interaction.\n seedOptions?: ComboboxOption[]\n loadSuggestions?: (query?: string) => Promise<Array<string | ComboboxOption>>\n // Eagerly resolve a pre-selected `value` to a human label when it is not\n // covered by `suggestions`/`seedOptions`/`loadSuggestions` results. Runs once\n // per value, before any user interaction. May be sync or async.\n resolveLabel?: (value: string) => string | Promise<string>\n resolveDescription?: (value: string) => string | null | undefined\n autoFocus?: boolean\n disabled?: boolean\n allowCustomValues?: boolean\n clearable?: boolean\n clearLabel?: string\n}\n\nfunction normalizeOptions(input?: Array<string | ComboboxOption>): ComboboxOption[] {\n if (!Array.isArray(input)) return []\n return input\n .map((option) => {\n if (typeof option === 'string') {\n const trimmed = option.trim()\n if (!trimmed) return null\n return { value: trimmed, label: trimmed }\n }\n const value = typeof option.value === 'string' ? option.value.trim() : ''\n if (!value) return null\n return {\n value,\n label: option.label?.trim() || value,\n description: option.description ?? null,\n }\n })\n .filter((option): option is ComboboxOption => !!option)\n}\n\nfunction areOptionsEqual(a: ComboboxOption[], b: ComboboxOption[]): boolean {\n if (a.length !== b.length) return false\n return a.every((option, index) => {\n const next = b[index]\n return option.value === next.value\n && option.label === next.label\n && (option.description ?? null) === (next.description ?? null)\n })\n}\n\nexport function ComboboxInput({\n value,\n onChange,\n placeholder,\n suggestions,\n seedOptions,\n loadSuggestions,\n resolveLabel,\n resolveDescription,\n autoFocus,\n disabled = false,\n allowCustomValues = true,\n clearable = false,\n clearLabel,\n}: ComboboxInputProps) {\n const t = useT()\n const resolvedPlaceholder = placeholder ?? t('ui.inputs.comboboxInput.placeholder', 'Type to search...')\n const loadingLabel = t('ui.inputs.comboboxInput.loading', 'Loading suggestions\u2026')\n const noMatchesLabel = t('ui.inputs.comboboxInput.noMatches', 'No matches found')\n const resolvedClearLabel = clearLabel ?? t('ui.inputs.comboboxInput.clear', 'Clear value')\n const blurCloseDelayMs = 250\n const blurCloseMaxDelayMs = 1000\n const [input, setInput] = React.useState('')\n const [asyncOptions, setAsyncOptions] = React.useState<ComboboxOption[]>([])\n const [resolvedOptions, setResolvedOptions] = React.useState<ComboboxOption[]>([])\n const [loading, setLoading] = React.useState(false)\n const [touched, setTouched] = React.useState(false)\n const [showSuggestions, setShowSuggestions] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(-1)\n const listboxId = React.useId()\n const inputRef = React.useRef<HTMLInputElement>(null)\n const loadingRef = React.useRef(false)\n const blurCloseTimerRef = React.useRef<number | null>(null)\n const blurClosePendingRef = React.useRef(false)\n const suppressOpenOnFocusRef = React.useRef(Boolean(autoFocus && !disabled))\n const eagerFallbackLoadedValueRef = React.useRef<string | null>(null)\n // Tracks whether the user actually typed into the field during the current focus\n // session. `touched` cannot serve this purpose because it is set by `onFocus`,\n // which `autoFocus` triggers before the user does anything at all.\n const userTypedRef = React.useRef(false)\n\n const staticOptions = React.useMemo(\n () => normalizeOptions([...(seedOptions ?? []), ...(suggestions ?? [])]),\n [seedOptions, suggestions]\n )\n\n // Single pass over all option sources to build both coverage sets at once.\n // knownLabelValues: values with a genuine label (not a self-mapping placeholder) \u2014\n // used to decide whether eager resolution still needs to run.\n // coveredOptionValues: all values present in any source (even self-mapped).\n const { knownLabelValues, coveredOptionValues } = React.useMemo(() => {\n const known = new Set<string>()\n const covered = new Set<string>()\n for (const opt of [...staticOptions, ...asyncOptions, ...resolvedOptions]) {\n covered.add(opt.value)\n if (opt.label && opt.label !== opt.value) known.add(opt.value)\n }\n return { knownLabelValues: known, coveredOptionValues: covered }\n }, [staticOptions, asyncOptions, resolvedOptions])\n\n const optionMap = React.useMemo(() => {\n const map = new Map<string, ComboboxOption>()\n const register = (option: ComboboxOption) => {\n const existing = map.get(option.value)\n // Prefer an entry that carries a real label over a self-mapping placeholder.\n if (!existing || (existing.label === existing.value && option.label !== option.value)) {\n map.set(option.value, option)\n }\n }\n staticOptions.forEach(register)\n asyncOptions.forEach(register)\n resolvedOptions.forEach(register)\n if (value) {\n const existing = map.get(value)\n if (!existing) {\n map.set(value, {\n value,\n label: value,\n description: resolveDescription?.(value) ?? null,\n })\n }\n }\n return map\n }, [asyncOptions, resolvedOptions, resolveDescription, staticOptions, value])\n\n const availableOptions = React.useMemo(() => {\n return Array.from(optionMap.values())\n }, [optionMap])\n\n React.useEffect(() => {\n loadingRef.current = loading\n }, [loading])\n\n const clearBlurCloseTimer = React.useCallback(() => {\n if (blurCloseTimerRef.current === null) return\n window.clearTimeout(blurCloseTimerRef.current)\n blurCloseTimerRef.current = null\n }, [])\n\n const resetBlurCloseState = React.useCallback(() => {\n blurClosePendingRef.current = false\n clearBlurCloseTimer()\n }, [clearBlurCloseTimer])\n\n React.useEffect(() => resetBlurCloseState, [resetBlurCloseState])\n\n const filteredSuggestions = React.useMemo(() => {\n const query = input.toLowerCase().trim()\n if (!query) return availableOptions\n return availableOptions.filter((option) => {\n const labelMatch = option.label.toLowerCase().includes(query)\n const descMatch = option.description?.toLowerCase().includes(query)\n return labelMatch || Boolean(descMatch)\n })\n }, [availableOptions, input])\n\n React.useEffect(() => {\n if (!loadSuggestions || !touched || disabled) return\n const query = input.trim()\n let cancelled = false\n const handle = window.setTimeout(() => {\n setLoading(true)\n Promise.resolve()\n .then(() => loadSuggestions(query))\n .then((items) => {\n if (!cancelled) {\n const normalized = normalizeOptions(items)\n setAsyncOptions((prev) => areOptionsEqual(prev, normalized) ? prev : normalized)\n }\n })\n .catch(() => {})\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n }, 200)\n return () => {\n cancelled = true\n window.clearTimeout(handle)\n }\n }, [disabled, input, loadSuggestions, touched])\n\n // Eagerly resolve a pre-selected value to its label without requiring the user\n // to focus the field. Runs once per value when it is not already covered.\n const eagerResolveLabel = typeof resolveLabel === 'function' ? resolveLabel : undefined\n React.useEffect(() => {\n if (!value || disabled) return\n let cancelled = false\n const apply = (label?: string | null, description?: string | null) => {\n const clean = typeof label === 'string' ? label.trim() : ''\n if (cancelled || !clean || clean === value) return\n setResolvedOptions((prev) => {\n if (prev.some((option) => option.value === value && option.label === clean)) return prev\n return [...prev.filter((option) => option.value !== value), { value, label: clean, description: description ?? null }]\n })\n }\n if (eagerResolveLabel) {\n if (knownLabelValues.has(value)) return\n Promise.resolve()\n .then(() => eagerResolveLabel(value))\n .then((label) => apply(label, resolveDescription?.(value)))\n .catch(() => {})\n return () => { cancelled = true }\n }\n if (coveredOptionValues.has(value)) return\n if (eagerFallbackLoadedValueRef.current === value) return\n eagerFallbackLoadedValueRef.current = value\n // Fallback: pull the first page of async suggestions so a remount that lost\n // its option cache can still recover the label without user interaction.\n // Note: if the loader is paginated and the value falls outside the first page,\n // the fallback silently fails and the raw value remains visible.\n if (loadSuggestions) {\n setLoading(true)\n Promise.resolve()\n .then(() => loadSuggestions())\n .then((items) => {\n if (cancelled) return\n const normalized = normalizeOptions(items)\n setAsyncOptions((prev) => areOptionsEqual(prev, normalized) ? prev : normalized)\n })\n .catch(() => {})\n .finally(() => { if (!cancelled) setLoading(false) })\n }\n return () => { cancelled = true }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- resolveDescription intentionally excluded:\n // including it would re-run the effect on every render when the prop is an inline function\n }, [value, disabled, knownLabelValues, coveredOptionValues, eagerResolveLabel, loadSuggestions])\n\n // Sync input with a value that changed outside the component. A focused field is\n // synced too, because `autoFocus` can focus the control before an async default value\n // arrives and a focus-only guard then leaves the control rendering an empty label for\n // a value the form has already committed. Two conditions still block the sync:\n // - the user is typing, so their query is never clobbered mid-keystroke;\n // - `optionMap` only holds the self-mapping placeholder it synthesises for an\n // uncovered value, which would paint the raw record id over a label the user just\n // picked (`asyncOptions` is replaced on every load, so any follow-up load that\n // misses the picked entry \u2014 a failed request, a debounce race, a composite label\n // the route's `?search=` cannot match \u2014 drops it back to the placeholder).\n React.useEffect(() => {\n const option = optionMap.get(value)\n const hasRealLabel = Boolean(option && option.label !== option.value)\n if (document.activeElement === inputRef.current && (userTypedRef.current || !hasRealLabel)) return\n setInput(option?.label ?? value ?? '')\n }, [value, optionMap])\n\n const selectValue = React.useCallback(\n (nextValue: string) => {\n if (disabled) return\n resetBlurCloseState()\n const trimmed = nextValue.trim()\n onChange(trimmed)\n const option = optionMap.get(trimmed)\n setInput(option?.label ?? trimmed)\n setShowSuggestions(false)\n setSelectedIndex(-1)\n userTypedRef.current = false\n },\n [disabled, onChange, optionMap, resetBlurCloseState]\n )\n\n const findOptionForInput = React.useCallback(\n (raw: string): ComboboxOption | null => {\n const query = raw.trim().toLowerCase()\n if (!query) return null\n for (const option of optionMap.values()) {\n if (option.value === raw.trim()) return option\n if (option.label.toLowerCase() === query) return option\n }\n return null\n },\n [optionMap]\n )\n\n const confirmSelection = React.useCallback(\n (raw: string) => {\n if (disabled) return\n if (clearable && raw.trim() === '') {\n selectValue('')\n return\n }\n const option = findOptionForInput(raw)\n if (option) {\n selectValue(option.value)\n return\n }\n if (!allowCustomValues) {\n // Revert to the current value's label \u2014 but only if we actually know it.\n // Baking the raw value back in while eager resolution is still pending\n // would freeze a placeholder (e.g. a UUID) into the visible input.\n setShowSuggestions(false)\n const currentOption = optionMap.get(value)\n if (currentOption && currentOption.label !== currentOption.value) {\n setInput(currentOption.label)\n } else if (!value) {\n setInput('')\n }\n return\n }\n selectValue(raw)\n },\n [allowCustomValues, clearable, disabled, findOptionForInput, optionMap, selectValue, value]\n )\n\n const handleClear = React.useCallback(() => {\n if (disabled) return\n selectValue('')\n inputRef.current?.focus()\n }, [disabled, selectValue])\n\n const closeAfterBlur = React.useCallback(() => {\n blurCloseTimerRef.current = null\n if (disabled) return\n blurClosePendingRef.current = false\n confirmSelection(input)\n setShowSuggestions(false)\n setSelectedIndex(-1)\n }, [confirmSelection, disabled, input])\n\n const attemptBlurClose = React.useCallback(() => {\n blurCloseTimerRef.current = null\n if (disabled) return\n if (loadingRef.current) {\n blurCloseTimerRef.current = window.setTimeout(closeAfterBlur, blurCloseMaxDelayMs)\n return\n }\n closeAfterBlur()\n }, [blurCloseMaxDelayMs, closeAfterBlur, disabled])\n\n React.useEffect(() => {\n if (!blurClosePendingRef.current) return\n if (loading) return\n clearBlurCloseTimer()\n closeAfterBlur()\n }, [clearBlurCloseTimer, closeAfterBlur, loading])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n if (!showSuggestions) {\n setShowSuggestions(true)\n setSelectedIndex(0)\n } else {\n setSelectedIndex((prev) => Math.min(prev + 1, filteredSuggestions.length - 1))\n }\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n setSelectedIndex((prev) => Math.max(prev - 1, -1))\n } else if (event.key === 'Enter') {\n event.preventDefault()\n if (selectedIndex >= 0 && filteredSuggestions[selectedIndex]) {\n selectValue(filteredSuggestions[selectedIndex].value)\n } else {\n confirmSelection(input)\n }\n } else if (event.key === 'Escape') {\n if (!showSuggestions) return\n event.preventDefault()\n event.stopPropagation()\n setShowSuggestions(false)\n setSelectedIndex(-1)\n }\n },\n [confirmSelection, disabled, filteredSuggestions, input, selectValue, selectedIndex, showSuggestions]\n )\n\n const optionDomId = React.useCallback(\n (index: number) => `${listboxId}-option-${index}`,\n [listboxId],\n )\n\n React.useEffect(() => {\n if (selectedIndex < 0 || !showSuggestions) return\n const activeElement = typeof document !== 'undefined'\n ? document.getElementById(optionDomId(selectedIndex))\n : null\n if (typeof activeElement?.scrollIntoView === 'function') {\n activeElement.scrollIntoView({ block: 'nearest' })\n }\n }, [optionDomId, selectedIndex, showSuggestions])\n\n const showClearButton = clearable && !disabled && (value !== '' || input !== '')\n const listboxVisible = showSuggestions\n && !disabled\n && (loading || filteredSuggestions.length > 0 || (touched && input.trim().length > 0))\n\n return (\n <div className=\"relative w-full\">\n {/* Use raw <input> here instead of the DS Input primitive: ComboboxInput's\n focus / suggestions-popup interplay relies on the trigger being a plain\n input element. The DS wrapper introduces a <div> that desyncs autocomplete\n on this specific surface. Keeps the rest of the form on Input primitive. */}\n <input\n ref={inputRef}\n type=\"text\"\n className={[\n 'w-full h-9 rounded-md border border-input bg-background px-3 text-sm shadow-xs transition-colors outline-none placeholder:text-muted-foreground focus-visible:shadow-focus focus-visible:border-foreground disabled:bg-bg-disabled disabled:border-border-disabled disabled:text-muted-foreground disabled:cursor-not-allowed',\n showClearButton ? 'pr-9' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n value={input}\n placeholder={resolvedPlaceholder}\n autoFocus={autoFocus}\n data-crud-focus-target=\"\"\n disabled={disabled}\n role=\"combobox\"\n aria-expanded={listboxVisible}\n aria-controls={listboxVisible && !loading && filteredSuggestions.length > 0 ? listboxId : undefined}\n aria-autocomplete=\"list\"\n aria-activedescendant={listboxVisible && selectedIndex >= 0 ? optionDomId(selectedIndex) : undefined}\n onFocus={() => {\n setTouched(true)\n if (suppressOpenOnFocusRef.current) {\n suppressOpenOnFocusRef.current = false\n return\n }\n resetBlurCloseState()\n if (loadSuggestions && availableOptions.length === 0) {\n setLoading(true)\n }\n setShowSuggestions(true)\n }}\n onChange={(event) => {\n setTouched(true)\n userTypedRef.current = true\n setInput(event.target.value)\n setShowSuggestions(true)\n setSelectedIndex(-1)\n }}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n // Delay closing so clicks on the popup can resolve first. If async\n // suggestions are still loading, keep the dropdown open instead of\n // closing before the first payload arrives.\n userTypedRef.current = false\n blurClosePendingRef.current = true\n clearBlurCloseTimer()\n if (loadingRef.current) {\n blurCloseTimerRef.current = window.setTimeout(closeAfterBlur, blurCloseMaxDelayMs)\n return\n }\n blurCloseTimerRef.current = window.setTimeout(attemptBlurClose, blurCloseDelayMs)\n }}\n />\n\n {showClearButton ? (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={resolvedClearLabel}\n className=\"absolute right-1 top-1/2 -translate-y-1/2\"\n onMouseDown={(event) => event.preventDefault()}\n onClick={handleClear}\n >\n <X className=\"size-3\" />\n </IconButton>\n ) : null}\n\n {listboxVisible && (\n <div\n className=\"absolute z-popover w-full mt-1 rounded-md border border-input bg-popover p-2 shadow-md max-h-48 sm:max-h-60 overflow-auto\"\n >\n {loading && touched ? (\n <div className=\"px-2 py-1.5 text-xs text-muted-foreground\" role=\"status\">{loadingLabel}</div>\n ) : touched && !filteredSuggestions.length ? (\n <div className=\"px-2 py-1.5 text-xs text-muted-foreground\" role=\"status\">{noMatchesLabel}</div>\n ) : (\n <div id={listboxId} role=\"listbox\" className=\"flex flex-col gap-1\">\n {filteredSuggestions.map((option, index) => (\n <Button\n key={option.value}\n id={optionDomId(index)}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n className={[\n 'w-full h-auto justify-start font-normal text-left flex flex-col items-start rounded-lg p-2',\n index === selectedIndex ? 'bg-muted' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n resetBlurCloseState()\n selectValue(option.value)\n }}\n onMouseEnter={() => setSelectedIndex(index)}\n >\n <span className=\"font-medium text-foreground\">{option.label}</span>\n {option.description ? (\n <span className=\"text-xs text-muted-foreground\">{option.description}</span>\n ) : null}\n </Button>\n ))}\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { X } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\n\nexport type ComboboxOption = {\n value: string\n label: string\n description?: string | null\n}\n\nexport type ComboboxInputProps = {\n value: string\n onChange: (next: string) => void\n placeholder?: string\n suggestions?: Array<string | ComboboxOption>\n // Options to hydrate the option map up front (typically the linked entity's\n // display fields, already present in a record-detail payload). Merged with\n // `suggestions` so a pre-selected value renders its label without interaction.\n seedOptions?: ComboboxOption[]\n loadSuggestions?: (query?: string) => Promise<Array<string | ComboboxOption>>\n // Eagerly resolve a pre-selected `value` to a human label when it is not\n // covered by `suggestions`/`seedOptions`/`loadSuggestions` results. Runs once\n // per value, before any user interaction. May be sync or async.\n resolveLabel?: (value: string) => string | Promise<string>\n resolveDescription?: (value: string) => string | null | undefined\n autoFocus?: boolean\n disabled?: boolean\n allowCustomValues?: boolean\n clearable?: boolean\n clearLabel?: string\n}\n\nfunction normalizeOptions(input?: Array<string | ComboboxOption>): ComboboxOption[] {\n if (!Array.isArray(input)) return []\n return input\n .map((option) => {\n if (typeof option === 'string') {\n const trimmed = option.trim()\n if (!trimmed) return null\n return { value: trimmed, label: trimmed }\n }\n const value = typeof option.value === 'string' ? option.value.trim() : ''\n if (!value) return null\n return {\n value,\n label: option.label?.trim() || value,\n description: option.description ?? null,\n }\n })\n .filter((option): option is ComboboxOption => !!option)\n}\n\nfunction areOptionsEqual(a: ComboboxOption[], b: ComboboxOption[]): boolean {\n if (a.length !== b.length) return false\n return a.every((option, index) => {\n const next = b[index]\n return option.value === next.value\n && option.label === next.label\n && (option.description ?? null) === (next.description ?? null)\n })\n}\n\nexport function ComboboxInput({\n value,\n onChange,\n placeholder,\n suggestions,\n seedOptions,\n loadSuggestions,\n resolveLabel,\n resolveDescription,\n autoFocus,\n disabled = false,\n allowCustomValues = true,\n clearable = false,\n clearLabel,\n}: ComboboxInputProps) {\n const t = useT()\n const resolvedPlaceholder = placeholder ?? t('ui.inputs.comboboxInput.placeholder', 'Type to search...')\n const loadingLabel = t('ui.inputs.comboboxInput.loading', 'Loading suggestions\u2026')\n const noMatchesLabel = t('ui.inputs.comboboxInput.noMatches', 'No matches found')\n const resolvedClearLabel = clearLabel ?? t('ui.inputs.comboboxInput.clear', 'Clear value')\n const blurCloseDelayMs = 250\n const blurCloseMaxDelayMs = 1000\n const [input, setInput] = React.useState('')\n const [asyncOptions, setAsyncOptions] = React.useState<ComboboxOption[]>([])\n const [resolvedOptions, setResolvedOptions] = React.useState<ComboboxOption[]>([])\n const [loading, setLoading] = React.useState(false)\n const [touched, setTouched] = React.useState(false)\n const [showSuggestions, setShowSuggestions] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(-1)\n const listboxId = React.useId()\n const inputRef = React.useRef<HTMLInputElement>(null)\n const loadingRef = React.useRef(false)\n const blurCloseTimerRef = React.useRef<number | null>(null)\n const blurClosePendingRef = React.useRef(false)\n const suppressOpenOnFocusRef = React.useRef(Boolean(autoFocus && !disabled))\n const eagerFallbackLoadedValueRef = React.useRef<string | null>(null)\n\n const staticOptions = React.useMemo(\n () => normalizeOptions([...(seedOptions ?? []), ...(suggestions ?? [])]),\n [seedOptions, suggestions]\n )\n\n // Single pass over all option sources to build both coverage sets at once.\n // knownLabelValues: values with a genuine label (not a self-mapping placeholder) \u2014\n // used to decide whether eager resolution still needs to run.\n // coveredOptionValues: all values present in any source (even self-mapped).\n const { knownLabelValues, coveredOptionValues } = React.useMemo(() => {\n const known = new Set<string>()\n const covered = new Set<string>()\n for (const opt of [...staticOptions, ...asyncOptions, ...resolvedOptions]) {\n covered.add(opt.value)\n if (opt.label && opt.label !== opt.value) known.add(opt.value)\n }\n return { knownLabelValues: known, coveredOptionValues: covered }\n }, [staticOptions, asyncOptions, resolvedOptions])\n\n const optionMap = React.useMemo(() => {\n const map = new Map<string, ComboboxOption>()\n const register = (option: ComboboxOption) => {\n const existing = map.get(option.value)\n // Prefer an entry that carries a real label over a self-mapping placeholder.\n if (!existing || (existing.label === existing.value && option.label !== option.value)) {\n map.set(option.value, option)\n }\n }\n staticOptions.forEach(register)\n asyncOptions.forEach(register)\n resolvedOptions.forEach(register)\n if (value) {\n const existing = map.get(value)\n if (!existing) {\n map.set(value, {\n value,\n label: value,\n description: resolveDescription?.(value) ?? null,\n })\n }\n }\n return map\n }, [asyncOptions, resolvedOptions, resolveDescription, staticOptions, value])\n\n const availableOptions = React.useMemo(() => {\n return Array.from(optionMap.values())\n }, [optionMap])\n\n React.useEffect(() => {\n loadingRef.current = loading\n }, [loading])\n\n const clearBlurCloseTimer = React.useCallback(() => {\n if (blurCloseTimerRef.current === null) return\n window.clearTimeout(blurCloseTimerRef.current)\n blurCloseTimerRef.current = null\n }, [])\n\n const resetBlurCloseState = React.useCallback(() => {\n blurClosePendingRef.current = false\n clearBlurCloseTimer()\n }, [clearBlurCloseTimer])\n\n React.useEffect(() => resetBlurCloseState, [resetBlurCloseState])\n\n const filteredSuggestions = React.useMemo(() => {\n const query = input.toLowerCase().trim()\n if (!query) return availableOptions\n return availableOptions.filter((option) => {\n const labelMatch = option.label.toLowerCase().includes(query)\n const descMatch = option.description?.toLowerCase().includes(query)\n return labelMatch || Boolean(descMatch)\n })\n }, [availableOptions, input])\n\n React.useEffect(() => {\n if (!loadSuggestions || !touched || disabled) return\n const query = input.trim()\n let cancelled = false\n const handle = window.setTimeout(() => {\n setLoading(true)\n Promise.resolve()\n .then(() => loadSuggestions(query))\n .then((items) => {\n if (!cancelled) {\n const normalized = normalizeOptions(items)\n setAsyncOptions((prev) => areOptionsEqual(prev, normalized) ? prev : normalized)\n }\n })\n .catch(() => {})\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n }, 200)\n return () => {\n cancelled = true\n window.clearTimeout(handle)\n }\n }, [disabled, input, loadSuggestions, touched])\n\n // Eagerly resolve a pre-selected value to its label without requiring the user\n // to focus the field. Runs once per value when it is not already covered.\n const eagerResolveLabel = typeof resolveLabel === 'function' ? resolveLabel : undefined\n React.useEffect(() => {\n if (!value || disabled) return\n let cancelled = false\n const apply = (label?: string | null, description?: string | null) => {\n const clean = typeof label === 'string' ? label.trim() : ''\n if (cancelled || !clean || clean === value) return\n setResolvedOptions((prev) => {\n if (prev.some((option) => option.value === value && option.label === clean)) return prev\n return [...prev.filter((option) => option.value !== value), { value, label: clean, description: description ?? null }]\n })\n }\n if (eagerResolveLabel) {\n if (knownLabelValues.has(value)) return\n Promise.resolve()\n .then(() => eagerResolveLabel(value))\n .then((label) => apply(label, resolveDescription?.(value)))\n .catch(() => {})\n return () => { cancelled = true }\n }\n if (coveredOptionValues.has(value)) return\n if (eagerFallbackLoadedValueRef.current === value) return\n eagerFallbackLoadedValueRef.current = value\n // Fallback: pull the first page of async suggestions so a remount that lost\n // its option cache can still recover the label without user interaction.\n // Note: if the loader is paginated and the value falls outside the first page,\n // the fallback silently fails and the raw value remains visible.\n if (loadSuggestions) {\n setLoading(true)\n Promise.resolve()\n .then(() => loadSuggestions())\n .then((items) => {\n if (cancelled) return\n const normalized = normalizeOptions(items)\n setAsyncOptions((prev) => areOptionsEqual(prev, normalized) ? prev : normalized)\n })\n .catch(() => {})\n .finally(() => { if (!cancelled) setLoading(false) })\n }\n return () => { cancelled = true }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- resolveDescription intentionally excluded:\n // including it would re-run the effect on every render when the prop is an inline function\n }, [value, disabled, knownLabelValues, coveredOptionValues, eagerResolveLabel, loadSuggestions])\n\n // Sync input with value when value changes externally and input is not focused.\n React.useEffect(() => {\n if (document.activeElement !== inputRef.current) {\n const option = optionMap.get(value)\n setInput(option?.label ?? value ?? '')\n }\n }, [value, optionMap])\n\n const selectValue = React.useCallback(\n (nextValue: string) => {\n if (disabled) return\n resetBlurCloseState()\n const trimmed = nextValue.trim()\n onChange(trimmed)\n const option = optionMap.get(trimmed)\n setInput(option?.label ?? trimmed)\n setShowSuggestions(false)\n setSelectedIndex(-1)\n },\n [disabled, onChange, optionMap, resetBlurCloseState]\n )\n\n const findOptionForInput = React.useCallback(\n (raw: string): ComboboxOption | null => {\n const query = raw.trim().toLowerCase()\n if (!query) return null\n for (const option of optionMap.values()) {\n if (option.value === raw.trim()) return option\n if (option.label.toLowerCase() === query) return option\n }\n return null\n },\n [optionMap]\n )\n\n const confirmSelection = React.useCallback(\n (raw: string) => {\n if (disabled) return\n if (clearable && raw.trim() === '') {\n selectValue('')\n return\n }\n const option = findOptionForInput(raw)\n if (option) {\n selectValue(option.value)\n return\n }\n if (!allowCustomValues) {\n // Revert to the current value's label \u2014 but only if we actually know it.\n // Baking the raw value back in while eager resolution is still pending\n // would freeze a placeholder (e.g. a UUID) into the visible input.\n setShowSuggestions(false)\n const currentOption = optionMap.get(value)\n if (currentOption && currentOption.label !== currentOption.value) {\n setInput(currentOption.label)\n } else if (!value) {\n setInput('')\n }\n return\n }\n selectValue(raw)\n },\n [allowCustomValues, clearable, disabled, findOptionForInput, optionMap, selectValue, value]\n )\n\n const handleClear = React.useCallback(() => {\n if (disabled) return\n selectValue('')\n inputRef.current?.focus()\n }, [disabled, selectValue])\n\n const closeAfterBlur = React.useCallback(() => {\n blurCloseTimerRef.current = null\n if (disabled) return\n blurClosePendingRef.current = false\n confirmSelection(input)\n setShowSuggestions(false)\n setSelectedIndex(-1)\n }, [confirmSelection, disabled, input])\n\n const attemptBlurClose = React.useCallback(() => {\n blurCloseTimerRef.current = null\n if (disabled) return\n if (loadingRef.current) {\n blurCloseTimerRef.current = window.setTimeout(closeAfterBlur, blurCloseMaxDelayMs)\n return\n }\n closeAfterBlur()\n }, [blurCloseMaxDelayMs, closeAfterBlur, disabled])\n\n React.useEffect(() => {\n if (!blurClosePendingRef.current) return\n if (loading) return\n clearBlurCloseTimer()\n closeAfterBlur()\n }, [clearBlurCloseTimer, closeAfterBlur, loading])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n if (!showSuggestions) {\n setShowSuggestions(true)\n setSelectedIndex(0)\n } else {\n setSelectedIndex((prev) => Math.min(prev + 1, filteredSuggestions.length - 1))\n }\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n setSelectedIndex((prev) => Math.max(prev - 1, -1))\n } else if (event.key === 'Enter') {\n event.preventDefault()\n if (selectedIndex >= 0 && filteredSuggestions[selectedIndex]) {\n selectValue(filteredSuggestions[selectedIndex].value)\n } else {\n confirmSelection(input)\n }\n } else if (event.key === 'Escape') {\n if (!showSuggestions) return\n event.preventDefault()\n event.stopPropagation()\n setShowSuggestions(false)\n setSelectedIndex(-1)\n }\n },\n [confirmSelection, disabled, filteredSuggestions, input, selectValue, selectedIndex, showSuggestions]\n )\n\n const optionDomId = React.useCallback(\n (index: number) => `${listboxId}-option-${index}`,\n [listboxId],\n )\n\n React.useEffect(() => {\n if (selectedIndex < 0 || !showSuggestions) return\n const activeElement = typeof document !== 'undefined'\n ? document.getElementById(optionDomId(selectedIndex))\n : null\n if (typeof activeElement?.scrollIntoView === 'function') {\n activeElement.scrollIntoView({ block: 'nearest' })\n }\n }, [optionDomId, selectedIndex, showSuggestions])\n\n const showClearButton = clearable && !disabled && (value !== '' || input !== '')\n const listboxVisible = showSuggestions\n && !disabled\n && (loading || filteredSuggestions.length > 0 || (touched && input.trim().length > 0))\n\n return (\n <div className=\"relative w-full\">\n {/* Use raw <input> here instead of the DS Input primitive: ComboboxInput's\n focus / suggestions-popup interplay relies on the trigger being a plain\n input element. The DS wrapper introduces a <div> that desyncs autocomplete\n on this specific surface. Keeps the rest of the form on Input primitive. */}\n <input\n ref={inputRef}\n type=\"text\"\n className={[\n 'w-full h-9 rounded-md border border-input bg-background px-3 text-sm shadow-xs transition-colors outline-none placeholder:text-muted-foreground focus-visible:shadow-focus focus-visible:border-foreground disabled:bg-bg-disabled disabled:border-border-disabled disabled:text-muted-foreground disabled:cursor-not-allowed',\n showClearButton ? 'pr-9' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n value={input}\n placeholder={resolvedPlaceholder}\n autoFocus={autoFocus}\n data-crud-focus-target=\"\"\n disabled={disabled}\n role=\"combobox\"\n aria-expanded={listboxVisible}\n aria-controls={listboxVisible && !loading && filteredSuggestions.length > 0 ? listboxId : undefined}\n aria-autocomplete=\"list\"\n aria-activedescendant={listboxVisible && selectedIndex >= 0 ? optionDomId(selectedIndex) : undefined}\n onFocus={() => {\n setTouched(true)\n if (suppressOpenOnFocusRef.current) {\n suppressOpenOnFocusRef.current = false\n return\n }\n resetBlurCloseState()\n if (loadSuggestions && availableOptions.length === 0) {\n setLoading(true)\n }\n setShowSuggestions(true)\n }}\n onChange={(event) => {\n setTouched(true)\n setInput(event.target.value)\n setShowSuggestions(true)\n setSelectedIndex(-1)\n }}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n // Delay closing so clicks on the popup can resolve first. If async\n // suggestions are still loading, keep the dropdown open instead of\n // closing before the first payload arrives.\n blurClosePendingRef.current = true\n clearBlurCloseTimer()\n if (loadingRef.current) {\n blurCloseTimerRef.current = window.setTimeout(closeAfterBlur, blurCloseMaxDelayMs)\n return\n }\n blurCloseTimerRef.current = window.setTimeout(attemptBlurClose, blurCloseDelayMs)\n }}\n />\n\n {showClearButton ? (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={resolvedClearLabel}\n className=\"absolute right-1 top-1/2 -translate-y-1/2\"\n onMouseDown={(event) => event.preventDefault()}\n onClick={handleClear}\n >\n <X className=\"size-3\" />\n </IconButton>\n ) : null}\n\n {listboxVisible && (\n <div\n className=\"absolute z-popover w-full mt-1 rounded-md border border-input bg-popover p-2 shadow-md max-h-48 sm:max-h-60 overflow-auto\"\n >\n {loading && touched ? (\n <div className=\"px-2 py-1.5 text-xs text-muted-foreground\" role=\"status\">{loadingLabel}</div>\n ) : touched && !filteredSuggestions.length ? (\n <div className=\"px-2 py-1.5 text-xs text-muted-foreground\" role=\"status\">{noMatchesLabel}</div>\n ) : (\n <div id={listboxId} role=\"listbox\" className=\"flex flex-col gap-1\">\n {filteredSuggestions.map((option, index) => (\n <Button\n key={option.value}\n id={optionDomId(index)}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n className={[\n 'w-full h-auto justify-start font-normal text-left flex flex-col items-start rounded-lg p-2',\n index === selectedIndex ? 'bg-muted' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n resetBlurCloseState()\n selectValue(option.value)\n }}\n onMouseEnter={() => setSelectedIndex(index)}\n >\n <span className=\"font-medium text-foreground\">{option.label}</span>\n {option.description ? (\n <span className=\"text-xs text-muted-foreground\">{option.description}</span>\n ) : null}\n </Button>\n ))}\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -74,7 +74,7 @@ function LookupSelect({
74
74
  setQueryRef.current = setQuery;
75
75
  if (onReadyRef.current) onReadyRef.current({ setQuery });
76
76
  }, [setQuery]);
77
- const shouldSearch = defaultOpen || query.trim().length >= minQuery || Boolean(value && (options?.length ?? 0) > 0);
77
+ const shouldSearch = defaultOpen || query.trim().length >= minQuery || Boolean(value);
78
78
  React.useEffect(() => {
79
79
  setActiveIndex(-1);
80
80
  }, [items]);
@@ -83,8 +83,8 @@ function LookupSelect({
83
83
  [listboxId]
84
84
  );
85
85
  const isInteractiveItem = React.useCallback(
86
- (item) => !disabled && (!item.disabled || value === item.id),
87
- [disabled, value]
86
+ (item) => !item.disabled || value === item.id,
87
+ [value]
88
88
  );
89
89
  const moveActiveIndex = React.useCallback((direction) => {
90
90
  setActiveIndex((current) => {
@@ -197,7 +197,7 @@ function LookupSelect({
197
197
  }
198
198
  )
199
199
  ] }),
200
- actionSlot && !disabled ? /* @__PURE__ */ jsx("div", { className: "sm:self-start", children: actionSlot }) : null
200
+ actionSlot ? /* @__PURE__ */ jsx("div", { className: "sm:self-start", children: actionSlot }) : null
201
201
  ] }),
202
202
  shouldSearch ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
203
203
  loading || loadingProp ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
@@ -213,7 +213,7 @@ function LookupSelect({
213
213
  className: "flex flex-col gap-1.5 max-h-80 overflow-y-auto -mx-0.5 px-0.5 py-0.5",
214
214
  children: items.map((item, index) => {
215
215
  const isSelected = value === item.id;
216
- const isInteractive = isInteractiveItem(item);
216
+ const isInteractive = !item.disabled || isSelected;
217
217
  const isActive = index === activeIndex;
218
218
  return /* @__PURE__ */ jsxs(
219
219
  "div",
@@ -226,7 +226,7 @@ function LookupSelect({
226
226
  isActive && !isSelected ? "border-foreground/20 bg-muted/30 shadow-sm" : null
227
227
  ),
228
228
  role: "option",
229
- tabIndex: isInteractive ? 0 : -1,
229
+ tabIndex: item.disabled ? -1 : 0,
230
230
  onClick: () => {
231
231
  if (!isInteractive) return;
232
232
  onChange(item.id);
@@ -239,8 +239,8 @@ function LookupSelect({
239
239
  }
240
240
  },
241
241
  "aria-selected": isSelected,
242
- "aria-disabled": isInteractive ? void 0 : true,
243
- title: isSelected ? resolvedSelectedLabel : isInteractive ? resolvedSelectLabel : void 0,
242
+ "aria-disabled": item.disabled && !isSelected ? true : void 0,
243
+ title: isSelected ? resolvedSelectedLabel : resolvedSelectLabel,
244
244
  children: [
245
245
  item.icon ? /* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden [&>svg]:size-6 [&_svg]:text-muted-foreground", children: item.icon }) : /* @__PURE__ */ jsx("div", { className: cn(
246
246
  "flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-lg border transition-colors",
@@ -262,7 +262,7 @@ function LookupSelect({
262
262
  })
263
263
  }
264
264
  ),
265
- value && !disabled ? /* @__PURE__ */ jsxs(
265
+ value ? /* @__PURE__ */ jsxs(
266
266
  Button,
267
267
  {
268
268
  type: "button",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/LookupSelect.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Check, Loader2, Search, X } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui').child({ component: 'LookupSelect' })\n\nexport type LookupSelectItem = {\n id: string\n title: string\n subtitle?: string | null\n badge?: string | null\n icon?: React.ReactNode\n disabled?: boolean\n rightLabel?: string | null\n description?: string | null\n}\n\ntype LookupSelectProps = {\n value: string | null\n onChange: (next: string | null) => void\n fetchItems?: (query: string) => Promise<LookupSelectItem[]>\n fetchOptions?: (query?: string) => Promise<LookupSelectItem[]>\n options?: LookupSelectItem[]\n minQuery?: number\n actionSlot?: React.ReactNode\n onReady?: (controls: { setQuery: (value: string) => void }) => void\n searchPlaceholder?: string\n placeholder?: string\n clearLabel?: string\n emptyLabel?: string\n loadingLabel?: string\n selectLabel?: string\n selectedLabel?: string\n minQueryHintLabel?: string\n startTypingLabel?: string\n selectedHintLabel?: (id: string) => string\n disabled?: boolean\n loading?: boolean\n defaultOpen?: boolean\n}\n\nexport function LookupSelect({\n value,\n onChange,\n fetchItems,\n fetchOptions,\n options,\n minQuery = 2,\n actionSlot,\n onReady,\n placeholder,\n searchPlaceholder,\n clearLabel,\n emptyLabel,\n loadingLabel,\n selectLabel,\n selectedLabel,\n minQueryHintLabel,\n startTypingLabel,\n selectedHintLabel,\n disabled = false,\n loading: loadingProp = false,\n defaultOpen = false,\n}: LookupSelectProps) {\n const t = useT()\n const resolvedSearchPlaceholder = searchPlaceholder ?? placeholder ?? t('ui.lookupSelect.searchPlaceholder', 'Search\u2026')\n const resolvedClearLabel = clearLabel ?? t('ui.lookupSelect.clearSelection', 'Clear selection')\n const resolvedEmptyLabel = emptyLabel ?? t('ui.lookupSelect.noResults', 'No results')\n const resolvedLoadingLabel = loadingLabel ?? t('ui.lookupSelect.searching', 'Searching\u2026')\n const resolvedSelectLabel = selectLabel ?? t('ui.lookupSelect.select', 'Select')\n const resolvedSelectedLabel = selectedLabel ?? t('ui.lookupSelect.selected', 'Selected')\n const resolvedStartTypingLabel = startTypingLabel ?? t('ui.lookupSelect.startTyping', 'Start typing to search.')\n const resolvedMinQueryHintLabel = minQueryHintLabel ?? t(\n 'ui.lookupSelect.minQueryHint',\n 'Type at least {minQuery} characters or paste an id to search.',\n { minQuery: String(minQuery) }\n )\n const [query, setQuery] = React.useState('')\n const [items, setItems] = React.useState<LookupSelectItem[]>(options ?? [])\n const [loading, setLoading] = React.useState(false)\n const [hasTyped, setHasTyped] = React.useState(defaultOpen)\n const [error, setError] = React.useState<string | null>(null)\n const [fetchKey, setFetchKey] = React.useState(0)\n const [activeIndex, setActiveIndex] = React.useState(-1)\n const listboxId = React.useId()\n const fetchItemsRef = React.useRef(fetchItems ?? fetchOptions)\n const setQueryRef = React.useRef(setQuery)\n const onReadyRef = React.useRef(onReady)\n const optionsWasArrayRef = React.useRef(Array.isArray(options))\n\n React.useEffect(() => {\n fetchItemsRef.current = fetchItems ?? fetchOptions\n }, [fetchItems, fetchOptions])\n\n React.useEffect(() => {\n onReadyRef.current = onReady\n }, [onReady])\n\n React.useEffect(() => {\n if (Array.isArray(options)) {\n optionsWasArrayRef.current = true\n setItems(options)\n } else if (optionsWasArrayRef.current) {\n optionsWasArrayRef.current = false\n setFetchKey((k) => k + 1)\n }\n }, [options])\n\n React.useEffect(() => {\n setQueryRef.current = setQuery\n if (onReadyRef.current) onReadyRef.current({ setQuery })\n }, [setQuery])\n\n const shouldSearch =\n defaultOpen || query.trim().length >= minQuery || Boolean(value && (options?.length ?? 0) > 0)\n\n React.useEffect(() => {\n setActiveIndex(-1)\n }, [items])\n\n const optionDomId = React.useCallback(\n (index: number) => `${listboxId}-option-${index}`,\n [listboxId],\n )\n\n const isInteractiveItem = React.useCallback(\n (item: LookupSelectItem) => !disabled && (!item.disabled || value === item.id),\n [disabled, value],\n )\n\n const moveActiveIndex = React.useCallback((direction: 1 | -1) => {\n setActiveIndex((current) => {\n if (!items.length) return -1\n let next = current\n for (let step = 0; step < items.length; step += 1) {\n next = (next + direction + items.length) % items.length\n if (isInteractiveItem(items[next])) return next\n }\n return current\n })\n }, [isInteractiveItem, items])\n\n React.useEffect(() => {\n if (activeIndex < 0) return\n const activeElement = typeof document !== 'undefined'\n ? document.getElementById(optionDomId(activeIndex))\n : null\n if (typeof activeElement?.scrollIntoView === 'function') {\n activeElement.scrollIntoView({ block: 'nearest' })\n }\n }, [activeIndex, optionDomId])\n\n const listboxVisible = shouldSearch && !disabled\n const handleInputKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {\n if (!listboxVisible) return\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n moveActiveIndex(1)\n return\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n moveActiveIndex(-1)\n return\n }\n if (event.key === 'Enter') {\n if (activeIndex < 0 || activeIndex >= items.length) return\n const item = items[activeIndex]\n if (!isInteractiveItem(item)) return\n event.preventDefault()\n onChange(item.id)\n setActiveIndex(-1)\n return\n }\n if (event.key === 'Escape') {\n if (query.length === 0 && activeIndex < 0) return\n event.preventDefault()\n event.stopPropagation()\n setQuery('')\n setActiveIndex(-1)\n }\n }, [activeIndex, items, isInteractiveItem, listboxVisible, moveActiveIndex, onChange, query])\n React.useEffect(() => {\n if (disabled) {\n setItems(options ?? [])\n setLoading(false)\n return\n }\n let cancelled = false\n let timer: ReturnType<typeof setTimeout> | null = null\n if (!shouldSearch) {\n setItems(options ?? [])\n setLoading(false)\n setError(null)\n return () => { cancelled = true }\n }\n setLoading(true)\n setError(null)\n timer = setTimeout(() => {\n const requestId = Date.now()\n const fetcher = fetchItemsRef.current\n const loader = fetcher ?? (() => Promise.resolve(options ?? []))\n loader(query.trim())\n .then((result) => {\n if (cancelled) return\n setItems(result)\n })\n .catch((err) => {\n if (cancelled) return\n logger.error('Failed to fetch lookup items', { err })\n setError('error')\n })\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n return requestId\n }, 220)\n return () => {\n cancelled = true\n if (timer) clearTimeout(timer)\n }\n }, [query, shouldSearch, fetchKey])\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground\" />\n <input\n className=\"w-full h-10 rounded-lg border border-input bg-background pl-10 pr-3 text-sm shadow-xs transition-colors outline-none placeholder:text-muted-foreground hover:border-foreground/20 focus-visible:shadow-focus focus-visible:border-brand-violet disabled:bg-bg-disabled disabled:border-border-disabled disabled:text-muted-foreground disabled:cursor-not-allowed\"\n value={query}\n onChange={(event) => {\n setQuery(event.target.value)\n setHasTyped(true)\n }}\n onKeyDown={handleInputKeyDown}\n placeholder={resolvedSearchPlaceholder}\n disabled={disabled}\n role=\"combobox\"\n aria-expanded={listboxVisible}\n aria-controls={listboxId}\n aria-autocomplete=\"list\"\n aria-activedescendant={activeIndex >= 0 ? optionDomId(activeIndex) : undefined}\n />\n </div>\n {actionSlot && !disabled ? <div className=\"sm:self-start\">{actionSlot}</div> : null}\n </div>\n {shouldSearch ? (\n <div className=\"space-y-2\">\n {loading || loadingProp ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {resolvedLoadingLabel}\n </div>\n ) : null}\n {!loading && !loadingProp && !items.length ? (\n <p className=\"text-xs text-muted-foreground\">{resolvedEmptyLabel}</p>\n ) : null}\n <div\n id={listboxId}\n role=\"listbox\"\n className=\"flex flex-col gap-1.5 max-h-80 overflow-y-auto -mx-0.5 px-0.5 py-0.5\"\n >\n {items.map((item, index) => {\n const isSelected = value === item.id\n const isInteractive = isInteractiveItem(item)\n const isActive = index === activeIndex\n return (\n <div\n key={item.id}\n id={optionDomId(index)}\n className={cn(\n 'group flex items-center gap-4 rounded-xl border p-4 transition-all duration-150 focus-visible:outline-none focus-visible:shadow-focus',\n isInteractive ? 'cursor-pointer' : 'cursor-not-allowed opacity-60',\n isSelected\n ? 'border-brand-violet bg-brand-violet/5 shadow-sm'\n : 'border-input bg-card hover:border-foreground/20 hover:bg-muted/30 hover:shadow-sm',\n isActive && !isSelected ? 'border-foreground/20 bg-muted/30 shadow-sm' : null\n )}\n role=\"option\"\n tabIndex={isInteractive ? 0 : -1}\n onClick={() => {\n if (!isInteractive) return\n onChange(item.id)\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n if (!isInteractive) return\n onChange(item.id)\n }\n }}\n aria-selected={isSelected}\n aria-disabled={isInteractive ? undefined : true}\n title={isSelected ? resolvedSelectedLabel : isInteractive ? resolvedSelectLabel : undefined}\n >\n {item.icon ? (\n <div className=\"flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden [&>svg]:size-6 [&_svg]:text-muted-foreground\">\n {item.icon}\n </div>\n ) : (\n <div className={cn(\n 'flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-lg border transition-colors',\n isSelected\n ? 'border-brand-violet/40 bg-brand-violet/10 text-brand-violet'\n : 'border-input bg-muted text-muted-foreground group-hover:border-foreground/20'\n )}>\n <span className=\"text-base font-semibold uppercase\">{item.title.slice(0, 1)}</span>\n </div>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"truncate text-sm font-semibold text-foreground\">{item.title}</div>\n {item.rightLabel ? (\n <div className=\"shrink-0 text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n {item.rightLabel}\n </div>\n ) : null}\n </div>\n {item.subtitle ? (\n <div className=\"text-xs text-muted-foreground truncate\">{item.subtitle}</div>\n ) : null}\n {item.description ? (\n <div className=\"text-xs text-muted-foreground/70 truncate\">{item.description}</div>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center justify-center\">\n {isSelected ? (\n <Check className=\"size-5 text-brand-violet\" aria-hidden=\"true\" />\n ) : (\n <div className=\"size-5\" aria-hidden=\"true\" />\n )}\n </div>\n </div>\n )\n })}\n </div>\n {value && !disabled ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-fit gap-1 text-sm font-normal\"\n onClick={() => onChange(null)}\n >\n <X className=\"h-4 w-4\" />\n {resolvedClearLabel}\n </Button>\n ) : null}\n </div>\n ) : hasTyped ? (\n <p className=\"text-xs text-muted-foreground\">\n {resolvedMinQueryHintLabel}\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{resolvedStartTypingLabel}</p>\n )}\n {error ? <p className=\"text-xs text-status-error-text\" role=\"alert\">{resolvedEmptyLabel}</p> : null}\n </div>\n )\n}\n"],
5
- "mappings": ";AAuOQ,SACE,KADF;AArOR,YAAY,WAAW;AACvB,SAAS,OAAO,SAAS,QAAQ,SAAS;AAC1C,SAAS,YAAY;AACrB,SAAS,cAAc;AACvB,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,IAAI,EAAE,MAAM,EAAE,WAAW,eAAe,CAAC;AAqC9D,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,SAAS,cAAc;AAAA,EACvB,cAAc;AAChB,GAAsB;AACpB,QAAM,IAAI,KAAK;AACf,QAAM,4BAA4B,qBAAqB,eAAe,EAAE,qCAAqC,cAAS;AACtH,QAAM,qBAAqB,cAAc,EAAE,kCAAkC,iBAAiB;AAC9F,QAAM,qBAAqB,cAAc,EAAE,6BAA6B,YAAY;AACpF,QAAM,uBAAuB,gBAAgB,EAAE,6BAA6B,iBAAY;AACxF,QAAM,sBAAsB,eAAe,EAAE,0BAA0B,QAAQ;AAC/E,QAAM,wBAAwB,iBAAiB,EAAE,4BAA4B,UAAU;AACvF,QAAM,2BAA2B,oBAAoB,EAAE,+BAA+B,yBAAyB;AAC/G,QAAM,4BAA4B,qBAAqB;AAAA,IACrD;AAAA,IACA;AAAA,IACA,EAAE,UAAU,OAAO,QAAQ,EAAE;AAAA,EAC/B;AACA,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,EAAE;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAA6B,WAAW,CAAC,CAAC;AAC1E,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,WAAW;AAC1D,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,CAAC;AAChD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,EAAE;AACvD,QAAM,YAAY,MAAM,MAAM;AAC9B,QAAM,gBAAgB,MAAM,OAAO,cAAc,YAAY;AAC7D,QAAM,cAAc,MAAM,OAAO,QAAQ;AACzC,QAAM,aAAa,MAAM,OAAO,OAAO;AACvC,QAAM,qBAAqB,MAAM,OAAO,MAAM,QAAQ,OAAO,CAAC;AAE9D,QAAM,UAAU,MAAM;AACpB,kBAAc,UAAU,cAAc;AAAA,EACxC,GAAG,CAAC,YAAY,YAAY,CAAC;AAE7B,QAAM,UAAU,MAAM;AACpB,eAAW,UAAU;AAAA,EACvB,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,UAAU,MAAM;AACpB,QAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,yBAAmB,UAAU;AAC7B,eAAS,OAAO;AAAA,IAClB,WAAW,mBAAmB,SAAS;AACrC,yBAAmB,UAAU;AAC7B,kBAAY,CAAC,MAAM,IAAI,CAAC;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,UAAU,MAAM;AACpB,gBAAY,UAAU;AACtB,QAAI,WAAW,QAAS,YAAW,QAAQ,EAAE,SAAS,CAAC;AAAA,EACzD,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,eACJ,eAAe,MAAM,KAAK,EAAE,UAAU,YAAY,QAAQ,UAAU,SAAS,UAAU,KAAK,CAAC;AAE/F,QAAM,UAAU,MAAM;AACpB,mBAAe,EAAE;AAAA,EACnB,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,cAAc,MAAM;AAAA,IACxB,CAAC,UAAkB,GAAG,SAAS,WAAW,KAAK;AAAA,IAC/C,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,oBAAoB,MAAM;AAAA,IAC9B,CAAC,SAA2B,CAAC,aAAa,CAAC,KAAK,YAAY,UAAU,KAAK;AAAA,IAC3E,CAAC,UAAU,KAAK;AAAA,EAClB;AAEA,QAAM,kBAAkB,MAAM,YAAY,CAAC,cAAsB;AAC/D,mBAAe,CAAC,YAAY;AAC1B,UAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,UAAI,OAAO;AACX,eAAS,OAAO,GAAG,OAAO,MAAM,QAAQ,QAAQ,GAAG;AACjD,gBAAQ,OAAO,YAAY,MAAM,UAAU,MAAM;AACjD,YAAI,kBAAkB,MAAM,IAAI,CAAC,EAAG,QAAO;AAAA,MAC7C;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAC,mBAAmB,KAAK,CAAC;AAE7B,QAAM,UAAU,MAAM;AACpB,QAAI,cAAc,EAAG;AACrB,UAAM,gBAAgB,OAAO,aAAa,cACtC,SAAS,eAAe,YAAY,WAAW,CAAC,IAChD;AACJ,QAAI,OAAO,eAAe,mBAAmB,YAAY;AACvD,oBAAc,eAAe,EAAE,OAAO,UAAU,CAAC;AAAA,IACnD;AAAA,EACF,GAAG,CAAC,aAAa,WAAW,CAAC;AAE7B,QAAM,iBAAiB,gBAAgB,CAAC;AACxC,QAAM,qBAAqB,MAAM,YAAY,CAAC,UAAiD;AAC7F,QAAI,CAAC,eAAgB;AACrB,QAAI,MAAM,QAAQ,aAAa;AAC7B,YAAM,eAAe;AACrB,sBAAgB,CAAC;AACjB;AAAA,IACF;AACA,QAAI,MAAM,QAAQ,WAAW;AAC3B,YAAM,eAAe;AACrB,sBAAgB,EAAE;AAClB;AAAA,IACF;AACA,QAAI,MAAM,QAAQ,SAAS;AACzB,UAAI,cAAc,KAAK,eAAe,MAAM,OAAQ;AACpD,YAAM,OAAO,MAAM,WAAW;AAC9B,UAAI,CAAC,kBAAkB,IAAI,EAAG;AAC9B,YAAM,eAAe;AACrB,eAAS,KAAK,EAAE;AAChB,qBAAe,EAAE;AACjB;AAAA,IACF;AACA,QAAI,MAAM,QAAQ,UAAU;AAC1B,UAAI,MAAM,WAAW,KAAK,cAAc,EAAG;AAC3C,YAAM,eAAe;AACrB,YAAM,gBAAgB;AACtB,eAAS,EAAE;AACX,qBAAe,EAAE;AAAA,IACnB;AAAA,EACF,GAAG,CAAC,aAAa,OAAO,mBAAmB,gBAAgB,iBAAiB,UAAU,KAAK,CAAC;AAC5F,QAAM,UAAU,MAAM;AACpB,QAAI,UAAU;AACZ,eAAS,WAAW,CAAC,CAAC;AACtB,iBAAW,KAAK;AAChB;AAAA,IACF;AACA,QAAI,YAAY;AAChB,QAAI,QAA8C;AAClD,QAAI,CAAC,cAAc;AACjB,eAAS,WAAW,CAAC,CAAC;AACtB,iBAAW,KAAK;AAChB,eAAS,IAAI;AACb,aAAO,MAAM;AAAE,oBAAY;AAAA,MAAK;AAAA,IAClC;AACA,eAAW,IAAI;AACf,aAAS,IAAI;AACb,YAAQ,WAAW,MAAM;AACvB,YAAM,YAAY,KAAK,IAAI;AAC3B,YAAM,UAAU,cAAc;AAC9B,YAAM,SAAS,YAAY,MAAM,QAAQ,QAAQ,WAAW,CAAC,CAAC;AAC9D,aAAO,MAAM,KAAK,CAAC,EAChB,KAAK,CAAC,WAAW;AAChB,YAAI,UAAW;AACf,iBAAS,MAAM;AAAA,MACjB,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,YAAI,UAAW;AACf,eAAO,MAAM,gCAAgC,EAAE,IAAI,CAAC;AACpD,iBAAS,OAAO;AAAA,MAClB,CAAC,EACA,QAAQ,MAAM;AACb,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAClC,CAAC;AACH,aAAO;AAAA,IACT,GAAG,GAAG;AACN,WAAO,MAAM;AACX,kBAAY;AACZ,UAAI,MAAO,cAAa,KAAK;AAAA,IAC/B;AAAA,EACF,GAAG,CAAC,OAAO,cAAc,QAAQ,CAAC;AAElC,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,yBAAC,SAAI,WAAU,4DACb;AAAA,2BAAC,SAAI,WAAU,mBACb;AAAA,4BAAC,UAAO,WAAU,gGAA+F;AAAA,QACjH;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO;AAAA,YACP,UAAU,CAAC,UAAU;AACnB,uBAAS,MAAM,OAAO,KAAK;AAC3B,0BAAY,IAAI;AAAA,YAClB;AAAA,YACA,WAAW;AAAA,YACX,aAAa;AAAA,YACb;AAAA,YACA,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,iBAAe;AAAA,YACf,qBAAkB;AAAA,YAClB,yBAAuB,eAAe,IAAI,YAAY,WAAW,IAAI;AAAA;AAAA,QACvE;AAAA,SACF;AAAA,MACC,cAAc,CAAC,WAAW,oBAAC,SAAI,WAAU,iBAAiB,sBAAW,IAAS;AAAA,OACjF;AAAA,IACC,eACC,qBAAC,SAAI,WAAU,aACZ;AAAA,iBAAW,cACV,qBAAC,SAAI,WAAU,yDACb;AAAA,4BAAC,WAAQ,WAAU,wBAAuB;AAAA,QACzC;AAAA,SACH,IACE;AAAA,MACH,CAAC,WAAW,CAAC,eAAe,CAAC,MAAM,SAClC,oBAAC,OAAE,WAAU,iCAAiC,8BAAmB,IAC/D;AAAA,MACJ;AAAA,QAAC;AAAA;AAAA,UACC,IAAI;AAAA,UACJ,MAAK;AAAA,UACL,WAAU;AAAA,UAET,gBAAM,IAAI,CAAC,MAAM,UAAU;AAC1B,kBAAM,aAAa,UAAU,KAAK;AAClC,kBAAM,gBAAgB,kBAAkB,IAAI;AAC5C,kBAAM,WAAW,UAAU;AAC3B,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,IAAI,YAAY,KAAK;AAAA,gBACrB,WAAW;AAAA,kBACT;AAAA,kBACA,gBAAgB,mBAAmB;AAAA,kBACnC,aACI,oDACA;AAAA,kBACJ,YAAY,CAAC,aAAa,+CAA+C;AAAA,gBAC3E;AAAA,gBACA,MAAK;AAAA,gBACL,UAAU,gBAAgB,IAAI;AAAA,gBAC9B,SAAS,MAAM;AACb,sBAAI,CAAC,cAAe;AACpB,2BAAS,KAAK,EAAE;AAAA,gBAClB;AAAA,gBACA,WAAW,CAAC,UAAU;AACpB,sBAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;AAC9C,0BAAM,eAAe;AACrB,wBAAI,CAAC,cAAe;AACpB,6BAAS,KAAK,EAAE;AAAA,kBAClB;AAAA,gBACF;AAAA,gBACA,iBAAe;AAAA,gBACf,iBAAe,gBAAgB,SAAY;AAAA,gBAC3C,OAAO,aAAa,wBAAwB,gBAAgB,sBAAsB;AAAA,gBAEjF;AAAA,uBAAK,OACJ,oBAAC,SAAI,WAAU,oHACZ,eAAK,MACR,IAEA,oBAAC,SAAI,WAAW;AAAA,oBACd;AAAA,oBACA,aACI,gEACA;AAAA,kBACN,GACE,8BAAC,UAAK,WAAU,qCAAqC,eAAK,MAAM,MAAM,GAAG,CAAC,GAAE,GAC9E;AAAA,kBAEF,qBAAC,SAAI,WAAU,wCACb;AAAA,yCAAC,SAAI,WAAU,2CACb;AAAA,0CAAC,SAAI,WAAU,kDAAkD,eAAK,OAAM;AAAA,sBAC3E,KAAK,aACJ,oBAAC,SAAI,WAAU,qFACZ,eAAK,YACR,IACE;AAAA,uBACN;AAAA,oBACC,KAAK,WACJ,oBAAC,SAAI,WAAU,0CAA0C,eAAK,UAAS,IACrE;AAAA,oBACH,KAAK,cACJ,oBAAC,SAAI,WAAU,6CAA6C,eAAK,aAAY,IAC3E;AAAA,qBACN;AAAA,kBACA,oBAAC,SAAI,WAAU,6CACZ,uBACC,oBAAC,SAAM,WAAU,4BAA2B,eAAY,QAAO,IAE/D,oBAAC,SAAI,WAAU,UAAS,eAAY,QAAO,GAE/C;AAAA;AAAA;AAAA,cA/DK,KAAK;AAAA,YAgEZ;AAAA,UAEJ,CAAC;AAAA;AAAA,MACH;AAAA,MACC,SAAS,CAAC,WACT;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,WAAU;AAAA,UACV,SAAS,MAAM,SAAS,IAAI;AAAA,UAE5B;AAAA,gCAAC,KAAE,WAAU,WAAU;AAAA,YACtB;AAAA;AAAA;AAAA,MACH,IACE;AAAA,OACN,IACE,WACF,oBAAC,OAAE,WAAU,iCACV,qCACH,IAEA,oBAAC,OAAE,WAAU,iCAAiC,oCAAyB;AAAA,IAExE,QAAQ,oBAAC,OAAE,WAAU,kCAAiC,MAAK,SAAS,8BAAmB,IAAO;AAAA,KACjG;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Check, Loader2, Search, X } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui').child({ component: 'LookupSelect' })\n\nexport type LookupSelectItem = {\n id: string\n title: string\n subtitle?: string | null\n badge?: string | null\n icon?: React.ReactNode\n disabled?: boolean\n rightLabel?: string | null\n description?: string | null\n}\n\ntype LookupSelectProps = {\n value: string | null\n onChange: (next: string | null) => void\n fetchItems?: (query: string) => Promise<LookupSelectItem[]>\n fetchOptions?: (query?: string) => Promise<LookupSelectItem[]>\n options?: LookupSelectItem[]\n minQuery?: number\n actionSlot?: React.ReactNode\n onReady?: (controls: { setQuery: (value: string) => void }) => void\n searchPlaceholder?: string\n placeholder?: string\n clearLabel?: string\n emptyLabel?: string\n loadingLabel?: string\n selectLabel?: string\n selectedLabel?: string\n minQueryHintLabel?: string\n startTypingLabel?: string\n selectedHintLabel?: (id: string) => string\n disabled?: boolean\n loading?: boolean\n defaultOpen?: boolean\n}\n\nexport function LookupSelect({\n value,\n onChange,\n fetchItems,\n fetchOptions,\n options,\n minQuery = 2,\n actionSlot,\n onReady,\n placeholder,\n searchPlaceholder,\n clearLabel,\n emptyLabel,\n loadingLabel,\n selectLabel,\n selectedLabel,\n minQueryHintLabel,\n startTypingLabel,\n selectedHintLabel,\n disabled = false,\n loading: loadingProp = false,\n defaultOpen = false,\n}: LookupSelectProps) {\n const t = useT()\n const resolvedSearchPlaceholder = searchPlaceholder ?? placeholder ?? t('ui.lookupSelect.searchPlaceholder', 'Search\u2026')\n const resolvedClearLabel = clearLabel ?? t('ui.lookupSelect.clearSelection', 'Clear selection')\n const resolvedEmptyLabel = emptyLabel ?? t('ui.lookupSelect.noResults', 'No results')\n const resolvedLoadingLabel = loadingLabel ?? t('ui.lookupSelect.searching', 'Searching\u2026')\n const resolvedSelectLabel = selectLabel ?? t('ui.lookupSelect.select', 'Select')\n const resolvedSelectedLabel = selectedLabel ?? t('ui.lookupSelect.selected', 'Selected')\n const resolvedStartTypingLabel = startTypingLabel ?? t('ui.lookupSelect.startTyping', 'Start typing to search.')\n const resolvedMinQueryHintLabel = minQueryHintLabel ?? t(\n 'ui.lookupSelect.minQueryHint',\n 'Type at least {minQuery} characters or paste an id to search.',\n { minQuery: String(minQuery) }\n )\n const [query, setQuery] = React.useState('')\n const [items, setItems] = React.useState<LookupSelectItem[]>(options ?? [])\n const [loading, setLoading] = React.useState(false)\n const [hasTyped, setHasTyped] = React.useState(defaultOpen)\n const [error, setError] = React.useState<string | null>(null)\n const [fetchKey, setFetchKey] = React.useState(0)\n const [activeIndex, setActiveIndex] = React.useState(-1)\n const listboxId = React.useId()\n const fetchItemsRef = React.useRef(fetchItems ?? fetchOptions)\n const setQueryRef = React.useRef(setQuery)\n const onReadyRef = React.useRef(onReady)\n const optionsWasArrayRef = React.useRef(Array.isArray(options))\n\n React.useEffect(() => {\n fetchItemsRef.current = fetchItems ?? fetchOptions\n }, [fetchItems, fetchOptions])\n\n React.useEffect(() => {\n onReadyRef.current = onReady\n }, [onReady])\n\n React.useEffect(() => {\n if (Array.isArray(options)) {\n optionsWasArrayRef.current = true\n setItems(options)\n } else if (optionsWasArrayRef.current) {\n optionsWasArrayRef.current = false\n setFetchKey((k) => k + 1)\n }\n }, [options])\n\n React.useEffect(() => {\n setQueryRef.current = setQuery\n if (onReadyRef.current) onReadyRef.current({ setQuery })\n }, [setQuery])\n\n // A set `value` always opens the list: it is the only place the selected row,\n // its checkmark and the clear control render, so collapsing over a selection\n // would hide it with no way to see or undo it. This used to also require an\n // `options` prop, which left every caller that resolves its selection through\n // `fetchItems` blind whenever minQuery kept the list shut.\n const shouldSearch = defaultOpen || query.trim().length >= minQuery || Boolean(value)\n\n React.useEffect(() => {\n setActiveIndex(-1)\n }, [items])\n\n const optionDomId = React.useCallback(\n (index: number) => `${listboxId}-option-${index}`,\n [listboxId],\n )\n\n const isInteractiveItem = React.useCallback(\n (item: LookupSelectItem) => !item.disabled || value === item.id,\n [value],\n )\n\n const moveActiveIndex = React.useCallback((direction: 1 | -1) => {\n setActiveIndex((current) => {\n if (!items.length) return -1\n let next = current\n for (let step = 0; step < items.length; step += 1) {\n next = (next + direction + items.length) % items.length\n if (isInteractiveItem(items[next])) return next\n }\n return current\n })\n }, [isInteractiveItem, items])\n\n React.useEffect(() => {\n if (activeIndex < 0) return\n const activeElement = typeof document !== 'undefined'\n ? document.getElementById(optionDomId(activeIndex))\n : null\n if (typeof activeElement?.scrollIntoView === 'function') {\n activeElement.scrollIntoView({ block: 'nearest' })\n }\n }, [activeIndex, optionDomId])\n\n const listboxVisible = shouldSearch && !disabled\n const handleInputKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {\n if (!listboxVisible) return\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n moveActiveIndex(1)\n return\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n moveActiveIndex(-1)\n return\n }\n if (event.key === 'Enter') {\n if (activeIndex < 0 || activeIndex >= items.length) return\n const item = items[activeIndex]\n if (!isInteractiveItem(item)) return\n event.preventDefault()\n onChange(item.id)\n setActiveIndex(-1)\n return\n }\n if (event.key === 'Escape') {\n if (query.length === 0 && activeIndex < 0) return\n event.preventDefault()\n event.stopPropagation()\n setQuery('')\n setActiveIndex(-1)\n }\n }, [activeIndex, items, isInteractiveItem, listboxVisible, moveActiveIndex, onChange, query])\n React.useEffect(() => {\n if (disabled) {\n setItems(options ?? [])\n setLoading(false)\n return\n }\n let cancelled = false\n let timer: ReturnType<typeof setTimeout> | null = null\n if (!shouldSearch) {\n setItems(options ?? [])\n setLoading(false)\n setError(null)\n return () => { cancelled = true }\n }\n setLoading(true)\n setError(null)\n timer = setTimeout(() => {\n const requestId = Date.now()\n const fetcher = fetchItemsRef.current\n const loader = fetcher ?? (() => Promise.resolve(options ?? []))\n loader(query.trim())\n .then((result) => {\n if (cancelled) return\n setItems(result)\n })\n .catch((err) => {\n if (cancelled) return\n logger.error('Failed to fetch lookup items', { err })\n setError('error')\n })\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n return requestId\n }, 220)\n return () => {\n cancelled = true\n if (timer) clearTimeout(timer)\n }\n }, [query, shouldSearch, fetchKey])\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground\" />\n <input\n className=\"w-full h-10 rounded-lg border border-input bg-background pl-10 pr-3 text-sm shadow-xs transition-colors outline-none placeholder:text-muted-foreground hover:border-foreground/20 focus-visible:shadow-focus focus-visible:border-brand-violet disabled:bg-bg-disabled disabled:border-border-disabled disabled:text-muted-foreground disabled:cursor-not-allowed\"\n value={query}\n onChange={(event) => {\n setQuery(event.target.value)\n setHasTyped(true)\n }}\n onKeyDown={handleInputKeyDown}\n placeholder={resolvedSearchPlaceholder}\n disabled={disabled}\n role=\"combobox\"\n aria-expanded={listboxVisible}\n aria-controls={listboxId}\n aria-autocomplete=\"list\"\n aria-activedescendant={activeIndex >= 0 ? optionDomId(activeIndex) : undefined}\n />\n </div>\n {actionSlot ? <div className=\"sm:self-start\">{actionSlot}</div> : null}\n </div>\n {shouldSearch ? (\n <div className=\"space-y-2\">\n {loading || loadingProp ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {resolvedLoadingLabel}\n </div>\n ) : null}\n {!loading && !loadingProp && !items.length ? (\n <p className=\"text-xs text-muted-foreground\">{resolvedEmptyLabel}</p>\n ) : null}\n <div\n id={listboxId}\n role=\"listbox\"\n className=\"flex flex-col gap-1.5 max-h-80 overflow-y-auto -mx-0.5 px-0.5 py-0.5\"\n >\n {items.map((item, index) => {\n const isSelected = value === item.id\n const isInteractive = !item.disabled || isSelected\n const isActive = index === activeIndex\n return (\n <div\n key={item.id}\n id={optionDomId(index)}\n className={cn(\n 'group flex items-center gap-4 rounded-xl border p-4 transition-all duration-150 focus-visible:outline-none focus-visible:shadow-focus',\n isInteractive ? 'cursor-pointer' : 'cursor-not-allowed opacity-60',\n isSelected\n ? 'border-brand-violet bg-brand-violet/5 shadow-sm'\n : 'border-input bg-card hover:border-foreground/20 hover:bg-muted/30 hover:shadow-sm',\n isActive && !isSelected ? 'border-foreground/20 bg-muted/30 shadow-sm' : null\n )}\n role=\"option\"\n tabIndex={item.disabled ? -1 : 0}\n onClick={() => {\n if (!isInteractive) return\n onChange(item.id)\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n if (!isInteractive) return\n onChange(item.id)\n }\n }}\n aria-selected={isSelected}\n aria-disabled={item.disabled && !isSelected ? true : undefined}\n title={isSelected ? resolvedSelectedLabel : resolvedSelectLabel}\n >\n {item.icon ? (\n <div className=\"flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden [&>svg]:size-6 [&_svg]:text-muted-foreground\">\n {item.icon}\n </div>\n ) : (\n <div className={cn(\n 'flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-lg border transition-colors',\n isSelected\n ? 'border-brand-violet/40 bg-brand-violet/10 text-brand-violet'\n : 'border-input bg-muted text-muted-foreground group-hover:border-foreground/20'\n )}>\n <span className=\"text-base font-semibold uppercase\">{item.title.slice(0, 1)}</span>\n </div>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"truncate text-sm font-semibold text-foreground\">{item.title}</div>\n {item.rightLabel ? (\n <div className=\"shrink-0 text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n {item.rightLabel}\n </div>\n ) : null}\n </div>\n {item.subtitle ? (\n <div className=\"text-xs text-muted-foreground truncate\">{item.subtitle}</div>\n ) : null}\n {item.description ? (\n <div className=\"text-xs text-muted-foreground/70 truncate\">{item.description}</div>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center justify-center\">\n {isSelected ? (\n <Check className=\"size-5 text-brand-violet\" aria-hidden=\"true\" />\n ) : (\n <div className=\"size-5\" aria-hidden=\"true\" />\n )}\n </div>\n </div>\n )\n })}\n </div>\n {value ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-fit gap-1 text-sm font-normal\"\n onClick={() => onChange(null)}\n >\n <X className=\"h-4 w-4\" />\n {resolvedClearLabel}\n </Button>\n ) : null}\n </div>\n ) : hasTyped ? (\n <p className=\"text-xs text-muted-foreground\">\n {resolvedMinQueryHintLabel}\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{resolvedStartTypingLabel}</p>\n )}\n {error ? <p className=\"text-xs text-status-error-text\" role=\"alert\">{resolvedEmptyLabel}</p> : null}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }