@trackunit/filters-filter-bar 0.0.492 → 0.0.496

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 CHANGED
@@ -2,19 +2,21 @@
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var i18nLibraryTranslation = require('@trackunit/i18n-library-translation');
5
- var tailwindMerge = require('tailwind-merge');
6
- var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
7
- var reactComponents = require('@trackunit/react-components');
5
+ var reactCoreHooks = require('@trackunit/react-core-hooks');
6
+ var reactFilterComponents = require('@trackunit/react-filter-components');
8
7
  var sharedUtils = require('@trackunit/shared-utils');
9
8
  var react = require('react');
10
- var reactFilterComponents = require('@trackunit/react-filter-components');
11
- var reactCoreHooks = require('@trackunit/react-core-hooks');
12
9
  var reactCoreContextsApi = require('@trackunit/react-core-contexts-api');
10
+ var reactComponents = require('@trackunit/react-components');
13
11
  var reactFormComponents = require('@trackunit/react-form-components');
14
12
  var reactDateAndTimeComponents = require('@trackunit/react-date-and-time-components');
13
+ var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
14
+ var tailwindMerge = require('tailwind-merge');
15
+ var zod = require('zod');
15
16
  var dequal = require('dequal');
16
- var stringTs = require('string-ts');
17
17
  var isEqual = require('lodash/isEqual');
18
+ var reduce = require('lodash/reduce');
19
+ var stringTs = require('string-ts');
18
20
 
19
21
  var defaultTranslations = {
20
22
  "access.management.filter.operator.role.keyAdmin": "Key Admin",
@@ -82,8 +84,8 @@ var defaultTranslations = {
82
84
  "assetFilters.regions.WESTERN_ASIA": "Western Asia",
83
85
  "assetFilters.regions.WESTERN_EUROPE": "Western Europe",
84
86
  "assetFilters.regions.WHOLE_WORLD": "Whole world",
85
- "assetFilters.searchFilter.label": "Search filter",
86
- "assetFilters.searchPlaceholder": "Search...",
87
+ "assetFilters.searchFilter.label": "Text search",
88
+ "assetFilters.searchPlaceholder": "Search by text...",
87
89
  "assetFilters.serviceAssignmentStatusFilter.label": "Assignment Status",
88
90
  "assetFilters.servicePlanFilter.fullyConfigured": "Fully configured",
89
91
  "assetFilters.servicePlanFilter.label": "Service Plan",
@@ -105,6 +107,12 @@ var defaultTranslations = {
105
107
  "auditlog.filter.users": "Users",
106
108
  "filter.more.options.if.you.search.description": "Use search to refine results.",
107
109
  "filter.more.options.if.you.search.title": "Over {{count}} items found.",
110
+ "filters.shared.clear": "clear",
111
+ "filters.shared.clearAll": "clear all",
112
+ "filters.shared.recentSearches": "Recent searches",
113
+ "filtersBar.appliedFiltersTooltip.none": "No filters applied",
114
+ "filtersBar.appliedFiltersTooltip.plural": "{{count}} filters applied:",
115
+ "filtersBar.appliedFiltersTooltip.singular": "{{filterName}} filter applied",
108
116
  "filtersBar.closeFilter": "Close",
109
117
  "filtersBar.defaultAssetFilters.followedFilter.ALL": "All Assets",
110
118
  "filtersBar.defaultAssetFilters.followedFilter.allLabel": "All Assets",
@@ -304,128 +312,6 @@ const setupLibraryTranslations = () => {
304
312
  i18nLibraryTranslation.registerTranslations(translations);
305
313
  };
306
314
 
307
- /**
308
- * Custom React hook for grouping and filtering a list of filters.
309
- *
310
- * @param {FilterDefinition[]} filterDefinitions - The list of filter definitions to group and filter.
311
- * @param {string[]} hiddenFilters - An array of filter keys representing filters to be hidden.
312
- * @returns {UseGroupFiltersReturnType} An object containing the grouped filters.
313
- */
314
- const useStarredGroupFilters = (filterDefinitions, hiddenFilters) => {
315
- const [t] = useTranslation();
316
- const filtersGrouped = react.useMemo(() => {
317
- const keys = uniqueKeysFromGroups(filterDefinitions);
318
- return keys
319
- .map(key => ({
320
- key: t(`filtersBar.groups.${key}`),
321
- filters: filterDefinitions.filter(rawFilter => rawFilter.group === key && hiddenFilters.includes(rawFilter.filterKey) === false),
322
- }))
323
- .filter(filter => filter.filters.length > 0);
324
- }, [filterDefinitions, hiddenFilters, t]);
325
- return { filtersGrouped };
326
- };
327
- const uniqueKeysFromGroups = (filters) => [...new Set(filters.map(filter => filter.group))];
328
-
329
- /**
330
- * Returns the two first values, appends counter if more.
331
- *
332
- * @param {string[]} [input] Array of values to reduce
333
- * @returns {*} {string}
334
- */
335
- const reduceFilterText = (input) => {
336
- if (!Array.isArray(input)) {
337
- return input;
338
- }
339
- // Only first two elements
340
- const firstTwo = input.slice(0, 2);
341
- const remainder = input.slice(2, input.length).length;
342
- const firstTwoJoined = firstTwo.join(", ");
343
- const remainderStr = remainder > 0 ? `, +${remainder}` : "";
344
- return firstTwoJoined + remainderStr;
345
- };
346
-
347
- /**
348
- * Filter is a React component that renders a filter element based on the provided filter definition and state.
349
- *
350
- * @returns {JSX.Element} - Returns the Filter component.
351
- */
352
- const Filter = ({ filter, filterBarActions, filterState, }) => {
353
- const values = filterBarActions.getValuesByKey(filter.filterKey);
354
- const filterText = () => {
355
- if (values) {
356
- if (filter.valueAsText) {
357
- return reduceFilterText(filter.valueAsText(values));
358
- }
359
- else if (filter.type === "valueName") {
360
- return reduceFilterText(values.map(value => value.name));
361
- }
362
- return values.toString();
363
- }
364
- return "";
365
- };
366
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
367
- const setValue = (callback) => {
368
- const newValue = callback(filterBarActions.getValuesByKey(filter.filterKey));
369
- if (filter.type === "string") {
370
- filterBarActions.setStringValue(filter.filterKey, newValue);
371
- }
372
- else if (filter.type === "stringArray") {
373
- filterBarActions.setArrayValue(filter.filterKey, newValue);
374
- }
375
- else if (filter.type === "dateRange") {
376
- filterBarActions.setDateRange(filter.filterKey, newValue);
377
- }
378
- else if (filter.type === "boundingBox") {
379
- filterBarActions.setBoundingBox(newValue);
380
- }
381
- else if (filter.type === "valueName") {
382
- filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
383
- }
384
- else if (filter.type === "minMax") {
385
- filterBarActions.setMinMaxValue(filter.filterKey, newValue);
386
- }
387
- else if (filter.type === "boolean") {
388
- filterBarActions.setBooleanValue(filter.filterKey, newValue);
389
- }
390
- else {
391
- filterBarActions.setNumberValue(filter.filterKey, newValue);
392
- }
393
- };
394
- const text = filterText();
395
- const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
396
- const showDirectly = filter.showDirectly || false;
397
- return showDirectly ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: filter.component({
398
- filterDefinition: filter,
399
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
400
- value: values,
401
- setValue,
402
- filterBarActions,
403
- filterState,
404
- }) })) : (jsxRuntime.jsx(reactFilterComponents.Filter, { activeLabel: activeFilterText, dataTestId: `${filter.filterKey}-filter-button`, isActive: filterText().length > 0, title: filter.title, withStickyHeader: true, children: filter.component({
405
- filterDefinition: filter,
406
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
407
- value: values,
408
- setValue,
409
- filterBarActions,
410
- filterState,
411
- }) }));
412
- };
413
-
414
- /**
415
- * ResetFiltersButton is a React component that provides a button for resetting filters.
416
- *
417
- * @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
418
- */
419
- const ResetFiltersButton = ({ filtersHaveBeenApplied, resetFiltersToInitialState, }) => {
420
- const [t] = useTranslation();
421
- if (!filtersHaveBeenApplied) {
422
- return null;
423
- }
424
- return (jsxRuntime.jsx(reactComponents.Button, { dataTestId: "reset-filters-button", onClick: () => {
425
- resetFiltersToInitialState();
426
- }, size: "small", variant: "ghost-neutral", children: t("filtersBar.resetFilters") }));
427
- };
428
-
429
315
  /**
430
316
  * Events — each with a name, and eventType.
431
317
  * Adding a Description is encouraged.
@@ -441,101 +327,6 @@ const FilterEvents = {
441
327
  "Starring Filter - Toggled": reactCoreContextsApi.createEvent(),
442
328
  };
443
329
 
444
- /**
445
- * StarredFiltersMenu is a React component that displays a menu of starred filters within a filter bar.
446
- *
447
- * @returns {JSX.Element} - Returns the StarredFiltersMenu component.
448
- */
449
- const StarredFiltersMenu = ({ filterBarDefinition, updateStarredFilters, starredFilterKeys, hiddenFilters = [], }) => {
450
- const [t] = useTranslation();
451
- const { logEvent } = reactCoreHooks.useAnalytics(FilterEvents);
452
- const hideInStarredMenu = react.useMemo(() => {
453
- return sharedUtils.objectValues(filterBarDefinition)
454
- .map(filter => {
455
- const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true;
456
- const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
457
- return !showInStarredMenu || !showInFilterBar ? filter.filterKey : null;
458
- })
459
- .filter(sharedUtils.truthy);
460
- }, [filterBarDefinition]);
461
- const { filtersGrouped } = useStarredGroupFilters(sharedUtils.objectValues(filterBarDefinition), [
462
- ...hideInStarredMenu,
463
- ...hiddenFilters,
464
- ]);
465
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs(reactComponents.Popover, { placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactComponents.IconButton, { dataTestId: "starred-filters-menu", icon: jsxRuntime.jsx(reactComponents.Icon, { name: "Funnel", size: "small" }), size: "small", title: t("filtersBar.starredFiltersTitle") }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsx(reactComponents.MenuList, { className: "max-h-[55vh] w-72 overflow-y-auto", dataTestId: "starred-filters-menu-popover", children: jsxRuntime.jsx("div", { children: filtersGrouped.map((group, idx) => {
466
- const isLast = idx === filtersGrouped.length - 1;
467
- return (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsx("div", { className: "mt-2 mb-1 flex px-2", children: jsxRuntime.jsx(reactComponents.Text, { dataTestId: "starred-filters-group-title", size: "small", subtle: true, uppercase: true, weight: "bold", children: group.key }) }), group.filters.map(filter => {
468
- return (jsxRuntime.jsxs("div", { className: "flex w-full cursor-pointer items-center justify-between rounded-sm py-0 px-2 transition hover:bg-neutral-50", "data-testid": `${filter.filterKey}-star-filter`, onClick: () => {
469
- updateStarredFilters(filter.filterKey);
470
- logEvent("Starring Filter - Toggled", {
471
- type: filter.filterKey,
472
- });
473
- }, children: [jsxRuntime.jsx(reactComponents.Text, { weight: "thick", children: filter.title }), jsxRuntime.jsx(reactComponents.StarButton, { onClick: e => {
474
- e.preventDefault();
475
- e.stopPropagation();
476
- updateStarredFilters(filter.filterKey);
477
- logEvent("Starring Filter - Toggled", {
478
- type: filter.filterKey,
479
- });
480
- }, starred: starredFilterKeys.includes(filter.filterKey) })] }, filter.filterKey));
481
- }), !isLast && jsxRuntime.jsx("div", { className: "mt-2 h-[1px] w-full bg-neutral-300" })] }, group.key));
482
- }) }) }) })] }), starredFilterKeys.length > 0 ? jsxRuntime.jsx("div", { className: "h-7 w-[1px] bg-neutral-300" }) : null] }));
483
- };
484
-
485
- const cvaCollapse = cssClassVarianceUtilities.cvaMerge("bg-white", {
486
- variants: {
487
- disableCollapse: { true: "hidden" },
488
- },
489
- });
490
- const cvaFilterWithoutCollapse = cssClassVarianceUtilities.cvaMerge([], {
491
- variants: { disableCollapse: { true: "flex", false: ["hidden", "md:flex"] } },
492
- });
493
- /**
494
- * StarredFilters is a React component that displays a list of starred filters based on the provided filter bar configuration.
495
- *
496
- * @template TFilterBarDefinition - The type representing the filter bar definition.
497
- * @returns {JSX.Element} - Returns the StarredFilters component.
498
- */
499
- const StarredFilters = ({ filterBarDefinition, filterBarConfig, disableCollapse, hiddenFilters = [], }) => {
500
- const [expanded, setExpanded] = react.useState(false);
501
- const [t] = useTranslation();
502
- const { isMd } = reactComponents.useViewportSize();
503
- return isMd || disableCollapse ? (jsxRuntime.jsx(FilterSelection, { className: cvaFilterWithoutCollapse({ disableCollapse }), filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters })) : (jsxRuntime.jsx(reactComponents.Collapse, { className: cvaCollapse({ disableCollapse }), id: "storybook_collapsible", initialExpanded: expanded, label: t("filtersBar.filtersHeading") + (filterBarConfig.filtersHaveBeenApplied ? " *" : ""), onToggle: () => setExpanded(prev => !prev), children: jsxRuntime.jsx(FilterSelection, { className: "flex", filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }) }));
504
- };
505
- const FilterSelection = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], className, }) => {
506
- const hideInFilterBar = react.useMemo(() => {
507
- return sharedUtils.objectValues(filterBarDefinition)
508
- .map(filter => {
509
- const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
510
- return !showInFilterBar ? filter.filterKey : null;
511
- })
512
- .filter(sharedUtils.truthy);
513
- }, [filterBarDefinition]);
514
- const { filtersGrouped } = useStarredGroupFilters(sharedUtils.objectValues(filterBarDefinition), [
515
- ...hideInFilterBar,
516
- ...hiddenFilters,
517
- ]);
518
- return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge([className, "flex-wrap", "items-center", "gap-3"]), "data-testid": "starred-filters", children: [jsxRuntime.jsx(StarredFiltersMenu, { filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, starredFilterKeys: filterBarConfig.starredFilterKeys, updateStarredFilters: filterBarConfig.updateStarredFilters }), filtersGrouped.map(group => group.filters
519
- .filter(filter => !hiddenFilters.includes(filter.filterKey))
520
- .map(filter => {
521
- const showMenuAnyway = filterBarConfig.filterHasChanged(filter.filterKey) &&
522
- filter.showInStarredMenu &&
523
- !filter.showInStarredMenu();
524
- if (filterBarConfig.starredFilterKeys.includes(filter.filterKey) ||
525
- showMenuAnyway) {
526
- return (jsxRuntime.jsx(react.Fragment, { children: jsxRuntime.jsx(Filter, { filter: filter, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters } }, `filter-${filter.filterKey}`) }, filter.filterKey));
527
- }
528
- return null;
529
- })), jsxRuntime.jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.filtersHaveBeenApplied, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })] }));
530
- };
531
-
532
- /**
533
- * The FilterBar component serves as a wrapper for managing filters.
534
- */
535
- const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, disableCollapse = false, }) => {
536
- return (jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("flex", className), "data-testid": `${filterBarConfig.name}-filterbar`, children: jsxRuntime.jsx(StarredFilters, { disableCollapse: disableCollapse, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }) }));
537
- };
538
-
539
330
  /**
540
331
  * Toggles a filter value in an array.
541
332
  *
@@ -561,7 +352,7 @@ const DynamicFilterList = ({ rowCount, keyMapper, labelMapper, onChange, checked
561
352
  const [t] = useTranslation();
562
353
  const parentRef = react.useRef(null);
563
354
  const updatedRowCount = react.useMemo(() => (showRequestMoreUseSearch ? rowCount + 1 : rowCount), [rowCount, showRequestMoreUseSearch]);
564
- return (jsxRuntime.jsx(reactFilterComponents.FilterBody, { dataTestId: "FUUUCK", limitSize: true, ref: parentRef, children: jsxRuntime.jsx(reactComponents.VirtualizedList, { count: updatedRowCount, rowHeight: 40, separator: "space", children: index => {
355
+ return (jsxRuntime.jsx(reactFilterComponents.FilterBody, { limitSize: true, ref: parentRef, children: jsxRuntime.jsx(reactComponents.VirtualizedList, { count: updatedRowCount, rowHeight: 40, separator: "space", children: index => {
565
356
  return (jsxRuntime.jsx("div", { children: showRequestMoreUseSearch && index === rowCount ? (jsxRuntime.jsxs("div", { className: "p-3 pt-2", children: [jsxRuntime.jsx("span", { className: "text-sm text-gray-600", children: t("filter.more.options.if.you.search.title", { count: rowCount }) }), jsxRuntime.jsx("br", {}), jsxRuntime.jsx("span", { className: "text-sm italic text-gray-400", children: t("filter.more.options.if.you.search.description") })] })) : type === "Radio" ? (jsxRuntime.jsx(reactFilterComponents.RadioFilterItem, { dataTestId: "dynamic-filter-radio-" + keyMapper(index), itemCount: count(index), label: labelMapper(index), selected: checked(index), value: keyMapper(index) }, keyMapper(index))) : (jsxRuntime.jsx(reactFilterComponents.CheckBoxFilterItem, { checked: checked(index), dataTestId: "dynamic-filter-check-box-" + keyMapper(index), itemCount: count(index), label: labelMapper(index), name: keyMapper(index), onChange: () => {
566
357
  onChange === null || onChange === void 0 ? void 0 : onChange(index);
567
358
  } }, keyMapper(index))) }));
@@ -573,13 +364,17 @@ const DynamicFilterList = ({ rowCount, keyMapper, labelMapper, onChange, checked
573
364
  *
574
365
  * @returns {JSX.Element} - Returns the FilterHeader component.
575
366
  */
576
- const FilterHeader = ({ filterKey, title, searchEnabled, searchProps, filterHasChanged, resetIndividualFilterToInitialState, onResetFilter, loading = false, }) => {
367
+ const FilterHeader = ({ filterKey, title, searchEnabled, searchProps, filterHasChanged, resetIndividualFilterToInitialState, onResetFilter, loading = false, className, dataTestId, }) => {
577
368
  const [t] = useTranslation();
578
369
  const handleResetFilter = () => {
579
370
  resetIndividualFilterToInitialState(filterKey);
580
371
  onResetFilter === null || onResetFilter === void 0 ? void 0 : onResetFilter();
581
372
  };
582
- return (jsxRuntime.jsx(reactFilterComponents.FilterHeader, { dataTestId: `${filterKey}-filter-header`, loading: loading, onReset: handleResetFilter, resetLabel: t("filtersBar.resetFilter"), showReset: filterHasChanged(filterKey), title: title, children: searchEnabled ? (jsxRuntime.jsx(reactFormComponents.Search, { autoFocus: true, fieldSize: "small", id: `${filterKey}-search`, onChange: e => searchProps.onChange(e.currentTarget.value), placeholder: t("assetFilters.searchPlaceholder"), suffix: jsxRuntime.jsx(reactComponents.Text, { size: "small", subtle: true, children: searchProps.count }), value: searchProps.value })) : null }));
373
+ return (jsxRuntime.jsx(reactFilterComponents.FilterHeader, { className: className, dataTestId: dataTestId !== null && dataTestId !== void 0 ? dataTestId : `${filterKey}-filter-header`, loading: loading, onReset: handleResetFilter, resetLabel: t("filtersBar.resetFilter"), showReset: filterHasChanged(filterKey), title: title, children: searchEnabled ? (jsxRuntime.jsx(reactFormComponents.Search, { autoFocus: true, fieldSize: "small", id: `${filterKey}-search`, onChange: e => searchProps.onChange(e.currentTarget.value), onKeyDown: e => {
374
+ if (e.key === "Enter" && searchProps.onEnter) {
375
+ searchProps.onEnter(searchProps.value);
376
+ }
377
+ }, placeholder: t("assetFilters.searchPlaceholder"), suffix: jsxRuntime.jsx(reactComponents.Text, { size: "small", subtle: true, children: searchProps.count }), value: searchProps.value })) : null }));
583
378
  };
584
379
 
585
380
  /**
@@ -794,6 +589,351 @@ const DefaultRadioFilter = ({ filterDefinition, filterBarActions, options, loadi
794
589
  }, value: selectedRadioId, children: jsxRuntime.jsx(DynamicFilterList, { checked: index => { var _a; return filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""); }, count: index => { var _a; return (_a = res[index]) === null || _a === void 0 ? void 0 : _a.count; }, keyMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""; }, labelMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.label) || ""; }, rowCount: res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "Radio" }) })) })] }));
795
590
  };
796
591
 
592
+ /**
593
+ * Custom React hook for grouping and filtering a list of filters.
594
+ *
595
+ * @param {FilterDefinition[]} filterDefinitions - The list of filter definitions to group and filter.
596
+ * @param {string[]} hiddenFilters - An array of filter keys representing filters to be hidden.
597
+ * @returns {UseGroupFiltersReturnType} An object containing the grouped filters.
598
+ */
599
+ const useStarredGroupFilters = (filterDefinitions, hiddenFilters) => {
600
+ const [t] = useTranslation();
601
+ const filtersGrouped = react.useMemo(() => {
602
+ const keys = uniqueKeysFromGroups(filterDefinitions);
603
+ return keys
604
+ .map(key => ({
605
+ key: t(`filtersBar.groups.${key}`),
606
+ filters: filterDefinitions.filter(rawFilter => rawFilter.group === key && hiddenFilters.includes(rawFilter.filterKey) === false),
607
+ }))
608
+ .filter(filter => filter.filters.length > 0);
609
+ }, [filterDefinitions, hiddenFilters, t]);
610
+ return { filtersGrouped };
611
+ };
612
+ const uniqueKeysFromGroups = (filters) => [...new Set(filters.map(filter => filter.group))];
613
+
614
+ /**
615
+ * Returns the two first values, appends counter if more.
616
+ *
617
+ * @param {string[]} [input] Array of values to reduce
618
+ * @returns {*} {string}
619
+ */
620
+ const reduceFilterText = (input) => {
621
+ if (!Array.isArray(input)) {
622
+ return input;
623
+ }
624
+ // Only first two elements
625
+ const firstTwo = input.slice(0, 2);
626
+ const remainder = input.slice(2, input.length).length;
627
+ const firstTwoJoined = firstTwo.join(", ");
628
+ const remainderStr = remainder > 0 ? `, +${remainder}` : "";
629
+ return firstTwoJoined + remainderStr;
630
+ };
631
+
632
+ /**
633
+ * Filter is a React component that renders a filter element based on the provided filter definition and state.
634
+ *
635
+ * @returns {JSX.Element} - Returns the Filter component.
636
+ */
637
+ const Filter = ({ filter, filterBarActions, filterState, }) => {
638
+ const values = filterBarActions.getValuesByKey(filter.filterKey);
639
+ const filterText = () => {
640
+ if (values) {
641
+ if (filter.valueAsText) {
642
+ return reduceFilterText(filter.valueAsText(values));
643
+ }
644
+ else if (filter.type === "valueName") {
645
+ return reduceFilterText(values.map(value => value.name));
646
+ }
647
+ return values.toString();
648
+ }
649
+ return "";
650
+ };
651
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
652
+ const setValue = (callback) => {
653
+ const newValue = callback(filterBarActions.getValuesByKey(filter.filterKey));
654
+ if (filter.type === "string") {
655
+ filterBarActions.setStringValue(filter.filterKey, newValue);
656
+ }
657
+ else if (filter.type === "stringArray") {
658
+ filterBarActions.setArrayValue(filter.filterKey, newValue);
659
+ }
660
+ else if (filter.type === "dateRange") {
661
+ filterBarActions.setDateRange(filter.filterKey, newValue);
662
+ }
663
+ else if (filter.type === "boundingBox") {
664
+ filterBarActions.setBoundingBox(newValue);
665
+ }
666
+ else if (filter.type === "valueName") {
667
+ filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
668
+ }
669
+ else if (filter.type === "minMax") {
670
+ filterBarActions.setMinMaxValue(filter.filterKey, newValue);
671
+ }
672
+ else if (filter.type === "boolean") {
673
+ filterBarActions.setBooleanValue(filter.filterKey, newValue);
674
+ }
675
+ else {
676
+ filterBarActions.setNumberValue(filter.filterKey, newValue);
677
+ }
678
+ };
679
+ const text = filterText();
680
+ const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
681
+ const showDirectly = filter.showDirectly || false;
682
+ return showDirectly ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: filter.component({
683
+ filterDefinition: filter,
684
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
685
+ value: values,
686
+ setValue,
687
+ filterBarActions,
688
+ filterState,
689
+ }) })) : (jsxRuntime.jsx(reactFilterComponents.Filter, { activeLabel: activeFilterText, dataTestId: `${filter.filterKey}-filter-button`, isActive: filterText().length > 0, title: filter.title, withStickyHeader: true, children: filter.component({
690
+ filterDefinition: filter,
691
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
692
+ value: values,
693
+ setValue,
694
+ filterBarActions,
695
+ filterState,
696
+ }) }));
697
+ };
698
+
699
+ /**
700
+ * ResetFiltersButton is a React component that provides a button for resetting filters.
701
+ *
702
+ * @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
703
+ */
704
+ const ResetFiltersButton = ({ filtersHaveBeenApplied, resetFiltersToInitialState, }) => {
705
+ const [t] = useTranslation();
706
+ if (!filtersHaveBeenApplied) {
707
+ return null;
708
+ }
709
+ return (jsxRuntime.jsx(reactComponents.Button, { dataTestId: "reset-filters-button", onClick: () => {
710
+ resetFiltersToInitialState();
711
+ }, size: "small", variant: "ghost-neutral", children: t("filtersBar.resetFilters") }));
712
+ };
713
+
714
+ const MAX_RECENT_SEARCHES = 20;
715
+ /**
716
+ *
717
+ */
718
+ const SearchFilterInline = ({ filterBarActions, filterState, filter, className, dataTestId, }) => {
719
+ const { logEvent } = reactCoreHooks.useAnalytics(FilterEvents);
720
+ const searchElementRef = react.useRef(null);
721
+ const geometry = reactComponents.useGeometry(searchElementRef);
722
+ const filterValue = (filter === null || filter === void 0 ? void 0 : filter.filterKey) ? String(filterState.values[filter.filterKey]) : undefined;
723
+ const [searchString, setSearchString] = react.useState(filterValue !== null && filterValue !== void 0 ? filterValue : "");
724
+ const previousFilterValue = reactComponents.usePrevious(filterValue);
725
+ reactComponents.useDebounce(searchString, 500, "both", debouncedSearchString => {
726
+ if (filterValue === debouncedSearchString) {
727
+ return;
728
+ }
729
+ apply({ value: debouncedSearchString, setRecent: false });
730
+ });
731
+ react.useEffect(() => {
732
+ // Catch a change in filter value from outside the component and update the search string to match
733
+ if (filterValue === searchString || previousFilterValue === filterValue) {
734
+ return;
735
+ }
736
+ filterValue !== undefined && setSearchString(filterValue);
737
+ }, [filterValue, previousFilterValue, searchString]);
738
+ const [t] = useTranslation();
739
+ const [recentSearches, setRecentSearches] = reactComponents.useLocalStorage({
740
+ defaultState: [],
741
+ key: "free-text-filter-recent-searches",
742
+ schema: zod.z.array(zod.z.string()),
743
+ });
744
+ const apply = react.useCallback(({ value, wasPreviousSearch = false, setRecent = true, }) => {
745
+ if (setRecent !== false) {
746
+ setRecentSearches(prev => {
747
+ const noDuplicatesRecent = prev.filter(r => r !== value);
748
+ const cappedLengthRecent = noDuplicatesRecent.slice(0, MAX_RECENT_SEARCHES - 1);
749
+ return [value, ...cappedLengthRecent];
750
+ });
751
+ }
752
+ logEvent("Filters Applied - V2", {
753
+ type: "free-text-search",
754
+ value,
755
+ additionalProperties: {
756
+ wasPreviousSearch: Boolean(wasPreviousSearch),
757
+ },
758
+ });
759
+ (filter === null || filter === void 0 ? void 0 : filter.filterKey) && filterBarActions.setStringValue(filter.filterKey, value);
760
+ }, [filter === null || filter === void 0 ? void 0 : filter.filterKey, filterBarActions, logEvent, setRecentSearches]);
761
+ return (jsxRuntime.jsxs(reactComponents.Popover, { activation: defaultActivation => ({ ...defaultActivation, keyboardHandlers: false }), dataTestId: dataTestId ? `${dataTestId}-popover` : undefined, placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactFormComponents.Search
762
+ //TODO: [Buss] remove height restruction once baseinput sixes matches buttons
763
+ , {
764
+ //TODO: [Buss] remove height restruction once baseinput sixes matches buttons
765
+ className: tailwindMerge.twMerge(className, "h-7"), fieldSize: "small", onChange: e => {
766
+ const parsedValue = zod.z.string().safeParse(e.target.value);
767
+ parsedValue.success ? setSearchString(parsedValue.data) : setSearchString("");
768
+ }, onClear: () => setSearchString(""), onKeyDown: e => {
769
+ if (e.key === "Enter") {
770
+ apply({ value: searchString });
771
+ }
772
+ }, ref: searchElementRef, value: searchString }) }), recentSearches.length > 0 ? (jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsx("div", { style: { minWidth: geometry.width }, children: jsxRuntime.jsxs(reactComponents.MenuList, { className: "overflow-hidden", children: [jsxRuntime.jsx(reactFilterComponents.FilterBody, { className: "max-w-none p-1", limitSize: true, children: jsxRuntime.jsx("ul", { className: "grid h-full gap-0.5", children: recentSearches.map((option, index) => (jsxRuntime.jsx(reactComponents.MenuItem, { className: "max-h-fit overflow-hidden", id: "free-text-search-filter-inline", label: option, onClick: () => {
773
+ apply({ value: option, wasPreviousSearch: true });
774
+ setRecentSearches(prev => {
775
+ const noDuplicatesRecent = prev.filter(r => r !== option);
776
+ const cappedLengthRecent = noDuplicatesRecent.slice(0, MAX_RECENT_SEARCHES - 1);
777
+ return [option, ...cappedLengthRecent];
778
+ });
779
+ setSearchString(option);
780
+ }, selected: option === filterValue, size: "small", suffix: jsxRuntime.jsx(reactComponents.Tooltip, { label: t("filters.shared.clear"), children: jsxRuntime.jsx(reactComponents.IconButton, { circular: true, icon: jsxRuntime.jsx(reactComponents.Icon, { name: "Trash", size: "small" }), onClick: e => {
781
+ e.stopPropagation();
782
+ setRecentSearches(prev => prev.filter(r => r !== option));
783
+ }, size: "small", variant: "ghost-neutral" }) }) }, `${option}-${index}`))) }) }), jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between gap-x-2 border-t-2 border-slate-300 px-2 py-0.5", children: [jsxRuntime.jsx(reactComponents.Text, { className: "py-0.5", size: "small", subtle: true, children: t("filters.shared.recentSearches") }), recentSearches.length > 1 ? (jsxRuntime.jsx(reactComponents.Button, { onClick: () => setRecentSearches([]), size: "small", variant: "ghost", children: t("filters.shared.clearAll") })) : null] })] }) }) })) : null] }));
784
+ };
785
+
786
+ /**
787
+ * StarredFiltersMenu is a React component that displays a menu of starred filters within a filter bar.
788
+ *
789
+ * @returns {JSX.Element} - Returns the StarredFiltersMenu component.
790
+ */
791
+ const StarredFiltersMenu = ({ filterBarDefinition, updateStarredFilters, starredFilterKeys, hiddenFilters = [], className, dataTestId, }) => {
792
+ const [t] = useTranslation();
793
+ const { logEvent } = reactCoreHooks.useAnalytics(FilterEvents);
794
+ const hideInStarredMenu = react.useMemo(() => {
795
+ return sharedUtils.objectValues(filterBarDefinition)
796
+ .map(filter => {
797
+ const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true;
798
+ const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
799
+ return !showInStarredMenu || !showInFilterBar ? filter.filterKey : null;
800
+ })
801
+ .filter(sharedUtils.truthy);
802
+ }, [filterBarDefinition]);
803
+ const { filtersGrouped } = useStarredGroupFilters(sharedUtils.objectValues(filterBarDefinition), [
804
+ ...hideInStarredMenu,
805
+ ...hiddenFilters,
806
+ ]);
807
+ return (jsxRuntime.jsxs(reactComponents.Popover, { placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactComponents.IconButton, { className: className, dataTestId: dataTestId !== null && dataTestId !== void 0 ? dataTestId : "starred-filters-menu", icon: jsxRuntime.jsx(reactComponents.Icon, { name: "Star", size: "small" }), size: "small", title: t("filtersBar.starredFiltersTitle"), variant: "secondary" }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsx(reactComponents.MenuList, { className: "overflow-hidden", dataTestId: "starred-filters-menu-popover", children: jsxRuntime.jsx("div", { className: "max-h-[55vh] w-72 overflow-y-auto", children: filtersGrouped.map((group, idx) => {
808
+ const isLast = idx === filtersGrouped.length - 1;
809
+ return (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsx("div", { className: "mb-1 mt-2 flex px-2", children: jsxRuntime.jsx(reactComponents.Text, { dataTestId: "starred-filters-group-title", size: "small", subtle: true, uppercase: true, weight: "bold", children: group.key }) }), group.filters.map(filter => {
810
+ return (jsxRuntime.jsxs("div", { className: "flex w-full cursor-pointer items-center justify-between rounded-sm px-2 py-0 transition hover:bg-neutral-50", "data-testid": `${filter.filterKey}-star-filter`, onClick: () => {
811
+ updateStarredFilters(filter.filterKey);
812
+ logEvent("Starring Filter - Toggled", {
813
+ type: filter.filterKey,
814
+ });
815
+ }, children: [jsxRuntime.jsx(reactComponents.Text, { weight: "thick", children: filter.title }), jsxRuntime.jsx(reactComponents.StarButton, { onClick: e => {
816
+ e.preventDefault();
817
+ e.stopPropagation();
818
+ updateStarredFilters(filter.filterKey);
819
+ logEvent("Starring Filter - Toggled", {
820
+ type: filter.filterKey,
821
+ });
822
+ }, starred: starredFilterKeys.includes(filter.filterKey) })] }, filter.filterKey));
823
+ }), !isLast && jsxRuntime.jsx("div", { className: "mt-2 h-[1px] w-full bg-neutral-300" })] }, group.key));
824
+ }) }) }) })] }));
825
+ };
826
+
827
+ /**
828
+ * StarredFilters is a React component that displays a list of starred filters based on the provided filter bar configuration.
829
+ *
830
+ * @template TFilterBarDefinition - The type representing the filter bar definition.
831
+ * @returns {JSX.Element} - Returns the StarredFilters component.
832
+ */
833
+ const StarredFilters = ({ filterBarDefinition, filterBarConfig, disableCollapse, hiddenFilters = [], }) => {
834
+ const [t] = useTranslation();
835
+ const { isLg } = reactComponents.useViewportSize();
836
+ return (jsxRuntime.jsxs("div", { className: cvaFiltersAndSearchContainer({ collapsed: !(isLg || disableCollapse) }), children: [isLg || disableCollapse ? (jsxRuntime.jsx(FilterSelection, { filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters })) : (jsxRuntime.jsxs(reactComponents.Popover, { placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx("div", { children: jsxRuntime.jsxs(reactComponents.Tooltip, { label: jsxRuntime.jsx(FilterButtonTooltipLabel, { filterBarConfig: filterBarConfig }), children: [jsxRuntime.jsx(reactComponents.Button, { className: "@xs:flex hidden", prefix: jsxRuntime.jsx(reactComponents.Icon, { color: filterBarConfig.appliedFilters.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", variant: "secondary", children: t("filtersBar.filtersHeading") }), jsxRuntime.jsx(reactComponents.IconButton, { className: "@xs:hidden", icon: jsxRuntime.jsx(reactComponents.Icon, { color: filterBarConfig.appliedFilters.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", variant: "secondary" })] }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { cellPadding: 100, children: jsxRuntime.jsx(reactComponents.Card, { children: jsxRuntime.jsx(reactComponents.CardBody, { children: jsxRuntime.jsx(FilterSelection, { filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }) }) }) })] })), filterBarDefinition.search ? (jsxRuntime.jsx("div", { className: "flex w-full", children: jsxRuntime.jsx(SearchFilterInline, { className: "w-full max-w-80", filter: filterBarDefinition.search, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters } }, "filter-search") })) : null] }));
837
+ };
838
+ const FilterSelection = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], className, }) => {
839
+ const hideInFilterBar = react.useMemo(() => {
840
+ return sharedUtils.objectValues(filterBarDefinition)
841
+ .map(filter => {
842
+ const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
843
+ return !showInFilterBar ? filter.filterKey : null;
844
+ })
845
+ .filter(sharedUtils.truthy);
846
+ }, [filterBarDefinition]);
847
+ const { filtersGrouped } = useStarredGroupFilters(sharedUtils.objectValues(filterBarDefinition), [
848
+ ...hideInFilterBar,
849
+ ...hiddenFilters,
850
+ ]);
851
+ return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge([className, "flex-wrap", "items-center", "gap-3", "flex"]), "data-testid": "starred-filters", children: [jsxRuntime.jsx(StarredFiltersMenu, { filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, starredFilterKeys: filterBarConfig.starredFilterKeys, updateStarredFilters: filterBarConfig.updateStarredFilters }), filterBarConfig.starredFilterKeys.length > 0 ? jsxRuntime.jsx("div", { className: "h-4 w-[1px] bg-slate-300" }) : null, filtersGrouped.map(group => group.filters
852
+ .filter(filter => !hiddenFilters.includes(filter.filterKey))
853
+ .map(filter => {
854
+ const showMenuAnyway = filterBarConfig.filterHasChanged(filter.filterKey) &&
855
+ filter.showInStarredMenu &&
856
+ !filter.showInStarredMenu();
857
+ if (filter.filterKey !== "search" &&
858
+ (filterBarConfig.starredFilterKeys.includes(filter.filterKey) ||
859
+ showMenuAnyway)) {
860
+ return (jsxRuntime.jsx(react.Fragment, { children: jsxRuntime.jsx(Filter, { filter: filter, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters } }, `filter-${filter.filterKey}`) }, filter.filterKey));
861
+ }
862
+ return null;
863
+ })), jsxRuntime.jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilters.length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })] }));
864
+ };
865
+ const cvaFiltersAndSearchContainer = cssClassVarianceUtilities.cvaMerge(["grid", "gap-2", "w-full"], {
866
+ variants: {
867
+ collapsed: {
868
+ true: ["grid-cols-min-fr"],
869
+ false: ["grid-cols-[minmax(0,1fr)_300px]"],
870
+ },
871
+ },
872
+ defaultVariants: {
873
+ collapsed: false,
874
+ },
875
+ });
876
+ const FilterButtonTooltipLabel = ({ filterBarConfig, }) => {
877
+ const [t] = useTranslation();
878
+ switch (filterBarConfig.appliedFilters.length) {
879
+ case 0:
880
+ return t("filtersBar.filtersHeading");
881
+ case 1:
882
+ return filterBarConfig.appliedFilters[0]
883
+ ? t("filtersBar.appliedFiltersTooltip.singular", {
884
+ filterName: filterBarConfig.getFilterTitle(filterBarConfig.appliedFilters[0]),
885
+ })
886
+ : null; // should never happen though
887
+ default:
888
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [t("filtersBar.appliedFiltersTooltip.plural", { count: filterBarConfig.appliedFilters.length }), jsxRuntime.jsx("ul", { className: "list-inside", children: filterBarConfig.appliedFilters.map((appliedFilterKey, index) => (jsxRuntime.jsx("li", { className: "list-disc", children: filterBarConfig.getFilterTitle(appliedFilterKey) }, index))) })] }));
889
+ }
890
+ };
891
+
892
+ /**
893
+ * The FilterBar component serves as a wrapper for managing filters.
894
+ */
895
+ const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, disableCollapse = false, }) => {
896
+ return (jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("flex", className), "data-testid": `${filterBarConfig.name}-filterbar`, children: jsxRuntime.jsx(StarredFilters, { disableCollapse: disableCollapse, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }) }));
897
+ };
898
+
899
+ // Can't import jest.fn so must define a function that does nothing but mimics the jest.fn
900
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
901
+ const doNothing = (args) => { };
902
+ const mockFilterBar = {
903
+ filterBarConfig: {
904
+ isFilterIncludedByKey: doNothing,
905
+ appliedFilters: [],
906
+ arrayIncludesValue: doNothing,
907
+ getFilterTitle: doNothing,
908
+ getFilterBarName: doNothing,
909
+ initialState: { customerType: [] },
910
+ name: "test",
911
+ objectArrayIncludesValue: doNothing,
912
+ resetFiltersToInitialState: doNothing,
913
+ resetIndividualFilterToInitialState: doNothing,
914
+ updateStarredFilters: doNothing,
915
+ setArrayObjectValue: doNothing,
916
+ setArrayValue: doNothing,
917
+ setBooleanValue: doNothing,
918
+ setBoundingBox: doNothing,
919
+ setDateRange: doNothing,
920
+ setMinMaxValue: doNothing,
921
+ setNumberValue: doNothing,
922
+ setStringValue: doNothing,
923
+ toggleArrayObjectValue: doNothing,
924
+ toggleArrayValue: doNothing,
925
+ values: { customerType: [] },
926
+ filterHasChanged: doNothing,
927
+ starredFilterKeys: [],
928
+ getValuesByKey: doNothing,
929
+ setters: {
930
+ setCriticality: doNothing,
931
+ setServicePlan: doNothing,
932
+ },
933
+ },
934
+ filterBarDefinition: {},
935
+ };
936
+
797
937
  /**
798
938
  * A helper function that returns a default value based on the filter type.
799
939
  *
@@ -1042,9 +1182,13 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1042
1182
  }, [filterBarConfig, name]);
1043
1183
  const filterMapGetter = react.useMemo(() => {
1044
1184
  return {
1045
- getFilterName: () => {
1185
+ getFilterBarName: () => {
1046
1186
  return filterBarConfig.name;
1047
1187
  },
1188
+ getFilterTitle(key) {
1189
+ var _a, _b;
1190
+ return (_b = (_a = filterBarDefinition[key]) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : key;
1191
+ },
1048
1192
  arrayIncludesValue(key, value) {
1049
1193
  const filter = filterBarConfig.values[key];
1050
1194
  return (filter === null || filter === void 0 ? void 0 : filter.includes(value)) || false;
@@ -1056,15 +1200,18 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1056
1200
  const values = filterBarConfig.values[key];
1057
1201
  return Boolean(values);
1058
1202
  },
1059
- get filtersHaveBeenApplied() {
1203
+ get appliedFilters() {
1060
1204
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1061
1205
  const initialStateValues = filterBarConfig.initialState || {};
1062
1206
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1063
1207
  const currentFilters = filterBarConfig.values || {};
1064
1208
  // parse and stringify to remove empty references before comparing
1209
+ // eslint-disable-next-line local-rules/no-typescript-assertion
1065
1210
  const currentValuesWithoutEmptyRefs = JSON.parse(JSON.stringify(currentFilters));
1211
+ // eslint-disable-next-line local-rules/no-typescript-assertion
1066
1212
  const initialStateValuesWithoutEmptyRefs = JSON.parse(JSON.stringify(initialStateValues));
1067
- return !dequal.dequal(currentValuesWithoutEmptyRefs, initialStateValuesWithoutEmptyRefs);
1213
+ return getKeysOfUnequalProperties(currentValuesWithoutEmptyRefs, initialStateValuesWithoutEmptyRefs).map(key => key.toString() // TODO: FilterName type should be PropertyKey instead of string to avoid this .toString() bs but out-of-scope for this round of improvements
1214
+ );
1068
1215
  },
1069
1216
  filterHasChanged(key) {
1070
1217
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -1081,7 +1228,7 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1081
1228
  return (filter === null || filter === void 0 ? void 0 : filter.find(f => f.value === value)) !== undefined || false;
1082
1229
  },
1083
1230
  };
1084
- }, [filterBarConfig.initialState, filterBarConfig.name, filterBarConfig.values]);
1231
+ }, [filterBarConfig.initialState, filterBarConfig.name, filterBarConfig.values, filterBarDefinition]);
1085
1232
  const filterMapActions = react.useMemo(() => {
1086
1233
  // Reset an individual filter to its initial state
1087
1234
  const resetIndividualFilterToInitialState = (key) => {
@@ -1294,6 +1441,14 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
1294
1441
  };
1295
1442
  }, [filterBarConfig, filterMapActions, filterMapGetter, filterBarDefinition]);
1296
1443
  };
1444
+ const getKeysOfUnequalProperties = (obj1, obj2) => {
1445
+ return reduce(obj1, (result, value, key) => {
1446
+ if (!isEqual(value, obj2[key])) {
1447
+ result.push(key);
1448
+ }
1449
+ return result;
1450
+ }, []);
1451
+ };
1297
1452
 
1298
1453
  /**
1299
1454
  * Merges additional filters into an existing filter bar definition.
@@ -1334,6 +1489,7 @@ exports.isMinMaxFilterValue = isMinMaxFilterValue;
1334
1489
  exports.isStringArrayFilterValue = isStringArrayFilterValue;
1335
1490
  exports.isValueNameFilterValue = isValueNameFilterValue;
1336
1491
  exports.mergeFilters = mergeFilters;
1492
+ exports.mockFilterBar = mockFilterBar;
1337
1493
  exports.toggleFilterValue = toggleFilterValue;
1338
1494
  exports.useFilterBar = useFilterBar;
1339
1495
  exports.validateFilter = validateFilter;