@evergis/react 4.0.76 → 4.0.78

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/dist/react.esm.js CHANGED
@@ -11444,9 +11444,12 @@ function getIconFromAttribute(option, iconAttribute) {
11444
11444
 
11445
11445
  const MAX_ANCESTOR_DEPTH = 50;
11446
11446
  const buildLevelOneCondition = (attributeLevel) => `${attributeLevel} = 1`;
11447
- const buildChildrenCondition = (attributeParentValue, parentId) => `${attributeParentValue} = ${formatConditionValue({ value: parentId })}`;
11447
+ const buildChildrenCondition = (attributeParentName, parentName) => `${attributeParentName} = ${formatConditionValue({ value: parentName })}`;
11448
11448
  const buildSearchCondition = (attributeAlias, query) => `${attributeAlias} LIKE '%${query.replace(/'/g, "''")}%'`;
11449
- const buildIdsCondition = (attributeValue, ids) => `${attributeValue} in ${formatConditionValue({ value: ids })}`;
11449
+ /** Запрос пачки записей по их именам (`attributeName`) для достройки предков. */
11450
+ const buildNamesCondition = (attributeName, names) => `${attributeName} in ${formatConditionValue({ value: names })}`;
11451
+ /** Запрос пачки записей по их значениям (`attributeValue`) — для восстановления `defaultValue`. */
11452
+ const buildValuesCondition = (attributeValue, values) => `${attributeValue} in ${formatConditionValue({ value: values })}`;
11450
11453
  /** Группирует выбранные id по уровню в объектное значение фильтра `{ l{N}: [ids] }`. */
11451
11454
  const buildFilterValue = (selected) => {
11452
11455
  const result = {};
@@ -11471,54 +11474,62 @@ const parseFilterValue = (value) => {
11471
11474
  });
11472
11475
  return result;
11473
11476
  };
11477
+ /** Стабильная сериализация выбора для сравнения (порядок не важен). */
11478
+ const serializeSelection = (selected) => [...selected.entries()]
11479
+ .map(([id, level]) => `${id}:${level}`)
11480
+ .sort()
11481
+ .join(",");
11474
11482
  /**
11475
- * Дозапрашивает цепочку предков вверх по `parentId` уровень за уровнем (батч `id in (...)`),
11476
- * пока не достигнет уровня 1. Возвращает исходные узлы вместе с предками.
11483
+ * Дозапрашивает цепочку предков вверх по `parentName` уровень за уровнем (батч `attributeName in
11484
+ * (...)`), пока не достигнет уровня 1. Связь идёт по именам (`name`/`parentName`), т.к. `node.id`
11485
+ * хранит значение (`attributeValue`). Возвращает исходные узлы вместе с предками.
11477
11486
  */
11478
- const collectAncestors = async (nodes, loadByIds) => {
11479
- const byId = new Map();
11480
- nodes.forEach(node => byId.set(node.id, node));
11487
+ const collectAncestors = async (nodes, loadByNames) => {
11488
+ const byName = new Map();
11489
+ nodes.forEach(node => byName.set(node.data.name, node));
11481
11490
  let frontier = nodes;
11482
11491
  for (let depth = 0; depth < MAX_ANCESTOR_DEPTH; depth++) {
11483
11492
  const missingParents = new Set();
11484
11493
  frontier.forEach(node => {
11485
- const { parentId, level } = node.data ?? {};
11486
- if (parentId != null && level > 1 && !byId.has(parentId)) {
11487
- missingParents.add(parentId);
11494
+ const { parentName, level } = node.data ?? {};
11495
+ if (parentName != null && level > 1 && !byName.has(parentName)) {
11496
+ missingParents.add(parentName);
11488
11497
  }
11489
11498
  });
11490
11499
  if (!missingParents.size) {
11491
11500
  break;
11492
11501
  }
11493
- const parents = await loadByIds([...missingParents]);
11502
+ const parents = await loadByNames([...missingParents]);
11494
11503
  if (!parents.length) {
11495
11504
  break;
11496
11505
  }
11497
- parents.forEach(parent => byId.set(parent.id, parent));
11506
+ parents.forEach(parent => byName.set(parent.data.name, parent));
11498
11507
  frontier = parents;
11499
11508
  }
11500
- return [...byId.values()];
11509
+ return [...byName.values()];
11501
11510
  };
11502
11511
  /**
11503
- * Собирает плоский набор узлов в дерево по `parentId`. Узлы с подгруженными потомками
11504
- * раскрываются; листья-папки без потомков получают `children = null` (ленивая подгрузка).
11512
+ * Собирает плоский набор узлов в дерево по связи `parentName` ↔ `name`. Узлы с подгруженными
11513
+ * потомками раскрываются (`expanded` хранит `node.id` = значение); листья-папки без потомков
11514
+ * получают `children = null` (ленивая подгрузка).
11505
11515
  */
11506
11516
  const assembleTree = (nodes) => {
11507
- const nodesById = new Map();
11508
- nodes.forEach(node => nodesById.set(node.id, node));
11517
+ const nodesByName = new Map();
11518
+ nodes.forEach(node => nodesByName.set(node.data.name, node));
11509
11519
  const childrenMap = new Map();
11510
- nodesById.forEach(node => {
11511
- const parentId = node.data?.parentId;
11512
- if (parentId != null && nodesById.has(parentId)) {
11513
- const siblings = childrenMap.get(parentId) ?? [];
11520
+ nodesByName.forEach(node => {
11521
+ const parentName = node.data?.parentName;
11522
+ if (parentName != null && nodesByName.has(parentName)) {
11523
+ const siblings = childrenMap.get(parentName) ?? [];
11514
11524
  siblings.push(node);
11515
- childrenMap.set(parentId, siblings);
11525
+ childrenMap.set(parentName, siblings);
11516
11526
  }
11517
11527
  });
11518
11528
  const roots = [];
11519
11529
  const expanded = [];
11520
- nodesById.forEach(node => {
11521
- const kids = childrenMap.get(node.id);
11530
+ nodesByName.forEach(node => {
11531
+ const { name, parentName } = node.data;
11532
+ const kids = childrenMap.get(name);
11522
11533
  if (kids?.length) {
11523
11534
  node.children = kids;
11524
11535
  expanded.push(node.id);
@@ -11526,8 +11537,7 @@ const assembleTree = (nodes) => {
11526
11537
  else if (node.isLeaf) {
11527
11538
  node.children = null;
11528
11539
  }
11529
- const parentId = node.data?.parentId;
11530
- if (parentId == null || !nodesById.has(parentId)) {
11540
+ if (parentName == null || !nodesByName.has(parentName)) {
11531
11541
  roots.push(node);
11532
11542
  }
11533
11543
  });
@@ -11538,22 +11548,24 @@ const useTreeFilterData = (type, filterName) => {
11538
11548
  const { api } = useGlobalContext();
11539
11549
  const { currentPage } = useWidgetPage(type);
11540
11550
  const configFilter = useMemo(() => getConfigFilter(filterName, currentPage?.filters), [filterName, currentPage?.filters]);
11541
- const { relatedDataSource, attributeValue = DEFAULT_ATTRIBUTE_NAME, attributeAlias = DEFAULT_ATTRIBUTE_NAME, attributeParentValue, attributeLevel, attributeHasChildren, limit = DEFAULT_DATA_SOURCE_LIMIT, } = configFilter ?? {};
11551
+ const { relatedDataSource, attributeName = DEFAULT_ATTRIBUTE_NAME, attributeValue, attributeAlias = DEFAULT_ATTRIBUTE_NAME, attributeParentName, attributeLevel, attributeHasChildren, limit = DEFAULT_DATA_SOURCE_LIMIT, } = configFilter ?? {};
11552
+ const valueAttribute = attributeValue ?? attributeName;
11542
11553
  const layerName = useMemo(() => currentPage?.dataSources?.find(({ name }) => name === relatedDataSource)?.layerName, [currentPage?.dataSources, relatedDataSource]);
11543
11554
  const mapNode = useCallback(({ properties }) => {
11544
11555
  const hasChildren = attributeHasChildren ? !!properties[attributeHasChildren] : false;
11545
11556
  const data = {
11546
- parentId: attributeParentValue ? properties[attributeParentValue] ?? null : null,
11557
+ name: properties[attributeName],
11558
+ parentName: attributeParentName ? properties[attributeParentName] ?? null : null,
11547
11559
  level: attributeLevel ? Number(properties[attributeLevel]) : 1,
11548
11560
  };
11549
11561
  return {
11550
- id: properties[attributeValue],
11562
+ id: properties[valueAttribute],
11551
11563
  text: String(properties[attributeAlias] ?? ""),
11552
11564
  isLeaf: hasChildren,
11553
11565
  children: hasChildren ? null : undefined,
11554
11566
  data,
11555
11567
  };
11556
- }, [attributeValue, attributeAlias, attributeParentValue, attributeLevel, attributeHasChildren]);
11568
+ }, [valueAttribute, attributeName, attributeAlias, attributeParentName, attributeLevel, attributeHasChildren]);
11557
11569
  const runQuery = useCallback(async (condition) => {
11558
11570
  if (!layerName) {
11559
11571
  return [];
@@ -11569,29 +11581,36 @@ const useTreeFilterData = (type, filterName) => {
11569
11581
  return features.map(mapNode);
11570
11582
  }, [attributeLevel, runQuery, mapNode]);
11571
11583
  const loadChildren = useCallback(async (item) => {
11572
- if (!attributeParentValue) {
11584
+ if (!attributeParentName) {
11573
11585
  return { children: [] };
11574
11586
  }
11575
- const features = await runQuery(buildChildrenCondition(attributeParentValue, item.id));
11587
+ const features = await runQuery(buildChildrenCondition(attributeParentName, item.data.name));
11576
11588
  return { children: features.map(mapNode) };
11577
- }, [attributeParentValue, runQuery, mapNode]);
11589
+ }, [attributeParentName, runQuery, mapNode]);
11578
11590
  const searchNodes = useCallback(async (query) => {
11579
11591
  const features = await runQuery(buildSearchCondition(attributeAlias, query));
11580
11592
  return features.map(mapNode);
11581
11593
  }, [attributeAlias, runQuery, mapNode]);
11582
- const loadByIds = useCallback(async (ids) => {
11583
- if (!ids.length) {
11594
+ const loadByNames = useCallback(async (names) => {
11595
+ if (!names.length) {
11584
11596
  return [];
11585
11597
  }
11586
- const features = await runQuery(buildIdsCondition(attributeValue, ids));
11598
+ const features = await runQuery(buildNamesCondition(attributeName, names));
11587
11599
  return features.map(mapNode);
11588
- }, [attributeValue, runQuery, mapNode]);
11589
- return { configFilter, layerName, loadLevelOne, loadChildren, searchNodes, loadByIds };
11600
+ }, [attributeName, runQuery, mapNode]);
11601
+ const loadByValues = useCallback(async (values) => {
11602
+ if (!values.length) {
11603
+ return [];
11604
+ }
11605
+ const features = await runQuery(buildValuesCondition(valueAttribute, values));
11606
+ return features.map(mapNode);
11607
+ }, [valueAttribute, runQuery, mapNode]);
11608
+ return { configFilter, layerName, loadLevelOne, loadChildren, searchNodes, loadByNames, loadByValues };
11590
11609
  };
11591
11610
 
11592
11611
  const SEARCH_MIN_LENGTH = 3;
11593
11612
  const SEARCH_DEBOUNCE = 300;
11594
- const useTreeFilterSearch = ({ searchNodes, loadByIds }) => {
11613
+ const useTreeFilterSearch = ({ searchNodes, loadByNames }) => {
11595
11614
  const [searchValue, setSearchValue] = useState("");
11596
11615
  const [searchData, setSearchData] = useState([]);
11597
11616
  const [searchExpanded, setSearchExpanded] = useState([]);
@@ -11604,7 +11623,7 @@ const useTreeFilterSearch = ({ searchNodes, loadByIds }) => {
11604
11623
  setSearching(true);
11605
11624
  try {
11606
11625
  const results = await searchNodes(query);
11607
- const withAncestors = await collectAncestors(results, loadByIds);
11626
+ const withAncestors = await collectAncestors(results, loadByNames);
11608
11627
  const { roots, expanded } = assembleTree(withAncestors);
11609
11628
  if (id !== requestId.current) {
11610
11629
  return;
@@ -11617,7 +11636,7 @@ const useTreeFilterSearch = ({ searchNodes, loadByIds }) => {
11617
11636
  setSearching(false);
11618
11637
  }
11619
11638
  }
11620
- }, [searchNodes, loadByIds]);
11639
+ }, [searchNodes, loadByNames]);
11621
11640
  const clearSearchState = useCallback(() => {
11622
11641
  requestId.current++;
11623
11642
  setSearchData([]);
@@ -11661,6 +11680,9 @@ const useTreeFilterSelection = (type, filterName, configFilter) => {
11661
11680
  const [selected, setSelected] = useState(() => parseFilterValue(configFilter?.defaultValue));
11662
11681
  const [selectedLabel, setSelectedLabel] = useState();
11663
11682
  const selectedIds = useMemo(() => new Set(selected.keys()), [selected]);
11683
+ /** Уже применённый (в redux) выбор — база для `dirty`. */
11684
+ const committed = useMemo(() => parseFilterValue((filters[filterName]?.value ?? configFilter?.defaultValue)), [filters, filterName, configFilter?.defaultValue]);
11685
+ const dirty = useMemo(() => serializeSelection(selected) !== serializeSelection(committed), [selected, committed]);
11664
11686
  const commit = useCallback((next) => {
11665
11687
  setSelected(next);
11666
11688
  changeFilters({ [filterName]: { value: buildFilterValue(next) } }, configFilter?.resetFilters);
@@ -11681,8 +11703,17 @@ const useTreeFilterSelection = (type, filterName, configFilter) => {
11681
11703
  }
11682
11704
  next.set(item.id, level);
11683
11705
  }
11684
- commit(next);
11706
+ // Мультивыбор не коммитит в redux — сохранение по кнопке (onSave). Single — сразу.
11707
+ if (multiSelect) {
11708
+ setSelected(next);
11709
+ }
11710
+ else {
11711
+ commit(next);
11712
+ }
11685
11713
  }, [selected, commit]);
11714
+ const onSave = useCallback(() => {
11715
+ changeFilters({ [filterName]: { value: buildFilterValue(selected) } }, configFilter?.resetFilters);
11716
+ }, [changeFilters, filterName, selected, configFilter?.resetFilters]);
11686
11717
  const onReset = useCallback(() => {
11687
11718
  setSelectedLabel(undefined);
11688
11719
  commit(new Map());
@@ -11697,10 +11728,10 @@ const useTreeFilterSelection = (type, filterName, configFilter) => {
11697
11728
  changeFilters({ [filterName]: { value: buildFilterValue(selected) } });
11698
11729
  }
11699
11730
  }, [filters, filterName, changeFilters, selected]);
11700
- return { selected, selectedIds, selectedLabel, onToggleSelect, onReset };
11731
+ return { selected, selectedIds, selectedLabel, dirty, onToggleSelect, onReset, onSave };
11701
11732
  };
11702
11733
 
11703
- const useTreeFilterTree = ({ selected, loadLevelOne, loadByIds }) => {
11734
+ const useTreeFilterTree = ({ selected, loadLevelOne, loadByValues, loadByNames }) => {
11704
11735
  const [treeData, setTreeData] = useState([]);
11705
11736
  const [treeExpanded, setTreeExpanded] = useState([]);
11706
11737
  const [loading, setLoading] = useState(false);
@@ -11720,8 +11751,8 @@ const useTreeFilterTree = ({ selected, loadLevelOne, loadByIds }) => {
11720
11751
  setTreeData(roots);
11721
11752
  return;
11722
11753
  }
11723
- const selectedNodes = await loadByIds(preselected);
11724
- const withAncestors = await collectAncestors([...roots, ...selectedNodes], loadByIds);
11754
+ const selectedNodes = await loadByValues(preselected);
11755
+ const withAncestors = await collectAncestors([...roots, ...selectedNodes], loadByNames);
11725
11756
  const { roots: assembled, expanded } = assembleTree(withAncestors);
11726
11757
  setTreeData(assembled);
11727
11758
  setTreeExpanded(expanded);
@@ -11729,20 +11760,21 @@ const useTreeFilterTree = ({ selected, loadLevelOne, loadByIds }) => {
11729
11760
  finally {
11730
11761
  setLoading(false);
11731
11762
  }
11732
- }, [loadLevelOne, loadByIds]);
11763
+ }, [loadLevelOne, loadByValues, loadByNames]);
11733
11764
  return { treeData, setTreeData, treeExpanded, setTreeExpanded, loading, onOpen };
11734
11765
  };
11735
11766
 
11736
11767
  const useTreeFilter = (type, filter) => {
11737
11768
  const { filterName, multiSelect, placeholder, width } = filter.options ?? {};
11738
- const { configFilter, loadLevelOne, loadChildren, searchNodes, loadByIds } = useTreeFilterData(type, filterName ?? "");
11739
- const { selected, selectedIds, selectedLabel, onToggleSelect: toggleSelect, onReset } = useTreeFilterSelection(type, filterName ?? "", configFilter);
11769
+ const { configFilter, loadLevelOne, loadChildren, searchNodes, loadByNames, loadByValues } = useTreeFilterData(type, filterName ?? "");
11770
+ const { selected, selectedIds, selectedLabel, dirty, onToggleSelect: toggleSelect, onReset, onSave, } = useTreeFilterSelection(type, filterName ?? "", configFilter);
11740
11771
  const { treeData, setTreeData, treeExpanded, setTreeExpanded, loading, onOpen } = useTreeFilterTree({
11741
11772
  selected,
11742
11773
  loadLevelOne,
11743
- loadByIds,
11774
+ loadByValues,
11775
+ loadByNames,
11744
11776
  });
11745
- const search = useTreeFilterSearch({ searchNodes, loadByIds });
11777
+ const search = useTreeFilterSearch({ searchNodes, loadByNames });
11746
11778
  const onToggleSelect = useCallback(item => toggleSelect(item, multiSelect), [toggleSelect, multiSelect]);
11747
11779
  const onClose = useCallback(() => search.resetSearch(), [search]);
11748
11780
  const view = useMemo(() => search.isSearchActive
@@ -11767,12 +11799,14 @@ const useTreeFilter = (type, filter) => {
11767
11799
  onClose,
11768
11800
  onToggleSelect,
11769
11801
  onReset,
11802
+ onSave,
11803
+ dirty,
11770
11804
  };
11771
11805
  };
11772
11806
 
11773
11807
  const TreeFilter = ({ type, filter }) => {
11774
- const { width, placeholder, multiSelect, loading, data, setData, expanded, setExpanded, onLoadChildren, selectedIds, selectedCount, selectedLabel, searchValue, onSearchChange, onOpen, onClose, onToggleSelect, onReset, } = useTreeFilter(type, filter);
11775
- return (jsx(TreeDropdown, { width: width, placeholder: placeholder, multiSelect: multiSelect, loading: loading, data: data, setData: setData, expanded: expanded, setExpanded: setExpanded, onLoadChildren: onLoadChildren, selectedIds: selectedIds, selectedCount: selectedCount, selectedLabel: selectedLabel, searchValue: searchValue, onSearchChange: onSearchChange, onOpen: onOpen, onClose: onClose, onToggleSelect: onToggleSelect, onReset: onReset }));
11808
+ const { width, placeholder, multiSelect, loading, data, setData, expanded, setExpanded, onLoadChildren, selectedIds, selectedCount, selectedLabel, searchValue, onSearchChange, onOpen, onClose, onToggleSelect, onReset, onSave, dirty, } = useTreeFilter(type, filter);
11809
+ return (jsx(TreeDropdown, { zIndex: 100, width: width, placeholder: placeholder, multiSelect: multiSelect, loading: loading, data: data, setData: setData, expanded: expanded, setExpanded: setExpanded, onLoadChildren: onLoadChildren, selectedIds: selectedIds, selectedCount: selectedCount, selectedLabel: selectedLabel, searchValue: searchValue, onSearchChange: onSearchChange, onOpen: onOpen, onClose: onClose, onToggleSelect: onToggleSelect, onReset: onReset, onSave: onSave, dirty: dirty }));
11776
11810
  };
11777
11811
 
11778
11812
  const getFilterComponent = (filterType) => {