@trackunit/filters-filter-bar 1.3.210 → 1.3.211
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 +290 -253
- package/index.esm.js +293 -258
- package/package.json +13 -13
- package/src/lib/FilterBar.d.ts +1 -1
- package/src/lib/components/AppliedFiltersRenderer.d.ts +8 -0
- package/src/lib/components/FilterTooltips/MultipleFilterTooltipLabel.d.ts +2 -2
- package/src/lib/components/FilterTooltips/SingleFilterTooltipLabel.d.ts +2 -2
- package/src/lib/components/index.d.ts +2 -0
- package/src/lib/hooks/mockFilterBar.d.ts +3 -42
- package/src/lib/types/FilterTypes.d.ts +9 -2
package/index.esm.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { jsx,
|
|
1
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import { registerTranslations, useNamespaceTranslation } from '@trackunit/i18n-library-translation';
|
|
3
|
-
import {
|
|
3
|
+
import { Filter, FilterBody, RadioFilterItem, CheckBoxFilterItem, FilterHeader as FilterHeader$1, FilterFooter } from '@trackunit/react-filter-components';
|
|
4
|
+
import { Button, VirtualizedList, Text, Card, CardBody, useViewportBreakpoints, Popover, PopoverTrigger, Tooltip, Icon, PopoverContent, IconButton, MenuList } from '@trackunit/react-components';
|
|
4
5
|
import { useAnalytics, useTextSearch, useCurrentUser } from '@trackunit/react-core-hooks';
|
|
5
|
-
import { FilterBody, RadioFilterItem, CheckBoxFilterItem, FilterHeader as FilterHeader$1, FilterFooter, Filter } from '@trackunit/react-filter-components';
|
|
6
6
|
import { useRef, useMemo, useState, useEffect, useCallback, Fragment as Fragment$1 } from 'react';
|
|
7
7
|
import { capitalize } from 'string-ts';
|
|
8
8
|
import { createEvent } from '@trackunit/react-core-contexts-api';
|
|
@@ -318,6 +318,137 @@ const setupLibraryTranslations = () => {
|
|
|
318
318
|
registerTranslations(translations);
|
|
319
319
|
};
|
|
320
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Returns the two first values, appends counter if more.
|
|
323
|
+
*
|
|
324
|
+
* @param {string[]} [input] Array of values to reduce
|
|
325
|
+
* @returns {*} {string}
|
|
326
|
+
*/
|
|
327
|
+
const reduceFilterText = (input) => {
|
|
328
|
+
if (!Array.isArray(input)) {
|
|
329
|
+
return input;
|
|
330
|
+
}
|
|
331
|
+
// Only first two elements
|
|
332
|
+
const firstTwo = input.slice(0, 2);
|
|
333
|
+
const remainder = input.slice(2, input.length).length;
|
|
334
|
+
const firstTwoJoined = firstTwo.join(", ");
|
|
335
|
+
const remainderStr = remainder > 0 ? `, +${remainder}` : "";
|
|
336
|
+
return firstTwoJoined + remainderStr;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Filter is a React component that renders a filter element based on the provided filter definition and state.
|
|
341
|
+
*
|
|
342
|
+
* @returns {ReactElement} - Returns the Filter component.
|
|
343
|
+
*/
|
|
344
|
+
const FilterComponent = ({ filter, filterBarActions, filterState, readOnly = false, visualStyle = "button", className, asIcon, size, }) => {
|
|
345
|
+
const values = filterBarActions.getValuesByKey(filter.filterKey);
|
|
346
|
+
const valuesLength = values && Array.isArray(values) ? values.length : undefined;
|
|
347
|
+
const getFilterText = () => {
|
|
348
|
+
if (!values) {
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
if (filter.valueAsText) {
|
|
352
|
+
return reduceFilterText(filter.valueAsText(values));
|
|
353
|
+
}
|
|
354
|
+
else if (filter.type === "valueNameArray") {
|
|
355
|
+
return reduceFilterText(values.map(value => value.name));
|
|
356
|
+
}
|
|
357
|
+
else if (filter.type === "valueName") {
|
|
358
|
+
return values.name;
|
|
359
|
+
}
|
|
360
|
+
return values.toString();
|
|
361
|
+
};
|
|
362
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
363
|
+
const setValue = (callback) => {
|
|
364
|
+
const newValue = callback(filterBarActions.getValuesByKey(filter.filterKey));
|
|
365
|
+
if (filter.type === "string") {
|
|
366
|
+
filterBarActions.setStringValue(filter.filterKey, newValue);
|
|
367
|
+
}
|
|
368
|
+
else if (filter.type === "stringArray") {
|
|
369
|
+
filterBarActions.setArrayValue(filter.filterKey, newValue);
|
|
370
|
+
}
|
|
371
|
+
else if (filter.type === "dateRange") {
|
|
372
|
+
filterBarActions.setDateRange(filter.filterKey, newValue);
|
|
373
|
+
}
|
|
374
|
+
else if (filter.type === "area") {
|
|
375
|
+
filterBarActions.setArea(newValue);
|
|
376
|
+
}
|
|
377
|
+
else if (filter.type === "valueNameArray") {
|
|
378
|
+
filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
|
|
379
|
+
}
|
|
380
|
+
else if (filter.type === "valueName") {
|
|
381
|
+
filterBarActions.setObjectValue(filter.filterKey, newValue);
|
|
382
|
+
}
|
|
383
|
+
else if (filter.type === "minMax") {
|
|
384
|
+
filterBarActions.setMinMaxValue(filter.filterKey, newValue);
|
|
385
|
+
}
|
|
386
|
+
else if (filter.type === "boolean") {
|
|
387
|
+
filterBarActions.setBooleanValue(filter.filterKey, newValue);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
filterBarActions.setNumberValue(filter.filterKey, newValue);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
const text = getFilterText();
|
|
394
|
+
const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
|
|
395
|
+
const showDirectly = filter.showDirectly || false;
|
|
396
|
+
return showDirectly ? (jsx(Fragment, { children: filter.component({
|
|
397
|
+
filterDefinition: filter,
|
|
398
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
399
|
+
value: values,
|
|
400
|
+
setValue,
|
|
401
|
+
filterBarActions,
|
|
402
|
+
filterState,
|
|
403
|
+
}) })) : (jsx(Filter, { activeLabel: activeFilterText, activeOptionsCount: valuesLength, asIcon: asIcon, className: className, dataTestId: `${filter.filterKey}-filter-button`, isActive: filterBarActions.appliedFilterKeys().includes(filter.filterKey), popoverProps: { placement: visualStyle === "list-item" ? "right-start" : "bottom-start" }, readOnly: readOnly, size: size, title: filter.title, visualStyle: visualStyle, withStickyHeader: true, children: filter.component({
|
|
404
|
+
filterDefinition: filter,
|
|
405
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
406
|
+
value: values,
|
|
407
|
+
setValue,
|
|
408
|
+
filterBarActions,
|
|
409
|
+
filterState,
|
|
410
|
+
}) }));
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* FiltersRenderer renders an array of Filter components from filter definitions
|
|
415
|
+
* It ignores hidden filters.
|
|
416
|
+
*
|
|
417
|
+
* Note: This component does not provide any styling or layout - use a wrapper for proper list styling.
|
|
418
|
+
*
|
|
419
|
+
* @param {object} props - The component props
|
|
420
|
+
* @param {FilterDefinition[]} props.filters - Array of filter definitions to render
|
|
421
|
+
* @param {FilterBarConfig & FilterMapActions & FilterMapGetter} props.filterBarConfig - The filter bar configuration
|
|
422
|
+
* @param {ComponentProps<typeof FilterComponent>["visualStyle"]} [props.visualStyle] - The visual style of the filters
|
|
423
|
+
* @returns {ReactElement[] | null} - Returns an array of Filter components or null
|
|
424
|
+
*/
|
|
425
|
+
const FiltersRenderer = ({ filters, filterBarConfig, visualStyle, }) => {
|
|
426
|
+
return filters.length === 0
|
|
427
|
+
? null
|
|
428
|
+
: filters
|
|
429
|
+
.filter(filter => !filter.isHidden)
|
|
430
|
+
.map(filter => (jsx(FilterComponent, { filter: filter, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters }, visualStyle: visualStyle }, `filter-${filter.filterKey}`)));
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* ResetFiltersButton is a React component that provides a button for resetting filters.
|
|
435
|
+
*
|
|
436
|
+
* @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
|
|
437
|
+
*/
|
|
438
|
+
const ResetFiltersButton = ({ resetFiltersToInitialState, dataTestId, className, }) => {
|
|
439
|
+
const [t] = useTranslation();
|
|
440
|
+
return (jsxs(Button, { className: className, dataTestId: dataTestId ?? "reset-filters-button", onClick: () => {
|
|
441
|
+
resetFiltersToInitialState();
|
|
442
|
+
}, size: "small", variant: "ghost", children: [t("filtersBar.resetFilters"), jsx("span", { className: "sr-only", children: "Resets all applied filters" })] }));
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
*
|
|
447
|
+
*/
|
|
448
|
+
const AppliedFiltersRenderer = ({ appliedFilters, filterBarConfig, }) => {
|
|
449
|
+
return (jsxs(Fragment, { children: [appliedFilters.filter(filter => !filter.showDirectly).length > 0 ? (jsx("div", { className: "h-4 w-[1px] bg-slate-300", "data-testid": "applied-filters-buttons" })) : null, jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: appliedFilters }), filterBarConfig.appliedFilterKeys().length > 0 ? (jsx(ResetFiltersButton, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] }));
|
|
450
|
+
};
|
|
451
|
+
|
|
321
452
|
/**
|
|
322
453
|
* Events — each with a name, and eventType.
|
|
323
454
|
* Adding a Description is encouraged.
|
|
@@ -662,98 +793,6 @@ const FilterButtonTooltipLabel = ({ filterBarConfig, }) => {
|
|
|
662
793
|
}
|
|
663
794
|
};
|
|
664
795
|
|
|
665
|
-
/**
|
|
666
|
-
* Returns the two first values, appends counter if more.
|
|
667
|
-
*
|
|
668
|
-
* @param {string[]} [input] Array of values to reduce
|
|
669
|
-
* @returns {*} {string}
|
|
670
|
-
*/
|
|
671
|
-
const reduceFilterText = (input) => {
|
|
672
|
-
if (!Array.isArray(input)) {
|
|
673
|
-
return input;
|
|
674
|
-
}
|
|
675
|
-
// Only first two elements
|
|
676
|
-
const firstTwo = input.slice(0, 2);
|
|
677
|
-
const remainder = input.slice(2, input.length).length;
|
|
678
|
-
const firstTwoJoined = firstTwo.join(", ");
|
|
679
|
-
const remainderStr = remainder > 0 ? `, +${remainder}` : "";
|
|
680
|
-
return firstTwoJoined + remainderStr;
|
|
681
|
-
};
|
|
682
|
-
|
|
683
|
-
/**
|
|
684
|
-
* Filter is a React component that renders a filter element based on the provided filter definition and state.
|
|
685
|
-
*
|
|
686
|
-
* @returns {ReactElement} - Returns the Filter component.
|
|
687
|
-
*/
|
|
688
|
-
const FilterComponent = ({ filter, filterBarActions, filterState, readOnly = false, visualStyle = "button", className, asIcon, size, }) => {
|
|
689
|
-
const values = filterBarActions.getValuesByKey(filter.filterKey);
|
|
690
|
-
const valuesLength = values && Array.isArray(values) ? values.length : undefined;
|
|
691
|
-
const getFilterText = () => {
|
|
692
|
-
if (!values) {
|
|
693
|
-
return undefined;
|
|
694
|
-
}
|
|
695
|
-
if (filter.valueAsText) {
|
|
696
|
-
return reduceFilterText(filter.valueAsText(values));
|
|
697
|
-
}
|
|
698
|
-
else if (filter.type === "valueNameArray") {
|
|
699
|
-
return reduceFilterText(values.map(value => value.name));
|
|
700
|
-
}
|
|
701
|
-
else if (filter.type === "valueName") {
|
|
702
|
-
return values.name;
|
|
703
|
-
}
|
|
704
|
-
return values.toString();
|
|
705
|
-
};
|
|
706
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
707
|
-
const setValue = (callback) => {
|
|
708
|
-
const newValue = callback(filterBarActions.getValuesByKey(filter.filterKey));
|
|
709
|
-
if (filter.type === "string") {
|
|
710
|
-
filterBarActions.setStringValue(filter.filterKey, newValue);
|
|
711
|
-
}
|
|
712
|
-
else if (filter.type === "stringArray") {
|
|
713
|
-
filterBarActions.setArrayValue(filter.filterKey, newValue);
|
|
714
|
-
}
|
|
715
|
-
else if (filter.type === "dateRange") {
|
|
716
|
-
filterBarActions.setDateRange(filter.filterKey, newValue);
|
|
717
|
-
}
|
|
718
|
-
else if (filter.type === "area") {
|
|
719
|
-
filterBarActions.setArea(newValue);
|
|
720
|
-
}
|
|
721
|
-
else if (filter.type === "valueNameArray") {
|
|
722
|
-
filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
|
|
723
|
-
}
|
|
724
|
-
else if (filter.type === "valueName") {
|
|
725
|
-
filterBarActions.setObjectValue(filter.filterKey, newValue);
|
|
726
|
-
}
|
|
727
|
-
else if (filter.type === "minMax") {
|
|
728
|
-
filterBarActions.setMinMaxValue(filter.filterKey, newValue);
|
|
729
|
-
}
|
|
730
|
-
else if (filter.type === "boolean") {
|
|
731
|
-
filterBarActions.setBooleanValue(filter.filterKey, newValue);
|
|
732
|
-
}
|
|
733
|
-
else {
|
|
734
|
-
filterBarActions.setNumberValue(filter.filterKey, newValue);
|
|
735
|
-
}
|
|
736
|
-
};
|
|
737
|
-
const text = getFilterText();
|
|
738
|
-
const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
|
|
739
|
-
const showDirectly = filter.showDirectly || false;
|
|
740
|
-
return showDirectly ? (jsx(Fragment, { children: filter.component({
|
|
741
|
-
filterDefinition: filter,
|
|
742
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
743
|
-
value: values,
|
|
744
|
-
setValue,
|
|
745
|
-
filterBarActions,
|
|
746
|
-
filterState,
|
|
747
|
-
}) })) : (jsx(Filter, { activeLabel: activeFilterText, activeOptionsCount: valuesLength, asIcon: asIcon, className: className, dataTestId: `${filter.filterKey}-filter-button`, isActive: filterBarActions.appliedFilterKeys().includes(filter.filterKey), popoverProps: { placement: visualStyle === "list-item" ? "right-start" : "bottom-start" }, readOnly: readOnly, size: size, title: filter.title, visualStyle: visualStyle, withStickyHeader: true, children: filter.component({
|
|
748
|
-
filterDefinition: filter,
|
|
749
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
750
|
-
value: values,
|
|
751
|
-
setValue,
|
|
752
|
-
filterBarActions,
|
|
753
|
-
filterState,
|
|
754
|
-
}) }));
|
|
755
|
-
};
|
|
756
|
-
|
|
757
796
|
/**
|
|
758
797
|
* Custom React hook for grouping and filtering a list of filters.
|
|
759
798
|
*
|
|
@@ -835,26 +874,6 @@ const useFiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters =
|
|
|
835
874
|
]);
|
|
836
875
|
};
|
|
837
876
|
|
|
838
|
-
/**
|
|
839
|
-
* FiltersRenderer renders an array of Filter components from filter definitions
|
|
840
|
-
* It ignores hidden filters.
|
|
841
|
-
*
|
|
842
|
-
* Note: This component does not provide any styling or layout - use a wrapper for proper list styling.
|
|
843
|
-
*
|
|
844
|
-
* @param {object} props - The component props
|
|
845
|
-
* @param {FilterDefinition[]} props.filters - Array of filter definitions to render
|
|
846
|
-
* @param {FilterBarConfig & FilterMapActions & FilterMapGetter} props.filterBarConfig - The filter bar configuration
|
|
847
|
-
* @param {ComponentProps<typeof FilterComponent>["visualStyle"]} [props.visualStyle] - The visual style of the filters
|
|
848
|
-
* @returns {ReactElement[] | null} - Returns an array of Filter components or null
|
|
849
|
-
*/
|
|
850
|
-
const FiltersRenderer = ({ filters, filterBarConfig, visualStyle, }) => {
|
|
851
|
-
return filters.length === 0
|
|
852
|
-
? null
|
|
853
|
-
: filters
|
|
854
|
-
.filter(filter => !filter.isHidden)
|
|
855
|
-
.map(filter => (jsx(FilterComponent, { filter: filter, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters }, visualStyle: visualStyle }, `filter-${filter.filterKey}`)));
|
|
856
|
-
};
|
|
857
|
-
|
|
858
877
|
/**
|
|
859
878
|
* FiltersList is a React component that displays a list of filters within a filter bar.
|
|
860
879
|
*
|
|
@@ -867,18 +886,6 @@ const GroupedFiltersList = ({ filterBarConfig, filtersGrouped, className, dataTe
|
|
|
867
886
|
}) }));
|
|
868
887
|
};
|
|
869
888
|
|
|
870
|
-
/**
|
|
871
|
-
* ResetFiltersButton is a React component that provides a button for resetting filters.
|
|
872
|
-
*
|
|
873
|
-
* @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
|
|
874
|
-
*/
|
|
875
|
-
const ResetFiltersButton = ({ resetFiltersToInitialState, dataTestId, className, }) => {
|
|
876
|
-
const [t] = useTranslation();
|
|
877
|
-
return (jsxs(Button, { className: className, dataTestId: dataTestId ?? "reset-filters-button", onClick: () => {
|
|
878
|
-
resetFiltersToInitialState();
|
|
879
|
-
}, size: "small", variant: "ghost", children: [t("filtersBar.resetFilters"), jsx("span", { className: "sr-only", children: "Resets all applied filters" })] }));
|
|
880
|
-
};
|
|
881
|
-
|
|
882
889
|
/**
|
|
883
890
|
*
|
|
884
891
|
*/
|
|
@@ -986,7 +993,7 @@ const MultipleFilterTooltipLabel = ({ filterBarConfig, filterKeys, filters, }) =
|
|
|
986
993
|
}, {});
|
|
987
994
|
switch (appliedFilterKeys.length) {
|
|
988
995
|
case 0:
|
|
989
|
-
return t("filtersBar.appliedFiltersTooltip.none");
|
|
996
|
+
return jsx("div", { className: "text-xs font-medium", children: t("filtersBar.appliedFiltersTooltip.none") });
|
|
990
997
|
case 1:
|
|
991
998
|
return (jsx(SingleFilterTooltipLabel, { filter: filtersMap[appliedFilterKeys[0]], filterBarConfig: filterBarConfig, filterKey: appliedFilterKeys[0] }));
|
|
992
999
|
default:
|
|
@@ -1018,7 +1025,7 @@ const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [],
|
|
|
1018
1025
|
}
|
|
1019
1026
|
}, placement: "bottom-start", children: modalState => {
|
|
1020
1027
|
return (jsxs(Fragment, { children: [jsx(PopoverTrigger, { children: jsx("div", { "data-testid": "starred-filters-menu-trigger", id: "starred-filters-menu-trigger", children: jsx(Tooltip, { disabled: !compact || modalState.isOpen, label: jsx(MultipleFilterTooltipLabel, { filterBarConfig: filterBarConfig, filters: appliedFilters }), children: jsx(Button, { prefix: jsx(Icon, { ariaHidden: true, color: filterBarConfig.appliedFilterKeys().length > 0 ? "primary" : undefined, name: "Filter", size: "small" }), size: "small", suffix: compact && showAppliedFiltersCount && filterBarConfig.appliedFilterKeys().length > 0 && isSm ? (jsxs("div", { children: [jsxs("span", { "aria-hidden": true, children: ["(", filterBarConfig.appliedFilterKeys().length, ")"] }), jsxs("span", { className: "sr-only", children: [filterBarConfig.appliedFilterKeys().length, " filters applied"] })] })) : undefined, variant: "secondary", ...buttonProps, children: title !== "" ? (jsx("span", { className: "hidden sm:block", children: title ?? t("filtersBar.filtersHeading") })) : null }) }) }) }), jsx(PopoverContent, { cellPadding: 100, children: jsx(FiltersMenuContent, { appliedCustomFields: appliedCustomFields, filterBarConfig: filterBarConfig, filterBarDefinitionCount: filterBarDefinitionCount, filtersToShowGrouped: filtersToShowGrouped, hasCustomFields: hasCustomFields, removeCustomFieldsGroup: removeCustomFieldsGroup, searchResultsGrouped: searchResultsGrouped, searchText: searchText, setSearchText: setSearchText, setShowCustomFilters: setShowCustomFilters, showCustomFilters: showCustomFilters }) })] }));
|
|
1021
|
-
} }), showDirectlyFilters.length > 0 ? (jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: showDirectlyFilters })) : null, !compact ? (
|
|
1028
|
+
} }), showDirectlyFilters.length > 0 ? (jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: showDirectlyFilters })) : null, !compact ? jsx(AppliedFiltersRenderer, { appliedFilters: appliedFilters, filterBarConfig: filterBarConfig }) : null] }));
|
|
1022
1029
|
};
|
|
1023
1030
|
|
|
1024
1031
|
/**
|
|
@@ -1278,7 +1285,7 @@ const HierarchicalCheckboxFilter = ({ filterDefinition, filterBarActions, option
|
|
|
1278
1285
|
/**
|
|
1279
1286
|
* The FilterBar component serves as a wrapper for managing filters.
|
|
1280
1287
|
*/
|
|
1281
|
-
const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact = true,
|
|
1288
|
+
const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact = true, allowShowFiltersDirectly = true, title, }) => {
|
|
1282
1289
|
return (jsx(FiltersMenu, { allowShowFiltersDirectly: allowShowFiltersDirectly, className: className, compact: compact, dataTestId: `${filterBarConfig.name}-filterbar`, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, title: title }));
|
|
1283
1290
|
};
|
|
1284
1291
|
|
|
@@ -1294,6 +1301,7 @@ const mockFilterBar = {
|
|
|
1294
1301
|
getFilterBarName: doNothing,
|
|
1295
1302
|
initialState: { customerType: [] },
|
|
1296
1303
|
name: "test",
|
|
1304
|
+
hasDefaultValue: doNothing,
|
|
1297
1305
|
objectArrayIncludesValue: doNothing,
|
|
1298
1306
|
resetFiltersToInitialState: doNothing,
|
|
1299
1307
|
resetIndividualFilterToInitialState: doNothing,
|
|
@@ -1397,8 +1405,133 @@ const createInitialState = ({ name, mainFilters, setValue, }) => {
|
|
|
1397
1405
|
};
|
|
1398
1406
|
};
|
|
1399
1407
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1408
|
+
const areaFilterGeoJsonGeometrySchema = z.union([geoJsonPolygonSchema, geoJsonMultiPolygonSchema]);
|
|
1409
|
+
|
|
1410
|
+
const hasValue = (value) => {
|
|
1411
|
+
if (value === undefined || value === null) {
|
|
1412
|
+
return false;
|
|
1413
|
+
}
|
|
1414
|
+
// eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1415
|
+
if (typeof value === "object" && Object.keys(value).length === 0) {
|
|
1416
|
+
return false;
|
|
1417
|
+
}
|
|
1418
|
+
return !(Array.isArray(value) && value.length === 0);
|
|
1419
|
+
};
|
|
1420
|
+
const isNotRightType = (filterDefinition, foundFilter) => {
|
|
1421
|
+
return ((filterDefinition.type === "valueNameArray" && !isValueNameArray(foundFilter)) ||
|
|
1422
|
+
(filterDefinition.type === "valueName" && !isValueName(foundFilter)) ||
|
|
1423
|
+
(filterDefinition.type === "stringArray" && !isStringArrayFilterValue(foundFilter)) ||
|
|
1424
|
+
(filterDefinition.type === "dateRange" && !isDateRangeValue(foundFilter)) ||
|
|
1425
|
+
(filterDefinition.type === "area" && !isAreaFilterValue(foundFilter)) ||
|
|
1426
|
+
(filterDefinition.type === "minMax" && !isMinMaxFilterValue(foundFilter)) ||
|
|
1427
|
+
(filterDefinition.type === "boolean" && !isBooleanValue(foundFilter)) ||
|
|
1428
|
+
(filterDefinition.type === "string" && typeof foundFilter !== "string") ||
|
|
1429
|
+
(filterDefinition.type === "number" && typeof foundFilter !== "number"));
|
|
1430
|
+
};
|
|
1431
|
+
/**
|
|
1432
|
+
*
|
|
1433
|
+
*/
|
|
1434
|
+
const isMinMaxFilterValue = (value) => {
|
|
1435
|
+
return value
|
|
1436
|
+
? // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1437
|
+
typeof value === "object" && (Object.keys(value).includes("min") || Object.keys(value).includes("max"))
|
|
1438
|
+
: false;
|
|
1439
|
+
};
|
|
1440
|
+
/**
|
|
1441
|
+
*
|
|
1442
|
+
*/
|
|
1443
|
+
const isDateRangeValue = (value) => {
|
|
1444
|
+
return value
|
|
1445
|
+
? // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1446
|
+
typeof value === "object" && (Object.keys(value).includes("from") || Object.keys(value).includes("to"))
|
|
1447
|
+
: false;
|
|
1448
|
+
};
|
|
1449
|
+
/**
|
|
1450
|
+
* {
|
|
1451
|
+
type: "Polygon";
|
|
1452
|
+
coordinates: [number, number][][];
|
|
1453
|
+
}
|
|
1454
|
+
*/
|
|
1455
|
+
const isAreaFilterValue = (value) => {
|
|
1456
|
+
return areaFilterGeoJsonGeometrySchema.safeParse(value).success;
|
|
1457
|
+
};
|
|
1458
|
+
/**
|
|
1459
|
+
*
|
|
1460
|
+
*/
|
|
1461
|
+
const isArrayFilterValue = (value) => {
|
|
1462
|
+
return Array.isArray(value);
|
|
1463
|
+
};
|
|
1464
|
+
/**
|
|
1465
|
+
*
|
|
1466
|
+
*/
|
|
1467
|
+
const isStringArrayFilterValue = (value) => {
|
|
1468
|
+
return isArrayFilterValue(value) && value.every(item => typeof item === "string");
|
|
1469
|
+
};
|
|
1470
|
+
/**
|
|
1471
|
+
*
|
|
1472
|
+
*/
|
|
1473
|
+
const isBooleanValue = (value) => {
|
|
1474
|
+
// eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1475
|
+
return value ? typeof value === "object" && Object.keys(value).includes("booleanValue") : false;
|
|
1476
|
+
};
|
|
1477
|
+
/**
|
|
1478
|
+
* Type guard to check if a value is a single ValueName object
|
|
1479
|
+
*/
|
|
1480
|
+
const isValueName = (value) => {
|
|
1481
|
+
return (typeof value === "object" &&
|
|
1482
|
+
value !== null &&
|
|
1483
|
+
// eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1484
|
+
Object.keys(value).includes("name") &&
|
|
1485
|
+
// eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1486
|
+
Object.keys(value).includes("value"));
|
|
1487
|
+
};
|
|
1488
|
+
/**
|
|
1489
|
+
* Type guard to check if a value is an array of ValueName objects
|
|
1490
|
+
*/
|
|
1491
|
+
const isValueNameArray = (value) => {
|
|
1492
|
+
return isArrayFilterValue(value) && value.every(isValueName);
|
|
1493
|
+
};
|
|
1494
|
+
/**
|
|
1495
|
+
* Validates a filter configuration against filter definitions.
|
|
1496
|
+
*
|
|
1497
|
+
* @template TFilterBarDefinition - The type of the filter bar definition.
|
|
1498
|
+
* @param {FilterBarConfig<TFilterBarDefinition>} filter - The filter configuration to validate.
|
|
1499
|
+
* @param {FilterDefinition[]} filterDefinitions - An array of filter definitions to validate against.
|
|
1500
|
+
* @returns {boolean} - Returns `true` if the filter configuration is valid, otherwise `false`.
|
|
1501
|
+
*/
|
|
1502
|
+
const validateFilter = ({ values, starredFilterKeys, filterDefinitions, }) => {
|
|
1503
|
+
const stateKeys = [];
|
|
1504
|
+
let inBadState = false;
|
|
1505
|
+
// eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1506
|
+
for (const key of Object.keys(values)) {
|
|
1507
|
+
if (filterDefinitions.find(filterDefinition => filterDefinition.filterKey === key)) {
|
|
1508
|
+
stateKeys.push(key);
|
|
1509
|
+
}
|
|
1510
|
+
else {
|
|
1511
|
+
inBadState = true;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
filterDefinitions.forEach(filterDefinition => {
|
|
1515
|
+
const foundFilter = values[filterDefinition.filterKey];
|
|
1516
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1517
|
+
if (foundFilter && hasValue(foundFilter) && isNotRightType(filterDefinition, foundFilter)) {
|
|
1518
|
+
inBadState = true;
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
if (starredFilterKeys.length > 0) {
|
|
1522
|
+
const allKeys = filterDefinitions.map(f => f.filterKey);
|
|
1523
|
+
const filteredStarredFilterKeys = starredFilterKeys.filter(key => allKeys.includes(key));
|
|
1524
|
+
if (filteredStarredFilterKeys.length !== starredFilterKeys.length) {
|
|
1525
|
+
inBadState = true;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
stateKeys.sort((a, b) => a.localeCompare(b));
|
|
1529
|
+
const filterKeysNotEqual = !isEqual(stateKeys, filterDefinitions.map(f => f.filterKey).sort((a, b) => a.localeCompare(b)));
|
|
1530
|
+
return !(inBadState || filterKeysNotEqual);
|
|
1531
|
+
};
|
|
1532
|
+
|
|
1533
|
+
/**
|
|
1534
|
+
* Custom hook for managing a filter bar's actions .
|
|
1402
1535
|
*
|
|
1403
1536
|
* @template TFilterBarDefinition - A generic type for the filter bar definition.
|
|
1404
1537
|
* @returns {object} An object containing filter bar configuration and actions.
|
|
@@ -1417,6 +1550,33 @@ const useFilterBarActions = ({ name, filterBarConfig, filterBarDefinition, setFi
|
|
|
1417
1550
|
const filter = filterBarConfig.values[key];
|
|
1418
1551
|
return filter?.includes(value) || false;
|
|
1419
1552
|
},
|
|
1553
|
+
hasDefaultValue(key) {
|
|
1554
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
1555
|
+
const filterValue = filterBarConfig.values[key];
|
|
1556
|
+
const filterDefinition = filterBarDefinition[key];
|
|
1557
|
+
if (dequal(filterValue, filterBarDefinition[key]?.defaultValue) || dequal(filterValue, initialState?.[key])) {
|
|
1558
|
+
return true;
|
|
1559
|
+
}
|
|
1560
|
+
if (filterDefinition && filterDefinition.defaultValue === undefined) {
|
|
1561
|
+
return false;
|
|
1562
|
+
}
|
|
1563
|
+
if (!filterValue) {
|
|
1564
|
+
return true;
|
|
1565
|
+
}
|
|
1566
|
+
if (Array.isArray(filterValue)) {
|
|
1567
|
+
return filterValue.length === 0;
|
|
1568
|
+
}
|
|
1569
|
+
if (isMinMaxFilterValue(filterValue)) {
|
|
1570
|
+
return filterValue.min === undefined && filterValue.max === undefined;
|
|
1571
|
+
}
|
|
1572
|
+
if (isValueName(filterValue)) {
|
|
1573
|
+
return filterValue.value === "";
|
|
1574
|
+
}
|
|
1575
|
+
if (typeof filterValue === "object") {
|
|
1576
|
+
return objectKeys(filterValue).length === 0;
|
|
1577
|
+
}
|
|
1578
|
+
return false;
|
|
1579
|
+
},
|
|
1420
1580
|
getValuesByKey(key) {
|
|
1421
1581
|
return filterBarConfig.values[key];
|
|
1422
1582
|
},
|
|
@@ -1681,131 +1841,6 @@ const useFilterBarActions = ({ name, filterBarConfig, filterBarDefinition, setFi
|
|
|
1681
1841
|
return useMemo(() => ({ filterMapGetter, filterMapActions }), [filterMapGetter, filterMapActions]);
|
|
1682
1842
|
};
|
|
1683
1843
|
|
|
1684
|
-
const areaFilterGeoJsonGeometrySchema = z.union([geoJsonPolygonSchema, geoJsonMultiPolygonSchema]);
|
|
1685
|
-
|
|
1686
|
-
const hasValue = (value) => {
|
|
1687
|
-
if (value === undefined || value === null) {
|
|
1688
|
-
return false;
|
|
1689
|
-
}
|
|
1690
|
-
// eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1691
|
-
if (typeof value === "object" && Object.keys(value).length === 0) {
|
|
1692
|
-
return false;
|
|
1693
|
-
}
|
|
1694
|
-
return !(Array.isArray(value) && value.length === 0);
|
|
1695
|
-
};
|
|
1696
|
-
const isNotRightType = (filterDefinition, foundFilter) => {
|
|
1697
|
-
return ((filterDefinition.type === "valueNameArray" && !isValueNameArray(foundFilter)) ||
|
|
1698
|
-
(filterDefinition.type === "valueName" && !isValueName(foundFilter)) ||
|
|
1699
|
-
(filterDefinition.type === "stringArray" && !isStringArrayFilterValue(foundFilter)) ||
|
|
1700
|
-
(filterDefinition.type === "dateRange" && !isDateRangeValue(foundFilter)) ||
|
|
1701
|
-
(filterDefinition.type === "area" && !isAreaFilterValue(foundFilter)) ||
|
|
1702
|
-
(filterDefinition.type === "minMax" && !isMinMaxFilterValue(foundFilter)) ||
|
|
1703
|
-
(filterDefinition.type === "boolean" && !isBooleanValue(foundFilter)) ||
|
|
1704
|
-
(filterDefinition.type === "string" && typeof foundFilter !== "string") ||
|
|
1705
|
-
(filterDefinition.type === "number" && typeof foundFilter !== "number"));
|
|
1706
|
-
};
|
|
1707
|
-
/**
|
|
1708
|
-
*
|
|
1709
|
-
*/
|
|
1710
|
-
const isMinMaxFilterValue = (value) => {
|
|
1711
|
-
return value
|
|
1712
|
-
? // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1713
|
-
typeof value === "object" && (Object.keys(value).includes("min") || Object.keys(value).includes("max"))
|
|
1714
|
-
: false;
|
|
1715
|
-
};
|
|
1716
|
-
/**
|
|
1717
|
-
*
|
|
1718
|
-
*/
|
|
1719
|
-
const isDateRangeValue = (value) => {
|
|
1720
|
-
return value
|
|
1721
|
-
? // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1722
|
-
typeof value === "object" && (Object.keys(value).includes("from") || Object.keys(value).includes("to"))
|
|
1723
|
-
: false;
|
|
1724
|
-
};
|
|
1725
|
-
/**
|
|
1726
|
-
* {
|
|
1727
|
-
type: "Polygon";
|
|
1728
|
-
coordinates: [number, number][][];
|
|
1729
|
-
}
|
|
1730
|
-
*/
|
|
1731
|
-
const isAreaFilterValue = (value) => {
|
|
1732
|
-
return areaFilterGeoJsonGeometrySchema.safeParse(value).success;
|
|
1733
|
-
};
|
|
1734
|
-
/**
|
|
1735
|
-
*
|
|
1736
|
-
*/
|
|
1737
|
-
const isArrayFilterValue = (value) => {
|
|
1738
|
-
return Array.isArray(value);
|
|
1739
|
-
};
|
|
1740
|
-
/**
|
|
1741
|
-
*
|
|
1742
|
-
*/
|
|
1743
|
-
const isStringArrayFilterValue = (value) => {
|
|
1744
|
-
return isArrayFilterValue(value) && value.every(item => typeof item === "string");
|
|
1745
|
-
};
|
|
1746
|
-
/**
|
|
1747
|
-
*
|
|
1748
|
-
*/
|
|
1749
|
-
const isBooleanValue = (value) => {
|
|
1750
|
-
// eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1751
|
-
return value ? typeof value === "object" && Object.keys(value).includes("booleanValue") : false;
|
|
1752
|
-
};
|
|
1753
|
-
/**
|
|
1754
|
-
* Type guard to check if a value is a single ValueName object
|
|
1755
|
-
*/
|
|
1756
|
-
const isValueName = (value) => {
|
|
1757
|
-
return (typeof value === "object" &&
|
|
1758
|
-
value !== null &&
|
|
1759
|
-
// eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1760
|
-
Object.keys(value).includes("name") &&
|
|
1761
|
-
// eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1762
|
-
Object.keys(value).includes("value"));
|
|
1763
|
-
};
|
|
1764
|
-
/**
|
|
1765
|
-
* Type guard to check if a value is an array of ValueName objects
|
|
1766
|
-
*/
|
|
1767
|
-
const isValueNameArray = (value) => {
|
|
1768
|
-
return isArrayFilterValue(value) && value.every(isValueName);
|
|
1769
|
-
};
|
|
1770
|
-
/**
|
|
1771
|
-
* Validates a filter configuration against filter definitions.
|
|
1772
|
-
*
|
|
1773
|
-
* @template TFilterBarDefinition - The type of the filter bar definition.
|
|
1774
|
-
* @param {FilterBarConfig<TFilterBarDefinition>} filter - The filter configuration to validate.
|
|
1775
|
-
* @param {FilterDefinition[]} filterDefinitions - An array of filter definitions to validate against.
|
|
1776
|
-
* @returns {boolean} - Returns `true` if the filter configuration is valid, otherwise `false`.
|
|
1777
|
-
*/
|
|
1778
|
-
const validateFilter = ({ values, starredFilterKeys, filterDefinitions, }) => {
|
|
1779
|
-
const stateKeys = [];
|
|
1780
|
-
let inBadState = false;
|
|
1781
|
-
// eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
|
|
1782
|
-
for (const key of Object.keys(values)) {
|
|
1783
|
-
if (filterDefinitions.find(filterDefinition => filterDefinition.filterKey === key)) {
|
|
1784
|
-
stateKeys.push(key);
|
|
1785
|
-
}
|
|
1786
|
-
else {
|
|
1787
|
-
inBadState = true;
|
|
1788
|
-
}
|
|
1789
|
-
}
|
|
1790
|
-
filterDefinitions.forEach(filterDefinition => {
|
|
1791
|
-
const foundFilter = values[filterDefinition.filterKey];
|
|
1792
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1793
|
-
if (foundFilter && hasValue(foundFilter) && isNotRightType(filterDefinition, foundFilter)) {
|
|
1794
|
-
inBadState = true;
|
|
1795
|
-
}
|
|
1796
|
-
});
|
|
1797
|
-
if (starredFilterKeys.length > 0) {
|
|
1798
|
-
const allKeys = filterDefinitions.map(f => f.filterKey);
|
|
1799
|
-
const filteredStarredFilterKeys = starredFilterKeys.filter(key => allKeys.includes(key));
|
|
1800
|
-
if (filteredStarredFilterKeys.length !== starredFilterKeys.length) {
|
|
1801
|
-
inBadState = true;
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
stateKeys.sort((a, b) => a.localeCompare(b));
|
|
1805
|
-
const filterKeysNotEqual = !isEqual(stateKeys, filterDefinitions.map(f => f.filterKey).sort((a, b) => a.localeCompare(b)));
|
|
1806
|
-
return !(inBadState || filterKeysNotEqual);
|
|
1807
|
-
};
|
|
1808
|
-
|
|
1809
1844
|
const getPersistenceKey = (name, clientSideUserId) => `filter-${name}-${clientSideUserId}`;
|
|
1810
1845
|
/**
|
|
1811
1846
|
* Custom hook for managing the persistence of filter bar configurations.
|
|
@@ -2095,4 +2130,4 @@ const mergeFilters = (filterBarDefinition, extraFilters) => {
|
|
|
2095
2130
|
*/
|
|
2096
2131
|
setupLibraryTranslations();
|
|
2097
2132
|
|
|
2098
|
-
export { DefaultCheckboxFilter, DefaultDateRangeFilter, DefaultMinMaxFilter, DefaultRadioFilter, DynamicFilterList, FilterBar, FilterButtonTooltipLabel, FilterComponent, FilterEvents, FilterHeader, FilterResults, FilterTableComponent, FiltersMenu, FiltersMenuContent, FiltersRenderer, GroupedFiltersList, HierarchicalCheckboxFilter, ResetFiltersButton, areaFilterGeoJsonGeometrySchema, isAreaFilterValue, isArrayFilterValue, isBooleanValue, isDateRangeValue, isMinMaxFilterValue, isStringArrayFilterValue, isValueName, isValueNameArray, mergeFilters, mockFilterBar, toggleFilterValue, useFilterBar, useFilterBarAsync, useFiltersMenu, useSearchParamAsFilter, validateFilter };
|
|
2133
|
+
export { AppliedFiltersRenderer, DefaultCheckboxFilter, DefaultDateRangeFilter, DefaultMinMaxFilter, DefaultRadioFilter, DynamicFilterList, FilterBar, FilterButtonTooltipLabel, FilterComponent, FilterEvents, FilterHeader, FilterResults, FilterTableComponent, FiltersMenu, FiltersMenuContent, FiltersRenderer, GroupedFiltersList, HierarchicalCheckboxFilter, MultipleFilterTooltipLabel, ResetFiltersButton, areaFilterGeoJsonGeometrySchema, isAreaFilterValue, isArrayFilterValue, isBooleanValue, isDateRangeValue, isMinMaxFilterValue, isStringArrayFilterValue, isValueName, isValueNameArray, mergeFilters, mockFilterBar, toggleFilterValue, useFilterBar, useFilterBarAsync, useFiltersMenu, useSearchParamAsFilter, validateFilter };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/filters-filter-bar",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.211",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -14,18 +14,18 @@
|
|
|
14
14
|
"tailwind-merge": "^2.0.0",
|
|
15
15
|
"string-ts": "^2.0.0",
|
|
16
16
|
"zod": "3.23.4",
|
|
17
|
-
"@trackunit/iris-app-api": "1.3.
|
|
18
|
-
"@trackunit/react-core-hooks": "1.3.
|
|
19
|
-
"@trackunit/react-filter-components": "1.3.
|
|
20
|
-
"@trackunit/react-date-and-time-components": "1.3.
|
|
21
|
-
"@trackunit/shared-utils": "1.5.
|
|
22
|
-
"@trackunit/react-form-components": "1.3.
|
|
23
|
-
"@trackunit/react-core-contexts-api": "1.4.
|
|
24
|
-
"@trackunit/geo-json-utils": "1.3.
|
|
25
|
-
"@trackunit/i18n-library-translation": "1.3.
|
|
26
|
-
"@trackunit/css-class-variance-utilities": "1.3.
|
|
27
|
-
"@trackunit/react-components": "1.4.
|
|
28
|
-
"@trackunit/react-test-setup": "1.0.
|
|
17
|
+
"@trackunit/iris-app-api": "1.3.135",
|
|
18
|
+
"@trackunit/react-core-hooks": "1.3.137",
|
|
19
|
+
"@trackunit/react-filter-components": "1.3.173",
|
|
20
|
+
"@trackunit/react-date-and-time-components": "1.3.176",
|
|
21
|
+
"@trackunit/shared-utils": "1.5.127",
|
|
22
|
+
"@trackunit/react-form-components": "1.3.174",
|
|
23
|
+
"@trackunit/react-core-contexts-api": "1.4.133",
|
|
24
|
+
"@trackunit/geo-json-utils": "1.3.127",
|
|
25
|
+
"@trackunit/i18n-library-translation": "1.3.144",
|
|
26
|
+
"@trackunit/css-class-variance-utilities": "1.3.127",
|
|
27
|
+
"@trackunit/react-components": "1.4.153",
|
|
28
|
+
"@trackunit/react-test-setup": "1.0.17"
|
|
29
29
|
},
|
|
30
30
|
"module": "./index.esm.js",
|
|
31
31
|
"main": "./index.cjs.js",
|