@dosgato/dialog 1.2.8 → 1.3.1
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.
- package/dist/Button.svelte +15 -13
- package/dist/ButtonGroup.svelte +44 -40
- package/dist/Checkbox.svelte +13 -12
- package/dist/Container.svelte +50 -46
- package/dist/Dialog.svelte +56 -41
- package/dist/FieldAutocomplete.svelte +77 -70
- package/dist/FieldCheckbox.svelte +25 -22
- package/dist/FieldChoices.svelte +74 -68
- package/dist/FieldChooserLink.svelte +148 -150
- package/dist/FieldDate.svelte +19 -18
- package/dist/FieldDateTime.svelte +36 -34
- package/dist/FieldDualListbox.svelte +80 -79
- package/dist/FieldHidden.svelte +16 -15
- package/dist/FieldIdentifier.svelte +18 -17
- package/dist/FieldMultiple.svelte +71 -74
- package/dist/FieldMultiselect.svelte +81 -72
- package/dist/FieldNumber.svelte +20 -19
- package/dist/FieldRadio.svelte +42 -41
- package/dist/FieldSelect.svelte +45 -45
- package/dist/FieldStandard.svelte +28 -27
- package/dist/FieldText.svelte +27 -24
- package/dist/FieldTextArea.svelte +24 -24
- package/dist/FileIcon.svelte +10 -8
- package/dist/Form.svelte +40 -18
- package/dist/Form.svelte.d.ts +15 -13
- package/dist/FormDialog.svelte +40 -25
- package/dist/FormDialog.svelte.d.ts +23 -17
- package/dist/Icon.svelte +38 -33
- package/dist/InlineMessage.svelte +31 -29
- package/dist/InlineMessages.svelte +10 -7
- package/dist/Input.svelte +40 -39
- package/dist/Listbox.svelte +102 -109
- package/dist/MaxLength.svelte +19 -18
- package/dist/Radio.svelte +18 -15
- package/dist/Switcher.svelte +37 -33
- package/dist/Tab.svelte +23 -21
- package/dist/Tabs.svelte +111 -110
- package/dist/Tooltip.svelte +7 -7
- package/dist/chooser/Chooser.svelte +83 -76
- package/dist/chooser/ChooserPreview.svelte +16 -14
- package/dist/chooser/Details.svelte +6 -4
- package/dist/chooser/Thumbnail.svelte +20 -16
- package/dist/chooser/UploadUI.svelte +78 -69
- package/dist/code/CodeEditor.svelte +63 -66
- package/dist/code/FieldCodeEditor.svelte +21 -19
- package/dist/colorpicker/FieldColorPicker.svelte +36 -35
- package/dist/cropper/FieldCropper.svelte +142 -141
- package/dist/iconpicker/FieldIconPicker.svelte +102 -94
- package/dist/imageposition/FieldImagePosition.svelte +107 -98
- package/dist/tagpicker/FieldTagPicker.svelte +63 -54
- package/dist/tree/LoadIcon.svelte +0 -1
- package/dist/tree/Tree.svelte +198 -192
- package/dist/tree/Tree.svelte.d.ts +5 -5
- package/dist/tree/TreeCell.svelte +10 -6
- package/dist/tree/TreeCell.svelte.d.ts +5 -5
- package/dist/tree/TreeNode.svelte +213 -241
- package/dist/tree/TreeNode.svelte.d.ts +5 -5
- package/package.json +8 -9
package/dist/FormDialog.svelte
CHANGED
|
@@ -1,29 +1,45 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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 {
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
formelement?: HTMLFormElement;
|
|
12
|
+
} & {
|
|
13
|
+
tagClient?: import("./").TagClient;
|
|
14
|
+
chooserClient?: import("./").Client<F> | undefined;
|
|
15
|
+
} & {
|
|
14
16
|
title?: string;
|
|
15
|
-
icon?: IconifyIcon
|
|
17
|
+
icon?: IconifyIcon;
|
|
16
18
|
size?: "tiny" | "small" | "normal" | "large";
|
|
17
|
-
|
|
19
|
+
expandable?: boolean;
|
|
18
20
|
};
|
|
19
21
|
events(): {
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
38
|
-
export type FormDialogEvents<T extends Record<string, any
|
|
39
|
-
export type FormDialogSlots<T extends Record<string, any
|
|
40
|
-
export default class FormDialog<T extends Record<string, any
|
|
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>
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export let
|
|
11
|
-
|
|
12
|
-
export let
|
|
13
|
-
export let
|
|
14
|
-
export let
|
|
15
|
-
let
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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'),
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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>
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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>
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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>
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
export let
|
|
9
|
-
export let
|
|
10
|
-
export let
|
|
11
|
-
export let
|
|
12
|
-
export let
|
|
13
|
-
export let
|
|
14
|
-
export let
|
|
15
|
-
export let
|
|
16
|
-
export let
|
|
17
|
-
export let
|
|
18
|
-
export let
|
|
19
|
-
export let
|
|
20
|
-
export let
|
|
21
|
-
export let
|
|
22
|
-
export let
|
|
23
|
-
export let
|
|
24
|
-
export let
|
|
25
|
-
export let
|
|
26
|
-
export let
|
|
27
|
-
export let
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (dt
|
|
35
|
-
|
|
36
|
-
return dt
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
$:
|
|
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 -->
|
package/dist/Listbox.svelte
CHANGED
|
@@ -1,121 +1,115 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export let
|
|
8
|
-
export let
|
|
9
|
-
export let
|
|
10
|
-
export let
|
|
11
|
-
export let
|
|
12
|
-
export let
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let
|
|
19
|
-
let
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
59
|
-
|
|
59
|
+
selected = [items[hilited]]
|
|
60
|
+
dispatch('change', selected)
|
|
60
61
|
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function onkeydown (e: KeyboardEvent) {
|
|
65
|
+
if (modifierKey(e)) return
|
|
65
66
|
if (e.key === 'ArrowDown') {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function focusListbox () {
|
|
102
96
|
if (selected.length) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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>
|