115 lines
2.9 KiB
Svelte
115 lines
2.9 KiB
Svelte
<script>
|
|
import { writable } from 'svelte/store';
|
|
import { tick, createEventDispatcher } from 'svelte';
|
|
|
|
import TriStateCheckbox from './TriStateCheckbox.svelte';
|
|
import { createTriState, createTriStates, UNCHECKED, CHECKED, WEIRD } from './stores';
|
|
import { Icon } from 'sveltestrap';
|
|
|
|
export let item = null;
|
|
|
|
export let children = [];
|
|
export let selected = [];
|
|
export let filtering = createTriState(CHECKED);
|
|
let subfiltering = createTriStates(CHECKED, Object.entries(children).length);
|
|
let subselected = Array.from({ length: Object.entries(children).length }, e => []);
|
|
|
|
function isVal(val) {
|
|
return function (other) {
|
|
return other === val;
|
|
};
|
|
}
|
|
|
|
function areAllChecked(values) {
|
|
return values.every(isVal(CHECKED));
|
|
}
|
|
|
|
function areAllUnchecked(values) {
|
|
return values.every(isVal(UNCHECKED));
|
|
}
|
|
|
|
function handleChildChange(i) {
|
|
return function handler(evt) {
|
|
subfiltering.setAt(i, evt.detail.value);
|
|
};
|
|
}
|
|
|
|
subfiltering.subscribe(subvalues => {
|
|
if (areAllChecked(subvalues) && $filtering !== CHECKED) {
|
|
filtering.setChecked();
|
|
} else if (areAllUnchecked(subvalues) && $filtering !== UNCHECKED) {
|
|
filtering.setUnchecked();
|
|
} else if (
|
|
!areAllChecked(subvalues) &&
|
|
!areAllUnchecked(subvalues) &&
|
|
$filtering !== WEIRD
|
|
) {
|
|
filtering.setWeird();
|
|
}
|
|
});
|
|
|
|
if (subfiltering.length > 0) {
|
|
filtering.subscribe(value => {
|
|
switch (value) {
|
|
case CHECKED:
|
|
subfiltering.updateAll(_ => CHECKED);
|
|
break;
|
|
case UNCHECKED:
|
|
subfiltering.updateAll(_ => UNCHECKED);
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
const dispatch = createEventDispatcher();
|
|
function handleChange(evt) {
|
|
filtering.set(evt.detail.value);
|
|
dispatch('change', { value: evt.detail.value });
|
|
}
|
|
|
|
$: icon = () => {
|
|
switch ($filtering) {
|
|
case UNCHECKED:
|
|
return 'circle';
|
|
case WEIRD:
|
|
return 'circle-half';
|
|
case CHECKED:
|
|
return 'check-circle-fill';
|
|
}
|
|
};
|
|
|
|
$: selected =
|
|
$filtering === CHECKED ? [item, ...subselected.flat()] : subselected.flat();
|
|
</script>
|
|
|
|
{#if item != null}
|
|
<li>
|
|
<TriStateCheckbox
|
|
state={$filtering}
|
|
on:change={handleChange}
|
|
value={item}
|
|
initialValue={true}
|
|
/>
|
|
<span><Icon name={icon()} />{item}</span>
|
|
<ul>
|
|
{#each Object.entries(children) as [toplevel, subchildren], i}
|
|
{#if subchildren}
|
|
<svelte:self
|
|
item={toplevel}
|
|
children={subchildren}
|
|
on:change={handleChildChange(i)}
|
|
filtering={subfiltering.storeAt(i)}
|
|
bind:selected={subselected[i]}
|
|
/>
|
|
{:else}
|
|
<svelte:self
|
|
item={toplevel}
|
|
on:change={handleChildChange(i)}
|
|
filtering={subfiltering.storeAt(i)}
|
|
bind:selected={subselected[i]}
|
|
/>
|
|
{/if}
|
|
{/each}
|
|
</ul>
|
|
</li>
|
|
{/if}
|