@txstate-mws/svelte-components 1.3.2 → 1.3.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/components/MultiSelect.svelte +26 -18
- package/components/PopupMenu.svelte +4 -1
- package/components/PopupMenu.svelte.d.ts +1 -0
- package/package.json +2 -2
- package/util/util.d.ts +0 -1
- package/util/util.js +0 -17
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script>import { createEventDispatcher } from 'svelte';
|
|
2
|
-
import { randomid,
|
|
2
|
+
import { randomid, Cache } from 'txstate-utils';
|
|
3
3
|
import ScreenReaderOnly from './ScreenReaderOnly.svelte';
|
|
4
4
|
import DefaultPopupMenu from './PopupMenu.svelte';
|
|
5
|
-
import {
|
|
5
|
+
import { modifierKey, selectionIsLeft } from '../util';
|
|
6
6
|
export let id = randomid();
|
|
7
7
|
export let name;
|
|
8
8
|
export let disabled = false;
|
|
@@ -16,38 +16,48 @@ export let placeholder = '';
|
|
|
16
16
|
export let emptyText = undefined;
|
|
17
17
|
export let PopupMenu = DefaultPopupMenu;
|
|
18
18
|
let menushown;
|
|
19
|
+
let loading = false;
|
|
19
20
|
let options = [];
|
|
20
21
|
let hilitedpill;
|
|
21
22
|
let inputvalue = '';
|
|
22
23
|
let popupvalue = undefined;
|
|
23
24
|
let inputelement;
|
|
24
25
|
const descriptionid = randomid();
|
|
25
|
-
|
|
26
|
+
const dispatch = createEventDispatcher();
|
|
27
|
+
const optionsCache = new Cache(async (ipt) => {
|
|
28
|
+
return await getOptions(ipt);
|
|
29
|
+
}, { freshseconds: 5 });
|
|
26
30
|
$: selectedSet = new Set(selected.map(s => s.value));
|
|
31
|
+
let optionsTimer;
|
|
27
32
|
async function reactToInput(..._) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
loading = true;
|
|
34
|
+
clearTimeout(optionsTimer);
|
|
35
|
+
setTimeout(async () => {
|
|
36
|
+
const saveval = inputvalue;
|
|
37
|
+
const rawOptions = await optionsCache.get(saveval);
|
|
38
|
+
if (inputvalue !== saveval)
|
|
39
|
+
return; // ignore any results that are out of date
|
|
40
|
+
options = rawOptions.filter(o => !selectedSet.has(o.value));
|
|
41
|
+
if (typeof document !== 'undefined' && inputelement === document.activeElement && (options.length || inputvalue?.length))
|
|
42
|
+
menushown = true;
|
|
43
|
+
loading = false;
|
|
44
|
+
}, 250);
|
|
37
45
|
}
|
|
38
|
-
$: reactToInput(inputvalue,
|
|
46
|
+
$: reactToInput(inputvalue, getOptions, selectedSet);
|
|
39
47
|
$: availablemessage = options.filter(o => o.value).length + ' autocomplete choices available';
|
|
40
48
|
function addSelection(e) {
|
|
41
49
|
inputvalue = '';
|
|
42
50
|
const opt = { ...e.detail, label: e.detail.label || e.detail.value };
|
|
43
51
|
selected = [...selected, opt];
|
|
44
52
|
popupvalue = undefined;
|
|
53
|
+
dispatch('change', selected);
|
|
45
54
|
}
|
|
46
55
|
function removeSelection(opt, idx, nextfocus = 1) {
|
|
47
56
|
if (hilitedpill === opt.value) {
|
|
48
57
|
hilitedpill = selected[idx + nextfocus]?.value;
|
|
49
58
|
}
|
|
50
59
|
selected = selected.filter(s => s.value !== opt.value);
|
|
60
|
+
dispatch('change', selected);
|
|
51
61
|
}
|
|
52
62
|
function inputkeydown(e) {
|
|
53
63
|
if (modifierKey(e))
|
|
@@ -80,8 +90,8 @@ function inputkeydown(e) {
|
|
|
80
90
|
}
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
|
-
function inputfocus() {
|
|
84
|
-
reactToInput(
|
|
93
|
+
async function inputfocus() {
|
|
94
|
+
reactToInput();
|
|
85
95
|
}
|
|
86
96
|
let popuphilited;
|
|
87
97
|
$: if (popuphilited != null)
|
|
@@ -97,8 +107,6 @@ function reactToHilite(..._) {
|
|
|
97
107
|
inputelement.removeAttribute('aria-activedescendant');
|
|
98
108
|
}
|
|
99
109
|
$: reactToHilite(hilitedpill, id);
|
|
100
|
-
const dispatch = createEventDispatcher();
|
|
101
|
-
$: dispatch('change', selected);
|
|
102
110
|
</script>
|
|
103
111
|
|
|
104
112
|
<fieldset>
|
|
@@ -126,7 +134,7 @@ $: dispatch('change', selected);
|
|
|
126
134
|
</ScreenReaderOnly>
|
|
127
135
|
<slot></slot>
|
|
128
136
|
</fieldset>
|
|
129
|
-
<svelte:component this={PopupMenu} bind:menushown bind:hilited={popuphilited} bind:value={popupvalue} align='bottomleft' {emptyText} {menuContainerClass} {menuClass} {menuItemClass} {menuItemHilitedClass} items={options} buttonelement={inputelement} on:change={addSelection}></svelte:component>
|
|
137
|
+
<svelte:component this={PopupMenu} bind:menushown bind:hilited={popuphilited} bind:value={popupvalue} align='bottomleft' {loading} {emptyText} {menuContainerClass} {menuClass} {menuItemClass} {menuItemHilitedClass} items={options} buttonelement={inputelement} on:change={addSelection}></svelte:component>
|
|
130
138
|
|
|
131
139
|
<style>
|
|
132
140
|
fieldset {
|
|
@@ -18,6 +18,7 @@ export let cover = false;
|
|
|
18
18
|
export let showSelected = true;
|
|
19
19
|
export let width = undefined;
|
|
20
20
|
export let computedalign = new Store({ valign: 'bottom', halign: 'left' });
|
|
21
|
+
export let loading = false;
|
|
21
22
|
export let usePortal = undefined;
|
|
22
23
|
export let emptyText = undefined;
|
|
23
24
|
export let value = undefined;
|
|
@@ -145,7 +146,8 @@ let blurTimer;
|
|
|
145
146
|
function onbuttonclick(e) {
|
|
146
147
|
e.preventDefault();
|
|
147
148
|
cancelAnimationFrame(blurTimer);
|
|
148
|
-
|
|
149
|
+
if (!loading)
|
|
150
|
+
menushown = !menushown;
|
|
149
151
|
}
|
|
150
152
|
async function onblur(e) {
|
|
151
153
|
// tabindex=-1 on our menu elements means e.relatedTarget will be set
|
|
@@ -197,6 +199,7 @@ $: hasSelected = showSelected && items.some(itm => itm.value === value);
|
|
|
197
199
|
<ul bind:this={menuelement} id={menuid} role='listbox' style={width ? `width: ${width}` : ''} class={menuClass} class:hasSelected class:defaultmenu={!menuClass && !menuContainerClass} on:keydown={onkeydown}>
|
|
198
200
|
{#each items as item, i (item.value)}
|
|
199
201
|
{#if showSelected || item.value !== value}
|
|
202
|
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
200
203
|
<li
|
|
201
204
|
id={`${menuid}-${i}`}
|
|
202
205
|
bind:this={itemelements[i]}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@txstate-mws/svelte-components",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"description": "Svelte components that are generically useful.",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@txstate-mws/svelte-store": "^1.0.3",
|
|
7
7
|
"tabbable": "^6.0.0",
|
|
8
|
-
"txstate-utils": "^1.7.
|
|
8
|
+
"txstate-utils": "^1.7.12"
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@playwright/test": "^1.18.1",
|
package/util/util.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export declare function classes(...classNames: (string | undefined)[]): string;
|
|
2
2
|
export declare function debounced(fn: Function, timeout: number): (...args: any[]) => void;
|
|
3
|
-
export declare function debouncedPromise<T>(fn: (..._: any) => Promise<T> | T, timeout: number, defaultValue: T): (..._: any) => Promise<T>;
|
|
4
3
|
export declare function modifierKey(e: any): any;
|
|
5
4
|
export declare function selectionIsLeft(ele: HTMLInputElement | HTMLTextAreaElement): boolean;
|
package/util/util.js
CHANGED
|
@@ -8,23 +8,6 @@ export function debounced(fn, timeout) {
|
|
|
8
8
|
timer = setTimeout(() => fn(...args), timeout);
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
export function debouncedPromise(fn, timeout, defaultValue) {
|
|
12
|
-
let version = 0;
|
|
13
|
-
return async (...args) => {
|
|
14
|
-
const myversion = ++version;
|
|
15
|
-
return await new Promise(resolve => {
|
|
16
|
-
setTimeout(() => {
|
|
17
|
-
if (version === myversion) {
|
|
18
|
-
version = 0;
|
|
19
|
-
resolve(fn(...args));
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
resolve(defaultValue);
|
|
23
|
-
}
|
|
24
|
-
}, timeout);
|
|
25
|
-
});
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
11
|
export function modifierKey(e) {
|
|
29
12
|
return e.ctrlKey || e.altKey || e.metaKey || e.key === 'Insert';
|
|
30
13
|
}
|