@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.esm.js CHANGED
@@ -1,8 +1,8 @@
1
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
1
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
2
  import { registerTranslations, useNamespaceTranslation } from '@trackunit/i18n-library-translation';
3
- import { VirtualizedList, Text, Button, Card, CardBody, useViewportBreakpoints, Popover, PopoverTrigger, Tooltip, Icon, PopoverContent, IconButton, MenuList } from '@trackunit/react-components';
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 ? (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] })) : null] }));
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, title, allowShowFiltersDirectly = 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
- * Custom hook for managing a filter bar's actions .
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.210",
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.134",
18
- "@trackunit/react-core-hooks": "1.3.136",
19
- "@trackunit/react-filter-components": "1.3.172",
20
- "@trackunit/react-date-and-time-components": "1.3.175",
21
- "@trackunit/shared-utils": "1.5.126",
22
- "@trackunit/react-form-components": "1.3.173",
23
- "@trackunit/react-core-contexts-api": "1.4.132",
24
- "@trackunit/geo-json-utils": "1.3.126",
25
- "@trackunit/i18n-library-translation": "1.3.143",
26
- "@trackunit/css-class-variance-utilities": "1.3.126",
27
- "@trackunit/react-components": "1.4.152",
28
- "@trackunit/react-test-setup": "1.0.16"
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",