All files / blong-browser/src/widgets MultiSelectTreeWidget.tsx

42.85% Statements 33/77
100% Branches 1/1
0% Functions 0/1
42.85% Lines 33/77

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 781x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                                          
import type {IWidgetProps} from '@feasibleone/blong';
import {useEffect, useState} from 'react';
import {useBlong} from '../context/BlongContext.js';
import {dropdownRegistry} from '../model/dropdownRegistry.js';
import {TreeSelect, type TreeNode} from '../primereact/index.js';
 
export function MultiSelectTreeWidget({
    id,
    name,
    schema,
    value,
    onChange,
    onBlur,
    error,
    readOnly,
    disabled,
}: IWidgetProps) {
    const {fetch: fetchAction, options: staticOptions, dropdown: dropdownKey} = schema.widget ?? {};
    const {handler} = useBlong();
    const [options, setOptions] = useState<unknown[]>(staticOptions ?? []);
 
    useEffect(() => {
        if (staticOptions) return;
        let cancelled = false;
 
        if (dropdownKey) {
            const loader = (key: string) =>
                (
                    handler.portalDropdownList({names: [key]}, {}) as Promise<
                        Record<string, unknown>
                    >
                ).then(result => result[key] as unknown[]);
            dropdownRegistry
                .get(dropdownKey, loader as Parameters<typeof dropdownRegistry.get>[1])
                .then(data => {
                    if (!cancelled) setOptions(data as unknown[]);
                })
                .catch(() => {});
            return () => {
                cancelled = true;
            };
        }

        if (!fetchAction) return;
        (handler[fetchAction]({}, {}) as Promise<unknown[]>)
            .then(data => {
                if (!cancelled) setOptions(Array.isArray(data) ? data : []);
            })
            .catch(() => {});
        return () => {
            cancelled = true;
        };
    }, [fetchAction, dropdownKey, handler, staticOptions]);

    // TreeSelect multi value: {[key]: true} ↔ string[]
    const arrValue = Array.isArray(value) ? (value as string[]) : [];
    const treeValue = Object.fromEntries(arrValue.map(v => [v, true])) as Record<string, boolean>;

    return (
        <TreeSelect
            inputId={id ?? name}
            data-testid={id ?? name}
            value={treeValue}
            options={options as TreeNode[]}
            selectionMode="multiple"
            display="chip"
            metaKeySelection={false}
            onChange={e =>
                onChange(e.value && typeof e.value === 'object' ? Object.keys(e.value) : [])
            }
            onHide={onBlur}
            disabled={disabled || readOnly}
            className={`blong-multiselect-tree w-full ${error ? 'p-invalid' : ''}`}
            placeholder={schema.placeholder ?? 'Select…'}
        />
    );
}