UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

360 lines (359 loc) 10.9 kB
"use client"; import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { clsx } from 'clsx'; import { useSharedState } from "../../shared/helpers/useSharedState.js"; import SharedContext from "../../shared/Context.js"; import { FilterContext } from "./FilterContext.js"; import { useSpacing } from "../space/SpacingUtils.js"; import withComponentMarkers from "../../shared/helpers/withComponentMarkers.js"; import { jsx as _jsx } from "react/jsx-runtime"; const initialState = { search: '', filters: {} }; function toChangeState({ search, filters }) { return { search, filters }; } function deriveAccordionState(filters) { const keys = new Set(); for (const key of Object.keys(filters)) { const lastSlash = key.lastIndexOf('/'); if (lastSlash > 0) { keys.add(key.slice(0, lastSlash)); } } const state = {}; for (const k of Array.from(keys)) { state[k] = true; } return state; } function FilterRoot({ id, behavior = 'realtime', defaultFilters, defaultPanelOpen, resultCount, resultLoading, onChange, className, style, children, ...spacingRest }) { const sharedContext = useContext(SharedContext); const { ariaLabel } = sharedContext.getTranslation({}).Filter; const onChangeRef = useRef(onChange); onChangeRef.current = onChange; const hasDefaultFilters = defaultFilters && Object.keys(defaultFilters).length > 0; const defaultAccordionState = useMemo(() => { if (!hasDefaultFilters) { return {}; } return deriveAccordionState(defaultFilters); }, []); const accordionStateId = id ? `${id}__accordion` : undefined; const { data: savedAccordionState, extend: extendAccordionState } = useSharedState(accordionStateId, defaultAccordionState); const accordionStateRef = useRef(savedAccordionState !== null && savedAccordionState !== void 0 ? savedAccordionState : defaultAccordionState); const defaultState = useMemo(() => defaultFilters ? { search: '', filters: defaultFilters } : initialState, [defaultFilters]); const { data, get, extend } = useSharedState(id, defaultState); const isManual = behavior === 'manual'; const [localState, setLocalState] = useState(defaultState); const committedState = id ? data !== null && data !== void 0 ? data : defaultState : localState; const committedStateRef = useRef(committedState); committedStateRef.current = committedState; const getCommittedState = useCallback(() => { var _get; return id ? (_get = get()) !== null && _get !== void 0 ? _get : defaultState : committedStateRef.current; }, [defaultState, get, id]); const updateCommittedState = useCallback(nextState => { if (id) { extend(nextState); } else { const next = { ...committedStateRef.current, ...nextState }; committedStateRef.current = next; setLocalState(next); } }, [extend, id]); const [draftState, setDraftState] = useState(() => data !== null && data !== void 0 ? data : defaultState); const draftRef = useRef(draftState); draftRef.current = draftState; useEffect(() => { if (hasDefaultFilters) { updateCommittedState({ filters: defaultFilters }); } }, []); useEffect(() => { const updates = {}; if (resultLoading !== undefined) { updates.resultLoading = resultLoading; } if (resultCount !== undefined) { updates.resultCount = resultCount; } if (Object.keys(updates).length > 0) { updateCommittedState(updates); } }, [resultLoading, resultCount, updateCommittedState]); const state = isManual ? draftState : committedState; const setSearch = useCallback(search => { if (isManual) { const filters = getCommittedState().filters; setDraftState(prev => ({ ...prev, search })); updateCommittedState({ search }); onChangeRef.current?.({ search, filters }); } else { updateCommittedState({ search }); const filters = getCommittedState().filters; onChangeRef.current?.({ search, filters }); } }, [getCommittedState, isManual, updateCommittedState]); const setFilter = useCallback((filterKey, value) => { if (isManual) { setDraftState(prev => { const next = { ...prev.filters }; if (value === undefined) { delete next[filterKey]; } else { next[filterKey] = value; } return { ...prev, filters: next }; }); } else { const latest = getCommittedState(); const next = { ...latest.filters }; if (value === undefined) { delete next[filterKey]; } else { next[filterKey] = value; } updateCommittedState({ filters: next }); onChangeRef.current?.(toChangeState({ ...latest, filters: next })); } }, [getCommittedState, isManual, updateCommittedState]); const getFilter = useCallback(filterKey => { return state.filters[filterKey]; }, [state.filters]); const submitFilterRemoval = useCallback(filterKey => { const committed = getCommittedState(); const nextAppliedFilters = { ...committed.filters }; delete nextAppliedFilters[filterKey]; const draft = draftRef.current; const nextDraftFilters = { ...draft.filters }; delete nextDraftFilters[filterKey]; const nextDraftState = { ...draft, filters: nextDraftFilters }; const nextAppliedState = { search: committed.search, filters: nextAppliedFilters }; setDraftState(nextDraftState); updateCommittedState(nextAppliedState); onChangeRef.current?.(toChangeState(nextAppliedState)); }, [getCommittedState, updateCommittedState]); const removeFilter = useCallback(filterKey => { if (isManual) { const appliedFilters = getCommittedState().filters; if (Object.hasOwn(appliedFilters, filterKey)) { submitFilterRemoval(filterKey); } else { setFilter(filterKey, undefined); } } else { setFilter(filterKey, undefined); } }, [getCommittedState, isManual, setFilter, submitFilterRemoval]); const removeAppliedFilter = useCallback(filterKey => { if (isManual) { submitFilterRemoval(filterKey); } else { removeFilter(filterKey); } }, [isManual, removeFilter, submitFilterRemoval]); const clearFilters = useCallback(() => { const latest = isManual ? draftRef.current : getCommittedState(); const nextState = { ...latest, filters: {} }; if (isManual) { setDraftState(nextState); } updateCommittedState({ search: nextState.search, filters: nextState.filters }); onChangeRef.current?.(toChangeState(nextState)); }, [getCommittedState, isManual, updateCommittedState]); const replaceFilters = useCallback(filters => { if (isManual) { setDraftState(prev => ({ ...prev, filters })); } else { updateCommittedState({ filters }); const latest = getCommittedState(); onChangeRef.current?.(toChangeState({ ...latest, filters })); } }, [getCommittedState, isManual, updateCommittedState]); const resetFilters = useCallback(() => { const next = { search: '', filters: {} }; if (isManual) { setDraftState(next); } updateCommittedState(next); onChangeRef.current?.(next); }, [isManual, updateCommittedState]); const commitFilters = useCallback(() => { const draft = draftRef.current; updateCommittedState({ search: draft.search, filters: draft.filters }); onChangeRef.current?.(toChangeState(draft)); }, [updateCommittedState]); const revertFilters = useCallback(() => { const committed = getCommittedState(); setDraftState({ search: committed.search, filters: committed.filters }); }, [getCommittedState]); const hasActiveFilters = state.search.length > 0 || Object.keys(state.filters).length > 0; const [panelOpen, setPanelOpen] = useState(defaultPanelOpen !== null && defaultPanelOpen !== void 0 ? defaultPanelOpen : !!hasDefaultFilters); const panelButtonRef = useRef(null); useEffect(() => { var _current$filters; if (defaultPanelOpen !== undefined || hasDefaultFilters) { return; } const current = getCommittedState(); const filters = (_current$filters = current?.filters) !== null && _current$filters !== void 0 ? _current$filters : {}; if (Object.keys(filters).length > 0) { setPanelOpen(true); const accordionState = deriveAccordionState(filters); accordionStateRef.current = { ...accordionStateRef.current, ...accordionState }; extendAccordionState(accordionState); } }, []); const getAccordionOpen = useCallback(filterKey => { return accordionStateRef.current[filterKey]; }, []); const setAccordionOpen = useCallback((filterKey, open) => { accordionStateRef.current[filterKey] = open; extendAccordionState({ [filterKey]: open }); }, [extendAccordionState]); const appliedState = committedState; const resolvedResultCount = resultCount !== null && resultCount !== void 0 ? resultCount : committedState.resultCount; const resolvedResultLoading = resultLoading !== null && resultLoading !== void 0 ? resultLoading : committedState.resultLoading; const contextValue = useMemo(() => ({ id, behavior, state, appliedState, setSearch, setFilter, getFilter, removeFilter, removeAppliedFilter, clearFilters, replaceFilters, resetFilters, commitFilters, revertFilters, hasActiveFilters, resultCount: resolvedResultCount, resultLoading: resolvedResultLoading, panelOpen, setPanelOpen, panelButtonRef, getAccordionOpen, setAccordionOpen }), [id, behavior, state, appliedState, setSearch, setFilter, getFilter, removeFilter, removeAppliedFilter, clearFilters, replaceFilters, resetFilters, commitFilters, revertFilters, hasActiveFilters, resolvedResultCount, resolvedResultLoading, panelOpen, setPanelOpen, panelButtonRef, getAccordionOpen, setAccordionOpen]); const spacingProps = useSpacing(spacingRest, { className: clsx('dnb-filter', className), style }); return _jsx(FilterContext, { value: contextValue, children: _jsx("div", { ...spacingProps, role: "search", "aria-label": ariaLabel, children: children }) }); } withComponentMarkers(FilterRoot, { _supportsSpacingProps: true }); export default FilterRoot; //# sourceMappingURL=FilterRoot.js.map