@smilodon/svelte 1.4.10 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPLETE-GUIDE.md +1323 -0
- package/README.md +29 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +160 -45
- package/dist/index.mjs.map +1 -1
- package/dist/types/Select.svelte.d.ts +22 -0
- package/package.json +4 -3
|
@@ -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*
|