svelte-multiselect 11.2.2 → 11.2.4
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 +15 -11
- package/dist/CmdPalette.svelte.d.ts +4 -2
- package/dist/CopyButton.svelte +27 -6
- package/dist/CopyButton.svelte.d.ts +3 -3
- package/dist/Icon.svelte.d.ts +2 -2
- package/dist/MultiSelect.svelte +150 -63
- package/dist/MultiSelect.svelte.d.ts +11 -4
- package/dist/PrevNext.svelte +9 -8
- package/dist/PrevNext.svelte.d.ts +32 -44
- package/dist/Toggle.svelte +2 -1
- package/dist/Toggle.svelte.d.ts +2 -2
- package/dist/attachments.d.ts +9 -6
- package/dist/attachments.js +187 -43
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +5 -20
- package/dist/utils.d.ts +3 -8
- package/dist/utils.js +24 -48
- package/package.json +17 -17
- package/readme.md +5 -5
- package/dist/RadioButtons.svelte +0 -67
- package/dist/RadioButtons.svelte.d.ts +0 -44
|
@@ -1,48 +1,36 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
no_scroll: boolean;
|
|
13
|
-
} | undefined;
|
|
14
|
-
titles?: {
|
|
15
|
-
prev: string;
|
|
16
|
-
next: string;
|
|
17
|
-
} | undefined;
|
|
18
|
-
onkeyup?: ((obj: {
|
|
19
|
-
prev: Item;
|
|
20
|
-
next: Item;
|
|
21
|
-
}) => Record<string, string>) | null | undefined;
|
|
22
|
-
prev_snippet?: Snippet<[{
|
|
23
|
-
item: Item;
|
|
24
|
-
}]> | undefined;
|
|
25
|
-
children?: Snippet<[{
|
|
26
|
-
kind: `prev` | `next`;
|
|
27
|
-
item: Item;
|
|
28
|
-
}]> | undefined;
|
|
29
|
-
between?: Snippet<[]>;
|
|
30
|
-
next_snippet?: Snippet<[{
|
|
31
|
-
item: Item;
|
|
32
|
-
}]> | undefined;
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
export type Item = [string, unknown];
|
|
4
|
+
interface Props extends Omit<HTMLAttributes<HTMLElement>, `children` | `onkeyup`> {
|
|
5
|
+
items?: (string | Item)[];
|
|
6
|
+
node?: string;
|
|
7
|
+
current?: string;
|
|
8
|
+
log?: `verbose` | `errors` | `silent`;
|
|
9
|
+
nav_options?: {
|
|
10
|
+
replace_state: boolean;
|
|
11
|
+
no_scroll: boolean;
|
|
33
12
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
13
|
+
titles?: {
|
|
14
|
+
prev: string;
|
|
15
|
+
next: string;
|
|
16
|
+
};
|
|
17
|
+
onkeyup?: ((obj: {
|
|
18
|
+
prev: Item;
|
|
19
|
+
next: Item;
|
|
20
|
+
}) => Record<string, string | undefined>) | null;
|
|
21
|
+
prev_snippet?: Snippet<[{
|
|
22
|
+
item: Item;
|
|
23
|
+
}]>;
|
|
24
|
+
children?: Snippet<[{
|
|
25
|
+
kind: `prev` | `next`;
|
|
26
|
+
item: Item;
|
|
27
|
+
}]>;
|
|
28
|
+
between?: Snippet<[]>;
|
|
29
|
+
next_snippet?: Snippet<[{
|
|
30
|
+
item: Item;
|
|
31
|
+
}]>;
|
|
32
|
+
min_items?: number;
|
|
45
33
|
}
|
|
46
|
-
declare const PrevNext:
|
|
47
|
-
type PrevNext
|
|
34
|
+
declare const PrevNext: import("svelte").Component<Props, {}, "">;
|
|
35
|
+
type PrevNext = ReturnType<typeof PrevNext>;
|
|
48
36
|
export default PrevNext;
|
package/dist/Toggle.svelte
CHANGED
package/dist/Toggle.svelte.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLLabelElement> {
|
|
3
4
|
checked?: boolean;
|
|
4
5
|
required?: boolean;
|
|
5
6
|
input_style?: string;
|
|
@@ -9,7 +10,6 @@ interface Props {
|
|
|
9
10
|
onblur?: (event: FocusEvent) => void;
|
|
10
11
|
onkeydown?: (event: KeyboardEvent) => void;
|
|
11
12
|
children?: Snippet<[]>;
|
|
12
|
-
[key: string]: unknown;
|
|
13
13
|
}
|
|
14
14
|
declare const Toggle: import("svelte").Component<Props, {}, "checked">;
|
|
15
15
|
type Toggle = ReturnType<typeof Toggle>;
|
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
|
@@ -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
|
|
@@ -161,11 +158,15 @@ export const sortable = ({ header_selector = `thead th`, asc_class = `table-sort
|
|
|
161
158
|
};
|
|
162
159
|
};
|
|
163
160
|
export const highlight_matches = (ops) => (node) => {
|
|
164
|
-
const { query = ``, disabled = false, node_filter = () => NodeFilter.FILTER_ACCEPT, css_class = `highlight-match`, } = ops;
|
|
165
|
-
//
|
|
166
|
-
CSS.highlights
|
|
167
|
-
|
|
168
|
-
|
|
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;
|
|
169
170
|
const tree_walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, {
|
|
170
171
|
acceptNode: node_filter,
|
|
171
172
|
});
|
|
@@ -178,28 +179,57 @@ export const highlight_matches = (ops) => (node) => {
|
|
|
178
179
|
// iterate over all text nodes and find matches
|
|
179
180
|
const ranges = text_nodes.map((el) => {
|
|
180
181
|
const text = el.textContent?.toLowerCase();
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
+
});
|
|
189
227
|
}
|
|
190
|
-
// create range object for each str found in the text node
|
|
191
|
-
return indices.map((index) => {
|
|
192
|
-
const range = new Range();
|
|
193
|
-
range.setStart(el, index);
|
|
194
|
-
range.setEnd(el, index + query?.length);
|
|
195
|
-
return range;
|
|
196
|
-
});
|
|
197
228
|
});
|
|
198
229
|
// create Highlight object from ranges and add to registry
|
|
199
230
|
CSS.highlights.set(css_class, new Highlight(...ranges.flat()));
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
};
|
|
231
|
+
// Return cleanup function
|
|
232
|
+
return () => CSS.highlights.delete(css_class);
|
|
203
233
|
};
|
|
204
234
|
// Global tooltip state to ensure only one tooltip is shown at a time
|
|
205
235
|
let current_tooltip = null;
|
|
@@ -223,7 +253,7 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
223
253
|
const safe_options = options || {};
|
|
224
254
|
const cleanup_functions = [];
|
|
225
255
|
function setup_tooltip(element) {
|
|
226
|
-
if (!element)
|
|
256
|
+
if (!element || safe_options.disabled)
|
|
227
257
|
return;
|
|
228
258
|
const content = safe_options.content || element.title ||
|
|
229
259
|
element.getAttribute(`aria-label`) || element.getAttribute(`data-title`);
|
|
@@ -240,19 +270,133 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
240
270
|
show_timeout = setTimeout(() => {
|
|
241
271
|
const tooltip = document.createElement(`div`);
|
|
242
272
|
tooltip.className = `custom-tooltip`;
|
|
273
|
+
const placement = safe_options.placement || `bottom`;
|
|
274
|
+
tooltip.setAttribute(`data-placement`, placement);
|
|
275
|
+
// Apply base styles
|
|
243
276
|
tooltip.style.cssText = `
|
|
244
277
|
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;
|
|
278
|
+
background: var(--tooltip-bg, #333); color: var(--text-color, white); border: var(--tooltip-border, none);
|
|
279
|
+
padding: var(--tooltip-padding, 6px 10px); border-radius: var(--tooltip-radius, 6px); font-size: var(--tooltip-font-size, 13px); line-height: 1.4;
|
|
280
|
+
max-width: var(--tooltip-max-width, 280px); word-wrap: break-word; pointer-events: none;
|
|
281
|
+
filter: var(--tooltip-shadow, drop-shadow(0 2px 8px rgba(0,0,0,0.25))); transition: opacity 0.15s ease-out;
|
|
249
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
|
+
}
|
|
250
293
|
tooltip.innerHTML = content?.replace(/\r/g, `<br/>`) ?? ``;
|
|
294
|
+
// Mirror CSS custom properties from the trigger node onto the tooltip element
|
|
295
|
+
const trigger_styles = getComputedStyle(element);
|
|
296
|
+
[
|
|
297
|
+
`--tooltip-bg`,
|
|
298
|
+
`--text-color`,
|
|
299
|
+
`--tooltip-border`,
|
|
300
|
+
`--tooltip-padding`,
|
|
301
|
+
`--tooltip-radius`,
|
|
302
|
+
`--tooltip-font-size`,
|
|
303
|
+
`--tooltip-shadow`,
|
|
304
|
+
`--tooltip-max-width`,
|
|
305
|
+
`--tooltip-opacity`,
|
|
306
|
+
`--tooltip-arrow-size`,
|
|
307
|
+
].forEach((name) => {
|
|
308
|
+
const value = trigger_styles.getPropertyValue(name).trim();
|
|
309
|
+
if (value)
|
|
310
|
+
tooltip.style.setProperty(name, value);
|
|
311
|
+
});
|
|
312
|
+
// Append early so we can read computed border styles for arrow border
|
|
251
313
|
document.body.appendChild(tooltip);
|
|
314
|
+
// Arrow elements: optional border triangle behind fill triangle
|
|
315
|
+
const tooltip_styles = getComputedStyle(tooltip);
|
|
316
|
+
const arrow = document.createElement(`div`);
|
|
317
|
+
arrow.className = `custom-tooltip-arrow`;
|
|
318
|
+
arrow.style.cssText =
|
|
319
|
+
`position: absolute; width: 0; height: 0; pointer-events: none;`;
|
|
320
|
+
const arrow_size_raw = trigger_styles.getPropertyValue(`--tooltip-arrow-size`)
|
|
321
|
+
.trim();
|
|
322
|
+
const arrow_size_num = Number.parseInt(arrow_size_raw || ``, 10);
|
|
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
|
|
367
|
+
if (placement === `top`) {
|
|
368
|
+
arrow.style.left = `calc(50% - ${arrow_px}px)`;
|
|
369
|
+
arrow.style.bottom = `-${arrow_px}px`;
|
|
370
|
+
arrow.style.borderLeft = `${arrow_px}px solid transparent`;
|
|
371
|
+
arrow.style.borderRight = `${arrow_px}px solid transparent`;
|
|
372
|
+
arrow.style.borderTop = `${arrow_px}px solid var(--tooltip-bg, #333)`;
|
|
373
|
+
}
|
|
374
|
+
else if (placement === `left`) {
|
|
375
|
+
arrow.style.top = `calc(50% - ${arrow_px}px)`;
|
|
376
|
+
arrow.style.right = `-${arrow_px}px`;
|
|
377
|
+
arrow.style.borderTop = `${arrow_px}px solid transparent`;
|
|
378
|
+
arrow.style.borderBottom = `${arrow_px}px solid transparent`;
|
|
379
|
+
arrow.style.borderLeft = `${arrow_px}px solid var(--tooltip-bg, #333)`;
|
|
380
|
+
}
|
|
381
|
+
else if (placement === `right`) {
|
|
382
|
+
arrow.style.top = `calc(50% - ${arrow_px}px)`;
|
|
383
|
+
arrow.style.left = `-${arrow_px}px`;
|
|
384
|
+
arrow.style.borderTop = `${arrow_px}px solid transparent`;
|
|
385
|
+
arrow.style.borderBottom = `${arrow_px}px solid transparent`;
|
|
386
|
+
arrow.style.borderRight = `${arrow_px}px solid var(--tooltip-bg, #333)`;
|
|
387
|
+
}
|
|
388
|
+
else { // bottom
|
|
389
|
+
arrow.style.left = `calc(50% - ${arrow_px}px)`;
|
|
390
|
+
arrow.style.top = `-${arrow_px}px`;
|
|
391
|
+
arrow.style.borderLeft = `${arrow_px}px solid transparent`;
|
|
392
|
+
arrow.style.borderRight = `${arrow_px}px solid transparent`;
|
|
393
|
+
arrow.style.borderBottom = `${arrow_px}px solid var(--tooltip-bg, #333)`;
|
|
394
|
+
}
|
|
395
|
+
maybe_append_border_arrow();
|
|
396
|
+
tooltip.appendChild(arrow);
|
|
252
397
|
// Position tooltip
|
|
253
398
|
const rect = element.getBoundingClientRect();
|
|
254
399
|
const tooltip_rect = tooltip.getBoundingClientRect();
|
|
255
|
-
const placement = safe_options.placement || `bottom`;
|
|
256
400
|
const margin = 12;
|
|
257
401
|
let top = 0, left = 0;
|
|
258
402
|
if (placement === `top`) {
|
|
@@ -276,29 +420,29 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
276
420
|
top = Math.max(8, Math.min(top, globalThis.innerHeight - tooltip_rect.height - 8));
|
|
277
421
|
tooltip.style.left = `${left + globalThis.scrollX}px`;
|
|
278
422
|
tooltip.style.top = `${top + globalThis.scrollY}px`;
|
|
279
|
-
|
|
423
|
+
const custom_opacity = trigger_styles.getPropertyValue(`--tooltip-opacity`).trim();
|
|
424
|
+
tooltip.style.opacity = custom_opacity || `1`;
|
|
280
425
|
current_tooltip = tooltip;
|
|
281
426
|
}, safe_options.delay || 100);
|
|
282
427
|
}
|
|
283
428
|
function hide_tooltip() {
|
|
284
429
|
clear_tooltip();
|
|
285
|
-
|
|
430
|
+
if (current_tooltip) {
|
|
431
|
+
current_tooltip.style.opacity = `0`;
|
|
286
432
|
if (current_tooltip) {
|
|
287
|
-
current_tooltip.
|
|
288
|
-
|
|
289
|
-
if (current_tooltip) {
|
|
290
|
-
current_tooltip.remove();
|
|
291
|
-
current_tooltip = null;
|
|
292
|
-
}
|
|
293
|
-
}, 150);
|
|
433
|
+
current_tooltip.remove();
|
|
434
|
+
current_tooltip = null;
|
|
294
435
|
}
|
|
295
|
-
}
|
|
436
|
+
}
|
|
296
437
|
}
|
|
297
438
|
const events = [`mouseenter`, `mouseleave`, `focus`, `blur`];
|
|
298
439
|
const handlers = [show_tooltip, hide_tooltip, show_tooltip, hide_tooltip];
|
|
299
440
|
events.forEach((event, idx) => element.addEventListener(event, handlers[idx]));
|
|
441
|
+
// Hide tooltip when user scrolls
|
|
442
|
+
globalThis.addEventListener(`scroll`, hide_tooltip, true);
|
|
300
443
|
return () => {
|
|
301
444
|
events.forEach((event, idx) => element.removeEventListener(event, handlers[idx]));
|
|
445
|
+
globalThis.removeEventListener(`scroll`, hide_tooltip, true);
|
|
302
446
|
const original_title = element.getAttribute(`data-original-title`);
|
|
303
447
|
if (original_title) {
|
|
304
448
|
element.setAttribute(`title`, original_title);
|
package/dist/index.d.ts
CHANGED
|
@@ -8,8 +8,8 @@ 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
10
|
export { default as PrevNext } from './PrevNext.svelte';
|
|
11
|
-
export { default as RadioButtons } from './RadioButtons.svelte';
|
|
12
11
|
export { default as Toggle } from './Toggle.svelte';
|
|
13
12
|
export * from './types';
|
|
13
|
+
export * from './utils';
|
|
14
14
|
export { default as Wiggle } from './Wiggle.svelte';
|
|
15
15
|
export declare function scroll_into_view_if_needed_polyfill(element: Element, centerIfNeeded?: boolean): IntersectionObserver;
|
package/dist/index.js
CHANGED
|
@@ -8,9 +8,9 @@ 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
10
|
export { default as PrevNext } from './PrevNext.svelte';
|
|
11
|
-
export { default as RadioButtons } from './RadioButtons.svelte';
|
|
12
11
|
export { default as Toggle } from './Toggle.svelte';
|
|
13
12
|
export * from './types';
|
|
13
|
+
export * from './utils';
|
|
14
14
|
export { default as Wiggle } from './Wiggle.svelte';
|
|
15
15
|
// Firefox lacks support for scrollIntoViewIfNeeded (https://caniuse.com/scrollintoviewifneeded).
|
|
16
16
|
// 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;
|
|
@@ -101,8 +87,10 @@ export interface MultiSelectParameters<T extends Option = Option> {
|
|
|
101
87
|
disabledInputTitle?: string;
|
|
102
88
|
duplicateOptionMsg?: string;
|
|
103
89
|
duplicates?: boolean;
|
|
90
|
+
keepSelectedInDropdown?: false | `plain` | `checkboxes`;
|
|
104
91
|
key?: (opt: T) => unknown;
|
|
105
92
|
filterFunc?: (opt: T, searchText: string) => boolean;
|
|
93
|
+
fuzzy?: boolean;
|
|
106
94
|
closeDropdownOnSelect?: boolean | `if-mobile` | `retain-focus`;
|
|
107
95
|
form_input?: HTMLInputElement | null;
|
|
108
96
|
highlightMatches?: boolean;
|
|
@@ -150,8 +138,5 @@ export interface MultiSelectParameters<T extends Option = Option> {
|
|
|
150
138
|
ulOptionsStyle?: string | null;
|
|
151
139
|
value?: T | T[] | null;
|
|
152
140
|
portal?: PortalParams;
|
|
153
|
-
[key: string]: unknown;
|
|
154
|
-
}
|
|
155
|
-
export interface MultiSelectProps<T extends Option = Option> extends MultiSelectNativeEvents, MultiSelectEvents<T>, MultiSelectSnippets<T>, MultiSelectParameters<T> {
|
|
156
141
|
}
|
|
157
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
|
}
|