@antscorp/antsomi-ui 1.3.7-beta.24 → 1.3.7-beta.26

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 (26) hide show
  1. package/es/components/molecules/SearchPopover/SearchPopover.js +2 -2
  2. package/es/components/molecules/SearchPopover/styled.d.ts +12 -1
  3. package/es/components/molecules/SearchPopover/styled.js +1 -2
  4. package/es/components/molecules/SearchPopover/types.d.ts +4 -3
  5. package/es/components/molecules/VirtualizedMenu/components/MenuInline/MenuInline.d.ts +9 -10
  6. package/es/components/molecules/VirtualizedMenu/components/MenuInline/MenuInline.js +45 -332
  7. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/index.d.ts +9 -0
  8. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/index.js +5 -0
  9. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useFocusManagement.d.ts +23 -0
  10. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useFocusManagement.js +81 -0
  11. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useItemInteraction.d.ts +24 -0
  12. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useItemInteraction.js +32 -0
  13. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useKeyboardNavigation.d.ts +26 -0
  14. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useKeyboardNavigation.js +93 -0
  15. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useTreeState.d.ts +24 -0
  16. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useTreeState.js +94 -0
  17. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useVisibleItems.d.ts +7 -0
  18. package/es/components/molecules/VirtualizedMenu/components/MenuInline/hooks/useVisibleItems.js +132 -0
  19. package/es/components/organism/TextEditor/TextEditor.js +5 -7
  20. package/es/components/organism/TextEditor/extensions/ListItem.d.ts +10 -0
  21. package/es/components/organism/TextEditor/extensions/ListItem.js +93 -0
  22. package/es/components/organism/TextEditor/styled.js +1 -0
  23. package/es/components/organism/TextEditor/ui/FontPopover/FontPopover.js +12 -2
  24. package/es/components/organism/TextEditor/ui/TextAlignSelect/TextAlignSelect.js +1 -3
  25. package/es/components/organism/TextEditor/ui/Toolbar/actions/FontSizeAction.js +6 -1
  26. package/package.json +1 -1
@@ -5,7 +5,7 @@ import { StyledInput } from './styled';
5
5
  import { translate, translations } from '@antscorp/antsomi-ui/es/locales';
6
6
  import { SearchIcon } from '../../icons';
7
7
  export const SearchPopover = props => {
8
- const { inputSearchProps = {}, ...popoverProps } = props;
8
+ const { inputSearchProps = {}, inputSearchRef, ...popoverProps } = props;
9
9
  const { overlayClassName, arrow = false, children, content, trigger = ['click'], ...restPopoverProps } = popoverProps;
10
- return (_jsx(Popover, { trigger: trigger, arrow: arrow, destroyTooltipOnHide: true, ...restPopoverProps, overlayClassName: clsx('no-padding-content', overlayClassName), overlay: true, content: _jsxs(_Fragment, { children: [_jsx(StyledInput, { placeholder: inputSearchProps?.placeholder || `${translate(translations.global.search)}`, bordered: false, ...inputSearchProps, addonAfter: _jsx(SearchIcon, {}), withWrapper: false }), content] }), children: children }));
10
+ return (_jsx(Popover, { trigger: trigger, arrow: arrow, destroyTooltipOnHide: true, ...restPopoverProps, overlayClassName: clsx('no-padding-content', overlayClassName), overlay: true, content: _jsxs(_Fragment, { children: [_jsx(StyledInput, { ref: inputSearchRef, placeholder: inputSearchProps?.placeholder || `${translate(translations.global.search)}`, bordered: false, ...inputSearchProps, addonAfter: _jsx(SearchIcon, {}), withWrapper: false }), content] }), children: children }));
11
11
  };
@@ -1 +1,12 @@
1
- export declare const StyledInput: import("styled-components").StyledComponent<(props: any) => import("react/jsx-runtime").JSX.Element, any, {}, never>;
1
+ /// <reference types="react" />
2
+ export declare const StyledInput: import("styled-components").StyledComponent<import("react").ForwardRefExoticComponent<import("../../atoms/Input").InputProps & import("react").RefAttributes<import("rc-input").InputRef>> & {
3
+ readonly DefaultInput: import("react").ForwardRefExoticComponent<import("antd").InputProps & import("react").RefAttributes<import("rc-input").InputRef>> & {
4
+ Group: import("react").FC<import("antd/es/input").GroupProps>;
5
+ Search: import("react").ForwardRefExoticComponent<import("antd/es/input").SearchProps & import("react").RefAttributes<import("rc-input").InputRef>>;
6
+ TextArea: import("react").ForwardRefExoticComponent<import("antd/es/input").TextAreaProps & import("react").RefAttributes<import("antd/es/input/TextArea").TextAreaRef>>;
7
+ Password: import("react").ForwardRefExoticComponent<import("antd/es/input").PasswordProps & import("react").RefAttributes<import("rc-input").InputRef>>;
8
+ };
9
+ readonly CustomSearch: import("react").ForwardRefExoticComponent<import("../../atoms/Input").InputProps & import("react").RefAttributes<import("rc-input").InputRef>>;
10
+ readonly TextArea: import("react").ForwardRefExoticComponent<import("antd/es/input").TextAreaProps & import("react").RefAttributes<import("antd/es/input/TextArea").TextAreaRef>>;
11
+ readonly Password: import("react").ForwardRefExoticComponent<import("../../atoms/Input").InputProps & import("react").RefAttributes<import("rc-input").InputRef>>;
12
+ }, any, {}, never>;
@@ -1,8 +1,7 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
1
  import styled from 'styled-components';
3
2
  import { Input } from '../../atoms/Input';
4
3
  import { THEME } from '@antscorp/antsomi-ui/es/constants';
5
- export const StyledInput = styled(props => _jsx(Input, { ...props })) `
4
+ export const StyledInput = styled(Input) `
6
5
  &.antsomi-input-group-wrapper {
7
6
  padding: 8px 10px;
8
7
  border-bottom: 1px solid var(--divider-1);
@@ -1,7 +1,7 @@
1
- import { PopoverProps } from 'antd';
2
- import { InputProps } from '../../atoms/Input';
1
+ import type { InputRef, PopoverProps } from 'antd';
2
+ import type { InputProps } from '../../atoms/Input';
3
3
  import React, { PropsWithChildren } from 'react';
4
- import { MenuInlineProps } from '../VirtualizedMenu/types';
4
+ import type { MenuInlineProps } from '../VirtualizedMenu/types';
5
5
  export type SearchConfig = {
6
6
  searchWithKey?: boolean;
7
7
  };
@@ -9,6 +9,7 @@ export type SearchPopoverProps = PropsWithChildren<{
9
9
  inputSearchProps?: InputProps & {
10
10
  searchConfig?: SearchConfig;
11
11
  };
12
+ inputSearchRef?: React.RefObject<InputRef>;
12
13
  isAllowEmpty?: boolean;
13
14
  noItemContent?: React.ReactNode;
14
15
  } & PopoverProps>;
@@ -1,7 +1,6 @@
1
- import React from 'react';
2
- import { ItemType } from '../../types';
3
- export declare const MenuInline: React.MemoExoticComponent<React.ForwardRefExoticComponent<Partial<{
4
- items: ItemType[];
1
+ /// <reference types="react" />
2
+ export declare const MenuInline: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<Partial<{
3
+ items: import("../../types").ItemType[];
5
4
  inlineIndent: number;
6
5
  inlinePadding: number;
7
6
  itemSpacing: number;
@@ -12,13 +11,13 @@ export declare const MenuInline: React.MemoExoticComponent<React.ForwardRefExoti
12
11
  focusedKey: string;
13
12
  expanded: string[];
14
13
  onClick: (args: {
15
- item: ItemType;
16
- domEvent?: React.MouseEvent<Element, MouseEvent> | undefined;
14
+ item: import("../../types").ItemType;
15
+ domEvent?: import("react").MouseEvent<Element, MouseEvent> | undefined;
17
16
  elKey: "item" | "item-expand-el";
18
17
  }) => void;
19
- action: React.ReactNode | ((item: ItemType, others: {
20
- expandEl: React.ReactNode;
21
- }) => React.ReactNode);
18
+ action: import("react").ReactNode | ((item: import("../../types").ItemType, others: {
19
+ expandEl: import("react").ReactNode;
20
+ }) => import("react").ReactNode);
22
21
  }> & {
23
22
  mode?: "inline" | undefined;
24
- } & React.RefAttributes<import("../../types").VirtualizedMenuBaseHandle>>>;
23
+ } & import("react").RefAttributes<import("../../types").VirtualizedMenuBaseHandle>>>;
@@ -1,334 +1,55 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { forwardRef, useEffect, useImperativeHandle, useMemo, useState, useCallback, useRef, memo, } from 'react';
2
+ import { forwardRef, useImperativeHandle, useRef, memo } from 'react';
3
3
  import { StyledAutoSizer, VirtualizedMenuContainer } from '../../styled';
4
4
  import { INLINE_INDENT, INLINE_PADDING, ITEM_SIZE, ITEM_SPACING } from '../../config';
5
- import { CLS, serializeItems, getItemSize } from '../../utils';
5
+ import { CLS, getItemSize } from '../../utils';
6
6
  import { Item } from '../Item';
7
- import { useDeepCompareEffect } from '@antscorp/antsomi-ui/es/hooks';
8
7
  import clsx from 'clsx';
9
- import { findNodes, getChildrenRecursively, getPathToNode, } from '@antscorp/antsomi-ui/es/utils/tree';
8
+ import { useTreeState } from './hooks/useTreeState';
9
+ import { useVisibleItems } from './hooks/useVisibleItems';
10
+ import { useFocusManagement } from './hooks/useFocusManagement';
11
+ import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
12
+ import { useItemInteraction } from './hooks/useItemInteraction';
10
13
  const innerElementType = forwardRef(({ style, ...rest }, ref) => (_jsx("ul", { role: "tree", "aria-label": "Virtualized Menu", ref: ref, style: style, ...rest })));
11
- /**
12
- * Optimized function to compute visible items using breadth-first traversal
13
- * instead of filtering all items
14
- */
15
- const computeVisibleItemsOptimized = (normalizeTreeItems, rootItemIds, expandedKeys) => {
16
- const visibleItems = [];
17
- const queue = Array.from(rootItemIds);
18
- // Process queue in breadth-first manner
19
- let index = 0;
20
- while (index < queue.length) {
21
- const itemId = queue[index++];
22
- const item = normalizeTreeItems.get(itemId);
23
- if (!item)
24
- continue;
25
- visibleItems.push(item);
26
- // Add children to queue only if this item is expanded
27
- if (expandedKeys.has(item.id) && item.childIds.length > 0) {
28
- queue.push(...item.childIds);
29
- }
30
- }
31
- return visibleItems;
32
- };
33
- /**
34
- * Alternative: Path-based optimization with memoization
35
- * Caches path calculations to avoid redundant work
36
- */
37
- const computeVisibleItemsWithPathCache = (normalizeTreeItems, expandedKeys, pathCache) => {
38
- const visibleItems = [];
39
- for (const item of normalizeTreeItems.values()) {
40
- const isRootItem = item.level === 0;
41
- if (isRootItem) {
42
- visibleItems.push(item);
43
- continue;
44
- }
45
- if (!item.parentId)
46
- continue;
47
- // Use cached path or compute and cache
48
- let pathToParent = pathCache.get(item.parentId);
49
- if (!pathToParent) {
50
- pathToParent = getPathToNode(item.parentId, normalizeTreeItems);
51
- pathCache.set(item.parentId, pathToParent);
52
- }
53
- // Check if all parents are expanded
54
- const allParentExpanded = pathToParent.every(parent => expandedKeys.has(parent.id));
55
- if (allParentExpanded) {
56
- visibleItems.push(item);
57
- }
58
- }
59
- return visibleItems;
60
- };
61
- /**
62
- * Level-based optimization: Process items level by level
63
- * More efficient for deep trees with many collapsed branches
64
- */
65
- const computeVisibleItemsByLevel = (normalizeTreeItems, rootItemIds, expandedKeys) => {
66
- const visibleItems = [];
67
- const expandedParents = new Set();
68
- // Add root items and track expanded ones
69
- for (const rootId of rootItemIds) {
70
- const rootItem = normalizeTreeItems.get(rootId);
71
- if (rootItem) {
72
- visibleItems.push(rootItem);
73
- if (expandedKeys.has(rootId)) {
74
- expandedParents.add(rootId);
75
- }
76
- }
77
- }
78
- // Process each level
79
- let currentLevelParents = expandedParents;
80
- let currentLevel = 1;
81
- while (currentLevelParents.size > 0) {
82
- const nextLevelParents = new Set();
83
- // Process all items at current level
84
- for (const parentId of currentLevelParents) {
85
- const parent = normalizeTreeItems.get(parentId);
86
- if (!parent)
87
- continue;
88
- // Add all children of this expanded parent
89
- for (const childId of parent.childIds) {
90
- const child = normalizeTreeItems.get(childId);
91
- if (child && child.level === currentLevel) {
92
- visibleItems.push(child);
93
- // Track if this child is expanded for next level
94
- if (expandedKeys.has(childId)) {
95
- nextLevelParents.add(childId);
96
- }
97
- }
98
- }
99
- }
100
- currentLevelParents = nextLevelParents;
101
- currentLevel++;
102
- }
103
- return visibleItems;
104
- };
105
14
  const MenuInlineInner = forwardRef((props, ref) => {
106
15
  const { items: itemsProp = [], expanded: expandedProp = [], selected: selectedProp = [], focusedKey, inlineIndent = INLINE_INDENT, inlinePadding = INLINE_PADDING, itemSize = ITEM_SIZE, itemSpacing = ITEM_SPACING, selectable = false, action, className, onClick, } = props;
107
- const [focusId, setFocusId] = useState(null);
108
- const [expandedKeys, setExpandedKeys] = useState(new Set());
109
- const [selectedKeys, setSelectedKeys] = useState(new Set());
16
+ // Refs for list and items
110
17
  const listRef = useRef(null);
111
18
  const itemRefs = useRef(new Map());
112
- const [{ items: normalizeTreeItems, rootItemIds }, setTreeState] = useState({
113
- items: new Map(),
114
- rootItemIds: new Set(),
19
+ const isMouseDownRef = useRef(false);
20
+ // Tree state management
21
+ const { normalizeTreeItems, rootItemIds, expandedKeys, selectedKeys, handleExpandItems, handleCollapseItems, handleToggleExpandItem, handleSelectItem, expandAll, } = useTreeState({
22
+ items: itemsProp,
23
+ expanded: expandedProp,
24
+ selected: selectedProp,
25
+ selectable,
115
26
  });
116
- // Cache for path calculations to avoid redundant work
117
- const pathCache = useMemo(() => new Map(), []);
118
- const rootNodes = useMemo(() => findNodes(node => rootItemIds.has(node.id), normalizeTreeItems), [normalizeTreeItems, rootItemIds]);
119
- useDeepCompareEffect(() => {
120
- setSelectedKeys(new Set(selectedProp));
121
- }, [selectedProp]);
122
- useDeepCompareEffect(() => {
123
- setExpandedKeys(new Set(expandedProp));
124
- }, [expandedProp]);
125
- // Sync external focusedKey prop with internal focusId state for auto-scrolling
126
- useEffect(() => {
127
- if (focusedKey !== undefined) {
128
- setFocusId(focusedKey);
129
- }
130
- }, [focusedKey]);
131
- useEffect(() => {
132
- setTreeState(serializeItems({
133
- items: itemsProp,
134
- }));
135
- }, [itemsProp]);
136
- // Clear path cache when tree structure changes
137
- useEffect(() => {
138
- pathCache.clear();
139
- }, [normalizeTreeItems, pathCache]);
140
- // Optimized visible items calculation
141
- const visibleItems = useMemo(() => {
142
- if (normalizeTreeItems.size === 0)
143
- return [];
144
- // Choose optimization strategy based on tree characteristics
145
- const treeDepth = Math.max(...Array.from(normalizeTreeItems.values()).map(item => item.level));
146
- const totalItems = normalizeTreeItems.size;
147
- const expandedCount = expandedKeys.size;
148
- // Strategy selection heuristics:
149
- // - For shallow trees with many expanded items: use breadth-first
150
- // - For deep trees with few expanded items: use level-based
151
- // - For medium trees: use path-cache optimization
152
- if (treeDepth <= 3 && expandedCount > totalItems * 0.3) {
153
- // Shallow tree with many expanded items
154
- return computeVisibleItemsOptimized(normalizeTreeItems, rootItemIds, expandedKeys);
155
- }
156
- if (treeDepth > 5 && expandedCount < totalItems * 0.2) {
157
- // Deep tree with few expanded items
158
- return computeVisibleItemsByLevel(normalizeTreeItems, rootItemIds, expandedKeys);
159
- }
160
- // Medium complexity - use cached path approach
161
- return computeVisibleItemsWithPathCache(normalizeTreeItems, expandedKeys, pathCache);
162
- }, [expandedKeys, normalizeTreeItems, rootItemIds, pathCache]);
163
- const handleCollapseItems = useCallback((items) => {
164
- setExpandedKeys(prev => {
165
- const newOpenKeys = new Set(prev);
166
- items.forEach(({ data }) => {
167
- if (prev.has(data.key)) {
168
- newOpenKeys.delete(data.key);
169
- }
170
- });
171
- return newOpenKeys;
172
- });
173
- }, []);
174
- useEffect(() => {
175
- if (focusId && listRef.current) {
176
- const index = visibleItems.findIndex(item => item.id === focusId);
177
- if (index !== -1) {
178
- listRef.current.scrollToItem(index, 'auto');
179
- }
180
- }
181
- }, [focusId, visibleItems]);
182
- const handleExpandItems = useCallback((items, deep = 0) => {
183
- setExpandedKeys(prev => {
184
- const updatedExpandedKeys = new Set(prev);
185
- items.forEach(item => {
186
- const normalizeItem = normalizeTreeItems.get(item.data.key);
187
- if (normalizeItem) {
188
- updatedExpandedKeys.add(normalizeItem.id);
189
- if (deep > 0) {
190
- getChildrenRecursively(normalizeItem.id, normalizeTreeItems, normalizeItem.level + deep).forEach(v => updatedExpandedKeys.add(v.id));
191
- }
192
- }
193
- });
194
- return updatedExpandedKeys;
195
- });
196
- }, [normalizeTreeItems]);
197
- useImperativeHandle(ref, () => ({
198
- expandAll: () => {
199
- handleExpandItems(rootNodes, Infinity);
200
- },
201
- }));
202
- const handleSelectItem = useCallback((item) => {
203
- const { key } = item.data;
204
- if (!selectable || selectedKeys.has(key) || item.data.displayOnly)
205
- return;
206
- setSelectedKeys(new Set([key]));
207
- }, [selectable, selectedKeys]);
208
- const handleToggleExpandItem = useCallback((item) => {
209
- const { key } = item.data;
210
- const isExpanded = expandedKeys.has(key);
211
- if (isExpanded) {
212
- handleCollapseItems([item]);
213
- }
214
- else {
215
- handleExpandItems([item]);
216
- }
217
- }, [expandedKeys, handleCollapseItems, handleExpandItems]);
218
- const handleClickItem = useCallback((args) => {
219
- const { item, elKey, domEvent } = args;
220
- const node = normalizeTreeItems.get(item.id);
221
- if (!node || node.data.displayOnly)
222
- return;
223
- const hasChilren = node.childIds.length > 0;
224
- handleSelectItem(node);
225
- if (hasChilren) {
226
- handleToggleExpandItem(node);
227
- }
228
- setFocusId(node.id);
229
- onClick?.({ item: node.data, elKey, domEvent });
230
- }, [normalizeTreeItems, handleSelectItem, handleToggleExpandItem, onClick]);
231
- // Helper function to find next focusable item (skip displayOnly items)
232
- const findNextFocusableItem = useCallback((startIndex, direction) => {
233
- const increment = direction === 'next' ? 1 : -1;
234
- let index = startIndex + increment;
235
- while (index >= 0 && index < visibleItems.length) {
236
- const item = visibleItems[index];
237
- if (!item.data.displayOnly) {
238
- return index;
239
- }
240
- index += increment;
241
- }
242
- return startIndex;
243
- }, [visibleItems]);
244
- const findFirstFocusableItem = useCallback(() => {
245
- for (let i = 0; i < visibleItems.length; i++) {
246
- if (!visibleItems[i].data.displayOnly) {
247
- return i;
248
- }
249
- }
250
- return 0;
251
- }, [visibleItems]);
252
- const findLastFocusableItem = useCallback(() => {
253
- for (let i = visibleItems.length - 1; i >= 0; i--) {
254
- if (!visibleItems[i].data.displayOnly) {
255
- return i;
256
- }
257
- }
258
- return visibleItems.length - 1;
259
- }, [visibleItems]);
260
- const handleKeyDown = useCallback((e) => {
261
- if (visibleItems.length === 0)
262
- return;
263
- e.preventDefault();
264
- const currentFocusIndex = visibleItems.findIndex(item => item.id === focusId);
265
- if (currentFocusIndex === -1) {
266
- const firstFocusableIndex = findFirstFocusableItem();
267
- setFocusId(visibleItems[firstFocusableIndex]?.id ?? null);
268
- return;
269
- }
270
- const currentFocusItem = visibleItems[currentFocusIndex];
271
- switch (e.key) {
272
- case 'ArrowDown': {
273
- const nextFocusIndex = findNextFocusableItem(currentFocusIndex, 'next');
274
- setFocusId(visibleItems[nextFocusIndex].id);
275
- break;
276
- }
277
- case 'ArrowUp': {
278
- const nextFocusIndex = findNextFocusableItem(currentFocusIndex, 'prev');
279
- setFocusId(visibleItems[nextFocusIndex].id);
280
- break;
281
- }
282
- case 'ArrowRight': {
283
- if (currentFocusItem.childIds.length > 0 && !expandedKeys.has(currentFocusItem.id)) {
284
- handleExpandItems([currentFocusItem]);
285
- }
286
- break;
287
- }
288
- case 'ArrowLeft': {
289
- if (currentFocusItem.childIds.length > 0 && expandedKeys.has(currentFocusItem.id)) {
290
- handleCollapseItems([currentFocusItem]);
291
- }
292
- else if (currentFocusItem?.parentId) {
293
- // Find parent and ensure it's focusable
294
- const parentItem = normalizeTreeItems.get(currentFocusItem.parentId);
295
- if (parentItem && !parentItem.data.displayOnly) {
296
- setFocusId(currentFocusItem.parentId);
297
- }
298
- }
299
- break;
300
- }
301
- case 'Home': {
302
- const firstFocusableIndex = findFirstFocusableItem();
303
- setFocusId(visibleItems[firstFocusableIndex].id);
304
- break;
305
- }
306
- case 'End': {
307
- const lastFocusableIndex = findLastFocusableItem();
308
- setFocusId(visibleItems[lastFocusableIndex].id);
309
- break;
310
- }
311
- case 'Enter': {
312
- if (currentFocusItem && !currentFocusItem.data.displayOnly) {
313
- handleSelectItem(currentFocusItem);
314
- if (currentFocusItem.childIds.length > 0) {
315
- handleToggleExpandItem(currentFocusItem);
316
- }
317
- onClick?.({
318
- item: currentFocusItem.data,
319
- elKey: 'item',
320
- });
321
- }
322
- break;
323
- }
324
- default:
325
- break;
326
- }
327
- }, [
27
+ // Visible items computation
28
+ const visibleItems = useVisibleItems(normalizeTreeItems, rootItemIds, expandedKeys);
29
+ // Focus management
30
+ const { focusId, setFocusId, findNextFocusableItem, findFirstFocusableItem, findLastFocusableItem, handleFocus, } = useFocusManagement({
31
+ visibleItems,
32
+ focusedKey,
33
+ listRef,
34
+ itemRefs,
35
+ isMouseDownRef,
36
+ });
37
+ // Item interaction
38
+ const { handleClickItem, handleMouseDown } = useItemInteraction({
39
+ normalizeTreeItems,
40
+ handleSelectItem,
41
+ handleToggleExpandItem,
42
+ setFocusId,
43
+ onClick,
44
+ isMouseDownRef,
45
+ });
46
+ // Keyboard navigation
47
+ const { handleKeyDown } = useKeyboardNavigation({
328
48
  visibleItems,
329
49
  focusId,
330
50
  expandedKeys,
331
- onClick,
51
+ normalizeTreeItems,
52
+ setFocusId,
332
53
  handleExpandItems,
333
54
  handleCollapseItems,
334
55
  handleSelectItem,
@@ -336,20 +57,12 @@ const MenuInlineInner = forwardRef((props, ref) => {
336
57
  findNextFocusableItem,
337
58
  findFirstFocusableItem,
338
59
  findLastFocusableItem,
339
- normalizeTreeItems,
340
- ]);
341
- const handleFocus = (e) => {
342
- let isClickFont = false;
343
- for (const itemEl of itemRefs.current.values()) {
344
- if (isClickFont)
345
- break;
346
- isClickFont = itemEl.isSameNode(e.target) || itemEl.contains(e.target);
347
- }
348
- if (!isClickFont && !focusId && visibleItems.length > 0) {
349
- const firstFocusableIndex = findFirstFocusableItem();
350
- setFocusId(visibleItems[firstFocusableIndex]?.id || null);
351
- }
352
- };
60
+ onClick,
61
+ });
62
+ // Expose expandAll method via ref
63
+ useImperativeHandle(ref, () => ({
64
+ expandAll,
65
+ }));
353
66
  const itemData = {
354
67
  itemSpacing,
355
68
  inlineIndent,
@@ -368,6 +81,6 @@ const MenuInlineInner = forwardRef((props, ref) => {
368
81
  })),
369
82
  onClick: handleClickItem,
370
83
  };
371
- return (_jsx(StyledAutoSizer, { className: clsx(CLS.Root.default, className), tabIndex: 0, onFocus: handleFocus, onKeyDown: handleKeyDown, children: autoSize => (_jsx(VirtualizedMenuContainer, { ref: listRef, className: clsx(CLS.MenuInline.default), itemSize: getItemSize(itemSize, itemSpacing), itemCount: visibleItems.length, height: autoSize.height, width: autoSize.width, innerElementType: innerElementType, itemData: itemData, useIsScrolling: true, children: Item })) }));
84
+ return (_jsx(StyledAutoSizer, { className: clsx(CLS.Root.default, className), tabIndex: 0, onFocus: handleFocus, onKeyDown: handleKeyDown, onMouseDown: handleMouseDown, children: autoSize => (_jsx(VirtualizedMenuContainer, { ref: listRef, className: clsx(CLS.MenuInline.default), itemSize: getItemSize(itemSize, itemSpacing), itemCount: visibleItems.length, height: autoSize.height, width: autoSize.width, innerElementType: innerElementType, itemData: itemData, useIsScrolling: true, children: Item })) }));
372
85
  });
373
86
  export const MenuInline = memo(MenuInlineInner);
@@ -0,0 +1,9 @@
1
+ export { useTreeState } from './useTreeState';
2
+ export type { UseTreeStateReturn, UseTreeStateProps } from './useTreeState';
3
+ export { useVisibleItems } from './useVisibleItems';
4
+ export { useFocusManagement } from './useFocusManagement';
5
+ export type { UseFocusManagementReturn, UseFocusManagementProps } from './useFocusManagement';
6
+ export { useKeyboardNavigation } from './useKeyboardNavigation';
7
+ export type { UseKeyboardNavigationReturn, UseKeyboardNavigationProps, } from './useKeyboardNavigation';
8
+ export { useItemInteraction } from './useItemInteraction';
9
+ export type { UseItemInteractionReturn, UseItemInteractionProps } from './useItemInteraction';
@@ -0,0 +1,5 @@
1
+ export { useTreeState } from './useTreeState';
2
+ export { useVisibleItems } from './useVisibleItems';
3
+ export { useFocusManagement } from './useFocusManagement';
4
+ export { useKeyboardNavigation } from './useKeyboardNavigation';
5
+ export { useItemInteraction } from './useItemInteraction';
@@ -0,0 +1,23 @@
1
+ /// <reference types="react" />
2
+ import { VariableSizeList } from 'react-window';
3
+ import { NormalizedNode } from '@antscorp/antsomi-ui/es/utils/tree';
4
+ import { ItemType } from '../../../types';
5
+ export interface UseFocusManagementReturn {
6
+ focusId: string | null;
7
+ setFocusId: (id: string | null) => void;
8
+ findNextFocusableItem: (startIndex: number, direction: 'next' | 'prev') => number;
9
+ findFirstFocusableItem: () => number;
10
+ findLastFocusableItem: () => number;
11
+ handleFocus: (e: React.FocusEvent) => void;
12
+ }
13
+ export interface UseFocusManagementProps {
14
+ visibleItems: NormalizedNode<ItemType>[];
15
+ focusedKey?: string;
16
+ listRef: React.RefObject<VariableSizeList | null>;
17
+ itemRefs: React.RefObject<Map<string, HTMLLIElement>>;
18
+ isMouseDownRef: React.MutableRefObject<boolean>;
19
+ }
20
+ /**
21
+ * Custom hook to manage focus state and navigation for tree items
22
+ */
23
+ export declare const useFocusManagement: (props: UseFocusManagementProps) => UseFocusManagementReturn;
@@ -0,0 +1,81 @@
1
+ import { useState, useCallback, useEffect } from 'react';
2
+ /**
3
+ * Custom hook to manage focus state and navigation for tree items
4
+ */
5
+ export const useFocusManagement = (props) => {
6
+ const { visibleItems, focusedKey, listRef, itemRefs, isMouseDownRef } = props;
7
+ const [focusId, setFocusId] = useState(null);
8
+ // Sync external focusedKey prop with internal focusId state for auto-scrolling
9
+ useEffect(() => {
10
+ if (focusedKey !== undefined) {
11
+ setFocusId(focusedKey);
12
+ }
13
+ }, [focusedKey]);
14
+ // Auto-scroll to focused item
15
+ useEffect(() => {
16
+ if (focusId && listRef.current) {
17
+ const index = visibleItems.findIndex(item => item.id === focusId);
18
+ if (index !== -1) {
19
+ listRef.current.scrollToItem(index, 'auto');
20
+ }
21
+ }
22
+ }, [focusId, visibleItems, listRef]);
23
+ // Helper function to find next focusable item (skip displayOnly items)
24
+ const findNextFocusableItem = useCallback((startIndex, direction) => {
25
+ const increment = direction === 'next' ? 1 : -1;
26
+ let index = startIndex + increment;
27
+ while (index >= 0 && index < visibleItems.length) {
28
+ const item = visibleItems[index];
29
+ if (!item.data.displayOnly) {
30
+ return index;
31
+ }
32
+ index += increment;
33
+ }
34
+ return startIndex;
35
+ }, [visibleItems]);
36
+ // Find first focusable item
37
+ const findFirstFocusableItem = useCallback(() => {
38
+ for (let i = 0; i < visibleItems.length; i++) {
39
+ if (!visibleItems[i].data.displayOnly) {
40
+ return i;
41
+ }
42
+ }
43
+ return 0;
44
+ }, [visibleItems]);
45
+ // Find last focusable item
46
+ const findLastFocusableItem = useCallback(() => {
47
+ for (let i = visibleItems.length - 1; i >= 0; i--) {
48
+ if (!visibleItems[i].data.displayOnly) {
49
+ return i;
50
+ }
51
+ }
52
+ return visibleItems.length - 1;
53
+ }, [visibleItems]);
54
+ // Handle focus event with auto-focusing logic
55
+ const handleFocus = useCallback((e) => {
56
+ // Skip auto-focusing if user is clicking on menu items
57
+ if (isMouseDownRef.current) {
58
+ return;
59
+ }
60
+ let isClickFont = false;
61
+ if (itemRefs.current) {
62
+ for (const itemEl of itemRefs.current.values()) {
63
+ if (isClickFont)
64
+ break;
65
+ isClickFont = itemEl.isSameNode(e.target) || itemEl.contains(e.target);
66
+ }
67
+ }
68
+ if (!isClickFont && !focusId && visibleItems.length > 0) {
69
+ const firstFocusableIndex = findFirstFocusableItem();
70
+ setFocusId(visibleItems[firstFocusableIndex]?.id || null);
71
+ }
72
+ }, [isMouseDownRef, itemRefs, focusId, visibleItems, findFirstFocusableItem]);
73
+ return {
74
+ focusId,
75
+ setFocusId,
76
+ findNextFocusableItem,
77
+ findFirstFocusableItem,
78
+ findLastFocusableItem,
79
+ handleFocus,
80
+ };
81
+ };
@@ -0,0 +1,24 @@
1
+ /// <reference types="react" />
2
+ import { NormalizedNode } from '@antscorp/antsomi-ui/es/utils/tree';
3
+ import { ItemType, MenuInlineProps } from '../../../types';
4
+ export interface UseItemInteractionReturn {
5
+ handleClickItem: (args: {
6
+ item: NormalizedNode<ItemType>;
7
+ elKey: 'item' | 'item-expand-el';
8
+ domEvent: React.MouseEvent;
9
+ }) => void;
10
+ handleMouseDown: () => void;
11
+ }
12
+ export interface UseItemInteractionProps {
13
+ normalizeTreeItems: Map<string, NormalizedNode<ItemType>>;
14
+ handleSelectItem: (item: NormalizedNode<ItemType>) => void;
15
+ handleToggleExpandItem: (item: NormalizedNode<ItemType>) => void;
16
+ setFocusId: (id: string) => void;
17
+ onClick?: MenuInlineProps['onClick'];
18
+ isMouseDownRef: React.MutableRefObject<boolean>;
19
+ }
20
+ /**
21
+ * Custom hook to handle user interactions with menu items
22
+ * Manages click events and mouse down state
23
+ */
24
+ export declare const useItemInteraction: (props: UseItemInteractionProps) => UseItemInteractionReturn;