@dosgato/dialog 1.2.6 → 1.2.8
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/FieldDateTime.svelte +19 -15
- package/dist/FieldDateTime.svelte.d.ts +2 -0
- package/dist/FieldMultiselect.svelte +21 -3
- package/dist/FieldMultiselect.svelte.d.ts +13 -1
- package/dist/chooser/Chooser.svelte +1 -1
- package/dist/tagpicker/FieldTagPicker.svelte +59 -2
- package/dist/tagpicker/FieldTagPicker.svelte.d.ts +7 -0
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script>import { datetimeSerialize, datetimeDeserialize } from '@txstate-mws/svelte-forms';
|
|
2
2
|
import FieldStandard from './FieldStandard.svelte';
|
|
3
3
|
import Input from './Input.svelte';
|
|
4
|
+
import { createEventDispatcher } from 'svelte';
|
|
4
5
|
let className = '';
|
|
5
6
|
export { className as class };
|
|
6
7
|
export let id = undefined;
|
|
@@ -16,23 +17,26 @@ export let related = 0;
|
|
|
16
17
|
export let extradescid = undefined;
|
|
17
18
|
export let helptext = undefined;
|
|
18
19
|
export let inputelement = undefined;
|
|
19
|
-
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const dispatch = createEventDispatcher();
|
|
21
|
+
function onChange(setVal) {
|
|
22
|
+
return () => {
|
|
23
|
+
if (inputelement) {
|
|
24
|
+
const date = new Date(inputelement.value);
|
|
25
|
+
const invalid = inputelement.validity.badInput || !(date instanceof Date && !isNaN(date.valueOf()));
|
|
26
|
+
if (invalid) {
|
|
27
|
+
setVal(undefined);
|
|
28
|
+
dispatch('change', undefined);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
setVal(datetimeDeserialize(inputelement.value));
|
|
32
|
+
dispatch('change', datetimeDeserialize(inputelement.value));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
24
36
|
}
|
|
25
37
|
</script>
|
|
26
38
|
|
|
27
|
-
<FieldStandard bind:id {label} {path} {required} {defaultValue} {conditional} {related} {helptext} serialize={datetimeSerialize} deserialize={datetimeDeserialize} let:value let:valid let:invalid let:id let:onBlur let:
|
|
28
|
-
{
|
|
29
|
-
<Input bind:inputelement={inputelement} type="datetime-local" name={path} {value} {id} class="dialog-input {className}" onBlur={() => { wrapOnBlur(onBlur) }} {onChange} {valid} {invalid} {min} {max} {step} {extradescid} {messagesid} {helptextid}/>
|
|
39
|
+
<FieldStandard bind:id {label} {path} {required} {defaultValue} {conditional} {related} {helptext} serialize={datetimeSerialize} deserialize={datetimeDeserialize} let:value let:valid let:invalid let:id let:onBlur let:helptextid let:messagesid let:setVal>
|
|
40
|
+
<Input bind:inputelement={inputelement} type="datetime-local" name={path} {value} {id} class="dialog-input {className}" {onBlur} onChange={onChange(setVal)} {valid} {invalid} {min} {max} {step} {extradescid} {messagesid} {helptextid}/>
|
|
30
41
|
</FieldStandard>
|
|
31
42
|
|
|
32
|
-
<style>
|
|
33
|
-
.bad-input-warning {
|
|
34
|
-
margin-bottom: 0.3em;
|
|
35
|
-
color: var(--dg-danger-bg, #9a3332);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
</style>
|
|
@@ -37,14 +37,26 @@ export let lookupByValue = async (val) => { const options = await getOptions(val
|
|
|
37
37
|
export let related = 0;
|
|
38
38
|
export let extradescid = undefined;
|
|
39
39
|
export let helptext = undefined;
|
|
40
|
+
export let menuContainerClass = '';
|
|
41
|
+
export let menuClass = '';
|
|
42
|
+
export let menuItemClass = '';
|
|
43
|
+
export let menuItemHilitedClass = '';
|
|
44
|
+
export let menuCategoryClass = '';
|
|
45
|
+
/** Add a Delete All button to clear all selected items */
|
|
46
|
+
export let includeDeleteAll = false;
|
|
47
|
+
/** Show a confirmation message before clearing all selections */
|
|
48
|
+
export let confirmDelete = undefined;
|
|
49
|
+
export let selectedItemLabel = (item) => item.label || item.value;
|
|
40
50
|
/** Each time we run getOptions we will save the value -> label mappings that it finds, so that we can display labels on pills. */
|
|
41
51
|
const valueToLabel = {};
|
|
52
|
+
const valueToGroup = {};
|
|
42
53
|
async function wrapGetOptions(search) {
|
|
43
54
|
const opts = await getOptions(search);
|
|
44
55
|
/* If no options are returned with the search term, we can end up with an infinite loop the first time reactToValue calls wrapGetOptions */
|
|
45
56
|
// if (opts.length === 0) opts = await getOptions('')
|
|
46
57
|
for (const opt of opts) {
|
|
47
58
|
valueToLabel[opt.value] = opt.label || opt.value;
|
|
59
|
+
valueToGroup[opt.value] = opt.group;
|
|
48
60
|
}
|
|
49
61
|
return opts;
|
|
50
62
|
}
|
|
@@ -62,11 +74,13 @@ async function reactToValue(value) {
|
|
|
62
74
|
await Promise.all(value.map(async (v) => {
|
|
63
75
|
if (!valueToLabel[v]) {
|
|
64
76
|
const item = await lookupByValue(v);
|
|
65
|
-
if (item)
|
|
77
|
+
if (item) {
|
|
66
78
|
valueToLabel[item.value] = item.label ?? item.value;
|
|
79
|
+
valueToGroup[item.value] = item.group;
|
|
80
|
+
}
|
|
67
81
|
}
|
|
68
82
|
}));
|
|
69
|
-
selectedStore.set(value.map(v => ({ value: v, label: valueToLabel[v] })).filter(v => isNotBlank(v.label)));
|
|
83
|
+
selectedStore.set(value.map(v => ({ value: v, label: valueToLabel[v], group: valueToGroup[v] })).filter(v => isNotBlank(v.label)));
|
|
70
84
|
}
|
|
71
85
|
</script>
|
|
72
86
|
|
|
@@ -79,7 +93,11 @@ async function reactToValue(value) {
|
|
|
79
93
|
{disabled} {maxSelections} selected={$selectedStore} {placeholder} {emptyText} getOptions={wrapGetOptions}
|
|
80
94
|
inputClass='multiselect-input'
|
|
81
95
|
on:change={e => setVal(e.detail.map(itm => itm.value))} on:blur={onBlur}
|
|
82
|
-
|
|
96
|
+
{includeDeleteAll} {confirmDelete} {selectedItemLabel}
|
|
97
|
+
{menuClass} {menuContainerClass} {menuItemClass} {menuItemHilitedClass} {menuCategoryClass}
|
|
98
|
+
>
|
|
99
|
+
<slot name="deleteall" slot="deleteall" />
|
|
100
|
+
</MultiSelect>
|
|
83
101
|
</div>
|
|
84
102
|
</FieldStandard>
|
|
85
103
|
{/if}
|
|
@@ -22,11 +22,23 @@ declare const __propDef: {
|
|
|
22
22
|
related?: true | number;
|
|
23
23
|
extradescid?: string | undefined;
|
|
24
24
|
helptext?: string | undefined;
|
|
25
|
+
menuContainerClass?: string;
|
|
26
|
+
menuClass?: string;
|
|
27
|
+
menuItemClass?: string;
|
|
28
|
+
menuItemHilitedClass?: string;
|
|
29
|
+
menuCategoryClass?: string;
|
|
30
|
+
/** Add a Delete All button to clear all selected items */ includeDeleteAll?: boolean;
|
|
31
|
+
/** Show a confirmation message before clearing all selections */ confirmDelete?: string | undefined;
|
|
32
|
+
selectedItemLabel?: ((item: PopupMenuItem) => string) | undefined;
|
|
25
33
|
};
|
|
26
34
|
events: {
|
|
27
35
|
[evt: string]: CustomEvent<any>;
|
|
28
36
|
};
|
|
29
|
-
slots: {
|
|
37
|
+
slots: {
|
|
38
|
+
deleteall: {
|
|
39
|
+
slot: string;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
30
42
|
};
|
|
31
43
|
export type FieldMultiselectProps = typeof __propDef.props;
|
|
32
44
|
export type FieldMultiselectEvents = typeof __propDef.events;
|
|
@@ -90,7 +90,7 @@ function iconForItem(item) {
|
|
|
90
90
|
{#if $store.initialized}
|
|
91
91
|
<Tree headers={[
|
|
92
92
|
{ label: 'Path', id: 'name', grow: 4, icon: item => iconForItem(item), get: 'name' }
|
|
93
|
-
]} singleSelect store={treeStore} on:deselect={onDeselect} on:choose={onChoose} />
|
|
93
|
+
]} singleSelect store={treeStore} on:deselect={onDeselect} on:choose={onChoose} searchable='name' />
|
|
94
94
|
{/if}
|
|
95
95
|
</section>
|
|
96
96
|
<ChooserPreview {thumbnailExpanded} {previewId} {store} on:thumbnailsizechange={() => { thumbnailExpanded = !thumbnailExpanded }}/>
|
|
@@ -2,15 +2,25 @@
|
|
|
2
2
|
import { getContext } from 'svelte';
|
|
3
3
|
import { isNotBlank } from 'txstate-utils';
|
|
4
4
|
import { TAG_API_CONTEXT } from './TagAPI';
|
|
5
|
+
import trashIcon from '@iconify-icons/ph/trash-simple-fill';
|
|
6
|
+
import { Icon } from '..';
|
|
5
7
|
export let path;
|
|
6
8
|
export let label;
|
|
7
9
|
export let target;
|
|
8
10
|
export let conditional = undefined;
|
|
9
11
|
export let required = false;
|
|
12
|
+
export let extradescid = undefined;
|
|
10
13
|
export let helptext = undefined;
|
|
11
14
|
export let emptyText = undefined;
|
|
12
15
|
/** Text to display in the tag picker input field when it's empty. */
|
|
13
16
|
export let placeholder = '';
|
|
17
|
+
/** We might want to show the title in the dialog for clarity, even if it is excluded when rendering tags on a page. */
|
|
18
|
+
export let showTitleInDialog = false;
|
|
19
|
+
export let menuContainerClass = '';
|
|
20
|
+
export let menuClass = '';
|
|
21
|
+
export let menuItemClass = 'default-menu-item';
|
|
22
|
+
export let menuItemHilitedClass = '';
|
|
23
|
+
export let menuCategoryClass = 'default-menu-category';
|
|
14
24
|
const tagClient = getContext(TAG_API_CONTEXT);
|
|
15
25
|
let lasttarget = -1;
|
|
16
26
|
let groups = [];
|
|
@@ -23,7 +33,11 @@ async function fetchAvailableTags() {
|
|
|
23
33
|
}
|
|
24
34
|
}
|
|
25
35
|
function tagToOption(tag) {
|
|
26
|
-
|
|
36
|
+
let group = undefined;
|
|
37
|
+
if (isNotBlank(tag.group.title) && (showTitleInDialog || !tag.group.excludeTitle)) {
|
|
38
|
+
group = tag.group.title;
|
|
39
|
+
}
|
|
40
|
+
return { label: tag.name, value: tag.id, group };
|
|
27
41
|
}
|
|
28
42
|
async function getOptions(search) {
|
|
29
43
|
await fetchAvailableTags();
|
|
@@ -37,6 +51,49 @@ async function lookupByValue(id) {
|
|
|
37
51
|
return undefined;
|
|
38
52
|
return tagToOption(tag);
|
|
39
53
|
}
|
|
54
|
+
function selectedItemLabel(item) {
|
|
55
|
+
const tag = item.label ?? item.value;
|
|
56
|
+
return item.group ? `${item.group}: ${tag}` : tag;
|
|
57
|
+
}
|
|
40
58
|
</script>
|
|
41
59
|
|
|
42
|
-
<FieldMultiselect {path} {label} {getOptions} {lookupByValue} {conditional} {required} {helptext} {emptyText} {placeholder}
|
|
60
|
+
<FieldMultiselect {path} {label} {getOptions} {lookupByValue} {conditional} {required} {extradescid} {helptext} {emptyText} {placeholder} {menuClass} {menuContainerClass} {menuItemClass} {menuItemHilitedClass} {menuCategoryClass} selectedItemLabel={showTitleInDialog ? selectedItemLabel : undefined} includeDeleteAll confirmDelete="Are you sure you want remove all tag selections?">
|
|
61
|
+
<svelte:fragment slot="deleteall"><span class="delete-button-text">Delete All <Icon icon={trashIcon}/></span></svelte:fragment>
|
|
62
|
+
</FieldMultiselect>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
:global(span.default-menu-category) {
|
|
66
|
+
padding: 0.5em;
|
|
67
|
+
border-bottom: 1px solid #767676;
|
|
68
|
+
margin: 0 -0.4em;
|
|
69
|
+
}
|
|
70
|
+
:global(.defaultmenu .group.group ul:has(.default-menu-item)) {
|
|
71
|
+
padding-inline-start: 0;
|
|
72
|
+
}
|
|
73
|
+
:global(li.default-menu-item) {
|
|
74
|
+
margin: 0 -0.4em;
|
|
75
|
+
padding: 0.5em;
|
|
76
|
+
border-bottom: 1px solid #767676;
|
|
77
|
+
}
|
|
78
|
+
:global(.defaultmenu .group li.default-menu-item) {
|
|
79
|
+
padding-left: 1.25em;
|
|
80
|
+
}
|
|
81
|
+
:global(li.default-menu-item):hover {
|
|
82
|
+
background-color: var(--dg-dialog-header-bg, #DDDDDD);
|
|
83
|
+
}
|
|
84
|
+
.delete-button-text {
|
|
85
|
+
font-size: 0.75em;
|
|
86
|
+
display: flex;
|
|
87
|
+
justify-content: center;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: 0.5em;
|
|
90
|
+
}
|
|
91
|
+
.delete-button-text :global(svg) {
|
|
92
|
+
color: #9A3332;
|
|
93
|
+
}
|
|
94
|
+
:global(button):has(.delete-button-text) {
|
|
95
|
+
background-color: transparent;
|
|
96
|
+
border: 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
</style>
|
|
@@ -6,9 +6,16 @@ declare const __propDef: {
|
|
|
6
6
|
target: any;
|
|
7
7
|
conditional?: boolean | undefined;
|
|
8
8
|
required?: boolean;
|
|
9
|
+
extradescid?: string | undefined;
|
|
9
10
|
helptext?: string | undefined;
|
|
10
11
|
emptyText?: string | undefined;
|
|
11
12
|
/** Text to display in the tag picker input field when it's empty. */ placeholder?: string;
|
|
13
|
+
/** We might want to show the title in the dialog for clarity, even if it is excluded when rendering tags on a page. */ showTitleInDialog?: boolean;
|
|
14
|
+
menuContainerClass?: string;
|
|
15
|
+
menuClass?: string;
|
|
16
|
+
menuItemClass?: string;
|
|
17
|
+
menuItemHilitedClass?: string;
|
|
18
|
+
menuCategoryClass?: string;
|
|
12
19
|
};
|
|
13
20
|
events: {
|
|
14
21
|
[evt: string]: CustomEvent<any>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dosgato/dialog",
|
|
3
3
|
"description": "A component library for building forms that edit a JSON document.",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.8",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepublishOnly": "svelte-package",
|
|
7
7
|
"dev": "vite dev --force",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@iconify/svelte": "^4.0.0",
|
|
27
27
|
"@iconify-icons/mdi": "^1.2.22",
|
|
28
28
|
"@iconify-icons/ph": "^1.2.2",
|
|
29
|
-
"@txstate-mws/svelte-components": "^1.6.
|
|
29
|
+
"@txstate-mws/svelte-components": "^1.6.11",
|
|
30
30
|
"@txstate-mws/svelte-forms": "^1.5.5",
|
|
31
31
|
"codemirror": "^6.0.1",
|
|
32
32
|
"txstate-utils": "^1.8.0"
|