@txstate-mws/svelte-components 1.4.6 → 1.4.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.
@@ -1,3 +1,6 @@
1
+ <!-- @component
2
+ [`Card`](https://github.com/txstate-etc/svelte-components/blob/main/src/lib/components/Card.svelte)
3
+ -->
1
4
  <script>export let className = '';
2
5
  import { getContext } from 'svelte';
3
6
  import { resize, passActions } from '../actions';
@@ -15,6 +15,7 @@ declare const __propDef: {
15
15
  export type CardProps = typeof __propDef.props;
16
16
  export type CardEvents = typeof __propDef.events;
17
17
  export type CardSlots = typeof __propDef.slots;
18
+ /** [`Card`](https://github.com/txstate-etc/svelte-components/blob/main/src/lib/components/Card.svelte) */
18
19
  export default class Card extends SvelteComponentTyped<CardProps, CardEvents, CardSlots> {
19
20
  }
20
21
  export {};
@@ -1,3 +1,6 @@
1
+ <!-- @component
2
+ [`CardLayout`](https://github.com/txstate-etc/svelte-components/blob/main/src/lib/components/CardLayout.svelte)
3
+ -->
1
4
  <script>import { onDestroy, tick, setContext, onMount } from 'svelte';
2
5
  import { writable } from 'svelte/store';
3
6
  import { resize, passActions } from '../actions';
@@ -18,6 +18,7 @@ declare const __propDef: {
18
18
  export type CardLayoutProps = typeof __propDef.props;
19
19
  export type CardLayoutEvents = typeof __propDef.events;
20
20
  export type CardLayoutSlots = typeof __propDef.slots;
21
+ /** [`CardLayout`](https://github.com/txstate-etc/svelte-components/blob/main/src/lib/components/CardLayout.svelte) */
21
22
  export default class CardLayout extends SvelteComponentTyped<CardLayoutProps, CardLayoutEvents, CardLayoutSlots> {
22
23
  }
23
24
  export {};
@@ -1,3 +1,10 @@
1
+ <!-- @component
2
+ [Collapsing Table](https://github.com/txstate-etc/svelte-components/blob/main/docs/CollapsingTable.md)
3
+
4
+ This component is meant to help make tables responsive by eliminating columns (from the right) as the screen width reduces.
5
+ When there are hidden columns, the last column header becomes a dropdown button allowing the selection of another column
6
+ to be displayed instead.
7
+ -->
1
8
  <script>import { Store } from '@txstate-mws/svelte-store';
2
9
  import { derived } from 'svelte/store';
3
10
  import { classes } from '../util';
@@ -54,6 +54,13 @@ declare const __propDef: {
54
54
  export type CollapsingTableProps = typeof __propDef.props;
55
55
  export type CollapsingTableEvents = typeof __propDef.events;
56
56
  export type CollapsingTableSlots = typeof __propDef.slots;
57
+ /**
58
+ * [Collapsing Table](https://github.com/txstate-etc/svelte-components/blob/main/docs/CollapsingTable.md)
59
+ *
60
+ * This component is meant to help make tables responsive by eliminating columns (from the right) as the screen width reduces.
61
+ * When there are hidden columns, the last column header becomes a dropdown button allowing the selection of another column
62
+ * to be displayed instead.
63
+ */
57
64
  export default class CollapsingTable extends SvelteComponentTyped<CollapsingTableProps, CollapsingTableEvents, CollapsingTableSlots> {
58
65
  }
59
66
  export {};
@@ -1,3 +1,27 @@
1
+ <!-- @component
2
+ [Conditional Wrapper](https://github.com/txstate-etc/svelte-components/blob/main/docs/ConditionalWrapper.md)
3
+ Sometimes you want to wrap a complex bit of HTML in a certain element based on a condition,
4
+ and not wrap it at all if the condition isn't met. Without help, this situation results in a
5
+ lot of duplicate code because you have to have an if/else block outside the whole thing, and
6
+ the complex HTML goes in both branches:
7
+ ```svelte
8
+ {#if shouldwrap}
9
+ <div class="wrapper">
10
+ ... a whole bunch of html ...
11
+ </div>
12
+ {:else}
13
+ ... a whole bunch of html, again ...
14
+ {/if}
15
+ ```
16
+ With this component, you can de-duplicate like this:
17
+ ```svelte
18
+ <ConditionalWrapper class="wrapper" condition={shouldwrap}>
19
+ ... a whole bunch of html ...
20
+ </ConditionalWrapper>
21
+ ```
22
+ Any props you provide (except the ConditionalWrapper props like `condition`) will pass through
23
+ to the wrapper element/component, if it gets inserted.
24
+ -->
1
25
  <script>export let condition;
2
26
  export let component = undefined;
3
27
  export let a = false;
@@ -3,7 +3,7 @@ declare const __propDef: {
3
3
  props: {
4
4
  [x: string]: any;
5
5
  condition: boolean | undefined;
6
- component?: Function | undefined;
6
+ component?: any | undefined;
7
7
  a?: boolean;
8
8
  span?: boolean;
9
9
  element?: HTMLElement | undefined;
@@ -18,6 +18,30 @@ declare const __propDef: {
18
18
  export type ConditionalWrapperProps = typeof __propDef.props;
19
19
  export type ConditionalWrapperEvents = typeof __propDef.events;
20
20
  export type ConditionalWrapperSlots = typeof __propDef.slots;
21
+ /**
22
+ * [Conditional Wrapper](https://github.com/txstate-etc/svelte-components/blob/main/docs/ConditionalWrapper.md)
23
+ * Sometimes you want to wrap a complex bit of HTML in a certain element based on a condition,
24
+ * and not wrap it at all if the condition isn't met. Without help, this situation results in a
25
+ * lot of duplicate code because you have to have an if/else block outside the whole thing, and
26
+ * the complex HTML goes in both branches:
27
+ * ```svelte
28
+ * {#if shouldwrap}
29
+ * <div class="wrapper">
30
+ * ... a whole bunch of html ...
31
+ * </div>
32
+ * {:else}
33
+ * ... a whole bunch of html, again ...
34
+ * {/if}
35
+ * ```
36
+ * With this component, you can de-duplicate like this:
37
+ * ```svelte
38
+ * <ConditionalWrapper class="wrapper" condition={shouldwrap}>
39
+ * ... a whole bunch of html ...
40
+ * </ConditionalWrapper>
41
+ * ```
42
+ * Any props you provide (except the ConditionalWrapper props like `condition`) will pass through
43
+ * to the wrapper element/component, if it gets inserted.
44
+ */
21
45
  export default class ConditionalWrapper extends SvelteComponentTyped<ConditionalWrapperProps, ConditionalWrapperEvents, ConditionalWrapperSlots> {
22
46
  }
23
47
  export {};
@@ -1,3 +1,17 @@
1
+ <!-- @component
2
+ The [`FocusLock`](https://github.com/txstate-etc/svelte-components/blob/main/docs/Modal.md#FocusLock)
3
+ component is for creating accessible modal dialogs that do not have a darkened backdrop. It shares the
4
+ (`escapable`, `hidefocus`, `hidefocuslabel`, `initialfocus`, `returnfocusto`, and `includeselector`)
5
+ props and the behaviors with `Modal` because the `Modal` component uses a `FocusLock` itself. In additon
6
+ it exports a `className` prop for passing the component a custom CSS class name to use.
7
+
8
+ Any time the `FocusLock` is in the DOM, it will lock screen readers inside it. You release them by
9
+ removing it from the DOM. Regular users are NOT trapped and may interact with other screen elements.
10
+ When they do, the escape event will be fired, allowing you to remove the dialog.
11
+
12
+ `FocusLock`s can be nested. The user will be trapped inside the deepest `FocusLock` present in the DOM.
13
+ When it goes away, they'll be trapped inside the previous `FocusLock`, and so on.
14
+ -->
1
15
  <script context="module">export const FocusLockStack = [];
2
16
  const waitAtick = typeof requestAnimationFrame !== 'undefined' ? resolve => requestAnimationFrame(resolve) : resolve => resolve(0);
3
17
  </script>
@@ -7,18 +21,16 @@ export let hidefocus = true;
7
21
  export let hidefocuslabel = 'focus is above modal dialog, start tabbing';
8
22
  export let initialfocus = undefined;
9
23
  export let returnfocusto = undefined;
10
- /**
11
- * If you expect any popup menus to be added to the body, we need to know that they
12
- * are considered to be part of the focus lock, or else the modal will be dismissed
13
- * when the user clicks inside
14
- * use commas to include multiple selectors
15
- */
24
+ /** If you expect any popup menus to be added to the body, we need to know that they
25
+ are considered to be part of the focus lock, or else the modal will be dismissed
26
+ when the user clicks inside. Use commas to include multiple selectors. */
16
27
  export let includeselector = undefined;
17
28
  let className = '';
18
29
  export { className as class };
30
+ export let focusId = randomid();
19
31
  import { onMount, onDestroy, createEventDispatcher, tick } from 'svelte';
20
32
  import { tabbable } from 'tabbable';
21
- import { sleep } from 'txstate-utils';
33
+ import { randomid, sleep } from 'txstate-utils';
22
34
  import { buttonify } from '../actions';
23
35
  import ScreenReaderOnly from './ScreenReaderOnly.svelte';
24
36
  const dispatch = createEventDispatcher();
@@ -32,10 +44,12 @@ onMount(async () => {
32
44
  if (prevFocusLock)
33
45
  prevFocusLock.pause();
34
46
  FocusLockStack.push({
47
+ focusId,
35
48
  pause: () => { state = 'paused'; },
36
49
  unpause: () => { if (state === 'paused')
37
50
  state = 'active'; }
38
51
  });
52
+ dispatch('focuslockupdate');
39
53
  if (typeof returnfocusto === 'undefined') {
40
54
  returnfocusto = document.querySelector(':focus');
41
55
  }
@@ -64,10 +78,13 @@ onDestroy(async () => {
64
78
  if (returnfocusto && wasactive) {
65
79
  returnfocusto.focus();
66
80
  }
67
- FocusLockStack.pop();
81
+ const idx = FocusLockStack.findIndex(f => f.focusId === focusId);
82
+ if (idx > -1)
83
+ FocusLockStack.splice(idx, 1);
68
84
  const prevFocusLock = FocusLockStack.slice(-1)[0];
69
85
  if (prevFocusLock)
70
86
  prevFocusLock.unpause();
87
+ dispatch('focuslockupdate');
71
88
  });
72
89
  const setInitialFocus = () => {
73
90
  const firstfocus = lockelement ? tabbable(lockelement)[0] : undefined;
@@ -1,5 +1,6 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  export declare const FocusLockStack: {
3
+ focusId: string;
3
4
  pause: () => void;
4
5
  unpause: () => void;
5
6
  }[];
@@ -10,18 +11,17 @@ declare const __propDef: {
10
11
  hidefocuslabel?: string;
11
12
  initialfocus?: string | undefined;
12
13
  returnfocusto?: HTMLElement | null | undefined;
13
- /**
14
- * If you expect any popup menus to be added to the body, we need to know that they
15
- * are considered to be part of the focus lock, or else the modal will be dismissed
16
- * when the user clicks inside
17
- * use commas to include multiple selectors
18
- */ includeselector?: string | undefined;
14
+ /** If you expect any popup menus to be added to the body, we need to know that they
15
+ are considered to be part of the focus lock, or else the modal will be dismissed
16
+ when the user clicks inside. Use commas to include multiple selectors. */ includeselector?: string | undefined;
19
17
  class?: string;
18
+ focusId?: string;
20
19
  };
21
20
  events: {
22
21
  click: MouseEvent;
23
22
  mousedown: MouseEvent;
24
23
  escape: CustomEvent<any>;
24
+ focuslockupdate: CustomEvent<any>;
25
25
  } & {
26
26
  [evt: string]: CustomEvent<any>;
27
27
  };
@@ -32,6 +32,20 @@ declare const __propDef: {
32
32
  export type FocusLockProps = typeof __propDef.props;
33
33
  export type FocusLockEvents = typeof __propDef.events;
34
34
  export type FocusLockSlots = typeof __propDef.slots;
35
+ /**
36
+ * The [`FocusLock`](https://github.com/txstate-etc/svelte-components/blob/main/docs/Modal.md#FocusLock)
37
+ * component is for creating accessible modal dialogs that do not have a darkened backdrop. It shares the
38
+ * (`escapable`, `hidefocus`, `hidefocuslabel`, `initialfocus`, `returnfocusto`, and `includeselector`)
39
+ * props and the behaviors with `Modal` because the `Modal` component uses a `FocusLock` itself. In additon
40
+ * it exports a `className` prop for passing the component a custom CSS class name to use.
41
+ *
42
+ * Any time the `FocusLock` is in the DOM, it will lock screen readers inside it. You release them by
43
+ * removing it from the DOM. Regular users are NOT trapped and may interact with other screen elements.
44
+ * When they do, the escape event will be fired, allowing you to remove the dialog.
45
+ *
46
+ * `FocusLock`s can be nested. The user will be trapped inside the deepest `FocusLock` present in the DOM.
47
+ * When it goes away, they'll be trapped inside the previous `FocusLock`, and so on.
48
+ */
35
49
  export default class FocusLock extends SvelteComponentTyped<FocusLockProps, FocusLockEvents, FocusLockSlots> {
36
50
  }
37
51
  export {};
@@ -1,3 +1,8 @@
1
+ <!-- @component
2
+ The purpose of `Loading` is to provide a visual, as well as Screen Reader friendly, cueue that associated content is loading
3
+ until the slotted content is signals it is done loading via the `loading` boolean bind at which point the slotted content
4
+ will be rendered.
5
+ -->
1
6
  <script>import { afterUpdate } from 'svelte';
2
7
  import ScreenReaderOnly from './ScreenReaderOnly.svelte';
3
8
  import { resize, ResizeStore } from '../actions';
@@ -14,6 +14,11 @@ declare const __propDef: {
14
14
  export type LoadingProps = typeof __propDef.props;
15
15
  export type LoadingEvents = typeof __propDef.events;
16
16
  export type LoadingSlots = typeof __propDef.slots;
17
+ /**
18
+ * The purpose of `Loading` is to provide a visual, as well as Screen Reader friendly, cueue that associated content is loading
19
+ * until the slotted content is signals it is done loading via the `loading` boolean bind at which point the slotted content
20
+ * will be rendered.
21
+ */
17
22
  export default class Loading extends SvelteComponentTyped<LoadingProps, LoadingEvents, LoadingSlots> {
18
23
  }
19
24
  export {};
@@ -1,3 +1,7 @@
1
+ <!-- @component
2
+ The purpose of `Lottie` is to provide a [Lottie](https://www.npmjs.com/package/lottie-web) player element
3
+ with Screen Reader compatible attributes and lables readily available through the `alt` prop.
4
+ -->
1
5
  <script>import { onMount } from 'svelte';
2
6
  import { isBlank } from 'txstate-utils';
3
7
  import ScreenReaderOnly from './ScreenReaderOnly.svelte';
@@ -9,6 +13,7 @@ export let direction = 1;
9
13
  export let paused = false;
10
14
  export let width = undefined;
11
15
  export let height = undefined;
16
+ /** `<ScreenReaderOnly>` lable to be applied. */
12
17
  export let alt = '';
13
18
  let container;
14
19
  let animation;
@@ -9,7 +9,7 @@ declare const __propDef: {
9
9
  paused?: boolean;
10
10
  width?: string | undefined;
11
11
  height?: string | undefined;
12
- alt?: string;
12
+ /** `<ScreenReaderOnly>` lable to be applied. */ alt?: string;
13
13
  };
14
14
  events: {
15
15
  [evt: string]: CustomEvent<any>;
@@ -19,6 +19,10 @@ declare const __propDef: {
19
19
  export type LottieProps = typeof __propDef.props;
20
20
  export type LottieEvents = typeof __propDef.events;
21
21
  export type LottieSlots = typeof __propDef.slots;
22
+ /**
23
+ * The purpose of `Lottie` is to provide a [Lottie](https://www.npmjs.com/package/lottie-web) player element
24
+ * with Screen Reader compatible attributes and lables readily available through the `alt` prop.
25
+ */
22
26
  export default class Lottie extends SvelteComponentTyped<LottieProps, LottieEvents, LottieSlots> {
23
27
  }
24
28
  export {};
@@ -1,4 +1,15 @@
1
- <script>export let opaque = false;
1
+ <!-- @component
2
+ The [`Modal`](https://github.com/txstate-etc/svelte-components/blob/main/docs/Modal.md#Modal) component is designed to block out the screen and focus the user on the content inside the Modal.
3
+
4
+ It provides only the backdrop and a scrollable container for your content. If your content should have a background color, be sure to add it yourself.
5
+
6
+ Any time the Modal is in the DOM, it will take over the screen. You make it go away by removing it from the DOM.
7
+ -->
8
+ <script>import { createEventDispatcher, onMount } from 'svelte';
9
+ import { randomid } from 'txstate-utils';
10
+ import FocusLock, { FocusLockStack } from './FocusLock.svelte';
11
+ import { portal } from '../actions';
12
+ export let opaque = false;
2
13
  export let containerClass = '';
3
14
  export let escapable = true;
4
15
  export let hidefocus = true;
@@ -6,23 +17,21 @@ export let hidefocuslabel = undefined;
6
17
  export let initialfocus = undefined;
7
18
  export let returnfocusto = undefined;
8
19
  export let usePortal = undefined;
9
- /**
10
- * If you expect any popup menus to be added to the body, we need to know that they
11
- * are considered to be part of the focus lock, or else the modal will be dismissed
12
- * when the user clicks inside
13
- * use commas to include multiple selectors
14
- */
20
+ /** If you expect any popup menus to be added to the body, we need to know that they
21
+ are considered to be part of the focus lock, or else the modal will be dismissed
22
+ when the user clicks inside. Use commas to include multiple selectors. */
15
23
  export let includeselector = undefined;
16
- import { createEventDispatcher, onMount } from 'svelte';
17
- import FocusLock, { FocusLockStack } from './FocusLock.svelte';
18
- import { portal } from '../actions';
24
+ export let focusId = randomid();
19
25
  const dispatch = createEventDispatcher();
20
26
  const endmodal = () => {
21
27
  dispatch('escape');
22
28
  };
23
29
  let stackPosition;
30
+ function onFocusLockUpdate() {
31
+ stackPosition = FocusLockStack.findIndex(f => f.focusId === focusId);
32
+ }
24
33
  onMount(() => {
25
- stackPosition = FocusLockStack.length - 1;
34
+ onFocusLockUpdate();
26
35
  document.body.style.marginRight = (window.innerWidth - document.body.clientWidth) + 'px';
27
36
  document.body.style.overflow = 'hidden';
28
37
  return () => {
@@ -34,7 +43,7 @@ onMount(() => {
34
43
 
35
44
  <!-- svelte-ignore a11y-click-events-have-key-events -->
36
45
  <div use:portal={usePortal} class="modal-backdrop" style:--modal-z={stackPosition * 10 + 3000} class:opaque on:mousedown|stopPropagation|preventDefault={() => escapable && endmodal()}>
37
- <FocusLock class="modal-container {containerClass}" {includeselector} {escapable} on:escape {hidefocus} {hidefocuslabel} {returnfocusto} {initialfocus}>
46
+ <FocusLock bind:focusId class="modal-container {containerClass}" {includeselector} {escapable} on:escape {hidefocus} {hidefocuslabel} {returnfocusto} {initialfocus} on:focuslockupdate={onFocusLockUpdate}>
38
47
  <slot></slot>
39
48
  </FocusLock>
40
49
  </div>
@@ -9,12 +9,10 @@ declare const __propDef: {
9
9
  initialfocus?: string | undefined;
10
10
  returnfocusto?: HTMLElement | undefined;
11
11
  usePortal?: HTMLElement | undefined;
12
- /**
13
- * If you expect any popup menus to be added to the body, we need to know that they
14
- * are considered to be part of the focus lock, or else the modal will be dismissed
15
- * when the user clicks inside
16
- * use commas to include multiple selectors
17
- */ includeselector?: string | undefined;
12
+ /** If you expect any popup menus to be added to the body, we need to know that they
13
+ are considered to be part of the focus lock, or else the modal will be dismissed
14
+ when the user clicks inside. Use commas to include multiple selectors. */ includeselector?: string | undefined;
15
+ focusId?: string;
18
16
  };
19
17
  events: {
20
18
  escape: CustomEvent<any>;
@@ -28,6 +26,13 @@ declare const __propDef: {
28
26
  export type ModalProps = typeof __propDef.props;
29
27
  export type ModalEvents = typeof __propDef.events;
30
28
  export type ModalSlots = typeof __propDef.slots;
29
+ /**
30
+ * The [`Modal`](https://github.com/txstate-etc/svelte-components/blob/main/docs/Modal.md#Modal) component is designed to block out the screen and focus the user on the content inside the Modal.
31
+ *
32
+ * It provides only the backdrop and a scrollable container for your content. If your content should have a background color, be sure to add it yourself.
33
+ *
34
+ * Any time the Modal is in the DOM, it will take over the screen. You make it go away by removing it from the DOM.
35
+ */
31
36
  export default class Modal extends SvelteComponentTyped<ModalProps, ModalEvents, ModalSlots> {
32
37
  }
33
38
  export {};
@@ -1,22 +1,39 @@
1
+ <!-- @component
2
+ The purpose of `MultiSelect` is to provide a text input associated with a popup menu that
3
+ displays, or completes, choice selections based on what's been typed in the text input.
4
+ Selected choices will be added to a list of selected items, in a pill format, that provides
5
+ a means for tracking and removing existing selections. The choices listed in the popup are
6
+ controlled by the parent component via the `getOptions` function that will be used as a
7
+ debounced callback on the contents of the text input.
8
+ -->
1
9
  <script>import { createEventDispatcher } from 'svelte';
2
10
  import { randomid, Cache } from 'txstate-utils';
3
11
  import ScreenReaderOnly from './ScreenReaderOnly.svelte';
4
12
  import DefaultPopupMenu from './PopupMenu.svelte';
5
13
  import { modifierKey, selectionIsLeft } from '../util';
6
- export let id = randomid();
7
14
  export let name;
8
- export let disabled = false;
15
+ /**
16
+ * Function to pass to the component that tells it how to use the text
17
+ * in the text input to determine what `PopupMenuItem[]` should be displayed
18
+ * in the `PopupMenu`. Items already 'selected' from the popup menu will be
19
+ * tracked and automatically filtered from the popup if returned as one of the
20
+ * `PopupMenuItem[]` by `getOptions`. */
9
21
  export let getOptions;
22
+ export let id = randomid();
23
+ export let disabled = false;
10
24
  export let menuContainerClass = undefined;
11
25
  export let menuClass = undefined;
12
26
  export let menuItemClass = undefined;
13
27
  export let menuItemHilitedClass = undefined;
28
+ export let inputClass = undefined;
29
+ /** The maximum number of selections allowed before making new selections is disabled. Default of 0 is unlimited. */
14
30
  export let maxSelections = 0;
15
31
  export let selected = [];
16
32
  export let placeholder = '';
17
33
  export let emptyText = undefined;
18
34
  export let usePortal = undefined;
19
35
  export let descid = undefined;
36
+ /** You can define your own PopupMenu and pass for that to be used or accept DefaultPopupMenu. */
20
37
  export let PopupMenu = DefaultPopupMenu;
21
38
  let menushown;
22
39
  let loading = false;
@@ -30,6 +47,7 @@ const optionsCache = new Cache(async (ipt) => {
30
47
  return await getOptions(ipt);
31
48
  }, { freshseconds: 5 });
32
49
  $: selectedSet = new Set(selected.map(s => s.value));
50
+ // Stop showing our menu if the maxSelections limit has been reached.
33
51
  $: if (maxSelections > 1 && selected.length >= maxSelections && menushown)
34
52
  menushown = false;
35
53
  let optionsTimer;
@@ -127,8 +145,8 @@ $: reactToHilite(hilitedpill, id);
127
145
  <ScreenReaderOnly>, click to deselect</ScreenReaderOnly>
128
146
  </li>
129
147
  {/each}
130
- <li class="input">
131
- <input type="text" id={id} name={name} {disabled} placeholder={placeholder}
148
+ <li class={`input ${inputClass ?? ''}`}>
149
+ <input type="text" {id} {name} {disabled} {placeholder}
132
150
  bind:this={inputelement} bind:value={inputvalue} on:blur
133
151
  on:focus={inputfocus} on:keydown={inputkeydown}
134
152
  autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list"
@@ -141,7 +159,9 @@ $: reactToHilite(hilitedpill, id);
141
159
  </ScreenReaderOnly>
142
160
  <slot></slot>
143
161
  </fieldset>
144
- <svelte:component this={PopupMenu} bind:menushown bind:hilited={popuphilited} bind:value={popupvalue} align='bottomleft' {usePortal} {loading} {emptyText} {menuContainerClass} {menuClass} {menuItemClass} {menuItemHilitedClass} items={options} buttonelement={inputelement} on:change={addSelection}></svelte:component>
162
+ <svelte:component this={PopupMenu} bind:menushown bind:hilited={popuphilited} bind:value={popupvalue} align='bottomleft'
163
+ {usePortal} {loading} {emptyText} {menuContainerClass} {menuClass} {menuItemClass} {menuItemHilitedClass} items={options} buttonelement={inputelement}
164
+ on:change={addSelection}/>
145
165
 
146
166
  <style>
147
167
  fieldset {
@@ -3,15 +3,21 @@ import DefaultPopupMenu from './PopupMenu.svelte';
3
3
  import type { PopupMenuItem } from '../types';
4
4
  declare const __propDef: {
5
5
  props: {
6
- id?: string;
7
6
  name: string;
7
+ /**
8
+ * Function to pass to the component that tells it how to use the text
9
+ * in the text input to determine what `PopupMenuItem[]` should be displayed
10
+ * in the `PopupMenu`. Items already 'selected' from the popup menu will be
11
+ * tracked and automatically filtered from the popup if returned as one of the
12
+ * `PopupMenuItem[]` by `getOptions`. */ getOptions: (search: string) => Promise<PopupMenuItem[]> | PopupMenuItem[];
13
+ id?: string;
8
14
  disabled?: boolean;
9
- getOptions: (search: string) => Promise<PopupMenuItem[]> | PopupMenuItem[];
10
15
  menuContainerClass?: string | undefined;
11
16
  menuClass?: string | undefined;
12
17
  menuItemClass?: string | undefined;
13
18
  menuItemHilitedClass?: string | undefined;
14
- maxSelections?: number;
19
+ inputClass?: string | undefined;
20
+ /** The maximum number of selections allowed before making new selections is disabled. Default of 0 is unlimited. */ maxSelections?: number;
15
21
  selected?: {
16
22
  value: string;
17
23
  label: string;
@@ -20,7 +26,7 @@ declare const __propDef: {
20
26
  emptyText?: string | undefined;
21
27
  usePortal?: HTMLElement | true | undefined;
22
28
  descid?: string | undefined;
23
- PopupMenu?: typeof DefaultPopupMenu;
29
+ /** You can define your own PopupMenu and pass for that to be used or accept DefaultPopupMenu. */ PopupMenu?: typeof DefaultPopupMenu;
24
30
  };
25
31
  events: {
26
32
  blur: FocusEvent;
@@ -35,6 +41,14 @@ declare const __propDef: {
35
41
  export type MultiSelectProps = typeof __propDef.props;
36
42
  export type MultiSelectEvents = typeof __propDef.events;
37
43
  export type MultiSelectSlots = typeof __propDef.slots;
44
+ /**
45
+ * The purpose of `MultiSelect` is to provide a text input associated with a popup menu that
46
+ * displays, or completes, choice selections based on what's been typed in the text input.
47
+ * Selected choices will be added to a list of selected items, in a pill format, that provides
48
+ * a means for tracking and removing existing selections. The choices listed in the popup are
49
+ * controlled by the parent component via the `getOptions` function that will be used as a
50
+ * debounced callback on the contents of the text input.
51
+ */
38
52
  export default class MultiSelect extends SvelteComponentTyped<MultiSelectProps, MultiSelectEvents, MultiSelectSlots> {
39
53
  }
40
54
  export {};
@@ -1,3 +1,7 @@
1
+ <!-- @component
2
+ The purpose of [`PopupMenu`](https://github.com/txstate-etc/svelte-components/blob/main/docs/PopupMenu.md) is to display a menu of options to the user, activated by clicking any element
3
+ bound to `buttonelement`. It can also be controlled from the parent, by binding the `menushown` boolean.
4
+ -->
1
5
  <script>import { createEventDispatcher, onDestroy, tick } from 'svelte';
2
6
  import { randomid } from 'txstate-utils';
3
7
  import { Store } from '@txstate-mws/svelte-store';
@@ -5,20 +9,49 @@ import { glue, portal } from '../actions';
5
9
  import ScreenReaderOnly from './ScreenReaderOnly.svelte';
6
10
  import { modifierKey } from '../util';
7
11
  const dispatch = createEventDispatcher();
12
+ /** The DOM element that will act as the "button" for this menu. The menu will be placed next to the
13
+ button and be controlled by the button. Keyboard access will be enabled when the button has focus.
14
+ This component adds all appropriate attributes (tabindex, roles, and aria) automatically. */
8
15
  export let buttonelement;
16
+ /** The list of menu items to be shown. Parent may change this at any time based on user activity. */
9
17
  export let items = [];
10
18
  export let menushown = false;
11
19
  export let value = undefined;
20
+ /** Control where the menu will appear. Default is to use the current viewport to make a decision to
21
+ maximize potential for menu growth. */
12
22
  export let align = 'auto';
23
+ /** When the menu is active, should it cover the button or be rendered above/below it? */
13
24
  export let cover = false;
25
+ /** When an item is currently selected, should it appear in the menu (with a visual indicator), or
26
+ simply be removed from the list (`showSelected: false`). */
14
27
  export let showSelected = true;
28
+ /** When defined, this width will be passed through to the menu's CSS width. Use a valid CSS dimension
29
+ such as 33em or 159px. Generally this will be used when you need to match the menu width to something
30
+ else, like the button. If the width is static like 100% a simple CSS rule is likely more efficient. */
15
31
  export let width = undefined;
32
+ /** Bind this prop to receive a store that is updated each time a menu placement decision is made. Example
33
+ of when this is useful is when you are trying to round corners of your button and need to know whether the
34
+ menu is above or below your button so you know which corners to round and which to leave square. Note that
35
+ the values do not update when the menu is hidden, so you may need to rely on `menushown` as well. */
16
36
  export let computedalign = new Store({ valign: 'bottom', halign: 'left' });
37
+ /** Set to `true` if you want the parent element's styling to increase its minHeight when necessary. Useful
38
+ for parent elements not styled for variable size children and which you want override the the size of. */
17
39
  export let adjustparentheight = false;
40
+ /** If the menu would be clipped by an `overflow: hidden`, you can set this prop and it will be placed in
41
+ the specified container, or the `document.body` if you simply say `true`. Placement will still be calculated
42
+ correctly, unless there are scrolling containers between the button and the menu. */
18
43
  export let usePortal = undefined;
44
+ /** Useful for when your `items` need to be fetched but you want the associated element shown. Set to loading
45
+ until they're ready to be displayed and the popup menu will not be shown until the `loading` bind is `true`. */
19
46
  export let loading = false;
47
+ /** A bindable value for inspecting which item in `items` is currently highlighted as the item currently
48
+ active in the list. This is not the same as `value` and the highlighted item may or may not be selected as
49
+ for the `value` of the field. */
20
50
  export let hilited = undefined;
51
+ /** The id of the <ul> element that is the displayed list of items. */
21
52
  export let menuid = randomid();
53
+ /** When there are no items (e.g. it's a filtered search and there were no results), we still display one
54
+ disabled item in the menu to let the user know what is going on. Use this prop to specify the message. */
22
55
  export let emptyText = undefined;
23
56
  export let menuContainerClass = '';
24
57
  export let menuClass = '';
@@ -200,8 +233,12 @@ $: hasSelected = showSelected && items.some(itm => itm.value === value);
200
233
  </script>
201
234
 
202
235
  {#if menushown}
203
- <div use:portal={usePortal === true ? undefined : (usePortal || null)} use:glue={{ target: buttonelement, align, cover, adjustparentheight, store: computedalign }} class={menuContainerClass}>
204
- <ul bind:this={menuelement} id={menuid} role='listbox' style={width ? `width: ${width}` : ''} class={menuClass} class:hasSelected class:defaultmenu={!menuClass && !menuContainerClass} on:keydown={onkeydown}>
236
+ <div use:portal={usePortal === true ? undefined : (usePortal || null)}
237
+ use:glue={{ target: buttonelement, align, cover, adjustparentheight, store: computedalign }}
238
+ class={menuContainerClass}>
239
+ <ul bind:this={menuelement} id={menuid} role='listbox' style={width ? `width: ${width}` : ''}
240
+ class={menuClass} class:hasSelected class:defaultmenu={!menuClass && !menuContainerClass}
241
+ on:keydown={onkeydown}>
205
242
  {#each items as item, i (item.value)}
206
243
  {#if showSelected || item.value !== value}
207
244
  <!-- svelte-ignore a11y-click-events-have-key-events -->
@@ -4,21 +4,37 @@ import type { GlueAlignOpts, GlueAlignStore } from '../actions';
4
4
  import type { PopupMenuItem } from '../types';
5
5
  declare const __propDef: {
6
6
  props: {
7
- buttonelement: HTMLElement;
8
- items?: PopupMenuItem[];
7
+ /** The DOM element that will act as the "button" for this menu. The menu will be placed next to the
8
+ button and be controlled by the button. Keyboard access will be enabled when the button has focus.
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?: PopupMenuItem[];
9
11
  menushown?: boolean;
10
12
  value?: string | undefined;
11
- align?: GlueAlignOpts;
12
- cover?: boolean;
13
- showSelected?: boolean;
14
- width?: string | undefined;
15
- computedalign?: Store<GlueAlignStore>;
16
- adjustparentheight?: boolean;
17
- usePortal?: HTMLElement | true | undefined;
18
- loading?: boolean;
19
- hilited?: number | undefined;
20
- menuid?: string;
21
- emptyText?: string | undefined;
13
+ /** Control where the menu will appear. Default is to use the current viewport to make a decision to
14
+ maximize potential for menu growth. */ align?: GlueAlignOpts;
15
+ /** When the menu is active, should it cover the button or be rendered above/below it? */ cover?: boolean;
16
+ /** When an item is currently selected, should it appear in the menu (with a visual indicator), or
17
+ simply be removed from the list (`showSelected: false`). */ showSelected?: boolean;
18
+ /** When defined, this width will be passed through to the menu's CSS width. Use a valid CSS dimension
19
+ such as 33em or 159px. Generally this will be used when you need to match the menu width to something
20
+ else, like the button. If the width is static like 100% a simple CSS rule is likely more efficient. */ width?: string | undefined;
21
+ /** Bind this prop to receive a store that is updated each time a menu placement decision is made. Example
22
+ of when this is useful is when you are trying to round corners of your button and need to know whether the
23
+ menu is above or below your button so you know which corners to round and which to leave square. Note that
24
+ the values do not update when the menu is hidden, so you may need to rely on `menushown` as well. */ computedalign?: Store<GlueAlignStore>;
25
+ /** Set to `true` if you want the parent element's styling to increase its minHeight when necessary. Useful
26
+ for parent elements not styled for variable size children and which you want override the the size of. */ adjustparentheight?: boolean;
27
+ /** If the menu would be clipped by an `overflow: hidden`, you can set this prop and it will be placed in
28
+ the specified container, or the `document.body` if you simply say `true`. Placement will still be calculated
29
+ correctly, unless there are scrolling containers between the button and the menu. */ usePortal?: HTMLElement | true | undefined;
30
+ /** Useful for when your `items` need to be fetched but you want the associated element shown. Set to loading
31
+ until they're ready to be displayed and the popup menu will not be shown until the `loading` bind is `true`. */ loading?: boolean;
32
+ /** A bindable value for inspecting which item in `items` is currently highlighted as the item currently
33
+ active in the list. This is not the same as `value` and the highlighted item may or may not be selected as
34
+ for the `value` of the field. */ hilited?: number | undefined;
35
+ /** The id of the <ul> element that is the displayed list of items. */ menuid?: string;
36
+ /** When there are no items (e.g. it's a filtered search and there were no results), we still display one
37
+ disabled item in the menu to let the user know what is going on. Use this prop to specify the message. */ emptyText?: string | undefined;
22
38
  menuContainerClass?: string;
23
39
  menuClass?: string;
24
40
  menuItemClass?: string;
@@ -43,6 +59,10 @@ declare const __propDef: {
43
59
  export type PopupMenuProps = typeof __propDef.props;
44
60
  export type PopupMenuEvents = typeof __propDef.events;
45
61
  export type PopupMenuSlots = typeof __propDef.slots;
62
+ /**
63
+ * The purpose of [`PopupMenu`](https://github.com/txstate-etc/svelte-components/blob/main/docs/PopupMenu.md) is to display a menu of options to the user, activated by clicking any element
64
+ * bound to `buttonelement`. It can also be controlled from the parent, by binding the `menushown` boolean.
65
+ */
46
66
  export default class PopupMenu extends SvelteComponentTyped<PopupMenuProps, PopupMenuEvents, PopupMenuSlots> {
47
67
  }
48
68
  export {};
@@ -1,18 +1,9 @@
1
1
  <!-- @component
2
2
  This component is for adding text that can be read by screen readers while remaining invisible to regular users.
3
- ```svelte
4
- <ScreenReaderOnly {id} {arialive} {ariaatomic}>
5
- {`Some descriptive text to be read about the component by screen readers.`}
6
- </ScreenReaderOnly>
7
- ```
8
- #### Properties
9
- ```ts
10
- id?: string|undefined = undefined // Any ID you'd like to bind to the underlying <span>.
11
- arialive?: 'off'|'assertive'|'polite'|undefined = undefined
12
- ariaatomic?: boolean|'false'|'true'|undefined = undefined
13
- ```
3
+ Useful for when aria-label isn't supported with the drawback that it can show up in copy/paste selections.
14
4
  -->
15
- <script>export let id = undefined;
5
+ <script>/** An id to bind with the `<span>` used to implement this component. */
6
+ export let id = undefined;
16
7
  export let arialive = undefined;
17
8
  export let ariaatomic = undefined;
18
9
  </script>
@@ -1,7 +1,7 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  declare const __propDef: {
3
3
  props: {
4
- id?: string | undefined;
4
+ /** An id to bind with the `<span>` used to implement this component. */ id?: string | undefined;
5
5
  arialive?: 'off' | 'assertive' | 'polite' | undefined;
6
6
  ariaatomic?: boolean | 'false' | 'true' | undefined;
7
7
  };
@@ -17,17 +17,7 @@ export type ScreenReaderOnlyEvents = typeof __propDef.events;
17
17
  export type ScreenReaderOnlySlots = typeof __propDef.slots;
18
18
  /**
19
19
  * This component is for adding text that can be read by screen readers while remaining invisible to regular users.
20
- * ```svelte
21
- * <ScreenReaderOnly {id} {arialive} {ariaatomic}>
22
- * {`Some descriptive text to be read about the component by screen readers.`}
23
- * </ScreenReaderOnly>
24
- * ```
25
- * #### Properties
26
- * ```ts
27
- * id?: string|undefined = undefined // Any ID you'd like to bind to the underlying <span>.
28
- * arialive?: 'off'|'assertive'|'polite'|undefined = undefined
29
- * ariaatomic?: boolean|'false'|'true'|undefined = undefined
30
- * ```
20
+ * Useful for when aria-label isn't supported with the drawback that it can show up in copy/paste selections.
31
21
  */
32
22
  export default class ScreenReaderOnly extends SvelteComponentTyped<ScreenReaderOnlyProps, ScreenReaderOnlyEvents, ScreenReaderOnlySlots> {
33
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@txstate-mws/svelte-components",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "Svelte components that are generically useful.",
5
5
  "scripts": {
6
6
  "prepublishOnly": "svelte-package",