@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 +323 -53
- package/dist/multiselect.js +1036 -711
- package/dist/multiselect.umd.js +36 -18
- package/dist/style.css +1 -1
- package/package.json +1 -2
- package/src/scss/_css-variables.scss +2 -0
- package/src/scss/_input-dropdown.scss +9 -0
- package/src/scss/_options.scss +22 -0
- package/src/scss/_tooltips-popover.scss +25 -0
- package/src/scss/_variables.scss +2 -0
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
|
-
-
|
|
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
|
|
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
|
-
###
|
|
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
|
-
<
|
|
72
|
+
<web-multiselect
|
|
73
|
+
id="my-select"
|
|
37
74
|
search-placeholder="Search options..."
|
|
38
75
|
initial-values='["js","ts"]'>
|
|
39
|
-
</
|
|
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('
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
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
|
-
<!--
|
|
311
|
-
<
|
|
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
|
-
<!--
|
|
314
|
-
<
|
|
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
|
-
<
|
|
578
|
+
<web-multiselect
|
|
318
579
|
pills-threshold="3"
|
|
319
580
|
pills-threshold-mode="count"
|
|
320
581
|
show-count-badge="true">
|
|
321
|
-
</
|
|
582
|
+
</web-multiselect>
|
|
322
583
|
|
|
323
584
|
<!-- Partial mode - Show limited pills + "+X more" badge -->
|
|
324
|
-
<
|
|
585
|
+
<web-multiselect
|
|
325
586
|
pills-threshold="5"
|
|
326
587
|
pills-threshold-mode="partial"
|
|
327
588
|
pills-max-visible="3">
|
|
328
|
-
</
|
|
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
|
-
<
|
|
607
|
+
<web-multiselect pills-position="bottom"></web-multiselect>
|
|
338
608
|
|
|
339
609
|
<!-- Pills above input -->
|
|
340
|
-
<
|
|
610
|
+
<web-multiselect pills-position="top"></web-multiselect>
|
|
341
611
|
|
|
342
612
|
<!-- Pills to the left of input -->
|
|
343
|
-
<
|
|
613
|
+
<web-multiselect pills-position="left"></web-multiselect>
|
|
344
614
|
|
|
345
615
|
<!-- Pills to the right of input -->
|
|
346
|
-
<
|
|
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
|
-
<
|
|
627
|
+
<web-multiselect
|
|
358
628
|
enable-pill-tooltips="true"
|
|
359
629
|
pill-tooltip-placement="top">
|
|
360
|
-
</
|
|
630
|
+
</web-multiselect>
|
|
361
631
|
|
|
362
632
|
<!-- Fast tooltips with custom delay -->
|
|
363
|
-
<
|
|
633
|
+
<web-multiselect
|
|
364
634
|
enable-pill-tooltips="true"
|
|
365
635
|
pill-tooltip-delay="100">
|
|
366
|
-
</
|
|
636
|
+
</web-multiselect>
|
|
367
637
|
|
|
368
638
|
<script type="module">
|
|
369
|
-
const select = document.querySelector('
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
<
|
|
681
|
+
<web-multiselect dir="rtl" search-placeholder="ابحث..."></web-multiselect>
|
|
412
682
|
|
|
413
683
|
<!-- RTL inherited from parent element -->
|
|
414
684
|
<div dir="rtl">
|
|
415
|
-
<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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('
|
|
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>>('
|
|
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
|
-
<
|
|
834
|
+
<web-multiselect
|
|
565
835
|
name="skills"
|
|
566
836
|
value-format="json"
|
|
567
837
|
multiple="true">
|
|
568
|
-
</
|
|
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('
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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('
|
|
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 `<
|
|
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
|