@smilodon/svelte 1.4.6 → 1.4.11

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,1323 @@
1
+ # @smilodon/svelte - Complete Guide
2
+
3
+ **Production-ready, accessible Select component for Svelte applications**
4
+
5
+ This guide provides comprehensive documentation for using Smilodon Select in Svelte applications, covering all features, styling options, and advanced use cases.
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ 1. [Installation & Setup](#installation--setup)
12
+ 2. [SvelteKit & SSR Setup](#sveltekit--ssr-setup)
13
+ 3. [Basic Usage](#basic-usage)
14
+ 4. [Complete Props Reference](#complete-props-reference)
15
+ 5. [Input Formats](#input-formats)
16
+ 6. [Single Selection](#single-selection)
17
+ 7. [Multi-Selection](#multi-selection)
18
+ 8. [Searchable Select](#searchable-select)
19
+ 9. [Grouped Options](#grouped-options)
20
+ 10. [Disabled States](#disabled-states)
21
+ 11. [Event Handling](#event-handling)
22
+ 12. [Styling & Theming](#styling--theming)
23
+ 13. [Custom Renderers](#custom-renderers)
24
+ 14. [Performance Optimization](#performance-optimization)
25
+ 15. [TypeScript Integration](#typescript-integration)
26
+ 16. [Stores Integration](#stores-integration)
27
+ 17. [Accessibility](#accessibility)
28
+ 18. [Advanced Patterns](#advanced-patterns)
29
+ 19. [Troubleshooting](#troubleshooting)
30
+
31
+ ---
32
+
33
+ ## Installation & Setup
34
+
35
+ ### Installation
36
+
37
+ ```bash
38
+ npm install @smilodon/svelte @smilodon/core
39
+ ```
40
+
41
+ or with yarn:
42
+
43
+ ```bash
44
+ yarn add @smilodon/svelte @smilodon/core
45
+ ```
46
+
47
+ or with pnpm:
48
+
49
+ ```bash
50
+ pnpm add @smilodon/svelte @smilodon/core
51
+ ```
52
+
53
+ ### Svelte Configuration
54
+
55
+ For SvelteKit projects, the component should work out of the box. For Vite-based Svelte projects:
56
+
57
+ ```javascript
58
+ // vite.config.js
59
+ import { defineConfig } from 'vite';
60
+ import { svelte } from '@sveltejs/vite-plugin-svelte';
61
+
62
+ export default defineConfig({
63
+ plugins: [svelte()],
64
+ });
65
+ ```
66
+
67
+ ### Basic Import
68
+
69
+ ```svelte
70
+ <script lang="ts">
71
+ import { Select } from '@smilodon/svelte';
72
+ import type { SelectItem } from '@smilodon/core';
73
+ </script>
74
+ ```
75
+
76
+ ---
77
+
78
+ ## SvelteKit & SSR Setup
79
+
80
+ The Svelte adapter is designed for browser hydration around the shared custom element runtime.
81
+
82
+ ### Safe SvelteKit pattern
83
+
84
+ ```svelte
85
+ <script lang="ts">
86
+ import { onMount } from 'svelte'
87
+ import { Select } from '@smilodon/svelte'
88
+
89
+ let items = []
90
+ let value: string | number | Array<string | number> = []
91
+
92
+ onMount(async () => {
93
+ items = await fetch('/api/frameworks').then((response) => response.json())
94
+ })
95
+ </script>
96
+
97
+ <Select
98
+ {items}
99
+ bind:value
100
+ searchable
101
+ multiple
102
+ clearable
103
+ />
104
+ ```
105
+
106
+ ### Using SvelteKit `load()` data
107
+
108
+ ```svelte
109
+ <script lang="ts">
110
+ import { Select } from '@smilodon/svelte'
111
+
112
+ export let data: {
113
+ frameworks: Array<{ value: string; label: string }>
114
+ }
115
+
116
+ let value = ''
117
+ </script>
118
+
119
+ <Select
120
+ items={data.frameworks}
121
+ bind:value
122
+ searchable
123
+ placeholder="Choose a framework"
124
+ />
125
+ ```
126
+
127
+ ### SSR guidance
128
+
129
+ - Keep direct `window`, `document`, and element-ref access inside `onMount()`.
130
+ - Pass serializable arrays from `load()` and let the adapter sync those items after hydration.
131
+ - Use exported component methods for imperative control instead of reaching into DOM internals.
132
+ - Prefer `optionRenderer` when you need full DOM-driven option content.
133
+
134
+ ### SvelteKit production checklist
135
+
136
+ 1. Fetch server data in `load()` or browser-only data in `onMount()`.
137
+ 2. Avoid direct custom-element mutation before mount.
138
+ 3. Keep large `items` arrays stable where possible.
139
+ 4. Keep `virtualized` enabled for large lists.
140
+ 5. Style through CSS variables and `:global(enhanced-select::part(...))` selectors.
141
+
142
+ ---
143
+
144
+ ## Basic Usage
145
+
146
+ ### Minimal Example
147
+
148
+ ```svelte
149
+ <script lang="ts">
150
+ import { Select } from '@smilodon/svelte';
151
+
152
+ let selectedValue: string | number = '';
153
+ const items = ['Apple', 'Banana', 'Cherry'];
154
+ </script>
155
+
156
+ <Select {items} bind:value={selectedValue} placeholder="Select a fruit..." />
157
+ ```
158
+
159
+ ### With Object Array
160
+
161
+ ```svelte
162
+ <script lang="ts">
163
+ import { Select } from '@smilodon/svelte';
164
+ import type { SelectItem } from '@smilodon/core';
165
+
166
+ let selectedValue: string | number = '';
167
+
168
+ const items: SelectItem[] = [
169
+ { value: 'apple', label: 'Apple' },
170
+ { value: 'banana', label: 'Banana' },
171
+ { value: 'cherry', label: 'Cherry' },
172
+ ];
173
+ </script>
174
+
175
+ <Select {items} bind:value={selectedValue} placeholder="Select a fruit..." />
176
+
177
+ {#if selectedValue}
178
+ <p>Selected: {selectedValue}</p>
179
+ {/if}
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Complete Props Reference
185
+
186
+ ### All Available Props
187
+
188
+ ```typescript
189
+ interface SelectProps {
190
+ // Required
191
+ items: SelectItem[] | string[] | number[];
192
+
193
+ // Value Management
194
+ value?: string | number | (string | number)[];
195
+
196
+ // Behavior
197
+ multiple?: boolean;
198
+ searchable?: boolean;
199
+ disabled?: boolean;
200
+ placeholder?: string;
201
+
202
+ // Display
203
+ maxHeight?: number;
204
+ estimatedItemHeight?: number;
205
+
206
+ // Styling
207
+ class?: string;
208
+ style?: string;
209
+
210
+ // Advanced
211
+ virtualization?: boolean;
212
+ customRenderer?: (item: SelectItem, index: number) => string;
213
+ }
214
+ ```
215
+
216
+ ### Props Details
217
+
218
+ | Prop | Type | Default | Description |
219
+ |------|------|---------|-------------|
220
+ | `items` | `SelectItem[] \| string[] \| number[]` | **Required** | Array of items to display |
221
+ | `value` | `string \| number \| array` | `undefined` | Current selected value(s) - use `bind:value` |
222
+ | `multiple` | `boolean` | `false` | Enable multi-selection |
223
+ | `searchable` | `boolean` | `true` | Enable search functionality |
224
+ | `disabled` | `boolean` | `false` | Disable the select |
225
+ | `placeholder` | `string` | `'Select...'` | Placeholder text |
226
+ | `maxHeight` | `number` | `300` | Max dropdown height (px) |
227
+ | `estimatedItemHeight` | `number` | `48` | Estimated item height for virtualization |
228
+ | `class` | `string` | `undefined` | CSS class name |
229
+ | `style` | `string` | `undefined` | Inline styles (CSS variables) |
230
+ | `virtualization` | `boolean` | `true` | Enable virtual scrolling |
231
+ | `customRenderer` | `function` | `undefined` | Custom option renderer |
232
+
233
+ ### Events
234
+
235
+ | Event | Detail | Description |
236
+ |-------|--------|-------------|
237
+ | `change` | `{ value }` | Fired when value changes |
238
+ | `open` | - | Fired when dropdown opens |
239
+ | `close` | - | Fired when dropdown closes |
240
+ | `search` | `{ query }` | Fired on search input |
241
+
242
+ ---
243
+
244
+ ## Input Formats
245
+
246
+ Smilodon accepts three input formats for maximum flexibility:
247
+
248
+ ### 1. Object Array (SelectItem[])
249
+
250
+ ```svelte
251
+ <script lang="ts">
252
+ import { Select } from '@smilodon/svelte';
253
+ import type { SelectItem } from '@smilodon/core';
254
+
255
+ let selectedValue: string | number = '';
256
+
257
+ const items: SelectItem[] = [
258
+ { value: 'us', label: 'United States' },
259
+ { value: 'ca', label: 'Canada' },
260
+ { value: 'mx', label: 'Mexico' },
261
+ ];
262
+ </script>
263
+
264
+ <Select {items} bind:value={selectedValue} />
265
+ ```
266
+
267
+ ### 2. String Array (Auto-converted)
268
+
269
+ ```svelte
270
+ <script lang="ts">
271
+ import { Select } from '@smilodon/svelte';
272
+
273
+ let selectedValue = '';
274
+ const items = ['Apple', 'Banana', 'Cherry', 'Date'];
275
+ </script>
276
+
277
+ <Select {items} bind:value={selectedValue} />
278
+ ```
279
+
280
+ Automatically converted to:
281
+ ```typescript
282
+ [
283
+ { value: 'Apple', label: 'Apple' },
284
+ { value: 'Banana', label: 'Banana' },
285
+ { value: 'Cherry', label: 'Cherry' },
286
+ { value: 'Date', label: 'Date' },
287
+ ]
288
+ ```
289
+
290
+ ### 3. Number Array (Auto-converted)
291
+
292
+ ```svelte
293
+ <script lang="ts">
294
+ import { Select } from '@smilodon/svelte';
295
+
296
+ let selectedValue: number = 0;
297
+ const items = [1, 2, 3, 5, 8, 13, 21];
298
+ </script>
299
+
300
+ <Select {items} bind:value={selectedValue} />
301
+ ```
302
+
303
+ ---
304
+
305
+ ## Single Selection
306
+
307
+ ### Basic Single Select
308
+
309
+ ```svelte
310
+ <script lang="ts">
311
+ import { Select } from '@smilodon/svelte';
312
+ import type { SelectItem } from '@smilodon/core';
313
+
314
+ let country: string | number = '';
315
+
316
+ const countries: SelectItem[] = [
317
+ { value: 'us', label: 'United States' },
318
+ { value: 'ca', label: 'Canada' },
319
+ { value: 'uk', label: 'United Kingdom' },
320
+ { value: 'au', label: 'Australia' },
321
+ ];
322
+ </script>
323
+
324
+ <Select {items}={countries} bind:value={country} placeholder="Select your country" />
325
+
326
+ {#if country}
327
+ <p>Selected: {country}</p>
328
+ {/if}
329
+ ```
330
+
331
+ ### With Form Integration
332
+
333
+ ```svelte
334
+ <script lang="ts">
335
+ import { Select } from '@smilodon/svelte';
336
+
337
+ let formData = {
338
+ name: '',
339
+ country: '',
340
+ language: '',
341
+ };
342
+
343
+ function handleSubmit() {
344
+ console.log('Form data:', formData);
345
+ }
346
+ </script>
347
+
348
+ <form on:submit|preventDefault={handleSubmit}>
349
+ <label>
350
+ Name:
351
+ <input bind:value={formData.name} type="text" />
352
+ </label>
353
+
354
+ <label>
355
+ Country:
356
+ <Select
357
+ items={['USA', 'Canada', 'Mexico', 'UK', 'Australia']}
358
+ bind:value={formData.country}
359
+ placeholder="Select country"
360
+ />
361
+ </label>
362
+
363
+ <label>
364
+ Language:
365
+ <Select
366
+ items={['English', 'Spanish', 'French', 'German']}
367
+ bind:value={formData.language}
368
+ placeholder="Select language"
369
+ />
370
+ </label>
371
+
372
+ <button type="submit">Submit</button>
373
+ </form>
374
+ ```
375
+
376
+ ---
377
+
378
+ ## Multi-Selection
379
+
380
+ ### Basic Multi-Select
381
+
382
+ ```svelte
383
+ <script lang="ts">
384
+ import { Select } from '@smilodon/svelte';
385
+ import type { SelectItem } from '@smilodon/core';
386
+
387
+ let languages: (string | number)[] = [];
388
+
389
+ const items: SelectItem[] = [
390
+ { value: 'js', label: 'JavaScript' },
391
+ { value: 'ts', label: 'TypeScript' },
392
+ { value: 'py', label: 'Python' },
393
+ { value: 'rs', label: 'Rust' },
394
+ { value: 'go', label: 'Go' },
395
+ ];
396
+ </script>
397
+
398
+ <Select {items} bind:value={languages} multiple placeholder="Select programming languages" />
399
+
400
+ <div>
401
+ <strong>Selected ({languages.length}):</strong>
402
+ {#if languages.length > 0}
403
+ <ul>
404
+ {#each languages as lang}
405
+ <li>{lang}</li>
406
+ {/each}
407
+ </ul>
408
+ {:else}
409
+ <p>No languages selected</p>
410
+ {/if}
411
+ </div>
412
+ ```
413
+
414
+ ### Multi-Select with Limit
415
+
416
+ ```svelte
417
+ <script lang="ts">
418
+ import { Select } from '@smilodon/svelte';
419
+
420
+ let selected: (string | number)[] = [];
421
+ const maxSelections = 3;
422
+
423
+ $: if (selected.length > maxSelections) {
424
+ selected = selected.slice(0, maxSelections);
425
+ }
426
+ </script>
427
+
428
+ <Select
429
+ items={['Red', 'Blue', 'Green', 'Yellow', 'Purple', 'Orange']}
430
+ bind:value={selected}
431
+ multiple
432
+ placeholder="Select up to {maxSelections} colors"
433
+ />
434
+
435
+ <p>{selected.length} / {maxSelections} selected</p>
436
+ ```
437
+
438
+ ---
439
+
440
+ ## Searchable Select
441
+
442
+ ### Basic Search
443
+
444
+ ```svelte
445
+ <script lang="ts">
446
+ import { Select } from '@smilodon/svelte';
447
+ import type { SelectItem } from '@smilodon/core';
448
+
449
+ let country: string | number = '';
450
+
451
+ // Large dataset
452
+ const countries: SelectItem[] = [
453
+ { value: 'us', label: 'United States' },
454
+ { value: 'ca', label: 'Canada' },
455
+ { value: 'mx', label: 'Mexico' },
456
+ { value: 'br', label: 'Brazil' },
457
+ { value: 'ar', label: 'Argentina' },
458
+ // ... 200+ countries
459
+ ];
460
+ </script>
461
+
462
+ <Select
463
+ {items}={countries}
464
+ bind:value={country}
465
+ searchable
466
+ placeholder="Search for a country..."
467
+ />
468
+ ```
469
+
470
+ ### Search with Event Handler
471
+
472
+ ```svelte
473
+ <script lang="ts">
474
+ import { Select } from '@smilodon/svelte';
475
+
476
+ let value = '';
477
+ let searchQuery = '';
478
+
479
+ function handleSearch(event: CustomEvent<{ query: string }>) {
480
+ searchQuery = event.detail.query;
481
+ console.log('Searching for:', searchQuery);
482
+ // Optionally trigger API call, analytics, etc.
483
+ }
484
+ </script>
485
+
486
+ <Select
487
+ items={['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']}
488
+ bind:value
489
+ searchable
490
+ on:search={handleSearch}
491
+ placeholder="Type to search..."
492
+ />
493
+
494
+ {#if searchQuery}
495
+ <p>Current search: {searchQuery}</p>
496
+ {/if}
497
+ ```
498
+
499
+ ---
500
+
501
+ ## Grouped Options
502
+
503
+ ### Basic Groups
504
+
505
+ ```svelte
506
+ <script lang="ts">
507
+ import { Select } from '@smilodon/svelte';
508
+ import type { SelectItem } from '@smilodon/core';
509
+
510
+ let value: string | number = '';
511
+
512
+ const items: SelectItem[] = [
513
+ // Fruits group
514
+ { value: 'apple', label: 'Apple', group: 'Fruits' },
515
+ { value: 'banana', label: 'Banana', group: 'Fruits' },
516
+ { value: 'cherry', label: 'Cherry', group: 'Fruits' },
517
+
518
+ // Vegetables group
519
+ { value: 'carrot', label: 'Carrot', group: 'Vegetables' },
520
+ { value: 'broccoli', label: 'Broccoli', group: 'Vegetables' },
521
+ { value: 'spinach', label: 'Spinach', group: 'Vegetables' },
522
+ ];
523
+ </script>
524
+
525
+ <Select {items} bind:value placeholder="Select food..." />
526
+ ```
527
+
528
+ ### Complex Grouped Structure
529
+
530
+ ```svelte
531
+ <script lang="ts">
532
+ import { Select } from '@smilodon/svelte';
533
+ import type { SelectItem } from '@smilodon/core';
534
+
535
+ let value: string | number = '';
536
+
537
+ const technologies: SelectItem[] = [
538
+ // Frontend
539
+ { value: 'react', label: 'React', group: 'Frontend' },
540
+ { value: 'vue', label: 'Vue.js', group: 'Frontend' },
541
+ { value: 'svelte', label: 'Svelte', group: 'Frontend' },
542
+
543
+ // Backend
544
+ { value: 'node', label: 'Node.js', group: 'Backend' },
545
+ { value: 'django', label: 'Django', group: 'Backend' },
546
+ { value: 'rails', label: 'Ruby on Rails', group: 'Backend' },
547
+
548
+ // Database
549
+ { value: 'postgres', label: 'PostgreSQL', group: 'Database' },
550
+ { value: 'mongo', label: 'MongoDB', group: 'Database' },
551
+ { value: 'redis', label: 'Redis', group: 'Database' },
552
+ ];
553
+ </script>
554
+
555
+ <Select
556
+ {items}={technologies}
557
+ bind:value
558
+ searchable
559
+ placeholder="Select technology..."
560
+ />
561
+ ```
562
+
563
+ ---
564
+
565
+ ## Disabled States
566
+
567
+ ### Disabled Select
568
+
569
+ ```svelte
570
+ <script lang="ts">
571
+ import { Select } from '@smilodon/svelte';
572
+
573
+ let value = '';
574
+ </script>
575
+
576
+ <Select
577
+ items={['Option 1', 'Option 2', 'Option 3']}
578
+ bind:value
579
+ disabled
580
+ placeholder="This select is disabled"
581
+ />
582
+ ```
583
+
584
+ ### Disabled Options
585
+
586
+ ```svelte
587
+ <script lang="ts">
588
+ import { Select } from '@smilodon/svelte';
589
+ import type { SelectItem } from '@smilodon/core';
590
+
591
+ let value: string | number = '';
592
+
593
+ const items: SelectItem[] = [
594
+ { value: '1', label: 'Available Option 1' },
595
+ { value: '2', label: 'Available Option 2' },
596
+ { value: '3', label: 'Disabled Option', disabled: true },
597
+ { value: '4', label: 'Available Option 3' },
598
+ { value: '5', label: 'Disabled Option 2', disabled: true },
599
+ ];
600
+ </script>
601
+
602
+ <Select {items} bind:value placeholder="Some options are disabled" />
603
+ ```
604
+
605
+ ### Conditional Disabling
606
+
607
+ ```svelte
608
+ <script lang="ts">
609
+ import { Select } from '@smilodon/svelte';
610
+
611
+ let value = '';
612
+ let isLoading = false;
613
+
614
+ async function handleChange(event: CustomEvent) {
615
+ isLoading = true;
616
+ // Simulate API call
617
+ await new Promise(resolve => setTimeout(resolve, 1000));
618
+ isLoading = false;
619
+ }
620
+ </script>
621
+
622
+ <Select
623
+ items={['Option 1', 'Option 2', 'Option 3']}
624
+ bind:value
625
+ disabled={isLoading}
626
+ placeholder={isLoading ? 'Loading...' : 'Select an option'}
627
+ on:change={handleChange}
628
+ />
629
+
630
+ {#if isLoading}
631
+ <p>Processing selection...</p>
632
+ {/if}
633
+ ```
634
+
635
+ ---
636
+
637
+ ## Event Handling
638
+
639
+ ### All Events
640
+
641
+ ```svelte
642
+ <script lang="ts">
643
+ import { Select } from '@smilodon/svelte';
644
+
645
+ let value = '';
646
+ let isOpen = false;
647
+ let logs: string[] = [];
648
+
649
+ function addLog(message: string) {
650
+ const timestamp = new Date().toLocaleTimeString();
651
+ logs = [...logs, `[${timestamp}] ${message}`];
652
+ }
653
+
654
+ function handleChange(event: CustomEvent) {
655
+ addLog(`Changed to: ${event.detail.value}`);
656
+ }
657
+
658
+ function handleOpen() {
659
+ isOpen = true;
660
+ addLog('Dropdown opened');
661
+ }
662
+
663
+ function handleClose() {
664
+ isOpen = false;
665
+ addLog('Dropdown closed');
666
+ }
667
+
668
+ function handleSearch(event: CustomEvent) {
669
+ addLog(`Searching for: ${event.detail.query}`);
670
+ }
671
+ </script>
672
+
673
+ <Select
674
+ items={['Apple', 'Banana', 'Cherry']}
675
+ bind:value
676
+ on:change={handleChange}
677
+ on:open={handleOpen}
678
+ on:close={handleClose}
679
+ on:search={handleSearch}
680
+ placeholder="Select a fruit"
681
+ />
682
+
683
+ <div>
684
+ <p>Current value: {value || 'None'}</p>
685
+ <p>Dropdown is: {isOpen ? 'Open' : 'Closed'}</p>
686
+ </div>
687
+
688
+ <div>
689
+ <strong>Event Log:</strong>
690
+ <ul style="max-height: 200px; overflow: auto;">
691
+ {#each logs as log}
692
+ <li>{log}</li>
693
+ {/each}
694
+ </ul>
695
+ </div>
696
+ ```
697
+
698
+ ---
699
+
700
+ ## Styling & Theming
701
+
702
+ ### Inline Styles with CSS Variables
703
+
704
+ ```svelte
705
+ <script lang="ts">
706
+ import { Select } from '@smilodon/svelte';
707
+
708
+ let value = '';
709
+ </script>
710
+
711
+ <Select
712
+ items={['Red', 'Blue', 'Green']}
713
+ bind:value
714
+ style="
715
+ --select-input-border: 2px solid #3b82f6;
716
+ --select-input-border-radius: 8px;
717
+ --select-input-focus-border: #2563eb;
718
+ --select-option-hover-bg: #dbeafe;
719
+ --select-option-selected-bg: #3b82f6;
720
+ --select-option-selected-color: white;
721
+ --select-badge-bg: #3b82f6;
722
+ "
723
+ />
724
+ ```
725
+
726
+ ### Complete CSS Variables Reference
727
+
728
+ ```svelte
729
+ <Select
730
+ items={['Option 1', 'Option 2', 'Option 3']}
731
+ bind:value
732
+ style="
733
+ --select-input-gap: 6px;
734
+ --select-input-padding: 6px 52px 6px 8px;
735
+ --select-input-min-height: 44px;
736
+ --select-input-bg: white;
737
+ --select-input-border: 1px solid #d1d5db;
738
+ --select-input-border-radius: 6px;
739
+ --select-input-focus-border: #667eea;
740
+ --select-input-focus-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
741
+
742
+ --select-input-min-width: 120px;
743
+ --select-input-field-padding: 4px;
744
+ --select-input-font-size: 14px;
745
+ --select-input-line-height: 1.5;
746
+ --select-input-color: #1f2937;
747
+ --select-input-placeholder-color: #9ca3af;
748
+
749
+ --select-arrow-width: 40px;
750
+ --select-arrow-size: 16px;
751
+ --select-arrow-color: #667eea;
752
+ --select-arrow-hover-bg: rgba(102, 126, 234, 0.08);
753
+
754
+ --select-separator-width: 1px;
755
+ --select-separator-height: 60%;
756
+ --select-separator-gradient: linear-gradient(to bottom, transparent, #ccc, transparent);
757
+
758
+ --select-badge-bg: #667eea;
759
+ --select-badge-color: white;
760
+ --select-badge-border-radius: 4px;
761
+ --select-badge-remove-bg: rgba(255, 255, 255, 0.3);
762
+ --select-badge-remove-hover-bg: rgba(255, 255, 255, 0.5);
763
+
764
+ --select-dropdown-border-radius: 4px;
765
+ --select-dropdown-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
766
+
767
+ --select-option-padding: 8px 12px;
768
+ --select-option-hover-bg: #f3f4f6;
769
+ --select-option-selected-bg: #e0e7ff;
770
+ --select-option-selected-color: #4338ca;
771
+ "
772
+ />
773
+ ```
774
+
775
+ ### Theme Examples
776
+
777
+ #### Bootstrap Theme
778
+
779
+ ```svelte
780
+ <script lang="ts">
781
+ import { Select } from '@smilodon/svelte';
782
+
783
+ let value = '';
784
+ </script>
785
+
786
+ <Select
787
+ items={['Option 1', 'Option 2', 'Option 3']}
788
+ bind:value
789
+ style="
790
+ --select-input-border: 1px solid #ced4da;
791
+ --select-input-border-radius: 0.375rem;
792
+ --select-input-focus-border: #86b7fe;
793
+ --select-input-focus-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
794
+ --select-option-hover-bg: #e9ecef;
795
+ --select-option-selected-bg: #0d6efd;
796
+ --select-option-selected-color: white;
797
+ --select-badge-bg: #0d6efd;
798
+ "
799
+ />
800
+ ```
801
+
802
+ #### Material Design Theme
803
+
804
+ ```svelte
805
+ <script lang="ts">
806
+ import { Select } from '@smilodon/svelte';
807
+
808
+ let value = '';
809
+ </script>
810
+
811
+ <Select
812
+ items={['Option 1', 'Option 2', 'Option 3']}
813
+ bind:value
814
+ style="
815
+ --select-input-border-radius: 4px;
816
+ --select-input-focus-border: #1976d2;
817
+ --select-dropdown-shadow: 0 2px 4px rgba(0,0,0,0.2), 0 4px 5px rgba(0,0,0,0.14);
818
+ --select-option-padding: 16px;
819
+ --select-option-hover-bg: rgba(0, 0, 0, 0.04);
820
+ --select-option-selected-bg: #e3f2fd;
821
+ --select-option-selected-color: #1976d2;
822
+ --select-badge-bg: #1976d2;
823
+ --select-badge-border-radius: 16px;
824
+ "
825
+ />
826
+ ```
827
+
828
+ #### Dark Mode Theme
829
+
830
+ ```svelte
831
+ <script lang="ts">
832
+ import { Select } from '@smilodon/svelte';
833
+
834
+ let value = '';
835
+ </script>
836
+
837
+ <Select
838
+ items={['Option 1', 'Option 2', 'Option 3']}
839
+ bind:value
840
+ class="dark-mode"
841
+ style="
842
+ --select-input-bg: #1f2937;
843
+ --select-input-border: 1px solid #4b5563;
844
+ --select-input-color: #f9fafb;
845
+ --select-dropdown-bg: #1f2937;
846
+ --select-options-bg: #1f2937;
847
+ --select-option-color: #f9fafb;
848
+ --select-option-bg: #1f2937;
849
+ --select-option-hover-bg: #374151;
850
+ --select-option-selected-bg: #3730a3;
851
+ --select-badge-bg: #4f46e5;
852
+ "
853
+ />
854
+ ```
855
+
856
+ ### External CSS with Scoped Styles
857
+
858
+ ```svelte
859
+ <script lang="ts">
860
+ import { Select } from '@smilodon/svelte';
861
+
862
+ let value = '';
863
+ </script>
864
+
865
+ <Select
866
+ items={['Option 1', 'Option 2', 'Option 3']}
867
+ bind:value
868
+ class="custom-select"
869
+ />
870
+
871
+ <style>
872
+ :global(.custom-select) {
873
+ --select-input-border: 2px solid #10b981;
874
+ --select-input-border-radius: 12px;
875
+ --select-option-hover-bg: #d1fae5;
876
+ --select-option-selected-bg: #10b981;
877
+ --select-option-selected-color: white;
878
+ --select-badge-bg: #10b981;
879
+ --select-separator-gradient: linear-gradient(
880
+ to bottom,
881
+ transparent 0%,
882
+ #10b981 20%,
883
+ #10b981 80%,
884
+ transparent 100%
885
+ );
886
+ }
887
+ </style>
888
+ ```
889
+
890
+ ---
891
+
892
+ ## Custom Renderers
893
+
894
+ ### Custom Option Renderer
895
+
896
+ ```svelte
897
+ <script lang="ts">
898
+ import { Select } from '@smilodon/svelte';
899
+ import type { SelectItem } from '@smilodon/core';
900
+
901
+ interface User extends SelectItem {
902
+ value: string;
903
+ label: string;
904
+ email: string;
905
+ avatar: string;
906
+ }
907
+
908
+ let selectedUser: string | number = '';
909
+
910
+ const users: User[] = [
911
+ {
912
+ value: '1',
913
+ label: 'John Doe',
914
+ email: 'john@example.com',
915
+ avatar: 'https://i.pravatar.cc/150?img=1'
916
+ },
917
+ {
918
+ value: '2',
919
+ label: 'Jane Smith',
920
+ email: 'jane@example.com',
921
+ avatar: 'https://i.pravatar.cc/150?img=2'
922
+ },
923
+ ];
924
+
925
+ function customRenderer(item: SelectItem): string {
926
+ const user = item as User;
927
+ return `
928
+ <div style="display: flex; align-items: center; gap: 12px;">
929
+ <img
930
+ src="${user.avatar}"
931
+ alt="${user.label}"
932
+ style="width: 32px; height: 32px; border-radius: 50%;"
933
+ />
934
+ <div>
935
+ <div style="font-weight: 500;">${user.label}</div>
936
+ <div style="font-size: 12px; color: #6b7280;">${user.email}</div>
937
+ </div>
938
+ </div>
939
+ `;
940
+ }
941
+ </script>
942
+
943
+ <Select
944
+ {items}={users}
945
+ bind:value={selectedUser}
946
+ {customRenderer}
947
+ placeholder="Select a user"
948
+ estimatedItemHeight={60}
949
+ />
950
+ ```
951
+
952
+ ---
953
+
954
+ ## Performance Optimization
955
+
956
+ ### Virtualization for Large Datasets
957
+
958
+ ```svelte
959
+ <script lang="ts">
960
+ import { Select } from '@smilodon/svelte';
961
+
962
+ let value = '';
963
+
964
+ // Generate 100,000 items
965
+ const items = Array.from({ length: 100_000 }, (_, i) => ({
966
+ value: `item-${i}`,
967
+ label: `Item ${i + 1}`,
968
+ }));
969
+ </script>
970
+
971
+ <Select
972
+ {items}
973
+ bind:value
974
+ virtualization={true}
975
+ estimatedItemHeight={48}
976
+ placeholder="Search 100k items..."
977
+ searchable
978
+ />
979
+ ```
980
+
981
+ ### Reactive Statements for Filtering
982
+
983
+ ```svelte
984
+ <script lang="ts">
985
+ import { Select } from '@smilodon/svelte';
986
+ import type { SelectItem } from '@smilodon/core';
987
+
988
+ let value = '';
989
+ let searchFilter = '';
990
+
991
+ const rawItems: SelectItem[] = [
992
+ { value: '1', label: 'Apple' },
993
+ { value: '2', label: 'Banana' },
994
+ { value: '3', label: 'Cherry' },
995
+ // ... many more items
996
+ ];
997
+
998
+ // Only recalculate when rawItems or searchFilter changes
999
+ $: filteredItems = searchFilter
1000
+ ? rawItems.filter(item =>
1001
+ item.label.toLowerCase().includes(searchFilter.toLowerCase())
1002
+ )
1003
+ : rawItems;
1004
+ </script>
1005
+
1006
+ <Select {items}={filteredItems} bind:value />
1007
+ ```
1008
+
1009
+ ---
1010
+
1011
+ ## TypeScript Integration
1012
+
1013
+ ### Full Type Safety
1014
+
1015
+ ```svelte
1016
+ <script lang="ts">
1017
+ import { Select } from '@smilodon/svelte';
1018
+ import type { SelectItem } from '@smilodon/core';
1019
+
1020
+ // Define your data type
1021
+ interface Product extends SelectItem {
1022
+ value: string;
1023
+ label: string;
1024
+ price: number;
1025
+ category: string;
1026
+ }
1027
+
1028
+ let selectedId: string = '';
1029
+
1030
+ const products: Product[] = [
1031
+ { value: 'p1', label: 'Laptop', price: 999, category: 'Electronics' },
1032
+ { value: 'p2', label: 'Phone', price: 699, category: 'Electronics' },
1033
+ { value: 'p3', label: 'Desk', price: 299, category: 'Furniture' },
1034
+ ];
1035
+
1036
+ function handleChange(event: CustomEvent<{ value: string | number }>) {
1037
+ selectedId = event.detail.value as string;
1038
+ const product = products.find(p => p.value === selectedId);
1039
+ if (product) {
1040
+ console.log('Selected product:', product.label, 'Price:', product.price);
1041
+ }
1042
+ }
1043
+ </script>
1044
+
1045
+ <Select
1046
+ {items}={products}
1047
+ bind:value={selectedId}
1048
+ on:change={handleChange}
1049
+ placeholder="Select a product"
1050
+ />
1051
+ ```
1052
+
1053
+ ---
1054
+
1055
+ ## Stores Integration
1056
+
1057
+ ### With Svelte Stores
1058
+
1059
+ ```typescript
1060
+ // stores.ts
1061
+ import { writable, derived } from 'svelte/store';
1062
+ import type { SelectItem } from '@smilodon/core';
1063
+
1064
+ export const items = writable<SelectItem[]>([
1065
+ { value: '1', label: 'Option 1' },
1066
+ { value: '2', label: 'Option 2' },
1067
+ { value: '3', label: 'Option 3' },
1068
+ ]);
1069
+
1070
+ export const selectedValue = writable<string | number>('');
1071
+
1072
+ export const selectedItem = derived(
1073
+ [items, selectedValue],
1074
+ ([$items, $selectedValue]) =>
1075
+ $items.find(item => item.value === $selectedValue)
1076
+ );
1077
+ ```
1078
+
1079
+ ```svelte
1080
+ <script lang="ts">
1081
+ import { Select } from '@smilodon/svelte';
1082
+ import { items, selectedValue, selectedItem } from './stores';
1083
+ </script>
1084
+
1085
+ <Select {items}={$items} bind:value={$selectedValue} />
1086
+
1087
+ {#if $selectedItem}
1088
+ <p>Selected: {$selectedItem.label}</p>
1089
+ {/if}
1090
+ ```
1091
+
1092
+ ---
1093
+
1094
+ ## Accessibility
1095
+
1096
+ Smilodon is WCAG 2.1 AAA compliant with full keyboard support:
1097
+
1098
+ ### Keyboard Navigation
1099
+
1100
+ - **Tab** - Focus the select
1101
+ - **Enter/Space** - Open dropdown
1102
+ - **↑/↓** - Navigate options
1103
+ - **Home/End** - Jump to first/last option
1104
+ - **Escape** - Close dropdown
1105
+ - **Type** - Search options
1106
+
1107
+ ### ARIA Attributes
1108
+
1109
+ The component automatically includes:
1110
+ - `role="combobox"`
1111
+ - `aria-expanded`
1112
+ - `aria-haspopup`
1113
+ - `aria-label` / `aria-labelledby`
1114
+ - `aria-activedescendant`
1115
+ - `aria-multiselectable` (when `multiple`)
1116
+
1117
+ ### Screen Reader Support
1118
+
1119
+ ```svelte
1120
+ <script lang="ts">
1121
+ import { Select } from '@smilodon/svelte';
1122
+
1123
+ let country = '';
1124
+ </script>
1125
+
1126
+ <label id="country-label">Country</label>
1127
+ <Select
1128
+ items={['USA', 'Canada', 'Mexico']}
1129
+ bind:value={country}
1130
+ placeholder="Select your country"
1131
+ />
1132
+ ```
1133
+
1134
+ ---
1135
+
1136
+ ## Advanced Patterns
1137
+
1138
+ ### Dependent Selects
1139
+
1140
+ ```svelte
1141
+ <script lang="ts">
1142
+ import { Select } from '@smilodon/svelte';
1143
+
1144
+ let country = '';
1145
+ let state = '';
1146
+ let states: string[] = [];
1147
+
1148
+ const countries = ['USA', 'Canada', 'Mexico'];
1149
+
1150
+ // Reactive statement to update states when country changes
1151
+ $: {
1152
+ state = ''; // Reset state when country changes
1153
+
1154
+ if (country === 'USA') {
1155
+ states = ['California', 'Texas', 'New York', 'Florida'];
1156
+ } else if (country === 'Canada') {
1157
+ states = ['Ontario', 'Quebec', 'British Columbia'];
1158
+ } else if (country === 'Mexico') {
1159
+ states = ['Mexico City', 'Jalisco', 'Nuevo León'];
1160
+ } else {
1161
+ states = [];
1162
+ }
1163
+ }
1164
+ </script>
1165
+
1166
+ <Select
1167
+ items={countries}
1168
+ bind:value={country}
1169
+ placeholder="Select country"
1170
+ />
1171
+
1172
+ <Select
1173
+ {items}={states}
1174
+ bind:value={state}
1175
+ disabled={!country}
1176
+ placeholder={country ? 'Select state' : 'Select country first'}
1177
+ />
1178
+ ```
1179
+
1180
+ ### Async Data Loading
1181
+
1182
+ ```svelte
1183
+ <script lang="ts">
1184
+ import { onMount } from 'svelte';
1185
+ import { Select } from '@smilodon/svelte';
1186
+ import type { SelectItem } from '@smilodon/core';
1187
+
1188
+ let items: SelectItem[] = [];
1189
+ let loading = true;
1190
+ let value = '';
1191
+
1192
+ onMount(async () => {
1193
+ loading = true;
1194
+
1195
+ // Simulate API call
1196
+ await new Promise(resolve => setTimeout(resolve, 1000));
1197
+
1198
+ items = [
1199
+ { value: '1', label: 'Option 1' },
1200
+ { value: '2', label: 'Option 2' },
1201
+ { value: '3', label: 'Option 3' },
1202
+ ];
1203
+
1204
+ loading = false;
1205
+ });
1206
+ </script>
1207
+
1208
+ {#if loading}
1209
+ <div>Loading options...</div>
1210
+ {:else}
1211
+ <Select {items} bind:value placeholder="Select an option" />
1212
+ {/if}
1213
+ ```
1214
+
1215
+ ### With Context API
1216
+
1217
+ ```svelte
1218
+ <!-- Parent.svelte -->
1219
+ <script lang="ts">
1220
+ import { setContext } from 'svelte';
1221
+ import { writable } from 'svelte/store';
1222
+ import type { SelectItem } from '@smilodon/core';
1223
+
1224
+ const items = writable<SelectItem[]>([
1225
+ { value: '1', label: 'Option 1' },
1226
+ { value: '2', label: 'Option 2' },
1227
+ ]);
1228
+
1229
+ const selectedValue = writable('');
1230
+
1231
+ setContext('select', { items, selectedValue });
1232
+ </script>
1233
+
1234
+ <slot />
1235
+ ```
1236
+
1237
+ ```svelte
1238
+ <!-- Child.svelte -->
1239
+ <script lang="ts">
1240
+ import { getContext } from 'svelte';
1241
+ import { Select } from '@smilodon/svelte';
1242
+
1243
+ const { items, selectedValue } = getContext('select');
1244
+ </script>
1245
+
1246
+ <Select {items}={$items} bind:value={$selectedValue} />
1247
+ ```
1248
+
1249
+ ---
1250
+
1251
+ ## Troubleshooting
1252
+
1253
+ ### Common Issues
1254
+
1255
+ #### Issue: bind:value not updating
1256
+
1257
+ ```svelte
1258
+ <!-- ❌ Wrong - missing bind: -->
1259
+ <Select items={items} value={selectedValue} />
1260
+
1261
+ <!-- ✅ Correct -->
1262
+ <Select items={items} bind:value={selectedValue} />
1263
+ ```
1264
+
1265
+ #### Issue: Multi-select type error
1266
+
1267
+ ```svelte
1268
+ <script lang="ts">
1269
+ // ❌ Wrong - value should be array for multiple
1270
+ let value = '';
1271
+
1272
+ // ✅ Correct
1273
+ let value: (string | number)[] = [];
1274
+ </script>
1275
+
1276
+ <Select items={items} bind:value multiple />
1277
+ ```
1278
+
1279
+ #### Issue: Styles not applying
1280
+
1281
+ ```svelte
1282
+ <!-- ❌ Wrong - missing quotes in style -->
1283
+ <Select items={items} style=--select-input-border: 2px solid red />
1284
+
1285
+ <!-- ✅ Correct -->
1286
+ <Select items={items} style="--select-input-border: 2px solid red;" />
1287
+ ```
1288
+
1289
+ ### Performance Issues
1290
+
1291
+ If you experience slow rendering with large datasets:
1292
+
1293
+ 1. Enable virtualization (enabled by default)
1294
+ 2. Use reactive statements (`$:`) for computed items
1295
+ 3. Increase `estimatedItemHeight` if items are taller than 48px
1296
+
1297
+ ```svelte
1298
+ <script lang="ts">
1299
+ $: items = generateLargeDataset();
1300
+ </script>
1301
+
1302
+ <Select
1303
+ {items}
1304
+ bind:value
1305
+ virtualization={true}
1306
+ estimatedItemHeight={48}
1307
+ />
1308
+ ```
1309
+
1310
+ ---
1311
+
1312
+ ## Additional Resources
1313
+
1314
+ - **Core Documentation**: [packages/core/README.md](../core/README.md)
1315
+ - **API Reference**: [docs/API-REFERENCE.md](../../docs/API-REFERENCE.md)
1316
+ - **Live Examples**: [Playground](../../playground)
1317
+ - **GitHub Issues**: [Report a bug](https://github.com/navidrezadoost/smilodon/issues)
1318
+
1319
+ ---
1320
+
1321
+ **Built with ❤️ by the Smilodon team**
1322
+
1323
+ *Last updated: February 9, 2026 - v1.3.6*