svelte-multiselect 6.1.0 → 7.0.0
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/MultiSelect.svelte +42 -33
- package/MultiSelect.svelte.d.ts +5 -5
- package/package.json +1 -1
- package/readme.md +29 -25
package/MultiSelect.svelte
CHANGED
|
@@ -22,6 +22,7 @@ export let focusInputOnSelect = `desktop`;
|
|
|
22
22
|
export let id = null;
|
|
23
23
|
export let input = null;
|
|
24
24
|
export let inputClass = ``;
|
|
25
|
+
export let inputmode = null;
|
|
25
26
|
export let invalid = false;
|
|
26
27
|
export let liActiveOptionClass = ``;
|
|
27
28
|
export let liOptionClass = ``;
|
|
@@ -37,19 +38,30 @@ export let options;
|
|
|
37
38
|
export let outerDiv = null;
|
|
38
39
|
export let outerDivClass = ``;
|
|
39
40
|
export let parseLabelsAsHtml = false; // should not be combined with allowUserOptions!
|
|
41
|
+
export let pattern = null;
|
|
40
42
|
export let placeholder = null;
|
|
41
43
|
export let removeAllTitle = `Remove all`;
|
|
42
44
|
export let removeBtnTitle = `Remove`;
|
|
43
45
|
export let required = false;
|
|
44
46
|
export let searchText = ``;
|
|
45
|
-
export let selected = options
|
|
47
|
+
export let selected = options
|
|
48
|
+
?.filter((op) => op?.preselected)
|
|
49
|
+
.slice(0, maxSelect ?? undefined) ?? [];
|
|
46
50
|
export let selectedLabels = [];
|
|
47
51
|
export let selectedValues = [];
|
|
48
52
|
export let sortSelected = false;
|
|
49
53
|
export let ulOptionsClass = ``;
|
|
50
54
|
export let ulSelectedClass = ``;
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
// selected and _selected are identical except if maxSelect=1, selected will be the single item (or null)
|
|
56
|
+
// in _selected which will always be an array for easier component internals. selected then solves
|
|
57
|
+
// https://github.com/janosh/svelte-multiselect/issues/86
|
|
58
|
+
let _selected = (selected ?? []);
|
|
59
|
+
$: selected = maxSelect === 1 ? _selected[0] ?? null : _selected;
|
|
60
|
+
let wiggle = false; // controls wiggle animation when user tries to exceed maxSelect
|
|
61
|
+
$: _selectedLabels = _selected?.map(get_label) ?? [];
|
|
62
|
+
$: selectedLabels = maxSelect === 1 ? _selectedLabels[0] ?? null : _selectedLabels;
|
|
63
|
+
$: _selectedValues = _selected?.map(get_value) ?? [];
|
|
64
|
+
$: selectedValues = maxSelect === 1 ? _selectedValues[0] ?? null : _selectedValues;
|
|
53
65
|
if (!(options?.length > 0)) {
|
|
54
66
|
if (allowUserOptions) {
|
|
55
67
|
options = []; // initializing as array avoids errors when component mounts
|
|
@@ -65,22 +77,19 @@ if (parseLabelsAsHtml && allowUserOptions) {
|
|
|
65
77
|
if (maxSelect !== null && maxSelect < 1) {
|
|
66
78
|
console.error(`maxSelect must be null or positive integer, got ${maxSelect}`);
|
|
67
79
|
}
|
|
68
|
-
if (!Array.isArray(
|
|
69
|
-
console.error(`
|
|
80
|
+
if (!Array.isArray(_selected)) {
|
|
81
|
+
console.error(`internal variable _selected prop should always be an array, got ${_selected}`);
|
|
70
82
|
}
|
|
71
83
|
const dispatch = createEventDispatcher();
|
|
72
84
|
let add_option_msg_is_active = false; // controls active state of <li>{addOptionMsg}</li>
|
|
73
85
|
let window_width;
|
|
74
|
-
let wiggle = false; // controls wiggle animation when user tries to exceed maxSelect
|
|
75
|
-
$: selectedLabels = selected.map(get_label);
|
|
76
|
-
$: selectedValues = selected.map(get_value);
|
|
77
86
|
// formValue binds to input.form-control to prevent form submission if required
|
|
78
87
|
// prop is true and no options are selected
|
|
79
|
-
$: formValue =
|
|
88
|
+
$: formValue = _selectedValues.join(`,`);
|
|
80
89
|
$: if (formValue)
|
|
81
90
|
invalid = false; // reset error status whenever component state changes
|
|
82
91
|
// options matching the current search text
|
|
83
|
-
$: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !
|
|
92
|
+
$: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !_selectedLabels.includes(get_label(op)) // remove already selected options from dropdown list
|
|
84
93
|
);
|
|
85
94
|
// raise if matchingOptions[activeIndex] does not yield a value
|
|
86
95
|
if (activeIndex !== null && !matchingOptions[activeIndex]) {
|
|
@@ -90,10 +99,10 @@ if (activeIndex !== null && !matchingOptions[activeIndex]) {
|
|
|
90
99
|
$: activeOption = activeIndex !== null ? matchingOptions[activeIndex] : null;
|
|
91
100
|
// add an option to selected list
|
|
92
101
|
function add(label, event) {
|
|
93
|
-
if (maxSelect && maxSelect > 1 &&
|
|
102
|
+
if (maxSelect && maxSelect > 1 && _selected.length >= maxSelect)
|
|
94
103
|
wiggle = true;
|
|
95
104
|
// to prevent duplicate selection, we could add `&& !selectedLabels.includes(label)`
|
|
96
|
-
if (maxSelect === null || maxSelect === 1 ||
|
|
105
|
+
if (maxSelect === null || maxSelect === 1 || _selected.length < maxSelect) {
|
|
97
106
|
// first check if we find option in the options list
|
|
98
107
|
let option = options.find((op) => get_label(op) === label);
|
|
99
108
|
if (!option && // this has the side-effect of not allowing to user to add the same
|
|
@@ -125,22 +134,22 @@ function add(label, event) {
|
|
|
125
134
|
}
|
|
126
135
|
if (maxSelect === 1) {
|
|
127
136
|
// for maxselect = 1 we always replace current option with new one
|
|
128
|
-
|
|
137
|
+
_selected = [option];
|
|
129
138
|
}
|
|
130
139
|
else {
|
|
131
|
-
|
|
140
|
+
_selected = [..._selected, option];
|
|
132
141
|
if (sortSelected === true) {
|
|
133
|
-
|
|
142
|
+
_selected = _selected.sort((op1, op2) => {
|
|
134
143
|
const [label1, label2] = [get_label(op1), get_label(op2)];
|
|
135
144
|
// coerce to string if labels are numbers
|
|
136
145
|
return `${label1}`.localeCompare(`${label2}`);
|
|
137
146
|
});
|
|
138
147
|
}
|
|
139
148
|
else if (typeof sortSelected === `function`) {
|
|
140
|
-
|
|
149
|
+
_selected = _selected.sort(sortSelected);
|
|
141
150
|
}
|
|
142
151
|
}
|
|
143
|
-
if (
|
|
152
|
+
if (_selected.length === maxSelect)
|
|
144
153
|
close_dropdown(event);
|
|
145
154
|
else if (focusInputOnSelect === true ||
|
|
146
155
|
(focusInputOnSelect === `desktop` && window_width > breakpoint)) {
|
|
@@ -152,10 +161,10 @@ function add(label, event) {
|
|
|
152
161
|
}
|
|
153
162
|
// remove an option from selected list
|
|
154
163
|
function remove(label) {
|
|
155
|
-
if (
|
|
164
|
+
if (_selected.length === 0)
|
|
156
165
|
return;
|
|
157
|
-
|
|
158
|
-
|
|
166
|
+
_selected.splice(_selectedLabels.lastIndexOf(label), 1);
|
|
167
|
+
_selected = _selected; // Svelte rerender after in-place splice
|
|
159
168
|
const option = options.find((option) => get_label(option) === label) ??
|
|
160
169
|
// if option with label could not be found but allowUserOptions is truthy,
|
|
161
170
|
// assume it was created by user and create correspondidng option object
|
|
@@ -243,17 +252,17 @@ async function handle_keydown(event) {
|
|
|
243
252
|
}
|
|
244
253
|
}
|
|
245
254
|
// on backspace key: remove last selected option
|
|
246
|
-
else if (event.key === `Backspace` &&
|
|
247
|
-
remove(
|
|
255
|
+
else if (event.key === `Backspace` && _selectedLabels.length > 0 && !searchText) {
|
|
256
|
+
remove(_selectedLabels.at(-1));
|
|
248
257
|
}
|
|
249
258
|
}
|
|
250
259
|
function remove_all() {
|
|
251
|
-
dispatch(`removeAll`, { options:
|
|
252
|
-
dispatch(`change`, { options:
|
|
253
|
-
|
|
260
|
+
dispatch(`removeAll`, { options: _selected });
|
|
261
|
+
dispatch(`change`, { options: _selected, type: `removeAll` });
|
|
262
|
+
_selected = [];
|
|
254
263
|
searchText = ``;
|
|
255
264
|
}
|
|
256
|
-
$: is_selected = (label) =>
|
|
265
|
+
$: is_selected = (label) => _selectedLabels.includes(label);
|
|
257
266
|
const if_enter_or_space = (handler) => (event) => {
|
|
258
267
|
if ([`Enter`, `Space`].includes(event.code)) {
|
|
259
268
|
event.preventDefault();
|
|
@@ -297,7 +306,7 @@ function on_click_outside(event) {
|
|
|
297
306
|
/>
|
|
298
307
|
<ExpandIcon width="15px" style="min-width: 1em; padding: 0 1pt;" />
|
|
299
308
|
<ul class="selected {ulSelectedClass}">
|
|
300
|
-
{#each
|
|
309
|
+
{#each _selected as option, idx}
|
|
301
310
|
<li class={liSelectedClass} aria-selected="true">
|
|
302
311
|
<slot name="selected" {option} {idx}>
|
|
303
312
|
{#if parseLabelsAsHtml}
|
|
@@ -325,7 +334,7 @@ function on_click_outside(event) {
|
|
|
325
334
|
{autocomplete}
|
|
326
335
|
bind:value={searchText}
|
|
327
336
|
on:mouseup|self|stopPropagation={open_dropdown}
|
|
328
|
-
on:keydown={handle_keydown}
|
|
337
|
+
on:keydown|stopPropagation={handle_keydown}
|
|
329
338
|
on:focus
|
|
330
339
|
on:focus={open_dropdown}
|
|
331
340
|
{id}
|
|
@@ -333,7 +342,7 @@ function on_click_outside(event) {
|
|
|
333
342
|
{disabled}
|
|
334
343
|
{inputmode}
|
|
335
344
|
{pattern}
|
|
336
|
-
placeholder={
|
|
345
|
+
placeholder={_selected.length == 0 ? placeholder : null}
|
|
337
346
|
aria-invalid={invalid ? `true` : null}
|
|
338
347
|
on:blur
|
|
339
348
|
on:change
|
|
@@ -360,16 +369,16 @@ function on_click_outside(event) {
|
|
|
360
369
|
<slot name="disabled-icon">
|
|
361
370
|
<DisabledIcon width="15px" />
|
|
362
371
|
</slot>
|
|
363
|
-
{:else if
|
|
372
|
+
{:else if _selected.length > 0}
|
|
364
373
|
{#if maxSelect && (maxSelect > 1 || maxSelectMsg)}
|
|
365
374
|
<Wiggle bind:wiggle angle={20}>
|
|
366
375
|
<span style="padding: 0 3pt;">
|
|
367
|
-
{maxSelectMsg?.(
|
|
368
|
-
(maxSelect > 1 ? `${
|
|
376
|
+
{maxSelectMsg?.(_selected.length, maxSelect) ??
|
|
377
|
+
(maxSelect > 1 ? `${_selected.length}/${maxSelect}` : ``)}
|
|
369
378
|
</span>
|
|
370
379
|
</Wiggle>
|
|
371
380
|
{/if}
|
|
372
|
-
{#if maxSelect !== 1 &&
|
|
381
|
+
{#if maxSelect !== 1 && _selected.length > 1}
|
|
373
382
|
<button
|
|
374
383
|
type="button"
|
|
375
384
|
class="remove-all"
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ declare const __propDef: {
|
|
|
17
17
|
id?: string | null | undefined;
|
|
18
18
|
input?: HTMLInputElement | null | undefined;
|
|
19
19
|
inputClass?: string | undefined;
|
|
20
|
+
inputmode?: string | null | undefined;
|
|
20
21
|
invalid?: boolean | undefined;
|
|
21
22
|
liActiveOptionClass?: string | undefined;
|
|
22
23
|
liOptionClass?: string | undefined;
|
|
@@ -32,19 +33,18 @@ declare const __propDef: {
|
|
|
32
33
|
outerDiv?: HTMLDivElement | null | undefined;
|
|
33
34
|
outerDivClass?: string | undefined;
|
|
34
35
|
parseLabelsAsHtml?: boolean | undefined;
|
|
36
|
+
pattern?: string | null | undefined;
|
|
35
37
|
placeholder?: string | null | undefined;
|
|
36
38
|
removeAllTitle?: string | undefined;
|
|
37
39
|
removeBtnTitle?: string | undefined;
|
|
38
40
|
required?: boolean | undefined;
|
|
39
41
|
searchText?: string | undefined;
|
|
40
|
-
selected?: Option[] | undefined;
|
|
41
|
-
selectedLabels?: (string | number)[] | undefined;
|
|
42
|
-
selectedValues?: unknown[] |
|
|
42
|
+
selected?: Option | Option[] | null | undefined;
|
|
43
|
+
selectedLabels?: string | number | (string | number)[] | null | undefined;
|
|
44
|
+
selectedValues?: unknown[] | unknown | null;
|
|
43
45
|
sortSelected?: boolean | ((op1: Option, op2: Option) => number) | undefined;
|
|
44
46
|
ulOptionsClass?: string | undefined;
|
|
45
47
|
ulSelectedClass?: string | undefined;
|
|
46
|
-
inputmode?: string | undefined;
|
|
47
|
-
pattern?: string | undefined;
|
|
48
48
|
};
|
|
49
49
|
slots: {
|
|
50
50
|
selected: {
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
- **v6.0.1** The prop `disabledTitle` which sets the title of the `<MultiSelect>` `<input>` node if in `disabled` mode was renamed to `disabledInputTitle`. See [PR 105](https://github.com/janosh/svelte-multiselect/pull/105).
|
|
42
42
|
- **v6.0.1** The default margin of `1em 0` on the wrapper `div.multiselect` was removed. Instead, there is now a new CSS variable `--sms-margin`. Set it to `--sms-margin: 1em 0;` to restore the old appearance. See [PR 105](https://github.com/janosh/svelte-multiselect/pull/105).
|
|
43
43
|
- **6.1.0** The `dispatch` events `focus` and `blur` were renamed to `open` and `close`, respectively. These actions refer to the dropdown list, i.e. `<MultiSelect on:open={(event) => console.log(event)}>` will trigger when the dropdown list opens. The focus and blur events are now regular DOM (not Svelte `dispatch`) events emitted by the `<input>` node. See [PR 120](https://github.com/janosh/svelte-multiselect/pull/120).
|
|
44
|
+
- **v7.0.0** `selected` (as well `selectedLabels` and `selectedValues`) used to be arrays always. Now, if `maxSelect=1`, they will no longer be a length-1 array but simply a single a option (label/value respectively) or `null` if no option is selected. See [PR 123](https://github.com/janosh/svelte-multiselect/pull/123).
|
|
44
45
|
|
|
45
46
|
## Installation
|
|
46
47
|
|
|
@@ -164,6 +165,12 @@ import type { Option } from 'svelte-multiselect'
|
|
|
164
165
|
|
|
165
166
|
Handle to the `<input>` DOM node. Only available after component mounts (`null` before then).
|
|
166
167
|
|
|
168
|
+
1. ```ts
|
|
169
|
+
inputmode: string | null = null
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The `inputmode` attribute hints at the type of data the user may enter. Values like `'numeric' | 'tel' | 'email'` allow browsers to display an appropriate virtual keyboard. See [MDN](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/inputmode) for details.
|
|
173
|
+
|
|
167
174
|
1. ```ts
|
|
168
175
|
invalid: boolean = false
|
|
169
176
|
```
|
|
@@ -220,7 +227,7 @@ import type { Option } from 'svelte-multiselect'
|
|
|
220
227
|
options: Option[]
|
|
221
228
|
```
|
|
222
229
|
|
|
223
|
-
**The only required prop** (no default). Array of strings/numbers or `Option` objects to be listed in the dropdown. The only required key on objects is `label` which must also be unique. An object's `value` defaults to `label` if `undefined`. You can add arbitrary additional keys to your option objects. A few keys like `preselected` and `title` have special meaning though. See `src/lib/index.ts` for all special keys and their purpose.
|
|
230
|
+
**The only required prop** (no default). Array of strings/numbers or `Option` objects to be listed in the dropdown. The only required key on objects is `label` which must also be unique. An object's `value` defaults to `label` if `undefined`. You can add arbitrary additional keys to your option objects. A few keys like `preselected` and `title` have special meaning though. See type `ObjectOption` in [`src/lib/index.ts`](https://github.com/janosh/svelte-multiselect/blob/main/src/lib/index.ts) for all special keys and their purpose.
|
|
224
231
|
|
|
225
232
|
1. ```ts
|
|
226
233
|
outerDiv: HTMLDivElement | null = null
|
|
@@ -234,6 +241,12 @@ import type { Option } from 'svelte-multiselect'
|
|
|
234
241
|
|
|
235
242
|
Whether option labels should be passed to [Svelte's `@html` directive](https://svelte.dev/tutorial/html-tags) or inserted into the DOM as plain text. `true` will raise an error if `allowUserOptions` is also truthy as it makes your site susceptible to [cross-site scripting (XSS) attacks](https://wikipedia.org/wiki/Cross-site_scripting).
|
|
236
243
|
|
|
244
|
+
1. ```ts
|
|
245
|
+
pattern: string | null = null
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The pattern attribute specifies a regular expression which the input's value must match. If a non-null value doesn't match the `pattern` regex, the read-only `patternMismatch` property will be `true`. See [MDN](https://developer.mozilla.org/docs/Web/HTML/Attributes/pattern) for details.
|
|
249
|
+
|
|
237
250
|
1. ```ts
|
|
238
251
|
placeholder: string | null = null
|
|
239
252
|
```
|
|
@@ -265,22 +278,25 @@ import type { Option } from 'svelte-multiselect'
|
|
|
265
278
|
Text the user-entered to filter down on the list of options. Binds both ways, i.e. can also be used to set the input text.
|
|
266
279
|
|
|
267
280
|
1. ```ts
|
|
268
|
-
selected: Option[]
|
|
281
|
+
selected: Option[] | Option | null =
|
|
282
|
+
options
|
|
283
|
+
?.filter((op) => (op as ObjectOption)?.preselected)
|
|
284
|
+
.slice(0, maxSelect ?? undefined) ?? []
|
|
269
285
|
```
|
|
270
286
|
|
|
271
|
-
Array of currently selected options.
|
|
287
|
+
Array of currently selected options. Supports 2-way binding `bind:selected={[1, 2, 3]}` to control component state externally or passed as prop to set pre-selected options that will already be populated when component mounts before any user interaction. If `maxSelect={1}`, selected will not be an array but a single `Option` or `null` if no options are selected.
|
|
272
288
|
|
|
273
289
|
1. ```ts
|
|
274
|
-
selectedLabels: (string | number)[] = []
|
|
290
|
+
selectedLabels: (string | number)[] | string | number | null = []
|
|
275
291
|
```
|
|
276
292
|
|
|
277
|
-
Labels of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.label)` when options are objects. If options are simple strings, `selected === selectedLabels`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedLabels`.
|
|
293
|
+
Labels of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.label)` when options are objects. If options are simple strings, `selected === selectedLabels`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedLabels`. If `maxSelect={1}`, selectedLabels will not be an array but a single `string | number` or `null` if no options are selected.
|
|
278
294
|
|
|
279
295
|
1. ```ts
|
|
280
|
-
selectedValues: unknown[] = []
|
|
296
|
+
selectedValues: unknown[] | unknown | null = []
|
|
281
297
|
```
|
|
282
298
|
|
|
283
|
-
Values of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.value)` when options are objects. If options are simple strings, `selected === selectedValues`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedValues`.
|
|
299
|
+
Values of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.value)` when options are objects. If options are simple strings, `selected === selectedValues`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedValues`. If `maxSelect={1}`, selectedLabels will not be an array but a single value or `null` if no options are selected.
|
|
284
300
|
|
|
285
301
|
1. ```ts
|
|
286
302
|
sortSelected: boolean | ((op1: Option, op2: Option) => number) = false
|
|
@@ -288,18 +304,6 @@ import type { Option } from 'svelte-multiselect'
|
|
|
288
304
|
|
|
289
305
|
Default behavior is to render selected items in the order they were chosen. `sortSelected={true}` uses default JS array sorting. A compare function enables custom logic for sorting selected options. See the [`/sort-selected`](https://svelte-multiselect.netlify.app/sort-selected) example.
|
|
290
306
|
|
|
291
|
-
1. ```ts
|
|
292
|
-
inputmode: string = ``
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
The `inputmode` attribute hints at the type of data the user may enter. Values like `'numeric' | 'tel' | 'email'` allow browsers to display an appropriate virtual keyboard. See [MDN](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/inputmode) for details.
|
|
296
|
-
|
|
297
|
-
1. ```ts
|
|
298
|
-
pattern: string = ``
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
The pattern attribute specifies a regular expression which the input's value must match. If a non-null value doesn't match the `pattern` regex, the read-only `patternMismatch` property will be `true`. See [MDN](https://developer.mozilla.org/docs/Web/HTML/Attributes/pattern) for details.
|
|
302
|
-
|
|
303
307
|
## Slots
|
|
304
308
|
|
|
305
309
|
`MultiSelect.svelte` has 3 named slots:
|
|
@@ -483,12 +487,12 @@ For example, to change the background color of the options dropdown:
|
|
|
483
487
|
|
|
484
488
|
The second method allows you to pass in custom classes to the important DOM elements of this component to target them with frameworks like [Tailwind CSS](https://tailwindcss.com).
|
|
485
489
|
|
|
486
|
-
- `outerDivClass`
|
|
487
|
-
- `ulSelectedClass
|
|
488
|
-
- `liSelectedClass
|
|
489
|
-
- `ulOptionsClass`
|
|
490
|
-
- `liOptionClass
|
|
491
|
-
- `liActiveOptionClass
|
|
490
|
+
- `outerDivClass`: wrapper `div` enclosing the whole component
|
|
491
|
+
- `ulSelectedClass`: list of selected options
|
|
492
|
+
- `liSelectedClass`: selected list items
|
|
493
|
+
- `ulOptionsClass`: available options listed in the dropdown when component is in `open` state
|
|
494
|
+
- `liOptionClass`: list items selectable from dropdown list
|
|
495
|
+
- `liActiveOptionClass`: the currently active dropdown list item (i.e. hovered or navigated to with arrow keys)
|
|
492
496
|
|
|
493
497
|
This simplified version of the DOM structure of the component shows where these classes are inserted:
|
|
494
498
|
|