@dosgato/dialog 0.0.54 → 0.0.56
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/ButtonGroup.svelte +1 -1
- package/dist/FieldChoices.svelte +18 -2
- package/dist/FieldChooserLink.svelte +34 -14
- package/dist/FieldRadio.svelte +19 -1
- package/dist/FieldSelect.svelte +20 -2
- package/dist/Switcher.svelte +1 -1
- package/dist/TabStore.d.ts +1 -0
- package/dist/TabStore.js +3 -2
- package/dist/Tabs.svelte +3 -3
- package/dist/colorpicker/FieldColorPicker.svelte +21 -4
- package/dist/tree/Tree.svelte +15 -12
- package/dist/tree/Tree.svelte.d.ts +3 -2
- package/dist/tree/treestore.d.ts +11 -2
- package/dist/tree/treestore.js +36 -4
- package/package.json +1 -1
package/dist/ButtonGroup.svelte
CHANGED
|
@@ -46,7 +46,7 @@ function onKeyDown(choice, idx) {
|
|
|
46
46
|
{/if}
|
|
47
47
|
<!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
|
|
48
48
|
<ul class="dialog-btn-group" class:disabled class:valid class:invalid aria-disabled={disabled} role="radiogroup" aria-labelledby={groupid} on:blur>
|
|
49
|
-
{#each choices as choice, i}
|
|
49
|
+
{#each choices as choice, i (choice.value)}
|
|
50
50
|
{@const selected = choice.value === value}
|
|
51
51
|
<li bind:this={elements[i]} role="radio" class:selected aria-checked={selected} tabindex={selected ? 0 : -1} aria-controls={ariaControls} on:click={onClick(choice, i)} on:keydown={onKeyDown(choice, i)} on:blur aria-describedby="{groupid} {messagesid}">{choice.label || choice.value}</li>
|
|
52
52
|
{/each}
|
package/dist/FieldChoices.svelte
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
flex. Ordering is top down by default but can be order horizontally by toggling `leftToRight`.
|
|
5
5
|
The value of the field will be an array corresponding to the values of the checkboxes that are checked.
|
|
6
6
|
-->
|
|
7
|
-
<script>import { getContext } from 'svelte';
|
|
7
|
+
<script>import { getContext, onMount } from 'svelte';
|
|
8
8
|
import { Field, FORM_CONTEXT } from '@txstate-mws/svelte-forms';
|
|
9
9
|
import { derivedStore } from '@txstate-mws/svelte-store';
|
|
10
10
|
import { randomid } from 'txstate-utils';
|
|
@@ -46,12 +46,28 @@ function onChangeCheckbox(setVal, choice, included) {
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
const descid = randomid();
|
|
49
|
+
let val, stVal;
|
|
50
|
+
function updateValue(valu, sVal) {
|
|
51
|
+
val = valu;
|
|
52
|
+
stVal = sVal;
|
|
53
|
+
}
|
|
54
|
+
function reactToChoices(..._) {
|
|
55
|
+
if (!stVal)
|
|
56
|
+
return;
|
|
57
|
+
const choiceSet = new Set(choices?.map(c => c.value));
|
|
58
|
+
const filtered = val?.filter(v => choiceSet.has(v));
|
|
59
|
+
if (filtered?.length !== val?.length)
|
|
60
|
+
stVal(filtered);
|
|
61
|
+
}
|
|
62
|
+
$: reactToChoices(choices);
|
|
63
|
+
onMount(reactToChoices);
|
|
49
64
|
</script>
|
|
50
65
|
|
|
51
66
|
<Field {path} {defaultValue} {conditional} let:path let:value let:onBlur let:setVal let:messages let:valid let:invalid>
|
|
67
|
+
{@const _ = updateValue(value, setVal)}
|
|
52
68
|
<Container {id} {label} {messages} {descid} {related} {helptext} let:messagesid let:helptextid>
|
|
53
69
|
<div class="dialog-choices {className}" class:valid class:invalid>
|
|
54
|
-
{#each choices as choice, idx}
|
|
70
|
+
{#each choices as choice, idx (choice.value)}
|
|
55
71
|
{@const checkid = `${path}.${idx}`}
|
|
56
72
|
{@const included = value && value.includes(choice.value)}
|
|
57
73
|
{@const label = choice.label || (typeof choice.value === 'string' ? choice.value : '')}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
<script context="module">function reconstructUrl(url) {
|
|
2
|
+
const urlToTest = (/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)+([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])/.test(url)) ? `https://${url}` : url;
|
|
3
|
+
return new URL(urlToTest).toString();
|
|
4
|
+
}
|
|
5
|
+
</script>
|
|
1
6
|
<script>import { FORM_CONTEXT, FORM_INHERITED_PATH } from '@txstate-mws/svelte-forms';
|
|
2
7
|
import { getContext } from 'svelte';
|
|
3
|
-
import { isNotBlank, randomid } from 'txstate-utils';
|
|
8
|
+
import { isBlank, isNotBlank, randomid } from 'txstate-utils';
|
|
4
9
|
import { Chooser, ChooserStore, CHOOSER_API_CONTEXT } from './chooser';
|
|
5
10
|
import Details from './chooser/Details.svelte';
|
|
6
11
|
import Thumbnail from './chooser/Thumbnail.svelte';
|
|
7
12
|
import FieldStandard from './FieldStandard.svelte';
|
|
13
|
+
import InlineMessage from './InlineMessage.svelte';
|
|
8
14
|
import { getDescribedBy } from './';
|
|
9
15
|
export let id = undefined;
|
|
10
16
|
export let path;
|
|
@@ -56,6 +62,11 @@ function userUrlEntry() {
|
|
|
56
62
|
async function userUrlEntryDebounced() {
|
|
57
63
|
const url = this.value;
|
|
58
64
|
store.clearPreview();
|
|
65
|
+
if (isBlank(url)) {
|
|
66
|
+
selectedAsset = undefined;
|
|
67
|
+
formStore.setField(finalPath, undefined);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
59
70
|
let found = false;
|
|
60
71
|
if (chooserClient.findByUrl) {
|
|
61
72
|
const item = await chooserClient.findByUrl(url);
|
|
@@ -83,18 +94,24 @@ async function userUrlEntryDebounced() {
|
|
|
83
94
|
}
|
|
84
95
|
}
|
|
85
96
|
if (!found) {
|
|
86
|
-
|
|
87
|
-
selectedAsset = {
|
|
88
|
-
type: 'raw',
|
|
89
|
-
id: urlToValueCache[url] ?? chooserClient.urlToValue?.(new URL(url).toString()),
|
|
90
|
-
url
|
|
91
|
-
};
|
|
97
|
+
if (urlToValueCache[url]) {
|
|
98
|
+
selectedAsset = { type: 'raw', id: urlToValueCache[url], url };
|
|
92
99
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
else {
|
|
101
|
+
try {
|
|
102
|
+
const reconstructed = reconstructUrl(url);
|
|
103
|
+
selectedAsset = {
|
|
104
|
+
type: 'raw',
|
|
105
|
+
id: chooserClient.urlToValue?.(reconstructed) ?? reconstructed,
|
|
106
|
+
url
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// here we "select" a raw url so that we do not interrupt the users' typing, but
|
|
111
|
+
// we set its id to 'undefined' so that nothing makes it into the form until it's
|
|
112
|
+
// a valid URL
|
|
113
|
+
selectedAsset = { type: 'raw', id: undefined, url };
|
|
114
|
+
}
|
|
98
115
|
}
|
|
99
116
|
}
|
|
100
117
|
formStore.setField(finalPath, selectedAsset?.id);
|
|
@@ -112,7 +129,7 @@ async function updateSelected(..._) {
|
|
|
112
129
|
if (!selectedAsset) {
|
|
113
130
|
const urlFromValue = chooserClient.valueToUrl?.($value) ?? $value;
|
|
114
131
|
try {
|
|
115
|
-
selectedAsset = { type: 'raw', id: $value, url:
|
|
132
|
+
selectedAsset = { type: 'raw', id: $value, url: reconstructUrl(urlFromValue) };
|
|
116
133
|
}
|
|
117
134
|
catch {
|
|
118
135
|
selectedAsset = { type: 'broken', id: $value, url: $value };
|
|
@@ -129,7 +146,7 @@ $: updateSelected($value);
|
|
|
129
146
|
</script>
|
|
130
147
|
|
|
131
148
|
<FieldStandard bind:id {path} {descid} {label} {defaultValue} {conditional} {required} {related} {helptext} let:value let:messagesid let:helptextid let:valid let:invalid let:id let:onBlur let:setVal>
|
|
132
|
-
{#if selectedAsset}
|
|
149
|
+
{#if selectedAsset?.id}
|
|
133
150
|
<div class="dialog-chooser-container">
|
|
134
151
|
<Thumbnail item={selectedAsset} />
|
|
135
152
|
<Details item={selectedAsset} />
|
|
@@ -141,6 +158,9 @@ $: updateSelected($value);
|
|
|
141
158
|
{/if}
|
|
142
159
|
<button type="button" on:click={show} aria-describedby={getDescribedBy([descid, messagesid, helptextid, extradescid])}>Select {#if value}New{/if} {#if assets && pages}Link Target{:else if images}Image{:else if assets}Asset{:else}Page{/if}</button>
|
|
143
160
|
</div>
|
|
161
|
+
{#if selectedAsset?.url.length && !selectedAsset.id}
|
|
162
|
+
<InlineMessage message={{ message: 'Entry does not match an internal resource and is not a valid URL. Nothing will be saved.', type: 'warning' }} />
|
|
163
|
+
{/if}
|
|
144
164
|
{#if modalshown}
|
|
145
165
|
<Chooser {store} {label} {pages} {assets} {images} {initialSource} {initialPath} {folders} {required} on:change={onChange(setVal)} on:escape={hide} />
|
|
146
166
|
{/if}
|
package/dist/FieldRadio.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script>import { Field } from '@txstate-mws/svelte-forms';
|
|
2
|
+
import { onMount } from 'svelte';
|
|
2
3
|
import Switcher from './Switcher.svelte';
|
|
3
4
|
let className = '';
|
|
4
5
|
export { className as class };
|
|
@@ -20,8 +21,25 @@ export let datetime = false;
|
|
|
20
21
|
export let boolean = false;
|
|
21
22
|
export let serialize = undefined;
|
|
22
23
|
export let deserialize = undefined;
|
|
24
|
+
let val, stVal, finalDeserialize;
|
|
25
|
+
function updateValue(valu, sVal, fDes) {
|
|
26
|
+
val = valu;
|
|
27
|
+
stVal = sVal;
|
|
28
|
+
finalDeserialize = fDes;
|
|
29
|
+
}
|
|
30
|
+
function reactToChoices(..._) {
|
|
31
|
+
if (!stVal)
|
|
32
|
+
return;
|
|
33
|
+
if (!choices.length)
|
|
34
|
+
stVal(finalDeserialize(''));
|
|
35
|
+
if (!choices.some(o => o.value === val))
|
|
36
|
+
stVal(notNull ? choices[0].value : finalDeserialize(''));
|
|
37
|
+
}
|
|
38
|
+
$: reactToChoices(choices);
|
|
39
|
+
onMount(reactToChoices);
|
|
23
40
|
</script>
|
|
24
41
|
|
|
25
|
-
<Field {path} {defaultValue} {conditional} {notNull} {number} {date} {datetime} {boolean} {serialize} {deserialize} let:value let:valid let:invalid let:onBlur let:onChange let:messages let:serialize>
|
|
42
|
+
<Field {path} {defaultValue} {conditional} {notNull} {number} {date} {datetime} {boolean} {serialize} {deserialize} let:value let:valid let:invalid let:onBlur let:onChange let:messages let:serialize let:setVal let:deserialize>
|
|
43
|
+
{@const _ = updateValue(value, setVal, deserialize)}
|
|
26
44
|
<Switcher bind:id class={className} name={path} {horizontal} {label} iptValue={value} {valid} {invalid} {required} {related} {extradescid} {helptext} {messages} on:change={onChange} {onBlur} choices={choices.map(c => ({ ...c, value: serialize(c.value) }))} />
|
|
27
45
|
</Field>
|
package/dist/FieldSelect.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<script>import {
|
|
1
|
+
<script>import { onMount } from 'svelte';
|
|
2
|
+
import { getDescribedBy } from './';
|
|
2
3
|
import FieldStandard from './FieldStandard.svelte';
|
|
3
4
|
let className = '';
|
|
4
5
|
export { className as class };
|
|
@@ -22,9 +23,26 @@ export let datetime = false;
|
|
|
22
23
|
export let boolean = false;
|
|
23
24
|
export let serialize = undefined;
|
|
24
25
|
export let deserialize = undefined;
|
|
26
|
+
let val, stVal, finalDeserialize;
|
|
27
|
+
function updateValue(valu, sVal, fDes) {
|
|
28
|
+
val = valu;
|
|
29
|
+
stVal = sVal;
|
|
30
|
+
finalDeserialize = fDes;
|
|
31
|
+
}
|
|
32
|
+
function reactToChoices(..._) {
|
|
33
|
+
if (!stVal)
|
|
34
|
+
return;
|
|
35
|
+
if (!choices.length)
|
|
36
|
+
stVal(finalDeserialize(''));
|
|
37
|
+
if (!choices.some(o => o.value === val))
|
|
38
|
+
stVal(notNull ? choices[0].value : finalDeserialize(''));
|
|
39
|
+
}
|
|
40
|
+
$: reactToChoices(choices);
|
|
41
|
+
onMount(reactToChoices);
|
|
25
42
|
</script>
|
|
26
43
|
|
|
27
|
-
<FieldStandard bind:id {label} {path} {required} {defaultValue} {conditional} {related} {helptext} {notNull} {number} {date} {datetime} {boolean} {serialize} {deserialize} let:value let:valid let:invalid let:id let:onBlur let:onChange let:messagesid let:helptextid let:serialize>
|
|
44
|
+
<FieldStandard bind:id {label} {path} {required} {defaultValue} {conditional} {related} {helptext} {notNull} {number} {date} {datetime} {boolean} {serialize} {deserialize} let:value let:valid let:invalid let:id let:onBlur let:onChange let:messagesid let:helptextid let:serialize let:deserialize let:setVal>
|
|
45
|
+
{@const _ = updateValue(value, setVal, deserialize)}
|
|
28
46
|
<select bind:this={inputelement} {id} name={path} {disabled} class="dialog-input dialog-select {className}" on:change={onChange} on:blur={onBlur} class:valid class:invalid aria-describedby={getDescribedBy([messagesid, helptextid, extradescid])}>
|
|
29
47
|
{#if !notNull}<option value="" selected={!value}>{placeholder}</option>{/if}
|
|
30
48
|
{#each choices as choice (choice.value)}
|
package/dist/Switcher.svelte
CHANGED
|
@@ -32,7 +32,7 @@ $: width = (horizontal ? 100 / Math.min(choices.length, choices.length === 4 &&
|
|
|
32
32
|
</script>
|
|
33
33
|
<Container {id} {label} {messages} descid={groupid} {required} {related} {helptext} let:helptextid>
|
|
34
34
|
<div class="dialog-radio {className}" use:eq={{ store }} class:horizontal role="radiogroup" aria-labelledby={groupid} class:valid class:invalid>
|
|
35
|
-
{#each choices as choice, idx}
|
|
35
|
+
{#each choices as choice, idx (choice.value)}
|
|
36
36
|
{@const radioid = `${groupid}.${idx}`}
|
|
37
37
|
<label for={radioid} style:width>
|
|
38
38
|
<Radio id={radioid} {name} value={choice.value} selected={iptValue === choice.value} disabled={choice.disabled} {onChange} {onBlur} {helptextid} {extradescid} />
|
package/dist/TabStore.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ interface ITabStore extends ElementSize {
|
|
|
17
17
|
visited: Record<string, boolean>;
|
|
18
18
|
tabids: Record<string, string>;
|
|
19
19
|
panelids: Record<string, string>;
|
|
20
|
+
accordionOnMobile: boolean;
|
|
20
21
|
}
|
|
21
22
|
export declare class TabStore extends Store<ITabStore> {
|
|
22
23
|
initialTab?: string | undefined;
|
package/dist/TabStore.js
CHANGED
|
@@ -19,7 +19,8 @@ export class TabStore extends Store {
|
|
|
19
19
|
requireNext: false,
|
|
20
20
|
tabids: tabs.reduce((acc, curr) => ({ ...acc, [curr.name]: randomid() }), {}),
|
|
21
21
|
panelids: tabs.reduce((acc, curr) => ({ ...acc, [curr.name]: randomid() }), {}),
|
|
22
|
-
clientWidth: 1024
|
|
22
|
+
clientWidth: 1024,
|
|
23
|
+
accordionOnMobile: true
|
|
23
24
|
}));
|
|
24
25
|
this.initialTab = initialTab;
|
|
25
26
|
}
|
|
@@ -49,7 +50,7 @@ export class TabStore extends Store {
|
|
|
49
50
|
return derivedStore(this, v => v.panelids[v.tabs[v.current].name]);
|
|
50
51
|
}
|
|
51
52
|
accordion() {
|
|
52
|
-
return derivedStore(this, v => v.clientWidth && v.clientWidth < 500);
|
|
53
|
+
return derivedStore(this, v => v.accordionOnMobile && v.clientWidth && v.clientWidth < 500);
|
|
53
54
|
}
|
|
54
55
|
left() {
|
|
55
56
|
this.update(v => ({ ...v, current: Math.max(0, v.current - 1) }));
|
package/dist/Tabs.svelte
CHANGED
|
@@ -10,7 +10,7 @@ export let active = undefined;
|
|
|
10
10
|
export let store = new TabStore(tabs, active);
|
|
11
11
|
export let disableDialogControl = false;
|
|
12
12
|
export let accordionOnMobile = true;
|
|
13
|
-
$: store.update(v => ({ ...v, tabs }));
|
|
13
|
+
$: store.update(v => ({ ...v, tabs, accordionOnMobile }));
|
|
14
14
|
const activeStore = new Store({});
|
|
15
15
|
const tabelements = [];
|
|
16
16
|
let activeelement;
|
|
@@ -30,7 +30,7 @@ const accordion = store.accordion();
|
|
|
30
30
|
$: cols = Math.min(Math.floor(($store.clientWidth ?? 1024) / 90), $store.tabs.length);
|
|
31
31
|
$: scalefactor = Math.min(roundTo(($store.clientWidth ?? 1024) / (cols * 130), 4), 1);
|
|
32
32
|
$: wrapping = cols !== $store.tabs.length;
|
|
33
|
-
$: dialogContext.hasTabs = !$accordion
|
|
33
|
+
$: dialogContext.hasTabs = !$accordion;
|
|
34
34
|
function onClick(idx) {
|
|
35
35
|
return () => store.activate(idx);
|
|
36
36
|
}
|
|
@@ -87,7 +87,7 @@ onMount(reactToCurrent);
|
|
|
87
87
|
</script>
|
|
88
88
|
|
|
89
89
|
{#if $store.tabs.length > 1}
|
|
90
|
-
{#if !$accordion
|
|
90
|
+
{#if !$accordion}
|
|
91
91
|
<!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
|
|
92
92
|
<ul use:resize={{ store }} class="tabs-buttons" role="tablist">
|
|
93
93
|
{#each $store.tabs as tab, idx (tab.name)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
<script>import
|
|
2
|
-
import { Radio } from '..';
|
|
1
|
+
<script>import { onMount } from 'svelte';
|
|
3
2
|
import { randomid, shouldUseWhiteText } from 'txstate-utils';
|
|
3
|
+
import FieldStandard from '../FieldStandard.svelte';
|
|
4
|
+
import { Radio } from '..';
|
|
4
5
|
export let id = undefined;
|
|
5
6
|
let className = '';
|
|
6
7
|
export { className as class };
|
|
@@ -14,15 +15,31 @@ export let defaultValue = notNull ? (addAllOption ? 'alternating' : options[0].v
|
|
|
14
15
|
export let conditional = undefined;
|
|
15
16
|
export let helptext = undefined;
|
|
16
17
|
const groupid = randomid();
|
|
18
|
+
let val, stVal;
|
|
19
|
+
function updateValue(valu, sVal) {
|
|
20
|
+
val = valu;
|
|
21
|
+
stVal = sVal;
|
|
22
|
+
}
|
|
23
|
+
function reactToOptions(..._) {
|
|
24
|
+
if (!stVal)
|
|
25
|
+
return;
|
|
26
|
+
if (!options.length)
|
|
27
|
+
stVal(addAllOption ? 'alternating' : undefined);
|
|
28
|
+
if (val !== 'alternating' && !options.some(o => o.value === val))
|
|
29
|
+
stVal(notNull ? options[0].value : (addAllOption ? 'alternating' : undefined));
|
|
30
|
+
}
|
|
31
|
+
$: reactToOptions(options);
|
|
32
|
+
onMount(reactToOptions);
|
|
17
33
|
</script>
|
|
18
34
|
|
|
19
|
-
<FieldStandard bind:id descid={groupid} {path} {label} {required} {defaultValue} {conditional} {helptext} let:value let:valid let:invalid let:onBlur let:onChange let:messagesid let:helptextid>
|
|
35
|
+
<FieldStandard bind:id descid={groupid} {path} {label} {required} {defaultValue} {conditional} {helptext} let:value let:valid let:invalid let:onBlur let:onChange let:messagesid let:helptextid let:setVal>
|
|
36
|
+
{@const _ = updateValue(value, setVal)}
|
|
20
37
|
<div class="color-container {className}" role="radiogroup" aria-labelledby={groupid} class:invalid class:valid>
|
|
21
38
|
{#if addAllOption}
|
|
22
39
|
<label for={`${path}.alt`} class="colorsel alternating">
|
|
23
40
|
<Radio id={`${path}.alt`} name={path} value="alternating" selected={value === 'alternating'} {onChange} {onBlur} {helptextid}/>
|
|
24
41
|
<span class="alternating-bg">
|
|
25
|
-
{#each options as option}
|
|
42
|
+
{#each options as option (option.value)}
|
|
26
43
|
<span style:background-color={option.color}></span>
|
|
27
44
|
{/each}
|
|
28
45
|
</span>
|
package/dist/tree/Tree.svelte
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
<script>import { resize } from '@txstate-mws/svelte-components';
|
|
2
2
|
import { derivedStore, Store } from '@txstate-mws/svelte-store';
|
|
3
3
|
import { afterUpdate, beforeUpdate, onDestroy, onMount, setContext } from 'svelte';
|
|
4
|
-
import {
|
|
4
|
+
import { isNotBlank } from 'txstate-utils';
|
|
5
5
|
import LoadIcon from './LoadIcon.svelte';
|
|
6
6
|
import TreeNode from './TreeNode.svelte';
|
|
7
|
-
import { getHashId, TreeStore, TREE_STORE_CONTEXT } from './treestore';
|
|
7
|
+
import { getHashId, transformSearchable, TreeStore, TREE_STORE_CONTEXT } from './treestore';
|
|
8
8
|
export let headers;
|
|
9
9
|
export let searchable = undefined;
|
|
10
|
+
export let filter = '';
|
|
10
11
|
export let nodeClass = undefined;
|
|
11
12
|
export let singleSelect = undefined;
|
|
12
13
|
export let enableResize = false;
|
|
@@ -24,8 +25,10 @@ export let moveHandler = undefined;
|
|
|
24
25
|
export let copyHandler = undefined;
|
|
25
26
|
export let dropEffect = undefined;
|
|
26
27
|
export let store = new TreeStore(fetchChildren, { copyHandler, dragEligible, dropEffect, moveHandler });
|
|
28
|
+
if (searchable)
|
|
29
|
+
store.searchableFn = transformSearchable(searchable);
|
|
27
30
|
setContext(TREE_STORE_CONTEXT, store);
|
|
28
|
-
const {
|
|
31
|
+
const { filteredRootItems, headerOverride } = store;
|
|
29
32
|
let checkboxelement;
|
|
30
33
|
const headerelements = [];
|
|
31
34
|
const treeWidth = new Store({});
|
|
@@ -57,14 +60,14 @@ function onDragEnd() {
|
|
|
57
60
|
}
|
|
58
61
|
let search = '';
|
|
59
62
|
let searchTimer = 0;
|
|
60
|
-
$:
|
|
63
|
+
$: store.filter(filter);
|
|
61
64
|
function onKeyUp(e) {
|
|
62
|
-
if (!searchableFn)
|
|
65
|
+
if (!store.searchableFn)
|
|
63
66
|
return;
|
|
64
67
|
if (e.key.length === 1) {
|
|
65
68
|
search += e.key;
|
|
66
69
|
const searchItems = $store.focused?.parent ? $store.focused.parent.children : $store.rootItems;
|
|
67
|
-
const newFocus = searchItems?.find(itm => searchableFn(itm).some(str => str.toLocaleLowerCase().startsWith(search.toLocaleLowerCase())));
|
|
70
|
+
const newFocus = searchItems?.find(itm => store.searchableFn(itm).some(str => str.toLocaleLowerCase().startsWith(search.toLocaleLowerCase())));
|
|
68
71
|
if (newFocus)
|
|
69
72
|
store.focus(newFocus);
|
|
70
73
|
clearTimeout(searchTimer);
|
|
@@ -150,24 +153,24 @@ afterUpdate(() => {
|
|
|
150
153
|
<div class="tree-header" class:resizing={!!dragheaderid} use:resize={{ store: treeWidth }} aria-hidden="true" on:mouseup={headerDragEnd} on:touchend={headerDragEnd} on:mousemove={dragheaderid ? headerDrag : undefined} on:touchmove={dragheaderid ? headerDrag : undefined}>
|
|
151
154
|
<div class="checkbox" bind:this={checkboxelement}> </div>
|
|
152
155
|
{#each headers as header, i (header.label)}
|
|
153
|
-
<div bind:this={headerelements[i]} id={header.id} class="tree-header-cell {header.id}" style:width={$headerOverride[header.id] ?? $headerSizes?.[i]}>{header.label}{#if i === 0 && $store.loading}<LoadIcon />{/if}{#if i === 0 && search} (searching: {search}){/if}</div>
|
|
156
|
+
<div bind:this={headerelements[i]} id={header.id} class="tree-header-cell {header.id}" style:width={$headerOverride[header.id] ?? $headerSizes?.[i]}>{header.label}{#if i === 0 && $store.loading}<LoadIcon />{/if}{#if i === 0 && isNotBlank(search)} (searching: {search}){/if}</div>
|
|
154
157
|
{#if enableResize && i !== headers.length - 1}<div class="tree-separator" on:mousedown={headerDragStart(header, i)} on:touchstart={headerDragStart(header, i)} on:dblclick={headerDragReset}> </div>{/if}
|
|
155
158
|
{/each}
|
|
156
159
|
</div>
|
|
157
|
-
{#if mounted && $
|
|
160
|
+
{#if mounted && $filteredRootItems?.length}
|
|
158
161
|
<!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
|
|
159
162
|
<ul bind:this={store.treeElement} role="tree" class:resizing={!!dragheaderid} on:mousemove={dragheaderid ? headerDrag : undefined} on:touchmove={dragheaderid ? headerDrag : undefined} on:mouseup={headerDragEnd} on:touchend={headerDragEnd} on:keyup={onKeyUp}>
|
|
160
|
-
{#each $
|
|
163
|
+
{#each $filteredRootItems as item, i (item.id)}
|
|
161
164
|
<TreeNode
|
|
162
165
|
{item}
|
|
163
166
|
{headers}
|
|
164
167
|
{headerSizes}
|
|
165
168
|
{nodeClass}
|
|
166
169
|
posinset={i + 1}
|
|
167
|
-
setsize={$
|
|
170
|
+
setsize={$filteredRootItems.length}
|
|
168
171
|
level={item.level}
|
|
169
|
-
prev={$
|
|
170
|
-
next={$
|
|
172
|
+
prev={$filteredRootItems[i - 1]}
|
|
173
|
+
next={$filteredRootItems[i + 1]}
|
|
171
174
|
on:choose
|
|
172
175
|
on:deselect
|
|
173
176
|
/>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
2
|
import { TreeStore } from './treestore';
|
|
3
|
-
import type { DragEligibleFn, CopyHandlerFn, DropEffectFn, FetchChildrenFn, MoveHandlerFn, TreeHeader, TreeItemFromDB } from './treestore';
|
|
3
|
+
import type { DragEligibleFn, CopyHandlerFn, DropEffectFn, FetchChildrenFn, MoveHandlerFn, TreeHeader, TreeItemFromDB, SearchableType } from './treestore';
|
|
4
4
|
declare class __sveltets_Render<T extends TreeItemFromDB> {
|
|
5
5
|
props(): {
|
|
6
6
|
headers: TreeHeader<T>[];
|
|
7
|
-
searchable?:
|
|
7
|
+
searchable?: SearchableType<T>;
|
|
8
|
+
filter?: string | undefined;
|
|
8
9
|
nodeClass?: ((itm: T) => string) | undefined;
|
|
9
10
|
singleSelect?: boolean | undefined;
|
|
10
11
|
enableResize?: boolean | undefined;
|
package/dist/tree/treestore.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface ITreeStore<T extends TreeItemFromDB> {
|
|
|
18
18
|
loading?: boolean;
|
|
19
19
|
rootItems?: TypedTreeItem<T>[];
|
|
20
20
|
itemsById: Record<string, TypedTreeItem<T> | undefined>;
|
|
21
|
+
filter?: string;
|
|
21
22
|
focused?: TypedTreeItem<T>;
|
|
22
23
|
selected: Map<string, TypedTreeItem<T>>;
|
|
23
24
|
selectedItems: TypedTreeItem<T>[];
|
|
@@ -33,6 +34,7 @@ export type DragEligibleFn<T extends TreeItemFromDB> = (selectedItems: TypedTree
|
|
|
33
34
|
export type DropEffectFn<T extends TreeItemFromDB> = (selectedItems: TypedTreeItem<T>[], dropTarget: TypedTreeItem<T>, above: boolean, userWantsCopy: boolean) => 'move' | 'copy' | 'none';
|
|
34
35
|
export type MoveHandlerFn<T extends TreeItemFromDB> = (selectedItems: TypedTreeItem<T>[], dropTarget: TypedTreeItem<T>, above: boolean) => boolean | Promise<boolean>;
|
|
35
36
|
export type CopyHandlerFn<T extends TreeItemFromDB> = (selectedItems: TypedTreeItem<T>[], dropTarget: TypedTreeItem<T>, above: boolean, userWantsRecursive: boolean | undefined) => boolean | Promise<boolean>;
|
|
37
|
+
export type SearchableFn<T extends TreeItemFromDB> = (item: TypedTreeItem<T>) => string[];
|
|
36
38
|
export interface TreeHeader<T extends TreeItemFromDB> {
|
|
37
39
|
id: string;
|
|
38
40
|
label: string;
|
|
@@ -48,7 +50,9 @@ export declare class TreeStore<T extends TreeItemFromDB> extends ActiveStore<ITr
|
|
|
48
50
|
#private;
|
|
49
51
|
fetchChildren: FetchChildrenFn<T>;
|
|
50
52
|
treeElement?: HTMLElement;
|
|
51
|
-
rootItems: import("svelte
|
|
53
|
+
rootItems: import("@txstate-mws/svelte-store").DerivedStore<TypedTreeItem<T>[] | undefined, ITreeStore<T>>;
|
|
54
|
+
filterTerm: import("@txstate-mws/svelte-store").DerivedStore<string | undefined, ITreeStore<T>>;
|
|
55
|
+
filteredRootItems: import("svelte/store").Readable<TypedTreeItem<T>[] | undefined>;
|
|
52
56
|
draggable: import("@txstate-mws/svelte-store").DerivedStore<boolean, ITreeStore<T>>;
|
|
53
57
|
dragging: import("@txstate-mws/svelte-store").DerivedStore<boolean, ITreeStore<T>>;
|
|
54
58
|
selectedUndraggable: import("@txstate-mws/svelte-store").DerivedStore<boolean | undefined, ITreeStore<T>>;
|
|
@@ -60,13 +64,15 @@ export declare class TreeStore<T extends TreeItemFromDB> extends ActiveStore<ITr
|
|
|
60
64
|
copyHandler?: CopyHandlerFn<T>;
|
|
61
65
|
dragEligibleHandler?: DragEligibleFn<T>;
|
|
62
66
|
dropEffectHandler?: DropEffectFn<T>;
|
|
67
|
+
searchableFn?: SearchableFn<T>;
|
|
63
68
|
singleSelect?: boolean;
|
|
64
69
|
private refreshPromise?;
|
|
65
|
-
constructor(fetchChildren: FetchChildrenFn<T>, { moveHandler, copyHandler, dragEligible, dropEffect, singleSelect }?: {
|
|
70
|
+
constructor(fetchChildren: FetchChildrenFn<T>, { moveHandler, copyHandler, dragEligible, dropEffect, searchableFn, singleSelect }?: {
|
|
66
71
|
moveHandler?: MoveHandlerFn<T>;
|
|
67
72
|
copyHandler?: CopyHandlerFn<T>;
|
|
68
73
|
dragEligible?: DragEligibleFn<T>;
|
|
69
74
|
dropEffect?: DropEffectFn<T>;
|
|
75
|
+
searchableFn?: SearchableFn<T>;
|
|
70
76
|
singleSelect?: boolean;
|
|
71
77
|
});
|
|
72
78
|
visit(item: TypedTreeItem<T>, cb: (item: TypedTreeItem<T>) => Promise<void>): Promise<void>;
|
|
@@ -77,6 +83,7 @@ export declare class TreeStore<T extends TreeItemFromDB> extends ActiveStore<ITr
|
|
|
77
83
|
trigger(): void;
|
|
78
84
|
fetch(item?: TypedTreeItem<T>): Promise<TypedTreeItem<T>[]>;
|
|
79
85
|
refresh(item?: TypedTreeItem<T>, skipNotify?: boolean): Promise<void>;
|
|
86
|
+
filter(term: string | undefined, notify?: boolean): void;
|
|
80
87
|
focus(item: TypedTreeItem<T> | undefined, notify?: boolean): void;
|
|
81
88
|
select(item: TypedTreeItem<T>, { clear, notify, toggle }: {
|
|
82
89
|
clear?: boolean | undefined;
|
|
@@ -118,3 +125,5 @@ export declare class TreeStore<T extends TreeItemFromDB> extends ActiveStore<ITr
|
|
|
118
125
|
export declare const hashedIds: Record<string, string>;
|
|
119
126
|
export declare function getHashId(str: string): string;
|
|
120
127
|
export declare const lazyObserver: IntersectionObserver | undefined;
|
|
128
|
+
export type SearchableType<T> = keyof T | (keyof T)[] | ((item: T) => string | string[]) | undefined;
|
|
129
|
+
export declare function transformSearchable<T>(searchable: SearchableType<T>): undefined | ((itm: T) => string[]);
|
package/dist/tree/treestore.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import { ActiveStore, derivedStore } from '@txstate-mws/svelte-store';
|
|
2
2
|
import { derived } from 'svelte/store';
|
|
3
|
-
import { hashid, keyby } from 'txstate-utils';
|
|
3
|
+
import { hashid, isBlank, keyby, toArray } from 'txstate-utils';
|
|
4
4
|
export const TREE_STORE_CONTEXT = {};
|
|
5
|
-
const rootItems = v => v.rootItems;
|
|
6
5
|
export class TreeStore extends ActiveStore {
|
|
7
6
|
fetchChildren;
|
|
8
7
|
treeElement;
|
|
9
|
-
rootItems =
|
|
8
|
+
rootItems = derivedStore(this, 'rootItems');
|
|
9
|
+
filterTerm = derivedStore(this, 'filter');
|
|
10
|
+
filteredRootItems = derived([this.rootItems, this.filterTerm], ([rootItems, filter]) => {
|
|
11
|
+
if (!this.searchableFn || !rootItems?.length || isBlank(filter))
|
|
12
|
+
return this.value.rootItems;
|
|
13
|
+
const ret = [];
|
|
14
|
+
for (const itm of this.value.rootItems ?? []) {
|
|
15
|
+
let found = false;
|
|
16
|
+
for (const val of this.searchableFn(itm)) {
|
|
17
|
+
if (val.startsWith(filter))
|
|
18
|
+
found = true;
|
|
19
|
+
}
|
|
20
|
+
if (found)
|
|
21
|
+
ret.push(itm);
|
|
22
|
+
}
|
|
23
|
+
return ret;
|
|
24
|
+
});
|
|
10
25
|
draggable = derivedStore(this, v => v.draggable && !v.loading);
|
|
11
26
|
dragging = derivedStore(this, 'dragging');
|
|
12
27
|
selectedUndraggable = derivedStore(this, 'selectedUndraggable');
|
|
@@ -18,15 +33,17 @@ export class TreeStore extends ActiveStore {
|
|
|
18
33
|
copyHandler;
|
|
19
34
|
dragEligibleHandler;
|
|
20
35
|
dropEffectHandler;
|
|
36
|
+
searchableFn;
|
|
21
37
|
singleSelect;
|
|
22
38
|
refreshPromise;
|
|
23
|
-
constructor(fetchChildren, { moveHandler, copyHandler, dragEligible, dropEffect, singleSelect } = {}) {
|
|
39
|
+
constructor(fetchChildren, { moveHandler, copyHandler, dragEligible, dropEffect, searchableFn, singleSelect } = {}) {
|
|
24
40
|
super({ itemsById: {}, selected: new Map(), selectedItems: [], copied: new Map(), dragging: false, draggable: !!moveHandler, headerWidthOverrides: {} });
|
|
25
41
|
this.fetchChildren = fetchChildren;
|
|
26
42
|
this.moveHandler = moveHandler;
|
|
27
43
|
this.copyHandler = copyHandler;
|
|
28
44
|
this.dragEligibleHandler = dragEligible;
|
|
29
45
|
this.dropEffectHandler = dropEffect;
|
|
46
|
+
this.searchableFn = searchableFn;
|
|
30
47
|
this.singleSelect = singleSelect;
|
|
31
48
|
}
|
|
32
49
|
async visit(item, cb) {
|
|
@@ -125,6 +142,11 @@ export class TreeStore extends ActiveStore {
|
|
|
125
142
|
await this.refreshPromise;
|
|
126
143
|
this.refreshPromise = undefined;
|
|
127
144
|
}
|
|
145
|
+
filter(term, notify = true) {
|
|
146
|
+
this.value.filter = term;
|
|
147
|
+
if (notify)
|
|
148
|
+
this.trigger();
|
|
149
|
+
}
|
|
128
150
|
focus(item, notify = true) {
|
|
129
151
|
this.value.focused = item;
|
|
130
152
|
if (notify)
|
|
@@ -357,3 +379,13 @@ export const lazyObserver = typeof IntersectionObserver !== 'undefined'
|
|
|
357
379
|
}
|
|
358
380
|
}, { rootMargin: '500px' })
|
|
359
381
|
: undefined;
|
|
382
|
+
export function transformSearchable(searchable) {
|
|
383
|
+
console.log('transformSearchable', searchable);
|
|
384
|
+
return searchable == null
|
|
385
|
+
? undefined
|
|
386
|
+
: (typeof searchable === 'function'
|
|
387
|
+
? (itm) => toArray(searchable(itm))
|
|
388
|
+
: (Array.isArray(searchable)
|
|
389
|
+
? (itm) => searchable.map(k => itm[k])
|
|
390
|
+
: (itm) => [itm[searchable]]));
|
|
391
|
+
}
|
package/package.json
CHANGED