@open-mercato/ui 0.6.8-develop.6998.1.d2bc46c56c → 0.6.8-develop.7003.1.24fef49d48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/backend/CrudForm.js +30 -19
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/backend/icons/lucideRegistry.generated.js +16 -2
- package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
- package/dist/backend/inputs/ComboboxInput.js +34 -27
- package/dist/backend/inputs/ComboboxInput.js.map +2 -2
- package/package.json +3 -3
- package/src/backend/CrudForm.tsx +41 -31
- package/src/backend/icons/lucideRegistry.generated.tsx +14 -0
- package/src/backend/inputs/ComboboxInput.tsx +11 -4
- package/src/backend/inputs/__tests__/ComboboxInput.test.tsx +21 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Activity,
|
|
3
|
+
AlarmClock,
|
|
3
4
|
AlertCircle,
|
|
4
5
|
AlertOctagon,
|
|
5
6
|
AlertTriangle,
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
Clock3,
|
|
40
41
|
Coins,
|
|
41
42
|
Copy,
|
|
43
|
+
CopyX,
|
|
42
44
|
CreditCard,
|
|
43
45
|
Crown,
|
|
44
46
|
Database,
|
|
@@ -62,6 +64,7 @@ import {
|
|
|
62
64
|
GitPullRequestArrow,
|
|
63
65
|
Globe,
|
|
64
66
|
GraduationCap,
|
|
67
|
+
Hammer,
|
|
65
68
|
Hand,
|
|
66
69
|
Handshake,
|
|
67
70
|
Headphones,
|
|
@@ -77,6 +80,7 @@ import {
|
|
|
77
80
|
LineChart,
|
|
78
81
|
Link,
|
|
79
82
|
List,
|
|
83
|
+
ListX,
|
|
80
84
|
Loader,
|
|
81
85
|
Loader2,
|
|
82
86
|
Lock,
|
|
@@ -87,6 +91,7 @@ import {
|
|
|
87
91
|
MapPinned,
|
|
88
92
|
Megaphone,
|
|
89
93
|
MessageCircle,
|
|
94
|
+
MessageSquareReply,
|
|
90
95
|
MinusCircle,
|
|
91
96
|
Notebook,
|
|
92
97
|
Package,
|
|
@@ -104,6 +109,7 @@ import {
|
|
|
104
109
|
ReceiptText,
|
|
105
110
|
RefreshCcw,
|
|
106
111
|
RefreshCw,
|
|
112
|
+
Repeat2,
|
|
107
113
|
Reply,
|
|
108
114
|
RotateCcw,
|
|
109
115
|
Ruler,
|
|
@@ -150,10 +156,12 @@ import {
|
|
|
150
156
|
Workflow,
|
|
151
157
|
Wrench,
|
|
152
158
|
X,
|
|
153
|
-
XCircle
|
|
159
|
+
XCircle,
|
|
160
|
+
ZapOff
|
|
154
161
|
} from "lucide-react";
|
|
155
162
|
const LUCIDE_ICON_REGISTRY = {
|
|
156
163
|
"activity": Activity,
|
|
164
|
+
"alarm-clock": AlarmClock,
|
|
157
165
|
"alert-circle": AlertCircle,
|
|
158
166
|
"alert-octagon": AlertOctagon,
|
|
159
167
|
"alert-triangle": AlertTriangle,
|
|
@@ -193,6 +201,7 @@ const LUCIDE_ICON_REGISTRY = {
|
|
|
193
201
|
"clock-3": Clock3,
|
|
194
202
|
"coins": Coins,
|
|
195
203
|
"copy": Copy,
|
|
204
|
+
"copy-x": CopyX,
|
|
196
205
|
"credit-card": CreditCard,
|
|
197
206
|
"crown": Crown,
|
|
198
207
|
"database": Database,
|
|
@@ -216,6 +225,7 @@ const LUCIDE_ICON_REGISTRY = {
|
|
|
216
225
|
"git-pull-request-arrow": GitPullRequestArrow,
|
|
217
226
|
"globe": Globe,
|
|
218
227
|
"graduation-cap": GraduationCap,
|
|
228
|
+
"hammer": Hammer,
|
|
219
229
|
"hand": Hand,
|
|
220
230
|
"handshake": Handshake,
|
|
221
231
|
"headphones": Headphones,
|
|
@@ -231,6 +241,7 @@ const LUCIDE_ICON_REGISTRY = {
|
|
|
231
241
|
"line-chart": LineChart,
|
|
232
242
|
"link": Link,
|
|
233
243
|
"list": List,
|
|
244
|
+
"list-x": ListX,
|
|
234
245
|
"loader": Loader,
|
|
235
246
|
"loader-2": Loader2,
|
|
236
247
|
"lock": Lock,
|
|
@@ -241,6 +252,7 @@ const LUCIDE_ICON_REGISTRY = {
|
|
|
241
252
|
"map-pinned": MapPinned,
|
|
242
253
|
"megaphone": Megaphone,
|
|
243
254
|
"message-circle": MessageCircle,
|
|
255
|
+
"message-square-reply": MessageSquareReply,
|
|
244
256
|
"minus-circle": MinusCircle,
|
|
245
257
|
"notebook": Notebook,
|
|
246
258
|
"package": Package,
|
|
@@ -258,6 +270,7 @@ const LUCIDE_ICON_REGISTRY = {
|
|
|
258
270
|
"receipt-text": ReceiptText,
|
|
259
271
|
"refresh-ccw": RefreshCcw,
|
|
260
272
|
"refresh-cw": RefreshCw,
|
|
273
|
+
"repeat-2": Repeat2,
|
|
261
274
|
"reply": Reply,
|
|
262
275
|
"rotate-ccw": RotateCcw,
|
|
263
276
|
"ruler": Ruler,
|
|
@@ -304,7 +317,8 @@ const LUCIDE_ICON_REGISTRY = {
|
|
|
304
317
|
"workflow": Workflow,
|
|
305
318
|
"wrench": Wrench,
|
|
306
319
|
"x": X,
|
|
307
|
-
"x-circle": XCircle
|
|
320
|
+
"x-circle": XCircle,
|
|
321
|
+
"zap-off": ZapOff
|
|
308
322
|
};
|
|
309
323
|
export {
|
|
310
324
|
LUCIDE_ICON_REGISTRY
|
|
@@ -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 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 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 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 Loader,\n Loader2,\n Lock,\n Mail,\n MailOpen,\n Map,\n MapPin,\n MapPinned,\n Megaphone,\n MessageCircle,\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 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} from 'lucide-react';\n\nexport const LUCIDE_ICON_REGISTRY: Record<string, LucideIcon> = {\n 'activity': Activity,\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 '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 '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 '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 '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 '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};\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
|
}
|
|
@@ -47,6 +47,7 @@ function ComboboxInput({
|
|
|
47
47
|
const t = useT();
|
|
48
48
|
const resolvedPlaceholder = placeholder ?? t("ui.inputs.comboboxInput.placeholder", "Type to search...");
|
|
49
49
|
const loadingLabel = t("ui.inputs.comboboxInput.loading", "Loading suggestions\u2026");
|
|
50
|
+
const noMatchesLabel = t("ui.inputs.comboboxInput.noMatches", "No matches found");
|
|
50
51
|
const resolvedClearLabel = clearLabel ?? t("ui.inputs.comboboxInput.clear", "Clear value");
|
|
51
52
|
const blurCloseDelayMs = 250;
|
|
52
53
|
const blurCloseMaxDelayMs = 1e3;
|
|
@@ -312,7 +313,7 @@ function ComboboxInput({
|
|
|
312
313
|
}
|
|
313
314
|
}, [optionDomId, selectedIndex, showSuggestions]);
|
|
314
315
|
const showClearButton = clearable && !disabled && (value !== "" || input !== "");
|
|
315
|
-
const listboxVisible = showSuggestions && !disabled && (loading || filteredSuggestions.length > 0);
|
|
316
|
+
const listboxVisible = showSuggestions && !disabled && (loading || filteredSuggestions.length > 0 || touched && input.trim().length > 0);
|
|
316
317
|
return /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
|
|
317
318
|
/* @__PURE__ */ jsx(
|
|
318
319
|
"input",
|
|
@@ -330,7 +331,7 @@ function ComboboxInput({
|
|
|
330
331
|
disabled,
|
|
331
332
|
role: "combobox",
|
|
332
333
|
"aria-expanded": listboxVisible,
|
|
333
|
-
"aria-controls": listboxId,
|
|
334
|
+
"aria-controls": listboxVisible && !loading && filteredSuggestions.length > 0 ? listboxId : void 0,
|
|
334
335
|
"aria-autocomplete": "list",
|
|
335
336
|
"aria-activedescendant": listboxVisible && selectedIndex >= 0 ? optionDomId(selectedIndex) : void 0,
|
|
336
337
|
onFocus: () => {
|
|
@@ -376,32 +377,38 @@ function ComboboxInput({
|
|
|
376
377
|
children: /* @__PURE__ */ jsx(X, { className: "size-3" })
|
|
377
378
|
}
|
|
378
379
|
) : null,
|
|
379
|
-
listboxVisible && /* @__PURE__ */ jsx(
|
|
380
|
-
|
|
380
|
+
listboxVisible && /* @__PURE__ */ jsx(
|
|
381
|
+
"div",
|
|
381
382
|
{
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
383
|
+
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",
|
|
384
|
+
children: loading && touched ? /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs text-muted-foreground", role: "status", children: loadingLabel }) : touched && !filteredSuggestions.length ? /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs text-muted-foreground", role: "status", children: noMatchesLabel }) : /* @__PURE__ */ jsx("div", { id: listboxId, role: "listbox", className: "flex flex-col gap-1", children: filteredSuggestions.map((option, index) => /* @__PURE__ */ jsxs(
|
|
385
|
+
Button,
|
|
386
|
+
{
|
|
387
|
+
id: optionDomId(index),
|
|
388
|
+
type: "button",
|
|
389
|
+
variant: "ghost",
|
|
390
|
+
size: "sm",
|
|
391
|
+
role: "option",
|
|
392
|
+
"aria-selected": index === selectedIndex,
|
|
393
|
+
className: [
|
|
394
|
+
"w-full h-auto justify-start font-normal text-left flex flex-col items-start rounded-lg p-2",
|
|
395
|
+
index === selectedIndex ? "bg-muted" : ""
|
|
396
|
+
].filter(Boolean).join(" "),
|
|
397
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
398
|
+
onClick: () => {
|
|
399
|
+
resetBlurCloseState();
|
|
400
|
+
selectValue(option.value);
|
|
401
|
+
},
|
|
402
|
+
onMouseEnter: () => setSelectedIndex(index),
|
|
403
|
+
children: [
|
|
404
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: option.label }),
|
|
405
|
+
option.description ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: option.description }) : null
|
|
406
|
+
]
|
|
407
|
+
},
|
|
408
|
+
option.value
|
|
409
|
+
)) })
|
|
410
|
+
}
|
|
411
|
+
)
|
|
405
412
|
] });
|
|
406
413
|
}
|
|
407
414
|
export {
|
|
@@ -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 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 && !disabled && (loading || filteredSuggestions.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={listboxId}\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 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 {loading && touched ? (\n <div className=\"px-2 py-1.5 text-xs text-muted-foreground\">{loadingLabel}</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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.7003.1.24fef49d48",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -155,14 +155,14 @@
|
|
|
155
155
|
"remark-gfm": "^4.0.1"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
158
|
+
"@open-mercato/shared": "0.6.8-develop.7003.1.24fef49d48",
|
|
159
159
|
"react": ">=18.0.0",
|
|
160
160
|
"react-dom": ">=18.0.0",
|
|
161
161
|
"react-is": ">=18.0.0"
|
|
162
162
|
},
|
|
163
163
|
"devDependencies": {
|
|
164
164
|
"@figma/code-connect": "^1.3.4",
|
|
165
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
165
|
+
"@open-mercato/shared": "0.6.8-develop.7003.1.24fef49d48",
|
|
166
166
|
"@testing-library/dom": "^10.4.1",
|
|
167
167
|
"@testing-library/jest-dom": "^7.0.0",
|
|
168
168
|
"@testing-library/react": "^16.3.1",
|
package/src/backend/CrudForm.tsx
CHANGED
|
@@ -115,6 +115,12 @@ import MarkdownField from './inputs/MarkdownField'
|
|
|
115
115
|
const logger = createLogger('ui').child({ component: 'CrudForm' })
|
|
116
116
|
|
|
117
117
|
const EMPTY_OPTIONS: CrudFieldOption[] = []
|
|
118
|
+
|
|
119
|
+
function areOptionListsEqual(a: CrudFieldOption[], b: CrudFieldOption[]): boolean {
|
|
120
|
+
if (a === b) return true
|
|
121
|
+
if (a.length !== b.length) return false
|
|
122
|
+
return a.every((option, index) => option.value === b[index].value && option.label === b[index].label)
|
|
123
|
+
}
|
|
118
124
|
// Sentinel for the optional-Select clear affordance. Radix Select forbids
|
|
119
125
|
// empty-string item values, so we use a stable non-empty token that maps to
|
|
120
126
|
// `undefined` in the change handler.
|
|
@@ -3075,7 +3081,11 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
3075
3081
|
if (!displayMessage) {
|
|
3076
3082
|
displayMessage = hasFieldErrors ? highlightedMessage : saveErrorMessage
|
|
3077
3083
|
}
|
|
3078
|
-
|
|
3084
|
+
// Translate the top-level submit error the same way field errors are translated above.
|
|
3085
|
+
// Server commands return stable i18n keys (e.g. `warranty_claims.errors.*`) in the error
|
|
3086
|
+
// body; without this they leaked to the UI as raw keys. `t(msg, msg)` is a no-op for
|
|
3087
|
+
// already-localized strings, so this only affects unresolved keys.
|
|
3088
|
+
displayMessage = translateValidationMessage(parseServerMessage(displayMessage))
|
|
3079
3089
|
if (optimisticLockConflict) {
|
|
3080
3090
|
// Primary surface for the conflict is the persistent, error-styled
|
|
3081
3091
|
// RecordConflictBanner (unified across all forms). Keep the inline
|
|
@@ -3099,6 +3109,18 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
3099
3109
|
Map<string, ((query?: string) => Promise<CrudFieldOption[]>) | undefined>
|
|
3100
3110
|
>(new Map())
|
|
3101
3111
|
|
|
3112
|
+
// Ref mirror of dynamicOptions so loadFieldOptions keeps a stable identity; a state-dep
|
|
3113
|
+
// callback re-created per cache write re-fired every field's init effect and cascaded
|
|
3114
|
+
// refetch loops across loader fields (same loop class as #814).
|
|
3115
|
+
const dynamicOptionsRef = React.useRef<Record<string, CrudFieldOption[]>>({})
|
|
3116
|
+
|
|
3117
|
+
const storeDynamicOptions = React.useCallback((fieldId: string, fetched: CrudFieldOption[]) => {
|
|
3118
|
+
const current = dynamicOptionsRef.current[fieldId]
|
|
3119
|
+
if (current && areOptionListsEqual(current, fetched)) return
|
|
3120
|
+
dynamicOptionsRef.current = { ...dynamicOptionsRef.current, [fieldId]: fetched }
|
|
3121
|
+
setDynamicOptions(dynamicOptionsRef.current)
|
|
3122
|
+
}, [])
|
|
3123
|
+
|
|
3102
3124
|
// Stable key prevents infinite re-render loop (see #814) — do not depend on allFields directly.
|
|
3103
3125
|
React.useEffect(() => {
|
|
3104
3126
|
let cancelled = false
|
|
@@ -3112,7 +3134,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
3112
3134
|
try {
|
|
3113
3135
|
dynamicOptionLoadersRef.current.set(f.id, f.loadOptions)
|
|
3114
3136
|
const opts = await f.loadOptions()
|
|
3115
|
-
if (!cancelled)
|
|
3137
|
+
if (!cancelled) storeDynamicOptions(f.id, opts)
|
|
3116
3138
|
} catch {
|
|
3117
3139
|
// ignore
|
|
3118
3140
|
}
|
|
@@ -3123,7 +3145,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
3123
3145
|
return () => {
|
|
3124
3146
|
cancelled = true
|
|
3125
3147
|
}
|
|
3126
|
-
}, [dynamicOptionLoaderKey])
|
|
3148
|
+
}, [dynamicOptionLoaderKey, storeDynamicOptions])
|
|
3127
3149
|
|
|
3128
3150
|
const loadFieldOptions = React.useCallback(async (field: CrudField, query?: string): Promise<CrudFieldOption[]> => {
|
|
3129
3151
|
if (!('type' in field) || field.type === 'custom') return EMPTY_OPTIONS
|
|
@@ -3133,28 +3155,22 @@ export function CrudForm<TValues extends Record<string, unknown>>({
|
|
|
3133
3155
|
const previousLoader = dynamicOptionLoadersRef.current.get(field.id)
|
|
3134
3156
|
const loaderChanged = previousLoader !== loader
|
|
3135
3157
|
dynamicOptionLoadersRef.current.set(field.id, loader)
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
Array.isArray(dynamicOptions[field.id])
|
|
3140
|
-
) {
|
|
3141
|
-
return dynamicOptions[field.id]
|
|
3158
|
+
const cached = dynamicOptionsRef.current[field.id]
|
|
3159
|
+
if (query === undefined && !loaderChanged && Array.isArray(cached)) {
|
|
3160
|
+
return cached
|
|
3142
3161
|
}
|
|
3143
3162
|
try {
|
|
3144
3163
|
const fetched = await loader(query)
|
|
3145
3164
|
if (query === undefined) {
|
|
3146
|
-
|
|
3147
|
-
...prev,
|
|
3148
|
-
[field.id]: fetched,
|
|
3149
|
-
}))
|
|
3165
|
+
storeDynamicOptions(field.id, fetched)
|
|
3150
3166
|
}
|
|
3151
3167
|
return fetched
|
|
3152
3168
|
} catch {
|
|
3153
3169
|
return builtin.options ?? EMPTY_OPTIONS
|
|
3154
3170
|
}
|
|
3155
3171
|
}
|
|
3156
|
-
return
|
|
3157
|
-
}, [
|
|
3172
|
+
return dynamicOptionsRef.current[field.id] || builtin.options || EMPTY_OPTIONS
|
|
3173
|
+
}, [storeDynamicOptions])
|
|
3158
3174
|
|
|
3159
3175
|
const fieldOptionsById = React.useMemo(() => {
|
|
3160
3176
|
const map = new globalThis.Map<string, CrudFieldOption[]>()
|
|
@@ -4382,6 +4398,14 @@ const FieldControl = React.memo(function FieldControlImpl({
|
|
|
4382
4398
|
loadFieldOptions(field).catch(() => {})
|
|
4383
4399
|
}, [field, hasLoader, loadFieldOptions])
|
|
4384
4400
|
|
|
4401
|
+
const loadFieldSuggestions = React.useMemo(() => {
|
|
4402
|
+
if (!hasLoader) return undefined
|
|
4403
|
+
return async (query?: string) => {
|
|
4404
|
+
const opts = await loadFieldOptions(field, query)
|
|
4405
|
+
return opts.map((opt) => ({ value: opt.value, label: opt.label }))
|
|
4406
|
+
}
|
|
4407
|
+
}, [field, hasLoader, loadFieldOptions])
|
|
4408
|
+
|
|
4385
4409
|
const placeholder = builtin?.placeholder
|
|
4386
4410
|
const rootClassName = wrapperClassName ? `space-y-1 ${wrapperClassName}` : 'space-y-1'
|
|
4387
4411
|
const validateOnWrapperBlur = supportsWrapperBlurValidation(field)
|
|
@@ -4545,14 +4569,7 @@ const FieldControl = React.memo(function FieldControlImpl({
|
|
|
4545
4569
|
autoFocus={autoFocusField}
|
|
4546
4570
|
suppressInitialSuggestionsOnFocus={autoFocusField}
|
|
4547
4571
|
suggestions={options.map((opt) => ({ value: opt.value, label: opt.label }))}
|
|
4548
|
-
loadSuggestions={
|
|
4549
|
-
typeof builtin?.loadOptions === 'function'
|
|
4550
|
-
? async (query?: string) => {
|
|
4551
|
-
const opts = await loadFieldOptions(field, query)
|
|
4552
|
-
return opts.map((opt) => ({ value: opt.value, label: opt.label }))
|
|
4553
|
-
}
|
|
4554
|
-
: undefined
|
|
4555
|
-
}
|
|
4572
|
+
loadSuggestions={loadFieldSuggestions}
|
|
4556
4573
|
/>
|
|
4557
4574
|
)}
|
|
4558
4575
|
{field.type === 'combobox' && (
|
|
@@ -4572,14 +4589,7 @@ const FieldControl = React.memo(function FieldControlImpl({
|
|
|
4572
4589
|
: undefined
|
|
4573
4590
|
}
|
|
4574
4591
|
resolveLabel={builtin?.resolveLabel}
|
|
4575
|
-
loadSuggestions={
|
|
4576
|
-
typeof builtin?.loadOptions === 'function'
|
|
4577
|
-
? async (query?: string) => {
|
|
4578
|
-
const opts = await loadFieldOptions(field, query)
|
|
4579
|
-
return opts.map((opt) => ({ value: opt.value, label: opt.label }))
|
|
4580
|
-
}
|
|
4581
|
-
: undefined
|
|
4582
|
-
}
|
|
4592
|
+
loadSuggestions={loadFieldSuggestions}
|
|
4583
4593
|
allowCustomValues={builtin?.allowCustomValues ?? true}
|
|
4584
4594
|
clearable={!field.required}
|
|
4585
4595
|
disabled={disabled}
|