@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 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" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
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