@tap-payments/os-micro-frontend-shared 0.1.396 → 0.1.398

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 (42) hide show
  1. package/build/components/TableCells/CustomCells/ReferenceCell/style.js +3 -1
  2. package/build/components/TableCells/CustomCells/StatusCell/style.js +3 -1
  3. package/build/components/TableReports/style.js +1 -1
  4. package/build/components/TreeDropdown/TreeDropdown.d.ts +4 -0
  5. package/build/components/TreeDropdown/TreeDropdown.js +67 -0
  6. package/build/components/TreeDropdown/TreeNodeItem/SelectionControl.d.ts +8 -0
  7. package/build/components/TreeDropdown/TreeNodeItem/SelectionControl.js +17 -0
  8. package/build/components/TreeDropdown/TreeNodeItem/TreeNodeItem.d.ts +12 -0
  9. package/build/components/TreeDropdown/TreeNodeItem/TreeNodeItem.js +64 -0
  10. package/build/components/TreeDropdown/TreeNodeItem/index.d.ts +2 -0
  11. package/build/components/TreeDropdown/TreeNodeItem/index.js +2 -0
  12. package/build/components/TreeDropdown/TreeNodeItem/style.d.ts +14 -0
  13. package/build/components/TreeDropdown/TreeNodeItem/style.js +46 -0
  14. package/build/components/TreeDropdown/TreeNodeList/List.d.ts +6 -0
  15. package/build/components/TreeDropdown/TreeNodeList/List.js +17 -0
  16. package/build/components/TreeDropdown/TreeNodeList/TreeNodeList.d.ts +17 -0
  17. package/build/components/TreeDropdown/TreeNodeList/TreeNodeList.js +36 -0
  18. package/build/components/TreeDropdown/TreeNodeList/index.d.ts +2 -0
  19. package/build/components/TreeDropdown/TreeNodeList/index.js +2 -0
  20. package/build/components/TreeDropdown/TreeNodeList/style.d.ts +7 -0
  21. package/build/components/TreeDropdown/TreeNodeList/style.js +60 -0
  22. package/build/components/TreeDropdown/context/TreeDropdownProvider.d.ts +15 -0
  23. package/build/components/TreeDropdown/context/TreeDropdownProvider.js +13 -0
  24. package/build/components/TreeDropdown/hooks/useSearch.d.ts +17 -0
  25. package/build/components/TreeDropdown/hooks/useSearch.js +38 -0
  26. package/build/components/TreeDropdown/hooks/useTreeDropdown.d.ts +12 -0
  27. package/build/components/TreeDropdown/hooks/useTreeDropdown.js +10 -0
  28. package/build/components/TreeDropdown/index.d.ts +4 -0
  29. package/build/components/TreeDropdown/index.js +4 -0
  30. package/build/components/TreeDropdown/style.d.ts +9 -0
  31. package/build/components/TreeDropdown/style.js +43 -0
  32. package/build/components/TreeDropdown/type.d.ts +89 -0
  33. package/build/components/TreeDropdown/type.js +6 -0
  34. package/build/components/TreeDropdown/utils.d.ts +21 -0
  35. package/build/components/TreeDropdown/utils.js +161 -0
  36. package/build/components/index.d.ts +2 -0
  37. package/build/components/index.js +2 -0
  38. package/build/hooks/index.d.ts +1 -0
  39. package/build/hooks/index.js +1 -0
  40. package/build/hooks/useScrolledTo.d.ts +13 -0
  41. package/build/hooks/useScrolledTo.js +45 -0
  42. package/package.json +1 -1
@@ -10,7 +10,9 @@ export const StyledSourceImage = styled('img')(() => ({
10
10
  maxWidth: '36px',
11
11
  maxHeight: '24px',
12
12
  }));
13
- export const ReferenceSourcesContainer = styled(motion.span)(({ theme, sourcesCount }) => ({
13
+ export const ReferenceSourcesContainer = styled(motion.span, {
14
+ shouldForwardProp: (prop) => prop !== 'sourcesCount',
15
+ })(({ theme, sourcesCount }) => ({
14
16
  display: 'flex',
15
17
  alignItems: 'center',
16
18
  justifyContent: 'flex-start',
@@ -46,7 +46,9 @@ export const statusAnimation = (index, shadow, xDelta) => ({
46
46
  export const StatusIcon = styled(motion.span)(({ isTextShown, theme }) => (Object.assign({ border: `1px solid ${theme.palette.divider}`, borderRadius: '12px', backgroundColor: theme.palette.background.paper, height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center', minWidth: isTextShown ? 'auto' : '36px' }, (isTextShown && {
47
47
  border: 'none',
48
48
  }))));
49
- export const StatusWrapper = styled(motion.div)(({ statusesCount }) => ({
49
+ export const StatusWrapper = styled(motion.div, {
50
+ shouldForwardProp: (prop) => prop !== 'statusesCount',
51
+ })(({ statusesCount }) => ({
50
52
  display: 'flex',
51
53
  alignItems: 'center',
52
54
  justifyContent: 'center',
@@ -1,7 +1,7 @@
1
1
  import { styled, Box, alpha } from '@mui/material';
2
2
  import { motion } from 'framer-motion';
3
3
  export const ExportButton = styled(motion.div, {
4
- shouldForwardProp: (props) => props !== 'notificationState' && props !== 'open' && props !== 'loading' && props !== 'ready',
4
+ shouldForwardProp: (prop) => !['notificationState', 'open', 'loading', 'ready', 'failed'].includes(prop),
5
5
  })(({ theme, notificationState, open, loading, ready, failed }) => (Object.assign(Object.assign({ position: 'relative', border: `1px solid ${theme.palette.divider}`, padding: '0 8px', height: 32, borderRadius: '4px', display: 'flex', alignItems: 'center', overflow: 'hidden', fontSize: '11px', fontWeight: 500, color: theme.palette.text.primary, transition: 'width 2s ease-in-out', cursor: 'pointer' }, (((notificationState === null || notificationState === void 0 ? void 0 : notificationState.success) || (notificationState === null || notificationState === void 0 ? void 0 : notificationState.failed)) &&
6
6
  !loading &&
7
7
  !ready &&
@@ -0,0 +1,4 @@
1
+ /// <reference types="react" />
2
+ import { TreeDropdownProps, TreeDropdownRef } from './type';
3
+ declare const TreeDropdown: import("react").ForwardRefExoticComponent<Readonly<TreeDropdownProps> & import("react").RefAttributes<TreeDropdownRef>>;
4
+ export default TreeDropdown;
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
3
+ import { SelectedState } from './type';
4
+ import TreeNodeList from './TreeNodeList/TreeNodeList';
5
+ import { initializeTreeMapper, promoteSingleChild, TreeManager, updateSelection } from './utils';
6
+ import TreeDropdownProvider from './context/TreeDropdownProvider';
7
+ const TreeDropdown = forwardRef((props, ref) => {
8
+ const { anchorEl, placement = 'bottom-start', treeDefinition, sx, onChange, multiple = true, exposeOnlySelectedTree = false, IconComponent, onMounted, } = props;
9
+ const { header, footer, children: menuItems = [], searchProps, levelKey } = treeDefinition;
10
+ const [treeMapper, setTreeMapper] = useState({});
11
+ const onMountedRef = useRef(onMounted);
12
+ onMountedRef.current = onMounted;
13
+ const formattedMenuItems = useMemo(() => promoteSingleChild(menuItems || []), [menuItems]);
14
+ const getFullTree = useCallback(() => TreeManager.denormalizeTree(menuItems, treeMapper), [menuItems, treeMapper]);
15
+ const getSelectedTree = useCallback(() => TreeManager.extractSelectedBranches(getFullTree()), [getFullTree]);
16
+ const handleSelect = useCallback((isChecked, targetNode) => {
17
+ const nextInternal = updateSelection(treeMapper, targetNode.id, isChecked, multiple);
18
+ setTreeMapper(nextInternal);
19
+ if (onChange) {
20
+ if (exposeOnlySelectedTree) {
21
+ onChange(TreeManager.extractSelectedBranches(TreeManager.denormalizeTree(menuItems, nextInternal)));
22
+ }
23
+ else {
24
+ onChange(TreeManager.denormalizeTree(menuItems, nextInternal));
25
+ }
26
+ }
27
+ }, [onChange, menuItems, treeMapper, multiple, exposeOnlySelectedTree]);
28
+ const toggleAll = useCallback((selected) => {
29
+ const setStatus = (tree, isChecked) => {
30
+ const next = Object.assign({}, tree);
31
+ Object.keys(next).forEach((nodeId) => {
32
+ next[nodeId] = Object.assign(Object.assign({}, next[nodeId]), { status: isChecked ? SelectedState.CHECKED : SelectedState.UNCHECKED });
33
+ });
34
+ return next;
35
+ };
36
+ const nextInternal = setStatus(treeMapper, selected);
37
+ setTreeMapper(nextInternal);
38
+ if (onChange) {
39
+ if (exposeOnlySelectedTree) {
40
+ onChange(TreeManager.extractSelectedBranches(TreeManager.denormalizeTree(menuItems, nextInternal)));
41
+ }
42
+ else {
43
+ onChange(TreeManager.denormalizeTree(menuItems, nextInternal));
44
+ }
45
+ }
46
+ }, [treeMapper, onChange, menuItems, exposeOnlySelectedTree]);
47
+ useEffect(() => {
48
+ var _a;
49
+ const intitialMapper = initializeTreeMapper(menuItems || [], multiple);
50
+ setTreeMapper(intitialMapper);
51
+ const tree = TreeManager.denormalizeTree(menuItems, intitialMapper);
52
+ const selectedTree = TreeManager.extractSelectedBranches(tree);
53
+ (_a = onMountedRef.current) === null || _a === void 0 ? void 0 : _a.call(onMountedRef, {
54
+ state: intitialMapper,
55
+ tree,
56
+ selectedTree,
57
+ });
58
+ }, [menuItems, multiple]);
59
+ useImperativeHandle(ref, () => ({
60
+ treeMapper,
61
+ getFullTree,
62
+ getSelectedTree,
63
+ toggleAll,
64
+ }), [treeMapper, getFullTree, getSelectedTree, toggleAll]);
65
+ return (_jsx(TreeDropdownProvider, Object.assign({ treeMapper: treeMapper, IconComponent: IconComponent, handleSelect: handleSelect, multiple: multiple }, { children: _jsx(TreeNodeList, { anchorEl: anchorEl, sx: sx, header: header, footer: footer, menuItems: formattedMenuItems, levelKey: levelKey, searchProps: searchProps, placement: placement }) })));
66
+ });
67
+ export default TreeDropdown;
@@ -0,0 +1,8 @@
1
+ /// <reference types="react" />
2
+ type SelectionControlProps = {
3
+ checked: boolean;
4
+ indeterminate: boolean;
5
+ onSelect: (e: React.ChangeEvent<HTMLInputElement>) => void;
6
+ };
7
+ declare const SelectionControl: (props: SelectionControlProps) => import("react/jsx-runtime").JSX.Element;
8
+ export default SelectionControl;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import useTreeDropdown from '../hooks/useTreeDropdown';
3
+ import { StyledCheckbox as Checkbox, StyledRadioButton as RadioButton } from './style';
4
+ import { useCallback } from 'react';
5
+ const SelectionControl = (props) => {
6
+ const { checked, indeterminate, onSelect } = props;
7
+ const { multiple } = useTreeDropdown();
8
+ const handleSelect = useCallback((e) => {
9
+ e.stopPropagation();
10
+ onSelect(e);
11
+ }, [onSelect]);
12
+ if (multiple) {
13
+ return _jsx(Checkbox, { size: "small", checked: checked, indeterminate: indeterminate, onChange: handleSelect });
14
+ }
15
+ return _jsx(RadioButton, { size: "small", checked: checked, onChange: handleSelect });
16
+ };
17
+ export default SelectionControl;
@@ -0,0 +1,12 @@
1
+ import { SxProps, Theme } from '@mui/material';
2
+ import { TreeNode } from '../type';
3
+ type InternalTreeNodeItemProps = {
4
+ levelIndex?: number;
5
+ nestedMenuSx?: SxProps<Theme>;
6
+ parentMenuFlipped?: boolean;
7
+ subLevelKey?: string;
8
+ disableHovering?: boolean;
9
+ };
10
+ type TreeNodeItemProps = TreeNode & InternalTreeNodeItemProps;
11
+ declare const TreeNodeItem: (props: TreeNodeItemProps) => import("react/jsx-runtime").JSX.Element;
12
+ export default TreeNodeItem;
@@ -0,0 +1,64 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import { useCallback, useEffect, useMemo, useState } from 'react';
14
+ import { Stack } from '@mui/material';
15
+ import { blackRightArrowIcon } from '../../../constants/index.js';
16
+ import { StyledMenuItem as Root } from '../style';
17
+ import { SelectedState } from '../type';
18
+ import TreeNodeList from '../TreeNodeList/TreeNodeList';
19
+ import useTreeDropdown from '../hooks/useTreeDropdown';
20
+ import SelectionControl from './SelectionControl';
21
+ import { ArrowIcon, ChildrenCount, Content, HintText, Label } from './style';
22
+ const TreeNodeItem = (props) => {
23
+ var _a, _b;
24
+ const { renderItem, header, footer } = props, nodeProps = __rest(props, ["renderItem", "header", "footer"]);
25
+ const { treeMapper, handleSelect, multiple, IconComponent } = useTreeDropdown();
26
+ const { label, icon, iconComponent, levelIndex = 0, children, nestedMenuSx, hintText, disableHovering, itemProps, searchProps, subLevelKey, } = nodeProps;
27
+ const [anchorEl, setAnchorEl] = useState(null);
28
+ const hasChildren = !!children;
29
+ const childrenCount = children === null || children === void 0 ? void 0 : children.length;
30
+ const handleMouseEnter = useCallback((event) => {
31
+ if (disableHovering)
32
+ return;
33
+ setAnchorEl(event.currentTarget);
34
+ }, [disableHovering]);
35
+ const handleMouseLeave = useCallback(() => {
36
+ if (disableHovering)
37
+ return;
38
+ setAnchorEl(null);
39
+ }, [disableHovering]);
40
+ useEffect(() => {
41
+ if (disableHovering) {
42
+ setAnchorEl(null);
43
+ }
44
+ }, [disableHovering]);
45
+ const renderedIcon = useMemo(() => {
46
+ var _a, _b;
47
+ if (iconComponent)
48
+ return iconComponent;
49
+ if (!icon)
50
+ return null;
51
+ return IconComponent ? _jsx(IconComponent, { fileId: icon.fileId, width: (_a = icon.width) !== null && _a !== void 0 ? _a : 20, height: (_b = icon.height) !== null && _b !== void 0 ? _b : 20 }) : null;
52
+ }, [iconComponent, icon, IconComponent]);
53
+ const checked = ((_a = treeMapper[nodeProps.id]) === null || _a === void 0 ? void 0 : _a.status) === SelectedState.CHECKED;
54
+ const indeterminate = multiple ? ((_b = treeMapper[nodeProps.id]) === null || _b === void 0 ? void 0 : _b.status) === SelectedState.INDETERMINATE : false;
55
+ const showSelectionControl = multiple || !hasChildren;
56
+ const handleSelectInternal = useCallback((e) => {
57
+ e.stopPropagation();
58
+ handleSelect(e.target.checked, nodeProps);
59
+ }, [handleSelect, nodeProps]);
60
+ return (_jsxs(Root, Object.assign({ hideCheckbox: true, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, "data-testid": nodeProps.id, onClick: () => {
61
+ handleSelect(!checked, nodeProps);
62
+ } }, itemProps, { children: [renderItem ? (renderItem({ nodeProps, checked, indeterminate, onSelect: handleSelectInternal })) : (_jsxs(Content, Object.assign({ width: "100%", "data-testid": "Content" }, { children: [showSelectionControl && _jsx(SelectionControl, { checked: checked, indeterminate: indeterminate, onSelect: handleSelectInternal }), renderedIcon, _jsxs(Stack, Object.assign({ flex: 1, width: "100%", minWidth: 0 }, { children: [_jsx(Label, Object.assign({ title: typeof label === 'string' ? label : undefined }, { children: label })), hintText && _jsx(HintText, Object.assign({ title: typeof hintText === 'string' ? hintText : undefined }, { children: hintText }))] })), _jsxs(Stack, Object.assign({ direction: "row", alignItems: "center", spacing: 0.5 }, { children: [childrenCount && _jsx(ChildrenCount, Object.assign({ isOneDigit: Math.floor(childrenCount / 10) === 0 }, { children: childrenCount })), hasChildren && _jsx(ArrowIcon, { src: blackRightArrowIcon, alt: "Expand" })] }))] }))), !!children && (_jsx(TreeNodeList, { anchorEl: anchorEl, menuItems: children, levelIndex: levelIndex + 1, sx: nestedMenuSx, header: header, levelKey: subLevelKey, footer: footer, searchProps: searchProps }))] })));
63
+ };
64
+ export default TreeNodeItem;
@@ -0,0 +1,2 @@
1
+ import TreeNodeItem from './TreeNodeItem';
2
+ export default TreeNodeItem;
@@ -0,0 +1,2 @@
1
+ import TreeNodeItem from './TreeNodeItem';
2
+ export default TreeNodeItem;
@@ -0,0 +1,14 @@
1
+ /// <reference types="react" />
2
+ export declare const StyledCheckbox: import("@emotion/styled").StyledComponent<import("@mui/material").CheckboxProps & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, {}, {}>;
3
+ export declare const StyledRadioButton: import("@emotion/styled").StyledComponent<import("../../index.js").RadioButtonProps & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, {}, {}>;
4
+ export declare const Content: import("@emotion/styled").StyledComponent<import("@mui/system").BoxOwnProps<import("@mui/material/styles").Theme> & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
5
+ ref?: ((instance: HTMLDivElement | null) => void) | import("react").RefObject<HTMLDivElement> | null | undefined;
6
+ }, keyof import("@mui/system").BoxOwnProps<import("@mui/material/styles").Theme>> & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, {}, {}>;
7
+ export declare const Label: import("@mui/material/OverridableComponent").OverridableComponent<import("@mui/material/Typography").TypographyTypeMap<{}, "span">>;
8
+ export declare const HintText: import("@mui/material/OverridableComponent").OverridableComponent<import("@mui/material/Typography").TypographyTypeMap<{}, "span">>;
9
+ export declare const ChildrenCount: import("@emotion/styled").StyledComponent<import("@mui/system").BoxOwnProps<import("@mui/material/styles").Theme> & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
10
+ ref?: ((instance: HTMLDivElement | null) => void) | import("react").RefObject<HTMLDivElement> | null | undefined;
11
+ }, keyof import("@mui/system").BoxOwnProps<import("@mui/material/styles").Theme>> & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme> & {
12
+ isOneDigit?: boolean | undefined;
13
+ }, {}, {}>;
14
+ export declare const ArrowIcon: import("@emotion/styled").StyledComponent<import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, import("react").DetailedHTMLProps<import("react").ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, {}>;
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { alpha, styled } from '@mui/material/styles';
3
+ import Typography from '@mui/material/Typography';
4
+ import Box from '@mui/material/Box';
5
+ import { Checkbox, RadioButton } from '../../index.js';
6
+ export const StyledCheckbox = styled(Checkbox)(({ theme }) => ({
7
+ padding: 0,
8
+ paddingRight: '4px',
9
+ width: '14px !important',
10
+ svg: {
11
+ width: '12px !important',
12
+ height: '12px !important',
13
+ },
14
+ '&.Mui-disabled': {
15
+ '[data-testid="IndeterminateCheckBoxIcon"]': {
16
+ fill: alpha(theme.palette.common.black, 0.26),
17
+ },
18
+ },
19
+ }));
20
+ export const StyledRadioButton = styled(RadioButton)({
21
+ padding: 0,
22
+ });
23
+ export const Content = styled(Box)(() => ({
24
+ display: 'flex',
25
+ gap: '8px',
26
+ justifyContent: 'space-between',
27
+ flexGrow: 1,
28
+ width: '100%',
29
+ alignItems: 'center',
30
+ minWidth: 0,
31
+ }));
32
+ export const Label = styled((props) => _jsx(Typography, Object.assign({ noWrap: true, component: "span" }, props)))(({ theme }) => ({
33
+ flex: 1,
34
+ fontSize: 'inherit',
35
+ width: '100%',
36
+ display: 'inline-block',
37
+ color: theme.palette.text.primary,
38
+ }));
39
+ export const HintText = styled((props) => _jsx(Typography, Object.assign({ noWrap: true }, props)))(({ theme }) => ({
40
+ color: alpha(theme.palette.text.primary, 0.5),
41
+ fontSize: 8,
42
+ }));
43
+ export const ChildrenCount = styled(Box, { shouldForwardProp: (prop) => prop !== 'isOneDigit' })(({ theme, isOneDigit = true }) => (Object.assign({ borderRadius: isOneDigit ? '50%' : '4px', background: theme.palette.divider, width: isOneDigit ? 13 : 'auto', height: 14, fontSize: '8px', fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', lineHeight: '10px' }, (!isOneDigit && { padding: '0 3px' }))));
44
+ export const ArrowIcon = styled('img')(() => ({
45
+ height: 12,
46
+ }));
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ declare const List: ({ children, onScroll }: {
3
+ children: React.ReactNode;
4
+ onScroll: (e: React.UIEvent<HTMLElement, UIEvent>) => void;
5
+ }) => import("react/jsx-runtime").JSX.Element;
6
+ export default List;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useScrolledTo } from '../../../hooks/index.js';
3
+ import { useCallback, useMemo, useRef } from 'react';
4
+ import { ListOuterContainer, ListInnerContainer } from './style';
5
+ const List = ({ children, onScroll }) => {
6
+ const containerRef = useRef(null);
7
+ const { scrolledToBottom, scrolledToTop, scrollable, handleScroll: handleScrolling } = useScrolledTo({ containerRef });
8
+ const classes = useMemo(() => {
9
+ return [scrolledToBottom && 'scrolled-to-bottom', scrolledToTop && 'scrolled-to-top', scrollable && 'scrollable'].filter(Boolean).join(' ');
10
+ }, [scrolledToBottom, scrolledToTop, scrollable]);
11
+ const handleScroll = useCallback((e) => {
12
+ onScroll(e);
13
+ handleScrolling(e);
14
+ }, [onScroll, handleScrolling]);
15
+ return (_jsx(ListOuterContainer, Object.assign({ sx: { position: 'relative' }, className: classes }, { children: _jsx(ListInnerContainer, Object.assign({ ref: containerRef, onScroll: handleScroll }, { children: children })) })));
16
+ };
17
+ export default List;
@@ -0,0 +1,17 @@
1
+ /// <reference types="react" />
2
+ import { PopperProps } from '@mui/material/Popper';
3
+ import { SxProps, Theme } from '@mui/material/styles';
4
+ import { TreeNode } from '../type';
5
+ type TreeNodeListProps = {
6
+ menuItems: TreeNode[];
7
+ anchorEl: HTMLElement | null;
8
+ sx?: SxProps<Theme>;
9
+ header?: React.ReactNode;
10
+ footer?: React.ReactNode;
11
+ levelIndex?: number;
12
+ levelKey?: TreeNode['levelKey'];
13
+ searchProps?: TreeNode['searchProps'];
14
+ placement?: PopperProps['placement'];
15
+ };
16
+ declare const TreeNodeList: (props: TreeNodeListProps) => import("react/jsx-runtime").JSX.Element;
17
+ export default TreeNodeList;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import Divider from '@mui/material/Divider';
4
+ import Stack from '@mui/material/Stack';
5
+ import Typography from '@mui/material/Typography';
6
+ import Box from '@mui/material/Box';
7
+ import { alpha } from '@mui/material/styles';
8
+ import { grayCloseIcon, searchIcon } from '../../../constants/index.js';
9
+ import { Section } from '../style';
10
+ import TreeNodeItem from '../TreeNodeItem';
11
+ import List from './List';
12
+ import { Root, SearchInput } from './style';
13
+ import useSearch from '../hooks/useSearch';
14
+ const TreeNodeList = (props) => {
15
+ var _a, _b;
16
+ const { anchorEl, sx, levelIndex = 0, placement, menuItems, header, footer, levelKey, searchProps } = props;
17
+ const [isScrolling, setIsScrolling] = useState(false);
18
+ const open = !!anchorEl;
19
+ const searchEnabled = !!searchProps;
20
+ const { searchText, filteredItems, handleSearch, handleReset } = useSearch(Object.assign(Object.assign({}, searchProps), { enabled: searchEnabled, items: menuItems }));
21
+ const timeout = useRef();
22
+ const handleScroll = useCallback(() => {
23
+ setIsScrolling(true);
24
+ clearTimeout(timeout.current);
25
+ timeout.current = setTimeout(() => {
26
+ setIsScrolling(false);
27
+ }, 100);
28
+ }, []);
29
+ useEffect(() => () => clearTimeout(timeout.current), []);
30
+ return (_jsx(Root, Object.assign({ open: open, anchorEl: anchorEl, placement: levelIndex > 0 ? 'right-start' : placement, "data-level": levelIndex, "data-level-key": levelKey, onClick: (e) => e.stopPropagation(), sx: Object.assign(Object.assign({}, sx), { '&.MuiPopper-dropdown': {
31
+ '&:after': {
32
+ display: 'none',
33
+ },
34
+ } }) }, { children: _jsxs(Stack, Object.assign({ divider: _jsx(Divider, { flexItem: true }) }, { children: [header && _jsx(Section, { children: header }), searchEnabled && (_jsxs(Stack, Object.assign({ divider: _jsx(Divider, { flexItem: true, sx: { my: 1 } }) }, { children: [_jsx(Box, Object.assign({ sx: { p: 1 } }, { children: _jsx(SearchInput, Object.assign({}, searchProps === null || searchProps === void 0 ? void 0 : searchProps.inputProps, { placeholder: (_b = (_a = searchProps === null || searchProps === void 0 ? void 0 : searchProps.inputProps) === null || _a === void 0 ? void 0 : _a.placeholder) !== null && _b !== void 0 ? _b : 'Search...', onChange: handleSearch, value: searchText, inputProps: { autoComplete: 'off' }, endAdornment: _jsx(Box, Object.assign({ component: "img", "data-icon": searchText ? 'close' : 'search', src: searchText ? grayCloseIcon : searchIcon, alt: "search", className: "icon" }, (searchText && { sx: { cursor: 'pointer' } }), { onClick: handleReset })) })) })), filteredItems.length === 0 && (searchProps === null || searchProps === void 0 ? void 0 : searchProps.noResultsText) && (_jsx(Typography, Object.assign({ component: "div", fontSize: 10, color: (theme) => alpha(theme.palette.text.primary, 0.4), textAlign: "center", minHeight: 36, fontWeight: 500, display: "flex", alignItems: "center", justifyContent: "center" }, { children: _jsx("span", { children: "No results found" }) })))] }))), _jsx(List, Object.assign({ onScroll: handleScroll }, { children: filteredItems.map((item, index) => (_jsx(TreeNodeItem, Object.assign({ disableHovering: isScrolling }, item, { levelIndex: levelIndex, subLevelKey: item.levelKey, nestedMenuSx: sx }), `${levelIndex}-${item.id}-${index}`))) })), footer && _jsx(Section, { children: footer })] })) })));
35
+ };
36
+ export default TreeNodeList;
@@ -0,0 +1,2 @@
1
+ import TreeNodeList from './TreeNodeList';
2
+ export default TreeNodeList;
@@ -0,0 +1,2 @@
1
+ import TreeNodeList from './TreeNodeList';
2
+ export default TreeNodeList;
@@ -0,0 +1,7 @@
1
+ /// <reference types="react" />
2
+ export declare const Root: import("@emotion/styled").StyledComponent<Omit<import("../../Menu/Menu").MenuProps, "ref"> & import("react").RefAttributes<HTMLDivElement> & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, {}, {}>;
3
+ export declare const ListOuterContainer: import("@emotion/styled").StyledComponent<import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>;
4
+ export declare const ListInnerContainer: import("@emotion/styled").StyledComponent<import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLUListElement>, HTMLUListElement>, {}>;
5
+ export declare const SearchInput: import("@emotion/styled").StyledComponent<Readonly<import("@mui/material").InputProps & {
6
+ isError?: boolean | undefined;
7
+ }> & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, {}, {}>;
@@ -0,0 +1,60 @@
1
+ import { styled } from '@mui/material/styles';
2
+ import { Input, Menu } from '../../index.js';
3
+ export const Root = styled(Menu)({
4
+ maxHeight: '100%',
5
+ });
6
+ export const ListOuterContainer = styled('div')({
7
+ position: 'relative',
8
+ '&:before, &:after': {
9
+ display: 'none',
10
+ },
11
+ '&.scrollable': {
12
+ position: 'relative',
13
+ '&::before, &::after': {
14
+ display: 'block',
15
+ content: '""',
16
+ position: 'absolute',
17
+ left: 0,
18
+ width: '100%',
19
+ height: '20px',
20
+ opacity: 1,
21
+ pointerEvents: 'none',
22
+ zIndex: 2,
23
+ transition: 'opacity 0.3s ease-out',
24
+ },
25
+ '&::before': {
26
+ top: 0,
27
+ background: 'linear-gradient(to top, transparent, rgba(255, 255, 255, 1))',
28
+ },
29
+ '&::after': {
30
+ bottom: 0,
31
+ background: 'linear-gradient(to bottom, transparent, rgba(255, 255, 255, 1))',
32
+ },
33
+ },
34
+ '&.scrolled-to-top': {
35
+ '&::before': {
36
+ opacity: 0,
37
+ },
38
+ },
39
+ '&.scrolled-to-bottom': {
40
+ '&::after': {
41
+ opacity: 0,
42
+ },
43
+ },
44
+ });
45
+ export const ListInnerContainer = styled('ul')({
46
+ listStyle: 'none',
47
+ padding: 0,
48
+ margin: 0,
49
+ maxHeight: 300,
50
+ overflow: 'auto',
51
+ });
52
+ export const SearchInput = styled(Input)(({ theme }) => ({
53
+ border: `1px solid ${theme.palette.divider}`,
54
+ '& .MuiInputBase-input': {
55
+ color: '#000000',
56
+ },
57
+ '.icon': {
58
+ width: 14,
59
+ },
60
+ }));
@@ -0,0 +1,15 @@
1
+ import { type PropsWithChildren } from 'react';
2
+ import { TreeMapper, TreeNode } from '../type';
3
+ type TreeDropdownContextType = {
4
+ treeMapper: TreeMapper;
5
+ handleSelect: (isChecked: boolean, targetNode: TreeNode) => void;
6
+ multiple?: boolean;
7
+ IconComponent?: React.ComponentType<{
8
+ fileId: string;
9
+ width?: number | string;
10
+ height?: number | string;
11
+ }>;
12
+ };
13
+ export declare const TreeDropdownContext: import("react").Context<TreeDropdownContextType>;
14
+ declare const TreeDropdownProvider: ({ children, treeMapper, handleSelect, multiple, IconComponent }: PropsWithChildren<TreeDropdownContextType>) => import("react/jsx-runtime").JSX.Element;
15
+ export default TreeDropdownProvider;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo, createContext } from 'react';
3
+ // eslint-disable-next-line react-refresh/only-export-components
4
+ export const TreeDropdownContext = createContext({
5
+ treeMapper: {},
6
+ handleSelect: () => { },
7
+ multiple: false,
8
+ });
9
+ const TreeDropdownProvider = ({ children, treeMapper, handleSelect, multiple, IconComponent }) => {
10
+ const ctxValues = useMemo(() => ({ treeMapper, handleSelect, multiple, IconComponent }), [treeMapper, handleSelect, multiple, IconComponent]);
11
+ return _jsx(TreeDropdownContext.Provider, Object.assign({ value: ctxValues }, { children: children }));
12
+ };
13
+ export default TreeDropdownProvider;
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { TreeNode } from '../type';
3
+ type UseSearchProps = {
4
+ initialValue?: string;
5
+ getValues?: (item: TreeNode) => string[];
6
+ onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
7
+ items: TreeNode[];
8
+ debounceMs?: number;
9
+ enabled?: boolean;
10
+ };
11
+ declare const useSearch: (props: UseSearchProps) => {
12
+ searchText: string;
13
+ filteredItems: TreeNode[];
14
+ handleSearch: (e: React.ChangeEvent<HTMLInputElement>) => void;
15
+ handleReset: () => void;
16
+ };
17
+ export default useSearch;
@@ -0,0 +1,38 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import debounce from 'lodash/debounce';
3
+ const useSearch = (props) => {
4
+ const { initialValue = '', getValues, onChange, items, enabled = false, debounceMs = 300 } = props;
5
+ const [searchText, setSearchText] = useState(initialValue);
6
+ const [debouncedText, setDebouncedText] = useState(initialValue);
7
+ const debouncedUpdate = useMemo(() => debounce((value) => {
8
+ setDebouncedText(value);
9
+ }, debounceMs), [debounceMs]);
10
+ useEffect(() => {
11
+ debouncedUpdate(searchText);
12
+ return () => debouncedUpdate.cancel();
13
+ }, [debouncedUpdate, searchText]);
14
+ const handleSearch = useCallback((e) => {
15
+ const value = e.target.value;
16
+ setSearchText(value);
17
+ debouncedUpdate(value);
18
+ onChange === null || onChange === void 0 ? void 0 : onChange(e);
19
+ }, [onChange, debouncedUpdate]);
20
+ const handleReset = useCallback(() => {
21
+ setSearchText('');
22
+ setDebouncedText('');
23
+ debouncedUpdate.cancel();
24
+ onChange === null || onChange === void 0 ? void 0 : onChange({ target: { value: '' } });
25
+ }, [onChange, debouncedUpdate]);
26
+ const filteredItems = useMemo(() => {
27
+ if (!enabled || !debouncedText.trim())
28
+ return items;
29
+ if (!getValues) {
30
+ console.error('`searchProps.getValues` is required for searching, provide it on the tree node level or the global search props');
31
+ return items;
32
+ }
33
+ const matchedText = debouncedText.toLowerCase();
34
+ return items.filter((item) => getValues(item).some((value) => value.toLowerCase().includes(matchedText)));
35
+ }, [items, debouncedText, getValues, enabled]);
36
+ return { searchText, filteredItems, handleSearch, handleReset };
37
+ };
38
+ export default useSearch;
@@ -0,0 +1,12 @@
1
+ /// <reference types="react" />
2
+ declare const useTreeDropdown: () => {
3
+ treeMapper: import("..").TreeMapper;
4
+ handleSelect: (isChecked: boolean, targetNode: import("..").TreeNode) => void;
5
+ multiple?: boolean | undefined;
6
+ IconComponent?: import("react").ComponentType<{
7
+ fileId: string;
8
+ width?: string | number | undefined;
9
+ height?: string | number | undefined;
10
+ }> | undefined;
11
+ };
12
+ export default useTreeDropdown;
@@ -0,0 +1,10 @@
1
+ import { useContext } from 'react';
2
+ import { TreeDropdownContext } from '../context/TreeDropdownProvider';
3
+ const useTreeDropdown = () => {
4
+ const context = useContext(TreeDropdownContext);
5
+ if (!context) {
6
+ throw new Error(`'useTreeDropdown' must be used within an 'TreeDropdownProvider'`);
7
+ }
8
+ return context;
9
+ };
10
+ export default useTreeDropdown;
@@ -0,0 +1,4 @@
1
+ import TreeDropdown from './TreeDropdown';
2
+ export default TreeDropdown;
3
+ export { TreeManager } from './utils';
4
+ export * from './type';
@@ -0,0 +1,4 @@
1
+ import TreeDropdown from './TreeDropdown';
2
+ export default TreeDropdown;
3
+ export { TreeManager } from './utils';
4
+ export * from './type';
@@ -0,0 +1,9 @@
1
+ /// <reference types="react" />
2
+ export declare const CollapseIcon: import("@emotion/styled").StyledComponent<import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme> & {
3
+ expanded: boolean;
4
+ }, import("react").DetailedHTMLProps<import("react").ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, {}>;
5
+ export declare const LoadingRow: import("@emotion/styled").StyledComponent<import("@mui/system").BoxOwnProps<import("@mui/material/styles").Theme> & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
6
+ ref?: ((instance: HTMLDivElement | null) => void) | import("react").RefObject<HTMLDivElement> | null | undefined;
7
+ }, keyof import("@mui/system").BoxOwnProps<import("@mui/material/styles").Theme>> & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, {}, {}>;
8
+ export declare const StyledMenuItem: import("@emotion/styled").StyledComponent<import("../MenuItem/MenuItem").MenuItemProps & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, {}, {}>;
9
+ export declare const Section: import("@emotion/styled").StyledComponent<import("../MenuItem/MenuItem").MenuItemProps & import("@mui/system").MUIStyledCommonProps<import("@mui/material/styles").Theme>, {}, {}>;
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import Box from '@mui/material/Box';
3
+ import { styled } from '@mui/material/styles';
4
+ import { MenuItem } from '../index.js';
5
+ export const CollapseIcon = styled('img', { shouldForwardProp: (props) => props !== 'expanded' })(({ theme, expanded }) => ({
6
+ transform: !expanded ? 'rotate(0deg)' : 'rotate(180deg)',
7
+ marginLeft: 'auto',
8
+ transition: theme.transitions.create('transform', {
9
+ duration: theme.transitions.duration.shortest,
10
+ }),
11
+ }));
12
+ export const LoadingRow = styled(Box)(() => ({
13
+ display: 'flex',
14
+ justifyContent: 'center',
15
+ alignItems: 'center',
16
+ paddingBlock: '4px',
17
+ borderTop: '1px solid #F2F2F2',
18
+ }));
19
+ export const StyledMenuItem = styled(MenuItem)({
20
+ minWidth: 120,
21
+ minHeight: 36,
22
+ maxHeight: 36,
23
+ paddingTop: 0,
24
+ paddingBottom: 0,
25
+ backgroundColor: '#fff',
26
+ '--menuitem-border-rad': '4px',
27
+ '&:hover': {
28
+ boxShadow: '0px 0px 16px rgba(0, 0, 0, 0.13)',
29
+ zIndex: 1,
30
+ },
31
+ });
32
+ export const Section = styled((props) => _jsx(StyledMenuItem, Object.assign({ hideCheckbox: true }, props)))(({ theme }) => ({
33
+ fontSize: 11,
34
+ fontWeight: 500,
35
+ color: theme.palette.grey['700'],
36
+ minHeight: 36,
37
+ maxHeight: 'auto',
38
+ cursor: 'auto',
39
+ ':hover': {
40
+ boxShadow: 'none',
41
+ zIndex: 1,
42
+ },
43
+ }));
@@ -0,0 +1,89 @@
1
+ /// <reference types="react" />
2
+ import { Input, MenuItem } from '../index.js';
3
+ import { type SxProps, type Theme } from '@mui/material/styles';
4
+ import { PopperProps } from '@mui/material/Popper';
5
+ export type TreeNodeIcon = {
6
+ fileId: string;
7
+ width?: number | string;
8
+ height?: number | string;
9
+ };
10
+ export type ExtendableObject<T> = T extends Record<string, any> ? T & Record<string, any> : never;
11
+ export type OnTreeMountedParams = {
12
+ state: TreeMapper;
13
+ tree: TreeNode[];
14
+ selectedTree: TreeNode[];
15
+ };
16
+ export type TreeDropdownProps = {
17
+ onMounted?: (params: OnTreeMountedParams) => void;
18
+ onChange?: (tree: TreeNode[]) => void;
19
+ multiple?: boolean;
20
+ anchorEl: HTMLElement | null;
21
+ placement?: PopperProps['placement'];
22
+ sx?: SxProps<Theme>;
23
+ treeDefinition: TreeDefinition;
24
+ exposeOnlySelectedTree?: boolean;
25
+ IconComponent?: React.ComponentType<{
26
+ fileId: string;
27
+ width?: number | string;
28
+ height?: number | string;
29
+ }>;
30
+ };
31
+ export type TreeDropdownRef = {
32
+ treeMapper: TreeMapper;
33
+ getFullTree: () => TreeNode[];
34
+ getSelectedTree: () => TreeNode[];
35
+ toggleAll: (selected: boolean) => void;
36
+ };
37
+ export type TreeNode = ExtendableObject<{
38
+ id: string;
39
+ label?: React.ReactNode;
40
+ hintText?: React.ReactNode;
41
+ icon?: TreeNodeIcon;
42
+ iconComponent?: React.ReactNode;
43
+ itemProps?: React.ComponentProps<typeof MenuItem>;
44
+ renderItem?: (params: {
45
+ nodeProps: TreeNode;
46
+ checked?: boolean;
47
+ indeterminate?: boolean;
48
+ onSelect: (e: React.ChangeEvent<HTMLInputElement>) => void;
49
+ }) => React.ReactNode;
50
+ }> & TreeDefinition;
51
+ export type TreeDefinition = {
52
+ header?: React.ReactNode;
53
+ footer?: React.ReactNode;
54
+ /**
55
+ * [Optional] Used to determine the level of the node in the tree
56
+ * It will appear as data attribute over the list element.
57
+ * ```
58
+ * <div ... data-level-key="some-unique-key"> ... </div>
59
+ * ```
60
+ */
61
+ levelKey?: string;
62
+ searchProps?: {
63
+ /**
64
+ * ### [Required For Search]
65
+ * The function to get the value of the item for the search
66
+ */
67
+ getValues: (item: TreeNode) => string[];
68
+ onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
69
+ noResultsText?: React.ReactNode;
70
+ inputProps?: React.ComponentProps<typeof Input>;
71
+ };
72
+ children?: TreeNode[];
73
+ disablePromotion?: boolean;
74
+ status?: `${SelectedState}`;
75
+ };
76
+ export declare enum SelectedState {
77
+ CHECKED = "CHECKED",
78
+ UNCHECKED = "UNCHECKED",
79
+ INDETERMINATE = "INDETERMINATE"
80
+ }
81
+ export type SelectedItems = {
82
+ [key: string]: `${SelectedState}`;
83
+ };
84
+ export type TreeMapper = {
85
+ [nodeKey: string]: {
86
+ status: `${SelectedState}`;
87
+ _parents?: string[];
88
+ };
89
+ };
@@ -0,0 +1,6 @@
1
+ export var SelectedState;
2
+ (function (SelectedState) {
3
+ SelectedState["CHECKED"] = "CHECKED";
4
+ SelectedState["UNCHECKED"] = "UNCHECKED";
5
+ SelectedState["INDETERMINATE"] = "INDETERMINATE";
6
+ })(SelectedState || (SelectedState = {}));
@@ -0,0 +1,21 @@
1
+ import { TreeMapper, TreeNode } from './type';
2
+ export declare function promoteSingleChild(items: TreeNode[]): TreeNode[];
3
+ export declare const getAncestorIdsByNodeId: (nodeId: string, nodes: TreeNode[], path?: TreeNode[]) => TreeNode[] | null;
4
+ export declare function collectCheckedIds(nodes: TreeNode[], multiple: boolean, acc?: string[]): string[];
5
+ export declare function normalizeStructure(nodes: TreeNode[], parents?: string[], acc?: TreeMapper, isParentSelected?: boolean): TreeMapper;
6
+ export declare function initializeTreeMapper(nodes: TreeNode[], multiple: boolean): TreeMapper;
7
+ export declare function denormalizeTree(nodes: TreeNode[], state: TreeMapper): TreeNode[];
8
+ export declare function updateTreeSelection(tree: TreeMapper, targetNodeId: string, isChecked: boolean): TreeMapper;
9
+ export declare function extractSelectedBranches(nodes: TreeNode[]): TreeNode[];
10
+ export declare function resetTreeState(tree: TreeMapper): TreeMapper;
11
+ export declare function updateSelection(tree: TreeMapper, targetNode: string, isChecked: boolean, multiple: boolean): TreeMapper;
12
+ export type GetNodesByLevelOptions = {
13
+ filter?: (node: TreeNode) => boolean;
14
+ includeChildren?: boolean;
15
+ };
16
+ export declare function filterNodesByLevelKey(nodes: TreeNode[], levelKey: string): TreeNode[];
17
+ export declare const TreeManager: {
18
+ denormalizeTree: import("memoize-one").MemoizedFn<(tree: TreeNode[], mapper: TreeMapper) => TreeNode[]>;
19
+ extractSelectedBranches: import("memoize-one").MemoizedFn<(nodes: TreeNode[]) => TreeNode[]>;
20
+ filterNodesByLevelKey: import("memoize-one").MemoizedFn<(nodes: TreeNode[], levelKey: string) => TreeNode[]>;
21
+ };
@@ -0,0 +1,161 @@
1
+ import memoizeOne from 'memoize-one';
2
+ import { SelectedState } from './type';
3
+ export function promoteSingleChild(items) {
4
+ function promote(item) {
5
+ var _a, _b, _c;
6
+ let children = item.children ? item.children.map(promote) : undefined;
7
+ // Promote children if exactly one child exists and it has children
8
+ if ((children === null || children === void 0 ? void 0 : children.length) === 1 && ((_a = children[0].children) === null || _a === void 0 ? void 0 : _a.length) && !children[0].disablePromotion) {
9
+ const onlyChild = children[0];
10
+ children = onlyChild.children;
11
+ item = Object.assign(Object.assign({}, item), { header: (_b = onlyChild.header) !== null && _b !== void 0 ? _b : item.header, footer: (_c = onlyChild.footer) !== null && _c !== void 0 ? _c : item.footer });
12
+ }
13
+ return Object.assign(Object.assign({}, item), { children });
14
+ }
15
+ return items.map(promote);
16
+ }
17
+ export const getAncestorIdsByNodeId = (nodeId, nodes, path = []) => {
18
+ for (const node of nodes) {
19
+ if (node.id === nodeId) {
20
+ return [...path, node];
21
+ }
22
+ if (node.children) {
23
+ const found = getAncestorIdsByNodeId(nodeId, node.children, [...path, node]);
24
+ if (found)
25
+ return found;
26
+ }
27
+ }
28
+ return null;
29
+ };
30
+ export function collectCheckedIds(nodes, multiple, acc = []) {
31
+ for (const node of nodes) {
32
+ if (node.status === SelectedState.CHECKED) {
33
+ acc.push(node.id);
34
+ }
35
+ if (node.children) {
36
+ collectCheckedIds(node.children, multiple, acc);
37
+ }
38
+ }
39
+ return acc;
40
+ }
41
+ export function normalizeStructure(nodes, parents = [], acc = {}, isParentSelected = false) {
42
+ var _a;
43
+ for (const node of nodes) {
44
+ const status = isParentSelected ? SelectedState.CHECKED : (_a = node.status) !== null && _a !== void 0 ? _a : SelectedState.UNCHECKED;
45
+ acc[node.id] = Object.assign({ status }, (parents.length ? { _parents: parents } : {}));
46
+ if (node.children) {
47
+ normalizeStructure(node.children, [...parents, node.id], acc, status === SelectedState.CHECKED);
48
+ }
49
+ }
50
+ return acc;
51
+ }
52
+ export function initializeTreeMapper(nodes, multiple) {
53
+ const base = normalizeStructure(nodes);
54
+ const checkedIds = collectCheckedIds(nodes, multiple);
55
+ let next = base;
56
+ for (const id of checkedIds) {
57
+ next = updateTreeSelection(next, id, true);
58
+ }
59
+ return next;
60
+ }
61
+ export function denormalizeTree(nodes, state) {
62
+ return nodes.map((node) => {
63
+ var _a, _b;
64
+ return (Object.assign(Object.assign({}, node), { status: (_b = (_a = state[node.id]) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : SelectedState.UNCHECKED, children: node.children ? denormalizeTree(node.children, state) : undefined }));
65
+ });
66
+ }
67
+ function buildChildrenMap(tree) {
68
+ var _a;
69
+ const map = {};
70
+ for (const key of Object.keys(tree)) {
71
+ const parents = tree[key]._parents;
72
+ if (!(parents === null || parents === void 0 ? void 0 : parents.length))
73
+ continue;
74
+ const parent = parents[parents.length - 1];
75
+ (_a = map[parent]) !== null && _a !== void 0 ? _a : (map[parent] = []);
76
+ map[parent].push(key);
77
+ }
78
+ return map;
79
+ }
80
+ export function updateTreeSelection(tree, targetNodeId, isChecked) {
81
+ const next = structuredClone(tree);
82
+ const childrenMap = buildChildrenMap(tree);
83
+ const targetStatus = isChecked ? SelectedState.CHECKED : SelectedState.UNCHECKED;
84
+ function updateDown(node) {
85
+ var _a;
86
+ next[node].status = targetStatus;
87
+ for (const child of (_a = childrenMap[node]) !== null && _a !== void 0 ? _a : []) {
88
+ updateDown(child);
89
+ }
90
+ }
91
+ function updateUp(node) {
92
+ var _a;
93
+ const parents = next[node]._parents;
94
+ if (!(parents === null || parents === void 0 ? void 0 : parents.length))
95
+ return;
96
+ const parent = parents[parents.length - 1];
97
+ const siblings = (_a = childrenMap[parent]) !== null && _a !== void 0 ? _a : [];
98
+ const statuses = siblings.map((id) => next[id].status);
99
+ if (statuses.every((s) => s === SelectedState.CHECKED)) {
100
+ next[parent].status = SelectedState.CHECKED;
101
+ }
102
+ else if (statuses.every((s) => s === SelectedState.UNCHECKED)) {
103
+ next[parent].status = SelectedState.UNCHECKED;
104
+ }
105
+ else {
106
+ next[parent].status = SelectedState.INDETERMINATE;
107
+ }
108
+ updateUp(parent);
109
+ }
110
+ updateDown(targetNodeId);
111
+ updateUp(targetNodeId);
112
+ return next;
113
+ }
114
+ export function extractSelectedBranches(nodes) {
115
+ function visit(node) {
116
+ var _a;
117
+ const children = (_a = node.children) === null || _a === void 0 ? void 0 : _a.map(visit).filter(Boolean);
118
+ const isSelected = node.status === SelectedState.CHECKED || node.status === SelectedState.INDETERMINATE;
119
+ if (isSelected || (children && children.length > 0)) {
120
+ return Object.assign(Object.assign({}, node), { children });
121
+ }
122
+ return null;
123
+ }
124
+ return nodes.map(visit).filter(Boolean);
125
+ }
126
+ export function resetTreeState(tree) {
127
+ const next = {};
128
+ for (const key in tree) {
129
+ next[key] = Object.assign({ status: SelectedState.UNCHECKED }, (tree[key]._parents ? { _parents: tree[key]._parents } : {}));
130
+ }
131
+ return next;
132
+ }
133
+ export function updateSelection(tree, targetNode, isChecked, multiple) {
134
+ if (multiple) {
135
+ return updateTreeSelection(tree, targetNode, isChecked);
136
+ }
137
+ // SINGLE SELECT
138
+ if (!isChecked) {
139
+ // unchecking in single mode clears everything
140
+ return resetTreeState(tree);
141
+ }
142
+ const cleared = resetTreeState(tree);
143
+ return updateTreeSelection(cleared, targetNode, true);
144
+ }
145
+ export function filterNodesByLevelKey(nodes, levelKey) {
146
+ const result = [];
147
+ function walk(node) {
148
+ var _a;
149
+ if (node.levelKey === levelKey) {
150
+ result.push(node);
151
+ }
152
+ (_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach((child) => walk(child));
153
+ }
154
+ nodes.forEach(walk);
155
+ return result;
156
+ }
157
+ export const TreeManager = {
158
+ denormalizeTree: memoizeOne((tree, mapper) => denormalizeTree(tree, mapper)),
159
+ extractSelectedBranches: memoizeOne((nodes) => extractSelectedBranches(nodes)),
160
+ filterNodesByLevelKey: memoizeOne((nodes, levelKey) => filterNodesByLevelKey(nodes, levelKey)),
161
+ };
@@ -153,3 +153,5 @@ export * from './SalesChannelFilter';
153
153
  export * from './ReferenceTypeFilter';
154
154
  export * from './ListColumnFilter';
155
155
  export * from './VerificationIcon';
156
+ export { default as TreeDropdown } from './TreeDropdown';
157
+ export * from './TreeDropdown';
@@ -153,3 +153,5 @@ export * from './SalesChannelFilter';
153
153
  export * from './ReferenceTypeFilter';
154
154
  export * from './ListColumnFilter';
155
155
  export * from './VerificationIcon';
156
+ export { default as TreeDropdown } from './TreeDropdown';
157
+ export * from './TreeDropdown';
@@ -14,3 +14,4 @@ export * from './useAppEventPublisher';
14
14
  export * from './useAppEventListener';
15
15
  export * from './useSelectedMerchantDetails';
16
16
  export * from './useToast';
17
+ export * from './useScrolledTo';
@@ -14,3 +14,4 @@ export * from './useAppEventPublisher';
14
14
  export * from './useAppEventListener';
15
15
  export * from './useSelectedMerchantDetails';
16
16
  export * from './useToast';
17
+ export * from './useScrolledTo';
@@ -0,0 +1,13 @@
1
+ /// <reference types="react" />
2
+ type UseScrolledToArgs = {
3
+ threshold?: number;
4
+ containerRef?: React.RefObject<HTMLElement>;
5
+ };
6
+ type ScrollSource = HTMLElement | React.UIEvent<HTMLElement, UIEvent>;
7
+ export declare function useScrolledTo({ threshold, containerRef }?: UseScrolledToArgs): {
8
+ handleScroll: (source: ScrollSource) => void;
9
+ scrolledToTop: boolean;
10
+ scrolledToBottom: boolean;
11
+ scrollable: boolean;
12
+ };
13
+ export {};
@@ -0,0 +1,45 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ const DEFAULT_THRESHOLD = 10;
3
+ export function useScrolledTo({ threshold = DEFAULT_THRESHOLD, containerRef } = {}) {
4
+ const [scrolledToTop, setScrolledToTop] = useState(true);
5
+ const [scrolledToBottom, setScrolledToBottom] = useState(false);
6
+ const [scrollable, setScrollable] = useState(false);
7
+ const updateFromElement = useCallback((el) => {
8
+ const { scrollTop, scrollHeight, clientHeight } = el;
9
+ setScrollable(scrollHeight > clientHeight + threshold);
10
+ setScrolledToTop(scrollTop <= threshold);
11
+ setScrolledToBottom(scrollHeight - (scrollTop + clientHeight) <= threshold);
12
+ }, [threshold]);
13
+ const handleScroll = useCallback((source) => {
14
+ if (!source)
15
+ return;
16
+ // React onScroll event
17
+ if ('currentTarget' in source) {
18
+ updateFromElement(source.currentTarget);
19
+ return;
20
+ }
21
+ // Direct HTMLElement
22
+ updateFromElement(source);
23
+ }, [updateFromElement]);
24
+ useEffect(() => {
25
+ if (!(containerRef === null || containerRef === void 0 ? void 0 : containerRef.current))
26
+ return;
27
+ const element = containerRef.current;
28
+ // Initial calculation
29
+ handleScroll(element);
30
+ // Watch for container size changes
31
+ const resizeObserver = new ResizeObserver(() => {
32
+ handleScroll(element);
33
+ });
34
+ resizeObserver.observe(element);
35
+ return () => {
36
+ resizeObserver.disconnect();
37
+ };
38
+ }, [containerRef, handleScroll]);
39
+ return {
40
+ handleScroll,
41
+ scrolledToTop,
42
+ scrolledToBottom,
43
+ scrollable,
44
+ };
45
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tap-payments/os-micro-frontend-shared",
3
3
  "description": "Shared components and utilities for Tap Payments micro frontends",
4
- "version": "0.1.396",
4
+ "version": "0.1.398",
5
5
  "testVersion": 0,
6
6
  "type": "module",
7
7
  "main": "build/index.js",