@keenmate/web-multiselect 1.0.0-rc06 → 1.0.0-rc10

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
@@ -7,10 +7,12 @@ A lightweight, accessible multiselect web component with typeahead search, RTL l
7
7
 
8
8
  ## Features
9
9
 
10
- - 🔍 **Typeahead Search** - Real-time filtering as you type
10
+ - 📝 **Declarative HTML** - Use standard `<option>` and `<optgroup>` elements - no JavaScript required for simple cases!
11
+ - ⚡ **Virtual Scrolling** - Handle 15,000+ options instantly (25× faster opening, 99.8% memory reduction)
12
+ - 🔍 **Flexible Search Modes** - Filter (hide non-matches) or navigate (jump to matches, keep all visible)
11
13
  - ⌨️ **Keyboard Navigation** - Full keyboard support (arrows, Enter, Esc, Tab)
12
14
  - 🎨 **Rich Content** - Icons, subtitles, and multiline text support
13
- - 📊 **Multiple Display Modes** - Pills, count, compact, or partial (pills + threshold)
15
+ - 📊 **Multiple Display Modes** - Pills, count, compact, partial, or none (minimal UI)
14
16
  - 💬 **Pill Tooltips** - Customizable tooltips on selected items with placement control
15
17
  - 🎯 **Single & Multi-Select** - Switch between single and multiple selection modes
16
18
  - 🔄 **Async Data Loading** - On-demand data fetching support
@@ -29,25 +31,51 @@ npm install @keenmate/web-multiselect
29
31
 
30
32
  ## Usage
31
33
 
32
- ### Basic HTML
34
+ ### Declarative (No JavaScript!)
35
+
36
+ Perfect for simple forms - just use standard HTML `<option>` elements:
37
+
38
+ ```html
39
+ <!-- Simple choice -->
40
+ <web-multiselect multiple="false">
41
+ <option value="yes">Yes</option>
42
+ <option value="no">No</option>
43
+ <option value="maybe" selected>Maybe</option>
44
+ </web-multiselect>
45
+
46
+ <!-- With icons -->
47
+ <web-multiselect>
48
+ <option value="apple" data-icon="🍎">Apple</option>
49
+ <option value="banana" data-icon="🍌" selected>Banana</option>
50
+ <option value="orange" data-icon="🍊">Orange</option>
51
+ </web-multiselect>
52
+
53
+ <!-- With groups -->
54
+ <web-multiselect>
55
+ <optgroup label="Frontend">
56
+ <option value="js" data-icon="🟨">JavaScript</option>
57
+ <option value="ts" data-icon="🔷">TypeScript</option>
58
+ </optgroup>
59
+ <optgroup label="Backend">
60
+ <option value="python" data-icon="🐍" selected>Python</option>
61
+ <option value="java" data-icon="☕">Java</option>
62
+ </optgroup>
63
+ </web-multiselect>
64
+ ```
65
+
66
+ ### Programmatic (With JavaScript)
67
+
68
+ For dynamic data and advanced features:
33
69
 
34
70
  ```html
35
71
  <!-- Multi-select -->
36
- <multi-select
72
+ <web-multiselect
73
+ id="my-select"
37
74
  search-placeholder="Search options..."
38
75
  initial-values='["js","ts"]'>
39
- </multi-select>
40
-
41
- <!-- Single-select -->
42
- <multi-select
43
- multiple="false"
44
- search-placeholder="Select one..."
45
- initial-values='["python"]'>
46
- </multi-select>
76
+ </web-multiselect>
47
77
  ```
48
78
 
49
- ### With JavaScript/TypeScript
50
-
51
79
  ```typescript
52
80
  // Import the component (includes styles)
53
81
  import '@keenmate/web-multiselect';
@@ -55,7 +83,7 @@ import '@keenmate/web-multiselect';
55
83
  // Or import styles separately if needed
56
84
  import '@keenmate/web-multiselect/style.css';
57
85
 
58
- const multiselect = document.querySelector('multi-select');
86
+ const multiselect = document.querySelector('web-multiselect');
59
87
 
60
88
  // Set options programmatically
61
89
  multiselect.options = [
@@ -88,7 +116,7 @@ multiselect.setSelected(['js', 'ts']);
88
116
  | `show-checkboxes` | `boolean` | `true` | Show checkboxes next to options |
89
117
  | `close-on-select` | `boolean` | `false` | Close dropdown after selecting |
90
118
  | `dropdown-min-width` | `string` | - | Min width for dropdown (e.g., '20rem') |
91
- | `pills-display-mode` | `'pills' \| 'count' \| 'compact' \| 'partial'` | `'pills'` | How to display selected items |
119
+ | `pills-display-mode` | `'pills' \| 'count' \| 'compact' \| 'partial' \| 'none'` | `'pills'` | How to display selected items. `compact`: first item + count. `none`: no display |
92
120
  | `pills-threshold` | `number` | - | Auto-switch mode when exceeded (see pills-threshold-mode) |
93
121
  | `pills-threshold-mode` | `'count' \| 'partial'` | `'count'` | Mode after threshold: 'count' shows badge, 'partial' shows limited pills + more badge |
94
122
  | `pills-max-visible` | `number` | `3` | Max pills shown in partial mode |
@@ -102,10 +130,12 @@ multiselect.setSelected(['js', 'ts']);
102
130
  | `empty-message` | `string` | `'No results found'` | Message when no options found |
103
131
  | `loading-message` | `string` | `'Loading...'` | Message while loading async data |
104
132
  | `min-search-length` | `number` | `0` | Minimum search length for async |
133
+ | `keep-options-on-search` | `boolean` | `true` | Keep initial options visible when searchCallback is active (hybrid search) |
105
134
  | `sticky-actions` | `boolean` | `true` | Keep Select All/Clear All buttons fixed at top while scrolling |
106
135
  | `lock-placement` | `boolean` | `true` | Lock dropdown placement after first open to prevent flipping |
107
136
  | `enable-search` | `boolean` | `true` | Enable/disable search functionality |
108
137
  | `search-input-mode` | `'normal' \| 'readonly' \| 'hidden'` | `'normal'` | Search input display mode |
138
+ | `search-mode` | `'filter' \| 'navigate'` | `'filter'` | Search behavior: 'filter' hides non-matches, 'navigate' jumps to matches |
109
139
  | `allow-add-new` | `boolean` | `false` | Allow adding new options not in the list |
110
140
  | `value-member` | `string` | - | Property name for value/ID extraction from custom objects |
111
141
  | `display-value-member` | `string` | - | Property name for display text extraction from custom objects |
@@ -117,6 +147,10 @@ multiselect.setSelected(['js', 'ts']);
117
147
  | `name` | `string` | - | HTML form field name for form integration (creates hidden input) |
118
148
  | `value-format` | `'json' \| 'csv' \| 'array'` | `'json'` | Format for form value serialization |
119
149
  | `initial-values` | `string` (JSON array) | - | Pre-selected values |
150
+ | `enable-virtual-scroll` | `boolean` | `false` | Enable virtual scrolling for large datasets |
151
+ | `virtual-scroll-threshold` | `number` | `100` | Minimum items before virtual scroll activates |
152
+ | `option-height` | `number` | `50` | Fixed height for each option in pixels (required for virtual scroll) |
153
+ | `virtual-scroll-buffer` | `number` | `10` | Buffer size - extra items rendered above/below viewport |
120
154
 
121
155
  ## Properties
122
156
 
@@ -133,6 +167,17 @@ multiselect.onSearch = async (searchTerm) => {
133
167
  return await response.json();
134
168
  };
135
169
 
170
+ // Pre-process search terms before calling searchCallback
171
+ multiselect.beforeSearchCallback = (searchTerm) => {
172
+ // Remove accents: "café" → "cafe"
173
+ const normalized = searchTerm.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
174
+
175
+ // Block search if too short (return null to prevent search)
176
+ if (normalized.length < 2) return null;
177
+
178
+ return normalized; // Return transformed term
179
+ };
180
+
136
181
  // Event callbacks
137
182
  multiselect.onSelect = (option) => {
138
183
  console.log('Selected:', option);
@@ -222,6 +267,7 @@ multiselect.addNewCallback = async (value) => {
222
267
  ## Keyboard Shortcuts
223
268
 
224
269
  - **↑ ↓** - Navigate up/down through options
270
+ - **Ctrl+↑ Ctrl+↓** - Jump between matched items (navigate mode only)
225
271
  - **Enter** - Select focused option
226
272
  - **Escape** - Close dropdown
227
273
  - **Tab** - Close dropdown and move to next field
@@ -234,7 +280,7 @@ multiselect.addNewCallback = async (value) => {
234
280
  Icons support multiple formats - emojis, SVG markup, Font Awesome, images, or any HTML:
235
281
 
236
282
  ```html
237
- <multi-select id="frameworks"></multi-select>
283
+ <web-multiselect id="frameworks"></web-multiselect>
238
284
 
239
285
  <script type="module">
240
286
  const select = document.getElementById('frameworks');
@@ -281,12 +327,12 @@ select.options = [
281
327
  ### Async Data Loading
282
328
 
283
329
  ```html
284
- <multi-select
330
+ <web-multiselect
285
331
  id="async-select"
286
332
  min-search-length="2"
287
333
  loading-message="Searching..."
288
334
  empty-message="No products found">
289
- </multi-select>
335
+ </web-multiselect>
290
336
 
291
337
  <script type="module">
292
338
  const select = document.getElementById('async-select');
@@ -299,51 +345,275 @@ select.options = [
299
345
  </script>
300
346
  ```
301
347
 
348
+ ### Hybrid Static + Dynamic Search
349
+
350
+ Show popular items initially, then switch to full database search when the user types. Perfect for showing "Top 10" items while supporting comprehensive search:
351
+
352
+ ```html
353
+ <web-multiselect
354
+ id="hybrid-select"
355
+ min-search-length="3"
356
+ keep-options-on-search="true">
357
+ </web-multiselect>
358
+
359
+ <script type="module">
360
+ const select = document.getElementById('hybrid-select');
361
+
362
+ // Set initial popular items (shown when dropdown opens)
363
+ select.options = [
364
+ { id: 1, name: 'React' },
365
+ { id: 2, name: 'Vue' },
366
+ { id: 3, name: 'Angular' },
367
+ { id: 4, name: 'Svelte' },
368
+ { id: 5, name: 'Solid' }
369
+ ];
370
+
371
+ // Pre-process search terms (remove accents, validate, etc.)
372
+ select.beforeSearchCallback = (searchTerm) => {
373
+ // Remove accents: "café" → "cafe"
374
+ const normalized = searchTerm.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
375
+
376
+ // Block search if too short (return null to prevent search)
377
+ if (normalized.length < 2) return null;
378
+
379
+ return normalized;
380
+ };
381
+
382
+ // Search full database when user types 3+ characters
383
+ select.onSearch = async (searchTerm) => {
384
+ const response = await fetch(`/api/frameworks/search?q=${searchTerm}`);
385
+ return await response.json();
386
+ };
387
+ </script>
388
+ ```
389
+
390
+ **How it works:**
391
+ 1. **Dropdown opens** → Shows 5 popular frameworks
392
+ 2. **User types "rea"** → Calls API, shows all matching results from database
393
+ 3. **User clears search** → Shows 5 popular frameworks again
394
+ 4. **User types "café"** → `beforeSearchCallback` converts to "cafe", then searches
395
+
396
+ **Key options:**
397
+ - `keep-options-on-search="true"` (default) - Keep initial options visible when search is empty/short
398
+ - `beforeSearchCallback` - Transform search text or block search by returning `null`
399
+ - `min-search-length` - Minimum characters before triggering search (shows initial options below this)
400
+
401
+ ### Virtual Scrolling for Large Datasets
402
+
403
+ Handle 10,000+ options with smooth 60fps performance by rendering only visible items:
404
+
405
+ ```html
406
+ <web-multiselect
407
+ id="large-dataset"
408
+ enable-virtual-scroll="true"
409
+ virtual-scroll-threshold="100"
410
+ option-height="50"
411
+ virtual-scroll-buffer="10"
412
+ search-mode="filter"
413
+ max-height="400px">
414
+ </web-multiselect>
415
+
416
+ <script type="module">
417
+ import '@keenmate/web-multiselect';
418
+
419
+ const select = document.getElementById('large-dataset');
420
+
421
+ // Generate 15,000 options
422
+ const largeDataset = Array.from({ length: 15000 }, (_, i) => ({
423
+ value: i,
424
+ label: `Item ${i.toString().padStart(5, '0')}`
425
+ }));
426
+
427
+ select.options = largeDataset;
428
+ </script>
429
+ ```
430
+
431
+ **Performance Comparison (15,000 items):**
432
+
433
+ | Metric | Without Virtual Scroll | With Virtual Scroll | Improvement |
434
+ |--------|------------------------|---------------------|-------------|
435
+ | Initial render | 750ms | 30ms | **25× faster** |
436
+ | Search keystroke | 200-500ms | 15ms | **13-33× faster** |
437
+ | DOM nodes | 15,000 | ~30 | **99.8% reduction** |
438
+ | Memory usage | ~7.5 MB | ~15 KB | **500× less** |
439
+
440
+ **Configuration:**
441
+
442
+ - `enable-virtual-scroll="true"` - Enable virtual scrolling (default: `false`)
443
+ - `virtual-scroll-threshold="100"` - Auto-activate when this many items are present (default: `100`)
444
+ - `option-height="50"` - Fixed height per option in pixels (default: `50px`)
445
+ - `virtual-scroll-buffer="10"` - Extra items rendered above/below viewport for smooth scrolling (default: `10`)
446
+
447
+ **How it works:**
448
+ - Only renders ~30 visible items instead of all 15,000 DOM elements
449
+ - Uses absolute positioning with calculated offsets
450
+ - Maintains 10-item buffer zones above/below viewport for smooth scrolling
451
+ - Automatically calculates visible range based on scroll position
452
+ - Works seamlessly with search filtering and selection
453
+
454
+ **Requirements:**
455
+ - All options must have the same fixed height (enforced via CSS)
456
+ - Not compatible with grouped options (automatically falls back to normal rendering)
457
+ - Works with both filter and navigate search modes
458
+
459
+ **Example with search:**
460
+ ```html
461
+ <!-- Virtual scroll + filter search for optimal large dataset performance -->
462
+ <web-multiselect
463
+ id="products"
464
+ enable-virtual-scroll="true"
465
+ search-mode="filter"
466
+ value-member="id"
467
+ display-value-member="name"
468
+ max-height="400px">
469
+ </web-multiselect>
470
+
471
+ <script type="module">
472
+ const select = document.getElementById('products');
473
+
474
+ // Load from API
475
+ const response = await fetch('/api/products');
476
+ const products = await response.json();
477
+
478
+ select.options = products; // Could be 10,000+ items
479
+ </script>
480
+ ```
481
+
482
+ **Live Demo:**
483
+ See [examples-performance.html](examples-performance.html) for a working demo with 15,000 randomly generated options.
484
+
485
+ ### Virtual Scrolling
486
+
487
+ Handle massive datasets (10,000+ items) with instant performance using virtual scrolling. Only visible items (~30) are rendered in the DOM, dramatically reducing memory usage and improving responsiveness.
488
+
489
+ **Enable virtual scrolling:**
490
+ ```html
491
+ <web-multiselect
492
+ enable-virtual-scroll="true"
493
+ virtual-scroll-threshold="100"
494
+ option-height="50"
495
+ virtual-scroll-buffer="10">
496
+ </web-multiselect>
497
+ ```
498
+
499
+ **Performance improvements with 15,000 items:**
500
+ - **Dropdown opening**: 750ms → 30ms (25× faster)
501
+ - **Search performance**: 200-500ms → 15ms per keystroke (13-33× faster)
502
+ - **Memory usage**: 7.5 MB → 15 KB (99.8% reduction)
503
+ - **DOM nodes**: 15,000 → ~30 visible items
504
+
505
+ **Configuration:**
506
+ - `enable-virtual-scroll="true"` - Opt-in to virtual scrolling
507
+ - `virtual-scroll-threshold="100"` - Auto-activates at 100+ items (default)
508
+ - `option-height="50"` - Fixed height per option in pixels (default: 50px)
509
+ - `virtual-scroll-buffer="10"` - Extra items rendered above/below viewport (default: 10)
510
+
511
+ **Features:**
512
+ - Full keyboard navigation (arrows, Page Up/Down, Home/End)
513
+ - Smooth mouse wheel scrolling
514
+ - Drag scrollbar support
515
+ - Works with search in both filter and navigate modes
516
+ - Automatic activation based on threshold
517
+
518
+ **Limitations:**
519
+ - Groups (`<optgroup>`) are disabled in virtual scroll mode (automatically falls back to standard rendering)
520
+ - All options must have consistent height (enforced via CSS)
521
+
522
+ **Live Demo:**
523
+ See [examples-performance.html](examples-performance.html) for a working demo testing virtual scroll with 15,000 randomly generated options.
524
+
525
+ ### Search Modes: Filter vs Navigate
526
+
527
+ Choose between two search behaviors:
528
+
529
+ **Filter Mode** (default) - Hide non-matching options as you type:
530
+ ```html
531
+ <web-multiselect search-mode="filter" id="countries"></web-multiselect>
532
+ ```
533
+
534
+ **Navigate Mode** - Keep all options visible, jump to matches:
535
+ ```html
536
+ <web-multiselect search-mode="navigate" id="states"></web-multiselect>
537
+
538
+ <script>
539
+ const select = document.getElementById('states');
540
+ select.options = [...50 US states...];
541
+
542
+ // User types "cal" → Jumps to "California", shows all states
543
+ // Matching options are highlighted with left border
544
+ </script>
545
+ ```
546
+
547
+ **When to use each mode:**
548
+ - **Filter Mode**: Large datasets where narrowing down is essential (product catalogs, user lists, search results)
549
+ - **Navigate Mode**: Quick selection from familiar lists (countries, states, keyboard shortcuts, known options)
550
+
551
+ **Key differences:**
552
+ - Filter mode hides non-matches, navigate mode highlights matches with a left border
553
+ - Navigate mode keeps previous focus if no match is found (type "xyz" → stays on current option)
554
+ - Navigate mode only works with local data (automatically falls back to filter mode when using `searchCallback`)
555
+ - Both modes respect `beforeSearchCallback` for search term preprocessing (accent removal, validation)
556
+ - **Ctrl+↑/↓** jumps between matches only (navigate mode) - regular arrows navigate through all items
557
+
302
558
  ### Display Modes
303
559
 
304
560
  Perfect for different use cases and space constraints:
305
561
 
306
562
  ```html
307
563
  <!-- Pills mode (default) - Show all selections as removable pills -->
308
- <multi-select pills-display-mode="pills"></multi-select>
564
+ <web-multiselect pills-display-mode="pills"></web-multiselect>
565
+
566
+ <!-- Count mode - Show "X selected" text with clear button -->
567
+ <web-multiselect pills-display-mode="count" show-count-badge="true"></web-multiselect>
309
568
 
310
- <!-- Count mode - Show only count badge -->
311
- <multi-select pills-display-mode="count" show-count-badge="true"></multi-select>
569
+ <!-- Compact mode - Show first item + count in a single removable pill -->
570
+ <web-multiselect pills-display-mode="compact"></web-multiselect>
571
+ <!-- Example output: [JavaScript (+2 more) | x] -->
312
572
 
313
- <!-- Compact mode - Show first item + count -->
314
- <multi-select pills-display-mode="compact"></multi-select>
573
+ <!-- None mode - No display in pills area (minimal UI) -->
574
+ <web-multiselect pills-display-mode="none" show-count-badge="true"></web-multiselect>
575
+ <!-- Only shows [X] badge next to toggle icon -->
315
576
 
316
577
  <!-- Auto-switch from pills to count at threshold -->
317
- <multi-select
578
+ <web-multiselect
318
579
  pills-threshold="3"
319
580
  pills-threshold-mode="count"
320
581
  show-count-badge="true">
321
- </multi-select>
582
+ </web-multiselect>
322
583
 
323
584
  <!-- Partial mode - Show limited pills + "+X more" badge -->
324
- <multi-select
585
+ <web-multiselect
325
586
  pills-threshold="5"
326
587
  pills-threshold-mode="partial"
327
588
  pills-max-visible="3">
328
- </multi-select>
589
+ </web-multiselect>
329
590
  ```
330
591
 
592
+ **Display Mode Behavior:**
593
+ - **`pills`**: Individual removable pills for each selected item. Calls `getPillDisplayCallback` for each item.
594
+ - **`count`**: Shows "X selected" text with clear button. Calls `getCountPillCallback(count)`.
595
+ - **`compact`**: Shows first item + count in single pill (e.g., "JavaScript (+2 more)"). Calls `getPillDisplayCallback(firstItem)` and `getCountPillCallback(count, remainingCount)`.
596
+ - **`partial`**: Shows first N pills + "+X more" badge. Calls `getPillDisplayCallback` for visible items and `getCountPillCallback(count, remainingCount)` for badge.
597
+ - **`none`**: No display in pills area. No callbacks invoked. Use with `show-count-badge="true"` for minimal UI.
598
+
599
+ **Count Badge (`show-count-badge="true"`)**: Independent feature showing `[X]` next to toggle icon. Works with all display modes. Not affected by callbacks.
600
+
331
601
  ### Pills Positioning
332
602
 
333
603
  Control where selected item badges appear relative to the input:
334
604
 
335
605
  ```html
336
606
  <!-- Pills below input (default) -->
337
- <multi-select pills-position="bottom"></multi-select>
607
+ <web-multiselect pills-position="bottom"></web-multiselect>
338
608
 
339
609
  <!-- Pills above input -->
340
- <multi-select pills-position="top"></multi-select>
610
+ <web-multiselect pills-position="top"></web-multiselect>
341
611
 
342
612
  <!-- Pills to the left of input -->
343
- <multi-select pills-position="left"></multi-select>
613
+ <web-multiselect pills-position="left"></web-multiselect>
344
614
 
345
615
  <!-- Pills to the right of input -->
346
- <multi-select pills-position="right"></multi-select>
616
+ <web-multiselect pills-position="right"></web-multiselect>
347
617
  ```
348
618
 
349
619
  **Note:** In RTL mode, left/right positions are automatically mirrored - `pills-position="left"` will appear on the physical right side in RTL languages.
@@ -354,19 +624,19 @@ Enable tooltips on selected item pills with customizable placement and delay:
354
624
 
355
625
  ```html
356
626
  <!-- Basic tooltips -->
357
- <multi-select
627
+ <web-multiselect
358
628
  enable-pill-tooltips="true"
359
629
  pill-tooltip-placement="top">
360
- </multi-select>
630
+ </web-multiselect>
361
631
 
362
632
  <!-- Fast tooltips with custom delay -->
363
- <multi-select
633
+ <web-multiselect
364
634
  enable-pill-tooltips="true"
365
635
  pill-tooltip-delay="100">
366
- </multi-select>
636
+ </web-multiselect>
367
637
 
368
638
  <script type="module">
369
- const select = document.querySelector('multi-select');
639
+ const select = document.querySelector('web-multiselect');
370
640
 
371
641
  // Custom tooltip content
372
642
  select.getPillTooltipCallback = (item) => {
@@ -380,12 +650,12 @@ Enable tooltips on selected item pills with customizable placement and delay:
380
650
  Customize count pill text for proper pluralization and localization:
381
651
 
382
652
  ```html
383
- <multi-select
653
+ <web-multiselect
384
654
  id="i18n-select"
385
655
  pills-threshold="5"
386
656
  pills-threshold-mode="partial"
387
657
  pills-max-visible="3">
388
- </multi-select>
658
+ </web-multiselect>
389
659
 
390
660
  <script type="module">
391
661
  const select = document.getElementById('i18n-select');
@@ -408,11 +678,11 @@ Full RTL support for Arabic, Hebrew, Persian, Urdu, and other right-to-left lang
408
678
 
409
679
  ```html
410
680
  <!-- Automatic RTL detection from dir attribute -->
411
- <multi-select dir="rtl" search-placeholder="ابحث..."></multi-select>
681
+ <web-multiselect dir="rtl" search-placeholder="ابحث..."></web-multiselect>
412
682
 
413
683
  <!-- RTL inherited from parent element -->
414
684
  <div dir="rtl">
415
- <multi-select search-placeholder="חיפוש..."></multi-select>
685
+ <web-multiselect search-placeholder="חיפוש..."></web-multiselect>
416
686
  </div>
417
687
 
418
688
  <!-- RTL on page level -->
@@ -438,14 +708,14 @@ The component supports **any data structure** through a member/callback pattern,
438
708
  For objects with consistent property names, use member attributes:
439
709
 
440
710
  ```html
441
- <multi-select
711
+ <web-multiselect
442
712
  id="products"
443
713
  value-member="productId"
444
714
  display-value-member="productName"
445
715
  icon-member="icon"
446
716
  subtitle-member="description"
447
717
  group-member="category">
448
- </multi-select>
718
+ </web-multiselect>
449
719
 
450
720
  <script type="module">
451
721
  const select = document.getElementById('products');
@@ -473,7 +743,7 @@ For objects with consistent property names, use member attributes:
473
743
  For complex data extraction or conditional logic, use callbacks:
474
744
 
475
745
  ```javascript
476
- const select = document.querySelector('multi-select');
746
+ const select = document.querySelector('web-multiselect');
477
747
 
478
748
  // Custom value extraction
479
749
  select.getValueCallback = (item) => item.id || item.code || item.value;
@@ -546,7 +816,7 @@ interface Product {
546
816
  category: string;
547
817
  }
548
818
 
549
- const select = document.querySelector<MultiSelectElement<Product>>('multi-select');
819
+ const select = document.querySelector<MultiSelectElement<Product>>('web-multiselect');
550
820
  select.options = [
551
821
  { id: 'p1', name: 'Laptop', price: 999, category: 'Electronics' }
552
822
  ];
@@ -561,11 +831,11 @@ The component seamlessly integrates with standard HTML forms by automatically cr
561
831
  ```html
562
832
  <form id="userForm" action="/submit" method="POST">
563
833
  <label>Select Skills:</label>
564
- <multi-select
834
+ <web-multiselect
565
835
  name="skills"
566
836
  value-format="json"
567
837
  multiple="true">
568
- </multi-select>
838
+ </web-multiselect>
569
839
 
570
840
  <button type="submit">Submit</button>
571
841
  </form>
@@ -574,7 +844,7 @@ The component seamlessly integrates with standard HTML forms by automatically cr
574
844
  import '@keenmate/web-multiselect';
575
845
 
576
846
  const form = document.getElementById('userForm');
577
- const select = form.querySelector('multi-select');
847
+ const select = form.querySelector('web-multiselect');
578
848
 
579
849
  select.options = [
580
850
  { value: 'js', label: 'JavaScript' },
@@ -600,19 +870,19 @@ Choose how selected values are serialized in forms:
600
870
 
601
871
  **JSON Format** (default):
602
872
  ```html
603
- <multi-select name="items" value-format="json"></multi-select>
873
+ <web-multiselect name="items" value-format="json"></web-multiselect>
604
874
  <!-- FormData result: items = ["item1","item2","item3"] -->
605
875
  ```
606
876
 
607
877
  **CSV Format**:
608
878
  ```html
609
- <multi-select name="items" value-format="csv"></multi-select>
879
+ <web-multiselect name="items" value-format="csv"></web-multiselect>
610
880
  <!-- FormData result: items = "item1,item2,item3" -->
611
881
  ```
612
882
 
613
883
  **Array Format** (multiple inputs):
614
884
  ```html
615
- <multi-select name="items" value-format="array"></multi-select>
885
+ <web-multiselect name="items" value-format="array"></web-multiselect>
616
886
  <!-- FormData result:
617
887
  items[] = "item1"
618
888
  items[] = "item2"
@@ -625,7 +895,7 @@ Choose how selected values are serialized in forms:
625
895
  For advanced use cases, provide a custom formatting function:
626
896
 
627
897
  ```javascript
628
- const select = document.querySelector('multi-select');
898
+ const select = document.querySelector('web-multiselect');
629
899
 
630
900
  select.name = 'product_ids';
631
901
  select.getValueFormatCallback = (values) => {
@@ -756,7 +1026,7 @@ The component exposes **150+ CSS custom properties** defined at the `:host` leve
756
1026
 
757
1027
  All CSS custom properties are now defined at the `:host` level in the compiled CSS, making them visible in browser DevTools:
758
1028
 
759
- 1. Open DevTools (F12) and select the `<multi-select>` element
1029
+ 1. Open DevTools (F12) and select the `<web-multiselect>` element
760
1030
  2. In the **Styles** panel, look for the `:host` selector
761
1031
  3. You'll see all 150+ variables with their default values
762
1032
  4. Edit values live to preview changes instantly