@keenmate/web-multiselect 1.0.0-rc02
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/LICENSE +21 -0
- package/README.md +562 -0
- package/dist/index.d.ts +26 -0
- package/dist/multiselect.d.ts +117 -0
- package/dist/multiselect.js +2231 -0
- package/dist/multiselect.umd.js +46 -0
- package/dist/style.css +1 -0
- package/dist/types.d.ts +187 -0
- package/dist/web-component.d.ts +103 -0
- package/package.json +75 -0
- package/src/scss/_base.scss +41 -0
- package/src/scss/_debug.scss +60 -0
- package/src/scss/_input-dropdown.scss +177 -0
- package/src/scss/_modifiers.scss +95 -0
- package/src/scss/_options.scss +175 -0
- package/src/scss/_pills-display.scss +218 -0
- package/src/scss/_tooltips-popover.scss +114 -0
- package/src/scss/_variables.scss +453 -0
- package/src/scss/main.scss +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Keenmate
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
# MultiSelect Web Component
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://www.npmjs.com/package/@keenmate/web-multiselect)
|
|
5
|
+
|
|
6
|
+
A lightweight, accessible multiselect web component with typeahead search, rich content support, and excellent keyboard navigation.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🔍 **Typeahead Search** - Real-time filtering as you type
|
|
11
|
+
- ⌨️ **Keyboard Navigation** - Full keyboard support (arrows, Enter, Esc, Tab)
|
|
12
|
+
- 🎨 **Rich Content** - Icons, subtitles, and multiline text support
|
|
13
|
+
- 📊 **Multiple Display Modes** - Pills, count, compact, or partial (pills + threshold)
|
|
14
|
+
- 💬 **Pill Tooltips** - Customizable tooltips on selected items with placement control
|
|
15
|
+
- 🎯 **Single & Multi-Select** - Switch between single and multiple selection modes
|
|
16
|
+
- 🔄 **Async Data Loading** - On-demand data fetching support
|
|
17
|
+
- 📦 **Grouped Options** - Organize options into collapsible groups
|
|
18
|
+
- 🎉 **Smart Positioning** - Uses Floating UI for intelligent dropdown placement
|
|
19
|
+
- 🌍 **i18n Support** - Customizable callbacks for pluralization and localization
|
|
20
|
+
- ✨ **Modern** - Web Component with Shadow DOM, TypeScript, bundled with Vite
|
|
21
|
+
- 🌐 **Framework Agnostic** - Works with any framework or vanilla JS
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @keenmate/web-multiselect
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Basic HTML
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<!-- Multi-select -->
|
|
35
|
+
<multi-select
|
|
36
|
+
search-placeholder="Search options..."
|
|
37
|
+
initial-values='["js","ts"]'>
|
|
38
|
+
</multi-select>
|
|
39
|
+
|
|
40
|
+
<!-- Single-select -->
|
|
41
|
+
<multi-select
|
|
42
|
+
multiple="false"
|
|
43
|
+
search-placeholder="Select one..."
|
|
44
|
+
initial-values='["python"]'>
|
|
45
|
+
</multi-select>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### With JavaScript/TypeScript
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// Import the component (includes styles)
|
|
52
|
+
import '@keenmate/web-multiselect';
|
|
53
|
+
|
|
54
|
+
// Or import styles separately if needed
|
|
55
|
+
import '@keenmate/web-multiselect/style.css';
|
|
56
|
+
|
|
57
|
+
const multiselect = document.querySelector('multi-select');
|
|
58
|
+
|
|
59
|
+
// Set options programmatically
|
|
60
|
+
multiselect.options = [
|
|
61
|
+
{ value: 'js', label: 'JavaScript', icon: '🟨' },
|
|
62
|
+
{ value: 'ts', label: 'TypeScript', icon: '🔷' },
|
|
63
|
+
{ value: 'py', label: 'Python', icon: '🐍' }
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
// Listen for events
|
|
67
|
+
multiselect.addEventListener('change', (e) => {
|
|
68
|
+
console.log('Selected:', e.detail.selectedOptions);
|
|
69
|
+
console.log('Values:', e.detail.selectedValues);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Public API
|
|
73
|
+
const selected = multiselect.getSelected();
|
|
74
|
+
multiselect.setSelected(['js', 'ts']);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Attributes
|
|
78
|
+
|
|
79
|
+
| Attribute | Type | Default | Description |
|
|
80
|
+
|-----------|------|---------|-------------|
|
|
81
|
+
| `multiple` | `boolean` | `true` | Allow multiple selections |
|
|
82
|
+
| `search-placeholder` | `string` | `'Search...'` | Placeholder text for search input |
|
|
83
|
+
| `search-hint` | `string` | - | Hint text shown above input when focused |
|
|
84
|
+
| `allow-groups` | `boolean` | `true` | Enable option grouping |
|
|
85
|
+
| `allow-select-all` | `boolean` | `true` | Show "Select All" button |
|
|
86
|
+
| `allow-clear-all` | `boolean` | `true` | Show "Clear All" button |
|
|
87
|
+
| `show-checkboxes` | `boolean` | `true` | Show checkboxes next to options |
|
|
88
|
+
| `close-on-select` | `boolean` | `false` | Close dropdown after selecting |
|
|
89
|
+
| `dropdown-min-width` | `string` | - | Min width for dropdown (e.g., '20rem') |
|
|
90
|
+
| `pills-display-mode` | `'pills' \| 'count' \| 'compact'` | `'pills'` | How to display selected items |
|
|
91
|
+
| `pills-threshold` | `number` | - | Auto-switch mode when exceeded (see pills-threshold-mode) |
|
|
92
|
+
| `pills-threshold-mode` | `'count' \| 'partial'` | `'count'` | Mode after threshold: 'count' shows badge, 'partial' shows limited pills + more badge |
|
|
93
|
+
| `pills-max-visible` | `number` | `3` | Max pills shown in partial mode |
|
|
94
|
+
| `pills-position` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | Position of pills container |
|
|
95
|
+
| `show-count-badge` | `boolean` | `false` | Show [3] badge next to toggle icon |
|
|
96
|
+
| `enable-pill-tooltips` | `boolean` | `false` | Enable tooltips on selected pills |
|
|
97
|
+
| `pill-tooltip-placement` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'top'` | Tooltip placement relative to pill |
|
|
98
|
+
| `pill-tooltip-delay` | `number` | `300` | Delay in ms before showing tooltip |
|
|
99
|
+
| `pill-tooltip-offset` | `number` | `8` | Distance in pixels between pill and tooltip |
|
|
100
|
+
| `max-height` | `string` | `'20rem'` | Maximum height of dropdown |
|
|
101
|
+
| `empty-message` | `string` | `'No results found'` | Message when no options found |
|
|
102
|
+
| `loading-message` | `string` | `'Loading...'` | Message while loading async data |
|
|
103
|
+
| `min-search-length` | `number` | `0` | Minimum search length for async |
|
|
104
|
+
| `initial-values` | `string` (JSON array) | - | Pre-selected values |
|
|
105
|
+
|
|
106
|
+
## Properties
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Get/set options
|
|
110
|
+
multiselect.options = [
|
|
111
|
+
{ value: 'js', label: 'JavaScript' },
|
|
112
|
+
{ value: 'ts', label: 'TypeScript' }
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
// Async data loading
|
|
116
|
+
multiselect.onSearch = async (searchTerm) => {
|
|
117
|
+
const response = await fetch(`/api/search?q=${searchTerm}`);
|
|
118
|
+
return await response.json();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Event callbacks
|
|
122
|
+
multiselect.onSelect = (option) => {
|
|
123
|
+
console.log('Selected:', option);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
multiselect.onDeselect = (option) => {
|
|
127
|
+
console.log('Deselected:', option);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
multiselect.onChange = (selectedOptions) => {
|
|
131
|
+
console.log('Changed:', selectedOptions);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Pill tooltip customization
|
|
135
|
+
multiselect.getPillTooltipCallback = (item) => {
|
|
136
|
+
return `${item.label} - ${item.subtitle}`;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Count pill i18n/pluralization
|
|
140
|
+
multiselect.getCountPillCallback = (count, moreCount) => {
|
|
141
|
+
if (moreCount !== undefined) {
|
|
142
|
+
return `+${moreCount} more`; // Partial mode badge
|
|
143
|
+
}
|
|
144
|
+
return `${count} selected`; // Count mode display
|
|
145
|
+
};
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Methods
|
|
149
|
+
|
|
150
|
+
| Method | Description |
|
|
151
|
+
|--------|-------------|
|
|
152
|
+
| `getSelected()` | Get currently selected options |
|
|
153
|
+
| `setSelected(values: string[])` | Set selected values |
|
|
154
|
+
| `destroy()` | Clean up and destroy instance |
|
|
155
|
+
|
|
156
|
+
## Events
|
|
157
|
+
|
|
158
|
+
| Event | Detail | Description |
|
|
159
|
+
|-------|--------|-------------|
|
|
160
|
+
| `select` | `{ option, selectedOptions }` | Fired when an option is selected |
|
|
161
|
+
| `deselect` | `{ option, selectedOptions }` | Fired when an option is deselected |
|
|
162
|
+
| `change` | `{ selectedOptions, selectedValues }` | Fired when selection changes |
|
|
163
|
+
|
|
164
|
+
## Keyboard Shortcuts
|
|
165
|
+
|
|
166
|
+
- **↑ ↓** - Navigate up/down through options
|
|
167
|
+
- **Enter** - Select focused option
|
|
168
|
+
- **Escape** - Close dropdown
|
|
169
|
+
- **Tab** - Close dropdown and move to next field
|
|
170
|
+
- **Type** - Filter options by search term
|
|
171
|
+
|
|
172
|
+
## Advanced Features
|
|
173
|
+
|
|
174
|
+
### Rich Content with Icons
|
|
175
|
+
|
|
176
|
+
Icons support multiple formats - emojis, SVG markup, Font Awesome, images, or any HTML:
|
|
177
|
+
|
|
178
|
+
```html
|
|
179
|
+
<multi-select id="frameworks"></multi-select>
|
|
180
|
+
|
|
181
|
+
<script type="module">
|
|
182
|
+
const select = document.getElementById('frameworks');
|
|
183
|
+
select.options = [
|
|
184
|
+
{
|
|
185
|
+
value: 'react',
|
|
186
|
+
label: 'React',
|
|
187
|
+
icon: '⚛️', // Emoji
|
|
188
|
+
subtitle: 'A JavaScript library for building user interfaces'
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
value: 'vue',
|
|
192
|
+
label: 'Vue.js',
|
|
193
|
+
icon: '<svg viewBox="0 0 24 24"><path d="M2 3l10 18L22 3h-4l-6 10.5L6 3H2z"/></svg>', // SVG
|
|
194
|
+
subtitle: 'The Progressive JavaScript Framework'
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
value: 'angular',
|
|
198
|
+
label: 'Angular',
|
|
199
|
+
icon: '<i class="fab fa-angular"></i>', // Font Awesome
|
|
200
|
+
subtitle: 'Platform for building mobile and desktop apps'
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
value: 'svelte',
|
|
204
|
+
label: 'Svelte',
|
|
205
|
+
icon: '<img src="svelte-logo.png" alt="Svelte" />', // Image
|
|
206
|
+
subtitle: 'Cybernetically enhanced web apps'
|
|
207
|
+
}
|
|
208
|
+
];
|
|
209
|
+
</script>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Grouped Options
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
select.options = [
|
|
216
|
+
{ value: 'js', label: 'JavaScript', group: 'Frontend' },
|
|
217
|
+
{ value: 'ts', label: 'TypeScript', group: 'Frontend' },
|
|
218
|
+
{ value: 'python', label: 'Python', group: 'Backend' },
|
|
219
|
+
{ value: 'java', label: 'Java', group: 'Backend' }
|
|
220
|
+
];
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Async Data Loading
|
|
224
|
+
|
|
225
|
+
```html
|
|
226
|
+
<multi-select
|
|
227
|
+
id="async-select"
|
|
228
|
+
min-search-length="2"
|
|
229
|
+
loading-message="Searching..."
|
|
230
|
+
empty-message="No products found">
|
|
231
|
+
</multi-select>
|
|
232
|
+
|
|
233
|
+
<script type="module">
|
|
234
|
+
const select = document.getElementById('async-select');
|
|
235
|
+
|
|
236
|
+
select.onSearch = async (searchTerm) => {
|
|
237
|
+
const response = await fetch(`/api/products?q=${searchTerm}`);
|
|
238
|
+
const data = await response.json();
|
|
239
|
+
return data.products;
|
|
240
|
+
};
|
|
241
|
+
</script>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Display Modes
|
|
245
|
+
|
|
246
|
+
Perfect for different use cases and space constraints:
|
|
247
|
+
|
|
248
|
+
```html
|
|
249
|
+
<!-- Pills mode (default) - Show all selections as removable pills -->
|
|
250
|
+
<multi-select pills-display-mode="pills"></multi-select>
|
|
251
|
+
|
|
252
|
+
<!-- Count mode - Show only count badge -->
|
|
253
|
+
<multi-select pills-display-mode="count" show-count-badge="true"></multi-select>
|
|
254
|
+
|
|
255
|
+
<!-- Compact mode - Show first item + count -->
|
|
256
|
+
<multi-select pills-display-mode="compact"></multi-select>
|
|
257
|
+
|
|
258
|
+
<!-- Auto-switch from pills to count at threshold -->
|
|
259
|
+
<multi-select
|
|
260
|
+
pills-threshold="3"
|
|
261
|
+
pills-threshold-mode="count"
|
|
262
|
+
show-count-badge="true">
|
|
263
|
+
</multi-select>
|
|
264
|
+
|
|
265
|
+
<!-- Partial mode - Show limited pills + "+X more" badge -->
|
|
266
|
+
<multi-select
|
|
267
|
+
pills-threshold="5"
|
|
268
|
+
pills-threshold-mode="partial"
|
|
269
|
+
pills-max-visible="3">
|
|
270
|
+
</multi-select>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Pills Positioning
|
|
274
|
+
|
|
275
|
+
Control where selected item badges appear:
|
|
276
|
+
|
|
277
|
+
```html
|
|
278
|
+
<!-- Pills below input (default) -->
|
|
279
|
+
<multi-select pills-position="bottom"></multi-select>
|
|
280
|
+
|
|
281
|
+
<!-- Pills above input -->
|
|
282
|
+
<multi-select pills-position="top"></multi-select>
|
|
283
|
+
|
|
284
|
+
<!-- Pills to the left (RTL) -->
|
|
285
|
+
<multi-select pills-position="left"></multi-select>
|
|
286
|
+
|
|
287
|
+
<!-- Pills to the right (LTR) -->
|
|
288
|
+
<multi-select pills-position="right"></multi-select>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Pill Tooltips
|
|
292
|
+
|
|
293
|
+
Enable tooltips on selected item pills with customizable placement and delay:
|
|
294
|
+
|
|
295
|
+
```html
|
|
296
|
+
<!-- Basic tooltips -->
|
|
297
|
+
<multi-select
|
|
298
|
+
enable-pill-tooltips="true"
|
|
299
|
+
pill-tooltip-placement="top">
|
|
300
|
+
</multi-select>
|
|
301
|
+
|
|
302
|
+
<!-- Fast tooltips with custom delay -->
|
|
303
|
+
<multi-select
|
|
304
|
+
enable-pill-tooltips="true"
|
|
305
|
+
pill-tooltip-delay="100">
|
|
306
|
+
</multi-select>
|
|
307
|
+
|
|
308
|
+
<script type="module">
|
|
309
|
+
const select = document.querySelector('multi-select');
|
|
310
|
+
|
|
311
|
+
// Custom tooltip content
|
|
312
|
+
select.getPillTooltipCallback = (item) => {
|
|
313
|
+
return `${item.label} - ${item.subtitle}`;
|
|
314
|
+
};
|
|
315
|
+
</script>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Internationalization (i18n)
|
|
319
|
+
|
|
320
|
+
Customize count pill text for proper pluralization and localization:
|
|
321
|
+
|
|
322
|
+
```html
|
|
323
|
+
<multi-select
|
|
324
|
+
id="i18n-select"
|
|
325
|
+
pills-threshold="5"
|
|
326
|
+
pills-threshold-mode="partial"
|
|
327
|
+
pills-max-visible="3">
|
|
328
|
+
</multi-select>
|
|
329
|
+
|
|
330
|
+
<script type="module">
|
|
331
|
+
const select = document.getElementById('i18n-select');
|
|
332
|
+
|
|
333
|
+
// Spanish pluralization example
|
|
334
|
+
select.getCountPillCallback = (count, moreCount) => {
|
|
335
|
+
if (moreCount !== undefined) {
|
|
336
|
+
// Partial mode: "+X more" badge
|
|
337
|
+
return moreCount === 1 ? '+1 más' : `+${moreCount} más`;
|
|
338
|
+
}
|
|
339
|
+
// Count mode: total count
|
|
340
|
+
return count === 1 ? '1 elemento seleccionado' : `${count} elementos seleccionados`;
|
|
341
|
+
};
|
|
342
|
+
</script>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Disabled Options
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
select.options = [
|
|
349
|
+
{ value: 'basic', label: 'Basic License', subtitle: 'Free forever' },
|
|
350
|
+
{ value: 'pro', label: 'Pro License', subtitle: 'Available for purchase' },
|
|
351
|
+
{
|
|
352
|
+
value: 'enterprise',
|
|
353
|
+
label: 'Enterprise License',
|
|
354
|
+
subtitle: 'Contact sales',
|
|
355
|
+
disabled: true
|
|
356
|
+
}
|
|
357
|
+
];
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Option Structure
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
interface MultiSelectOption {
|
|
364
|
+
value: string; // Required: Unique identifier
|
|
365
|
+
label: string; // Required: Display text
|
|
366
|
+
icon?: string; // Optional: Icon or emoji
|
|
367
|
+
subtitle?: string; // Optional: Subtitle/description
|
|
368
|
+
group?: string; // Optional: Group name
|
|
369
|
+
disabled?: boolean; // Optional: Disable selection
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Styling
|
|
374
|
+
|
|
375
|
+
The component uses Shadow DOM for style encapsulation, but exposes CSS custom properties (CSS variables) that you can override to customize the appearance.
|
|
376
|
+
|
|
377
|
+
### CSS Variables (No Build System Required)
|
|
378
|
+
|
|
379
|
+
You can customize the component using CSS variables even with just a `<script>` tag:
|
|
380
|
+
|
|
381
|
+
```html
|
|
382
|
+
<style>
|
|
383
|
+
/* Override tooltip appearance */
|
|
384
|
+
multi-select {
|
|
385
|
+
--ml-tooltip-bg: #1f2937;
|
|
386
|
+
--ml-tooltip-color: #f9fafb;
|
|
387
|
+
--ml-tooltip-padding: 0.625rem 0.875rem;
|
|
388
|
+
--ml-tooltip-border-radius: 0.5rem;
|
|
389
|
+
--ml-tooltip-font-size: 0.8125rem;
|
|
390
|
+
--ml-tooltip-max-width: 24rem;
|
|
391
|
+
--ml-tooltip-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
|
392
|
+
--ml-tooltip-z-index: 10000;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* Override "+X more" badge colors */
|
|
396
|
+
multi-select {
|
|
397
|
+
--ml-more-badge-bg: #dbeafe;
|
|
398
|
+
--ml-more-badge-hover-bg: #bfdbfe;
|
|
399
|
+
--ml-more-badge-active-bg: #93c5fd;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/* Size the component */
|
|
403
|
+
multi-select {
|
|
404
|
+
width: 100%;
|
|
405
|
+
max-width: 400px;
|
|
406
|
+
}
|
|
407
|
+
</style>
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Available CSS Variables
|
|
411
|
+
|
|
412
|
+
All 211 SCSS variables are exposed as CSS custom properties with fallbacks. Below are the most commonly customized variables organized by category. For the complete list, see [_multiselect.scss](./src/scss/_multiselect.scss).
|
|
413
|
+
|
|
414
|
+
#### Colors
|
|
415
|
+
|
|
416
|
+
| Variable | Default | Description |
|
|
417
|
+
|----------|---------|-------------|
|
|
418
|
+
| `--ml-accent-color` | `#3b82f6` | Primary accent color (blue) |
|
|
419
|
+
| `--ml-accent-color-hover` | `#2563eb` | Accent color on hover |
|
|
420
|
+
| `--ml-accent-color-active` | `#1d4ed8` | Accent color when active |
|
|
421
|
+
| `--ml-text-primary` | `#111827` | Primary text color |
|
|
422
|
+
| `--ml-text-secondary` | `#6b7280` | Secondary/muted text color |
|
|
423
|
+
| `--ml-border-color` | `#e5e7eb` | Default border color |
|
|
424
|
+
|
|
425
|
+
#### Input Component
|
|
426
|
+
|
|
427
|
+
| Variable | Default | Description |
|
|
428
|
+
|----------|---------|-------------|
|
|
429
|
+
| `--ml-input-bg` | `#ffffff` | Input background |
|
|
430
|
+
| `--ml-input-text` | `#111827` | Input text color |
|
|
431
|
+
| `--ml-input-border` | `#d1d5db` | Input border color |
|
|
432
|
+
| `--ml-input-focus-border-color` | `#3b82f6` | Border color when focused |
|
|
433
|
+
| `--ml-input-padding-v` | `0.5rem` | Input vertical padding |
|
|
434
|
+
| `--ml-input-padding-h` | `0.75rem` | Input horizontal padding |
|
|
435
|
+
| `--ml-input-font-size` | `0.875rem` | Input font size |
|
|
436
|
+
| `--ml-input-border-radius` | `0.375rem` | Input border radius |
|
|
437
|
+
| `--ml-input-placeholder-color` | `#6b7280` | Placeholder text color |
|
|
438
|
+
|
|
439
|
+
#### Dropdown & Options
|
|
440
|
+
|
|
441
|
+
| Variable | Default | Description |
|
|
442
|
+
|----------|---------|-------------|
|
|
443
|
+
| `--ml-dropdown-bg` | `#ffffff` | Dropdown background |
|
|
444
|
+
| `--ml-dropdown-border` | `#e5e7eb` | Dropdown border color |
|
|
445
|
+
| `--ml-dropdown-shadow` | (box shadow) | Dropdown shadow |
|
|
446
|
+
| `--ml-dropdown-max-height` | `20rem` | Max height of dropdown |
|
|
447
|
+
| `--ml-option-padding-v` | `0.5rem` | Option vertical padding |
|
|
448
|
+
| `--ml-option-padding-h` | `0.75rem` | Option horizontal padding |
|
|
449
|
+
| `--ml-option-hover-bg` | `#f9fafb` | Option background on hover |
|
|
450
|
+
| `--ml-option-bg-selected` | (rgba accent) | Selected option background |
|
|
451
|
+
|
|
452
|
+
#### Pills & Badges
|
|
453
|
+
|
|
454
|
+
| Variable | Default | Description |
|
|
455
|
+
|----------|---------|-------------|
|
|
456
|
+
| `--ml-pill-bg` | `#eff6ff` | Pill background color |
|
|
457
|
+
| `--ml-pill-text-color` | `#3b82f6` | Pill text color |
|
|
458
|
+
| `--ml-pill-gap` | `0.5rem` | Gap between pills |
|
|
459
|
+
| `--ml-pill-height` | `1.5rem` | Height of pills |
|
|
460
|
+
| `--ml-pill-font-size` | `0.75rem` | Pill font size |
|
|
461
|
+
| `--ml-pill-border-radius` | `0.375rem` | Pill border radius |
|
|
462
|
+
| `--ml-pill-remove-bg` | `#3b82f6` | Remove button background |
|
|
463
|
+
| `--ml-pill-remove-color` | `#ffffff` | Remove button color |
|
|
464
|
+
| `--ml-more-badge-bg` | (pill background) | "+X more" badge background |
|
|
465
|
+
| `--ml-more-badge-hover-bg` | `#ffffff` | "+X more" badge hover |
|
|
466
|
+
| `--ml-more-badge-active-bg` | `#e0f2fe` | "+X more" badge active |
|
|
467
|
+
|
|
468
|
+
#### Count Badge (in input)
|
|
469
|
+
|
|
470
|
+
| Variable | Default | Description |
|
|
471
|
+
|----------|---------|-------------|
|
|
472
|
+
| `--ml-count-badge-bg` | `#3b82f6` | Count badge background |
|
|
473
|
+
| `--ml-count-badge-color` | `#ffffff` | Count badge text color |
|
|
474
|
+
| `--ml-count-badge-font-size` | `0.75rem` | Count badge font size |
|
|
475
|
+
| `--ml-count-badge-bg-hover` | `#2563eb` | Hover background color |
|
|
476
|
+
|
|
477
|
+
#### Tooltips
|
|
478
|
+
|
|
479
|
+
| Variable | Default | Description |
|
|
480
|
+
|----------|---------|-------------|
|
|
481
|
+
| `--ml-tooltip-bg` | `#333` | Tooltip background color |
|
|
482
|
+
| `--ml-tooltip-color` | `#fff` | Tooltip text color |
|
|
483
|
+
| `--ml-tooltip-padding` | `0.5rem 0.75rem` | Tooltip padding |
|
|
484
|
+
| `--ml-tooltip-border-radius` | `0.375rem` | Tooltip border radius |
|
|
485
|
+
| `--ml-tooltip-font-size` | `0.875rem` | Tooltip font size |
|
|
486
|
+
| `--ml-tooltip-max-width` | `20rem` | Tooltip maximum width |
|
|
487
|
+
| `--ml-tooltip-shadow` | (box shadow) | Tooltip box shadow |
|
|
488
|
+
| `--ml-tooltip-z-index` | `10000` | Tooltip z-index |
|
|
489
|
+
|
|
490
|
+
#### Typography
|
|
491
|
+
|
|
492
|
+
| Variable | Default | Description |
|
|
493
|
+
|----------|---------|-------------|
|
|
494
|
+
| `--ml-font-size-xs` | `0.75rem` | Extra small font size |
|
|
495
|
+
| `--ml-font-size-sm` | `0.875rem` | Small font size |
|
|
496
|
+
| `--ml-font-size-base` | `1rem` | Base font size |
|
|
497
|
+
| `--ml-font-weight-medium` | `500` | Medium font weight |
|
|
498
|
+
| `--ml-font-weight-semibold` | `600` | Semibold font weight |
|
|
499
|
+
|
|
500
|
+
#### Effects & Transitions
|
|
501
|
+
|
|
502
|
+
| Variable | Default | Description |
|
|
503
|
+
|----------|---------|-------------|
|
|
504
|
+
| `--ml-transition-fast` | `150ms` | Fast transition duration |
|
|
505
|
+
| `--ml-transition-normal` | `200ms` | Normal transition duration |
|
|
506
|
+
| `--ml-easing-snappy` | (cubic-bezier) | Snappy easing function |
|
|
507
|
+
| `--ml-shadow-md` | (box shadow) | Medium shadow |
|
|
508
|
+
| `--ml-shadow-xl` | (box shadow) | Extra large shadow |
|
|
509
|
+
| `--ml-disabled-opacity` | `0.5` | Opacity for disabled state |
|
|
510
|
+
|
|
511
|
+
### Advanced: Custom SCSS
|
|
512
|
+
|
|
513
|
+
For users with a build system, you can import and customize the SCSS:
|
|
514
|
+
|
|
515
|
+
```scss
|
|
516
|
+
// Import and override SCSS variables
|
|
517
|
+
@use '@keenmate/web-multiselect/scss' with (
|
|
518
|
+
$ml-primary: #10b981,
|
|
519
|
+
$ml-border-radius: 0.5rem,
|
|
520
|
+
$ml-font-size: 1rem
|
|
521
|
+
);
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## Browser Support
|
|
525
|
+
|
|
526
|
+
- Modern browsers with Web Components support
|
|
527
|
+
- Chrome/Edge 67+
|
|
528
|
+
- Firefox 63+
|
|
529
|
+
- Safari 10.1+
|
|
530
|
+
|
|
531
|
+
## Development
|
|
532
|
+
|
|
533
|
+
```bash
|
|
534
|
+
# Install dependencies
|
|
535
|
+
npm install
|
|
536
|
+
|
|
537
|
+
# Start dev server
|
|
538
|
+
npm run dev
|
|
539
|
+
|
|
540
|
+
# Build for production
|
|
541
|
+
npm run build
|
|
542
|
+
|
|
543
|
+
# Create package
|
|
544
|
+
npm run package
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
## License
|
|
548
|
+
|
|
549
|
+
Copyright (c) 2024 Keenmate
|
|
550
|
+
|
|
551
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
552
|
+
|
|
553
|
+
**What this means:**
|
|
554
|
+
- ✅ Free to use in commercial products
|
|
555
|
+
- ✅ Free to modify and distribute
|
|
556
|
+
- ✅ No licensing fees or restrictions
|
|
557
|
+
- ⚠️ Provided "as is" without warranty
|
|
558
|
+
- 📝 Must include copyright notice in copies
|
|
559
|
+
|
|
560
|
+
## Credits
|
|
561
|
+
|
|
562
|
+
Created by [Keenmate](https://github.com/keenmate) as part of the Pure Admin design system.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import './scss/main.scss';
|
|
2
|
+
import { MultiSelectElement } from './web-component';
|
|
3
|
+
export { MultiSelectElement };
|
|
4
|
+
export { PureMultiSelect } from './multiselect';
|
|
5
|
+
export type { MultiSelectOption, MultiSelectOptions, MultiSelectEventDetail, PillsDisplayMode, PillsPosition, PillsThresholdMode, SearchInputMode, ValueFormat } from './types';
|
|
6
|
+
import './web-component';
|
|
7
|
+
export interface GlobalMultiSelectAPI {
|
|
8
|
+
version: () => string;
|
|
9
|
+
config: {
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
author: string;
|
|
13
|
+
license: string;
|
|
14
|
+
repository: string;
|
|
15
|
+
homepage: string;
|
|
16
|
+
};
|
|
17
|
+
register: () => void;
|
|
18
|
+
getInstances: () => HTMLElement[];
|
|
19
|
+
}
|
|
20
|
+
declare global {
|
|
21
|
+
interface Window {
|
|
22
|
+
keenmate?: {
|
|
23
|
+
multiselect?: GlobalMultiSelectAPI;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|