@smilodon/core 1.3.5 → 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
@@ -310,13 +629,53 @@ See the [full CSS variables reference](https://github.com/navidrezadoost/smilodo
310
629
  - Input container (gap, padding, height, borders, focus states)
311
630
  - Input field (width, padding, font, colors)
312
631
  - Arrow/icon (size, color, hover states, position)
313
- - Separator line (width, height, gradient)
314
- - Selection badges (padding, colors, remove button)
632
+ - **Separator line** (width, height, gradient, position)
633
+ - **Selection badges** (padding, colors, **remove/delete button**)
315
634
  - Dropdown (margins, max-height, borders, shadows)
316
635
  - Options (font size, line height, borders, transitions)
317
636
  - Load more button (padding, borders, colors, hover states)
318
637
  - Loading/empty states (padding, colors, backgrounds, spinner)
319
638
 
639
+ #### Highlighted Customization Features
640
+
641
+ **Separator Line Between Input and Arrow**
642
+ The vertical separator line that appears between the input area and the dropdown arrow is fully customizable:
643
+
644
+ ```css
645
+ enhanced-select {
646
+ /* Customize the separator line */
647
+ --select-separator-width: 2px; /* Line thickness */
648
+ --select-separator-height: 80%; /* Line height */
649
+ --select-separator-position: 40px; /* Distance from right edge */
650
+ --select-separator-gradient: linear-gradient(
651
+ to bottom,
652
+ transparent 0%,
653
+ #3b82f6 20%,
654
+ #3b82f6 80%,
655
+ transparent 100%
656
+ );
657
+ }
658
+ ```
659
+
660
+ **Badge Remove/Delete Button (Multi-Select)**
661
+ The × button that removes selected items in multi-select mode is fully customizable:
662
+
663
+ ```css
664
+ enhanced-select {
665
+ /* Customize badge appearance */
666
+ --select-badge-bg: #10b981; /* Badge background */
667
+ --select-badge-color: white; /* Badge text color */
668
+ --select-badge-padding: 6px 10px; /* Badge spacing */
669
+
670
+ /* Customize the × (remove/delete) button */
671
+ --select-badge-remove-size: 18px; /* Button size */
672
+ --select-badge-remove-bg: rgba(255, 255, 255, 0.3); /* Button background */
673
+ --select-badge-remove-color: white; /* × symbol color */
674
+ --select-badge-remove-font-size: 18px; /* × symbol size */
675
+ --select-badge-remove-hover-bg: rgba(255, 255, 255, 0.6); /* Hover state */
676
+ }
677
+ ```
678
+
320
679
  #### Real-World Customization Examples
321
680
 
322
681
  **Example 1: Bootstrap-style Select**
@@ -379,7 +738,7 @@ enhanced-select {
379
738
 
380
739
  #### Framework-Specific Examples
381
740
 
382
- **React**
741
+ **React - Customizing Separator & Badge Remove Button**
383
742
  ```jsx
384
743
  import { Select } from '@smilodon/react';
385
744
 
@@ -387,32 +746,69 @@ function App() {
387
746
  return (
388
747
  <Select
389
748
  items={items}
390
- className="dark-mode"
749
+ multiple
391
750
  style={{
392
751
  '--select-option-hover-bg': '#2563eb',
393
752
  '--select-option-padding': '12px 16px',
394
- '--select-badge-bg': '#3b82f6'
753
+ '--select-badge-bg': '#3b82f6',
754
+ '--select-badge-remove-bg': 'rgba(255, 255, 255, 0.4)',
755
+ '--select-badge-remove-hover-bg': 'rgba(255, 255, 255, 0.7)',
756
+ '--select-separator-gradient': 'linear-gradient(to bottom, transparent 0%, #3b82f6 20%, #3b82f6 80%, transparent 100%)'
395
757
  }}
396
758
  />
397
759
  );
398
760
  }
399
761
  ```
400
762
 
401
- **Vue**
763
+ **Vue - Complete Customization**
402
764
  ```vue
403
765
  <template>
404
766
  <Select
405
767
  :items="items"
406
- class="dark-mode"
768
+ multiple
407
769
  :style="{
408
770
  '--select-option-hover-bg': '#2563eb',
409
771
  '--select-option-padding': '12px 16px',
410
- '--select-badge-bg': '#3b82f6'
772
+ '--select-badge-bg': '#3b82f6',
773
+ '--select-badge-remove-size': '20px',
774
+ '--select-badge-remove-bg': 'rgba(255, 255, 255, 0.4)',
775
+ '--select-separator-width': '2px',
776
+ '--select-separator-height': '70%'
411
777
  }"
412
778
  />
413
779
  </template>
414
780
  ```
415
781
 
782
+ **Svelte - Themed Components**
783
+ ```svelte
784
+ <script>
785
+ import { Select } from '@smilodon/svelte';
786
+ </script>
787
+
788
+ <Select
789
+ items={items}
790
+ multiple
791
+ style="
792
+ --select-badge-bg: #10b981;
793
+ --select-badge-remove-bg: rgba(255, 255, 255, 0.3);
794
+ --select-badge-remove-hover-bg: rgba(255, 255, 255, 0.6);
795
+ --select-separator-gradient: linear-gradient(to bottom, transparent, #10b981, transparent);
796
+ "
797
+ />
798
+ ```
799
+
800
+ **Vanilla JS - Dynamic Styling**
801
+ ```javascript
802
+ const select = document.querySelector('enhanced-select');
803
+
804
+ // Apply custom separator and badge styles
805
+ select.style.setProperty('--select-separator-width', '2px');
806
+ select.style.setProperty('--select-separator-gradient', 'linear-gradient(to bottom, transparent, #ff6b6b, transparent)');
807
+ select.style.setProperty('--select-badge-bg', '#ff6b6b');
808
+ select.style.setProperty('--select-badge-remove-size', '18px');
809
+ select.style.setProperty('--select-badge-remove-bg', 'rgba(255, 255, 255, 0.3)');
810
+ ```
811
+
416
812
  ### Server-Side Rendering (SSR)
417
813
 
418
814
  Smilodon gracefully handles SSR environments:
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