svelte-multiselect 11.2.3 → 11.3.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/dist/CircleSpinner.svelte.d.ts +3 -3
- package/dist/CmdPalette.svelte +20 -13
- package/dist/CmdPalette.svelte.d.ts +61 -14
- package/dist/CodeExample.svelte +10 -3
- package/dist/CodeExample.svelte.d.ts +6 -3
- package/dist/CopyButton.svelte +26 -4
- package/dist/CopyButton.svelte.d.ts +4 -4
- package/dist/FileDetails.svelte +3 -3
- package/dist/FileDetails.svelte.d.ts +6 -3
- package/dist/GitHubCorner.svelte.d.ts +3 -3
- package/dist/Icon.svelte.d.ts +4 -4
- package/dist/MultiSelect.svelte +86 -62
- package/dist/MultiSelect.svelte.d.ts +9 -9
- package/dist/Nav.svelte +447 -0
- package/dist/Nav.svelte.d.ts +42 -0
- package/dist/PrevNext.svelte +11 -10
- package/dist/PrevNext.svelte.d.ts +16 -15
- package/dist/Toggle.svelte +4 -8
- package/dist/Toggle.svelte.d.ts +5 -10
- package/dist/Wiggle.svelte.d.ts +3 -3
- package/dist/attachments.d.ts +9 -6
- package/dist/attachments.js +124 -35
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/types.d.ts +4 -20
- package/dist/utils.d.ts +3 -8
- package/dist/utils.js +24 -48
- package/package.json +19 -19
- package/readme.md +4 -6
- package/dist/RadioButtons.svelte +0 -67
- package/dist/RadioButtons.svelte.d.ts +0 -51
package/dist/attachments.d.ts
CHANGED
|
@@ -25,22 +25,25 @@ export declare const sortable: ({ header_selector, asc_class, desc_class, sorted
|
|
|
25
25
|
backgroundColor: string;
|
|
26
26
|
} | undefined;
|
|
27
27
|
}) => (node: HTMLElement) => () => void;
|
|
28
|
-
type HighlightOptions = {
|
|
28
|
+
export type HighlightOptions = {
|
|
29
29
|
query?: string;
|
|
30
30
|
disabled?: boolean;
|
|
31
|
+
fuzzy?: boolean;
|
|
31
32
|
node_filter?: (node: Node) => number;
|
|
32
33
|
css_class?: string;
|
|
33
34
|
};
|
|
34
|
-
export declare const highlight_matches: (ops: HighlightOptions) => (node: HTMLElement) => (() =>
|
|
35
|
-
export
|
|
35
|
+
export declare const highlight_matches: (ops: HighlightOptions) => (node: HTMLElement) => (() => boolean) | undefined;
|
|
36
|
+
export interface TooltipOptions {
|
|
36
37
|
content?: string;
|
|
37
38
|
placement?: `top` | `bottom` | `left` | `right`;
|
|
38
39
|
delay?: number;
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
style?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare const tooltip: (options?: TooltipOptions) => Attachment;
|
|
44
|
+
export type ClickOutsideConfig<T extends HTMLElement> = {
|
|
41
45
|
enabled?: boolean;
|
|
42
46
|
exclude?: string[];
|
|
43
47
|
callback?: (node: T, config: ClickOutsideConfig<T>) => void;
|
|
44
48
|
};
|
|
45
49
|
export declare const click_outside: <T extends HTMLElement>(config?: ClickOutsideConfig<T>) => (node: T) => () => void;
|
|
46
|
-
export {};
|
package/dist/attachments.js
CHANGED
|
@@ -158,11 +158,15 @@ export const sortable = ({ header_selector = `thead th`, asc_class = `table-sort
|
|
|
158
158
|
};
|
|
159
159
|
};
|
|
160
160
|
export const highlight_matches = (ops) => (node) => {
|
|
161
|
-
const { query = ``, disabled = false, node_filter = () => NodeFilter.FILTER_ACCEPT, css_class = `highlight-match`, } = ops;
|
|
162
|
-
//
|
|
163
|
-
CSS.highlights
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
const { query = ``, disabled = false, fuzzy = false, node_filter = () => NodeFilter.FILTER_ACCEPT, css_class = `highlight-match`, } = ops;
|
|
162
|
+
// abort if CSS highlight API not supported
|
|
163
|
+
if (typeof CSS === `undefined` || !CSS.highlights)
|
|
164
|
+
return;
|
|
165
|
+
// always clear our own highlight first
|
|
166
|
+
CSS.highlights.delete(css_class);
|
|
167
|
+
// if disabled or empty query, stop after cleanup
|
|
168
|
+
if (!query || disabled)
|
|
169
|
+
return;
|
|
166
170
|
const tree_walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, {
|
|
167
171
|
acceptNode: node_filter,
|
|
168
172
|
});
|
|
@@ -175,28 +179,57 @@ export const highlight_matches = (ops) => (node) => {
|
|
|
175
179
|
// iterate over all text nodes and find matches
|
|
176
180
|
const ranges = text_nodes.map((el) => {
|
|
177
181
|
const text = el.textContent?.toLowerCase();
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
if (!text)
|
|
183
|
+
return [];
|
|
184
|
+
const search = query.toLowerCase();
|
|
185
|
+
if (fuzzy) {
|
|
186
|
+
// Fuzzy highlighting: highlight individual characters that match in order
|
|
187
|
+
const matching_indices = [];
|
|
188
|
+
let search_idx = 0;
|
|
189
|
+
let target_idx = 0;
|
|
190
|
+
// Find matching character indices
|
|
191
|
+
while (search_idx < search.length && target_idx < text.length) {
|
|
192
|
+
if (search[search_idx] === text[target_idx]) {
|
|
193
|
+
matching_indices.push(target_idx);
|
|
194
|
+
search_idx++;
|
|
195
|
+
}
|
|
196
|
+
target_idx++;
|
|
197
|
+
}
|
|
198
|
+
// Only create ranges if we found all characters in order
|
|
199
|
+
if (search_idx === search.length) {
|
|
200
|
+
return matching_indices.map((index) => {
|
|
201
|
+
const range = new Range();
|
|
202
|
+
range.setStart(el, index);
|
|
203
|
+
range.setEnd(el, index + 1); // highlight single character
|
|
204
|
+
return range;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
// Substring highlighting: highlight consecutive substrings
|
|
211
|
+
const indices = [];
|
|
212
|
+
let start_pos = 0;
|
|
213
|
+
while (start_pos < text.length) {
|
|
214
|
+
const index = text.indexOf(search, start_pos);
|
|
215
|
+
if (index === -1)
|
|
216
|
+
break;
|
|
217
|
+
indices.push(index);
|
|
218
|
+
start_pos = index + search.length;
|
|
219
|
+
}
|
|
220
|
+
// create range object for each substring found in the text node
|
|
221
|
+
return indices.map((index) => {
|
|
222
|
+
const range = new Range();
|
|
223
|
+
range.setStart(el, index);
|
|
224
|
+
range.setEnd(el, index + search.length);
|
|
225
|
+
return range;
|
|
226
|
+
});
|
|
186
227
|
}
|
|
187
|
-
// create range object for each str found in the text node
|
|
188
|
-
return indices.map((index) => {
|
|
189
|
-
const range = new Range();
|
|
190
|
-
range.setStart(el, index);
|
|
191
|
-
range.setEnd(el, index + query?.length);
|
|
192
|
-
return range;
|
|
193
|
-
});
|
|
194
228
|
});
|
|
195
229
|
// create Highlight object from ranges and add to registry
|
|
196
230
|
CSS.highlights.set(css_class, new Highlight(...ranges.flat()));
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
};
|
|
231
|
+
// Return cleanup function
|
|
232
|
+
return () => CSS.highlights.delete(css_class);
|
|
200
233
|
};
|
|
201
234
|
// Global tooltip state to ensure only one tooltip is shown at a time
|
|
202
235
|
let current_tooltip = null;
|
|
@@ -220,7 +253,7 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
220
253
|
const safe_options = options || {};
|
|
221
254
|
const cleanup_functions = [];
|
|
222
255
|
function setup_tooltip(element) {
|
|
223
|
-
if (!element)
|
|
256
|
+
if (!element || safe_options.disabled)
|
|
224
257
|
return;
|
|
225
258
|
const content = safe_options.content || element.title ||
|
|
226
259
|
element.getAttribute(`aria-label`) || element.getAttribute(`data-title`);
|
|
@@ -239,6 +272,7 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
239
272
|
tooltip.className = `custom-tooltip`;
|
|
240
273
|
const placement = safe_options.placement || `bottom`;
|
|
241
274
|
tooltip.setAttribute(`data-placement`, placement);
|
|
275
|
+
// Apply base styles
|
|
242
276
|
tooltip.style.cssText = `
|
|
243
277
|
position: absolute; z-index: 9999; opacity: 0;
|
|
244
278
|
background: var(--tooltip-bg, #333); color: var(--text-color, white); border: var(--tooltip-border, none);
|
|
@@ -246,6 +280,16 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
246
280
|
max-width: var(--tooltip-max-width, 280px); word-wrap: break-word; pointer-events: none;
|
|
247
281
|
filter: var(--tooltip-shadow, drop-shadow(0 2px 8px rgba(0,0,0,0.25))); transition: opacity 0.15s ease-out;
|
|
248
282
|
`;
|
|
283
|
+
// Apply custom styles if provided (these will override base styles due to CSS specificity)
|
|
284
|
+
if (safe_options.style) {
|
|
285
|
+
// Parse and apply custom styles as individual properties for better control
|
|
286
|
+
const custom_styles = safe_options.style.split(`;`).filter((style) => style.trim());
|
|
287
|
+
custom_styles.forEach((style) => {
|
|
288
|
+
const [property, value] = style.split(`:`).map((s) => s.trim());
|
|
289
|
+
if (property && value)
|
|
290
|
+
tooltip.style.setProperty(property, value);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
249
293
|
tooltip.innerHTML = content?.replace(/\r/g, `<br/>`) ?? ``;
|
|
250
294
|
// Mirror CSS custom properties from the trigger node onto the tooltip element
|
|
251
295
|
const trigger_styles = getComputedStyle(element);
|
|
@@ -265,7 +309,10 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
265
309
|
if (value)
|
|
266
310
|
tooltip.style.setProperty(name, value);
|
|
267
311
|
});
|
|
268
|
-
//
|
|
312
|
+
// Append early so we can read computed border styles for arrow border
|
|
313
|
+
document.body.appendChild(tooltip);
|
|
314
|
+
// Arrow elements: optional border triangle behind fill triangle
|
|
315
|
+
const tooltip_styles = getComputedStyle(tooltip);
|
|
269
316
|
const arrow = document.createElement(`div`);
|
|
270
317
|
arrow.className = `custom-tooltip-arrow`;
|
|
271
318
|
arrow.style.cssText =
|
|
@@ -274,6 +321,49 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
274
321
|
.trim();
|
|
275
322
|
const arrow_size_num = Number.parseInt(arrow_size_raw || ``, 10);
|
|
276
323
|
const arrow_px = Number.isFinite(arrow_size_num) ? arrow_size_num : 6;
|
|
324
|
+
const border_color = tooltip_styles.borderTopColor;
|
|
325
|
+
const border_width_num = Number.parseFloat(tooltip_styles.borderTopWidth || `0`);
|
|
326
|
+
const has_border = !!border_color && border_color !== `rgba(0, 0, 0, 0)` &&
|
|
327
|
+
border_width_num > 0;
|
|
328
|
+
const maybe_append_border_arrow = () => {
|
|
329
|
+
if (!has_border)
|
|
330
|
+
return;
|
|
331
|
+
const border_arrow = document.createElement(`div`);
|
|
332
|
+
border_arrow.className = `custom-tooltip-arrow-border`;
|
|
333
|
+
border_arrow.style.cssText =
|
|
334
|
+
`position: absolute; width: 0; height: 0; pointer-events: none;`;
|
|
335
|
+
const border_size = arrow_px + (border_width_num * 1.4);
|
|
336
|
+
if (placement === `top`) {
|
|
337
|
+
border_arrow.style.left = `calc(50% - ${border_size}px)`;
|
|
338
|
+
border_arrow.style.bottom = `-${border_size}px`;
|
|
339
|
+
border_arrow.style.borderLeft = `${border_size}px solid transparent`;
|
|
340
|
+
border_arrow.style.borderRight = `${border_size}px solid transparent`;
|
|
341
|
+
border_arrow.style.borderTop = `${border_size}px solid ${border_color}`;
|
|
342
|
+
}
|
|
343
|
+
else if (placement === `left`) {
|
|
344
|
+
border_arrow.style.top = `calc(50% - ${border_size}px)`;
|
|
345
|
+
border_arrow.style.right = `-${border_size}px`;
|
|
346
|
+
border_arrow.style.borderTop = `${border_size}px solid transparent`;
|
|
347
|
+
border_arrow.style.borderBottom = `${border_size}px solid transparent`;
|
|
348
|
+
border_arrow.style.borderLeft = `${border_size}px solid ${border_color}`;
|
|
349
|
+
}
|
|
350
|
+
else if (placement === `right`) {
|
|
351
|
+
border_arrow.style.top = `calc(50% - ${border_size}px)`;
|
|
352
|
+
border_arrow.style.left = `-${border_size}px`;
|
|
353
|
+
border_arrow.style.borderTop = `${border_size}px solid transparent`;
|
|
354
|
+
border_arrow.style.borderBottom = `${border_size}px solid transparent`;
|
|
355
|
+
border_arrow.style.borderRight = `${border_size}px solid ${border_color}`;
|
|
356
|
+
}
|
|
357
|
+
else { // bottom
|
|
358
|
+
border_arrow.style.left = `calc(50% - ${border_size}px)`;
|
|
359
|
+
border_arrow.style.top = `-${border_size}px`;
|
|
360
|
+
border_arrow.style.borderLeft = `${border_size}px solid transparent`;
|
|
361
|
+
border_arrow.style.borderRight = `${border_size}px solid transparent`;
|
|
362
|
+
border_arrow.style.borderBottom = `${border_size}px solid ${border_color}`;
|
|
363
|
+
}
|
|
364
|
+
tooltip.appendChild(border_arrow);
|
|
365
|
+
};
|
|
366
|
+
// Create the fill arrow on top
|
|
277
367
|
if (placement === `top`) {
|
|
278
368
|
arrow.style.left = `calc(50% - ${arrow_px}px)`;
|
|
279
369
|
arrow.style.bottom = `-${arrow_px}px`;
|
|
@@ -302,8 +392,8 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
302
392
|
arrow.style.borderRight = `${arrow_px}px solid transparent`;
|
|
303
393
|
arrow.style.borderBottom = `${arrow_px}px solid var(--tooltip-bg, #333)`;
|
|
304
394
|
}
|
|
395
|
+
maybe_append_border_arrow();
|
|
305
396
|
tooltip.appendChild(arrow);
|
|
306
|
-
document.body.appendChild(tooltip);
|
|
307
397
|
// Position tooltip
|
|
308
398
|
const rect = element.getBoundingClientRect();
|
|
309
399
|
const tooltip_rect = tooltip.getBoundingClientRect();
|
|
@@ -337,23 +427,22 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
337
427
|
}
|
|
338
428
|
function hide_tooltip() {
|
|
339
429
|
clear_tooltip();
|
|
340
|
-
|
|
430
|
+
if (current_tooltip) {
|
|
431
|
+
current_tooltip.style.opacity = `0`;
|
|
341
432
|
if (current_tooltip) {
|
|
342
|
-
current_tooltip.
|
|
343
|
-
|
|
344
|
-
if (current_tooltip) {
|
|
345
|
-
current_tooltip.remove();
|
|
346
|
-
current_tooltip = null;
|
|
347
|
-
}
|
|
348
|
-
}, 150);
|
|
433
|
+
current_tooltip.remove();
|
|
434
|
+
current_tooltip = null;
|
|
349
435
|
}
|
|
350
|
-
}
|
|
436
|
+
}
|
|
351
437
|
}
|
|
352
438
|
const events = [`mouseenter`, `mouseleave`, `focus`, `blur`];
|
|
353
439
|
const handlers = [show_tooltip, hide_tooltip, show_tooltip, hide_tooltip];
|
|
354
440
|
events.forEach((event, idx) => element.addEventListener(event, handlers[idx]));
|
|
441
|
+
// Hide tooltip when user scrolls
|
|
442
|
+
globalThis.addEventListener(`scroll`, hide_tooltip, true);
|
|
355
443
|
return () => {
|
|
356
444
|
events.forEach((event, idx) => element.removeEventListener(event, handlers[idx]));
|
|
445
|
+
globalThis.removeEventListener(`scroll`, hide_tooltip, true);
|
|
357
446
|
const original_title = element.getAttribute(`data-original-title`);
|
|
358
447
|
if (original_title) {
|
|
359
448
|
element.setAttribute(`title`, original_title);
|
package/dist/index.d.ts
CHANGED
|
@@ -7,9 +7,10 @@ export { default as FileDetails } from './FileDetails.svelte';
|
|
|
7
7
|
export { default as GitHubCorner } from './GitHubCorner.svelte';
|
|
8
8
|
export { default as Icon } from './Icon.svelte';
|
|
9
9
|
export { default, default as MultiSelect } from './MultiSelect.svelte';
|
|
10
|
+
export { default as Nav } from './Nav.svelte';
|
|
10
11
|
export { default as PrevNext } from './PrevNext.svelte';
|
|
11
|
-
export { default as RadioButtons } from './RadioButtons.svelte';
|
|
12
12
|
export { default as Toggle } from './Toggle.svelte';
|
|
13
13
|
export * from './types';
|
|
14
|
+
export * from './utils';
|
|
14
15
|
export { default as Wiggle } from './Wiggle.svelte';
|
|
15
16
|
export declare function scroll_into_view_if_needed_polyfill(element: Element, centerIfNeeded?: boolean): IntersectionObserver;
|
package/dist/index.js
CHANGED
|
@@ -7,10 +7,11 @@ export { default as FileDetails } from './FileDetails.svelte';
|
|
|
7
7
|
export { default as GitHubCorner } from './GitHubCorner.svelte';
|
|
8
8
|
export { default as Icon } from './Icon.svelte';
|
|
9
9
|
export { default, default as MultiSelect } from './MultiSelect.svelte';
|
|
10
|
+
export { default as Nav } from './Nav.svelte';
|
|
10
11
|
export { default as PrevNext } from './PrevNext.svelte';
|
|
11
|
-
export { default as RadioButtons } from './RadioButtons.svelte';
|
|
12
12
|
export { default as Toggle } from './Toggle.svelte';
|
|
13
13
|
export * from './types';
|
|
14
|
+
export * from './utils';
|
|
14
15
|
export { default as Wiggle } from './Wiggle.svelte';
|
|
15
16
|
// Firefox lacks support for scrollIntoViewIfNeeded (https://caniuse.com/scrollintoviewifneeded).
|
|
16
17
|
// See https://github.com/janosh/svelte-multiselect/issues/87
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import type {
|
|
2
|
+
import type { HTMLAttributes, HTMLInputAttributes } from 'svelte/elements';
|
|
3
3
|
export type Option = string | number | ObjectOption;
|
|
4
4
|
export type OptionStyle = string | {
|
|
5
5
|
option: string;
|
|
@@ -16,20 +16,6 @@ export type ObjectOption = {
|
|
|
16
16
|
style?: OptionStyle;
|
|
17
17
|
[key: string]: unknown;
|
|
18
18
|
};
|
|
19
|
-
export interface MultiSelectNativeEvents {
|
|
20
|
-
onblur?: FocusEventHandler<HTMLInputElement>;
|
|
21
|
-
onclick?: MouseEventHandler<HTMLInputElement>;
|
|
22
|
-
onfocus?: FocusEventHandler<HTMLInputElement>;
|
|
23
|
-
onkeydown?: KeyboardEventHandler<HTMLInputElement>;
|
|
24
|
-
onkeyup?: KeyboardEventHandler<HTMLInputElement>;
|
|
25
|
-
onmousedown?: MouseEventHandler<HTMLInputElement>;
|
|
26
|
-
onmouseenter?: MouseEventHandler<HTMLInputElement>;
|
|
27
|
-
onmouseleave?: MouseEventHandler<HTMLInputElement>;
|
|
28
|
-
ontouchcancel?: TouchEventHandler<HTMLInputElement>;
|
|
29
|
-
ontouchend?: TouchEventHandler<HTMLInputElement>;
|
|
30
|
-
ontouchmove?: TouchEventHandler<HTMLInputElement>;
|
|
31
|
-
ontouchstart?: TouchEventHandler<HTMLInputElement>;
|
|
32
|
-
}
|
|
33
19
|
export interface MultiSelectEvents<T extends Option = Option> {
|
|
34
20
|
onadd?: (data: {
|
|
35
21
|
option: T;
|
|
@@ -55,7 +41,7 @@ export interface MultiSelectEvents<T extends Option = Option> {
|
|
|
55
41
|
event: Event;
|
|
56
42
|
}) => unknown;
|
|
57
43
|
}
|
|
58
|
-
type AfterInputProps = Pick<
|
|
44
|
+
type AfterInputProps = Pick<MultiSelectProps, `selected` | `disabled` | `invalid` | `id` | `placeholder` | `open` | `required`>;
|
|
59
45
|
type UserMsgProps = {
|
|
60
46
|
searchText: string;
|
|
61
47
|
msgType: false | `dupe` | `create` | `no-match`;
|
|
@@ -87,7 +73,7 @@ export interface PortalParams {
|
|
|
87
73
|
target_node?: HTMLElement | null;
|
|
88
74
|
active?: boolean;
|
|
89
75
|
}
|
|
90
|
-
export interface
|
|
76
|
+
export interface MultiSelectProps<T extends Option = Option> extends MultiSelectEvents<T>, MultiSelectSnippets<T>, Omit<HTMLAttributes<HTMLDivElement>, `children` | `onchange` | `onclose`> {
|
|
91
77
|
activeIndex?: number | null;
|
|
92
78
|
activeOption?: T | null;
|
|
93
79
|
createOptionMsg?: string | null;
|
|
@@ -104,6 +90,7 @@ export interface MultiSelectParameters<T extends Option = Option> {
|
|
|
104
90
|
keepSelectedInDropdown?: false | `plain` | `checkboxes`;
|
|
105
91
|
key?: (opt: T) => unknown;
|
|
106
92
|
filterFunc?: (opt: T, searchText: string) => boolean;
|
|
93
|
+
fuzzy?: boolean;
|
|
107
94
|
closeDropdownOnSelect?: boolean | `if-mobile` | `retain-focus`;
|
|
108
95
|
form_input?: HTMLInputElement | null;
|
|
109
96
|
highlightMatches?: boolean;
|
|
@@ -151,8 +138,5 @@ export interface MultiSelectParameters<T extends Option = Option> {
|
|
|
151
138
|
ulOptionsStyle?: string | null;
|
|
152
139
|
value?: T | T[] | null;
|
|
153
140
|
portal?: PortalParams;
|
|
154
|
-
[key: string]: unknown;
|
|
155
|
-
}
|
|
156
|
-
export interface MultiSelectProps<T extends Option = Option> extends MultiSelectNativeEvents, MultiSelectEvents<T>, MultiSelectSnippets<T>, MultiSelectParameters<T> {
|
|
157
141
|
}
|
|
158
142
|
export {};
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import type { Option
|
|
1
|
+
import type { Option } from './types';
|
|
2
2
|
export declare const get_label: (opt: Option) => string | number;
|
|
3
|
-
export declare function get_style(option:
|
|
4
|
-
|
|
5
|
-
[key: string]: unknown;
|
|
6
|
-
} | string | number, key?: `selected` | `option` | null): string;
|
|
7
|
-
export declare function highlight_matching_nodes(element: HTMLElement, // parent element
|
|
8
|
-
query?: string, // search query
|
|
9
|
-
noMatchingOptionsMsg?: string): void;
|
|
3
|
+
export declare function get_style(option: Option, key?: `selected` | `option` | null | undefined): string;
|
|
4
|
+
export declare function fuzzy_match(search_text: string, target_text: string): boolean;
|
package/dist/utils.js
CHANGED
|
@@ -14,14 +14,15 @@ export const get_label = (opt) => {
|
|
|
14
14
|
// object to be used in the style attribute of the option.
|
|
15
15
|
// If the style is a string, it will be returned as is
|
|
16
16
|
export function get_style(option, key = null) {
|
|
17
|
+
if (key === undefined)
|
|
18
|
+
key = null;
|
|
17
19
|
let css_str = ``;
|
|
18
|
-
|
|
20
|
+
const valid_key = key === null || key === `selected` || key === `option`;
|
|
21
|
+
if (!valid_key)
|
|
19
22
|
console.error(`MultiSelect: Invalid key=${key} for get_style`);
|
|
20
|
-
}
|
|
21
23
|
if (typeof option === `object` && option.style) {
|
|
22
|
-
if (typeof option.style === `string`)
|
|
24
|
+
if (typeof option.style === `string`)
|
|
23
25
|
css_str = option.style;
|
|
24
|
-
}
|
|
25
26
|
if (typeof option.style === `object`) {
|
|
26
27
|
if (key && key in option.style)
|
|
27
28
|
return option.style[key] ?? ``;
|
|
@@ -35,49 +36,24 @@ export function get_style(option, key = null) {
|
|
|
35
36
|
css_str += `;`;
|
|
36
37
|
return css_str;
|
|
37
38
|
}
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
let current_node = tree_walker.nextNode();
|
|
57
|
-
while (current_node) {
|
|
58
|
-
text_nodes.push(current_node);
|
|
59
|
-
current_node = tree_walker.nextNode();
|
|
39
|
+
// Fuzzy string matching function
|
|
40
|
+
// Returns true if the search string can be found as a subsequence in the target string
|
|
41
|
+
// e.g., "tageoo" matches "tasks/geo-opt" because t-a-g-e-o-o appears in order
|
|
42
|
+
export function fuzzy_match(search_text, target_text) {
|
|
43
|
+
// Handle null/undefined inputs first
|
|
44
|
+
if (search_text === null || search_text === undefined || target_text === null ||
|
|
45
|
+
target_text === undefined)
|
|
46
|
+
return false;
|
|
47
|
+
if (!search_text)
|
|
48
|
+
return true;
|
|
49
|
+
if (!target_text)
|
|
50
|
+
return false;
|
|
51
|
+
const [search, target] = [search_text.toLowerCase(), target_text.toLowerCase()];
|
|
52
|
+
let [search_idx, target_idx] = [0, 0];
|
|
53
|
+
while (search_idx < search.length && target_idx < target.length) {
|
|
54
|
+
if (search[search_idx] === target[target_idx])
|
|
55
|
+
search_idx++;
|
|
56
|
+
target_idx++;
|
|
60
57
|
}
|
|
61
|
-
|
|
62
|
-
const ranges = text_nodes.map((el) => {
|
|
63
|
-
const text = el.textContent?.toLowerCase();
|
|
64
|
-
const indices = [];
|
|
65
|
-
let start_pos = 0;
|
|
66
|
-
while (text && start_pos < text.length) {
|
|
67
|
-
const index = text.indexOf(query, start_pos);
|
|
68
|
-
if (index === -1)
|
|
69
|
-
break;
|
|
70
|
-
indices.push(index);
|
|
71
|
-
start_pos = index + query.length;
|
|
72
|
-
}
|
|
73
|
-
// create range object for each str found in the text node
|
|
74
|
-
return indices.map((index) => {
|
|
75
|
-
const range = new Range();
|
|
76
|
-
range.setStart(el, index);
|
|
77
|
-
range.setEnd(el, index + query.length);
|
|
78
|
-
return range;
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
// create Highlight object from ranges and add to registry
|
|
82
|
-
CSS.highlights.set(`sms-search-matches`, new Highlight(...ranges.flat()));
|
|
58
|
+
return search_idx === search.length;
|
|
83
59
|
}
|
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.
|
|
8
|
+
"version": "11.3.0",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"svelte": "./dist/index.js",
|
|
11
11
|
"bugs": "https://github.com/janosh/svelte-multiselect/issues",
|
|
@@ -13,31 +13,31 @@
|
|
|
13
13
|
"svelte": "^5.35.6"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@playwright/test": "^1.
|
|
17
|
-
"@stylistic/eslint-plugin": "^5.
|
|
18
|
-
"@sveltejs/adapter-static": "^3.0.
|
|
19
|
-
"@sveltejs/kit": "^2.
|
|
20
|
-
"@sveltejs/package": "2.4
|
|
21
|
-
"@sveltejs/vite-plugin-svelte": "^6.
|
|
22
|
-
"@types/node": "^24.
|
|
23
|
-
"@vitest/coverage-v8": "^
|
|
24
|
-
"eslint": "^9.
|
|
25
|
-
"eslint-plugin-svelte": "^3.
|
|
26
|
-
"happy-dom": "^
|
|
16
|
+
"@playwright/test": "^1.56.1",
|
|
17
|
+
"@stylistic/eslint-plugin": "^5.5.0",
|
|
18
|
+
"@sveltejs/adapter-static": "^3.0.10",
|
|
19
|
+
"@sveltejs/kit": "^2.48.4",
|
|
20
|
+
"@sveltejs/package": "2.5.4",
|
|
21
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
22
|
+
"@types/node": "^24.10.0",
|
|
23
|
+
"@vitest/coverage-v8": "^4.0.8",
|
|
24
|
+
"eslint": "^9.39.1",
|
|
25
|
+
"eslint-plugin-svelte": "^3.13.0",
|
|
26
|
+
"happy-dom": "^20.0.10",
|
|
27
27
|
"hastscript": "^9.0.1",
|
|
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.3.
|
|
32
|
+
"svelte": "^5.43.5",
|
|
33
|
+
"svelte-check": "^4.3.3",
|
|
34
34
|
"svelte-preprocess": "^6.0.3",
|
|
35
35
|
"svelte-toc": "^0.6.2",
|
|
36
|
-
"svelte2tsx": "^0.7.
|
|
37
|
-
"typescript": "5.9.
|
|
38
|
-
"typescript-eslint": "^8.
|
|
39
|
-
"vite": "^7.
|
|
40
|
-
"vitest": "^
|
|
36
|
+
"svelte2tsx": "^0.7.45",
|
|
37
|
+
"typescript": "5.9.3",
|
|
38
|
+
"typescript-eslint": "^8.46.3",
|
|
39
|
+
"vite": "^7.2.2",
|
|
40
|
+
"vitest": "^4.0.8"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -32,8 +32,6 @@
|
|
|
32
32
|
- **Single / multiple select:** pass `maxSelect={1, 2, 3, ...}` prop to restrict the number of selectable options
|
|
33
33
|
- **Configurable:** see props
|
|
34
34
|
|
|
35
|
-
<slot name="nav" />
|
|
36
|
-
|
|
37
35
|
## 🧪   Coverage
|
|
38
36
|
|
|
39
37
|
| Statements | Branches | Lines |
|
|
@@ -643,10 +641,10 @@ For example, here's how you might annoy your users with an alert every time one
|
|
|
643
641
|
|
|
644
642
|
```svelte
|
|
645
643
|
<MultiSelect
|
|
646
|
-
onchange={(
|
|
647
|
-
if (
|
|
648
|
-
if (
|
|
649
|
-
if (
|
|
644
|
+
onchange={(event) => {
|
|
645
|
+
if (event.detail.type === 'add') alert(`You added ${event.detail.option}`)
|
|
646
|
+
if (event.detail.type === 'remove') alert(`You removed ${event.detail.option}`)
|
|
647
|
+
if (event.detail.type === 'removeAll') alert(`You removed ${event.detail.options}`)
|
|
650
648
|
}}
|
|
651
649
|
/>
|
|
652
650
|
```
|
package/dist/RadioButtons.svelte
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
<script lang="ts">// get the label key from an option object or the option itself if it's a string or number
|
|
2
|
-
const get_label = (op) => {
|
|
3
|
-
if (op instanceof Object) {
|
|
4
|
-
if (op.label === undefined) {
|
|
5
|
-
console.error(`RadioButton option ${JSON.stringify(op)} is an object but has no label key`);
|
|
6
|
-
}
|
|
7
|
-
return op.label;
|
|
8
|
-
}
|
|
9
|
-
return op;
|
|
10
|
-
};
|
|
11
|
-
let { options, selected = $bindable(), id = null, name = null, disabled = false, required = false, aria_label = null, onclick, onchange, oninput, option_snippet, children, ...rest } = $props();
|
|
12
|
-
export {};
|
|
13
|
-
</script>
|
|
14
|
-
|
|
15
|
-
<div {id} {...rest}>
|
|
16
|
-
{#each options as option (JSON.stringify(option))}
|
|
17
|
-
{@const label = get_label(option)}
|
|
18
|
-
{@const active = selected && get_label(option) === get_label(selected)}
|
|
19
|
-
<label class:active aria-label={aria_label}>
|
|
20
|
-
<input
|
|
21
|
-
type="radio"
|
|
22
|
-
value={option}
|
|
23
|
-
{name}
|
|
24
|
-
{disabled}
|
|
25
|
-
{required}
|
|
26
|
-
bind:group={selected}
|
|
27
|
-
{onchange}
|
|
28
|
-
{oninput}
|
|
29
|
-
{onclick}
|
|
30
|
-
/>
|
|
31
|
-
{#if option_snippet}
|
|
32
|
-
{@render option_snippet({ option, selected, active })}
|
|
33
|
-
{:else if children}
|
|
34
|
-
{@render children({ option, selected, active })}
|
|
35
|
-
{:else}<span>{label}</span>{/if}
|
|
36
|
-
</label>
|
|
37
|
-
{/each}
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<style>
|
|
41
|
-
div {
|
|
42
|
-
max-width: max-content;
|
|
43
|
-
overflow: hidden;
|
|
44
|
-
height: fit-content;
|
|
45
|
-
display: var(--radio-btn-display, inline-flex);
|
|
46
|
-
border-radius: var(--radio-btn-border-radius, 0.5em);
|
|
47
|
-
}
|
|
48
|
-
input {
|
|
49
|
-
display: none;
|
|
50
|
-
}
|
|
51
|
-
span {
|
|
52
|
-
cursor: pointer;
|
|
53
|
-
display: inline-block;
|
|
54
|
-
color: var(--radio-btn-color, white);
|
|
55
|
-
padding: var(--radio-btn-padding, 2pt 5pt);
|
|
56
|
-
background: var(--radio-btn-bg, black);
|
|
57
|
-
transition: var(--radio-btn-transition, background 0.3s, transform 0.3s);
|
|
58
|
-
}
|
|
59
|
-
label:not(.active) span:hover {
|
|
60
|
-
background: var(--radio-btn-hover-bg, cornflowerblue);
|
|
61
|
-
color: var(--radio-btn-hover-color, white);
|
|
62
|
-
}
|
|
63
|
-
label.active span {
|
|
64
|
-
box-shadow: var(--radio-btn-checked-shadow, inset 0 0 1em -3pt black);
|
|
65
|
-
background: var(--radio-btn-checked-bg, darkcyan);
|
|
66
|
-
}
|
|
67
|
-
</style>
|