@smilodon/svelte 1.0.0 → 1.1.1
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/README.md +570 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Production-ready, accessible select component for Svelte applications. Part of the [Smilodon](https://github.com/navidrezadoost/smilodon) UI toolkit.
|
|
4
4
|
|
|
5
|
+
## 📖 Documentation
|
|
6
|
+
|
|
7
|
+
**For comprehensive documentation covering all features, styling options, and advanced patterns:**
|
|
8
|
+
|
|
9
|
+
👉 **[Complete Svelte Guide](./COMPLETE-GUIDE.md)** 👈
|
|
10
|
+
|
|
11
|
+
The complete guide includes:
|
|
12
|
+
- ✅ All 60+ CSS variables for complete customization
|
|
13
|
+
- ✅ Svelte-specific patterns (reactive statements, stores, bind:value)
|
|
14
|
+
- ✅ Complete API reference with TypeScript types
|
|
15
|
+
- ✅ Svelte stores integration (writable, derived)
|
|
16
|
+
- ✅ Custom renderers returning HTML strings
|
|
17
|
+
- ✅ Theme examples with :global() styling
|
|
18
|
+
- ✅ Advanced patterns (Context API, reactive dependencies)
|
|
19
|
+
- ✅ Troubleshooting and accessibility information
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
5
23
|
## Features
|
|
6
24
|
|
|
7
25
|
- ✨ **Single & Multi-Select** - Choose one or multiple options
|
|
@@ -320,6 +338,558 @@ interface GroupedItem {
|
|
|
320
338
|
}
|
|
321
339
|
```
|
|
322
340
|
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## 🎯 Two Ways to Specify Options
|
|
344
|
+
|
|
345
|
+
Smilodon Svelte provides **two powerful approaches** for defining select options, each optimized for different use cases:
|
|
346
|
+
|
|
347
|
+
### Method 1: Data-Driven (Object Arrays) 📊
|
|
348
|
+
|
|
349
|
+
**Use when**: You have structured data and want simple, declarative option rendering.
|
|
350
|
+
|
|
351
|
+
**Advantages**:
|
|
352
|
+
- ✅ Simple and declarative - Svelte-friendly
|
|
353
|
+
- ✅ Auto-conversion from strings/numbers
|
|
354
|
+
- ✅ Perfect for basic dropdowns
|
|
355
|
+
- ✅ Works seamlessly with Svelte stores
|
|
356
|
+
- ✅ Extremely performant (millions of items)
|
|
357
|
+
- ✅ Built-in search and filtering
|
|
358
|
+
- ✅ Full TypeScript type safety
|
|
359
|
+
|
|
360
|
+
**Examples**:
|
|
361
|
+
|
|
362
|
+
```svelte
|
|
363
|
+
<script lang="ts">
|
|
364
|
+
import { Select } from '@smilodon/svelte';
|
|
365
|
+
|
|
366
|
+
// Example 1: Simple object array
|
|
367
|
+
let value = '';
|
|
368
|
+
|
|
369
|
+
const items = [
|
|
370
|
+
{ value: '1', label: 'Apple' },
|
|
371
|
+
{ value: '2', label: 'Banana' },
|
|
372
|
+
{ value: '3', label: 'Cherry' }
|
|
373
|
+
];
|
|
374
|
+
</script>
|
|
375
|
+
|
|
376
|
+
<Select
|
|
377
|
+
items={items}
|
|
378
|
+
bind:value
|
|
379
|
+
placeholder="Select a fruit..."
|
|
380
|
+
/>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
```svelte
|
|
384
|
+
<script lang="ts">
|
|
385
|
+
import { Select } from '@smilodon/svelte';
|
|
386
|
+
|
|
387
|
+
// Example 2: With metadata and disabled options
|
|
388
|
+
let country = '';
|
|
389
|
+
|
|
390
|
+
const countries = [
|
|
391
|
+
{ value: 'us', label: 'United States', disabled: false },
|
|
392
|
+
{ value: 'ca', label: 'Canada', disabled: false },
|
|
393
|
+
{ value: 'mx', label: 'Mexico', disabled: true }
|
|
394
|
+
];
|
|
395
|
+
</script>
|
|
396
|
+
|
|
397
|
+
<Select
|
|
398
|
+
items={countries}
|
|
399
|
+
bind:value={country}
|
|
400
|
+
placeholder="Select a country..."
|
|
401
|
+
/>
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
```svelte
|
|
405
|
+
<script lang="ts">
|
|
406
|
+
import { Select } from '@smilodon/svelte';
|
|
407
|
+
|
|
408
|
+
// Example 3: With grouping
|
|
409
|
+
let food = '';
|
|
410
|
+
|
|
411
|
+
const foods = [
|
|
412
|
+
{ value: 'apple', label: 'Apple', group: 'Fruits' },
|
|
413
|
+
{ value: 'banana', label: 'Banana', group: 'Fruits' },
|
|
414
|
+
{ value: 'carrot', label: 'Carrot', group: 'Vegetables' },
|
|
415
|
+
{ value: 'broccoli', label: 'Broccoli', group: 'Vegetables' }
|
|
416
|
+
];
|
|
417
|
+
</script>
|
|
418
|
+
|
|
419
|
+
<Select
|
|
420
|
+
items={foods}
|
|
421
|
+
bind:value={food}
|
|
422
|
+
placeholder="Select food..."
|
|
423
|
+
/>
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
```svelte
|
|
427
|
+
<script lang="ts">
|
|
428
|
+
import { Select } from '@smilodon/svelte';
|
|
429
|
+
|
|
430
|
+
// Example 4: Auto-conversion from strings
|
|
431
|
+
let color = '';
|
|
432
|
+
const colors = ['Red', 'Green', 'Blue', 'Yellow'];
|
|
433
|
+
</script>
|
|
434
|
+
|
|
435
|
+
<Select
|
|
436
|
+
items={colors}
|
|
437
|
+
bind:value={color}
|
|
438
|
+
placeholder="Select a color..."
|
|
439
|
+
/>
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
```svelte
|
|
443
|
+
<script lang="ts">
|
|
444
|
+
import { Select } from '@smilodon/svelte';
|
|
445
|
+
|
|
446
|
+
// Example 5: Auto-conversion from numbers
|
|
447
|
+
let size: number | string = '';
|
|
448
|
+
const sizes = [10, 20, 30, 40, 50];
|
|
449
|
+
</script>
|
|
450
|
+
|
|
451
|
+
<Select
|
|
452
|
+
items={sizes}
|
|
453
|
+
bind:value={size}
|
|
454
|
+
placeholder="Select size..."
|
|
455
|
+
/>
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
```svelte
|
|
459
|
+
<script lang="ts">
|
|
460
|
+
import { Select } from '@smilodon/svelte';
|
|
461
|
+
|
|
462
|
+
// Example 6: Large datasets with reactive statement
|
|
463
|
+
let id = '';
|
|
464
|
+
|
|
465
|
+
$: items = Array.from({ length: 100_000 }, (_, i) => ({
|
|
466
|
+
value: i.toString(),
|
|
467
|
+
label: `Item ${i + 1}`
|
|
468
|
+
}));
|
|
469
|
+
</script>
|
|
470
|
+
|
|
471
|
+
<Select
|
|
472
|
+
items={items}
|
|
473
|
+
bind:value={id}
|
|
474
|
+
virtualized
|
|
475
|
+
placeholder="Select from 100K items..."
|
|
476
|
+
/>
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Method 2: Component-Driven (Custom Renderers) 🎨
|
|
480
|
+
|
|
481
|
+
**Use when**: You need rich, interactive option content with custom HTML/styling.
|
|
482
|
+
|
|
483
|
+
**Advantages**:
|
|
484
|
+
- ✅ Full control over option rendering
|
|
485
|
+
- ✅ Rich content (images, icons, badges, multi-line text)
|
|
486
|
+
- ✅ Custom HTML and styling
|
|
487
|
+
- ✅ Reactive data binding
|
|
488
|
+
- ✅ Conditional rendering based on item data
|
|
489
|
+
- ✅ Works with Svelte stores
|
|
490
|
+
- ✅ Perfect for complex UIs (user cards, product listings, etc.)
|
|
491
|
+
|
|
492
|
+
**How it works**: Provide an `optionTemplate` function that returns HTML string for each option.
|
|
493
|
+
|
|
494
|
+
**Examples**:
|
|
495
|
+
|
|
496
|
+
```svelte
|
|
497
|
+
<script lang="ts">
|
|
498
|
+
import { Select, type SelectItem } from '@smilodon/svelte';
|
|
499
|
+
|
|
500
|
+
// Example 1: Simple custom template with icons
|
|
501
|
+
interface Language extends SelectItem {
|
|
502
|
+
icon: string;
|
|
503
|
+
description: string;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
let lang = '';
|
|
507
|
+
|
|
508
|
+
const languages: Language[] = [
|
|
509
|
+
{ value: 'js', label: 'JavaScript', icon: '🟨', description: 'Dynamic scripting language' },
|
|
510
|
+
{ value: 'py', label: 'Python', icon: '🐍', description: 'General-purpose programming' },
|
|
511
|
+
{ value: 'rs', label: 'Rust', icon: '🦀', description: 'Systems programming language' }
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
const languageRenderer = (item: Language, index: number) => `
|
|
515
|
+
<div style="display: flex; align-items: center; gap: 12px;">
|
|
516
|
+
<span style="font-size: 24px;">${item.icon}</span>
|
|
517
|
+
<div>
|
|
518
|
+
<div style="font-weight: 600;">${item.label}</div>
|
|
519
|
+
<div style="font-size: 12px; color: #6b7280;">${item.description}</div>
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
`;
|
|
523
|
+
</script>
|
|
524
|
+
|
|
525
|
+
<Select
|
|
526
|
+
items={languages}
|
|
527
|
+
bind:value={lang}
|
|
528
|
+
optionTemplate={languageRenderer}
|
|
529
|
+
placeholder="Select a language..."
|
|
530
|
+
/>
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
```svelte
|
|
534
|
+
<script lang="ts">
|
|
535
|
+
import { Select, type SelectItem } from '@smilodon/svelte';
|
|
536
|
+
|
|
537
|
+
// Example 2: User selection with avatars
|
|
538
|
+
interface User extends SelectItem {
|
|
539
|
+
email: string;
|
|
540
|
+
avatar: string;
|
|
541
|
+
role: 'Admin' | 'User' | 'Moderator';
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
let userId = '';
|
|
545
|
+
|
|
546
|
+
const users: User[] = [
|
|
547
|
+
{
|
|
548
|
+
value: '1',
|
|
549
|
+
label: 'John Doe',
|
|
550
|
+
email: 'john@example.com',
|
|
551
|
+
avatar: 'https://i.pravatar.cc/150?img=1',
|
|
552
|
+
role: 'Admin'
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
value: '2',
|
|
556
|
+
label: 'Jane Smith',
|
|
557
|
+
email: 'jane@example.com',
|
|
558
|
+
avatar: 'https://i.pravatar.cc/150?img=2',
|
|
559
|
+
role: 'User'
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
value: '3',
|
|
563
|
+
label: 'Bob Johnson',
|
|
564
|
+
email: 'bob@example.com',
|
|
565
|
+
avatar: 'https://i.pravatar.cc/150?img=3',
|
|
566
|
+
role: 'Moderator'
|
|
567
|
+
}
|
|
568
|
+
];
|
|
569
|
+
|
|
570
|
+
const userRenderer = (item: User) => `
|
|
571
|
+
<div style="display: flex; align-items: center; gap: 12px; padding: 4px 0;">
|
|
572
|
+
<img
|
|
573
|
+
src="${item.avatar}"
|
|
574
|
+
alt="${item.label}"
|
|
575
|
+
style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover;"
|
|
576
|
+
/>
|
|
577
|
+
<div style="flex: 1;">
|
|
578
|
+
<div style="font-weight: 600; color: #1f2937;">${item.label}</div>
|
|
579
|
+
<div style="font-size: 13px; color: #6b7280;">${item.email}</div>
|
|
580
|
+
</div>
|
|
581
|
+
<span style="
|
|
582
|
+
padding: 4px 8px;
|
|
583
|
+
background: ${item.role === 'Admin' ? '#dbeafe' : '#f3f4f6'};
|
|
584
|
+
color: ${item.role === 'Admin' ? '#1e40af' : '#374151'};
|
|
585
|
+
border-radius: 12px;
|
|
586
|
+
font-size: 11px;
|
|
587
|
+
font-weight: 600;
|
|
588
|
+
">${item.role}</span>
|
|
589
|
+
</div>
|
|
590
|
+
`;
|
|
591
|
+
</script>
|
|
592
|
+
|
|
593
|
+
<Select
|
|
594
|
+
items={users}
|
|
595
|
+
bind:value={userId}
|
|
596
|
+
optionTemplate={userRenderer}
|
|
597
|
+
placeholder="Select a user..."
|
|
598
|
+
/>
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
```svelte
|
|
602
|
+
<script lang="ts">
|
|
603
|
+
import { Select, type SelectItem } from '@smilodon/svelte';
|
|
604
|
+
|
|
605
|
+
// Example 3: Product selection with images and pricing
|
|
606
|
+
interface Product extends SelectItem {
|
|
607
|
+
price: number;
|
|
608
|
+
stock: number;
|
|
609
|
+
image: string;
|
|
610
|
+
badge?: string;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
let productId = '';
|
|
614
|
+
|
|
615
|
+
const products: Product[] = [
|
|
616
|
+
{
|
|
617
|
+
value: 'p1',
|
|
618
|
+
label: 'Premium Laptop',
|
|
619
|
+
price: 1299.99,
|
|
620
|
+
stock: 15,
|
|
621
|
+
image: 'https://via.placeholder.com/60',
|
|
622
|
+
badge: 'Best Seller'
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
value: 'p2',
|
|
626
|
+
label: 'Wireless Mouse',
|
|
627
|
+
price: 29.99,
|
|
628
|
+
stock: 150,
|
|
629
|
+
image: 'https://via.placeholder.com/60'
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
value: 'p3',
|
|
633
|
+
label: 'Mechanical Keyboard',
|
|
634
|
+
price: 89.99,
|
|
635
|
+
stock: 0,
|
|
636
|
+
image: 'https://via.placeholder.com/60',
|
|
637
|
+
badge: 'Out of Stock'
|
|
638
|
+
}
|
|
639
|
+
];
|
|
640
|
+
|
|
641
|
+
const productRenderer = (item: Product) => `
|
|
642
|
+
<div style="display: flex; align-items: center; gap: 12px; opacity: ${item.stock === 0 ? '0.5' : '1'};">
|
|
643
|
+
<img
|
|
644
|
+
src="${item.image}"
|
|
645
|
+
alt="${item.label}"
|
|
646
|
+
style="width: 60px; height: 60px; border-radius: 8px; object-fit: cover; border: 1px solid #e5e7eb;"
|
|
647
|
+
/>
|
|
648
|
+
<div style="flex: 1;">
|
|
649
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
650
|
+
<span style="font-weight: 600; color: #1f2937;">${item.label}</span>
|
|
651
|
+
${item.badge ? `
|
|
652
|
+
<span style="
|
|
653
|
+
padding: 2px 6px;
|
|
654
|
+
background: ${item.badge === 'Best Seller' ? '#dcfce7' : '#fee2e2'};
|
|
655
|
+
color: ${item.badge === 'Best Seller' ? '#166534' : '#991b1b'};
|
|
656
|
+
border-radius: 4px;
|
|
657
|
+
font-size: 10px;
|
|
658
|
+
font-weight: 600;
|
|
659
|
+
">${item.badge}</span>
|
|
660
|
+
` : ''}
|
|
661
|
+
</div>
|
|
662
|
+
<div style="margin-top: 4px; display: flex; justify-content: space-between; align-items: center;">
|
|
663
|
+
<span style="font-size: 16px; font-weight: 700; color: #059669;">$${item.price.toFixed(2)}</span>
|
|
664
|
+
<span style="font-size: 12px; color: #6b7280;">${item.stock > 0 ? `${item.stock} in stock` : 'Out of stock'}</span>
|
|
665
|
+
</div>
|
|
666
|
+
</div>
|
|
667
|
+
</div>
|
|
668
|
+
`;
|
|
669
|
+
</script>
|
|
670
|
+
|
|
671
|
+
<Select
|
|
672
|
+
items={products}
|
|
673
|
+
bind:value={productId}
|
|
674
|
+
optionTemplate={productRenderer}
|
|
675
|
+
placeholder="Select a product..."
|
|
676
|
+
/>
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
```svelte
|
|
680
|
+
<script lang="ts">
|
|
681
|
+
import { Select, type SelectItem } from '@smilodon/svelte';
|
|
682
|
+
|
|
683
|
+
// Example 4: Status indicators with conditional styling
|
|
684
|
+
interface Task extends SelectItem {
|
|
685
|
+
status: 'completed' | 'in-progress' | 'pending';
|
|
686
|
+
priority: 'high' | 'medium' | 'low';
|
|
687
|
+
assignee: string;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
let taskId = '';
|
|
691
|
+
|
|
692
|
+
const tasks: Task[] = [
|
|
693
|
+
{ value: 't1', label: 'Design Homepage', status: 'completed', priority: 'high', assignee: 'John' },
|
|
694
|
+
{ value: 't2', label: 'API Integration', status: 'in-progress', priority: 'high', assignee: 'Jane' },
|
|
695
|
+
{ value: 't3', label: 'Write Documentation', status: 'pending', priority: 'medium', assignee: 'Bob' },
|
|
696
|
+
{ value: 't4', label: 'Bug Fixes', status: 'in-progress', priority: 'low', assignee: 'Alice' }
|
|
697
|
+
];
|
|
698
|
+
|
|
699
|
+
const statusConfig = {
|
|
700
|
+
'completed': { bg: '#dcfce7', color: '#166534', icon: '✓' },
|
|
701
|
+
'in-progress': { bg: '#dbeafe', color: '#1e40af', icon: '⟳' },
|
|
702
|
+
'pending': { bg: '#fef3c7', color: '#92400e', icon: '○' }
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
const priorityColors = {
|
|
706
|
+
'high': '#ef4444',
|
|
707
|
+
'medium': '#f59e0b',
|
|
708
|
+
'low': '#10b981'
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
const taskRenderer = (item: Task) => {
|
|
712
|
+
const status = statusConfig[item.status];
|
|
713
|
+
return `
|
|
714
|
+
<div style="display: flex; align-items: center; gap: 10px; padding: 4px 0;">
|
|
715
|
+
<div style="
|
|
716
|
+
width: 24px;
|
|
717
|
+
height: 24px;
|
|
718
|
+
border-radius: 50%;
|
|
719
|
+
background: ${status.bg};
|
|
720
|
+
color: ${status.color};
|
|
721
|
+
display: flex;
|
|
722
|
+
align-items: center;
|
|
723
|
+
justify-content: center;
|
|
724
|
+
font-weight: bold;
|
|
725
|
+
">${status.icon}</div>
|
|
726
|
+
<div style="flex: 1;">
|
|
727
|
+
<div style="font-weight: 600; color: #1f2937;">${item.label}</div>
|
|
728
|
+
<div style="font-size: 12px; color: #6b7280; margin-top: 2px;">
|
|
729
|
+
Assigned to ${item.assignee}
|
|
730
|
+
</div>
|
|
731
|
+
</div>
|
|
732
|
+
<div style="
|
|
733
|
+
width: 8px;
|
|
734
|
+
height: 8px;
|
|
735
|
+
border-radius: 50%;
|
|
736
|
+
background: ${priorityColors[item.priority]};
|
|
737
|
+
" title="${item.priority} priority"></div>
|
|
738
|
+
</div>
|
|
739
|
+
`;
|
|
740
|
+
};
|
|
741
|
+
</script>
|
|
742
|
+
|
|
743
|
+
<Select
|
|
744
|
+
items={tasks}
|
|
745
|
+
bind:value={taskId}
|
|
746
|
+
optionTemplate={taskRenderer}
|
|
747
|
+
placeholder="Select a task..."
|
|
748
|
+
/>
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
```svelte
|
|
752
|
+
<script lang="ts">
|
|
753
|
+
import { Select, type SelectItem } from '@smilodon/svelte';
|
|
754
|
+
import { writable } from 'svelte/store';
|
|
755
|
+
|
|
756
|
+
// Example 5: Using Svelte stores
|
|
757
|
+
interface Tag extends SelectItem {
|
|
758
|
+
color: string;
|
|
759
|
+
count: number;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const tag = writable('');
|
|
763
|
+
|
|
764
|
+
const tags: Tag[] = [
|
|
765
|
+
{ value: 'react', label: 'React', color: 'blue', count: 1250 },
|
|
766
|
+
{ value: 'vue', label: 'Vue', color: 'green', count: 850 },
|
|
767
|
+
{ value: 'angular', label: 'Angular', color: 'red', count: 420 }
|
|
768
|
+
];
|
|
769
|
+
|
|
770
|
+
const tagRenderer = (item: Tag) => `
|
|
771
|
+
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px;">
|
|
772
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
773
|
+
<span style="width: 12px; height: 12px; border-radius: 50%; background: ${item.color};"></span>
|
|
774
|
+
<span style="font-weight: 600; color: #1f2937;">${item.label}</span>
|
|
775
|
+
</div>
|
|
776
|
+
<span style="font-size: 14px; color: #6b7280;">${item.count} posts</span>
|
|
777
|
+
</div>
|
|
778
|
+
`;
|
|
779
|
+
</script>
|
|
780
|
+
|
|
781
|
+
<Select
|
|
782
|
+
items={tags}
|
|
783
|
+
bind:value={$tag}
|
|
784
|
+
optionTemplate={tagRenderer}
|
|
785
|
+
placeholder="Select a tag..."
|
|
786
|
+
/>
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
### Comparison: When to Use Each Method
|
|
790
|
+
|
|
791
|
+
| Feature | Method 1: Object Arrays | Method 2: Custom Renderers |
|
|
792
|
+
|---------|------------------------|---------------------------|
|
|
793
|
+
| **Setup Complexity** | ⭐ Simple | ⭐⭐ Moderate |
|
|
794
|
+
| **Rendering Speed** | ⭐⭐⭐ Fastest | ⭐⭐ Fast |
|
|
795
|
+
| **Visual Customization** | ⭐⭐ Limited | ⭐⭐⭐ Unlimited |
|
|
796
|
+
| **Svelte Integration** | ⭐⭐⭐ Seamless | ⭐⭐⭐ Seamless |
|
|
797
|
+
| **Store Support** | ⭐⭐⭐ Full | ⭐⭐⭐ Full |
|
|
798
|
+
| **TypeScript Support** | ⭐⭐⭐ Full | ⭐⭐⭐ Full |
|
|
799
|
+
| **Performance (1M items)** | ⭐⭐⭐ Excellent | ⭐⭐ Good |
|
|
800
|
+
| **Learning Curve** | ⭐ Easy | ⭐⭐ Medium |
|
|
801
|
+
|
|
802
|
+
**Best Practices**:
|
|
803
|
+
|
|
804
|
+
✅ **Use Method 1 (Object Arrays) when**:
|
|
805
|
+
- You need simple text-based options
|
|
806
|
+
- Performance is critical (millions of items)
|
|
807
|
+
- You want minimal code
|
|
808
|
+
- Built-in search/filter is sufficient
|
|
809
|
+
- Working with external APIs returning plain data
|
|
810
|
+
|
|
811
|
+
✅ **Use Method 2 (Custom Renderers) when**:
|
|
812
|
+
- You need images, icons, or badges
|
|
813
|
+
- Options require multiple lines of text
|
|
814
|
+
- Custom styling/layout is important
|
|
815
|
+
- Conditional rendering based on data
|
|
816
|
+
- Rich user experience is priority
|
|
817
|
+
- Need to integrate with Svelte stores in rendering
|
|
818
|
+
|
|
819
|
+
### Combining Both Methods
|
|
820
|
+
|
|
821
|
+
You can start with Method 1 and add Method 2 later as your UI evolves:
|
|
822
|
+
|
|
823
|
+
```svelte
|
|
824
|
+
<script lang="ts">
|
|
825
|
+
import { Select } from '@smilodon/svelte';
|
|
826
|
+
|
|
827
|
+
let value = '';
|
|
828
|
+
|
|
829
|
+
// Start simple
|
|
830
|
+
const items = ['Option 1', 'Option 2', 'Option 3'];
|
|
831
|
+
|
|
832
|
+
// Later, add custom rendering without changing items
|
|
833
|
+
const customRenderer = (item: any, index: number) => `
|
|
834
|
+
<div style="padding: 8px; background: ${index % 2 ? '#f9fafb' : 'white'};">
|
|
835
|
+
<strong>${item.label || item}</strong>
|
|
836
|
+
</div>
|
|
837
|
+
`;
|
|
838
|
+
</script>
|
|
839
|
+
|
|
840
|
+
<Select
|
|
841
|
+
items={items}
|
|
842
|
+
bind:value
|
|
843
|
+
optionTemplate={customRenderer}
|
|
844
|
+
/>
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
### Performance Tips
|
|
848
|
+
|
|
849
|
+
**For Method 1**:
|
|
850
|
+
- Use reactive statements (`$:`) to memoize large item arrays
|
|
851
|
+
- Enable `virtualized` prop for 1000+ items
|
|
852
|
+
- Enable `infiniteScroll` for dynamic loading
|
|
853
|
+
|
|
854
|
+
**For Method 2**:
|
|
855
|
+
- Keep renderer function pure (no side effects)
|
|
856
|
+
- Avoid heavy computations in renderer
|
|
857
|
+
- Cache renderer functions when possible
|
|
858
|
+
- Use template literals for cleaner HTML strings
|
|
859
|
+
|
|
860
|
+
```svelte
|
|
861
|
+
<script lang="ts">
|
|
862
|
+
import { Select } from '@smilodon/svelte';
|
|
863
|
+
|
|
864
|
+
let value = '';
|
|
865
|
+
|
|
866
|
+
// Memoize items with reactive statement
|
|
867
|
+
$: items = Array.from({ length: 10000 }, (_, i) => ({
|
|
868
|
+
value: i.toString(),
|
|
869
|
+
label: `Item ${i + 1}`,
|
|
870
|
+
description: `Description for item ${i + 1}`
|
|
871
|
+
}));
|
|
872
|
+
|
|
873
|
+
// Pure renderer function
|
|
874
|
+
const renderer = (item: any, index: number) => `
|
|
875
|
+
<div>
|
|
876
|
+
<div style="font-weight: 600;">${item.label}</div>
|
|
877
|
+
<div style="font-size: 12px; color: #666;">${item.description}</div>
|
|
878
|
+
</div>
|
|
879
|
+
`;
|
|
880
|
+
</script>
|
|
881
|
+
|
|
882
|
+
<Select
|
|
883
|
+
items={items}
|
|
884
|
+
bind:value
|
|
885
|
+
optionTemplate={renderer}
|
|
886
|
+
virtualized
|
|
887
|
+
estimatedItemHeight={60}
|
|
888
|
+
/>
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
323
893
|
## Styling
|
|
324
894
|
|
|
325
895
|
The component uses CSS variables for easy customization:
|