@keenmate/web-multiselect 1.12.0-rc08 → 2.0.0-rc01
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 +31 -6
- package/custom-elements.json +6591 -0
- package/dist/index.d.ts +191 -298
- package/dist/multiselect.js +2409 -2141
- package/dist/multiselect.umd.js +23 -20
- package/docs/examples.md +2 -2
- package/docs/usage.md +396 -339
- package/package.json +16 -4
- package/vscode.css-custom-data.json +1615 -0
- package/vscode.html-custom-data.json +443 -0
- package/web-types.json +2113 -0
package/docs/usage.md
CHANGED
|
@@ -1,339 +1,396 @@
|
|
|
1
|
-
# Usage & API reference
|
|
2
|
-
|
|
3
|
-
This document covers the public surface of `<web-multiselect>` — declarative HTML usage, the full attribute / property / method / event tables, and the data shape.
|
|
4
|
-
|
|
5
|
-
## Declarative (no JavaScript)
|
|
6
|
-
|
|
7
|
-
Perfect for simple forms — just use standard HTML `<option>` and `<optgroup>` elements:
|
|
8
|
-
|
|
9
|
-
```html
|
|
10
|
-
<!-- Simple choice -->
|
|
11
|
-
<web-multiselect multiple="false">
|
|
12
|
-
<option value="yes">Yes</option>
|
|
13
|
-
<option value="no">No</option>
|
|
14
|
-
<option value="maybe" selected>Maybe</option>
|
|
15
|
-
</web-multiselect>
|
|
16
|
-
|
|
17
|
-
<!-- With icons -->
|
|
18
|
-
<web-multiselect>
|
|
19
|
-
<option value="apple" data-icon="🍎">Apple</option>
|
|
20
|
-
<option value="banana" data-icon="🍌" selected>Banana</option>
|
|
21
|
-
<option value="orange" data-icon="🍊">Orange</option>
|
|
22
|
-
</web-multiselect>
|
|
23
|
-
|
|
24
|
-
<!-- With groups -->
|
|
25
|
-
<web-multiselect>
|
|
26
|
-
<optgroup label="Frontend">
|
|
27
|
-
<option value="js" data-icon="🟨">JavaScript</option>
|
|
28
|
-
<option value="ts" data-icon="🔷">TypeScript</option>
|
|
29
|
-
</optgroup>
|
|
30
|
-
<optgroup label="Backend">
|
|
31
|
-
<option value="python" data-icon="🐍" selected>Python</option>
|
|
32
|
-
<option value="java" data-icon="☕">Java</option>
|
|
33
|
-
</optgroup>
|
|
34
|
-
</web-multiselect>
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
For
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
</web-multiselect>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
multiselect
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
|
118
|
-
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
| `search-
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
multiselect.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
multiselect
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
//
|
|
287
|
-
multiselect.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
multiselect.
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
1
|
+
# Usage & API reference
|
|
2
|
+
|
|
3
|
+
This document covers the public surface of `<web-multiselect>` — declarative HTML usage, the full attribute / property / method / event tables, and the data shape.
|
|
4
|
+
|
|
5
|
+
## Declarative (no JavaScript)
|
|
6
|
+
|
|
7
|
+
Perfect for simple forms — just use standard HTML `<option>` and `<optgroup>` elements:
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<!-- Simple choice -->
|
|
11
|
+
<web-multiselect multiple="false">
|
|
12
|
+
<option value="yes">Yes</option>
|
|
13
|
+
<option value="no">No</option>
|
|
14
|
+
<option value="maybe" selected>Maybe</option>
|
|
15
|
+
</web-multiselect>
|
|
16
|
+
|
|
17
|
+
<!-- With icons -->
|
|
18
|
+
<web-multiselect>
|
|
19
|
+
<option value="apple" data-icon="🍎">Apple</option>
|
|
20
|
+
<option value="banana" data-icon="🍌" selected>Banana</option>
|
|
21
|
+
<option value="orange" data-icon="🍊">Orange</option>
|
|
22
|
+
</web-multiselect>
|
|
23
|
+
|
|
24
|
+
<!-- With groups -->
|
|
25
|
+
<web-multiselect>
|
|
26
|
+
<optgroup label="Frontend">
|
|
27
|
+
<option value="js" data-icon="🟨">JavaScript</option>
|
|
28
|
+
<option value="ts" data-icon="🔷">TypeScript</option>
|
|
29
|
+
</optgroup>
|
|
30
|
+
<optgroup label="Backend">
|
|
31
|
+
<option value="python" data-icon="🐍" selected>Python</option>
|
|
32
|
+
<option value="java" data-icon="☕">Java</option>
|
|
33
|
+
</optgroup>
|
|
34
|
+
</web-multiselect>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Option data in HTML (`data-options`)
|
|
38
|
+
|
|
39
|
+
For simple static lists without `<option>` children or JavaScript, put the data
|
|
40
|
+
in the `data-options` attribute and pick a format with `data-options-format`:
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<!-- json (default): array of objects or [value, label] tuples -->
|
|
44
|
+
<web-multiselect
|
|
45
|
+
value-member="value" display-value-member="label"
|
|
46
|
+
data-options='[{"value":"js","label":"JavaScript"},{"value":"ts","label":"TypeScript"}]'>
|
|
47
|
+
</web-multiselect>
|
|
48
|
+
|
|
49
|
+
<!-- csv: first row is a header; map the columns via *-member -->
|
|
50
|
+
<web-multiselect
|
|
51
|
+
data-options-format="csv"
|
|
52
|
+
value-member="value" display-value-member="label"
|
|
53
|
+
data-options="value,label js,JavaScript ts,TypeScript">
|
|
54
|
+
</web-multiselect>
|
|
55
|
+
|
|
56
|
+
<!-- plain: comma/newline-separated bare values (value === label), no member config -->
|
|
57
|
+
<web-multiselect data-options-format="plain" data-options="Apple,Banana,Cherry"></web-multiselect>
|
|
58
|
+
|
|
59
|
+
<!-- custom delimiters: semicolon cells, pipe rows (single-line csv) -->
|
|
60
|
+
<web-multiselect
|
|
61
|
+
data-options-format="csv"
|
|
62
|
+
data-options-splitter=";" data-options-row-splitter="|"
|
|
63
|
+
value-member="value" display-value-member="label"
|
|
64
|
+
data-options="value;label|js;JavaScript|ts;TypeScript">
|
|
65
|
+
</web-multiselect>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`data-options-splitter` / `data-options-row-splitter` customize the `csv` and
|
|
69
|
+
`plain` delimiters (default `,` and newline); use `\t` for a tab (TSV). They're
|
|
70
|
+
ignored for `json`.
|
|
71
|
+
|
|
72
|
+
Both attributes are reactive — changing either re-renders. Declarative `<option>`
|
|
73
|
+
children and a `.options` property set in JS both take precedence over `data-options`.
|
|
74
|
+
|
|
75
|
+
## Programmatic (with JavaScript)
|
|
76
|
+
|
|
77
|
+
For dynamic data and advanced features:
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<!-- Multi-select -->
|
|
81
|
+
<web-multiselect
|
|
82
|
+
id="my-select"
|
|
83
|
+
search-placeholder="Search options..."
|
|
84
|
+
initial-values='["js","ts"]'>
|
|
85
|
+
</web-multiselect>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// Import the component (includes styles)
|
|
90
|
+
import '@keenmate/web-multiselect';
|
|
91
|
+
|
|
92
|
+
// Or import styles separately if needed
|
|
93
|
+
import '@keenmate/web-multiselect/style.css';
|
|
94
|
+
|
|
95
|
+
const multiselect = document.querySelector('web-multiselect');
|
|
96
|
+
|
|
97
|
+
// Set options programmatically
|
|
98
|
+
multiselect.options = [
|
|
99
|
+
{ value: 'js', label: 'JavaScript', icon: '🟨' },
|
|
100
|
+
{ value: 'ts', label: 'TypeScript', icon: '🔷' },
|
|
101
|
+
{ value: 'py', label: 'Python', icon: '🐍' }
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
// Listen for events
|
|
105
|
+
multiselect.addEventListener('change', (e) => {
|
|
106
|
+
console.log('Selected:', e.detail.selectedOptions);
|
|
107
|
+
console.log('Values:', e.detail.selectedValues);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Public API
|
|
111
|
+
const selected = multiselect.getSelected();
|
|
112
|
+
multiselect.setSelected(['js', 'ts']);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Attributes
|
|
116
|
+
|
|
117
|
+
| Attribute | Type | Default | Description |
|
|
118
|
+
|-----------|------|---------|-------------|
|
|
119
|
+
| `multiple` | `boolean` | `true` | Allow multiple selections |
|
|
120
|
+
| `search-placeholder` | `string` | `'Search...'` | Placeholder text shown while search is usable |
|
|
121
|
+
| `select-placeholder` | `string` | `'Pick an option...'` | Placeholder shown when search is disabled (`enable-search="false"`, or `search-input-mode` `readonly`/`hidden`) — the input acts as a picker, not a search box |
|
|
122
|
+
| `no-data-placeholder` | `string` | - | Opt-in placeholder shown when the option list is empty, so users see there's no data without opening. Highest priority when the list is empty. Useful for cascade multiselects (a child whose parent isn't resolved yet) |
|
|
123
|
+
| `search-hint` | `string` | - | Hint text shown above input when focused |
|
|
124
|
+
| `allow-groups` | `boolean` | `true` | Enable option grouping |
|
|
125
|
+
| `show-checkboxes` | `boolean` | `true` | Show checkboxes next to options |
|
|
126
|
+
| `close-on-select` | `boolean` | `false` | Close dropdown after selecting |
|
|
127
|
+
| `dropdown-min-width` | `string` | - | Min width for dropdown (e.g., `'20rem'`) |
|
|
128
|
+
| `badges-display-mode` | `'pills' \| 'count' \| 'compact' \| 'partial' \| 'none'` | `'pills'` | How to display selected items. `compact`: first item + count. `none`: no display |
|
|
129
|
+
| `badges-threshold` | `number` | - | Auto-switch mode when exceeded (see `badges-threshold-mode`) |
|
|
130
|
+
| `badges-threshold-mode` | `'count' \| 'partial'` | `'count'` | Mode after threshold: `count` shows badge, `partial` shows limited badges + more badge |
|
|
131
|
+
| `badges-max-visible` | `number` | `3` | Max badges shown in partial mode |
|
|
132
|
+
| `badges-position` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | Position of badges container |
|
|
133
|
+
| `show-counter` | `boolean` | `false` | Show `[3]` badge next to toggle icon |
|
|
134
|
+
| `enable-badge-tooltips` | `boolean` | `false` | Enable tooltips on selected badges |
|
|
135
|
+
| `badge-tooltip-placement` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'top'` | Tooltip placement relative to badge |
|
|
136
|
+
| `badge-tooltip-delay` | `number` | `100` | Delay in ms before showing tooltip |
|
|
137
|
+
| `badge-tooltip-offset` | `number` | `8` | Distance in pixels between badge and tooltip |
|
|
138
|
+
| `enable-option-tooltips` | `boolean` | `false` | Enable hover tooltips on dropdown options |
|
|
139
|
+
| `option-tooltip-placement` | `'top' \| 'top-start' \| … \| 'left' \| 'right'` | `'top-start'` | Option tooltip placement (anchored to the row's start edge by default; use `left`/`right` for the start/end side on a narrow control) |
|
|
140
|
+
| `option-tooltip-delay` | `number` | inherits `badge-tooltip-delay`, then `100` | Delay in ms before showing an option tooltip |
|
|
141
|
+
| `option-tooltip-offset` | `number` | inherits `badge-tooltip-offset`, then `8` | Distance in pixels between row and option tooltip |
|
|
142
|
+
| `option-tooltip-follow-cursor` | `boolean` | `false` | Anchor the option tooltip to the mouse pointer and follow it across the row (best for full-width rows) |
|
|
143
|
+
| `max-height` | `string` | `'20rem'` | Maximum height of dropdown |
|
|
144
|
+
| `empty-message` | `string` | `'No results found'` | Message when no options found |
|
|
145
|
+
| `loading-message` | `string` | `'Loading...'` | Message while loading async data |
|
|
146
|
+
| `min-search-length` | `number` | `0` | Minimum search length for async |
|
|
147
|
+
| `search-debounce` | `number` | `0` | Debounce (ms) before the async `searchCallback` fires; coalesces keystroke bursts into one request. Resets on each keystroke. Async callback only — local filtering stays instant. `0` = no debounce |
|
|
148
|
+
| `keep-options-on-search` | `boolean` | `true` | Keep initial options visible when `searchCallback` is active (hybrid search) |
|
|
149
|
+
| `should-keep-search-on-close` | `boolean` | `true` | Preserve search text and filtered results when dropdown closes |
|
|
150
|
+
| `sticky-actions` | `boolean` | `true` | Keep the action-buttons block pinned to its edge while scrolling |
|
|
151
|
+
| `actions-layout` | `'nowrap' \| 'wrap'` | `'nowrap'` | Whether buttons in a row wrap: `nowrap` (single line) or `wrap` |
|
|
152
|
+
| `actions-position` | `'top' \| 'bottom'` | `'top'` | Place the actions block at the top or bottom (sticky footer) of the dropdown |
|
|
153
|
+
| `actions-align` | `'stretch' \| 'left' \| 'right' \| 'center' \| 'space-between'` | `'stretch'` | Horizontal arrangement of buttons within a row (`stretch` = full-width) |
|
|
154
|
+
| `lock-placement` | `boolean` | `true` | Lock dropdown placement after first open to prevent flipping |
|
|
155
|
+
| `enable-search` | `boolean` | `true` | Enable/disable search functionality |
|
|
156
|
+
| `search-input-mode` | `'normal' \| 'readonly' \| 'hidden'` | `'normal'` | Search input display mode |
|
|
157
|
+
| `search-mode` | `'filter' \| 'navigate'` | `'filter'` | Search behavior: `filter` hides non-matches, `navigate` jumps to matches |
|
|
158
|
+
| `allow-add-new` | `boolean` | `false` | Allow adding new options not in the list |
|
|
159
|
+
| `value-member` | `string` | - | Property name for value/ID extraction from custom objects |
|
|
160
|
+
| `display-value-member` | `string` | - | Property name for display text extraction from custom objects |
|
|
161
|
+
| `search-value-member` | `string` | - | Property name for search text extraction from custom objects |
|
|
162
|
+
| `icon-member` | `string` | - | Property name for icon extraction from custom objects |
|
|
163
|
+
| `subtitle-member` | `string` | - | Property name for subtitle extraction from custom objects |
|
|
164
|
+
| `group-member` | `string` | - | Property name for group extraction from custom objects |
|
|
165
|
+
| `disabled-member` | `string` | - | Property name for disabled state extraction from custom objects |
|
|
166
|
+
| `name` | `string` | - | HTML form field name for form integration (creates hidden input) |
|
|
167
|
+
| `value-format` | `'json' \| 'csv' \| 'array'` | `'json'` | Format for form value serialization |
|
|
168
|
+
| `initial-values` | `string` (JSON array or CSV) | - | Pre-selected values. Accepts `["js","ts"]` or a bare `js,ts` |
|
|
169
|
+
| `data-options` | `string` | - | HTML-authoring source for the option list, parsed per `data-options-format`. Prefer the `options` property in JS. Reactive; declarative `<option>` children and a set `options` property both take precedence |
|
|
170
|
+
| `data-options-format` | `'json' \| 'csv' \| 'plain'` | `'json'` | How to parse `data-options`: `json` (array of objects or `[value,label]` tuples), `csv` (first row = header → object per row, keyed by the header cells; map columns via `*-member`), or `plain` (bare values → `value=label` options) |
|
|
171
|
+
| `data-options-splitter` | `string` | `','` | Field/cell delimiter for the `csv` and `plain` formats (e.g. `;`, `\|`). Escapes `\t` `\n` `\r` are honoured (`"\t"` → TSV). Ignored for `json` |
|
|
172
|
+
| `data-options-row-splitter` | `string` | newline | Row/record delimiter for the `csv` and `plain` formats (e.g. `;` for single-line data). Escapes honoured. Ignored for `json` |
|
|
173
|
+
| `enable-virtual-scroll` | `boolean` | `false` | Enable virtual scrolling for large datasets |
|
|
174
|
+
| `virtual-scroll-threshold` | `number` | `100` | Minimum items before virtual scroll activates |
|
|
175
|
+
| `option-height` | `number` | `50` | Fixed height for each option in pixels (required for virtual scroll) |
|
|
176
|
+
| `virtual-scroll-buffer` | `number` | `10` | Buffer size — extra items rendered above/below viewport |
|
|
177
|
+
|
|
178
|
+
## Properties
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// Get/set options
|
|
182
|
+
multiselect.options = [
|
|
183
|
+
{ value: 'js', label: 'JavaScript' },
|
|
184
|
+
{ value: 'ts', label: 'TypeScript' }
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
// Async data loading. `signal` aborts when a newer search supersedes this one —
|
|
188
|
+
// forward it to fetch to cancel the stale request.
|
|
189
|
+
multiselect.searchCallback = async (searchTerm, signal) => {
|
|
190
|
+
const response = await fetch(`/api/search?q=${searchTerm}`, { signal });
|
|
191
|
+
return await response.json();
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Pre-process search terms before calling searchCallback
|
|
195
|
+
multiselect.beforeSearchCallback = (searchTerm) => {
|
|
196
|
+
// Remove accents: "café" → "cafe"
|
|
197
|
+
const normalized = searchTerm.normalize('NFD').replace(/[̀-ͯ]/g, '');
|
|
198
|
+
|
|
199
|
+
// Block search if too short (return null to prevent search)
|
|
200
|
+
if (normalized.length < 2) return null;
|
|
201
|
+
|
|
202
|
+
return normalized; // Return transformed term
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Interceptors — veto a selection/deselection before it happens.
|
|
206
|
+
// Return false to block; return true/undefined to allow. Silent (no event).
|
|
207
|
+
// Receives the option being toggled and the current selection (before the change).
|
|
208
|
+
// Note: programmatic setSelected() and the Select-All / Clear-All buttons bypass these.
|
|
209
|
+
multiselect.beforeSelectCallback = (option, selectedOptions) => {
|
|
210
|
+
// Example: don't allow selecting "item1" while "item2" is already selected
|
|
211
|
+
if (option.value === 'item1' && selectedOptions.some(o => o.value === 'item2')) {
|
|
212
|
+
return false; // blocked
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
multiselect.beforeDeselectCallback = (option, selectedOptions) => {
|
|
217
|
+
// Example: "item1" is required — can't be removed once chosen
|
|
218
|
+
if (option.value === 'item1') return false;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Event handler properties. Since v2 these are real listeners: each receives
|
|
222
|
+
// the same CustomEvent addEventListener('select', ...) would get, so read the
|
|
223
|
+
// payload off `e.detail` — NOT as a bare argument.
|
|
224
|
+
multiselect.onSelect = (e) => {
|
|
225
|
+
console.log('Selected:', e.detail.option);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
multiselect.onDeselect = (e) => {
|
|
229
|
+
console.log('Deselected:', e.detail.option);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
multiselect.onChange = (e) => {
|
|
233
|
+
console.log('Changed:', e.detail.selectedOptions, e.detail.selectedValues);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Badge display customization (show different text in badges vs dropdown)
|
|
237
|
+
multiselect.getBadgeDisplayCallback = (item) => {
|
|
238
|
+
// Show shorter text in badges (e.g., just name instead of "name (email)")
|
|
239
|
+
return item.name; // Dropdown might show "John Doe (john@example.com)"
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Badge tooltip customization
|
|
243
|
+
multiselect.getBadgeTooltipCallback = (item) => {
|
|
244
|
+
return `${item.label} - ${item.subtitle}`;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Option (dropdown row) tooltip customization — requires enable-option-tooltips
|
|
248
|
+
multiselect.getOptionTooltipCallback = (item) => {
|
|
249
|
+
return `${item.label} — ${item.description}`;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Action buttons (Select All, Clear All, custom actions)
|
|
253
|
+
multiselect.actionButtons = [
|
|
254
|
+
{
|
|
255
|
+
action: 'select-all',
|
|
256
|
+
text: 'Select All',
|
|
257
|
+
tooltip: 'Select all items',
|
|
258
|
+
cssClass: 'my-custom-class',
|
|
259
|
+
getIsVisibleCallback: (multiselect) => multiselect.getSelected().length < 5 // Hide if 5+ selected
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
action: 'clear-all',
|
|
263
|
+
text: 'Clear All',
|
|
264
|
+
tooltip: 'Clear selection',
|
|
265
|
+
isVisible: true, // Static visibility
|
|
266
|
+
isDisabled: false // Static disabled state
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
action: 'custom',
|
|
270
|
+
text: 'Invert',
|
|
271
|
+
tooltip: 'Invert selection',
|
|
272
|
+
onClick: (multiselect) => {
|
|
273
|
+
// Custom action - invert selection
|
|
274
|
+
const allValues = multiselect.options.map(opt => opt.value);
|
|
275
|
+
const selectedValues = multiselect.getValue();
|
|
276
|
+
const inverted = allValues.filter(v => !selectedValues.includes(v));
|
|
277
|
+
multiselect.setSelected(inverted);
|
|
278
|
+
},
|
|
279
|
+
// Dynamic callbacks (take priority over static properties)
|
|
280
|
+
getIsDisabledCallback: (multiselect) => multiselect.getSelected().length === 0,
|
|
281
|
+
getTextCallback: (multiselect) => multiselect.getSelected().length > 0 ? 'Invert' : 'Select Items First',
|
|
282
|
+
getClassCallback: (multiselect) => multiselect.getSelected().length > 0 ? 'active' : 'inactive'
|
|
283
|
+
}
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
// Counter i18n/pluralization
|
|
287
|
+
multiselect.getCounterCallback = (count, moreCount) => {
|
|
288
|
+
if (moreCount !== undefined) {
|
|
289
|
+
return `+${moreCount} more`; // Partial mode badge
|
|
290
|
+
}
|
|
291
|
+
return `${count} selected`; // Count mode display
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// Data extraction - Member properties (for simple property names)
|
|
295
|
+
multiselect.valueMember = 'id';
|
|
296
|
+
multiselect.displayValueMember = 'name';
|
|
297
|
+
multiselect.iconMember = 'icon';
|
|
298
|
+
multiselect.subtitleMember = 'description';
|
|
299
|
+
multiselect.groupMember = 'category';
|
|
300
|
+
multiselect.disabledMember = 'isDisabled';
|
|
301
|
+
|
|
302
|
+
// Data extraction - Callback functions (for complex logic)
|
|
303
|
+
multiselect.getValueCallback = (item) => item.id || item.value;
|
|
304
|
+
multiselect.getDisplayValueCallback = (item) => item.label || item.name;
|
|
305
|
+
multiselect.getSearchValueCallback = (item) => `${item.name} ${item.tags.join(' ')}`;
|
|
306
|
+
multiselect.getIconCallback = (item) => item.icon || '📄';
|
|
307
|
+
multiselect.getSubtitleCallback = (item) => `${item.price} - ${item.stock} in stock`;
|
|
308
|
+
multiselect.getGroupCallback = (item) => item.category;
|
|
309
|
+
multiselect.getDisabledCallback = (item) => item.stock === 0;
|
|
310
|
+
|
|
311
|
+
// Custom rendering - Full HTML control
|
|
312
|
+
multiselect.renderGroupLabelContentCallback = (groupName) => {
|
|
313
|
+
return `<strong>📦 ${groupName.toUpperCase()}</strong>`;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
multiselect.renderOptionContentCallback = (item, context) => {
|
|
317
|
+
return `<strong>${item.name}</strong> <span class="badge">${item.status}</span>`;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
multiselect.renderBadgeContentCallback = (item, context) => {
|
|
321
|
+
return context.isInPopover
|
|
322
|
+
? `${item.icon} ${item.name} - ${item.description}`
|
|
323
|
+
: `${item.icon} ${item.name}`;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
multiselect.renderSelectedContentCallback = (item) => {
|
|
327
|
+
// Customize selected item text in single-select mode (plain text only)
|
|
328
|
+
return item.firstName;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// Form integration
|
|
332
|
+
multiselect.name = 'selected_items';
|
|
333
|
+
multiselect.valueFormat = 'json'; // 'json' | 'csv' | 'array'
|
|
334
|
+
multiselect.getValueFormatCallback = (values) => values.join('|'); // Custom format
|
|
335
|
+
|
|
336
|
+
// Read-only properties
|
|
337
|
+
const selectedValue = multiselect.selectedValue; // string | number | array | null
|
|
338
|
+
const selectedItem = multiselect.selectedItem; // First selected item object
|
|
339
|
+
|
|
340
|
+
// Add new option callback
|
|
341
|
+
multiselect.addNewCallback = async (value) => {
|
|
342
|
+
const newOption = await fetch('/api/options', {
|
|
343
|
+
method: 'POST',
|
|
344
|
+
body: JSON.stringify({ name: value })
|
|
345
|
+
}).then(r => r.json());
|
|
346
|
+
return newOption;
|
|
347
|
+
};
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Reading back state after a write (v2)
|
|
351
|
+
|
|
352
|
+
Since v2, **property writes are coalesced** — setting a property (e.g. `el.options = […]`) applies on a microtask, not synchronously. This only matters when you read *rendered DOM* right after a write:
|
|
353
|
+
|
|
354
|
+
```javascript
|
|
355
|
+
el.options = data;
|
|
356
|
+
await el.whenSettled(); // wait for the pending re-render
|
|
357
|
+
console.log(el.shadowRoot.querySelectorAll('.ms__option').length);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
You do **not** need to await between a property write and an imperative method — `getSelected()` / `setSelected()` / `getValue()` / `selectedValue` / `selectedItem` flush any pending write internally, so `el.options = data; el.setSelected(sel)` works with no `await` in between. `setAttributes()` and `batch()` also flush synchronously.
|
|
361
|
+
|
|
362
|
+
## Methods
|
|
363
|
+
|
|
364
|
+
| Method | Description |
|
|
365
|
+
|--------|-------------|
|
|
366
|
+
| `getSelected()` | Get currently selected options as array of option objects |
|
|
367
|
+
| `setSelected(values: (string \| number)[])` | Set selected values by ID/value |
|
|
368
|
+
| `getValue()` | Get selected value(s) — returns single value in single-select mode, array in multi-select mode |
|
|
369
|
+
| `setAttributes(values: Record<string, unknown>)` | Apply several inputs as a single in-place update (one re-render instead of one per property). Keys are property names (`configKey`, e.g. `searchPlaceholder`) or their kebab attribute (`search-placeholder`); values are **typed property values**, validated exactly like a direct property assignment — not raw attribute strings. Flushes synchronously. Handy for i18n switches that change several strings at once. To batch raw attribute **strings** instead, use `batch(() => { setAttribute('search-placeholder', '…'); … })` |
|
|
370
|
+
| `destroy()` | Clean up and destroy instance |
|
|
371
|
+
|
|
372
|
+
## Events
|
|
373
|
+
|
|
374
|
+
| Event | Detail | Description |
|
|
375
|
+
|-------|--------|-------------|
|
|
376
|
+
| `select` | `{ option, selectedOptions }` | Fired when an option is selected |
|
|
377
|
+
| `deselect` | `{ option, selectedOptions }` | Fired when an option is deselected |
|
|
378
|
+
| `change` | `{ selectedOptions, selectedValues }` | Fired when selection changes |
|
|
379
|
+
|
|
380
|
+
## Option structure
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
interface MultiSelectOption {
|
|
384
|
+
value: string; // Required: Unique identifier
|
|
385
|
+
label: string; // Required: Display text
|
|
386
|
+
icon?: string; // Optional: Icon or emoji
|
|
387
|
+
subtitle?: string; // Optional: Subtitle/description
|
|
388
|
+
group?: string; // Optional: Group name
|
|
389
|
+
disabled?: boolean; // Optional: Disable selection
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The component also accepts:
|
|
394
|
+
|
|
395
|
+
- **Tuple arrays** like `[['js', 'JavaScript'], ['ts', 'TypeScript']]` — first element becomes value, second becomes display text.
|
|
396
|
+
- **Arbitrary custom objects** via member attributes (`value-member`, `display-value-member`, etc.) or callbacks (`getValueCallback`, `getDisplayValueCallback`, etc.). See [examples.md → Flexible Data Handling](./examples.md#flexible-data-handling).
|