@dosgato/dialog 1.2.7 → 1.3.0

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.
Files changed (60) hide show
  1. package/dist/Button.svelte +15 -13
  2. package/dist/ButtonGroup.svelte +44 -40
  3. package/dist/Checkbox.svelte +13 -12
  4. package/dist/Container.svelte +50 -46
  5. package/dist/Dialog.svelte +56 -41
  6. package/dist/FieldAutocomplete.svelte +77 -70
  7. package/dist/FieldCheckbox.svelte +25 -22
  8. package/dist/FieldChoices.svelte +74 -68
  9. package/dist/FieldChooserLink.svelte +148 -150
  10. package/dist/FieldDate.svelte +19 -18
  11. package/dist/FieldDateTime.svelte +36 -34
  12. package/dist/FieldDualListbox.svelte +80 -79
  13. package/dist/FieldHidden.svelte +16 -15
  14. package/dist/FieldIdentifier.svelte +18 -17
  15. package/dist/FieldMultiple.svelte +71 -74
  16. package/dist/FieldMultiselect.svelte +86 -59
  17. package/dist/FieldMultiselect.svelte.d.ts +13 -1
  18. package/dist/FieldNumber.svelte +20 -19
  19. package/dist/FieldRadio.svelte +42 -41
  20. package/dist/FieldSelect.svelte +45 -45
  21. package/dist/FieldStandard.svelte +28 -27
  22. package/dist/FieldText.svelte +27 -24
  23. package/dist/FieldTextArea.svelte +24 -24
  24. package/dist/FileIcon.svelte +10 -8
  25. package/dist/Form.svelte +40 -18
  26. package/dist/Form.svelte.d.ts +15 -13
  27. package/dist/FormDialog.svelte +40 -25
  28. package/dist/FormDialog.svelte.d.ts +23 -17
  29. package/dist/Icon.svelte +38 -33
  30. package/dist/InlineMessage.svelte +31 -29
  31. package/dist/InlineMessages.svelte +10 -7
  32. package/dist/Input.svelte +40 -39
  33. package/dist/Listbox.svelte +102 -109
  34. package/dist/MaxLength.svelte +19 -18
  35. package/dist/Radio.svelte +18 -15
  36. package/dist/Switcher.svelte +37 -33
  37. package/dist/Tab.svelte +23 -21
  38. package/dist/Tabs.svelte +111 -110
  39. package/dist/Tooltip.svelte +7 -7
  40. package/dist/chooser/Chooser.svelte +83 -76
  41. package/dist/chooser/ChooserPreview.svelte +16 -14
  42. package/dist/chooser/Details.svelte +6 -4
  43. package/dist/chooser/Thumbnail.svelte +20 -16
  44. package/dist/chooser/UploadUI.svelte +78 -69
  45. package/dist/code/CodeEditor.svelte +63 -66
  46. package/dist/code/FieldCodeEditor.svelte +21 -19
  47. package/dist/colorpicker/FieldColorPicker.svelte +36 -35
  48. package/dist/cropper/FieldCropper.svelte +142 -141
  49. package/dist/iconpicker/FieldIconPicker.svelte +102 -94
  50. package/dist/imageposition/FieldImagePosition.svelte +107 -98
  51. package/dist/tagpicker/FieldTagPicker.svelte +104 -38
  52. package/dist/tagpicker/FieldTagPicker.svelte.d.ts +7 -0
  53. package/dist/tree/LoadIcon.svelte +0 -1
  54. package/dist/tree/Tree.svelte +198 -192
  55. package/dist/tree/Tree.svelte.d.ts +5 -5
  56. package/dist/tree/TreeCell.svelte +10 -6
  57. package/dist/tree/TreeCell.svelte.d.ts +5 -5
  58. package/dist/tree/TreeNode.svelte +213 -241
  59. package/dist/tree/TreeNode.svelte.d.ts +5 -5
  60. package/package.json +9 -10
@@ -1,29 +1,45 @@
1
- <script>import contentSave from '@iconify-icons/mdi/content-save';
2
- import { createEventDispatcher, setContext } from 'svelte';
3
- import { CHOOSER_API_CONTEXT, Form } from './';
4
- import Dialog from './Dialog.svelte';
5
- export let submit;
6
- export let validate = undefined;
7
- export let store = undefined;
8
- export let chooserClient = undefined;
9
- export let tagClient = undefined;
10
- export let autocomplete = undefined;
11
- export let name = undefined;
12
- export let title = '';
13
- export let icon = undefined;
14
- export let size = 'normal';
15
- export let preload = undefined;
16
- const dispatch = createEventDispatcher();
17
- async function onSubmit() {
18
- const resp = await store.submit();
19
- if (resp.success)
20
- dispatch('saved', resp.data);
21
- }
22
- setContext(CHOOSER_API_CONTEXT, chooserClient);
1
+ <script lang="ts" generics="T extends Record<string, any> = Record<string, any>, F = any">
2
+ import type { IconifyIcon } from '@iconify/svelte'
3
+ import contentSave from '@iconify-icons/mdi/content-save'
4
+ import type { Feedback, FormStore } from '@txstate-mws/svelte-forms'
5
+ import { type ComponentProps } from 'svelte'
6
+ import { Form } from './'
7
+ import Dialog from './Dialog.svelte'
8
+
9
+ interface $$Props extends ComponentProps<Form<T, F>> {
10
+ title?: string
11
+ icon?: IconifyIcon
12
+ size?: 'tiny' | 'small' | 'normal' | 'large'
13
+ expandable?: boolean
14
+ }
15
+
16
+ interface $$Slots {
17
+ default: {
18
+ messages: Feedback[]
19
+ allMessages: Feedback[]
20
+ saved: boolean
21
+ validating: boolean
22
+ submitting: boolean
23
+ valid: boolean
24
+ invalid: boolean
25
+ showingInlineErrors: boolean
26
+ data: Partial<T>
27
+ }
28
+ }
29
+
30
+ export let store: FormStore<T> | undefined = undefined
31
+ export let title: string = ''
32
+ export let icon: IconifyIcon | undefined = undefined
33
+ export let size: 'tiny' | 'small' | 'normal' | 'large' = 'normal'
34
+ export let expandable = true
35
+
36
+ async function onSubmit () {
37
+ await store!.submit()
38
+ }
23
39
  </script>
24
40
 
25
- <Dialog continueText="Save" continueIcon={contentSave} cancelText="Cancel" on:escape on:continue={onSubmit} {title} {icon} {size} escapable={false} expandable>
26
- <Form bind:store {submit} {validate} {chooserClient} {tagClient} {autocomplete} {name} {preload} on:saved let:messages let:allMessages let:showingInlineErrors let:saved let:valid let:invalid let:validating let:submitting let:data>
41
+ <Dialog continueText="Save" continueIcon={contentSave} cancelText="Cancel" on:escape on:continue={onSubmit} {title} {icon} {size} escapable={false} {expandable}>
42
+ <Form bind:store {...$$restProps} on:saved on:autosaved on:validationfail let:messages let:allMessages let:showingInlineErrors let:saved let:valid let:invalid let:validating let:submitting let:data>
27
43
  <slot {messages} {allMessages} {saved} {validating} {submitting} {valid} {invalid} {data} {showingInlineErrors} />
28
44
  </Form>
29
45
  </Dialog>
@@ -32,5 +48,4 @@ setContext(CHOOSER_API_CONTEXT, chooserClient);
32
48
  :global(:root) {
33
49
  --ck-z-default: var(--popup-z, 3001);
34
50
  }
35
-
36
51
  </style>
@@ -1,24 +1,30 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  import type { IconifyIcon } from '@iconify/svelte';
3
- import type { Feedback, FormStore, SubmitResponse } from '@txstate-mws/svelte-forms';
4
- import { type Client, type TagClient } from './';
5
- declare class __sveltets_Render<T extends Record<string, any>> {
6
- props(): {
7
- submit: (state: T) => Promise<SubmitResponse<T>>;
3
+ import type { Feedback, FormStore } from '@txstate-mws/svelte-forms';
4
+ declare class __sveltets_Render<T extends Record<string, any> = Record<string, any>, F = any> {
5
+ props(): import("svelte/elements").HTMLFormAttributes & {
6
+ submit?: ((state: T) => Promise<import("@txstate-mws/svelte-forms").SubmitResponse<T>>) | undefined;
8
7
  validate?: ((state: T) => Promise<Feedback[]>) | undefined;
8
+ autoSave?: boolean;
9
+ preload?: T | undefined;
9
10
  store?: FormStore<T> | undefined;
10
- chooserClient?: Client | undefined;
11
- tagClient?: TagClient | undefined;
12
- autocomplete?: string | undefined;
13
- name?: string | undefined;
11
+ formelement?: HTMLFormElement;
12
+ } & {
13
+ tagClient?: import("./").TagClient;
14
+ chooserClient?: import("./").Client<F> | undefined;
15
+ } & {
14
16
  title?: string;
15
- icon?: IconifyIcon | undefined;
17
+ icon?: IconifyIcon;
16
18
  size?: "tiny" | "small" | "normal" | "large";
17
- preload?: T | undefined;
19
+ expandable?: boolean;
18
20
  };
19
21
  events(): {
20
- saved: CustomEvent<T>;
21
- escape: CustomEvent<undefined>;
22
+ escape: CustomEvent<any>;
23
+ saved: any;
24
+ autosaved: any;
25
+ validationfail: any;
26
+ } & {
27
+ [evt: string]: CustomEvent<any>;
22
28
  };
23
29
  slots(): {
24
30
  default: {
@@ -34,9 +40,9 @@ declare class __sveltets_Render<T extends Record<string, any>> {
34
40
  };
35
41
  };
36
42
  }
37
- export type FormDialogProps<T extends Record<string, any>> = ReturnType<__sveltets_Render<T>['props']>;
38
- export type FormDialogEvents<T extends Record<string, any>> = ReturnType<__sveltets_Render<T>['events']>;
39
- export type FormDialogSlots<T extends Record<string, any>> = ReturnType<__sveltets_Render<T>['slots']>;
40
- export default class FormDialog<T extends Record<string, any>> extends SvelteComponentTyped<FormDialogProps<T>, FormDialogEvents<T>, FormDialogSlots<T>> {
43
+ export type FormDialogProps<T extends Record<string, any> = Record<string, any>, F = any> = ReturnType<__sveltets_Render<T, F>['props']>;
44
+ export type FormDialogEvents<T extends Record<string, any> = Record<string, any>, F = any> = ReturnType<__sveltets_Render<T, F>['events']>;
45
+ export type FormDialogSlots<T extends Record<string, any> = Record<string, any>, F = any> = ReturnType<__sveltets_Render<T, F>['slots']>;
46
+ export default class FormDialog<T extends Record<string, any> = Record<string, any>, F = any> extends SvelteComponentTyped<FormDialogProps<T, F>, FormDialogEvents<T, F>, FormDialogSlots<T, F>> {
41
47
  }
42
48
  export {};
package/dist/Icon.svelte CHANGED
@@ -3,42 +3,48 @@
3
3
  implementation of icons that adds a hidden label that can be read by screen readers. Useful for situations where aria-label
4
4
  isn't supported, to provide in kind icon support, while still making use of aria attributes regardless of support.
5
5
  -->
6
- <script>import { randomid } from 'txstate-utils';
7
- import Tooltip from './Tooltip.svelte';
8
- export let icon;
9
- /** Label used in a `<ScreenReaderOnly>`. */
10
- export let hiddenLabel = undefined;
11
- export let inline = false;
12
- export let width = undefined;
13
- export let height = undefined;
14
- export let tooltip = undefined;
15
- let className = undefined;
16
- export { className as class };
17
- function replaceIDs(body) {
18
- const matches = body.matchAll(/\sid="(\S+)"/g);
19
- const ids = Array.from(matches).map(m => m[1]);
20
- if (!ids.length)
21
- return body;
6
+ <script lang="ts">
7
+ import type { IconifyIcon } from '@iconify/svelte'
8
+ import { randomid } from 'txstate-utils'
9
+ import Tooltip from './Tooltip.svelte'
10
+ export let icon: IconifyIcon | undefined
11
+ /** Label used in a `<ScreenReaderOnly>`. */
12
+ export let hiddenLabel: string | undefined = undefined
13
+ export let inline: boolean = false
14
+ export let width: string | number | undefined = undefined
15
+ export let height: string | number | undefined = undefined
16
+ export let tooltip: string | undefined = undefined
17
+ let className: string | undefined = undefined
18
+ export { className as class }
19
+
20
+ function replaceIDs (body: string): string {
21
+ const matches = body.matchAll(/\sid="(\S+)"/g)
22
+ const ids = Array.from(matches).map(m => m[1])
23
+ if (!ids.length) return body
24
+
22
25
  // Replace with unique ids
23
26
  ids.forEach((id) => {
24
- const escapedID = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
25
- body = body.replace(
27
+ const escapedID = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
28
+
29
+ body = body.replace(
26
30
  // Allowed characters before id: [#;"]
27
31
  // Allowed characters after id: [)"], .[a-z]
28
- new RegExp('([#;"])(' + escapedID + ')([")]|\\.[a-z])', 'g'), '$1' + randomid() + '$3');
29
- });
30
- return body;
31
- }
32
- const fixedSVG = new Map();
33
- function svgBody(icon) {
34
- if (!fixedSVG.has(icon))
35
- fixedSVG.set(icon, replaceIDs(icon.body));
36
- return fixedSVG.get(icon);
37
- }
38
- // If neither is defined, set both to 1em
39
- if (!width && !height)
40
- width = height = '1em';
41
- height ??= width;
32
+ new RegExp('([#;"])(' + escapedID + ')([")]|\\.[a-z])', 'g'),
33
+ '$1' + randomid() + '$3'
34
+ )
35
+ })
36
+ return body
37
+ }
38
+
39
+ const fixedSVG = new Map<IconifyIcon, string>()
40
+ function svgBody (icon: IconifyIcon) {
41
+ if (!fixedSVG.has(icon)) fixedSVG.set(icon, replaceIDs(icon.body))
42
+ return fixedSVG.get(icon)
43
+ }
44
+
45
+ // If neither is defined, set both to 1em
46
+ if (!width && !height) width = height = '1em'
47
+ height ??= width
42
48
  </script>
43
49
 
44
50
  {#if icon}
@@ -62,5 +68,4 @@ height ??= width;
62
68
  svg.hFlip.vFlip {
63
69
  transform: scale(-1, -1);
64
70
  }
65
-
66
71
  </style>
@@ -1,37 +1,40 @@
1
1
  <!-- @component
2
2
  The purpose of this component is to provide common `Feedback` message styling with icons that support screen readers.
3
3
  -->
4
- <script>import { htmlEncode } from 'txstate-utils';
5
- import { messageIcons, messageLabels } from './';
6
- import Icon from './Icon.svelte';
7
- export let message;
8
- $: icon = messageIcons[message.type] ?? messageIcons.error;
9
- $: iconLabel = messageLabels[message.type] ?? messageLabels.error;
10
- function addMarkup(msg) {
11
- const lines = msg.split(/\r?\n/);
12
- const output = [];
4
+ <script lang="ts">
5
+ import type { Feedback } from '@txstate-mws/svelte-forms'
6
+ import { htmlEncode } from 'txstate-utils'
7
+
8
+ import { messageIcons, messageLabels } from './'
9
+ import Icon from './Icon.svelte'
10
+ export let message: Feedback
11
+
12
+ $: icon = messageIcons[message.type] ?? messageIcons.error
13
+ $: iconLabel = messageLabels[message.type] ?? messageLabels.error
14
+
15
+ function addMarkup (msg: string) {
16
+ const lines = msg.split(/\r?\n/)
17
+ const output: string[] = []
13
18
  for (const line of lines) {
14
- const splitLinks = line.split(/((?:\[[^\]]+\])?\([^)]+\))/);
15
- const replaced = [];
16
- for (const segment of splitLinks) {
17
- const m = segment.match(/(?:\[([^\]]+)\])?\(([^)]+)\)/);
18
- if (m) {
19
- try {
20
- const url = new URL(m[2]);
21
- replaced.push('<a href="' + htmlEncode(url.toString()) + '" target="_blank">' + htmlEncode(m[1] || m[2]) + '</a>');
22
- }
23
- catch {
24
- replaced.push(htmlEncode(m[0]));
25
- }
26
- }
27
- else {
28
- replaced.push(htmlEncode(segment));
29
- }
19
+ const splitLinks = line.split(/((?:\[[^\]]+\])?\([^)]+\))/)
20
+ const replaced: string[] = []
21
+ for (const segment of splitLinks) {
22
+ const m = segment.match(/(?:\[([^\]]+)\])?\(([^)]+)\)/)
23
+ if (m) {
24
+ try {
25
+ const url = new URL(m[2])
26
+ replaced.push('<a href="' + htmlEncode(url.toString()) + '" target="_blank">' + htmlEncode(m[1] || m[2]) + '</a>')
27
+ } catch {
28
+ replaced.push(htmlEncode(m[0]))
29
+ }
30
+ } else {
31
+ replaced.push(htmlEncode(segment))
30
32
  }
31
- output.push(replaced.join(''));
33
+ }
34
+ output.push(replaced.join(''))
32
35
  }
33
- return output.join('<br>');
34
- }
36
+ return output.join('<br>')
37
+ }
35
38
  </script>
36
39
 
37
40
  <div class={message.type}><Icon width='1.5em' {icon} inline hiddenLabel={iconLabel} /><span>{@html addMarkup(message.message)}</span></div>
@@ -63,5 +66,4 @@ function addMarkup(msg) {
63
66
  div :global(a) {
64
67
  color: inherit;
65
68
  }
66
-
67
69
  </style>
@@ -1,12 +1,16 @@
1
1
  <!-- @component
2
2
  This renders the `Feedback` messages bound to it if there are any to display.
3
3
  -->
4
- <script>import { randomid } from 'txstate-utils';
5
- import InlineMessage from './InlineMessage.svelte';
6
- export let messages;
7
- export let id = randomid();
8
- const savedid = id;
9
- $: id = messages.length ? savedid : undefined;
4
+ <script lang="ts">
5
+ import type { Feedback } from '@txstate-mws/svelte-forms'
6
+ import { randomid } from 'txstate-utils'
7
+ import InlineMessage from './InlineMessage.svelte'
8
+
9
+ export let messages: Feedback[]
10
+ export let id: string | undefined = randomid()
11
+
12
+ const savedid = id
13
+ $: id = messages.length ? savedid : undefined
10
14
  </script>
11
15
 
12
16
  {#if id}
@@ -21,5 +25,4 @@ $: id = messages.length ? savedid : undefined;
21
25
  div {
22
26
  width: 100%;
23
27
  }
24
-
25
28
  </style>
package/dist/Input.svelte CHANGED
@@ -1,42 +1,43 @@
1
- <script>import { passActions } from '@txstate-mws/svelte-components';
2
- import { dateSerialize, datetimeSerialize } from '@txstate-mws/svelte-forms';
3
- import { isNotBlank } from 'txstate-utils';
4
- let className = '';
5
- export { className as class };
6
- export let name;
7
- export let value;
8
- export let type = 'text';
9
- export let allowlastpass = false;
10
- export let placeholder = undefined;
11
- export let maxlength = undefined;
12
- export let min = undefined;
13
- export let max = undefined;
14
- export let step = undefined;
15
- export let id = undefined;
16
- export let disabled = false;
17
- export let autocomplete = 'off';
18
- export let extradescid = undefined;
19
- export let messagesid = undefined;
20
- export let helptextid = undefined;
21
- export let valid = false;
22
- export let invalid = false;
23
- export let onChange;
24
- export let onBlur;
25
- export let onSelect = undefined;
26
- export let use = [];
27
- export let inputelement = undefined;
28
- $: descby = [messagesid, helptextid, extradescid].filter(isNotBlank).join(' ');
29
- function resolveMinMax(dt) {
30
- if (typeof dt === 'undefined')
31
- return undefined;
32
- if ('toJSDate' in dt)
33
- dt = dt.toJSDate();
34
- if (dt instanceof Date)
35
- return type === 'date' ? dateSerialize(dt) : datetimeSerialize(dt);
36
- return dt;
37
- }
38
- $: minStr = resolveMinMax(min);
39
- $: maxStr = resolveMinMax(max);
1
+ <script lang="ts">
2
+ import { passActions, type HTMLActionEntry } from '@txstate-mws/svelte-components'
3
+ import { dateSerialize, datetimeSerialize } from '@txstate-mws/svelte-forms'
4
+ import { isNotBlank } from 'txstate-utils'
5
+
6
+ let className = ''
7
+ export { className as class }
8
+ export let name: string
9
+ export let value: string
10
+ export let type: string = 'text'
11
+ export let allowlastpass = false
12
+ export let placeholder: string | undefined = undefined
13
+ export let maxlength: number | undefined = undefined
14
+ export let min: string | Date | { toJSDate: () => Date } | number | undefined = undefined
15
+ export let max: string | Date | { toJSDate: () => Date } | number | undefined = undefined
16
+ export let step: number | undefined = undefined
17
+ export let id: string | undefined = undefined
18
+ export let disabled = false
19
+ export let autocomplete = 'off'
20
+ export let extradescid: string | undefined = undefined
21
+ export let messagesid: string | undefined = undefined
22
+ export let helptextid: string | undefined = undefined
23
+ export let valid = false
24
+ export let invalid = false
25
+ export let onChange: any
26
+ export let onBlur: any
27
+ export let onSelect: any = undefined
28
+ export let use: HTMLActionEntry[] = []
29
+ export let inputelement: HTMLInputElement = undefined as any
30
+
31
+ $: descby = [messagesid, helptextid, extradescid].filter(isNotBlank).join(' ')
32
+
33
+ function resolveMinMax (dt: any) {
34
+ if (typeof dt === 'undefined') return undefined
35
+ if ('toJSDate' in dt) dt = dt.toJSDate()
36
+ if (dt instanceof Date) return type === 'date' ? dateSerialize(dt) : datetimeSerialize(dt)
37
+ return dt
38
+ }
39
+ $: minStr = resolveMinMax(min)
40
+ $: maxStr = resolveMinMax(max)
40
41
  </script>
41
42
 
42
43
  <!-- svelte-ignore a11y-autocomplete-valid -->
@@ -1,121 +1,115 @@
1
- <script>import { createEventDispatcher } from 'svelte';
2
- import { modifierKey } from '@txstate-mws/svelte-components';
3
- import check from '@iconify-icons/mdi/check.js';
4
- import Icon from './Icon.svelte';
5
- const dispatch = createEventDispatcher();
6
- export let items = [];
7
- export let label;
8
- export let multiselect = false;
9
- export let selected = [];
10
- export let descid = undefined;
11
- export let valid = false;
12
- export let invalid = false;
13
- import { randomid } from 'txstate-utils';
14
- const listId = randomid();
15
- const labelId = randomid();
16
- let listboxElement;
17
- let hilited = undefined;
18
- let firstactive = 0;
19
- let lastactive = items.length - 1;
20
- const itemelements = [];
21
- $: selectedSet = new Set(selected.map(s => s.value));
22
- async function reactToItems(..._) {
23
- firstactive = items.findIndex(itm => !itm.disabled);
24
- lastactive = items.length - [...items].reverse().findIndex(itm => !itm.disabled) - 1;
25
- hilited = undefined;
26
- if (listboxElement)
27
- listboxElement.removeAttribute('aria-activedescendant');
28
- }
29
- $: void reactToItems(items);
30
- const selectItem = (item, index) => (e) => {
31
- e.stopPropagation();
32
- e.preventDefault();
33
- if (item.disabled)
34
- return;
35
- listboxElement.setAttribute('aria-activedescendant', `${listId}-${index}`);
36
- hilited = index;
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte'
3
+ import { modifierKey, type PopupMenuItem } from '@txstate-mws/svelte-components'
4
+ import check from '@iconify-icons/mdi/check.js'
5
+ import Icon from './Icon.svelte'
6
+ const dispatch = createEventDispatcher()
7
+ export let items: PopupMenuItem[] = []
8
+ export let label: string
9
+ export let multiselect: boolean = false
10
+ export let selected: { value: string, label?: string }[] = []
11
+ export let descid: string | undefined = undefined
12
+ export let valid = false
13
+ export let invalid = false
14
+
15
+ import { randomid } from 'txstate-utils'
16
+ const listId = randomid()
17
+ const labelId = randomid()
18
+ let listboxElement: HTMLElement
19
+ let hilited: number | undefined = undefined
20
+ let firstactive = 0
21
+ let lastactive = items.length - 1
22
+ const itemelements: HTMLElement[] = []
23
+ $: selectedSet = new Set(selected.map(s => s.value))
24
+
25
+ async function reactToItems (..._: any[]) {
26
+ firstactive = items.findIndex(itm => !itm.disabled)
27
+ lastactive = items.length - [...items].reverse().findIndex(itm => !itm.disabled) - 1
28
+ hilited = undefined
29
+
30
+ if (listboxElement) listboxElement.removeAttribute('aria-activedescendant')
31
+ }
32
+ $: void reactToItems(items)
33
+
34
+ const selectItem = (item: PopupMenuItem, index: number) => (e: MouseEvent) => {
35
+ e.stopPropagation()
36
+ e.preventDefault()
37
+ if (item.disabled) return
38
+ listboxElement.setAttribute('aria-activedescendant', `${listId}-${index}`)
39
+ hilited = index
37
40
  if (multiselect) {
38
- if (selectedSet.has(item.value)) {
39
- // remove it from selected
40
- selected = selected.filter(s => s.value !== item.value);
41
- }
42
- else {
43
- selected = [...selected, item];
44
- }
45
- }
46
- else {
47
- selected = [item];
41
+ if (selectedSet.has(item.value)) {
42
+ // remove it from selected
43
+ selected = selected.filter(s => s.value !== item.value)
44
+ } else {
45
+ selected = [...selected, item]
46
+ }
47
+ } else {
48
+ selected = [item]
48
49
  }
49
- dispatch('change', selected);
50
- };
51
- function move(idx) {
52
- if (items[idx]?.disabled)
53
- return;
54
- hilited = Math.max(firstactive, Math.min(lastactive, idx));
55
- itemelements[hilited].scrollIntoView({ block: 'center' });
56
- listboxElement.setAttribute('aria-activedescendant', `${listId}-${hilited}`);
50
+ dispatch('change', selected)
51
+ }
52
+
53
+ function move (idx: number) {
54
+ if (items[idx]?.disabled) return
55
+ hilited = Math.max(firstactive, Math.min(lastactive, idx))
56
+ itemelements[hilited].scrollIntoView({ block: 'center' })
57
+ listboxElement.setAttribute('aria-activedescendant', `${listId}-${hilited}`)
57
58
  if (!multiselect) {
58
- selected = [items[hilited]];
59
- dispatch('change', selected);
59
+ selected = [items[hilited]]
60
+ dispatch('change', selected)
60
61
  }
61
- }
62
- function onkeydown(e) {
63
- if (modifierKey(e))
64
- return;
62
+ }
63
+
64
+ function onkeydown (e: KeyboardEvent) {
65
+ if (modifierKey(e)) return
65
66
  if (e.key === 'ArrowDown') {
66
- e.preventDefault();
67
- if (items.length < 1)
68
- return;
69
- let i = (hilited ?? firstactive - 1) + 1;
70
- while (items[i]?.disabled)
71
- i++;
72
- move(i);
73
- }
74
- else if (e.key === 'ArrowUp') {
75
- e.preventDefault();
76
- if (items.length < 1)
77
- return;
78
- let i = (hilited ?? lastactive + 1) - 1;
79
- while (items[i]?.disabled)
80
- i--;
81
- move(i);
82
- }
83
- else if (e.key === ' ') {
84
- e.preventDefault();
85
- if (items.length < 1)
86
- return;
87
- if (multiselect) {
88
- if (hilited != null) {
89
- if (selectedSet.has(items[hilited].value)) {
90
- // remove it from selected
91
- selected = selected.filter(s => s.value !== items[hilited].value);
92
- }
93
- else {
94
- selected = [...selected, items[hilited]];
95
- }
96
- dispatch('change', selected);
97
- }
67
+ e.preventDefault()
68
+ if (items.length < 1) return
69
+ let i = (hilited ?? firstactive - 1) + 1
70
+ while (items[i]?.disabled) i++
71
+ move(i)
72
+ } else if (e.key === 'ArrowUp') {
73
+ e.preventDefault()
74
+ if (items.length < 1) return
75
+ let i = (hilited ?? lastactive + 1) - 1
76
+ while (items[i]?.disabled) i--
77
+ move(i)
78
+ } else if (e.key === ' ') {
79
+ e.preventDefault()
80
+ if (items.length < 1) return
81
+ if (multiselect) {
82
+ if (hilited != null) {
83
+ if (selectedSet.has(items[hilited].value)) {
84
+ // remove it from selected
85
+ selected = selected.filter(s => s.value !== items[hilited!].value)
86
+ } else {
87
+ selected = [...selected, items[hilited]]
88
+ }
89
+ dispatch('change', selected)
98
90
  }
91
+ }
99
92
  }
100
- }
101
- function focusListbox() {
93
+ }
94
+
95
+ function focusListbox () {
102
96
  if (selected.length) {
103
- for (let i = 0; i < items.length; i++) {
104
- if (items[i].value === selected[0].value) {
105
- hilited = i;
106
- listboxElement.setAttribute('aria-activedescendant', `${listId}-${hilited}`);
107
- }
97
+ for (let i = 0; i < items.length; i++) {
98
+ if (items[i].value === selected[0].value) {
99
+ hilited = i
100
+ listboxElement.setAttribute('aria-activedescendant', `${listId}-${hilited}`)
108
101
  }
102
+ }
103
+ } else {
104
+ hilited = firstactive
105
+ listboxElement.setAttribute('aria-activedescendant', `${listId}-${hilited}`)
106
+ if (!multiselect) {
107
+ selected = [items[hilited]]
108
+ dispatch('change', selected)
109
+ }
109
110
  }
110
- else {
111
- hilited = firstactive;
112
- listboxElement.setAttribute('aria-activedescendant', `${listId}-${hilited}`);
113
- if (!multiselect) {
114
- selected = [items[hilited]];
115
- dispatch('change', selected);
116
- }
117
- }
118
- }
111
+ }
112
+
119
113
  </script>
120
114
 
121
115
  <div class="listbox-container" class:valid class:invalid>
@@ -165,5 +159,4 @@ function focusListbox() {
165
159
  .listbox-item.disabled {
166
160
  color: rgba(0,0,0,0.6);
167
161
  }
168
-
169
162
  </style>