@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.
@@ -1,8 +1,8 @@
1
1
  <script>import { createEventDispatcher } from 'svelte';
2
- import { randomid, sleep } from 'txstate-utils';
2
+ import { randomid, Cache } from 'txstate-utils';
3
3
  import ScreenReaderOnly from './ScreenReaderOnly.svelte';
4
4
  import DefaultPopupMenu from './PopupMenu.svelte';
5
- import { debouncedPromise, modifierKey, selectionIsLeft } from '../util';
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
- $: debouncedGetOptions = debouncedPromise(getOptions, 100, []);
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
- const saveval = inputvalue;
29
- const rawOptions = await debouncedGetOptions(saveval);
30
- if (inputvalue !== saveval)
31
- return; // ignore any results that are out of date
32
- options = rawOptions.filter(o => !selectedSet.has(o.value));
33
- await sleep(100);
34
- // needed to sleep a little bit or this will turn the menu on only to have popupmenu's click handler toggle it back off
35
- if (typeof document !== 'undefined' && inputelement === document.activeElement)
36
- menushown = true;
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, debouncedGetOptions, selectedSet);
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(true);
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
- menushown = !menushown;
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]}
@@ -17,6 +17,7 @@ declare const __propDef: {
17
17
  showSelected?: boolean;
18
18
  width?: string | undefined;
19
19
  computedalign?: Store<GlueAlignStore>;
20
+ loading?: boolean;
20
21
  usePortal?: HTMLElement | true | undefined;
21
22
  emptyText?: string | undefined;
22
23
  value?: string | undefined;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@txstate-mws/svelte-components",
3
- "version": "1.3.2",
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.6"
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
  }