@smilodon/core 1.3.6 → 1.3.10
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 +326 -0
- package/dist/index.cjs +1901 -1601
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1901 -1601
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.umd.js +1902 -1602
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/index.umd.min.js.map +1 -1
- package/dist/types/src/components/enhanced-select.d.ts +18 -2
- package/dist/types/src/components/select-option.d.ts +2 -0
- package/dist/types/tests/enhanced-option-renderer.spec.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,31 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
|
+
## 📖 Documentation
|
|
14
|
+
|
|
15
|
+
**For comprehensive documentation covering all features, styling options, and advanced patterns:**
|
|
16
|
+
|
|
17
|
+
👉 **[Complete Vanilla JS Guide](../vanilla/COMPLETE-GUIDE.md)** 👈
|
|
18
|
+
|
|
19
|
+
Additional references:
|
|
20
|
+
- **Performance Runbook**: [`docs/PERFORMANCE-RUNBOOK.md`](../../docs/PERFORMANCE-RUNBOOK.md)
|
|
21
|
+
- **Performance Guide**: [`docs/PERFORMANCE.md`](../../docs/PERFORMANCE.md)
|
|
22
|
+
- **Known Limitations**: [`docs/KNOWN-LIMITATIONS.md`](../../docs/KNOWN-LIMITATIONS.md)
|
|
23
|
+
|
|
24
|
+
The complete guide includes:
|
|
25
|
+
- ✅ All 60+ CSS variables for complete customization
|
|
26
|
+
- ✅ Vanilla JavaScript patterns (DOM manipulation, event listeners)
|
|
27
|
+
- ✅ Complete API reference with all properties and methods
|
|
28
|
+
- ✅ CDN usage and module bundler integration
|
|
29
|
+
- ✅ Custom renderers with HTML templates
|
|
30
|
+
- ✅ Theme examples and dynamic styling
|
|
31
|
+
- ✅ Advanced patterns (async loading, local storage, dependent selects)
|
|
32
|
+
- ✅ Troubleshooting and accessibility information
|
|
33
|
+
|
|
34
|
+
> **WebKit e2e note (Linux/Arch)**: WebKit Playwright binaries depend on older system libraries; on Arch-based distros we recommend running WebKit tests via the Playwright Docker image.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
13
38
|
## Why Smilodon?
|
|
14
39
|
|
|
15
40
|
Smilodon is a Web Component that renders **1,000,000+ items at 60 FPS** with constant DOM size, sub-millisecond search, and zero framework lock-in. Built for extreme-scale data applications where legacy libraries crash or lag.
|
|
@@ -171,6 +196,307 @@ select.items = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89];
|
|
|
171
196
|
// ]
|
|
172
197
|
```
|
|
173
198
|
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 🎯 Two Ways to Specify Options
|
|
202
|
+
|
|
203
|
+
Smilodon provides **two powerful approaches** for defining select options, each optimized for different use cases:
|
|
204
|
+
|
|
205
|
+
### Method 1: Data-Driven (Object Arrays) 📊
|
|
206
|
+
|
|
207
|
+
**Use when**: You have structured data and want simple, declarative option rendering.
|
|
208
|
+
|
|
209
|
+
**Advantages**:
|
|
210
|
+
- ✅ Simple and declarative
|
|
211
|
+
- ✅ Auto-conversion from strings/numbers
|
|
212
|
+
- ✅ Perfect for basic dropdowns
|
|
213
|
+
- ✅ Zero boilerplate code
|
|
214
|
+
- ✅ Extremely performant (millions of items)
|
|
215
|
+
- ✅ Built-in search and filtering
|
|
216
|
+
- ✅ TypeScript type safety
|
|
217
|
+
|
|
218
|
+
**Examples**:
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
// Simple object array
|
|
222
|
+
const select = document.querySelector('enhanced-select');
|
|
223
|
+
|
|
224
|
+
select.items = [
|
|
225
|
+
{ value: '1', label: 'Apple' },
|
|
226
|
+
{ value: '2', label: 'Banana' },
|
|
227
|
+
{ value: '3', label: 'Cherry' }
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
// With additional metadata
|
|
231
|
+
select.items = [
|
|
232
|
+
{ value: 'us', label: 'United States', disabled: false },
|
|
233
|
+
{ value: 'ca', label: 'Canada', disabled: false },
|
|
234
|
+
{ value: 'mx', label: 'Mexico', disabled: true } // Disabled option
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
// With grouping
|
|
238
|
+
select.items = [
|
|
239
|
+
{ value: 'apple', label: 'Apple', group: 'Fruits' },
|
|
240
|
+
{ value: 'banana', label: 'Banana', group: 'Fruits' },
|
|
241
|
+
{ value: 'carrot', label: 'Carrot', group: 'Vegetables' },
|
|
242
|
+
{ value: 'broccoli', label: 'Broccoli', group: 'Vegetables' }
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
// Auto-conversion from strings
|
|
246
|
+
select.items = ['Red', 'Green', 'Blue', 'Yellow'];
|
|
247
|
+
|
|
248
|
+
// Auto-conversion from numbers
|
|
249
|
+
select.items = [10, 20, 30, 40, 50];
|
|
250
|
+
|
|
251
|
+
// Large datasets (millions of items)
|
|
252
|
+
select.items = Array.from({ length: 1_000_000 }, (_, i) => ({
|
|
253
|
+
value: i,
|
|
254
|
+
label: `Item ${i + 1}`
|
|
255
|
+
}));
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Method 2: Component-Driven (Custom Renderers) 🎨
|
|
259
|
+
|
|
260
|
+
**Use when**: You need rich, interactive option content with custom HTML/styling.
|
|
261
|
+
|
|
262
|
+
**Advantages**:
|
|
263
|
+
- ✅ Full control over option rendering
|
|
264
|
+
- ✅ Rich content (images, icons, badges, multi-line text)
|
|
265
|
+
- ✅ Custom HTML and styling
|
|
266
|
+
- ✅ Interactive elements within options
|
|
267
|
+
- ✅ Conditional rendering based on item data
|
|
268
|
+
- ✅ Perfect for complex UIs (user cards, product listings, etc.)
|
|
269
|
+
|
|
270
|
+
**How it works**: Provide an `optionTemplate` function that returns HTML string for each option.
|
|
271
|
+
|
|
272
|
+
**Examples**:
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
const select = document.querySelector('enhanced-select');
|
|
276
|
+
|
|
277
|
+
// Example 1: Simple custom template with icons
|
|
278
|
+
const items = [
|
|
279
|
+
{ value: 'js', label: 'JavaScript', icon: '🟨', description: 'Dynamic scripting language' },
|
|
280
|
+
{ value: 'py', label: 'Python', icon: '🐍', description: 'General-purpose programming' },
|
|
281
|
+
{ value: 'rs', label: 'Rust', icon: '🦀', description: 'Systems programming language' }
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
select.items = items;
|
|
285
|
+
select.optionTemplate = (item, index) => `
|
|
286
|
+
<div style="display: flex; align-items: center; gap: 12px;">
|
|
287
|
+
<span style="font-size: 24px;">${item.icon}</span>
|
|
288
|
+
<div>
|
|
289
|
+
<div style="font-weight: 600;">${item.label}</div>
|
|
290
|
+
<div style="font-size: 12px; color: #6b7280;">${item.description}</div>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
`;
|
|
294
|
+
|
|
295
|
+
// Example 2: User selection with avatars
|
|
296
|
+
const users = [
|
|
297
|
+
{
|
|
298
|
+
value: '1',
|
|
299
|
+
label: 'John Doe',
|
|
300
|
+
email: 'john@example.com',
|
|
301
|
+
avatar: 'https://i.pravatar.cc/150?img=1',
|
|
302
|
+
role: 'Admin'
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
value: '2',
|
|
306
|
+
label: 'Jane Smith',
|
|
307
|
+
email: 'jane@example.com',
|
|
308
|
+
avatar: 'https://i.pravatar.cc/150?img=2',
|
|
309
|
+
role: 'User'
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
value: '3',
|
|
313
|
+
label: 'Bob Johnson',
|
|
314
|
+
email: 'bob@example.com',
|
|
315
|
+
avatar: 'https://i.pravatar.cc/150?img=3',
|
|
316
|
+
role: 'Moderator'
|
|
317
|
+
}
|
|
318
|
+
];
|
|
319
|
+
|
|
320
|
+
select.items = users;
|
|
321
|
+
select.optionTemplate = (item, index) => `
|
|
322
|
+
<div style="display: flex; align-items: center; gap: 12px; padding: 4px 0;">
|
|
323
|
+
<img
|
|
324
|
+
src="${item.avatar}"
|
|
325
|
+
alt="${item.label}"
|
|
326
|
+
style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover;"
|
|
327
|
+
/>
|
|
328
|
+
<div style="flex: 1;">
|
|
329
|
+
<div style="font-weight: 600; color: #1f2937;">${item.label}</div>
|
|
330
|
+
<div style="font-size: 13px; color: #6b7280;">${item.email}</div>
|
|
331
|
+
</div>
|
|
332
|
+
<span style="
|
|
333
|
+
padding: 4px 8px;
|
|
334
|
+
background: ${item.role === 'Admin' ? '#dbeafe' : '#f3f4f6'};
|
|
335
|
+
color: ${item.role === 'Admin' ? '#1e40af' : '#374151'};
|
|
336
|
+
border-radius: 12px;
|
|
337
|
+
font-size: 11px;
|
|
338
|
+
font-weight: 600;
|
|
339
|
+
">${item.role}</span>
|
|
340
|
+
</div>
|
|
341
|
+
`;
|
|
342
|
+
|
|
343
|
+
// Example 3: Product selection with images and pricing
|
|
344
|
+
const products = [
|
|
345
|
+
{
|
|
346
|
+
value: 'p1',
|
|
347
|
+
label: 'Premium Laptop',
|
|
348
|
+
price: 1299.99,
|
|
349
|
+
stock: 15,
|
|
350
|
+
image: 'https://via.placeholder.com/60',
|
|
351
|
+
badge: 'Best Seller'
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
value: 'p2',
|
|
355
|
+
label: 'Wireless Mouse',
|
|
356
|
+
price: 29.99,
|
|
357
|
+
stock: 150,
|
|
358
|
+
image: 'https://via.placeholder.com/60',
|
|
359
|
+
badge: null
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
value: 'p3',
|
|
363
|
+
label: 'Mechanical Keyboard',
|
|
364
|
+
price: 89.99,
|
|
365
|
+
stock: 0,
|
|
366
|
+
image: 'https://via.placeholder.com/60',
|
|
367
|
+
badge: 'Out of Stock'
|
|
368
|
+
}
|
|
369
|
+
];
|
|
370
|
+
|
|
371
|
+
select.items = products;
|
|
372
|
+
select.optionTemplate = (item, index) => `
|
|
373
|
+
<div style="display: flex; align-items: center; gap: 12px; opacity: ${item.stock === 0 ? '0.5' : '1'};">
|
|
374
|
+
<img
|
|
375
|
+
src="${item.image}"
|
|
376
|
+
alt="${item.label}"
|
|
377
|
+
style="width: 60px; height: 60px; border-radius: 8px; object-fit: cover; border: 1px solid #e5e7eb;"
|
|
378
|
+
/>
|
|
379
|
+
<div style="flex: 1;">
|
|
380
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
381
|
+
<span style="font-weight: 600; color: #1f2937;">${item.label}</span>
|
|
382
|
+
${item.badge ? `
|
|
383
|
+
<span style="
|
|
384
|
+
padding: 2px 6px;
|
|
385
|
+
background: ${item.badge === 'Best Seller' ? '#dcfce7' : '#fee2e2'};
|
|
386
|
+
color: ${item.badge === 'Best Seller' ? '#166534' : '#991b1b'};
|
|
387
|
+
border-radius: 4px;
|
|
388
|
+
font-size: 10px;
|
|
389
|
+
font-weight: 600;
|
|
390
|
+
">${item.badge}</span>
|
|
391
|
+
` : ''}
|
|
392
|
+
</div>
|
|
393
|
+
<div style="margin-top: 4px; display: flex; justify-content: space-between; align-items: center;">
|
|
394
|
+
<span style="font-size: 16px; font-weight: 700; color: #059669;">$${item.price.toFixed(2)}</span>
|
|
395
|
+
<span style="font-size: 12px; color: #6b7280;">${item.stock > 0 ? `${item.stock} in stock` : 'Out of stock'}</span>
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
`;
|
|
400
|
+
|
|
401
|
+
// Example 4: Status indicators with conditional styling
|
|
402
|
+
const tasks = [
|
|
403
|
+
{ value: 't1', label: 'Design Homepage', status: 'completed', priority: 'high', assignee: 'John' },
|
|
404
|
+
{ value: 't2', label: 'API Integration', status: 'in-progress', priority: 'high', assignee: 'Jane' },
|
|
405
|
+
{ value: 't3', label: 'Write Documentation', status: 'pending', priority: 'medium', assignee: 'Bob' },
|
|
406
|
+
{ value: 't4', label: 'Bug Fixes', status: 'in-progress', priority: 'low', assignee: 'Alice' }
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
const statusColors = {
|
|
410
|
+
'completed': { bg: '#dcfce7', color: '#166534', icon: '✓' },
|
|
411
|
+
'in-progress': { bg: '#dbeafe', color: '#1e40af', icon: '⟳' },
|
|
412
|
+
'pending': { bg: '#fef3c7', color: '#92400e', icon: '○' }
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const priorityColors = {
|
|
416
|
+
'high': '#ef4444',
|
|
417
|
+
'medium': '#f59e0b',
|
|
418
|
+
'low': '#10b981'
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
select.items = tasks;
|
|
422
|
+
select.optionTemplate = (item, index) => {
|
|
423
|
+
const status = statusColors[item.status];
|
|
424
|
+
return `
|
|
425
|
+
<div style="display: flex; align-items: center; gap: 10px; padding: 4px 0;">
|
|
426
|
+
<div style="
|
|
427
|
+
width: 24px;
|
|
428
|
+
height: 24px;
|
|
429
|
+
border-radius: 50%;
|
|
430
|
+
background: ${status.bg};
|
|
431
|
+
color: ${status.color};
|
|
432
|
+
display: flex;
|
|
433
|
+
align-items: center;
|
|
434
|
+
justify-content: center;
|
|
435
|
+
font-weight: bold;
|
|
436
|
+
">${status.icon}</div>
|
|
437
|
+
<div style="flex: 1;">
|
|
438
|
+
<div style="font-weight: 600; color: #1f2937;">${item.label}</div>
|
|
439
|
+
<div style="font-size: 12px; color: #6b7280; margin-top: 2px;">
|
|
440
|
+
Assigned to ${item.assignee}
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
|
443
|
+
<div style="
|
|
444
|
+
width: 8px;
|
|
445
|
+
height: 8px;
|
|
446
|
+
border-radius: 50%;
|
|
447
|
+
background: ${priorityColors[item.priority]};
|
|
448
|
+
" title="${item.priority} priority"></div>
|
|
449
|
+
</div>
|
|
450
|
+
`;
|
|
451
|
+
};
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Comparison: When to Use Each Method
|
|
455
|
+
|
|
456
|
+
| Feature | Method 1: Object Arrays | Method 2: Custom Renderers |
|
|
457
|
+
|---------|------------------------|---------------------------|
|
|
458
|
+
| **Setup Complexity** | ⭐ Simple | ⭐⭐ Moderate |
|
|
459
|
+
| **Rendering Speed** | ⭐⭐⭐ Fastest | ⭐⭐ Fast |
|
|
460
|
+
| **Visual Customization** | ⭐⭐ Limited | ⭐⭐⭐ Unlimited |
|
|
461
|
+
| **Use Case** | Standard dropdowns | Rich, complex UIs |
|
|
462
|
+
| **Code Amount** | Minimal | More code |
|
|
463
|
+
| **TypeScript Support** | ⭐⭐⭐ Full | ⭐⭐⭐ Full |
|
|
464
|
+
| **Performance (1M items)** | ⭐⭐⭐ Excellent | ⭐⭐ Good |
|
|
465
|
+
| **Learning Curve** | ⭐ Easy | ⭐⭐ Medium |
|
|
466
|
+
|
|
467
|
+
**Best Practices**:
|
|
468
|
+
|
|
469
|
+
✅ **Use Method 1 (Object Arrays) when**:
|
|
470
|
+
- You need simple text-based options
|
|
471
|
+
- Performance is critical (millions of items)
|
|
472
|
+
- You want minimal code
|
|
473
|
+
- Built-in search/filter is sufficient
|
|
474
|
+
|
|
475
|
+
✅ **Use Method 2 (Custom Renderers) when**:
|
|
476
|
+
- You need images, icons, or badges
|
|
477
|
+
- Options require multiple lines of text
|
|
478
|
+
- Custom styling/layout is important
|
|
479
|
+
- Conditional rendering based on data
|
|
480
|
+
- Rich user experience is priority
|
|
481
|
+
|
|
482
|
+
### Combining Both Methods
|
|
483
|
+
|
|
484
|
+
You can start with Method 1 and add Method 2 later as your UI evolves:
|
|
485
|
+
|
|
486
|
+
```javascript
|
|
487
|
+
// Start simple
|
|
488
|
+
select.items = ['Option 1', 'Option 2', 'Option 3'];
|
|
489
|
+
|
|
490
|
+
// Later, add custom rendering without changing items
|
|
491
|
+
select.optionTemplate = (item, index) => `
|
|
492
|
+
<div style="padding: 8px; background: ${index % 2 ? '#f9fafb' : 'white'};">
|
|
493
|
+
<strong>${item.label || item}</strong>
|
|
494
|
+
</div>
|
|
495
|
+
`;
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
174
500
|
### Events
|
|
175
501
|
|
|
176
502
|
```typescript
|