@txstate-mws/svelte-components 1.5.5 → 1.5.7

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.
@@ -0,0 +1,52 @@
1
+ <script>import { randomid } from 'txstate-utils';
2
+ import { Store } from '@txstate-mws/svelte-store';
3
+ import PopupMenu from './PopupMenu.svelte';
4
+ export let buttonelement;
5
+ export let items = [];
6
+ export let menushown = false;
7
+ export let value = undefined;
8
+ export let align = 'auto';
9
+ export let cover = false;
10
+ export let showSelected = true;
11
+ export let width = undefined;
12
+ export let computedalign = new Store({ valign: 'bottom', halign: 'left' });
13
+ export let gap = 0;
14
+ export let adjustparentheight = false;
15
+ export let usePortal = undefined;
16
+ export let loading = false;
17
+ export let hilited = undefined;
18
+ export let menuid = randomid();
19
+ export let emptyText = undefined;
20
+ export let menuContainerClass = '';
21
+ export let menuClass = '';
22
+ export let menuItemClass = '';
23
+ export let menuItemHilitedClass = '';
24
+ export let menuItemSelectedClass = '';
25
+ export let menuDividerClass = '';
26
+ const usemenurole = true;
27
+ </script>
28
+
29
+ <PopupMenu
30
+ {usemenurole}
31
+ {buttonelement}
32
+ {items}
33
+ {menushown}
34
+ {value}
35
+ {align}
36
+ {cover}
37
+ {showSelected}
38
+ {width}
39
+ {computedalign}
40
+ {gap}
41
+ {adjustparentheight}
42
+ {usePortal}
43
+ {loading}
44
+ {hilited}
45
+ {menuid}
46
+ {emptyText}
47
+ {menuContainerClass}
48
+ {menuClass}
49
+ {menuItemClass}
50
+ {menuItemHilitedClass}
51
+ {menuItemSelectedClass}
52
+ {menuDividerClass}/>
@@ -0,0 +1,40 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ import { Store } from '@txstate-mws/svelte-store';
3
+ import type { GlueAlignOpts, GlueAlignStore } from '../actions';
4
+ import type { PopupMenuTypes } from '../types';
5
+ declare const __propDef: {
6
+ props: {
7
+ buttonelement: HTMLButtonElement;
8
+ items?: PopupMenuTypes[] | undefined;
9
+ menushown?: boolean | undefined;
10
+ value?: string | undefined;
11
+ align?: GlueAlignOpts | undefined;
12
+ cover?: boolean | undefined;
13
+ showSelected?: boolean | undefined;
14
+ width?: string | undefined;
15
+ computedalign?: Store<GlueAlignStore> | undefined;
16
+ gap?: number | undefined;
17
+ adjustparentheight?: boolean | undefined;
18
+ usePortal?: HTMLElement | true | undefined;
19
+ loading?: boolean | undefined;
20
+ hilited?: number | undefined;
21
+ menuid?: string | undefined;
22
+ emptyText?: string | undefined;
23
+ menuContainerClass?: string | undefined;
24
+ menuClass?: string | undefined;
25
+ menuItemClass?: string | undefined;
26
+ menuItemHilitedClass?: string | undefined;
27
+ menuItemSelectedClass?: string | undefined;
28
+ menuDividerClass?: string | undefined;
29
+ };
30
+ events: {
31
+ [evt: string]: CustomEvent<any>;
32
+ };
33
+ slots: {};
34
+ };
35
+ export type ButtonMenuProps = typeof __propDef.props;
36
+ export type ButtonMenuEvents = typeof __propDef.events;
37
+ export type ButtonMenuSlots = typeof __propDef.slots;
38
+ export default class ButtonMenu extends SvelteComponentTyped<ButtonMenuProps, ButtonMenuEvents, ButtonMenuSlots> {
39
+ }
40
+ export {};
@@ -163,7 +163,7 @@ function triggerrecalc(width) {
163
163
  return;
164
164
  cancelAnimationFrame(timer);
165
165
  timer = requestAnimationFrame(() => {
166
- recalculate(width);
166
+ void recalculate(width);
167
167
  savewidth = width;
168
168
  });
169
169
  }
@@ -178,7 +178,7 @@ function hardrecalc() {
178
178
  function onResize(e) {
179
179
  triggerrecalc(e.detail.clientWidth ?? 0);
180
180
  }
181
- onMount(() => triggerrecalc(layoutelement.clientWidth));
181
+ onMount(() => { triggerrecalc(layoutelement.clientWidth); });
182
182
  </script>
183
183
 
184
184
  <ul class="cardlayout {className}" class:ssr style:height={ssr ? undefined : `${fullheight + gutter}px`} bind:this={layoutelement} use:resize on:resize={onResize} use:passActions={use}>
@@ -16,7 +16,12 @@ When it goes away, they'll be trapped inside the previous `FocusLock`, and so on
16
16
  const waitAtick = typeof requestAnimationFrame !== 'undefined' ? resolve => requestAnimationFrame(resolve) : resolve => resolve(0);
17
17
  </script>
18
18
 
19
- <script>export let escapable = true;
19
+ <script>import { onMount, onDestroy, createEventDispatcher } from 'svelte';
20
+ import { tabbable } from 'tabbable';
21
+ import { randomid, sleep } from 'txstate-utils';
22
+ import { buttonify } from '../actions';
23
+ import ScreenReaderOnly from './ScreenReaderOnly.svelte';
24
+ export let escapable = true;
20
25
  export let hidefocus = true;
21
26
  export let hidefocuslabel = 'focus is above modal dialog, start tabbing';
22
27
  export let initialfocus = undefined;
@@ -28,11 +33,6 @@ export let includeselector = undefined;
28
33
  let className = '';
29
34
  export { className as class };
30
35
  export let focusId = randomid();
31
- import { onMount, onDestroy, createEventDispatcher, tick } from 'svelte';
32
- import { tabbable } from 'tabbable';
33
- import { randomid, sleep } from 'txstate-utils';
34
- import { buttonify } from '../actions';
35
- import ScreenReaderOnly from './ScreenReaderOnly.svelte';
36
36
  const dispatch = createEventDispatcher();
37
37
  let lockelement;
38
38
  let abovelockelement;
@@ -91,13 +91,13 @@ onDestroy(async () => {
91
91
  });
92
92
  const setInitialFocus = () => {
93
93
  const firstfocus = lockelement ? tabbable(lockelement)[0] : undefined;
94
- if (firstfocus && firstfocus.focus) {
94
+ if (firstfocus?.focus) {
95
95
  firstfocus.focus();
96
96
  }
97
97
  };
98
98
  const setLastFocus = () => {
99
99
  const lastfocus = lockelement ? tabbable(lockelement).slice(-1)[0] : undefined;
100
- if (lastfocus && lastfocus.focus) {
100
+ if (lastfocus?.focus) {
101
101
  lastfocus.focus();
102
102
  }
103
103
  };
@@ -123,9 +123,11 @@ const windowclick = (e) => {
123
123
  };
124
124
  </script>
125
125
  <svelte:window on:mousedown={windowclick} />
126
+ <!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
126
127
  <div class={className} role="alertdialog" aria-modal="true" on:click|stopPropagation on:mousedown|stopPropagation on:keydown|stopPropagation={keydown} on:focusin={focusin}>
127
128
  <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
128
129
  <div bind:this={abovelockelement} tabindex="0"></div>
130
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
129
131
  <div bind:this={lockelement} on:keydown={keydown}>
130
132
  <!-- svelte-ignore a11y-click-events-have-key-events -->
131
133
  {#if hidefocus}<div class="hiddenfocus" use:buttonify on:blur={() => { hidefocus = false }} on:click={() => escapable && dispatch('escape')}><ScreenReaderOnly>{hidefocuslabel}{#if escapable}, click to escape or use escape key at any time{/if}</ScreenReaderOnly></div>{/if}
@@ -42,7 +42,8 @@ onMount(() => {
42
42
  </script>
43
43
 
44
44
  <!-- svelte-ignore a11y-click-events-have-key-events -->
45
- <div use:portal={usePortal} class="modal-backdrop" style:--modal-z={stackPosition * 10 + 3000} class:opaque on:mousedown|stopPropagation|preventDefault={() => escapable && endmodal()}>
45
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
46
+ <div use:portal={usePortal} class="modal-backdrop" style:--modal-z={stackPosition * 10 + 3000} class:opaque on:mousedown|stopPropagation|preventDefault={() => { escapable && endmodal() }}>
46
47
  <FocusLock bind:focusId class="modal-container {containerClass}" {includeselector} {escapable} on:escape {hidefocus} {hidefocuslabel} {returnfocusto} {initialfocus} on:focuslockupdate={onFocusLockUpdate}>
47
48
  <slot></slot>
48
49
  </FocusLock>
@@ -73,7 +73,7 @@ async function reactToInput(..._) {
73
73
  loading = false;
74
74
  }, 250);
75
75
  }
76
- $: reactToInput(inputvalue, getOptions, selectedSet);
76
+ $: void reactToInput(inputvalue, getOptions, selectedSet);
77
77
  $: availablemessage = options.filter(o => 'value' in o && o.value).length + ' autocomplete choices available';
78
78
  function addSelection(e) {
79
79
  inputvalue = '';
@@ -123,8 +123,8 @@ function inputkeydown(e) {
123
123
  e.preventDefault();
124
124
  }
125
125
  }
126
- async function inputfocus() {
127
- reactToInput();
126
+ function inputfocus() {
127
+ void reactToInput();
128
128
  }
129
129
  let popuphilited;
130
130
  $: if (popuphilited != null)
@@ -153,7 +153,7 @@ onMount(() => {
153
153
  {#each selected as option, i}
154
154
  <!-- svelte-ignore a11y-click-events-have-key-events -->
155
155
  <li id={id + option.value} role="option" tabindex="-1" class="multiselect-pill" class:hilited={hilitedpill === option.value}
156
- on:click|preventDefault|stopPropagation={() => !disabled && removeSelection(option, i, 1)} on:mousedown={e => disabled && e.preventDefault()}
156
+ on:click|preventDefault|stopPropagation={() => { !disabled && removeSelection(option, i, 1) }} on:mousedown={e => { disabled && e.preventDefault() }}
157
157
  aria-selected="true">
158
158
  {option.label || option.value}
159
159
  <i aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256"><path fill="currentColor" d="M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128L50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z"/></svg></i>
@@ -61,6 +61,7 @@ export let menuItemClass = '';
61
61
  export let menuItemHilitedClass = '';
62
62
  export let menuItemSelectedClass = '';
63
63
  export let menuDividerClass = '';
64
+ export let usemenurole = false;
64
65
  let menuelement;
65
66
  const itemelements = [];
66
67
  let firstactive = 0;
@@ -74,7 +75,7 @@ async function reactToItems(..._) {
74
75
  if (hilited && items[hilited]?.disabled)
75
76
  hilited = firstactive;
76
77
  }
77
- $: reactToItems(items, value);
78
+ $: void reactToItems(items, value);
78
79
  async function reactToMenuShown(_) {
79
80
  if (!buttonelement) {
80
81
  hilited = undefined;
@@ -99,7 +100,7 @@ async function reactToMenuShown(_) {
99
100
  buttonelement.focus();
100
101
  }
101
102
  }
102
- $: reactToMenuShown(menushown);
103
+ $: void reactToMenuShown(menushown);
103
104
  function move(idx) {
104
105
  if (!menushown)
105
106
  return;
@@ -127,7 +128,7 @@ function onkeydown(e) {
127
128
  }
128
129
  else {
129
130
  menushown = true;
130
- tick().then(() => move(firstactive));
131
+ tick().then(() => { move(firstactive); }).catch(console.error);
131
132
  }
132
133
  }
133
134
  else if (e.key === 'ArrowUp') {
@@ -140,7 +141,19 @@ function onkeydown(e) {
140
141
  }
141
142
  else {
142
143
  menushown = true;
143
- tick().then(() => move(lastactive));
144
+ tick().then(() => { move(lastactive); }).catch(console.error);
145
+ }
146
+ }
147
+ else if (e.key === 'Home') {
148
+ e.preventDefault();
149
+ if (menushown) {
150
+ move(firstactive);
151
+ }
152
+ }
153
+ else if (e.key === 'End') {
154
+ e.preventDefault();
155
+ if (menushown) {
156
+ move(lastactive);
144
157
  }
145
158
  }
146
159
  else if (e.key === 'Enter') {
@@ -154,7 +167,7 @@ function onkeydown(e) {
154
167
  }
155
168
  else {
156
169
  menushown = true;
157
- tick().then(() => move(firstactive));
170
+ tick().then(() => { move(firstactive); }).catch(console.error);
158
171
  }
159
172
  }
160
173
  else if (e.key === ' ') {
@@ -178,7 +191,7 @@ function onkeydown(e) {
178
191
  if (buttonelement.tagName !== 'INPUT') {
179
192
  e.preventDefault();
180
193
  menushown = true;
181
- tick().then(() => move(firstactive));
194
+ tick().then(() => { move(firstactive); }).catch(console.error);
182
195
  }
183
196
  }
184
197
  }
@@ -195,7 +208,7 @@ function onbuttonclick(e) {
195
208
  if (!loading)
196
209
  menushown = !menushown;
197
210
  }
198
- async function onblur(e) {
211
+ function onblur(e) {
199
212
  // tabindex=-1 on our menu elements means e.relatedTarget will be set
200
213
  if (!(e.relatedTarget instanceof HTMLElement && menuelement?.contains(e.relatedTarget))) {
201
214
  blurTimer = requestAnimationFrame(() => { if (buttonelement !== document.activeElement)
@@ -217,7 +230,7 @@ function cleanup(element) {
217
230
  element.removeAttribute('aria-activedescendant');
218
231
  }
219
232
  }
220
- onDestroy(() => cleanup(buttonelement));
233
+ onDestroy(() => { cleanup(buttonelement); });
221
234
  // if buttonelement changes we need to handle listeners and aria
222
235
  let lastbuttonelement;
223
236
  let observers = [];
@@ -225,7 +238,8 @@ function reactToButtonElement(buttonelement) {
225
238
  cleanup(lastbuttonelement);
226
239
  lastbuttonelement = buttonelement;
227
240
  if (buttonelement) {
228
- buttonelement.setAttribute('aria-haspopup', 'listbox');
241
+ buttonelement.setAttribute('aria-haspopup', (usemenurole ? 'true' : 'listbox'));
242
+ buttonelement.setAttribute('aria-expanded', menushown ? 'true' : 'false');
229
243
  buttonelement.addEventListener('click', onbuttonclick);
230
244
  buttonelement.addEventListener('keydown', onkeydown);
231
245
  buttonelement.addEventListener('blur', onblur);
@@ -259,7 +273,7 @@ $: hasSelected = showSelected && items.some(itm => 'value' in itm && itm.value =
259
273
  <div use:portal={usePortal === true ? undefined : (usePortal || null)}
260
274
  use:glue={{ target: buttonelement, align, cover, gap, adjustparentheight, store: computedalign }}
261
275
  class={menuContainerClass}>
262
- <ul bind:this={menuelement} id={menuid} role='listbox' style={width ? `width: ${width}` : ''}
276
+ <ul bind:this={menuelement} id={menuid} role={usemenurole ? 'menu' : 'listbox'} style={width ? `width: ${width}` : ''}
263
277
  class={menuClass} class:hasSelected class:defaultmenu={!menuClass && !menuContainerClass}
264
278
  on:keydown={onkeydown}>
265
279
  {#each items as item, i ('value' in item ? item.value : `popupmenu_divider_${i}`)}
@@ -273,17 +287,18 @@ $: hasSelected = showSelected && items.some(itm => 'value' in itm && itm.value =
273
287
  class:hilited={!menuItemHilitedClass && i === hilited}
274
288
  class:selected={showSelected && !menuItemSelectedClass && value === item.value}
275
289
  on:click={onclick(item)}
276
- role='option'
290
+ role={usemenurole ? 'menuitem' : 'option'}
277
291
  tabindex=-1
278
- aria-selected={value === item.value}
292
+ aria-selected={usemenurole ? undefined : (value === item.value)}
279
293
  aria-disabled={item.disabled}
280
294
  ><slot {item} label={item.label || item.value} hilited={i === hilited} selected={value === item.value}>{item.label || item.value}</slot></li>
281
295
  {:else if 'divider' in item && item.divider}
296
+ <!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
282
297
  <li class={`divider ${menuDividerClass}`} on:mousedown|stopPropagation|preventDefault class:group={isNotBlank(item.label)}>{item.label}</li>
283
298
  {/if}
284
299
  {/each}
285
300
  {#if items.length === 0}
286
- <li role="option" class={`${menuItemClass} disabled`} aria-live="assertive" aria-selected={false}>
301
+ <li role={usemenurole ? 'menuitem' : 'option'} class={`${menuItemClass} disabled`} aria-live="assertive" aria-selected={usemenurole ? undefined : false}>
287
302
  <slot name="noresults">
288
303
  {#if !emptyText}
289
304
  <span aria-hidden="true">{'¯\\_(ツ)_/¯'}</span><ScreenReaderOnly>{emptyText || 'no results found'}</ScreenReaderOnly>
@@ -42,6 +42,7 @@ declare const __propDef: {
42
42
  menuItemHilitedClass?: string | undefined;
43
43
  menuItemSelectedClass?: string | undefined;
44
44
  menuDividerClass?: string | undefined;
45
+ usemenurole?: boolean | undefined;
45
46
  };
46
47
  events: {
47
48
  mousedown: MouseEvent;
@@ -1,9 +1,11 @@
1
+ export { default as ButtonMenu } from './ButtonMenu.svelte';
1
2
  export { default as CardLayout } from './CardLayout.svelte';
2
3
  export { default as Card } from './Card.svelte';
3
4
  export { default as CollapsingTable } from './CollapsingTable.svelte';
4
5
  export { default as ConditionalWrapper } from './ConditionalWrapper.svelte';
5
6
  export { default as FocusLock } from './FocusLock.svelte';
6
7
  export { default as Loading } from './Loading.svelte';
8
+ export { default as Lottie } from './Lottie.svelte';
7
9
  export { default as Modal } from './Modal.svelte';
8
10
  export { default as MultiSelect } from './MultiSelect.svelte';
9
11
  export { default as PopupMenu } from './PopupMenu.svelte';
@@ -1,9 +1,11 @@
1
+ export { default as ButtonMenu } from './ButtonMenu.svelte';
1
2
  export { default as CardLayout } from './CardLayout.svelte';
2
3
  export { default as Card } from './Card.svelte';
3
4
  export { default as CollapsingTable } from './CollapsingTable.svelte';
4
5
  export { default as ConditionalWrapper } from './ConditionalWrapper.svelte';
5
6
  export { default as FocusLock } from './FocusLock.svelte';
6
7
  export { default as Loading } from './Loading.svelte';
8
+ export { default as Lottie } from './Lottie.svelte';
7
9
  export { default as Modal } from './Modal.svelte';
8
10
  export { default as MultiSelect } from './MultiSelect.svelte';
9
11
  export { default as PopupMenu } from './PopupMenu.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@txstate-mws/svelte-components",
3
- "version": "1.5.5",
3
+ "version": "1.5.7",
4
4
  "description": "Svelte components that are generically useful.",
5
5
  "scripts": {
6
6
  "prepublishOnly": "svelte-package",
@@ -26,7 +26,7 @@
26
26
  "@sveltejs/package": "^2.0.2",
27
27
  "@types/tabbable": "^3.1.2",
28
28
  "eslint-config-standard-with-typescript": "^39.0.0",
29
- "eslint-plugin-svelte3": "^4.0.0",
29
+ "eslint-plugin-svelte": "^2.0.0",
30
30
  "svelte-check": "^3.0.1",
31
31
  "svelte-preprocess": "^5.0.0",
32
32
  "svelte2tsx": "^0.6.0",