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