All files / core/blong-browser/src/widgets MultiSelectWidget.tsx

56.75% Statements 21/37
59.61% Branches 31/52
43.75% Functions 7/16
65.51% Lines 19/29

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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112                    4x 23x                                           29x 29x 29x 18x     29x 18x 18x     14x 14x   1x       14x               14x 14x                               29x       29x   29x           29x                                              
import {MultiSelect} from '../primereact/index.js';
 
import type {IDropdownOption, IWidgetProps} from '@feasibleone/blong';
import {useEffect, useState} from 'react';
import {useBlong} from '../context/BlongContext.js';
import {dropdownRegistry} from '../model/dropdownRegistry.js';
 
type SelectOption = IDropdownOption;
 
function toOptions(data: unknown): SelectOption[] {
    Iif (!Array.isArray(data)) return [];
    return data.map((item: Record<string, unknown>) => ({
        label: String(item.label ?? item.name ?? item.value ?? item),
        value: item.value ?? item,
    }));
}
 
export function MultiSelectWidget({
    id,
    name,
    schema,
    value,
    onChange,
    onBlur,
    error,
    readOnly,
    disabled,
}: IWidgetProps) {
    const {
        fetch: fetchAction,
        options: staticOptions,
        dropdown: dropdownKey,
        type: widgetType,
    } = schema.widget ?? {};
    const {handler} = useBlong();
    const [options, setOptions] = useState<SelectOption[]>(() =>
        staticOptions ? toOptions(staticOptions) : [],
    );
 
    useEffect(() => {
        let cancelled = false;
        if (staticOptions) return;
 
        // Priority 1: named dropdown via portal orchestrator (batched + cached)
        Eif (dropdownKey) {
            const loader = (key: string) =>
                (
                    handler.portalDropdownList({names: [key]}, {}) as Promise<
                        Record<string, unknown>
                    >
                ).then(result => toOptions(result[key]));
            dropdownRegistry
                .get(dropdownKey, loader)
                .then(data => {
                    if (!cancelled) setOptions(data);
                })
                .catch(() => {
                    // Error surfaced by the central dispatch wrapper in BlongProvider.
                });
            return () => {
                cancelled = true;
            };
        }
 
        // Priority 2: explicit fetch action
        if (!fetchAction) return;
        (handler[fetchAction]({}, {}) as Promise<unknown>)
            .then(data => {
                if (!cancelled) setOptions(toOptions(data));
            })
            .catch(() => {});
        return () => {
            cancelled = true;
        };
    }, [fetchAction, dropdownKey, handler, staticOptions]);
 
    const arrValue: unknown[] = Array.isArray(value) ? value : value != null ? [value] : [];
 
    // multiSelectPanel: render as inline checkbox panel
    // (inline + flex + itemClassName='col-3' → 4-column grid of checkboxes)
    const isPanel = widgetType === 'multiSelectPanel';
 
    Iif (readOnly && !isPanel) {
        // Regular multi-select in read-only: show comma-separated labels
        const labels = arrValue.map(v => options.find(o => o.value === v)?.label ?? String(v));
        return <span className="blong-display">{labels.join(', ')}</span>;
    }
 
    return (
        <MultiSelect
            inputId={id ?? name}
            data-testid={id ?? name}
            value={arrValue}
            options={options}
            onChange={readOnly ? undefined : e => onChange(e.value)}
            onHide={isPanel || readOnly ? undefined : onBlur}
            disabled={disabled || readOnly}
            className={`blong-multiselect w-full ${error ? 'p-invalid' : ''}`}
            display="chip"
            filter={!isPanel && options.length > 8}
            placeholder={schema.placeholder ?? 'Select…'}
            {...(isPanel
                ? {
                      inline: true,
                      flex: true,
                      itemClassName: 'col-3',
                  }
                : {})}
        />
    );
}