@txstate-mws/svelte-components 1.6.9 → 1.6.11

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.
@@ -20,17 +20,18 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
20
20
  }
21
21
  }
22
22
  let lastrect;
23
+ let timer;
23
24
  function reposition() {
24
25
  const fixedParent = fixedContainingBlock(el);
25
26
  let fixedRect = { left: 0, top: 0, right: 0, bottom: 0 };
26
27
  if (fixedParent) {
27
28
  const tmpRect = fixedParent.getBoundingClientRect();
28
- fixedRect = { left: tmpRect.left, right: document.documentElement.clientWidth - tmpRect.right, top: tmpRect.top, bottom: window.innerHeight - tmpRect.bottom };
29
+ fixedRect = { left: tmpRect.left, right: document.documentElement.clientWidth - tmpRect.right, top: tmpRect.top, bottom: document.documentElement.clientHeight - tmpRect.bottom };
29
30
  }
30
31
  if (!target)
31
32
  return;
32
33
  const tmpRect = target.getBoundingClientRect();
33
- const rect = { left: tmpRect.left - fixedRect.left, right: document.documentElement.clientWidth - tmpRect.right - fixedRect.right, top: tmpRect.top - fixedRect.top, bottom: window.innerHeight - tmpRect.bottom - fixedRect.bottom, width: tmpRect.width, height: tmpRect.height };
34
+ const rect = { left: tmpRect.left - fixedRect.left, right: document.documentElement.clientWidth - tmpRect.right - fixedRect.right, top: tmpRect.top - fixedRect.top, bottom: document.documentElement.clientHeight - tmpRect.bottom - fixedRect.bottom, width: tmpRect.width, height: tmpRect.height };
34
35
  if (equal(rect, lastrect))
35
36
  return;
36
37
  let autoalign = align;
@@ -41,7 +42,8 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
41
42
  }
42
43
  const targetHeight = rect.height;
43
44
  if (autoalign === 'bottomleft') {
44
- requestAnimationFrame(() => {
45
+ cancelAnimationFrame(timer);
46
+ timer = requestAnimationFrame(() => {
45
47
  el.style.top = `${rect.top + (cover ? 0 : targetHeight) + gap}px`;
46
48
  el.style.left = `${rect.left}px`;
47
49
  el.style.removeProperty('bottom');
@@ -52,7 +54,8 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
52
54
  halign = 'left';
53
55
  }
54
56
  else if (autoalign === 'bottomright') {
55
- requestAnimationFrame(() => {
57
+ cancelAnimationFrame(timer);
58
+ timer = requestAnimationFrame(() => {
56
59
  el.style.top = `${rect.top + (cover ? 0 : targetHeight) + gap}px`;
57
60
  el.style.removeProperty('left');
58
61
  el.style.removeProperty('bottom');
@@ -63,7 +66,8 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
63
66
  halign = 'right';
64
67
  }
65
68
  else if (autoalign === 'topleft') {
66
- requestAnimationFrame(() => {
69
+ cancelAnimationFrame(timer);
70
+ timer = requestAnimationFrame(() => {
67
71
  el.style.removeProperty('top');
68
72
  el.style.left = `${rect.left}px`;
69
73
  el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight) + gap}px`;
@@ -74,7 +78,8 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
74
78
  halign = 'left';
75
79
  }
76
80
  else if (autoalign === 'topright') {
77
- requestAnimationFrame(() => {
81
+ cancelAnimationFrame(timer);
82
+ timer = requestAnimationFrame(() => {
78
83
  el.style.removeProperty('top');
79
84
  el.style.removeProperty('left');
80
85
  el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight) + gap}px`;
@@ -25,7 +25,7 @@ export let menuClass = '';
25
25
  export let menuItemClass = '';
26
26
  export let menuItemHilitedClass = '';
27
27
  export let inputClass = '';
28
- export let menuDividerClass = '';
28
+ export let menuCategoryClass = '';
29
29
  /** The maximum number of selections allowed before making new selections is disabled. Default of 0 is unlimited. */
30
30
  export let maxSelections = 0;
31
31
  /** Consuming components may need to make decisions about what to display or return as options in the popup based
@@ -41,6 +41,13 @@ export let usePortal = undefined;
41
41
  export let descid = undefined;
42
42
  /** You can define your own PopupMenu and pass for that to be used or accept DefaultPopupMenu. */
43
43
  export let PopupMenu = DefaultPopupMenu;
44
+ /** Add a Delete All button to clear all selected items */
45
+ export let includeDeleteAll = false;
46
+ /** Show a confirmation message before clearing all selections */
47
+ export let confirmDelete = undefined;
48
+ /** A function that can be used to customize the labels on the selected item pills. Default is a function
49
+ * that takes a PopupMenuItem and returns its label, if available, or its value when no label is provided. */
50
+ export let selectedItemLabel = (item) => item.label || item.value;
44
51
  let menushown;
45
52
  let loading = false;
46
53
  let options = [];
@@ -137,6 +144,20 @@ function inputblur() {
137
144
  active = false;
138
145
  dispatch('blur');
139
146
  }
147
+ function removeAllSelections() {
148
+ selected = [];
149
+ dispatch('change', selected);
150
+ }
151
+ function confirmRemoveAllSelections() {
152
+ if (confirmDelete) {
153
+ if (confirm(confirmDelete)) {
154
+ removeAllSelections();
155
+ }
156
+ }
157
+ else {
158
+ removeAllSelections();
159
+ }
160
+ }
140
161
  let popuphilited;
141
162
  $: if (popuphilited != null)
142
163
  hilitedpill = undefined;
@@ -161,6 +182,13 @@ onMount(() => {
161
182
  </script>
162
183
 
163
184
  <div class="multiselect" class:disabled role="combobox" aria-controls={menushown ? menuid : undefined} aria-expanded={menushown}>
185
+ {#if includeDeleteAll && selected.length}
186
+ <div class="delete-all-container">
187
+ <button on:click|preventDefault|stopPropagation={confirmRemoveAllSelections}>
188
+ <slot name="deleteall">Delete All <ScreenReaderOnly>{selected.length} items</ScreenReaderOnly></slot>
189
+ </button>
190
+ </div>
191
+ {/if}
164
192
  {#if selected.length}
165
193
  <div class="multiselect-selected" role="listbox" aria-label="selected options">
166
194
  {#each selected as option, i (option.value)}
@@ -168,7 +196,7 @@ onMount(() => {
168
196
  <div id={id + option.value} role="option" tabindex="-1" class="multiselect-pill" class:hilited={hilitedpill === option.value}
169
197
  on:click|preventDefault|stopPropagation={() => { !disabled && removeSelection(option, i, 1) }} on:mousedown={e => { disabled && e.preventDefault() }}
170
198
  aria-selected="true" aria-posinset={i + 1} aria-setsize={selected.length}>
171
- {option.label || option.value}
199
+ {selectedItemLabel(option)}
172
200
  <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>
173
201
  <ScreenReaderOnly>, click to deselect</ScreenReaderOnly>
174
202
  </div>
@@ -188,7 +216,7 @@ onMount(() => {
188
216
 
189
217
  <svelte:component this={PopupMenu} bind:menushown bind:menuid bind:hilited={popuphilited} bind:value={popupvalue} align='bottomleft'
190
218
  {usePortal} {loading} {emptyText} {gap}
191
- {menuContainerClass} {menuClass} {menuItemClass} {menuItemHilitedClass} {menuDividerClass}
219
+ {menuContainerClass} {menuClass} {menuItemClass} {menuItemHilitedClass} {menuCategoryClass}
192
220
  items={options} buttonelement={inputelement}
193
221
  on:change={addSelection}/>
194
222
 
@@ -257,4 +285,9 @@ onMount(() => {
257
285
  border: var(--multiselect-pill-selected-border, 1px solid transparent);
258
286
  color: var(--multiselect-pill-selected-text, white);
259
287
  }
288
+ .delete-all-container {
289
+ width: 100%;
290
+ display: flex;
291
+ justify-content: flex-end;
292
+ }
260
293
  </style>
@@ -1,13 +1,13 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  import DefaultPopupMenu from './PopupMenu.svelte';
3
- import type { PopupMenuItem, PopupMenuTypes } from '../types';
3
+ import type { PopupMenuItem } from '../types';
4
4
  declare const __propDef: {
5
5
  props: {
6
6
  name: string;
7
7
  /**
8
8
  * Function to pass to the component that tells it how to use the text in the text input to determine what
9
9
  * `PopupMenuItem[]` should be displayed in the `PopupMenu`. Items already 'selected' from the popup menu will
10
- * be tracked and automatically filtered from the popup if returned as one of the `PopupMenuItem[]` by `getOptions`. */ getOptions: (search: string) => Promise<PopupMenuTypes[]> | PopupMenuTypes[];
10
+ * be tracked and automatically filtered from the popup if returned as one of the `PopupMenuItem[]` by `getOptions`. */ getOptions: (search: string) => Promise<PopupMenuItem[]> | PopupMenuItem[];
11
11
  id?: string;
12
12
  disabled?: boolean;
13
13
  menuContainerClass?: string;
@@ -15,7 +15,7 @@ declare const __propDef: {
15
15
  menuItemClass?: string;
16
16
  menuItemHilitedClass?: string;
17
17
  inputClass?: string;
18
- menuDividerClass?: string;
18
+ menuCategoryClass?: string;
19
19
  /** The maximum number of selections allowed before making new selections is disabled. Default of 0 is unlimited. */ maxSelections?: number;
20
20
  /** Consuming components may need to make decisions about what to display or return as options in the popup based
21
21
  * on what's already selected. This component property can be used to both pass an initial set of selections but
@@ -26,6 +26,10 @@ declare const __propDef: {
26
26
  usePortal?: HTMLElement | true | undefined;
27
27
  descid?: string | undefined;
28
28
  /** You can define your own PopupMenu and pass for that to be used or accept DefaultPopupMenu. */ PopupMenu?: typeof DefaultPopupMenu;
29
+ /** Add a Delete All button to clear all selected items */ includeDeleteAll?: boolean;
30
+ /** Show a confirmation message before clearing all selections */ confirmDelete?: string | undefined;
31
+ /** A function that can be used to customize the labels on the selected item pills. Default is a function
32
+ * that takes a PopupMenuItem and returns its label, if available, or its value when no label is provided. */ selectedItemLabel?: (item: PopupMenuItem) => string;
29
33
  };
30
34
  events: {
31
35
  change: CustomEvent<any>;
@@ -35,6 +39,7 @@ declare const __propDef: {
35
39
  [evt: string]: CustomEvent<any>;
36
40
  };
37
41
  slots: {
42
+ deleteall: {};
38
43
  default: {};
39
44
  };
40
45
  };
@@ -3,7 +3,7 @@
3
3
  bound to `buttonelement`. It can also be controlled from the parent, by binding the `menushown` boolean.
4
4
  -->
5
5
  <script lang="ts">import { createEventDispatcher, onDestroy, tick } from 'svelte';
6
- import { isNotBlank, randomid } from 'txstate-utils';
6
+ import { groupby, isNull, randomid } from 'txstate-utils';
7
7
  import { Store } from '@txstate-mws/svelte-store';
8
8
  import { glue, portal } from '../actions';
9
9
  import ScreenReaderOnly from './ScreenReaderOnly.svelte';
@@ -60,22 +60,40 @@ export let menuClass = '';
60
60
  export let menuItemClass = '';
61
61
  export let menuItemHilitedClass = '';
62
62
  export let menuItemSelectedClass = '';
63
- export let menuDividerClass = '';
63
+ export let menuCategoryClass = '';
64
64
  export let hideSelectedIndicator = false;
65
65
  export let hideEmptyText = false;
66
+ /** If true, the role for the popup menu will be 'menu' instead of the default 'listbox' value */
66
67
  export let usemenurole = false;
67
68
  let menuelement;
68
69
  const itemelements = [];
69
70
  let firstactive = 0;
70
71
  let lastactive = items.length - 1;
71
- $: hasMeaningfulItems = showSelected ? !!items.filter(itm => 'value' in itm).length : !!items.filter(itm => 'value' in itm && itm.value !== value).length;
72
+ $: hasMeaningfulItems = showSelected ? !!items.length : !!items.filter(itm => itm.value !== value).length;
72
73
  function hiddenItem(item) {
73
74
  return !!item && !showSelected && item.value === value;
74
75
  }
76
+ $: grouped = groupby(items, item => item.group ?? 'default-group');
77
+ let itemsOrderedByGroup = [];
78
+ function getItemIndex(item) {
79
+ return itemsOrderedByGroup.findIndex(itm => {
80
+ // need to handle default group options
81
+ if (itm.value === item.value) {
82
+ if (item.group === 'default-group') {
83
+ return isNull(itm.group);
84
+ }
85
+ else {
86
+ return item.group === itm.group;
87
+ }
88
+ }
89
+ return false;
90
+ });
91
+ }
75
92
  async function reactToItems(..._) {
76
- firstactive = items.findIndex(itm => 'value' in itm && !itm.disabled && !hiddenItem(itm));
77
- lastactive = items.length - [...items].reverse().findIndex(itm => 'value' in itm && !itm.disabled && !hiddenItem(itm)) - 1;
78
- if (hilited && items[hilited]?.disabled)
93
+ itemsOrderedByGroup = Object.values(grouped).flat();
94
+ firstactive = itemsOrderedByGroup.findIndex(itm => 'value' in itm && !itm.disabled && !hiddenItem(itm));
95
+ lastactive = itemsOrderedByGroup.length - [...itemsOrderedByGroup].reverse().findIndex(itm => 'value' in itm && !itm.disabled && !hiddenItem(itm)) - 1;
96
+ if (hilited && (itemsOrderedByGroup[hilited])?.disabled)
79
97
  hilited = firstactive;
80
98
  }
81
99
  $: void reactToItems(items, value);
@@ -107,9 +125,7 @@ $: void reactToMenuShown(menushown);
107
125
  function move(idx) {
108
126
  if (!menushown)
109
127
  return;
110
- while (idx <= lastactive && items[idx] != null && 'divider' in items[idx])
111
- idx++;
112
- if (items[idx] == null || items[idx]?.disabled)
128
+ if (itemsOrderedByGroup[idx] == null || (itemsOrderedByGroup[idx])?.disabled)
113
129
  return;
114
130
  hilited = Math.max(firstactive, Math.min(lastactive, idx));
115
131
  itemelements[hilited].scrollIntoView({ block: 'center' });
@@ -125,7 +141,7 @@ function onkeydown(e) {
125
141
  e.preventDefault();
126
142
  if (menushown) {
127
143
  let i = (hilited ?? firstactive - 1) + 1;
128
- while (items[i] && !isSelectable(items[i]))
144
+ while (itemsOrderedByGroup[i] && !isSelectable(itemsOrderedByGroup[i]))
129
145
  i++;
130
146
  move(i);
131
147
  }
@@ -138,7 +154,7 @@ function onkeydown(e) {
138
154
  e.preventDefault();
139
155
  if (menushown) {
140
156
  let i = (hilited ?? lastactive + 1) - 1;
141
- while (items[i] && !isSelectable(items[i]))
157
+ while (itemsOrderedByGroup[i] && !isSelectable(itemsOrderedByGroup[i]))
142
158
  i--;
143
159
  move(i);
144
160
  }
@@ -164,8 +180,8 @@ function onkeydown(e) {
164
180
  if (menushown) {
165
181
  menushown = false;
166
182
  if (typeof hilited !== 'undefined') {
167
- value = items[hilited]?.value;
168
- dispatch('change', items[hilited]);
183
+ value = (itemsOrderedByGroup[hilited])?.value;
184
+ dispatch('change', itemsOrderedByGroup[hilited]);
169
185
  }
170
186
  }
171
187
  else {
@@ -180,8 +196,8 @@ function onkeydown(e) {
180
196
  if (typeof hilited !== 'undefined') {
181
197
  e.preventDefault();
182
198
  menushown = false;
183
- value = items[hilited]?.value;
184
- dispatch('change', items[hilited]);
199
+ value = (itemsOrderedByGroup[hilited])?.value;
200
+ dispatch('change', itemsOrderedByGroup[hilited]);
185
201
  }
186
202
  else {
187
203
  if (buttonelement.tagName !== 'INPUT') {
@@ -247,6 +263,9 @@ function reactToButtonElement(buttonelement) {
247
263
  if (buttonelement) {
248
264
  buttonelement.setAttribute('aria-haspopup', (usemenurole ? 'true' : 'listbox'));
249
265
  buttonelement.setAttribute('aria-expanded', menushown ? 'true' : 'false');
266
+ if (!usemenurole) {
267
+ buttonelement.setAttribute('role', 'combobox');
268
+ }
250
269
  buttonelement.addEventListener('click', onbuttonclick);
251
270
  buttonelement.addEventListener('keydown', onkeydown);
252
271
  buttonelement.addEventListener('blur', onblur);
@@ -255,10 +274,10 @@ function reactToButtonElement(buttonelement) {
255
274
  for (const parent of [...parents, undefined]) {
256
275
  const observer = new IntersectionObserver((entries) => {
257
276
  for (const entry of entries) {
258
- if (entry.intersectionRatio < 1)
277
+ if (entry.intersectionRatio < 0.1)
259
278
  menushown = false;
260
279
  }
261
- }, { root: parent, threshold: 1 });
280
+ }, { root: parent, threshold: 0.1 });
262
281
  observer.observe(buttonelement);
263
282
  observers.push(observer);
264
283
  }
@@ -278,41 +297,62 @@ $: hasSelected = showSelected && !hideSelectedIndicator && items.some(itm => 'va
278
297
  </script>
279
298
 
280
299
  {#if menushown && !loading && (hasMeaningfulItems || !hideEmptyText)}
281
- <div use:portal={usePortal === true ? undefined : (usePortal || null)}
282
- use:glue={{ target: buttonelement, align, cover, gap, adjustparentheight, store: computedalign }}
283
- class={menuContainerClass}>
284
- <ul bind:this={menuelement} id={menuid} role={usemenurole ? 'menu' : 'listbox'} tabindex="-1" style={width ? `width: ${width}` : ''}
285
- class={menuClass} class:hasSelected class:defaultmenu={!menuClass && !menuContainerClass}
286
- on:keydown={onkeydown}>
287
- {#each items as item, i ('value' in item ? item.value : `popupmenu_divider_${i}`)}
288
- {#if 'value' in item && (showSelected || item.value !== value)}
289
- <!-- svelte-ignore a11y-click-events-have-key-events -->
290
- <li
291
- id={`${menuid}-${i}`}
292
- bind:this={itemelements[i]}
293
- class={`${menuItemClass} ${i === hilited ? menuItemHilitedClass : ''} ${value === item.value ? menuItemSelectedClass : ''}`}
294
- class:disabled={!!item.disabled}
295
- class:hilited={!menuItemHilitedClass && i === hilited}
296
- class:selected={showSelected && !hideSelectedIndicator && value === item.value}
297
- on:click={onclick(item)}
298
- role={usemenurole ? 'menuitem' : 'option'}
299
- tabindex=-1
300
- aria-selected={usemenurole ? undefined : (value === item.value)}
301
- aria-disabled={item.disabled}
302
- ><slot {item} label={item.label || item.value} hilited={i === hilited} selected={value === item.value}>{item.label || item.value}</slot></li>
303
- {:else if 'divider' in item && item.divider}
304
- <!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
305
- <li class={`divider ${menuDividerClass}`} on:mousedown|stopPropagation|preventDefault={() => {}} class:group={isNotBlank(item.label)}>{item.label}</li>
300
+ <div use:portal={usePortal === true ? undefined : (usePortal || null)} use:glue={{ target: buttonelement, align, cover, gap, adjustparentheight, store: computedalign }} class={menuContainerClass}>
301
+ <ul bind:this={menuelement} id={menuid} role={usemenurole ? 'menu' : 'listbox'} tabindex="-1" style={width ? `width: ${width}` : ''} class={menuClass} class:hasSelected class:defaultmenu={!menuClass && !menuContainerClass} on:keydown={onkeydown}>
302
+ {#each Object.entries(grouped) as [group, groupitems]}
303
+ {#if group === 'default-group'}
304
+ {#each groupitems as item, i (item.value)}
305
+ {@const itemIndex = getItemIndex(item)}
306
+ {#if (showSelected || item.value !== value)}
307
+ <li
308
+ id={`${menuid}-${itemIndex}`}
309
+ bind:this={itemelements[itemIndex]}
310
+ class={`${menuItemClass} ${itemIndex === hilited ? menuItemHilitedClass : ''} ${value === item.value ? menuItemSelectedClass : ''}`}
311
+ class:disabled={!!item.disabled}
312
+ class:hilited={!menuItemHilitedClass && itemIndex === hilited}
313
+ class:selected={showSelected && !hideSelectedIndicator && value === item.value}
314
+ on:click={onclick(item)}
315
+ role={usemenurole ? 'menuitem' : 'option'}
316
+ tabindex=-1
317
+ aria-selected={usemenurole ? undefined : (value === item.value)}
318
+ aria-disabled={item.disabled}
319
+ ><slot {item} label={item.label || item.value} hilited={itemIndex === hilited} selected={value === item.value}>{item.label || item.value}</slot></li>
320
+ {/if}
321
+ {/each}
322
+ {:else}
323
+ <li role="group" aria-labelledby="{menuid}-{group}" class="group">
324
+ <span id="{menuid}-{group}" class="category {menuCategoryClass}">{group}</span>
325
+ <ul role="presentation">
326
+ {#each groupitems as item, i (item.value)}
327
+ {@const itemIndex = getItemIndex(item)}
328
+ {#if (showSelected || item.value !== value)}
329
+ <li
330
+ id={`${menuid}-${itemIndex}`}
331
+ bind:this={itemelements[itemIndex]}
332
+ class={`${menuItemClass} ${itemIndex === hilited ? menuItemHilitedClass : ''} ${value === item.value ? menuItemSelectedClass : ''}`}
333
+ class:disabled={!!item.disabled}
334
+ class:hilited={!menuItemHilitedClass && itemIndex === hilited}
335
+ class:selected={showSelected && !hideSelectedIndicator && value === item.value}
336
+ on:click={onclick(item)}
337
+ role={usemenurole ? 'menuitem' : 'option'}
338
+ tabindex=-1
339
+ aria-selected={usemenurole ? undefined : (value === item.value)}
340
+ aria-disabled={item.disabled}
341
+ ><slot {item} label={item.label || item.value} hilited={itemIndex === hilited} selected={value === item.value}>{item.label || item.value}</slot></li>
342
+ {/if}
343
+ {/each}
344
+ </ul>
345
+ </li>
306
346
  {/if}
307
347
  {/each}
308
348
  {#if !hasMeaningfulItems}
309
349
  <li role={usemenurole ? 'menuitem' : 'option'} class={`${menuItemClass} disabled`} aria-live="assertive" aria-selected={usemenurole ? undefined : false}>
310
350
  <slot name="noresults">
311
- {#if !emptyText}
312
- <span aria-hidden="true">{'¯\\_(ツ)_/¯'}</span><ScreenReaderOnly>{emptyText || 'no results found'}</ScreenReaderOnly>
313
- {:else}
314
- {emptyText}
315
- {/if}
351
+ {#if !emptyText}
352
+ <span aria-hidden="true">{'¯\\_(ツ)_/¯'}</span><ScreenReaderOnly>{emptyText || 'no results found'}</ScreenReaderOnly>
353
+ {:else}
354
+ {emptyText}
355
+ {/if}
316
356
  </slot>
317
357
  </li>
318
358
  {/if}
@@ -389,4 +429,11 @@ $: hasSelected = showSelected && !hideSelectedIndicator && items.some(itm => 'va
389
429
  border-right: 0.2em solid;
390
430
  box-sizing: border-box;
391
431
  }
432
+ span.category {
433
+ display: block;
434
+ font-weight: bold;
435
+ }
436
+ .group ul {
437
+ padding-inline-start: 20px;
438
+ }
392
439
  </style>
@@ -1,13 +1,13 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  import { Store } from '@txstate-mws/svelte-store';
3
3
  import type { GlueAlignOpts, GlueAlignStore } from '../actions';
4
- import type { PopupMenuItem, PopupMenuTypes } from '../types';
4
+ import type { PopupMenuItem } from '../types';
5
5
  declare const __propDef: {
6
6
  props: {
7
7
  /** The DOM element that will act as the "button" for this menu. The menu will be placed next to the
8
8
  button and be controlled by the button. Keyboard access will be enabled when the button has focus.
9
9
  This component adds all appropriate attributes (tabindex, roles, and aria) automatically. */ buttonelement: HTMLElement;
10
- /** The list of menu items to be shown. Parent may change this at any time based on user activity. */ items?: PopupMenuTypes[];
10
+ /** The list of menu items to be shown. Parent may change this at any time based on user activity. */ items?: PopupMenuItem[];
11
11
  menushown?: boolean;
12
12
  value?: string | undefined;
13
13
  /** Control where the menu will appear. Default is to use the current viewport to make a decision to
@@ -41,15 +41,15 @@ declare const __propDef: {
41
41
  menuItemClass?: string;
42
42
  menuItemHilitedClass?: string;
43
43
  menuItemSelectedClass?: string;
44
- menuDividerClass?: string;
44
+ menuCategoryClass?: string;
45
45
  hideSelectedIndicator?: boolean;
46
46
  hideEmptyText?: boolean;
47
- usemenurole?: boolean;
47
+ /** If true, the role for the popup menu will be 'menu' instead of the default 'listbox' value */ usemenurole?: boolean;
48
48
  };
49
49
  slots: {
50
50
  default: {
51
- item: PopupMenuTypes;
52
- label: any;
51
+ item: PopupMenuItem;
52
+ label: string;
53
53
  hilited: boolean;
54
54
  selected: boolean;
55
55
  };
@@ -7,8 +7,12 @@ export interface PopupMenuItem {
7
7
  /** Toggles the choice as one that's visible but is disabled and unclickable until conditions
8
8
  * are met that would cause it to become enabled. */
9
9
  disabled?: boolean;
10
+ /** Allows PopupMenuItems to be grouped by categories in the menu */
11
+ group?: string;
10
12
  }
11
- /** A PopupMenu list native type for displaying */
13
+ /** A PopupMenu list native type for displaying
14
+ * @note Not currently in use
15
+ */
12
16
  export interface PopupMenuDivider {
13
17
  /** Toggle for whether this instance of this type is shown. */
14
18
  divider: boolean;
@@ -23,7 +27,7 @@ export interface PopupMenuDivider {
23
27
  * @note Not currently in use. */
24
28
  export interface PopupMenuGroup {
25
29
  /** The label for the group. */
26
- lable: string;
30
+ label: string;
27
31
  /** The listing of options that would be listed under the parent `<ul>`. */
28
32
  options: PopupMenuTypes[];
29
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@txstate-mws/svelte-components",
3
- "version": "1.6.9",
3
+ "version": "1.6.11",
4
4
  "description": "Svelte components that are generically useful.",
5
5
  "scripts": {
6
6
  "prepublishOnly": "svelte-package",