svelte-multiselect 11.2.2 → 11.2.3
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/dist/CmdPalette.svelte.d.ts +1 -1
- package/dist/CopyButton.svelte +3 -4
- package/dist/CopyButton.svelte.d.ts +1 -1
- package/dist/MultiSelect.svelte +86 -21
- package/dist/MultiSelect.svelte.d.ts +11 -4
- package/dist/PrevNext.svelte.d.ts +18 -11
- package/dist/RadioButtons.svelte.d.ts +16 -9
- package/dist/attachments.js +65 -10
- package/dist/types.d.ts +1 -0
- package/package.json +17 -17
- package/readme.md +1 -1
|
@@ -14,6 +14,6 @@ interface Props extends Omit<MultiSelectProps<Action>, `options`> {
|
|
|
14
14
|
input?: HTMLInputElement | null;
|
|
15
15
|
placeholder?: string;
|
|
16
16
|
}
|
|
17
|
-
declare const CmdPalette: import("svelte").Component<Props, {}, "
|
|
17
|
+
declare const CmdPalette: import("svelte").Component<Props, {}, "open" | "input" | "dialog">;
|
|
18
18
|
type CmdPalette = ReturnType<typeof CmdPalette>;
|
|
19
19
|
export default CmdPalette;
|
package/dist/CopyButton.svelte
CHANGED
|
@@ -9,15 +9,14 @@ $effect(() => {
|
|
|
9
9
|
if (!global && !global_selector)
|
|
10
10
|
return;
|
|
11
11
|
const apply_copy_buttons = () => {
|
|
12
|
-
const
|
|
13
|
-
? global
|
|
14
|
-
: `position: absolute; top: 9pt; right: 9pt;`;
|
|
12
|
+
const btn_style = `position: absolute; top: 9pt; right: 9pt; ${rest.style ?? ``}`;
|
|
15
13
|
for (const code of document.querySelectorAll(global_selector ?? `pre > code`)) {
|
|
16
14
|
const pre = code.parentElement;
|
|
15
|
+
const content = code.textContent ?? ``;
|
|
17
16
|
if (pre && !(skip_selector && pre.querySelector(skip_selector))) {
|
|
18
17
|
mount(CopyButton, {
|
|
19
18
|
target: pre,
|
|
20
|
-
props: { content
|
|
19
|
+
props: { content, as, labels, ...rest, style: btn_style },
|
|
21
20
|
});
|
|
22
21
|
}
|
|
23
22
|
}
|
package/dist/MultiSelect.svelte
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { tick } from 'svelte';
|
|
3
3
|
import { flip } from 'svelte/animate';
|
|
4
4
|
import { get_label, get_style, highlight_matching_nodes } from './utils';
|
|
5
|
-
let { activeIndex = $bindable(null), activeOption = $bindable(null), createOptionMsg = `Create this option...`, allowUserOptions = false, allowEmpty = false, autocomplete = `off`, autoScroll = true, breakpoint = 800, defaultDisabledTitle = `This option is disabled`, disabled = false, disabledInputTitle = `This input is disabled`, duplicateOptionMsg = `This option is already selected`, duplicates = false, key = (opt) => `${get_label(opt)}`.toLowerCase(), filterFunc = (opt, searchText) => {
|
|
5
|
+
let { activeIndex = $bindable(null), activeOption = $bindable(null), createOptionMsg = `Create this option...`, allowUserOptions = false, allowEmpty = false, autocomplete = `off`, autoScroll = true, breakpoint = 800, defaultDisabledTitle = `This option is disabled`, disabled = false, disabledInputTitle = `This input is disabled`, duplicateOptionMsg = `This option is already selected`, duplicates = false, keepSelectedInDropdown = false, key = (opt) => `${get_label(opt)}`.toLowerCase(), filterFunc = (opt, searchText) => {
|
|
6
6
|
if (!searchText)
|
|
7
7
|
return true;
|
|
8
8
|
return `${get_label(opt)}`.toLowerCase().includes(searchText.toLowerCase());
|
|
@@ -62,7 +62,9 @@ let window_width = $state(0);
|
|
|
62
62
|
$effect.pre(() => {
|
|
63
63
|
matchingOptions = options.filter((opt) => filterFunc(opt, searchText) &&
|
|
64
64
|
// remove already selected options from dropdown list unless duplicate selections are allowed
|
|
65
|
-
|
|
65
|
+
// or keepSelectedInDropdown is enabled
|
|
66
|
+
(!selected.map(key).includes(key(opt)) || duplicates ||
|
|
67
|
+
keepSelectedInDropdown));
|
|
66
68
|
});
|
|
67
69
|
// raise if matchingOptions[activeIndex] does not yield a value
|
|
68
70
|
if (activeIndex !== null && !matchingOptions[activeIndex]) {
|
|
@@ -72,6 +74,17 @@ if (activeIndex !== null && !matchingOptions[activeIndex]) {
|
|
|
72
74
|
$effect(() => {
|
|
73
75
|
activeOption = matchingOptions[activeIndex ?? -1] ?? null;
|
|
74
76
|
});
|
|
77
|
+
// toggle an option between selected and unselected states (for keepSelectedInDropdown mode)
|
|
78
|
+
function toggle_option(option_to_toggle, event) {
|
|
79
|
+
const is_currently_selected = selected.map(key).includes(key(option_to_toggle));
|
|
80
|
+
if (is_currently_selected) {
|
|
81
|
+
if (minSelect === null || selected.length > minSelect) { // Only remove if it wouldn't violate minSelect
|
|
82
|
+
remove(option_to_toggle, event);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else
|
|
86
|
+
add(option_to_toggle, event);
|
|
87
|
+
}
|
|
75
88
|
// add an option to selected list
|
|
76
89
|
function add(option_to_add, event) {
|
|
77
90
|
event.stopPropagation();
|
|
@@ -115,10 +128,9 @@ function add(option_to_add, event) {
|
|
|
115
128
|
console.error(`MultiSelect: encountered falsy option ${option_to_add}`);
|
|
116
129
|
return;
|
|
117
130
|
}
|
|
118
|
-
|
|
119
|
-
|
|
131
|
+
// for maxSelect = 1 we always replace current option with new one
|
|
132
|
+
if (maxSelect === 1)
|
|
120
133
|
selected = [option_to_add];
|
|
121
|
-
}
|
|
122
134
|
else {
|
|
123
135
|
selected = [...selected, option_to_add];
|
|
124
136
|
if (sortSelected === true) {
|
|
@@ -132,7 +144,7 @@ function add(option_to_add, event) {
|
|
|
132
144
|
selected = selected.sort(sortSelected);
|
|
133
145
|
}
|
|
134
146
|
}
|
|
135
|
-
const reached_max_select = selected.length
|
|
147
|
+
const reached_max_select = selected.length >= (maxSelect ?? Infinity);
|
|
136
148
|
const dropdown_should_close = closeDropdownOnSelect === true ||
|
|
137
149
|
closeDropdownOnSelect === `retain-focus` ||
|
|
138
150
|
(closeDropdownOnSelect === `if-mobile` && window_width &&
|
|
@@ -141,9 +153,8 @@ function add(option_to_add, event) {
|
|
|
141
153
|
if (reached_max_select || dropdown_should_close) {
|
|
142
154
|
close_dropdown(event, should_retain_focus);
|
|
143
155
|
}
|
|
144
|
-
else if (!dropdown_should_close)
|
|
156
|
+
else if (!dropdown_should_close)
|
|
145
157
|
input?.focus();
|
|
146
|
-
}
|
|
147
158
|
onadd?.({ option: option_to_add });
|
|
148
159
|
onchange?.({ option: option_to_add, type: `add` });
|
|
149
160
|
invalid = false; // reset error status whenever new items are selected
|
|
@@ -203,8 +214,12 @@ async function handle_keydown(event) {
|
|
|
203
214
|
event.stopPropagation();
|
|
204
215
|
event.preventDefault(); // prevent enter key from triggering form submission
|
|
205
216
|
if (activeOption) {
|
|
206
|
-
if (selected.includes(activeOption))
|
|
207
|
-
remove
|
|
217
|
+
if (selected.includes(activeOption)) {
|
|
218
|
+
// Only remove if it wouldn't violate minSelect
|
|
219
|
+
if (minSelect === null || selected.length > minSelect) {
|
|
220
|
+
remove(activeOption, event);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
208
223
|
else
|
|
209
224
|
add(activeOption, event);
|
|
210
225
|
searchText = ``;
|
|
@@ -256,8 +271,11 @@ async function handle_keydown(event) {
|
|
|
256
271
|
} // on backspace key: remove last selected option
|
|
257
272
|
else if (event.key === `Backspace` && selected.length > 0 && !searchText) {
|
|
258
273
|
event.stopPropagation();
|
|
274
|
+
// Only remove option if it wouldn't violate minSelect
|
|
275
|
+
if (minSelect === null || selected.length > minSelect) {
|
|
276
|
+
remove(selected.at(-1), event);
|
|
277
|
+
}
|
|
259
278
|
// Don't prevent default, allow normal backspace behavior if not removing
|
|
260
|
-
remove(selected.at(-1), event);
|
|
261
279
|
} // make first matching option active on any keypress (if none of the above special cases match)
|
|
262
280
|
else if (matchingOptions.length > 0 && activeIndex === null) {
|
|
263
281
|
// Don't stop propagation or prevent default here, allow normal character input
|
|
@@ -266,11 +284,23 @@ async function handle_keydown(event) {
|
|
|
266
284
|
}
|
|
267
285
|
function remove_all(event) {
|
|
268
286
|
event.stopPropagation();
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
287
|
+
// Keep the first minSelect items, remove the rest
|
|
288
|
+
let removed_options = [];
|
|
289
|
+
if (minSelect === null) {
|
|
290
|
+
// If no minSelect constraint, remove all
|
|
291
|
+
removed_options = selected;
|
|
292
|
+
selected = [];
|
|
293
|
+
searchText = ``;
|
|
294
|
+
}
|
|
295
|
+
else if (selected.length > minSelect) {
|
|
296
|
+
// Keep the first minSelect items
|
|
297
|
+
removed_options = selected.slice(minSelect);
|
|
298
|
+
selected = selected.slice(0, minSelect);
|
|
299
|
+
searchText = ``;
|
|
300
|
+
}
|
|
301
|
+
onremoveAll?.({ options: removed_options });
|
|
273
302
|
onchange?.({ options: selected, type: `removeAll` });
|
|
303
|
+
// If selected.length <= minSelect, do nothing (can't remove any more)
|
|
274
304
|
}
|
|
275
305
|
let is_selected = $derived((label) => selected.map(get_label).includes(label));
|
|
276
306
|
const if_enter_or_space = (handler) => (event) => {
|
|
@@ -312,6 +342,17 @@ const dragstart = (idx) => (event) => {
|
|
|
312
342
|
event.dataTransfer.setData(`text/plain`, `${idx}`);
|
|
313
343
|
};
|
|
314
344
|
let ul_options = $state();
|
|
345
|
+
// Update highlights whenever search text changes (after ul_options is available)
|
|
346
|
+
$effect(() => {
|
|
347
|
+
if (ul_options && highlightMatches) {
|
|
348
|
+
if (searchText) {
|
|
349
|
+
highlight_matching_nodes(ul_options, searchText, noMatchingOptionsMsg);
|
|
350
|
+
}
|
|
351
|
+
else if (typeof CSS !== `undefined` && CSS.highlights) {
|
|
352
|
+
CSS.highlights.delete?.(`sms-search-matches`); // Clear highlights when search text is empty
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
});
|
|
315
356
|
// highlight text matching user-entered search text in available options
|
|
316
357
|
function highlight_matching_options(event) {
|
|
317
358
|
if (!highlightMatches || !ul_options)
|
|
@@ -581,6 +622,7 @@ function portal(node, params) {
|
|
|
581
622
|
width="14pt"
|
|
582
623
|
style="margin: 0 2pt"
|
|
583
624
|
data-name="disabled-icon"
|
|
625
|
+
aria-disabled="true"
|
|
584
626
|
/>
|
|
585
627
|
{/if}
|
|
586
628
|
{:else if selected.length > 0}
|
|
@@ -641,7 +683,9 @@ function portal(node, params) {
|
|
|
641
683
|
null}
|
|
642
684
|
<li
|
|
643
685
|
onclick={(event) => {
|
|
644
|
-
if (
|
|
686
|
+
if (disabled) return
|
|
687
|
+
if (keepSelectedInDropdown) toggle_option(optionItem, event)
|
|
688
|
+
else add(optionItem, event)
|
|
645
689
|
}}
|
|
646
690
|
title={disabled ? disabledTitle : (is_selected(label) && selectedTitle) || title}
|
|
647
691
|
class:selected={is_selected(label)}
|
|
@@ -660,10 +704,20 @@ function portal(node, params) {
|
|
|
660
704
|
onkeydown={(event) => {
|
|
661
705
|
if (!disabled && (event.key === `Enter` || event.code === `Space`)) {
|
|
662
706
|
event.preventDefault()
|
|
663
|
-
|
|
707
|
+
if (keepSelectedInDropdown) toggle_option(optionItem, event)
|
|
708
|
+
else add(optionItem, event)
|
|
664
709
|
}
|
|
665
710
|
}}
|
|
666
711
|
>
|
|
712
|
+
{#if keepSelectedInDropdown === `checkboxes`}
|
|
713
|
+
<input
|
|
714
|
+
type="checkbox"
|
|
715
|
+
class="option-checkbox"
|
|
716
|
+
checked={is_selected(label)}
|
|
717
|
+
aria-label="Toggle {get_label(optionItem)}"
|
|
718
|
+
tabindex="-1"
|
|
719
|
+
/>
|
|
720
|
+
{/if}
|
|
667
721
|
{#if option}
|
|
668
722
|
{@render option({
|
|
669
723
|
option: optionItem,
|
|
@@ -895,11 +949,13 @@ function portal(node, params) {
|
|
|
895
949
|
visibility: hidden;
|
|
896
950
|
opacity: 0;
|
|
897
951
|
transform: translateY(50px);
|
|
952
|
+
pointer-events: none;
|
|
898
953
|
}
|
|
899
954
|
ul.options > li {
|
|
900
|
-
padding: 3pt
|
|
955
|
+
padding: 3pt 1ex;
|
|
901
956
|
cursor: pointer;
|
|
902
957
|
scroll-margin: var(--sms-options-scroll-margin, 100px);
|
|
958
|
+
border-left: 3px solid transparent;
|
|
903
959
|
}
|
|
904
960
|
ul.options .user-msg {
|
|
905
961
|
/* block needed so vertical padding applies to span */
|
|
@@ -907,8 +963,11 @@ function portal(node, params) {
|
|
|
907
963
|
padding: 3pt 2ex;
|
|
908
964
|
}
|
|
909
965
|
ul.options > li.selected {
|
|
910
|
-
background: var(--sms-li-selected-bg);
|
|
911
|
-
|
|
966
|
+
background: var(--sms-li-selected-plain-bg, rgba(0, 123, 255, 0.1));
|
|
967
|
+
border-left: var(
|
|
968
|
+
--sms-li-selected-plain-border,
|
|
969
|
+
3px solid var(--sms-active-color, cornflowerblue)
|
|
970
|
+
);
|
|
912
971
|
}
|
|
913
972
|
ul.options > li.active {
|
|
914
973
|
background: var(--sms-li-active-bg, var(--sms-active-color, rgba(0, 0, 0, 0.15)));
|
|
@@ -918,7 +977,13 @@ function portal(node, params) {
|
|
|
918
977
|
background: var(--sms-li-disabled-bg, #f5f5f6);
|
|
919
978
|
color: var(--sms-li-disabled-text, #b8b8b8);
|
|
920
979
|
}
|
|
921
|
-
|
|
980
|
+
/* Checkbox styling for keepSelectedInDropdown='checkboxes' mode */
|
|
981
|
+
ul.options > li > input.option-checkbox {
|
|
982
|
+
width: 16px;
|
|
983
|
+
height: 16px;
|
|
984
|
+
margin-right: 6px;
|
|
985
|
+
accent-color: var(--sms-active-color, cornflowerblue);
|
|
986
|
+
}
|
|
922
987
|
:is(span.max-select-msg) {
|
|
923
988
|
padding: 0 3pt;
|
|
924
989
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type { MultiSelectProps, Option as T } from './types';
|
|
2
|
+
declare function $$render<Option extends T>(): {
|
|
3
|
+
props: MultiSelectProps;
|
|
4
|
+
exports: {};
|
|
5
|
+
bindings: "value" | "selected" | "invalid" | "open" | "activeIndex" | "activeOption" | "form_input" | "input" | "matchingOptions" | "options" | "outerDiv" | "searchText";
|
|
6
|
+
slots: {};
|
|
7
|
+
events: {};
|
|
8
|
+
};
|
|
2
9
|
declare class __sveltets_Render<Option extends T> {
|
|
3
|
-
props():
|
|
4
|
-
events():
|
|
5
|
-
slots():
|
|
6
|
-
bindings(): "
|
|
10
|
+
props(): ReturnType<typeof $$render<Option>>['props'];
|
|
11
|
+
events(): ReturnType<typeof $$render<Option>>['events'];
|
|
12
|
+
slots(): ReturnType<typeof $$render<Option>>['slots'];
|
|
13
|
+
bindings(): "value" | "selected" | "invalid" | "open" | "activeIndex" | "activeOption" | "form_input" | "input" | "matchingOptions" | "options" | "outerDiv" | "searchText";
|
|
7
14
|
exports(): {};
|
|
8
15
|
}
|
|
9
16
|
interface $$IsomorphicComponent {
|
|
@@ -1,38 +1,45 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
export type Item = string | [string, unknown];
|
|
3
|
-
declare
|
|
4
|
-
props
|
|
3
|
+
declare function $$render<T extends Item>(): {
|
|
4
|
+
props: {
|
|
5
5
|
[key: string]: unknown;
|
|
6
|
-
items?: T[]
|
|
6
|
+
items?: T[];
|
|
7
7
|
node?: string;
|
|
8
8
|
current?: string;
|
|
9
9
|
log?: `verbose` | `errors` | `silent`;
|
|
10
10
|
nav_options?: {
|
|
11
11
|
replace_state: boolean;
|
|
12
12
|
no_scroll: boolean;
|
|
13
|
-
}
|
|
13
|
+
};
|
|
14
14
|
titles?: {
|
|
15
15
|
prev: string;
|
|
16
16
|
next: string;
|
|
17
|
-
}
|
|
17
|
+
};
|
|
18
18
|
onkeyup?: ((obj: {
|
|
19
19
|
prev: Item;
|
|
20
20
|
next: Item;
|
|
21
|
-
}) => Record<string, string>) | null
|
|
21
|
+
}) => Record<string, string>) | null;
|
|
22
22
|
prev_snippet?: Snippet<[{
|
|
23
23
|
item: Item;
|
|
24
|
-
}]
|
|
24
|
+
}]>;
|
|
25
25
|
children?: Snippet<[{
|
|
26
26
|
kind: `prev` | `next`;
|
|
27
27
|
item: Item;
|
|
28
|
-
}]
|
|
28
|
+
}]>;
|
|
29
29
|
between?: Snippet<[]>;
|
|
30
30
|
next_snippet?: Snippet<[{
|
|
31
31
|
item: Item;
|
|
32
|
-
}]
|
|
32
|
+
}]>;
|
|
33
33
|
};
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
exports: {};
|
|
35
|
+
bindings: "";
|
|
36
|
+
slots: {};
|
|
37
|
+
events: {};
|
|
38
|
+
};
|
|
39
|
+
declare class __sveltets_Render<T extends Item> {
|
|
40
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
41
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
42
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
36
43
|
bindings(): "";
|
|
37
44
|
exports(): {};
|
|
38
45
|
}
|
|
@@ -3,8 +3,8 @@ type GenericOption = string | number | {
|
|
|
3
3
|
value: unknown;
|
|
4
4
|
label: string | number;
|
|
5
5
|
};
|
|
6
|
-
declare
|
|
7
|
-
props
|
|
6
|
+
declare function $$render<Option extends GenericOption>(): {
|
|
7
|
+
props: {
|
|
8
8
|
[key: string]: unknown;
|
|
9
9
|
options: Option[];
|
|
10
10
|
selected?: string | number | null;
|
|
@@ -13,22 +13,29 @@ declare class __sveltets_Render<Option extends GenericOption> {
|
|
|
13
13
|
disabled?: boolean;
|
|
14
14
|
required?: boolean;
|
|
15
15
|
aria_label?: string | null;
|
|
16
|
-
onclick?: (
|
|
17
|
-
onchange?: (
|
|
18
|
-
oninput?: (
|
|
16
|
+
onclick?: (event: MouseEvent) => void;
|
|
17
|
+
onchange?: (event: Event) => void;
|
|
18
|
+
oninput?: (event: Event) => void;
|
|
19
19
|
option_snippet?: Snippet<[{
|
|
20
20
|
option: Option;
|
|
21
21
|
selected: boolean;
|
|
22
22
|
active: boolean;
|
|
23
|
-
}]
|
|
23
|
+
}]>;
|
|
24
24
|
children?: Snippet<[{
|
|
25
25
|
option: Option;
|
|
26
26
|
selected: boolean;
|
|
27
27
|
active: boolean;
|
|
28
|
-
}]
|
|
28
|
+
}]>;
|
|
29
29
|
};
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
exports: {};
|
|
31
|
+
bindings: "selected";
|
|
32
|
+
slots: {};
|
|
33
|
+
events: {};
|
|
34
|
+
};
|
|
35
|
+
declare class __sveltets_Render<Option extends GenericOption> {
|
|
36
|
+
props(): ReturnType<typeof $$render<Option>>['props'];
|
|
37
|
+
events(): ReturnType<typeof $$render<Option>>['events'];
|
|
38
|
+
slots(): ReturnType<typeof $$render<Option>>['slots'];
|
|
32
39
|
bindings(): "selected";
|
|
33
40
|
exports(): {};
|
|
34
41
|
}
|
package/dist/attachments.js
CHANGED
|
@@ -7,7 +7,7 @@ export const draggable = (options = {}) => (element) => {
|
|
|
7
7
|
// Use simple variables for maximum performance
|
|
8
8
|
let dragging = false;
|
|
9
9
|
let start = { x: 0, y: 0 };
|
|
10
|
-
const initial = { left: 0, top: 0
|
|
10
|
+
const initial = { left: 0, top: 0 };
|
|
11
11
|
const handle = options.handle_selector
|
|
12
12
|
? node.querySelector(options.handle_selector)
|
|
13
13
|
: node;
|
|
@@ -26,17 +26,14 @@ export const draggable = (options = {}) => (element) => {
|
|
|
26
26
|
const rect = node.getBoundingClientRect();
|
|
27
27
|
initial.left = rect.left;
|
|
28
28
|
initial.top = rect.top;
|
|
29
|
-
initial.width = rect.width;
|
|
30
29
|
}
|
|
31
30
|
else {
|
|
32
31
|
// For other positioning, use offset values
|
|
33
32
|
initial.left = node.offsetLeft;
|
|
34
33
|
initial.top = node.offsetTop;
|
|
35
|
-
initial.width = node.offsetWidth;
|
|
36
34
|
}
|
|
37
35
|
node.style.left = `${initial.left}px`;
|
|
38
36
|
node.style.top = `${initial.top}px`;
|
|
39
|
-
node.style.width = `${initial.width}px`;
|
|
40
37
|
node.style.right = `auto`; // Prevent conflict with left
|
|
41
38
|
start = { x: event.clientX, y: event.clientY };
|
|
42
39
|
document.body.style.userSelect = `none`; // Prevent text selection during drag
|
|
@@ -240,19 +237,76 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
240
237
|
show_timeout = setTimeout(() => {
|
|
241
238
|
const tooltip = document.createElement(`div`);
|
|
242
239
|
tooltip.className = `custom-tooltip`;
|
|
240
|
+
const placement = safe_options.placement || `bottom`;
|
|
241
|
+
tooltip.setAttribute(`data-placement`, placement);
|
|
243
242
|
tooltip.style.cssText = `
|
|
244
243
|
position: absolute; z-index: 9999; opacity: 0;
|
|
245
|
-
background: var(--tooltip-bg); color: var(--text-color); border: var(--tooltip-border);
|
|
246
|
-
padding: 6px 10px; border-radius: 6px; font-size: 13px; line-height: 1.4;
|
|
247
|
-
max-width: 280px; word-wrap: break-word; pointer-events: none;
|
|
248
|
-
filter: drop-shadow(0 2px 8px rgba(0,0,0,0.25)); transition: opacity 0.15s ease-out;
|
|
244
|
+
background: var(--tooltip-bg, #333); color: var(--text-color, white); border: var(--tooltip-border, none);
|
|
245
|
+
padding: var(--tooltip-padding, 6px 10px); border-radius: var(--tooltip-radius, 6px); font-size: var(--tooltip-font-size, 13px); line-height: 1.4;
|
|
246
|
+
max-width: var(--tooltip-max-width, 280px); word-wrap: break-word; pointer-events: none;
|
|
247
|
+
filter: var(--tooltip-shadow, drop-shadow(0 2px 8px rgba(0,0,0,0.25))); transition: opacity 0.15s ease-out;
|
|
249
248
|
`;
|
|
250
249
|
tooltip.innerHTML = content?.replace(/\r/g, `<br/>`) ?? ``;
|
|
250
|
+
// Mirror CSS custom properties from the trigger node onto the tooltip element
|
|
251
|
+
const trigger_styles = getComputedStyle(element);
|
|
252
|
+
[
|
|
253
|
+
`--tooltip-bg`,
|
|
254
|
+
`--text-color`,
|
|
255
|
+
`--tooltip-border`,
|
|
256
|
+
`--tooltip-padding`,
|
|
257
|
+
`--tooltip-radius`,
|
|
258
|
+
`--tooltip-font-size`,
|
|
259
|
+
`--tooltip-shadow`,
|
|
260
|
+
`--tooltip-max-width`,
|
|
261
|
+
`--tooltip-opacity`,
|
|
262
|
+
`--tooltip-arrow-size`,
|
|
263
|
+
].forEach((name) => {
|
|
264
|
+
const value = trigger_styles.getPropertyValue(name).trim();
|
|
265
|
+
if (value)
|
|
266
|
+
tooltip.style.setProperty(name, value);
|
|
267
|
+
});
|
|
268
|
+
// Arrow element pointing to the trigger, oriented by placement
|
|
269
|
+
const arrow = document.createElement(`div`);
|
|
270
|
+
arrow.className = `custom-tooltip-arrow`;
|
|
271
|
+
arrow.style.cssText =
|
|
272
|
+
`position: absolute; width: 0; height: 0; pointer-events: none;`;
|
|
273
|
+
const arrow_size_raw = trigger_styles.getPropertyValue(`--tooltip-arrow-size`)
|
|
274
|
+
.trim();
|
|
275
|
+
const arrow_size_num = Number.parseInt(arrow_size_raw || ``, 10);
|
|
276
|
+
const arrow_px = Number.isFinite(arrow_size_num) ? arrow_size_num : 6;
|
|
277
|
+
if (placement === `top`) {
|
|
278
|
+
arrow.style.left = `calc(50% - ${arrow_px}px)`;
|
|
279
|
+
arrow.style.bottom = `-${arrow_px}px`;
|
|
280
|
+
arrow.style.borderLeft = `${arrow_px}px solid transparent`;
|
|
281
|
+
arrow.style.borderRight = `${arrow_px}px solid transparent`;
|
|
282
|
+
arrow.style.borderTop = `${arrow_px}px solid var(--tooltip-bg, #333)`;
|
|
283
|
+
}
|
|
284
|
+
else if (placement === `left`) {
|
|
285
|
+
arrow.style.top = `calc(50% - ${arrow_px}px)`;
|
|
286
|
+
arrow.style.right = `-${arrow_px}px`;
|
|
287
|
+
arrow.style.borderTop = `${arrow_px}px solid transparent`;
|
|
288
|
+
arrow.style.borderBottom = `${arrow_px}px solid transparent`;
|
|
289
|
+
arrow.style.borderLeft = `${arrow_px}px solid var(--tooltip-bg, #333)`;
|
|
290
|
+
}
|
|
291
|
+
else if (placement === `right`) {
|
|
292
|
+
arrow.style.top = `calc(50% - ${arrow_px}px)`;
|
|
293
|
+
arrow.style.left = `-${arrow_px}px`;
|
|
294
|
+
arrow.style.borderTop = `${arrow_px}px solid transparent`;
|
|
295
|
+
arrow.style.borderBottom = `${arrow_px}px solid transparent`;
|
|
296
|
+
arrow.style.borderRight = `${arrow_px}px solid var(--tooltip-bg, #333)`;
|
|
297
|
+
}
|
|
298
|
+
else { // bottom
|
|
299
|
+
arrow.style.left = `calc(50% - ${arrow_px}px)`;
|
|
300
|
+
arrow.style.top = `-${arrow_px}px`;
|
|
301
|
+
arrow.style.borderLeft = `${arrow_px}px solid transparent`;
|
|
302
|
+
arrow.style.borderRight = `${arrow_px}px solid transparent`;
|
|
303
|
+
arrow.style.borderBottom = `${arrow_px}px solid var(--tooltip-bg, #333)`;
|
|
304
|
+
}
|
|
305
|
+
tooltip.appendChild(arrow);
|
|
251
306
|
document.body.appendChild(tooltip);
|
|
252
307
|
// Position tooltip
|
|
253
308
|
const rect = element.getBoundingClientRect();
|
|
254
309
|
const tooltip_rect = tooltip.getBoundingClientRect();
|
|
255
|
-
const placement = safe_options.placement || `bottom`;
|
|
256
310
|
const margin = 12;
|
|
257
311
|
let top = 0, left = 0;
|
|
258
312
|
if (placement === `top`) {
|
|
@@ -276,7 +330,8 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
276
330
|
top = Math.max(8, Math.min(top, globalThis.innerHeight - tooltip_rect.height - 8));
|
|
277
331
|
tooltip.style.left = `${left + globalThis.scrollX}px`;
|
|
278
332
|
tooltip.style.top = `${top + globalThis.scrollY}px`;
|
|
279
|
-
|
|
333
|
+
const custom_opacity = trigger_styles.getPropertyValue(`--tooltip-opacity`).trim();
|
|
334
|
+
tooltip.style.opacity = custom_opacity || `1`;
|
|
280
335
|
current_tooltip = tooltip;
|
|
281
336
|
}, safe_options.delay || 100);
|
|
282
337
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -101,6 +101,7 @@ export interface MultiSelectParameters<T extends Option = Option> {
|
|
|
101
101
|
disabledInputTitle?: string;
|
|
102
102
|
duplicateOptionMsg?: string;
|
|
103
103
|
duplicates?: boolean;
|
|
104
|
+
keepSelectedInDropdown?: false | `plain` | `checkboxes`;
|
|
104
105
|
key?: (opt: T) => unknown;
|
|
105
106
|
filterFunc?: (opt: T, searchText: string) => boolean;
|
|
106
107
|
closeDropdownOnSelect?: boolean | `if-mobile` | `retain-focus`;
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"homepage": "https://janosh.github.io/svelte-multiselect",
|
|
6
6
|
"repository": "https://github.com/janosh/svelte-multiselect",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"version": "11.2.
|
|
8
|
+
"version": "11.2.3",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"svelte": "./dist/index.js",
|
|
11
11
|
"bugs": "https://github.com/janosh/svelte-multiselect/issues",
|
|
@@ -13,30 +13,30 @@
|
|
|
13
13
|
"svelte": "^5.35.6"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@playwright/test": "^1.54.
|
|
17
|
-
"@stylistic/eslint-plugin": "^5.
|
|
18
|
-
"@sveltejs/adapter-static": "^3.0.
|
|
19
|
-
"@sveltejs/kit": "^2.
|
|
20
|
-
"@sveltejs/package": "2.
|
|
21
|
-
"@sveltejs/vite-plugin-svelte": "^6.
|
|
22
|
-
"@types/node": "^24.
|
|
16
|
+
"@playwright/test": "^1.54.2",
|
|
17
|
+
"@stylistic/eslint-plugin": "^5.2.3",
|
|
18
|
+
"@sveltejs/adapter-static": "^3.0.9",
|
|
19
|
+
"@sveltejs/kit": "^2.28.0",
|
|
20
|
+
"@sveltejs/package": "2.4.1",
|
|
21
|
+
"@sveltejs/vite-plugin-svelte": "^6.1.1",
|
|
22
|
+
"@types/node": "^24.2.1",
|
|
23
23
|
"@vitest/coverage-v8": "^3.2.4",
|
|
24
|
-
"eslint": "^9.
|
|
25
|
-
"eslint-plugin-svelte": "^3.
|
|
24
|
+
"eslint": "^9.33.0",
|
|
25
|
+
"eslint-plugin-svelte": "^3.11.0",
|
|
26
|
+
"happy-dom": "^18.0.1",
|
|
26
27
|
"hastscript": "^9.0.1",
|
|
27
|
-
"jsdom": "^26.1.0",
|
|
28
28
|
"mdsvex": "^0.12.6",
|
|
29
29
|
"mdsvexamples": "^0.5.0",
|
|
30
30
|
"rehype-autolink-headings": "^7.1.0",
|
|
31
31
|
"rehype-slug": "^6.0.0",
|
|
32
|
-
"svelte": "^5.
|
|
33
|
-
"svelte-check": "^4.
|
|
32
|
+
"svelte": "^5.38.1",
|
|
33
|
+
"svelte-check": "^4.3.1",
|
|
34
34
|
"svelte-preprocess": "^6.0.3",
|
|
35
35
|
"svelte-toc": "^0.6.2",
|
|
36
|
-
"svelte2tsx": "^0.7.
|
|
37
|
-
"typescript": "5.
|
|
38
|
-
"typescript-eslint": "^8.
|
|
39
|
-
"vite": "^7.
|
|
36
|
+
"svelte2tsx": "^0.7.42",
|
|
37
|
+
"typescript": "5.9.2",
|
|
38
|
+
"typescript-eslint": "^8.39.1",
|
|
39
|
+
"vite": "^7.1.2",
|
|
40
40
|
"vitest": "^3.2.4"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
package/readme.md
CHANGED
|
@@ -619,7 +619,7 @@ Example using several snippets:
|
|
|
619
619
|
onremoveAll={(event) => console.log(event.detail.options)}`
|
|
620
620
|
```
|
|
621
621
|
|
|
622
|
-
Triggers when all selected options are removed. The payload `event.detail.options` gives the options that were
|
|
622
|
+
Triggers when all selected options are removed. The payload `event.detail.options` gives the options that were removed (might not be all if `minSelect` is set).
|
|
623
623
|
|
|
624
624
|
1. ```ts
|
|
625
625
|
onchange={(event) => console.log(`${event.detail.type}: '${event.detail.option}'`)}
|