@djangocfg/ui-core 2.1.89 → 2.1.91

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.
@@ -0,0 +1,600 @@
1
+ /**
2
+ * MultiSelectProAsync - Extended MultiSelectPro with async search support
3
+ *
4
+ * Based on MultiSelectPro with added features:
5
+ * - Async search through external API
6
+ * - Debounced search queries
7
+ * - Loading states
8
+ * - Controlled search value from parent
9
+ *
10
+ * All original MultiSelectPro features preserved:
11
+ * - Animations, variants, responsive, groups, etc.
12
+ */
13
+
14
+ "use client"
15
+
16
+ import { cva } from 'class-variance-authority';
17
+ import { Check, ChevronsUpDown, Loader2, X, XCircle } from 'lucide-react';
18
+ import * as React from 'react';
19
+
20
+
21
+ import { Badge } from '../badge';
22
+ import { Button } from '../button';
23
+ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '../command';
24
+ import { Popover, PopoverContent, PopoverTrigger } from '../popover';
25
+ import { Separator } from '../separator';
26
+ import { cn } from '../../lib';
27
+
28
+ // ==================== TYPES ====================
29
+
30
+ export interface MultiSelectProAsyncOption {
31
+ label: string
32
+ value: string
33
+ description?: string // Optional subtitle/description text shown below label
34
+ icon?: React.ComponentType<{ className?: string }>
35
+ disabled?: boolean
36
+ style?: {
37
+ badgeColor?: string
38
+ iconColor?: string
39
+ gradient?: string
40
+ }
41
+ }
42
+
43
+ export interface MultiSelectProAsyncGroup {
44
+ heading: string
45
+ options: MultiSelectProAsyncOption[]
46
+ }
47
+
48
+ export interface AnimationConfig {
49
+ badgeAnimation?: "bounce" | "pulse" | "wiggle" | "fade" | "slide" | "none"
50
+ popoverAnimation?: "scale" | "slide" | "fade" | "flip" | "none"
51
+ optionHoverAnimation?: "highlight" | "scale" | "glow" | "none"
52
+ duration?: number
53
+ delay?: number
54
+ }
55
+
56
+ export interface MultiSelectProAsyncRef {
57
+ reset: () => void
58
+ getSelectedValues: () => string[]
59
+ setSelectedValues: (values: string[]) => void
60
+ clear: () => void
61
+ focus: () => void
62
+ }
63
+
64
+ export interface MultiSelectProAsyncProps {
65
+ // Options from API
66
+ options: MultiSelectProAsyncOption[] | MultiSelectProAsyncGroup[]
67
+
68
+ // Selection
69
+ onValueChange?: (value: string[]) => void
70
+ defaultValue?: string[]
71
+
72
+ // Async Search
73
+ searchValue: string
74
+ onSearchChange: (value: string) => void
75
+ isLoading?: boolean
76
+
77
+ // UI
78
+ placeholder?: string
79
+ searchPlaceholder?: string
80
+ emptyText?: string
81
+ loadingText?: string
82
+ variant?: "default" | "secondary" | "destructive" | "inverted"
83
+ animation?: number
84
+ animationConfig?: AnimationConfig
85
+ maxCount?: number
86
+ modalPopover?: boolean
87
+ className?: string
88
+ popoverClassName?: string
89
+ disabled?: boolean
90
+
91
+ // Behavior
92
+ hideSelectAll?: boolean
93
+ searchable?: boolean
94
+ closeOnSelect?: boolean
95
+ resetOnDefaultValueChange?: boolean
96
+
97
+ // Advanced
98
+ autoSize?: boolean
99
+ singleLine?: boolean
100
+ minWidth?: string
101
+ maxWidth?: string
102
+ deduplicateOptions?: boolean
103
+ }
104
+
105
+ // ==================== VARIANTS ====================
106
+
107
+ const multiSelectVariants = cva(
108
+ "w-full justify-between min-h-10 h-auto py-2",
109
+ {
110
+ variants: {
111
+ variant: {
112
+ default: "border-input bg-background hover:bg-accent/50",
113
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
114
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
115
+ inverted: "bg-primary text-primary-foreground hover:bg-primary/90",
116
+ },
117
+ },
118
+ defaultVariants: {
119
+ variant: "default",
120
+ },
121
+ }
122
+ )
123
+
124
+ const badgeAnimations = {
125
+ bounce: "animate-bounce",
126
+ pulse: "animate-pulse",
127
+ wiggle: "animate-wiggle",
128
+ fade: "animate-fadeIn",
129
+ slide: "animate-slideIn",
130
+ none: "",
131
+ }
132
+
133
+ const popoverAnimations = {
134
+ scale: "data-[state=open]:animate-scaleIn data-[state=closed]:animate-scaleOut",
135
+ slide: "data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp",
136
+ fade: "data-[state=open]:animate-fadeIn data-[state=closed]:animate-fadeOut",
137
+ flip: "data-[state=open]:animate-flipIn data-[state=closed]:animate-flipOut",
138
+ none: "",
139
+ }
140
+
141
+ // ==================== HELPERS ====================
142
+
143
+ function isGroupedOptions(options: MultiSelectProAsyncOption[] | MultiSelectProAsyncGroup[]): options is MultiSelectProAsyncGroup[] {
144
+ return options.length > 0 && 'heading' in options[0]
145
+ }
146
+
147
+ function flattenOptions(options: MultiSelectProAsyncOption[] | MultiSelectProAsyncGroup[]): MultiSelectProAsyncOption[] {
148
+ if (isGroupedOptions(options)) {
149
+ return options.flatMap((group) => group.options)
150
+ }
151
+ return options
152
+ }
153
+
154
+ function deduplicateOptions(options: MultiSelectProAsyncOption[]): MultiSelectProAsyncOption[] {
155
+ const seen = new Set<string>()
156
+ return options.filter((option) => {
157
+ if (seen.has(option.value)) {
158
+ return false
159
+ }
160
+ seen.add(option.value)
161
+ return true
162
+ })
163
+ }
164
+
165
+ // ==================== COMPONENT ====================
166
+
167
+ export const MultiSelectProAsync = React.forwardRef<MultiSelectProAsyncRef, MultiSelectProAsyncProps>(
168
+ (
169
+ {
170
+ options,
171
+ onValueChange,
172
+ defaultValue = [],
173
+ searchValue,
174
+ onSearchChange,
175
+ isLoading = false,
176
+ placeholder = "Select options",
177
+ searchPlaceholder = "Search...",
178
+ emptyText = "No results found.",
179
+ loadingText = "Loading...",
180
+ variant = "default",
181
+ animation = 0,
182
+ animationConfig,
183
+ maxCount = 3,
184
+ modalPopover = false,
185
+ className,
186
+ popoverClassName,
187
+ disabled = false,
188
+ hideSelectAll = false,
189
+ searchable = true,
190
+ closeOnSelect = false,
191
+ resetOnDefaultValueChange = true,
192
+ autoSize = false,
193
+ singleLine = false,
194
+ minWidth,
195
+ maxWidth,
196
+ deduplicateOptions: shouldDeduplicate = false,
197
+ },
198
+ ref
199
+ ) => {
200
+ const [open, setOpen] = React.useState(false)
201
+ const [selectedValues, setSelectedValues] = React.useState<string[]>(defaultValue)
202
+ const buttonRef = React.useRef<HTMLButtonElement>(null)
203
+ const [announcements, setAnnouncements] = React.useState<string>("")
204
+ // Cache selected options to persist them when they disappear from current options
205
+ const [selectedOptionsCache, setSelectedOptionsCache] = React.useState<Map<string, MultiSelectProAsyncOption>>(new Map())
206
+
207
+ // Process options
208
+ const flatOptions = React.useMemo(() => {
209
+ const flat = flattenOptions(options)
210
+ return shouldDeduplicate ? deduplicateOptions(flat) : flat
211
+ }, [options, shouldDeduplicate])
212
+
213
+ // Update cache whenever new options appear
214
+ React.useEffect(() => {
215
+ setSelectedOptionsCache(prev => {
216
+ const updated = new Map(prev)
217
+ flatOptions.forEach(option => {
218
+ if (selectedValues.includes(option.value)) {
219
+ updated.set(option.value, option)
220
+ }
221
+ })
222
+ return updated
223
+ })
224
+ }, [flatOptions, selectedValues])
225
+
226
+ // Animation configuration
227
+ const animConfig = React.useMemo(
228
+ (): AnimationConfig => ({
229
+ badgeAnimation: animationConfig?.badgeAnimation || (animation > 0 ? "bounce" : "none"),
230
+ popoverAnimation: animationConfig?.popoverAnimation || "scale",
231
+ optionHoverAnimation: animationConfig?.optionHoverAnimation || "highlight",
232
+ duration: animationConfig?.duration || animation || 0.3,
233
+ delay: animationConfig?.delay || 0,
234
+ }),
235
+ [animation, animationConfig]
236
+ )
237
+
238
+ // Reset on defaultValue change
239
+ React.useEffect(() => {
240
+ if (resetOnDefaultValueChange) {
241
+ setSelectedValues(defaultValue)
242
+ }
243
+ }, [JSON.stringify(defaultValue), resetOnDefaultValueChange])
244
+
245
+ // Announce changes for screen readers
246
+ const announce = React.useCallback((message: string) => {
247
+ setAnnouncements(message)
248
+ setTimeout(() => setAnnouncements(""), 1000)
249
+ }, [])
250
+
251
+ // Toggle selection
252
+ const toggleOption = React.useCallback(
253
+ (value: string) => {
254
+ const isRemoving = selectedValues.includes(value)
255
+ const newValues = isRemoving
256
+ ? selectedValues.filter((v) => v !== value)
257
+ : [...selectedValues, value]
258
+
259
+ setSelectedValues(newValues)
260
+ onValueChange?.(newValues)
261
+
262
+ const option = flatOptions.find((o) => o.value === value)
263
+ if (option) {
264
+ // Add to cache when selecting
265
+ if (!isRemoving) {
266
+ setSelectedOptionsCache(prev => new Map(prev).set(value, option))
267
+ }
268
+
269
+ announce(
270
+ isRemoving
271
+ ? `Removed ${option.label}`
272
+ : `Added ${option.label}`
273
+ )
274
+ }
275
+
276
+ if (closeOnSelect) {
277
+ setOpen(false)
278
+ }
279
+ },
280
+ [selectedValues, onValueChange, flatOptions, announce, closeOnSelect]
281
+ )
282
+
283
+ // Select all
284
+ const handleSelectAll = React.useCallback(() => {
285
+ const allValues = flatOptions.filter((o) => !o.disabled).map((o) => o.value)
286
+ setSelectedValues(allValues)
287
+ onValueChange?.(allValues)
288
+
289
+ // Cache all selected options
290
+ setSelectedOptionsCache(prev => {
291
+ const updated = new Map(prev)
292
+ flatOptions.forEach(option => {
293
+ if (!option.disabled) {
294
+ updated.set(option.value, option)
295
+ }
296
+ })
297
+ return updated
298
+ })
299
+
300
+ announce(`Selected all ${allValues.length} options`)
301
+ }, [flatOptions, onValueChange, announce])
302
+
303
+ // Clear all
304
+ const handleClearAll = React.useCallback(() => {
305
+ setSelectedValues([])
306
+ onValueChange?.([])
307
+ announce("Cleared all selections")
308
+ }, [onValueChange, announce])
309
+
310
+ // Imperative methods
311
+ React.useImperativeHandle(ref, () => ({
312
+ reset: () => {
313
+ setSelectedValues(defaultValue)
314
+ announce("Reset to default values")
315
+ },
316
+ getSelectedValues: () => selectedValues,
317
+ setSelectedValues: (values: string[]) => {
318
+ setSelectedValues(values)
319
+ onValueChange?.(values)
320
+ },
321
+ clear: handleClearAll,
322
+ focus: () => buttonRef.current?.focus(),
323
+ }))
324
+
325
+ // Selected options for display - use cache as fallback
326
+ const selectedOptions = React.useMemo(
327
+ () => selectedValues.map(value => {
328
+ // First try to find in current options
329
+ const option = flatOptions.find(o => o.value === value)
330
+ // If not found, fallback to cache
331
+ return option || selectedOptionsCache.get(value)
332
+ }).filter((option): option is MultiSelectProAsyncOption => option !== undefined),
333
+ [flatOptions, selectedValues, selectedOptionsCache]
334
+ )
335
+
336
+ // Render badge with custom styles
337
+ const renderBadge = (option: MultiSelectProAsyncOption, index: number) => {
338
+ const { style, icon: Icon } = option
339
+ const badgeStyle: React.CSSProperties = {}
340
+
341
+ if (style?.gradient) {
342
+ badgeStyle.background = style.gradient
343
+ badgeStyle.color = style.iconColor || "white"
344
+ } else if (style?.badgeColor) {
345
+ badgeStyle.backgroundColor = style.badgeColor
346
+ badgeStyle.color = style.iconColor || "white"
347
+ }
348
+
349
+ const animationClass = animConfig.badgeAnimation
350
+ ? badgeAnimations[animConfig.badgeAnimation]
351
+ : ""
352
+
353
+ return (
354
+ <Badge
355
+ key={option.value}
356
+ variant={variant === "default" ? "secondary" : "outline"}
357
+ className={cn(
358
+ "mr-1 mb-1 text-xs gap-1 flex items-center",
359
+ animationClass
360
+ )}
361
+ style={{
362
+ ...badgeStyle,
363
+ animationDelay: `${(animConfig.delay || 0) * index}s`,
364
+ animationDuration: `${animConfig.duration}s`,
365
+ }}
366
+ >
367
+ {Icon && <Icon className="h-3 w-3" />}
368
+ <span>{option.label}</span>
369
+ {!disabled && (
370
+ <button
371
+ className="ml-1 rounded-full hover:bg-muted-foreground/20 focus:outline-none focus:ring-2 focus:ring-ring"
372
+ onClick={(e) => {
373
+ e.stopPropagation()
374
+ toggleOption(option.value)
375
+ }}
376
+ aria-label={`Remove ${option.label}`}
377
+ >
378
+ <X className="h-3 w-3" />
379
+ </button>
380
+ )}
381
+ </Badge>
382
+ )
383
+ }
384
+
385
+ // Display value
386
+ const displayValue = React.useMemo(() => {
387
+ if (selectedOptions.length === 0) {
388
+ return <span className="text-muted-foreground">{placeholder}</span>
389
+ }
390
+
391
+ const displayed = selectedOptions.slice(0, maxCount)
392
+ const remaining = selectedOptions.length - maxCount
393
+
394
+ return (
395
+ <div className={cn("flex gap-1", singleLine ? "flex-nowrap overflow-x-auto" : "flex-wrap")}>
396
+ {displayed.map((option, index) => renderBadge(option, index))}
397
+ {remaining > 0 && (
398
+ <Badge variant="outline" className="text-xs">
399
+ +{remaining} more
400
+ </Badge>
401
+ )}
402
+ </div>
403
+ )
404
+ }, [selectedOptions, maxCount, placeholder, singleLine, variant, disabled, animConfig])
405
+
406
+ // Render options
407
+ const renderOptions = () => {
408
+ if (isGroupedOptions(options)) {
409
+ return options.map((group, groupIndex) => (
410
+ <React.Fragment key={group.heading}>
411
+ {groupIndex > 0 && <CommandSeparator />}
412
+ <CommandGroup heading={group.heading}>
413
+ {group.options.map((option) => {
414
+ const isSelected = selectedValues.includes(option.value)
415
+ const Icon = option.icon
416
+
417
+ return (
418
+ <CommandItem
419
+ key={option.value}
420
+ value={option.value}
421
+ onSelect={() => !option.disabled && toggleOption(option.value)}
422
+ disabled={option.disabled}
423
+ className={cn(
424
+ "cursor-pointer",
425
+ option.disabled && "opacity-50 cursor-not-allowed"
426
+ )}
427
+ >
428
+ <Check
429
+ className={cn(
430
+ "mr-2 h-4 w-4 shrink-0",
431
+ isSelected ? "opacity-100" : "opacity-0"
432
+ )}
433
+ />
434
+ {Icon && <Icon className="mr-2 h-4 w-4" />}
435
+ <div className="flex-1 flex flex-col gap-0.5">
436
+ <span>{option.label}</span>
437
+ {option.description && (
438
+ <span className="text-xs text-muted-foreground">{option.description}</span>
439
+ )}
440
+ </div>
441
+ </CommandItem>
442
+ )
443
+ })}
444
+ </CommandGroup>
445
+ </React.Fragment>
446
+ ))
447
+ }
448
+
449
+ return (
450
+ <CommandGroup>
451
+ {(options as MultiSelectProAsyncOption[]).map((option) => {
452
+ const isSelected = selectedValues.includes(option.value)
453
+ const Icon = option.icon
454
+
455
+ return (
456
+ <CommandItem
457
+ key={option.value}
458
+ value={option.value}
459
+ onSelect={() => !option.disabled && toggleOption(option.value)}
460
+ disabled={option.disabled}
461
+ className={cn(
462
+ "cursor-pointer",
463
+ option.disabled && "opacity-50 cursor-not-allowed"
464
+ )}
465
+ >
466
+ <Check
467
+ className={cn(
468
+ "mr-2 h-4 w-4 shrink-0",
469
+ isSelected ? "opacity-100" : "opacity-0"
470
+ )}
471
+ />
472
+ {Icon && <Icon className="mr-2 h-4 w-4" />}
473
+ <div className="flex-1 flex flex-col gap-0.5">
474
+ <span>{option.label}</span>
475
+ {option.description && (
476
+ <span className="text-xs text-muted-foreground">{option.description}</span>
477
+ )}
478
+ </div>
479
+ </CommandItem>
480
+ )
481
+ })}
482
+ </CommandGroup>
483
+ )
484
+ }
485
+
486
+ const containerStyle: React.CSSProperties = {}
487
+ if (minWidth) containerStyle.minWidth = minWidth
488
+ if (maxWidth) containerStyle.maxWidth = maxWidth
489
+
490
+ return (
491
+ <div style={containerStyle} className="relative">
492
+ {/* ARIA Live Region for announcements */}
493
+ <div
494
+ role="status"
495
+ aria-live="polite"
496
+ aria-atomic="true"
497
+ className="sr-only"
498
+ >
499
+ {announcements}
500
+ </div>
501
+
502
+ <Popover
503
+ open={open}
504
+ onOpenChange={(isOpen) => {
505
+ if (!disabled) {
506
+ setOpen(isOpen)
507
+ if (isOpen) {
508
+ announce(`Dropdown opened. ${flatOptions.length} options available`)
509
+ } else {
510
+ onSearchChange("") // Clear search when closing
511
+ announce("Dropdown closed")
512
+ }
513
+ }
514
+ }}
515
+ modal={modalPopover}
516
+ >
517
+ <PopoverTrigger asChild>
518
+ <Button
519
+ ref={buttonRef}
520
+ variant="outline"
521
+ role="combobox"
522
+ aria-expanded={open}
523
+ aria-label={placeholder}
524
+ className={cn(multiSelectVariants({ variant }), className)}
525
+ disabled={disabled}
526
+ >
527
+ <div className={cn("flex-1 text-left", autoSize ? "" : "overflow-hidden")}>
528
+ {displayValue}
529
+ </div>
530
+ <div className="flex items-center gap-1 ml-2">
531
+ {selectedValues.length > 0 && !disabled && (
532
+ <button
533
+ onClick={(e) => {
534
+ e.stopPropagation()
535
+ handleClearAll()
536
+ }}
537
+ className="rounded-full hover:bg-muted-foreground/20 focus:outline-none focus:ring-2 focus:ring-ring"
538
+ aria-label="Clear all selections"
539
+ >
540
+ <XCircle className="h-4 w-4 shrink-0 opacity-50" />
541
+ </button>
542
+ )}
543
+ <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
544
+ </div>
545
+ </Button>
546
+ </PopoverTrigger>
547
+ <PopoverContent
548
+ className={cn(
549
+ "w-[var(--radix-popover-trigger-width)] p-0",
550
+ animConfig.popoverAnimation ? popoverAnimations[animConfig.popoverAnimation] : "",
551
+ popoverClassName
552
+ )}
553
+ align="start"
554
+ style={{
555
+ animationDuration: `${animConfig.duration}s`,
556
+ }}
557
+ >
558
+ <Command shouldFilter={false}>
559
+ {searchable && (
560
+ <CommandInput
561
+ placeholder={searchPlaceholder}
562
+ value={searchValue}
563
+ onValueChange={onSearchChange}
564
+ />
565
+ )}
566
+ <CommandList>
567
+ {!hideSelectAll && !isGroupedOptions(options) && (
568
+ <>
569
+ <CommandGroup>
570
+ <CommandItem
571
+ onSelect={handleSelectAll}
572
+ className="cursor-pointer justify-center font-medium"
573
+ >
574
+ Select All
575
+ </CommandItem>
576
+ </CommandGroup>
577
+ <Separator />
578
+ </>
579
+ )}
580
+
581
+ {isLoading ? (
582
+ <div className="py-6 text-center text-sm">
583
+ <Loader2 className="h-4 w-4 animate-spin mx-auto mb-2" />
584
+ {loadingText}
585
+ </div>
586
+ ) : options.length === 0 ? (
587
+ <CommandEmpty>{emptyText}</CommandEmpty>
588
+ ) : (
589
+ renderOptions()
590
+ )}
591
+ </CommandList>
592
+ </Command>
593
+ </PopoverContent>
594
+ </Popover>
595
+ </div>
596
+ )
597
+ }
598
+ )
599
+
600
+ MultiSelectProAsync.displayName = "MultiSelectProAsync"
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Helper utilities for MultiSelectPro components
3
+ */
4
+
5
+ import type { MultiSelectProOption } from './index'
6
+ import type { MultiSelectProAsyncOption } from './async'
7
+
8
+ // ==================== TYPES ====================
9
+
10
+ /**
11
+ * Generic option builder config
12
+ */
13
+ export interface OptionBuilderConfig<T = any> {
14
+ /** Extract unique ID/value from item */
15
+ getValue: (item: T) => string
16
+ /** Extract main label text */
17
+ getLabel: (item: T) => string
18
+ /** Optional: Extract description/subtitle text */
19
+ getDescription?: (item: T) => string | undefined
20
+ /** Optional: Check if item is disabled */
21
+ isDisabled?: (item: T) => boolean
22
+ /** Optional: Custom icon component */
23
+ getIcon?: (item: T) => React.ComponentType<{ className?: string }> | undefined
24
+ /** Optional: Custom styling */
25
+ getStyle?: (item: T) => {
26
+ badgeColor?: string
27
+ iconColor?: string
28
+ gradient?: string
29
+ } | undefined
30
+ }
31
+
32
+ // ==================== HELPERS ====================
33
+
34
+ /**
35
+ * Generic option builder for MultiSelectPro components
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * // Simple usage
40
+ * const options = items.map(createOption({
41
+ * getValue: (item) => item.id,
42
+ * getLabel: (item) => item.name,
43
+ * }))
44
+ *
45
+ * // With description
46
+ * const options = channels.map(createOption({
47
+ * getValue: (ch) => ch.id,
48
+ * getLabel: (ch) => ch.title,
49
+ * getDescription: (ch) => `📱 ${ch.phone}`,
50
+ * }))
51
+ * ```
52
+ */
53
+ export function createOption<T>(
54
+ config: OptionBuilderConfig<T>
55
+ ): (item: T) => MultiSelectProOption | MultiSelectProAsyncOption {
56
+ return (item: T) => ({
57
+ value: config.getValue(item),
58
+ label: config.getLabel(item),
59
+ description: config.getDescription?.(item),
60
+ disabled: config.isDisabled?.(item) ?? false,
61
+ icon: config.getIcon?.(item),
62
+ style: config.getStyle?.(item),
63
+ })
64
+ }
65
+
66
+ /**
67
+ * Batch create options from array
68
+ *
69
+ * @example
70
+ * ```tsx
71
+ * const options = createOptions(channels, {
72
+ * getValue: (ch) => ch.id,
73
+ * getLabel: (ch) => ch.title,
74
+ * getDescription: (ch) => ch.phone,
75
+ * })
76
+ * ```
77
+ */
78
+ export function createOptions<T>(
79
+ items: T[],
80
+ config: OptionBuilderConfig<T>
81
+ ): (MultiSelectProOption | MultiSelectProAsyncOption)[] {
82
+ const builder = createOption(config)
83
+ return items.map(builder)
84
+ }