@trackunit/filters-filter-bar 1.21.4 → 1.21.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +38 -145
- package/index.esm.js +39 -146
- package/package.json +9 -9
- package/src/lib/hooks/utils/useFilterBarPersistence.d.ts +2 -7
package/index.cjs.js
CHANGED
|
@@ -17,7 +17,6 @@ var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities
|
|
|
17
17
|
var dequal = require('dequal');
|
|
18
18
|
var geoJsonUtils = require('@trackunit/geo-json-utils');
|
|
19
19
|
var zod = require('zod');
|
|
20
|
-
var reactRouter = require('@tanstack/react-router');
|
|
21
20
|
|
|
22
21
|
var defaultTranslations = {
|
|
23
22
|
"access.management.filter.operator.role.keyAdmin": "Key Admin",
|
|
@@ -2045,125 +2044,31 @@ const useFilterUrlSync = () => {
|
|
|
2045
2044
|
}), [getFilterValuesFromUrl, getFilterValuesToUrl]);
|
|
2046
2045
|
};
|
|
2047
2046
|
|
|
2048
|
-
/**
|
|
2049
|
-
* This hook provides functionality to:
|
|
2050
|
-
* - Get the length of the search parameters excluding a specific key
|
|
2051
|
-
*/
|
|
2052
|
-
const useSearchUtils = () => {
|
|
2053
|
-
const location = reactRouter.useLocation();
|
|
2054
|
-
const getSearchParamsLengthExcluding = react.useCallback((excludeKey, searchParams) => {
|
|
2055
|
-
return sharedUtils.objectKeys(searchParams)
|
|
2056
|
-
.filter(key => key !== excludeKey)
|
|
2057
|
-
.reduce((totalLength, key) => {
|
|
2058
|
-
const keyLength = encodeURIComponent(String(key)).length;
|
|
2059
|
-
const value = searchParams[key] !== null ? encodeURIComponent(searchParams[key]?.toString() ?? "") : "";
|
|
2060
|
-
const valueLength = value.length;
|
|
2061
|
-
// Add 1 for '=' and 1 for '&' (except for the first param)
|
|
2062
|
-
return totalLength + keyLength + valueLength + (totalLength > 0 ? 2 : 1);
|
|
2063
|
-
}, 0);
|
|
2064
|
-
}, []);
|
|
2065
|
-
const getUrlLengthWithSearchParam = react.useCallback((key, value, prev) => {
|
|
2066
|
-
const searchParamsLength = getSearchParamsLengthExcluding(key, prev);
|
|
2067
|
-
const urlLength = location.href.length - (location.searchStr.length || 0) + (location.hash.length || 0) + searchParamsLength;
|
|
2068
|
-
// 1 === '&' and 1 === '='
|
|
2069
|
-
return urlLength + 1 + key.length + 1 + value.length;
|
|
2070
|
-
}, [getSearchParamsLengthExcluding, location]);
|
|
2071
|
-
return react.useMemo(() => ({ getSearchParamsLengthExcluding, getUrlLengthWithSearchParam }), [getSearchParamsLengthExcluding, getUrlLengthWithSearchParam]);
|
|
2072
|
-
};
|
|
2073
|
-
|
|
2074
|
-
const MAX_URL_LENGTH = 5000;
|
|
2075
|
-
/**
|
|
2076
|
-
* Get the persistence key for the filter bar.
|
|
2077
|
-
*
|
|
2078
|
-
* @param {string} name - The name of the filter bar.
|
|
2079
|
-
* @param {string} clientSideUserId - The client side user id.
|
|
2080
|
-
* @returns {string} The persistence key.
|
|
2081
|
-
*/
|
|
2082
|
-
const getPersistenceKey = (name, clientSideUserId) => `filter-${name}-${clientSideUserId}`;
|
|
2083
2047
|
/**
|
|
2084
2048
|
* Custom hook for managing the persistence of filter bar configurations.
|
|
2085
2049
|
*/
|
|
2086
2050
|
const useFilterBarPersistence = ({ name, setValue, refreshData, isDefaultValue, loadData: inputLoadData, saveData: inputSaveData, }) => {
|
|
2087
2051
|
const { clientSideUserId } = reactCoreHooks.useCurrentUser();
|
|
2088
|
-
const search = reactRouter.useSearch({ strict: false, shouldThrow: false });
|
|
2089
|
-
const location = reactRouter.useLocation();
|
|
2090
|
-
const navigate = reactRouter.useNavigate();
|
|
2091
2052
|
const { encode, decode } = reactComponents.useCustomEncoding();
|
|
2092
|
-
const { getUrlLengthWithSearchParam } = useSearchUtils();
|
|
2093
|
-
const updateSearch = react.useCallback(async (searchParams) => {
|
|
2094
|
-
if (!inputLoadData && !inputSaveData && Boolean(search)) {
|
|
2095
|
-
// should check if the state has actually changed from what we last sent to the URL
|
|
2096
|
-
if (!searchParams[name] ||
|
|
2097
|
-
(typeof searchParams[name] === "string" &&
|
|
2098
|
-
!dequal.dequal(decode(searchParams[name]), typeof search[name] === "string" ? decode(search[name]) : search[name]))) {
|
|
2099
|
-
return requestAnimationFrame(async () => {
|
|
2100
|
-
const replace = search[name] === undefined || search[name] === null;
|
|
2101
|
-
await navigate({
|
|
2102
|
-
to: ".",
|
|
2103
|
-
search: (prev) => {
|
|
2104
|
-
if (getUrlLengthWithSearchParam(name, searchParams[name] || "", prev) <= MAX_URL_LENGTH) {
|
|
2105
|
-
return { ...prev, ...searchParams };
|
|
2106
|
-
}
|
|
2107
|
-
else {
|
|
2108
|
-
// eslint-disable-next-line no-console
|
|
2109
|
-
console.log(`URL too long, skipping sync of filters to the browsers URL, to avoid crashing the browser, limit is ${MAX_URL_LENGTH}, was: ${getUrlLengthWithSearchParam(name, searchParams[name] || "", prev)}`);
|
|
2110
|
-
const newSearchParams = { ...prev, [name]: undefined };
|
|
2111
|
-
return newSearchParams;
|
|
2112
|
-
}
|
|
2113
|
-
},
|
|
2114
|
-
hash: location.hash,
|
|
2115
|
-
replace,
|
|
2116
|
-
});
|
|
2117
|
-
});
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
return Promise.resolve();
|
|
2121
|
-
}, [navigate, name, search, inputLoadData, inputSaveData, decode, getUrlLengthWithSearchParam, location.hash]);
|
|
2122
|
-
const getFromLocalStorage = react.useCallback((persistenceKey) => {
|
|
2123
|
-
return localStorage.getItem(getPersistenceKey(persistenceKey, clientSideUserId));
|
|
2124
|
-
}, [clientSideUserId]);
|
|
2125
|
-
const lastName = react.useRef(name);
|
|
2126
|
-
const [initialStoredFilters] = react.useState(() => (!inputLoadData && getFromLocalStorage(name)) || "{}");
|
|
2127
|
-
const { getFilterValuesFromUrl, getFilterValuesToUrl } = useFilterUrlSync();
|
|
2128
|
-
const lastSavedStateRef = react.useRef(undefined);
|
|
2129
|
-
const lastSearchUpdateRef = react.useRef(undefined);
|
|
2130
2053
|
const refreshDataRef = react.useRef(refreshData);
|
|
2131
2054
|
react.useEffect(() => {
|
|
2132
2055
|
refreshDataRef.current = refreshData;
|
|
2133
2056
|
}, [refreshData]);
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
}
|
|
2150
|
-
catch (_) {
|
|
2151
|
-
// Invalid compressed data, treat as undefined
|
|
2152
|
-
lastSearchUpdateRefParsed = undefined;
|
|
2153
|
-
}
|
|
2154
|
-
// H4sIADGRuGgAA3WTy07eQAxGXwVlXUtje27uruqWd0C2x1NaAa2AdoN49xq6-LOgUpQo0onny7H9ctzHsy591q8_73_dxXM8xNPT8fnl-KN3v-P4fHy5vj4-HQ96__5yd3e8fjqeQh_9Nt9v48fVfayr9f3b1YrHBH3f3GDBaj4Zui-HuvsADeO8KeFGrmYrj8hCbzTZrF0coRMRVEYHGcKAsW1Iw8qiF3ox06zbASth1t4GVsqCUvIxqO9R-EJ7w2aDJnjHCbV6JtmlgOSHQzB4DLvQtiwiRGB3aUk3h0lLgdyjFiyD8USPhd55BYzakkbaSbeA2jfmFaV7v9BMErKLguWpUNdQEKwTsqqXNETL_eRkbqojy1K0pCtVkK4LJM2VPtk6lQtdcIpSs2RGno9aQG0LSJrmvgrKHCeDFWWQN0iNNZMYgczFQNtH0VbqKifaq7uVBFdvBIgRIBoClcLXQFlC7VR7E7l2PdHKnH36kN5rJFY96Z0m3mhTKv-hG8cscytgU0vLnAOlTNAWdd_TR11y8m3dsxsGyJp_2XOg5hyZOxaP2Qu6nX137DbnW5L0_S-3p9CPk2hC09sG15SXgrKrHgQlpYe6ZKl9oScNb74IOmMmmbkSYmmHdYWZ7z3baWKpiWq0nA6u2fkEYFoOS-_OtibuIqfdaa0oDytgnB2saSC3YTSI0UKIc9nofWJf_wIRCrRd6AMAAA
|
|
2155
|
-
// Check if this is an external URL change (not from our own updates)
|
|
2156
|
-
// Only trigger refreshData if there's a meaningful change in the URL value
|
|
2157
|
-
if (search?.[name] !== undefined && !dequal.dequal(lastSearchUpdateRefParsed, currentSearchValueParsed)) {
|
|
2158
|
-
// This is an external URL change with actual data, trigger refreshData
|
|
2159
|
-
void requestAnimationFrame(() => {
|
|
2160
|
-
if (refreshDataRef.current) {
|
|
2161
|
-
refreshDataRef.current();
|
|
2162
|
-
}
|
|
2163
|
-
});
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
}, [search, name, inputLoadData, inputSaveData, decode]);
|
|
2057
|
+
const { searchValue, updateSearchParam } = reactComponents.useSearchParamSync({
|
|
2058
|
+
key: name,
|
|
2059
|
+
enabled: !inputLoadData && !inputSaveData,
|
|
2060
|
+
onExternalChange: () => {
|
|
2061
|
+
refreshDataRef.current?.();
|
|
2062
|
+
},
|
|
2063
|
+
});
|
|
2064
|
+
const storageKey = reactComponents.useStorageKey(`filter-${name}`, clientSideUserId);
|
|
2065
|
+
const getFromLocalStorage = react.useCallback(() => {
|
|
2066
|
+
return localStorage.getItem(storageKey);
|
|
2067
|
+
}, [storageKey]);
|
|
2068
|
+
const lastName = react.useRef(name);
|
|
2069
|
+
const [initialStoredFilters] = react.useState(() => (!inputLoadData && getFromLocalStorage()) || "{}");
|
|
2070
|
+
const { getFilterValuesFromUrl, getFilterValuesToUrl } = useFilterUrlSync();
|
|
2071
|
+
const lastSavedStateRef = react.useRef(undefined);
|
|
2167
2072
|
const saveData = react.useCallback((filterBarConfig, filterBarDefinitions) => {
|
|
2168
2073
|
const newValues = Object.assign({}, lastSavedStateRef.current || {});
|
|
2169
2074
|
if (filterBarConfig.values) {
|
|
@@ -2184,22 +2089,16 @@ const useFilterBarPersistence = ({ name, setValue, refreshData, isDefaultValue,
|
|
|
2184
2089
|
inputSaveData(newValues);
|
|
2185
2090
|
}
|
|
2186
2091
|
else {
|
|
2187
|
-
localStorage.setItem(
|
|
2092
|
+
localStorage.setItem(storageKey, reactComponents.storageSerializer.serialize(toPersist));
|
|
2188
2093
|
const urlObject = getFilterValuesToUrl(newValues, sharedUtils.objectValues(filterBarDefinitions), false, isDefaultValue);
|
|
2189
|
-
|
|
2190
|
-
if (filterBarConfig.name) {
|
|
2191
|
-
result[filterBarConfig.name] = encode(urlObject);
|
|
2192
|
-
// Update the ref BEFORE updating the URL to prevent false external change detection
|
|
2193
|
-
lastSearchUpdateRef.current = result[filterBarConfig.name];
|
|
2194
|
-
}
|
|
2195
|
-
updateSearch(result);
|
|
2094
|
+
updateSearchParam(encode(urlObject));
|
|
2196
2095
|
}
|
|
2197
2096
|
}
|
|
2198
|
-
}, [
|
|
2097
|
+
}, [storageKey, inputSaveData, getFilterValuesToUrl, updateSearchParam, encode, isDefaultValue]);
|
|
2199
2098
|
const loadFromLocalStorage = react.useCallback(() => {
|
|
2200
2099
|
let storedFilters = null;
|
|
2201
2100
|
if (lastName.current !== name) {
|
|
2202
|
-
storedFilters = localStorage.getItem(
|
|
2101
|
+
storedFilters = localStorage.getItem(storageKey) || "{}";
|
|
2203
2102
|
lastName.current = name;
|
|
2204
2103
|
}
|
|
2205
2104
|
else {
|
|
@@ -2207,29 +2106,34 @@ const useFilterBarPersistence = ({ name, setValue, refreshData, isDefaultValue,
|
|
|
2207
2106
|
}
|
|
2208
2107
|
if (storedFilters && storedFilters !== "undefined") {
|
|
2209
2108
|
try {
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2109
|
+
const deserialized = reactComponents.storageSerializer.deserialize(storedFilters);
|
|
2110
|
+
if (typeof deserialized === "object" && deserialized !== null && !Array.isArray(deserialized)) {
|
|
2111
|
+
const record = Object.fromEntries(Object.entries(deserialized));
|
|
2112
|
+
return record.values ?? {};
|
|
2113
|
+
}
|
|
2114
|
+
return {};
|
|
2212
2115
|
}
|
|
2213
|
-
catch
|
|
2116
|
+
catch {
|
|
2214
2117
|
return {};
|
|
2215
2118
|
}
|
|
2216
2119
|
}
|
|
2217
2120
|
return {};
|
|
2218
|
-
}, [
|
|
2121
|
+
}, [storageKey, name, initialStoredFilters, lastName]);
|
|
2219
2122
|
const loadFromSearchURL = react.useCallback(() => {
|
|
2220
|
-
|
|
2221
|
-
const searchParamValue = searchParams[name];
|
|
2222
|
-
if (searchParamValue !== undefined && searchParamValue !== null) {
|
|
2123
|
+
if (searchValue !== undefined) {
|
|
2223
2124
|
try {
|
|
2224
|
-
|
|
2125
|
+
const decoded = decode(searchValue);
|
|
2126
|
+
if (typeof decoded === "object" && !Array.isArray(decoded)) {
|
|
2127
|
+
return Object.fromEntries(Object.entries(decoded));
|
|
2128
|
+
}
|
|
2129
|
+
return {};
|
|
2225
2130
|
}
|
|
2226
|
-
catch
|
|
2227
|
-
|
|
2131
|
+
catch {
|
|
2132
|
+
return {};
|
|
2228
2133
|
}
|
|
2229
|
-
return searchParams;
|
|
2230
2134
|
}
|
|
2231
2135
|
return null;
|
|
2232
|
-
}, [
|
|
2136
|
+
}, [searchValue, decode]);
|
|
2233
2137
|
const loadValues = react.useCallback((filterDefinitions) => {
|
|
2234
2138
|
if (inputLoadData) {
|
|
2235
2139
|
return inputLoadData();
|
|
@@ -2240,7 +2144,6 @@ const useFilterBarPersistence = ({ name, setValue, refreshData, isDefaultValue,
|
|
|
2240
2144
|
if (searchParams !== null && sharedUtils.objectKeys(searchParams).length > 0) {
|
|
2241
2145
|
const valuesFromUrl = getFilterValuesFromUrl(filterDefinitions, searchParams);
|
|
2242
2146
|
if (sharedUtils.objectKeys(valuesFromUrl).length > 0) {
|
|
2243
|
-
// if there are values from the URL, update ALL filters values based on the searchParams or their defaults
|
|
2244
2147
|
filterDefinitions.forEach(filter => {
|
|
2245
2148
|
const key = filter.filterKey;
|
|
2246
2149
|
if (key in valuesFromUrl) {
|
|
@@ -2300,19 +2203,9 @@ const useFilterBarPersistence = ({ name, setValue, refreshData, isDefaultValue,
|
|
|
2300
2203
|
initialFilterBarConfig.setters[`set${stringTs.capitalize(key)}`] = (callback) => setValue(key, callback);
|
|
2301
2204
|
});
|
|
2302
2205
|
const urlObject = getFilterValuesToUrl(initialFilterBarConfig.values, updatedFilterDefinitionsValues, false, isDefaultValue);
|
|
2303
|
-
|
|
2304
|
-
const result = {};
|
|
2305
|
-
if (initialFilterBarConfig.name) {
|
|
2306
|
-
result[initialFilterBarConfig.name] = encode(urlObject);
|
|
2307
|
-
needUpdate = !dequal.dequal(lastSearchUpdateRef.current, result[initialFilterBarConfig.name]);
|
|
2308
|
-
// Update the ref BEFORE updating the URL to prevent false external change detection
|
|
2309
|
-
lastSearchUpdateRef.current = result[initialFilterBarConfig.name];
|
|
2310
|
-
}
|
|
2311
|
-
if (needUpdate) {
|
|
2312
|
-
void updateSearch(result);
|
|
2313
|
-
}
|
|
2206
|
+
updateSearchParam(encode(urlObject));
|
|
2314
2207
|
return initialFilterBarConfig;
|
|
2315
|
-
}, [name, setValue, loadValues, getFilterValuesToUrl,
|
|
2208
|
+
}, [name, setValue, loadValues, getFilterValuesToUrl, updateSearchParam, encode, isDefaultValue]);
|
|
2316
2209
|
return react.useMemo(() => ({ loadData, saveData, getFilterValuesToUrl }), [loadData, saveData, getFilterValuesToUrl]);
|
|
2317
2210
|
};
|
|
2318
2211
|
|
package/index.esm.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
|
2
2
|
import { registerTranslations, useNamespaceTranslation } from '@trackunit/i18n-library-translation';
|
|
3
3
|
import { useMemo, useRef, useState, useEffect, useCallback, Fragment as Fragment$1 } from 'react';
|
|
4
4
|
import { Filter, FilterBody, RadioFilterItem, CheckBoxFilterItem, FilterHeader as FilterHeader$1, FilterFooter } from '@trackunit/react-filter-components';
|
|
5
|
-
import { Button, Icon, useList, List, Text, useTextSearch, Card, CardBody, useViewportBreakpoints, Popover, PopoverTrigger, Tooltip, Badge, PopoverContent, IconButton, MenuList, useCustomEncoding, useWatch } from '@trackunit/react-components';
|
|
5
|
+
import { Button, Icon, useList, List, Text, useTextSearch, Card, CardBody, useViewportBreakpoints, Popover, PopoverTrigger, Tooltip, Badge, PopoverContent, IconButton, MenuList, useCustomEncoding, useSearchParamSync, useStorageKey, storageSerializer, useWatch } from '@trackunit/react-components';
|
|
6
6
|
import { useAnalytics, useCurrentUser } from '@trackunit/react-core-hooks';
|
|
7
7
|
import { capitalize } from 'string-ts';
|
|
8
8
|
import { createEvent } from '@trackunit/iris-app-runtime-core-api';
|
|
@@ -15,7 +15,6 @@ import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
|
15
15
|
import { dequal } from 'dequal';
|
|
16
16
|
import { geoJsonPolygonSchema, geoJsonMultiPolygonSchema } from '@trackunit/geo-json-utils';
|
|
17
17
|
import { z } from 'zod';
|
|
18
|
-
import { useLocation, useSearch, useNavigate } from '@tanstack/react-router';
|
|
19
18
|
|
|
20
19
|
var defaultTranslations = {
|
|
21
20
|
"access.management.filter.operator.role.keyAdmin": "Key Admin",
|
|
@@ -2043,125 +2042,31 @@ const useFilterUrlSync = () => {
|
|
|
2043
2042
|
}), [getFilterValuesFromUrl, getFilterValuesToUrl]);
|
|
2044
2043
|
};
|
|
2045
2044
|
|
|
2046
|
-
/**
|
|
2047
|
-
* This hook provides functionality to:
|
|
2048
|
-
* - Get the length of the search parameters excluding a specific key
|
|
2049
|
-
*/
|
|
2050
|
-
const useSearchUtils = () => {
|
|
2051
|
-
const location = useLocation();
|
|
2052
|
-
const getSearchParamsLengthExcluding = useCallback((excludeKey, searchParams) => {
|
|
2053
|
-
return objectKeys(searchParams)
|
|
2054
|
-
.filter(key => key !== excludeKey)
|
|
2055
|
-
.reduce((totalLength, key) => {
|
|
2056
|
-
const keyLength = encodeURIComponent(String(key)).length;
|
|
2057
|
-
const value = searchParams[key] !== null ? encodeURIComponent(searchParams[key]?.toString() ?? "") : "";
|
|
2058
|
-
const valueLength = value.length;
|
|
2059
|
-
// Add 1 for '=' and 1 for '&' (except for the first param)
|
|
2060
|
-
return totalLength + keyLength + valueLength + (totalLength > 0 ? 2 : 1);
|
|
2061
|
-
}, 0);
|
|
2062
|
-
}, []);
|
|
2063
|
-
const getUrlLengthWithSearchParam = useCallback((key, value, prev) => {
|
|
2064
|
-
const searchParamsLength = getSearchParamsLengthExcluding(key, prev);
|
|
2065
|
-
const urlLength = location.href.length - (location.searchStr.length || 0) + (location.hash.length || 0) + searchParamsLength;
|
|
2066
|
-
// 1 === '&' and 1 === '='
|
|
2067
|
-
return urlLength + 1 + key.length + 1 + value.length;
|
|
2068
|
-
}, [getSearchParamsLengthExcluding, location]);
|
|
2069
|
-
return useMemo(() => ({ getSearchParamsLengthExcluding, getUrlLengthWithSearchParam }), [getSearchParamsLengthExcluding, getUrlLengthWithSearchParam]);
|
|
2070
|
-
};
|
|
2071
|
-
|
|
2072
|
-
const MAX_URL_LENGTH = 5000;
|
|
2073
|
-
/**
|
|
2074
|
-
* Get the persistence key for the filter bar.
|
|
2075
|
-
*
|
|
2076
|
-
* @param {string} name - The name of the filter bar.
|
|
2077
|
-
* @param {string} clientSideUserId - The client side user id.
|
|
2078
|
-
* @returns {string} The persistence key.
|
|
2079
|
-
*/
|
|
2080
|
-
const getPersistenceKey = (name, clientSideUserId) => `filter-${name}-${clientSideUserId}`;
|
|
2081
2045
|
/**
|
|
2082
2046
|
* Custom hook for managing the persistence of filter bar configurations.
|
|
2083
2047
|
*/
|
|
2084
2048
|
const useFilterBarPersistence = ({ name, setValue, refreshData, isDefaultValue, loadData: inputLoadData, saveData: inputSaveData, }) => {
|
|
2085
2049
|
const { clientSideUserId } = useCurrentUser();
|
|
2086
|
-
const search = useSearch({ strict: false, shouldThrow: false });
|
|
2087
|
-
const location = useLocation();
|
|
2088
|
-
const navigate = useNavigate();
|
|
2089
2050
|
const { encode, decode } = useCustomEncoding();
|
|
2090
|
-
const { getUrlLengthWithSearchParam } = useSearchUtils();
|
|
2091
|
-
const updateSearch = useCallback(async (searchParams) => {
|
|
2092
|
-
if (!inputLoadData && !inputSaveData && Boolean(search)) {
|
|
2093
|
-
// should check if the state has actually changed from what we last sent to the URL
|
|
2094
|
-
if (!searchParams[name] ||
|
|
2095
|
-
(typeof searchParams[name] === "string" &&
|
|
2096
|
-
!dequal(decode(searchParams[name]), typeof search[name] === "string" ? decode(search[name]) : search[name]))) {
|
|
2097
|
-
return requestAnimationFrame(async () => {
|
|
2098
|
-
const replace = search[name] === undefined || search[name] === null;
|
|
2099
|
-
await navigate({
|
|
2100
|
-
to: ".",
|
|
2101
|
-
search: (prev) => {
|
|
2102
|
-
if (getUrlLengthWithSearchParam(name, searchParams[name] || "", prev) <= MAX_URL_LENGTH) {
|
|
2103
|
-
return { ...prev, ...searchParams };
|
|
2104
|
-
}
|
|
2105
|
-
else {
|
|
2106
|
-
// eslint-disable-next-line no-console
|
|
2107
|
-
console.log(`URL too long, skipping sync of filters to the browsers URL, to avoid crashing the browser, limit is ${MAX_URL_LENGTH}, was: ${getUrlLengthWithSearchParam(name, searchParams[name] || "", prev)}`);
|
|
2108
|
-
const newSearchParams = { ...prev, [name]: undefined };
|
|
2109
|
-
return newSearchParams;
|
|
2110
|
-
}
|
|
2111
|
-
},
|
|
2112
|
-
hash: location.hash,
|
|
2113
|
-
replace,
|
|
2114
|
-
});
|
|
2115
|
-
});
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
return Promise.resolve();
|
|
2119
|
-
}, [navigate, name, search, inputLoadData, inputSaveData, decode, getUrlLengthWithSearchParam, location.hash]);
|
|
2120
|
-
const getFromLocalStorage = useCallback((persistenceKey) => {
|
|
2121
|
-
return localStorage.getItem(getPersistenceKey(persistenceKey, clientSideUserId));
|
|
2122
|
-
}, [clientSideUserId]);
|
|
2123
|
-
const lastName = useRef(name);
|
|
2124
|
-
const [initialStoredFilters] = useState(() => (!inputLoadData && getFromLocalStorage(name)) || "{}");
|
|
2125
|
-
const { getFilterValuesFromUrl, getFilterValuesToUrl } = useFilterUrlSync();
|
|
2126
|
-
const lastSavedStateRef = useRef(undefined);
|
|
2127
|
-
const lastSearchUpdateRef = useRef(undefined);
|
|
2128
2051
|
const refreshDataRef = useRef(refreshData);
|
|
2129
2052
|
useEffect(() => {
|
|
2130
2053
|
refreshDataRef.current = refreshData;
|
|
2131
2054
|
}, [refreshData]);
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
}
|
|
2148
|
-
catch (_) {
|
|
2149
|
-
// Invalid compressed data, treat as undefined
|
|
2150
|
-
lastSearchUpdateRefParsed = undefined;
|
|
2151
|
-
}
|
|
2152
|
-
// H4sIADGRuGgAA3WTy07eQAxGXwVlXUtje27uruqWd0C2x1NaAa2AdoN49xq6-LOgUpQo0onny7H9ctzHsy591q8_73_dxXM8xNPT8fnl-KN3v-P4fHy5vj4-HQ96__5yd3e8fjqeQh_9Nt9v48fVfayr9f3b1YrHBH3f3GDBaj4Zui-HuvsADeO8KeFGrmYrj8hCbzTZrF0coRMRVEYHGcKAsW1Iw8qiF3ox06zbASth1t4GVsqCUvIxqO9R-EJ7w2aDJnjHCbV6JtmlgOSHQzB4DLvQtiwiRGB3aUk3h0lLgdyjFiyD8USPhd55BYzakkbaSbeA2jfmFaV7v9BMErKLguWpUNdQEKwTsqqXNETL_eRkbqojy1K0pCtVkK4LJM2VPtk6lQtdcIpSs2RGno9aQG0LSJrmvgrKHCeDFWWQN0iNNZMYgczFQNtH0VbqKifaq7uVBFdvBIgRIBoClcLXQFlC7VR7E7l2PdHKnH36kN5rJFY96Z0m3mhTKv-hG8cscytgU0vLnAOlTNAWdd_TR11y8m3dsxsGyJp_2XOg5hyZOxaP2Qu6nX137DbnW5L0_S-3p9CPk2hC09sG15SXgrKrHgQlpYe6ZKl9oScNb74IOmMmmbkSYmmHdYWZ7z3baWKpiWq0nA6u2fkEYFoOS-_OtibuIqfdaa0oDytgnB2saSC3YTSI0UKIc9nofWJf_wIRCrRd6AMAAA
|
|
2153
|
-
// Check if this is an external URL change (not from our own updates)
|
|
2154
|
-
// Only trigger refreshData if there's a meaningful change in the URL value
|
|
2155
|
-
if (search?.[name] !== undefined && !dequal(lastSearchUpdateRefParsed, currentSearchValueParsed)) {
|
|
2156
|
-
// This is an external URL change with actual data, trigger refreshData
|
|
2157
|
-
void requestAnimationFrame(() => {
|
|
2158
|
-
if (refreshDataRef.current) {
|
|
2159
|
-
refreshDataRef.current();
|
|
2160
|
-
}
|
|
2161
|
-
});
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
}, [search, name, inputLoadData, inputSaveData, decode]);
|
|
2055
|
+
const { searchValue, updateSearchParam } = useSearchParamSync({
|
|
2056
|
+
key: name,
|
|
2057
|
+
enabled: !inputLoadData && !inputSaveData,
|
|
2058
|
+
onExternalChange: () => {
|
|
2059
|
+
refreshDataRef.current?.();
|
|
2060
|
+
},
|
|
2061
|
+
});
|
|
2062
|
+
const storageKey = useStorageKey(`filter-${name}`, clientSideUserId);
|
|
2063
|
+
const getFromLocalStorage = useCallback(() => {
|
|
2064
|
+
return localStorage.getItem(storageKey);
|
|
2065
|
+
}, [storageKey]);
|
|
2066
|
+
const lastName = useRef(name);
|
|
2067
|
+
const [initialStoredFilters] = useState(() => (!inputLoadData && getFromLocalStorage()) || "{}");
|
|
2068
|
+
const { getFilterValuesFromUrl, getFilterValuesToUrl } = useFilterUrlSync();
|
|
2069
|
+
const lastSavedStateRef = useRef(undefined);
|
|
2165
2070
|
const saveData = useCallback((filterBarConfig, filterBarDefinitions) => {
|
|
2166
2071
|
const newValues = Object.assign({}, lastSavedStateRef.current || {});
|
|
2167
2072
|
if (filterBarConfig.values) {
|
|
@@ -2182,22 +2087,16 @@ const useFilterBarPersistence = ({ name, setValue, refreshData, isDefaultValue,
|
|
|
2182
2087
|
inputSaveData(newValues);
|
|
2183
2088
|
}
|
|
2184
2089
|
else {
|
|
2185
|
-
localStorage.setItem(
|
|
2090
|
+
localStorage.setItem(storageKey, storageSerializer.serialize(toPersist));
|
|
2186
2091
|
const urlObject = getFilterValuesToUrl(newValues, objectValues(filterBarDefinitions), false, isDefaultValue);
|
|
2187
|
-
|
|
2188
|
-
if (filterBarConfig.name) {
|
|
2189
|
-
result[filterBarConfig.name] = encode(urlObject);
|
|
2190
|
-
// Update the ref BEFORE updating the URL to prevent false external change detection
|
|
2191
|
-
lastSearchUpdateRef.current = result[filterBarConfig.name];
|
|
2192
|
-
}
|
|
2193
|
-
updateSearch(result);
|
|
2092
|
+
updateSearchParam(encode(urlObject));
|
|
2194
2093
|
}
|
|
2195
2094
|
}
|
|
2196
|
-
}, [
|
|
2095
|
+
}, [storageKey, inputSaveData, getFilterValuesToUrl, updateSearchParam, encode, isDefaultValue]);
|
|
2197
2096
|
const loadFromLocalStorage = useCallback(() => {
|
|
2198
2097
|
let storedFilters = null;
|
|
2199
2098
|
if (lastName.current !== name) {
|
|
2200
|
-
storedFilters = localStorage.getItem(
|
|
2099
|
+
storedFilters = localStorage.getItem(storageKey) || "{}";
|
|
2201
2100
|
lastName.current = name;
|
|
2202
2101
|
}
|
|
2203
2102
|
else {
|
|
@@ -2205,29 +2104,34 @@ const useFilterBarPersistence = ({ name, setValue, refreshData, isDefaultValue,
|
|
|
2205
2104
|
}
|
|
2206
2105
|
if (storedFilters && storedFilters !== "undefined") {
|
|
2207
2106
|
try {
|
|
2208
|
-
const
|
|
2209
|
-
|
|
2107
|
+
const deserialized = storageSerializer.deserialize(storedFilters);
|
|
2108
|
+
if (typeof deserialized === "object" && deserialized !== null && !Array.isArray(deserialized)) {
|
|
2109
|
+
const record = Object.fromEntries(Object.entries(deserialized));
|
|
2110
|
+
return record.values ?? {};
|
|
2111
|
+
}
|
|
2112
|
+
return {};
|
|
2210
2113
|
}
|
|
2211
|
-
catch
|
|
2114
|
+
catch {
|
|
2212
2115
|
return {};
|
|
2213
2116
|
}
|
|
2214
2117
|
}
|
|
2215
2118
|
return {};
|
|
2216
|
-
}, [
|
|
2119
|
+
}, [storageKey, name, initialStoredFilters, lastName]);
|
|
2217
2120
|
const loadFromSearchURL = useCallback(() => {
|
|
2218
|
-
|
|
2219
|
-
const searchParamValue = searchParams[name];
|
|
2220
|
-
if (searchParamValue !== undefined && searchParamValue !== null) {
|
|
2121
|
+
if (searchValue !== undefined) {
|
|
2221
2122
|
try {
|
|
2222
|
-
|
|
2123
|
+
const decoded = decode(searchValue);
|
|
2124
|
+
if (typeof decoded === "object" && !Array.isArray(decoded)) {
|
|
2125
|
+
return Object.fromEntries(Object.entries(decoded));
|
|
2126
|
+
}
|
|
2127
|
+
return {};
|
|
2223
2128
|
}
|
|
2224
|
-
catch
|
|
2225
|
-
|
|
2129
|
+
catch {
|
|
2130
|
+
return {};
|
|
2226
2131
|
}
|
|
2227
|
-
return searchParams;
|
|
2228
2132
|
}
|
|
2229
2133
|
return null;
|
|
2230
|
-
}, [
|
|
2134
|
+
}, [searchValue, decode]);
|
|
2231
2135
|
const loadValues = useCallback((filterDefinitions) => {
|
|
2232
2136
|
if (inputLoadData) {
|
|
2233
2137
|
return inputLoadData();
|
|
@@ -2238,7 +2142,6 @@ const useFilterBarPersistence = ({ name, setValue, refreshData, isDefaultValue,
|
|
|
2238
2142
|
if (searchParams !== null && objectKeys(searchParams).length > 0) {
|
|
2239
2143
|
const valuesFromUrl = getFilterValuesFromUrl(filterDefinitions, searchParams);
|
|
2240
2144
|
if (objectKeys(valuesFromUrl).length > 0) {
|
|
2241
|
-
// if there are values from the URL, update ALL filters values based on the searchParams or their defaults
|
|
2242
2145
|
filterDefinitions.forEach(filter => {
|
|
2243
2146
|
const key = filter.filterKey;
|
|
2244
2147
|
if (key in valuesFromUrl) {
|
|
@@ -2298,19 +2201,9 @@ const useFilterBarPersistence = ({ name, setValue, refreshData, isDefaultValue,
|
|
|
2298
2201
|
initialFilterBarConfig.setters[`set${capitalize(key)}`] = (callback) => setValue(key, callback);
|
|
2299
2202
|
});
|
|
2300
2203
|
const urlObject = getFilterValuesToUrl(initialFilterBarConfig.values, updatedFilterDefinitionsValues, false, isDefaultValue);
|
|
2301
|
-
|
|
2302
|
-
const result = {};
|
|
2303
|
-
if (initialFilterBarConfig.name) {
|
|
2304
|
-
result[initialFilterBarConfig.name] = encode(urlObject);
|
|
2305
|
-
needUpdate = !dequal(lastSearchUpdateRef.current, result[initialFilterBarConfig.name]);
|
|
2306
|
-
// Update the ref BEFORE updating the URL to prevent false external change detection
|
|
2307
|
-
lastSearchUpdateRef.current = result[initialFilterBarConfig.name];
|
|
2308
|
-
}
|
|
2309
|
-
if (needUpdate) {
|
|
2310
|
-
void updateSearch(result);
|
|
2311
|
-
}
|
|
2204
|
+
updateSearchParam(encode(urlObject));
|
|
2312
2205
|
return initialFilterBarConfig;
|
|
2313
|
-
}, [name, setValue, loadValues, getFilterValuesToUrl,
|
|
2206
|
+
}, [name, setValue, loadValues, getFilterValuesToUrl, updateSearchParam, encode, isDefaultValue]);
|
|
2314
2207
|
return useMemo(() => ({ loadData, saveData, getFilterValuesToUrl }), [loadData, saveData, getFilterValuesToUrl]);
|
|
2315
2208
|
};
|
|
2316
2209
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/filters-filter-bar",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.7",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -10,18 +10,18 @@
|
|
|
10
10
|
"dequal": "^2.0.3",
|
|
11
11
|
"tailwind-merge": "^2.0.0",
|
|
12
12
|
"string-ts": "^2.0.0",
|
|
13
|
-
"zod": "^3.
|
|
13
|
+
"zod": "^3.25.76",
|
|
14
14
|
"@trackunit/iris-app-api": "1.17.2",
|
|
15
|
-
"@trackunit/react-core-hooks": "1.15.
|
|
16
|
-
"@trackunit/react-filter-components": "1.20.
|
|
17
|
-
"@trackunit/react-date-and-time-components": "1.23.
|
|
15
|
+
"@trackunit/react-core-hooks": "1.15.12",
|
|
16
|
+
"@trackunit/react-filter-components": "1.20.7",
|
|
17
|
+
"@trackunit/react-date-and-time-components": "1.23.7",
|
|
18
18
|
"@trackunit/shared-utils": "1.13.97",
|
|
19
|
-
"@trackunit/react-form-components": "1.21.
|
|
20
|
-
"@trackunit/iris-app-runtime-core-api": "1.14.
|
|
19
|
+
"@trackunit/react-form-components": "1.21.7",
|
|
20
|
+
"@trackunit/iris-app-runtime-core-api": "1.14.11",
|
|
21
21
|
"@trackunit/geo-json-utils": "1.12.8",
|
|
22
|
-
"@trackunit/i18n-library-translation": "1.18.
|
|
22
|
+
"@trackunit/i18n-library-translation": "1.18.3",
|
|
23
23
|
"@trackunit/css-class-variance-utilities": "1.11.97",
|
|
24
|
-
"@trackunit/react-components": "1.21.
|
|
24
|
+
"@trackunit/react-components": "1.21.18"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"@apollo/client": "^3.13.8",
|
|
@@ -9,13 +9,9 @@ export interface FilterBarPersistenceProps<TFilterBarDefinition extends FilterBa
|
|
|
9
9
|
*/
|
|
10
10
|
isDefaultValue: (key: string, value: FilterValueType) => boolean;
|
|
11
11
|
}
|
|
12
|
-
export
|
|
12
|
+
export { MAX_URL_LENGTH } from "@trackunit/react-components";
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* @param {string} name - The name of the filter bar.
|
|
17
|
-
* @param {string} clientSideUserId - The client side user id.
|
|
18
|
-
* @returns {string} The persistence key.
|
|
14
|
+
* @deprecated Use `useStorageKey` from `@trackunit/react-components` with key `filter-${name}` instead.
|
|
19
15
|
*/
|
|
20
16
|
export declare const getPersistenceKey: (name: string, clientSideUserId?: string) => string;
|
|
21
17
|
/**
|
|
@@ -72,4 +68,3 @@ type UseFilterBarPersistenceReturn<TFilterBarDefinition extends FilterBarDefinit
|
|
|
72
68
|
* Custom hook for managing the persistence of filter bar configurations.
|
|
73
69
|
*/
|
|
74
70
|
export declare const useFilterBarPersistence: <TFilterBarDefinition extends FilterBarDefinition>({ name, setValue, refreshData, isDefaultValue, loadData: inputLoadData, saveData: inputSaveData, }: FilterBarPersistenceProps<TFilterBarDefinition>) => UseFilterBarPersistenceReturn<TFilterBarDefinition>;
|
|
75
|
-
export {};
|