@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
361 lines (360 loc) • 10.9 kB
JavaScript
"use client";
import _Object$hasOwn from "core-js-pure/stable/object/has-own.js";
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