@astral/ui 4.6.0 → 4.8.0

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.
Files changed (65) hide show
  1. package/components/CheckableTag/styles.js +3 -3
  2. package/components/MenuOrganization/OrganizationButton/OrganizationButton.d.ts +7 -1
  3. package/components/MenuOrganization/OrganizationButton/OrganizationButton.js +4 -2
  4. package/components/MenuOrganization/types.d.ts +13 -0
  5. package/components/MenuOrganization/useLogic/useLogic.d.ts +4 -1
  6. package/components/MenuOrganization/useLogic/useLogic.js +5 -1
  7. package/components/TreeLikeAsyncAutocomplete/OptionsModal/OptionsModal.js +7 -2
  8. package/components/TreeLikeAsyncAutocomplete/OptionsModal/styles.js +1 -1
  9. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/index.d.ts +1 -0
  10. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/index.js +1 -0
  11. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/useSelectedTreeView/index.d.ts +1 -0
  12. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/useSelectedTreeView/index.js +1 -0
  13. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/useSelectedTreeView/useSelectedTreeView.d.ts +20 -0
  14. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/useSelectedTreeView/useSelectedTreeView.js +67 -0
  15. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/useLogic.d.ts +5 -0
  16. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/useLogic.js +56 -5
  17. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/buildSelectedTree/buildSelectedTree.d.ts +6 -0
  18. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/buildSelectedTree/buildSelectedTree.js +101 -0
  19. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/buildSelectedTree/index.d.ts +1 -0
  20. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/buildSelectedTree/index.js +1 -0
  21. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/findInTree/findInTree.d.ts +2 -0
  22. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/findInTree/findInTree.js +43 -0
  23. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/findInTree/index.d.ts +1 -0
  24. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/findInTree/index.js +1 -0
  25. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/index.d.ts +3 -0
  26. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/index.js +3 -0
  27. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/preserveNodeOrder/index.d.ts +1 -0
  28. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/preserveNodeOrder/index.js +1 -0
  29. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/preserveNodeOrder/preserveNodeOrder.d.ts +7 -0
  30. package/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/preserveNodeOrder/preserveNodeOrder.js +31 -0
  31. package/components/TreeLikeAsyncAutocomplete/types.d.ts +50 -3
  32. package/components/TreeLikeAsyncAutocomplete/useLogic/useLogic.d.ts +4 -1
  33. package/node/components/CheckableTag/styles.js +3 -3
  34. package/node/components/MenuOrganization/OrganizationButton/OrganizationButton.d.ts +7 -1
  35. package/node/components/MenuOrganization/OrganizationButton/OrganizationButton.js +4 -2
  36. package/node/components/MenuOrganization/types.d.ts +13 -0
  37. package/node/components/MenuOrganization/useLogic/useLogic.d.ts +4 -1
  38. package/node/components/MenuOrganization/useLogic/useLogic.js +5 -1
  39. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/OptionsModal.js +7 -2
  40. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/styles.js +1 -1
  41. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/index.d.ts +1 -0
  42. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/index.js +17 -0
  43. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/useSelectedTreeView/index.d.ts +1 -0
  44. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/useSelectedTreeView/index.js +17 -0
  45. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/useSelectedTreeView/useSelectedTreeView.d.ts +20 -0
  46. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/hooks/useSelectedTreeView/useSelectedTreeView.js +71 -0
  47. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/useLogic.d.ts +5 -0
  48. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/useLogic.js +56 -5
  49. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/buildSelectedTree/buildSelectedTree.d.ts +6 -0
  50. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/buildSelectedTree/buildSelectedTree.js +105 -0
  51. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/buildSelectedTree/index.d.ts +1 -0
  52. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/buildSelectedTree/index.js +17 -0
  53. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/findInTree/findInTree.d.ts +2 -0
  54. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/findInTree/findInTree.js +47 -0
  55. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/findInTree/index.d.ts +1 -0
  56. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/findInTree/index.js +17 -0
  57. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/index.d.ts +3 -0
  58. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/index.js +19 -0
  59. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/preserveNodeOrder/index.d.ts +1 -0
  60. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/preserveNodeOrder/index.js +17 -0
  61. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/preserveNodeOrder/preserveNodeOrder.d.ts +7 -0
  62. package/node/components/TreeLikeAsyncAutocomplete/OptionsModal/useLogic/utils/preserveNodeOrder/preserveNodeOrder.js +35 -0
  63. package/node/components/TreeLikeAsyncAutocomplete/types.d.ts +50 -3
  64. package/node/components/TreeLikeAsyncAutocomplete/useLogic/useLogic.d.ts +4 -1
  65. package/package.json +1 -1
@@ -76,11 +76,11 @@ const getColor = ({ theme, isChecked, disabled, }) => {
76
76
  }
77
77
  return 'inherit';
78
78
  };
79
- const getBorder = ({ isChecked, variant, color, theme, tagState = 'default', }) => {
79
+ const getBorder = ({ isChecked, variant, color, theme, disabled, tagState = 'default', }) => {
80
80
  if (variant === 'text' || color !== 'default') {
81
81
  return 'none';
82
82
  }
83
- if (isChecked) {
83
+ if (isChecked || (disabled && variant === 'light')) {
84
84
  return '1px solid transparent';
85
85
  }
86
86
  if (tagState === 'default') {
@@ -104,7 +104,7 @@ export const StyledTag = styled(Tag, {
104
104
  `
105
105
  background-color: ${disabled ? theme.palette.grey[100] : theme.palette.grey[900]};
106
106
  `}
107
- border: ${({ theme, variant, $isChecked, color }) => getBorder({ theme, variant, isChecked: $isChecked, color })};
107
+ border: ${({ theme, variant, $isChecked, color, disabled }) => getBorder({ theme, variant, isChecked: $isChecked, color, disabled })};
108
108
 
109
109
  .${tagClassnames.label} {
110
110
  color: ${({ theme, disabled, $isChecked }) => getColor({ theme, isChecked: $isChecked, disabled })};
@@ -1,4 +1,4 @@
1
- import { type SyntheticEvent } from 'react';
1
+ import { type ReactNode, type SyntheticEvent } from 'react';
2
2
  import { type Organization } from '../types';
3
3
  export type OrganizationButtonProps = {
4
4
  onClick: (event: SyntheticEvent) => void;
@@ -10,5 +10,11 @@ export type OrganizationButtonProps = {
10
10
  * Скрытие персональных данных от инструментов мониторинга
11
11
  */
12
12
  isHidePersonalData?: boolean;
13
+ /**
14
+ * Render-props, позволяет кастомизировать содержимое кнопки
15
+ */
16
+ renderPreview?: (organization: Organization & Record<string, unknown>, params: {
17
+ className?: string;
18
+ }) => ReactNode;
13
19
  };
14
20
  export declare const OrganizationButton: import("react").ForwardRefExoticComponent<OrganizationButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
@@ -5,12 +5,14 @@ import { useHidePersonalData } from '../../personalDataSecurity';
5
5
  import { Tooltip } from '../../Tooltip';
6
6
  import { OrganizationData } from '../styles';
7
7
  import { Container, StyledButton, StyledChevron, StyledTypography, } from './styles';
8
- export const OrganizationButton = forwardRef(({ onClick, isOpen, currentOrganization, isDisabled, disabledReason, isHidePersonalData, }, ref) => {
8
+ export const OrganizationButton = forwardRef(({ onClick, isOpen, currentOrganization, isDisabled, disabledReason, isHidePersonalData, renderPreview, }, ref) => {
9
9
  const { name, inn, kpp } = currentOrganization;
10
10
  const hidePersonalDataClassname = useHidePersonalData({
11
11
  isEnabled: isHidePersonalData,
12
12
  });
13
- const renderButton = () => (_jsx(StyledButton, { ref: ref, variant: "text", disabled: isDisabled, onClick: onClick, endIcon: _jsx(StyledChevron, { isActive: isOpen, width: 24, height: 24 }), children: _jsxs(Container, { className: hidePersonalDataClassname, children: [_jsx(OverflowTypography, { variant: "h6", component: "div", children: name }), _jsxs(OrganizationData, { children: [_jsxs(StyledTypography, { "$isDisabled": isDisabled, variant: "caption", color: "textSecondary", children: ["\u0418\u041D\u041D ", inn] }), kpp && (_jsxs(StyledTypography, { "$isDisabled": isDisabled, variant: "caption", color: "textSecondary", children: ["\u041A\u041F\u041F ", kpp] }))] })] }) }));
13
+ const renderButton = () => (_jsx(StyledButton, { ref: ref, variant: "text", disabled: isDisabled, onClick: onClick, endIcon: _jsx(StyledChevron, { isActive: isOpen, width: 24, height: 24 }), children: renderPreview ? (renderPreview(currentOrganization, {
14
+ className: hidePersonalDataClassname,
15
+ })) : (_jsxs(Container, { className: hidePersonalDataClassname, children: [_jsx(OverflowTypography, { variant: "h6", component: "div", children: name }), _jsxs(OrganizationData, { children: [_jsxs(StyledTypography, { "$isDisabled": isDisabled, variant: "caption", color: "textSecondary", children: ["\u0418\u041D\u041D ", inn] }), kpp && (_jsxs(StyledTypography, { "$isDisabled": isDisabled, variant: "caption", color: "textSecondary", children: ["\u041A\u041F\u041F ", kpp] }))] })] })) }));
14
16
  if (isDisabled && disabledReason) {
15
17
  return (_jsx(Tooltip, { title: disabledReason, withoutContainer: false, placement: "bottom", children: renderButton() }));
16
18
  }
@@ -113,4 +113,17 @@ export type MenuOrganizationProps<TData extends Organization = Organization> = {
113
113
  * )}
114
114
  */
115
115
  renderItem?: (params: RenderItemParams<TData>) => ReactNode;
116
+ /**
117
+ * Render-props, позволяет кастомизировать содержимое кнопки OrganizationButton
118
+ *
119
+ * @example
120
+ * renderPreview={(organization) => (
121
+ * <Avatar color="#EC4899" size="md">
122
+ * {getInitials(organization.name)}
123
+ * </Avatar>
124
+ * )}
125
+ */
126
+ renderPreview?: (organization: TData, params: {
127
+ className?: string;
128
+ }) => ReactNode;
116
129
  };
@@ -1,7 +1,7 @@
1
1
  import { type ChangeEvent, type SyntheticEvent } from 'react';
2
2
  import { type MenuOrganizationProps, type Organization } from '../types';
3
3
  type UseLogicParams<TData extends Organization & Record<string, unknown>> = MenuOrganizationProps<TData>;
4
- export declare const useLogic: <TData extends Organization & Record<string, unknown>>({ organizations, onChangeSearch, onChange, currentOrganizationGroupLabel, currentOrganization, groupBy, onClose, onOpen, isOpen: isOpenPopover, isDisabled, disabledReason, isHidePersonalData, action, isLoading, isError, onRetry, renderItem, }: UseLogicParams<TData>) => {
4
+ export declare const useLogic: <TData extends Organization & Record<string, unknown>>({ organizations, onChangeSearch, onChange, currentOrganizationGroupLabel, currentOrganization, groupBy, onClose, onOpen, isOpen: isOpenPopover, isDisabled, disabledReason, isHidePersonalData, action, isLoading, isError, onRetry, renderItem, renderPreview, }: UseLogicParams<TData>) => {
5
5
  action: ({
6
6
  text: string;
7
7
  } & Pick<import("../..").ButtonProps, "onClick" | "href" | "endIcon" | "startIcon" | "variant" | "component">) | undefined;
@@ -19,6 +19,9 @@ export declare const useLogic: <TData extends Organization & Record<string, unkn
19
19
  isDisabled: boolean | undefined;
20
20
  disabledReason: string | undefined;
21
21
  isHidePersonalData: boolean | undefined;
22
+ renderPreview: ((organization: Organization & Record<string, unknown>, params: {
23
+ className?: string;
24
+ }) => import("react").ReactNode) | undefined;
22
25
  ref: (node: HTMLButtonElement | null) => void;
23
26
  };
24
27
  searchProps: {
@@ -2,7 +2,7 @@ import { useCallback, useContext, useMemo, useState, } from 'react';
2
2
  import { DashboardContext } from '../../DashboardLayout';
3
3
  import { usePopover } from '../../hooks';
4
4
  import { useHidePersonalData } from '../../personalDataSecurity';
5
- export const useLogic = ({ organizations = [], onChangeSearch, onChange, currentOrganizationGroupLabel, currentOrganization, groupBy, onClose, onOpen, isOpen: isOpenPopover, isDisabled, disabledReason, isHidePersonalData, action, isLoading, isError, onRetry, renderItem, }) => {
5
+ export const useLogic = ({ organizations = [], onChangeSearch, onChange, currentOrganizationGroupLabel, currentOrganization, groupBy, onClose, onOpen, isOpen: isOpenPopover, isDisabled, disabledReason, isHidePersonalData, action, isLoading, isError, onRetry, renderItem, renderPreview, }) => {
6
6
  const { isLoading: isDashboardLoading } = useContext(DashboardContext);
7
7
  const [searchValue, setSearchValue] = useState('');
8
8
  const [anchorButtonEl, setAnchorButtonEl] = useState(null);
@@ -50,6 +50,9 @@ export const useLogic = ({ organizations = [], onChangeSearch, onChange, current
50
50
  }
51
51
  return !isError && !isLoading && organizations.length > 10;
52
52
  }, [searchValue.length, isError, isLoading, organizations.length]);
53
+ const renderPreviewWrapper = useMemo(() => renderPreview
54
+ ? (organization, params) => renderPreview(organization, params)
55
+ : undefined, [renderPreview]);
53
56
  return {
54
57
  action,
55
58
  isLoading,
@@ -64,6 +67,7 @@ export const useLogic = ({ organizations = [], onChangeSearch, onChange, current
64
67
  isDisabled,
65
68
  disabledReason,
66
69
  isHidePersonalData,
70
+ renderPreview: renderPreviewWrapper,
67
71
  ref: buttonRef,
68
72
  },
69
73
  searchProps: {
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button } from '../../Button';
3
+ import { CheckableTag } from '../../CheckableTag';
3
4
  import { Dialog } from '../../Dialog';
4
5
  import { DialogActions } from '../../DialogActions';
5
6
  import { Paper } from '../../Paper';
7
+ import { TagBadge } from '../../TagBadge';
6
8
  import { TreeLikeList } from '../../TreeLikeList';
7
9
  import { Typography } from '../../Typography';
8
10
  import { SEARCH_FIELD_HELPER_TEXT } from './constants';
@@ -13,7 +15,7 @@ import { SearchSuggestion } from './SearchSuggestion';
13
15
  import { StyledDialogContent, StyledSearchField, TreeListWrapper, UserHintWrapper, } from './styles';
14
16
  import { useLogic } from './useLogic';
15
17
  export const OptionsModal = (props) => {
16
- const { isNoResult, searchFieldProps, modalProps, treeListProps, cancelButtonProps, confirmButtonProps, handleRetry, isShowUserHint, isShowModalLoader, isShowSearchFieldLoader, isShowSearchSuggestion, isLoadingError, treeProps, loadingErrorMsg, } = useLogic(props);
18
+ const { isNoResult, searchFieldProps, modalProps, treeListProps, cancelButtonProps, confirmButtonProps, handleRetry, isShowUserHint, isShowModalLoader, isShowSearchFieldLoader, isShowSearchSuggestion, isLoadingError, treeProps, loadingErrorMsg, badgeContent, checkableTagProps, isShowSelectedTree, selectedTreeProps, } = useLogic(props);
17
19
  const renderComponent = () => {
18
20
  if (isShowSearchSuggestion) {
19
21
  return _jsx(SearchSuggestion, {});
@@ -27,7 +29,10 @@ export const OptionsModal = (props) => {
27
29
  if (isNoResult) {
28
30
  return _jsx(NoData, {});
29
31
  }
32
+ if (isShowSelectedTree) {
33
+ return (_jsx(TreeLikeList, { ...treeProps, ...selectedTreeProps }));
34
+ }
30
35
  return (_jsx(TreeLikeList, { ...treeProps, ...treeListProps }));
31
36
  };
32
- return (_jsxs(Dialog, { ...modalProps, children: [_jsxs(StyledDialogContent, { "$size": modalProps.size, children: [_jsx(StyledSearchField, { fullWidth: true, ...searchFieldProps, isLoading: isShowSearchFieldLoader, helperText: SEARCH_FIELD_HELPER_TEXT }), _jsx(Paper, { variant: "outlined", children: _jsxs(TreeListWrapper, { children: [renderComponent(), isShowUserHint && (_jsx(UserHintWrapper, { children: _jsx(Typography, { variant: "caption", color: "grey", colorIntensity: "600", children: "\u0423\u0442\u043E\u0447\u043D\u0438\u0442\u0435 \u0437\u0430\u043F\u0440\u043E\u0441 \u0434\u043B\u044F \u0431\u043E\u043B\u0435\u0435 \u043B\u0443\u0447\u0448\u0435\u0433\u043E \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430 \u043F\u043E\u0438\u0441\u043A\u0430" }) }))] }) })] }), _jsxs(DialogActions, { children: [_jsx(Button, { variant: "text", ...cancelButtonProps, children: "\u041E\u0442\u043C\u0435\u043D\u0430" }), _jsx(Button, { ...confirmButtonProps, children: "\u0412\u044B\u0431\u0440\u0430\u0442\u044C" })] })] }));
37
+ return (_jsxs(Dialog, { ...modalProps, children: [_jsxs(StyledDialogContent, { "$size": modalProps.size, children: [_jsx(StyledSearchField, { fullWidth: true, ...searchFieldProps, isLoading: isShowSearchFieldLoader, helperText: SEARCH_FIELD_HELPER_TEXT }), _jsx(CheckableTag, { ...checkableTagProps, endAddon: (badgeProps) => (_jsx(TagBadge, { ...badgeProps, badgeContent: badgeContent, showZero: true })) }), _jsx(Paper, { variant: "outlined", children: _jsxs(TreeListWrapper, { children: [renderComponent(), isShowUserHint && (_jsx(UserHintWrapper, { children: _jsx(Typography, { variant: "caption", color: "grey", colorIntensity: "600", children: "\u0423\u0442\u043E\u0447\u043D\u0438\u0442\u0435 \u0437\u0430\u043F\u0440\u043E\u0441 \u0434\u043B\u044F \u0431\u043E\u043B\u0435\u0435 \u043B\u0443\u0447\u0448\u0435\u0433\u043E \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430 \u043F\u043E\u0438\u0441\u043A\u0430" }) }))] }) })] }), _jsxs(DialogActions, { children: [_jsx(Button, { variant: "text", ...cancelButtonProps, children: "\u041E\u0442\u043C\u0435\u043D\u0430" }), _jsx(Button, { ...confirmButtonProps, children: "\u0412\u044B\u0431\u0440\u0430\u0442\u044C" })] })] }));
33
38
  };
@@ -9,7 +9,7 @@ export const StyledDialogContent = styled(DialogContent, {
9
9
  }) `
10
10
  display: grid;
11
11
  grid-template-columns: 100%;
12
- grid-template-rows: max-content 1fr;
12
+ grid-template-rows: max-content 32px 1fr;
13
13
  gap: ${({ theme }) => theme.spacing(4)};
14
14
 
15
15
  width: ${({ $size }) => DIALOG_SIZES[$size].minWidth};
@@ -0,0 +1 @@
1
+ export * from './useSelectedTreeView';
@@ -0,0 +1 @@
1
+ export * from './useSelectedTreeView';
@@ -0,0 +1,20 @@
1
+ import { type ChangeEvent } from 'react';
2
+ import { type TreeListData } from '../../../../../Tree';
3
+ import type { TreeLikeAsyncAutocompleteValue } from '../../../../types';
4
+ type UseSelectedTreeViewParams = {
5
+ value?: TreeLikeAsyncAutocompleteValue;
6
+ searchValue?: string;
7
+ onInputChange: (value: string) => void;
8
+ onClearSearch: () => void;
9
+ };
10
+ type UseSelectedTreeViewResult = {
11
+ isShowSelectedTree: boolean;
12
+ setIsShowSelectedTree: (value: boolean) => void;
13
+ selectedTreeData: TreeLikeAsyncAutocompleteValue;
14
+ filteredSelectedTreeData: TreeListData[];
15
+ selectedTreeDataToRender: TreeListData[];
16
+ onChangeTreeView: (event: ChangeEvent<HTMLInputElement>) => void;
17
+ isNoResultInSelectedTree: boolean;
18
+ };
19
+ export declare const useSelectedTreeView: ({ value, searchValue, onInputChange, onClearSearch, }: UseSelectedTreeViewParams) => UseSelectedTreeViewResult;
20
+ export {};
@@ -0,0 +1,67 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { deepMap } from '../../../../../utils/array';
3
+ import { buildSelectedTree, findInTree, preserveNodeOrder } from '../../utils';
4
+ const addIsForceExpandedFlag = (item) => {
5
+ if (item.children) {
6
+ return {
7
+ ...item,
8
+ options: { ...(item.options || {}), isForceExpanded: true },
9
+ };
10
+ }
11
+ return item;
12
+ };
13
+ export const useSelectedTreeView = ({ value, searchValue, onInputChange, onClearSearch, }) => {
14
+ const [isShowSelectedTree, setIsShowSelectedTree] = useState();
15
+ // Сохраняем порядок узлов
16
+ const nodeOrderRef = useRef(new Map());
17
+ // Строим дерево из выбранных элементов
18
+ const selectedTreeData = useMemo(() => {
19
+ if (!value || value.length === 0) {
20
+ nodeOrderRef.current.clear();
21
+ return [];
22
+ }
23
+ const tree = buildSelectedTree(value);
24
+ return preserveNodeOrder(tree, nodeOrderRef.current);
25
+ }, [value]);
26
+ // Применяем клиентский поиск к выбранным элементам
27
+ const filteredSelectedTreeData = useMemo(() => {
28
+ if (!isShowSelectedTree || !searchValue) {
29
+ return selectedTreeData;
30
+ }
31
+ return findInTree(selectedTreeData, searchValue);
32
+ }, [selectedTreeData, searchValue, isShowSelectedTree]);
33
+ // Определяем isNoResult для режима выбранных элементов
34
+ const isNoResultInSelectedTree = !searchValue || !isShowSelectedTree
35
+ ? false
36
+ : filteredSelectedTreeData.length === 0;
37
+ // Применяем флаг раскрытия для отфильтрованного дерева выбранных элементов
38
+ const selectedTreeDataToRender = useMemo(() => {
39
+ if (!searchValue || !isShowSelectedTree) {
40
+ return filteredSelectedTreeData;
41
+ }
42
+ return deepMap(filteredSelectedTreeData, 'children', addIsForceExpandedFlag);
43
+ }, [filteredSelectedTreeData, searchValue, isShowSelectedTree]);
44
+ const onChangeTreeView = (event) => {
45
+ const newIsShowSelectedTree = event.target.checked;
46
+ setIsShowSelectedTree(newIsShowSelectedTree);
47
+ onClearSearch();
48
+ if (!newIsShowSelectedTree) {
49
+ onInputChange('');
50
+ }
51
+ };
52
+ useEffect(() => {
53
+ // Когда все элементы убраны из выбранного дерева, возвращаемся на основное дерево
54
+ if (!value?.length) {
55
+ setIsShowSelectedTree(false);
56
+ }
57
+ }, [value]);
58
+ return {
59
+ isShowSelectedTree: isShowSelectedTree ?? false,
60
+ setIsShowSelectedTree,
61
+ selectedTreeData,
62
+ filteredSelectedTreeData,
63
+ selectedTreeDataToRender,
64
+ onChangeTreeView,
65
+ isNoResultInSelectedTree,
66
+ };
67
+ };
@@ -1,4 +1,5 @@
1
1
  import { type ButtonProps } from '../../../Button';
2
+ import { type CheckableTagProps } from '../../../CheckableTag';
2
3
  import { type DialogProps, type DialogSize } from '../../../Dialog';
3
4
  import { type SearchFieldProps } from '../../../SearchField';
4
5
  import { type TreeLikeListProps } from '../../../TreeLikeList';
@@ -22,6 +23,10 @@ type UseLogicResult = {
22
23
  treeProps?: Pick<TreeLikeListProps, 'disabledItems' | 'renderItem'>;
23
24
  isLoadingError?: boolean;
24
25
  loadingErrorMsg?: string;
26
+ badgeContent: number;
27
+ selectedTreeProps: TreeLikeListProps<TreeLikeAsyncAutocompleteValue>;
28
+ isShowSelectedTree?: boolean;
29
+ checkableTagProps: CheckableTagProps;
25
30
  };
26
31
  export declare const useLogic: ({ isOpen, initialValue, options, onChange, dialogProps, onInputChange, onRetry, meta, onShowUserHint, isLoadingModal, minSymbolsToFetch, isLoading, isLoadingError, loadingErrorMsg, treeProps, }: UseLogicParams) => UseLogicResult;
27
32
  export {};
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useMemo, useState, } from 'react';
2
2
  import { deepMap } from '../../../utils/array';
3
+ import { useSelectedTreeView } from './hooks';
3
4
  export const useLogic = ({ isOpen, initialValue, options, onChange, dialogProps, onInputChange, onRetry, meta, onShowUserHint, isLoadingModal, minSymbolsToFetch = 0, isLoading, isLoadingError, loadingErrorMsg, treeProps, }) => {
4
5
  const [value, setValue] = useState(initialValue);
5
6
  const [searchValue, setSearchValue] = useState();
@@ -13,6 +14,15 @@ export const useLogic = ({ isOpen, initialValue, options, onChange, dialogProps,
13
14
  setSearchValue('');
14
15
  }
15
16
  }, [isOpen]);
17
+ const onClearSearch = () => {
18
+ setSearchValue('');
19
+ };
20
+ const { isShowSelectedTree, selectedTreeDataToRender, onChangeTreeView, isNoResultInSelectedTree, } = useSelectedTreeView({
21
+ value,
22
+ searchValue,
23
+ onInputChange,
24
+ onClearSearch,
25
+ });
16
26
  const addIsForceExpandedFlag = (item) => {
17
27
  if (item.children) {
18
28
  return {
@@ -27,8 +37,13 @@ export const useLogic = ({ isOpen, initialValue, options, onChange, dialogProps,
27
37
  ? deepMap(options, 'children', addIsForceExpandedFlag)
28
38
  : options, [options, searchValue, isLoading]);
29
39
  const handleChangeSearchField = (event) => {
30
- setSearchValue(event.target.value);
31
- onInputChange(event.target.value);
40
+ const newSearchValue = event.target.value;
41
+ setSearchValue(newSearchValue);
42
+ // В режиме выбранных элементов используем клиентский поиск, не вызываем onInputChange
43
+ // В обычном режиме используем асинхронный поиск через запросы
44
+ if (!isShowSelectedTree) {
45
+ onInputChange(newSearchValue);
46
+ }
32
47
  };
33
48
  const handleChange = (newValue) => setValue(newValue);
34
49
  const handleClose = (event) => {
@@ -43,7 +58,23 @@ export const useLogic = ({ isOpen, initialValue, options, onChange, dialogProps,
43
58
  onChange?.(value);
44
59
  onClose?.(event, 'escapeKeyDown');
45
60
  };
46
- const isNoResult = Boolean(searchValue) && !options.length;
61
+ // Определяем isNoResult в зависимости от режима
62
+ const isNoResult = useMemo(() => {
63
+ if (!searchValue) {
64
+ return false;
65
+ }
66
+ // В режиме выбранных элементов проверяем отфильтрованное дерево
67
+ if (isShowSelectedTree) {
68
+ return isNoResultInSelectedTree;
69
+ }
70
+ // В обычном режиме проверяем options
71
+ return !options.length;
72
+ }, [
73
+ searchValue,
74
+ isShowSelectedTree,
75
+ isNoResultInSelectedTree,
76
+ options.length,
77
+ ]);
47
78
  const isDisabledButton = !value || isNoResult;
48
79
  const handleRetry = () => {
49
80
  onRetry?.(searchValue);
@@ -52,8 +83,10 @@ export const useLogic = ({ isOpen, initialValue, options, onChange, dialogProps,
52
83
  const isShowModalLoader = isLoadingModal && options.length === 0;
53
84
  const isShowSearchFieldLoader = (isLoading && !isLoadingModal) ||
54
85
  (isLoading && isLoadingModal && options.length > 0);
55
- const isShowSearchSuggestion = Boolean((!searchValue && minSymbolsToFetch > 0) ||
56
- (searchValue && searchValue.length < minSymbolsToFetch));
86
+ // Подсказка о минимальном количестве символов показывается только в обычном режиме
87
+ const isShowSearchSuggestion = Boolean(!isShowSelectedTree &&
88
+ ((!searchValue && minSymbolsToFetch > 0) ||
89
+ (searchValue && searchValue.length < minSymbolsToFetch)));
57
90
  return {
58
91
  isShowModalLoader,
59
92
  isShowSearchFieldLoader,
@@ -64,6 +97,24 @@ export const useLogic = ({ isOpen, initialValue, options, onChange, dialogProps,
64
97
  treeProps,
65
98
  loadingErrorMsg,
66
99
  isLoadingError,
100
+ badgeContent: value?.length ?? 0,
101
+ isShowSelectedTree,
102
+ checkableTagProps: {
103
+ onChange: onChangeTreeView,
104
+ checked: isShowSelectedTree,
105
+ disabled: !value?.length,
106
+ size: 'large',
107
+ variant: 'light',
108
+ label: 'Показать выбранные',
109
+ color: 'default',
110
+ },
111
+ selectedTreeProps: {
112
+ value,
113
+ data: selectedTreeDataToRender,
114
+ onChange: handleChange,
115
+ isObjectMode: true,
116
+ isInitialExpanded: true,
117
+ },
67
118
  modalProps: {
68
119
  onClose: handleClose,
69
120
  disableRestoreFocus: true,
@@ -0,0 +1,6 @@
1
+ import type { TreeLikeAsyncAutocompleteValue } from '../../../../types';
2
+ /**
3
+ * Строит дерево из выбранных элементов с учетом breadcrumbs
4
+ * Если несколько элементов имеют общего родителя, они объединяются в один узел
5
+ */
6
+ export declare const buildSelectedTree: (selectedItems: TreeLikeAsyncAutocompleteValue) => TreeLikeAsyncAutocompleteValue;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Создает breadcrumbs для элемента на основе его позиции в пути
3
+ */
4
+ const createBreadcrumbs = (path, currentIndex) => {
5
+ if (currentIndex === 0) {
6
+ return undefined;
7
+ }
8
+ // Берем все элементы до текущего индекса и создаем breadcrumbs
9
+ // Breadcrumbs должны содержать только id, label, note (без children и options)
10
+ return path.slice(0, currentIndex).map((item) => ({
11
+ id: item.id,
12
+ label: item.label,
13
+ note: item.note,
14
+ }));
15
+ };
16
+ /**
17
+ * Получает или создает Map для дочернего узла
18
+ */
19
+ const getOrCreateChildrenMap = (node, nodeChildrenMaps) => {
20
+ const existingMap = nodeChildrenMaps.get(node);
21
+ if (existingMap) {
22
+ return existingMap;
23
+ }
24
+ const childrenMap = new Map();
25
+ nodeChildrenMaps.set(node, childrenMap);
26
+ // Если у узла уже есть children, добавляем их в Map
27
+ if (node.children) {
28
+ node.children.forEach((child) => {
29
+ childrenMap.set(child.id, child);
30
+ });
31
+ }
32
+ return childrenMap;
33
+ };
34
+ /**
35
+ * Преобразует Map в массив TreeLikeAsyncAutocompleteValue
36
+ */
37
+ const convertToArray = (level, nodeChildrenMaps) => {
38
+ const result = [];
39
+ level.forEach((node) => {
40
+ const childrenMap = nodeChildrenMaps.get(node);
41
+ const children = childrenMap && childrenMap.size > 0
42
+ ? convertToArray(childrenMap, nodeChildrenMaps)
43
+ : undefined;
44
+ result.push({
45
+ ...node,
46
+ children: children && children.length > 0 ? children : undefined,
47
+ });
48
+ });
49
+ return result;
50
+ };
51
+ /**
52
+ * Строит дерево из выбранных элементов с учетом breadcrumbs
53
+ * Если несколько элементов имеют общего родителя, они объединяются в один узел
54
+ */
55
+ export const buildSelectedTree = (selectedItems) => {
56
+ if (!selectedItems || selectedItems.length === 0) {
57
+ return [];
58
+ }
59
+ const rootLevel = new Map();
60
+ const nodeChildrenMaps = new WeakMap();
61
+ // Обрабатываем каждый выбранный элемент
62
+ selectedItems.forEach((item) => {
63
+ // Строим путь от корня к элементу: breadcrumbs + сам элемент
64
+ const path = [
65
+ ...(item.breadcrumbs ?? []),
66
+ item,
67
+ ];
68
+ // Проходим по пути и создаем/обновляем узлы дерева
69
+ path.reduce((currentLevel, pathItem, index) => {
70
+ const existingNode = currentLevel.get(pathItem.id);
71
+ const restoredBreadcrumbs = createBreadcrumbs(path, index);
72
+ if (existingNode) {
73
+ // Если узел уже существует, обновляем его поля из pathItem
74
+ const existingChildren = existingNode.children;
75
+ Object.assign(existingNode, pathItem, {
76
+ children: existingChildren,
77
+ breadcrumbs: restoredBreadcrumbs,
78
+ options: {
79
+ ...existingNode.options,
80
+ ...pathItem.options,
81
+ isDefaultExpanded: true,
82
+ },
83
+ });
84
+ return getOrCreateChildrenMap(existingNode, nodeChildrenMaps);
85
+ }
86
+ // Создаем новый узел с восстановленными breadcrumbs
87
+ const newNode = {
88
+ ...pathItem,
89
+ breadcrumbs: restoredBreadcrumbs,
90
+ children: undefined,
91
+ options: {
92
+ ...pathItem.options,
93
+ isDefaultExpanded: true,
94
+ },
95
+ };
96
+ currentLevel.set(pathItem.id, newNode);
97
+ return getOrCreateChildrenMap(newNode, nodeChildrenMaps);
98
+ }, rootLevel);
99
+ });
100
+ return convertToArray(rootLevel, nodeChildrenMaps);
101
+ };
@@ -0,0 +1,2 @@
1
+ import { type TreeListData } from '../../../../../Tree';
2
+ export declare const findInTree: (tree: TreeListData[], searchValue: string, filterOptions?: ((node: TreeListData, filterSearchValue: string) => boolean) | undefined) => TreeListData[];
@@ -0,0 +1,43 @@
1
+ const defaultFilterOptions = (node, searchValue) => {
2
+ if (typeof node.label !== 'string') {
3
+ return;
4
+ }
5
+ const preparedSearchValue = searchValue.trim().toLowerCase();
6
+ const preparedLabel = node.label.toLowerCase();
7
+ const resultByLabel = preparedLabel.includes(preparedSearchValue);
8
+ if (resultByLabel) {
9
+ return resultByLabel;
10
+ }
11
+ if (typeof node.note !== 'string') {
12
+ return;
13
+ }
14
+ const preparedNote = node.note.toLowerCase();
15
+ return preparedNote.includes(preparedSearchValue);
16
+ };
17
+ export const findInTree = (tree, searchValue, filterOptions) => {
18
+ const compareFunc = filterOptions || defaultFilterOptions;
19
+ const search = (nodes) => {
20
+ const results = [];
21
+ for (const node of nodes) {
22
+ // Если label включает искомый термин, добавляем узел в результаты
23
+ if (compareFunc(node, searchValue)) {
24
+ results.push({ ...node, options: { isDefaultExpanded: false } });
25
+ // Если узел соответствует поиску, пропускаем обработку его потомков
26
+ continue;
27
+ }
28
+ // Если у узла есть потомки, рекурсивно ищем в них
29
+ if (node.children) {
30
+ const childResults = search(node.children);
31
+ if (childResults.length > 0) {
32
+ // Добавляем узел только один раз с найденными потомками
33
+ results.push({
34
+ ...node,
35
+ children: childResults,
36
+ });
37
+ }
38
+ }
39
+ }
40
+ return results;
41
+ };
42
+ return search(tree);
43
+ };
@@ -0,0 +1,3 @@
1
+ export * from './buildSelectedTree';
2
+ export * from './findInTree';
3
+ export * from './preserveNodeOrder';
@@ -0,0 +1,3 @@
1
+ export * from './buildSelectedTree';
2
+ export * from './findInTree';
3
+ export * from './preserveNodeOrder';
@@ -0,0 +1,7 @@
1
+ import type { TreeLikeAsyncAutocompleteValue } from '../../../../types';
2
+ type NodeOrderMap = Map<string, number>;
3
+ /**
4
+ * Гарантирует неизменность порядка узлов при операциях выбора элементов в дереве
5
+ */
6
+ export declare const preserveNodeOrder: (tree: TreeLikeAsyncAutocompleteValue, nodeOrderRef: NodeOrderMap) => TreeLikeAsyncAutocompleteValue;
7
+ export {};
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Гарантирует неизменность порядка узлов при операциях выбора элементов в дереве
3
+ */
4
+ export const preserveNodeOrder = (tree, nodeOrderRef) => {
5
+ // Обновляем порядок узлов: сохраняем существующий или используем порядок из дерева
6
+ const updateNodeOrder = (nodes) => {
7
+ nodes.forEach((node) => {
8
+ const nodeWithOrder = node;
9
+ const orderFromTree = nodeWithOrder.options?.order;
10
+ if (!nodeOrderRef.has(node.id)) {
11
+ nodeOrderRef.set(node.id, orderFromTree ?? nodeOrderRef.size);
12
+ }
13
+ if (node.children) {
14
+ updateNodeOrder(node.children);
15
+ }
16
+ });
17
+ };
18
+ updateNodeOrder(tree);
19
+ // Сортируем дерево по сохраненному порядку
20
+ const sortByOrder = (nodes) => [...nodes]
21
+ .sort((a, b) => {
22
+ const orderA = nodeOrderRef.get(a.id) ?? Infinity;
23
+ const orderB = nodeOrderRef.get(b.id) ?? Infinity;
24
+ return orderA - orderB;
25
+ })
26
+ .map((node) => ({
27
+ ...node,
28
+ children: node.children ? sortByOrder(node.children) : undefined,
29
+ }));
30
+ return sortByOrder(tree);
31
+ };