Skip to content

Commit

Permalink
[TreeView] Add JSDoc to every instance method (#13219)
Browse files Browse the repository at this point in the history
Signed-off-by: Flavien DELANGLE <flaviendelangle@gmail.com>
Co-authored-by: Nora <72460825+noraleonte@users.noreply.github.com>
  • Loading branch information
flaviendelangle and noraleonte committed May 23, 2024
1 parent b6b7e89 commit 1a453ab
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
return temp;
}, [models.expandedItems.value]);

const setExpandedItems = (event: React.SyntheticEvent, value: string[]) => {
const setExpandedItems = (event: React.SyntheticEvent, value: TreeViewItemId[]) => {
params.onExpandedItemsChange?.(event, value);
models.expandedItems.setControlledValue(value);
};
Expand All @@ -33,13 +33,15 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
[instance],
);

const toggleItemExpansion = useEventCallback((event: React.SyntheticEvent, itemId: string) => {
const isExpandedBefore = instance.isItemExpanded(itemId);
instance.setItemExpansion(event, itemId, !isExpandedBefore);
});
const toggleItemExpansion = useEventCallback(
(event: React.SyntheticEvent, itemId: TreeViewItemId) => {
const isExpandedBefore = instance.isItemExpanded(itemId);
instance.setItemExpansion(event, itemId, !isExpandedBefore);
},
);

const setItemExpansion = useEventCallback(
(event: React.SyntheticEvent, itemId: string, isExpanded: boolean) => {
(event: React.SyntheticEvent, itemId: TreeViewItemId, isExpanded: boolean) => {
const isExpandedBefore = instance.isItemExpanded(itemId);
if (isExpandedBefore === isExpanded) {
return;
Expand All @@ -60,7 +62,7 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
},
);

const expandAllSiblings = (event: React.KeyboardEvent, itemId: string) => {
const expandAllSiblings = (event: React.KeyboardEvent, itemId: TreeViewItemId) => {
const itemMeta = instance.getItemMeta(itemId);
const siblings = instance.getItemOrderedChildrenIds(itemMeta.parentId);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { DefaultizedProps, TreeViewPluginSignature } from '../../models';
import { UseTreeViewItemsSignature } from '../useTreeViewItems';
import { TreeViewItemId } from '../../../models';

export interface UseTreeViewExpansionPublicAPI {
/**
Expand All @@ -13,10 +14,33 @@ export interface UseTreeViewExpansionPublicAPI {
}

export interface UseTreeViewExpansionInstance extends UseTreeViewExpansionPublicAPI {
isItemExpanded: (itemId: string) => boolean;
isItemExpandable: (itemId: string) => boolean;
toggleItemExpansion: (event: React.SyntheticEvent, itemId: string) => void;
expandAllSiblings: (event: React.KeyboardEvent, itemId: string) => void;
/**
* Check if an item is expanded.
* @param {TreeViewItemId} itemId The id of the item to check.
* @returns {boolean} `true` if the item is expanded, `false` otherwise.
*/
isItemExpanded: (itemId: TreeViewItemId) => boolean;
/**
* Check if an item is expandable.
* Currently, an item is expandable if it has children.
* In the future, the user should be able to flag an item as expandable even if it has no loaded children to support children lazy loading.
* @param {TreeViewItemId} itemId The id of the item to check.
* @returns {boolean} `true` if the item can be expanded, `false` otherwise.
*/
isItemExpandable: (itemId: TreeViewItemId) => boolean;
/**
* Toggle the current expansion of an item.
* If it is expanded, it will be collapsed, and vice versa.
* @param {React.SyntheticEvent} event The UI event that triggered the change.
* @param {TreeViewItemId} itemId The id of the item to toggle.
*/
toggleItemExpansion: (event: React.SyntheticEvent, itemId: TreeViewItemId) => void;
/**
* Expand all the siblings (i.e.: the items that have the same parent) of a given item.
* @param {React.SyntheticEvent} event The UI event that triggered the change.
* @param {TreeViewItemId} itemId The id of the item whose siblings will be expanded.
*/
expandAllSiblings: (event: React.KeyboardEvent, itemId: TreeViewItemId) => void;
}

export interface UseTreeViewExpansionParameters {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,20 @@ import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler';
import { getActiveElement } from '../../utils/utils';
import { getFirstNavigableItem } from '../../utils/tree';
import { MuiCancellableEvent } from '../../models/MuiCancellableEvent';
import { convertSelectedItemsToArray } from '../useTreeViewSelection/useTreeViewSelection.utils';

const useTabbableItemId = (
const useDefaultFocusableItemId = (
instance: TreeViewUsedInstance<UseTreeViewFocusSignature>,
selectedItems: string | string[] | null,
) => {
const isItemVisible = (itemId: string) => {
): string => {
let tabbableItemId = convertSelectedItemsToArray(selectedItems).find((itemId) => {
if (!instance.isItemNavigable(itemId)) {
return false;
}

const itemMeta = instance.getItemMeta(itemId);
return itemMeta && (itemMeta.parentId == null || instance.isItemExpanded(itemMeta.parentId));
};

let tabbableItemId: string | null | undefined;
if (Array.isArray(selectedItems)) {
tabbableItemId = selectedItems.find(isItemVisible);
} else if (selectedItems != null && isItemVisible(selectedItems)) {
tabbableItemId = selectedItems;
}
});

if (tabbableItemId == null) {
tabbableItemId = getFirstNavigableItem(instance);
Expand All @@ -40,7 +38,7 @@ export const useTreeViewFocus: TreeViewPlugin<UseTreeViewFocusSignature> = ({
models,
rootRef,
}) => {
const tabbableItemId = useTabbableItemId(instance, models.selectedItems.value);
const defaultFocusableItemId = useDefaultFocusableItemId(instance, models.selectedItems.value);

const setFocusedItemId = useEventCallback((itemId: React.SetStateAction<string | null>) => {
const cleanItemId = typeof itemId === 'function' ? itemId(state.focusedItemId) : itemId;
Expand Down Expand Up @@ -88,21 +86,6 @@ export const useTreeViewFocus: TreeViewPlugin<UseTreeViewFocusSignature> = ({
}
});

const focusDefaultItem = useEventCallback((event: React.SyntheticEvent | null) => {
let itemToFocusId: string | null | undefined;
if (Array.isArray(models.selectedItems.value)) {
itemToFocusId = models.selectedItems.value.find(isItemVisible);
} else if (models.selectedItems.value != null && isItemVisible(models.selectedItems.value)) {
itemToFocusId = models.selectedItems.value;
}

if (itemToFocusId == null) {
itemToFocusId = getFirstNavigableItem(instance);
}

innerFocusItem(event, itemToFocusId);
});

const removeFocusedItem = useEventCallback(() => {
if (state.focusedItemId == null) {
return;
Expand All @@ -121,11 +104,11 @@ export const useTreeViewFocus: TreeViewPlugin<UseTreeViewFocusSignature> = ({
setFocusedItemId(null);
});

const canItemBeTabbed = (itemId: string) => itemId === tabbableItemId;
const canItemBeTabbed = (itemId: string) => itemId === defaultFocusableItemId;

useInstanceEventHandler(instance, 'removeItem', ({ id }) => {
if (state.focusedItemId === id) {
instance.focusDefaultItem(null);
innerFocusItem(null, defaultFocusableItemId);
}
});

Expand All @@ -139,7 +122,7 @@ export const useTreeViewFocus: TreeViewPlugin<UseTreeViewFocusSignature> = ({

// if the event bubbled (which is React specific) we don't want to steal focus
if (event.target === event.currentTarget) {
instance.focusDefaultItem(event);
innerFocusItem(event, defaultFocusableItemId);
}
};

Expand All @@ -154,7 +137,6 @@ export const useTreeViewFocus: TreeViewPlugin<UseTreeViewFocusSignature> = ({
isItemFocused,
canItemBeTabbed,
focusItem,
focusDefaultItem,
removeFocusedItem,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,38 @@ import { UseTreeViewIdSignature } from '../useTreeViewId/useTreeViewId.types';
import type { UseTreeViewItemsSignature } from '../useTreeViewItems';
import type { UseTreeViewSelectionSignature } from '../useTreeViewSelection';
import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
import { TreeViewItemId } from '../../../models';

export interface UseTreeViewFocusPublicAPI {
/**
* Focuses the item with the given id.
* Focus the item with the given id.
*
* If the item is the child of a collapsed item, then this method will do nothing.
* Make sure to expand the ancestors of the item before calling this method if needed.
* @param {React.SyntheticEvent} event The event source of the action.
* @param {string} itemId The id of the item to focus.
* @param {TreeViewItemId} itemId The id of the item to focus.
*/
focusItem: (event: React.SyntheticEvent, itemId: string) => void;
}

export interface UseTreeViewFocusInstance extends UseTreeViewFocusPublicAPI {
isItemFocused: (itemId: string) => boolean;
canItemBeTabbed: (itemId: string) => boolean;
focusDefaultItem: (event: React.SyntheticEvent | null) => void;
/**
* Check if an item is the currently focused item.
* @param {TreeViewItemId} itemId The id of the item to check.
* @returns {boolean} `true` if the item is focused, `false` otherwise.
*/
isItemFocused: (itemId: TreeViewItemId) => boolean;
/**
* Check if an item should be sequentially focusable (usually with the Tab key).
* At any point in time, there is a single item that can be sequentially focused in the Tree View.
* This item is the first selected item (that is both visible and navigable), if any, or the first navigable item if no item is selected.
* @param {TreeViewItemId} itemId The id of the item to check.
* @returns {boolean} `true` if the item can be sequentially focusable, `false` otherwise.
*/
canItemBeTabbed: (itemId: TreeViewItemId) => boolean;
/**
* Remove the focus from the currently focused item (both from the internal state and the DOM).
*/
removeFocusedItem: () => void;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { TreeViewPluginSignature } from '../../models';
import { TreeViewItemId } from '../../../models';

export interface UseTreeViewIdInstance {
getTreeItemIdAttribute: (itemId: string, idAttribute: string | undefined) => string;
/**
* Get the id attribute (i.e.: the `id` attribute passed to the DOM element) of a tree item.
* If the user explicitly defined an id attribute, it will be returned.
* Otherwise, the method created a unique id for the item based on the Tree View id attribute and the item `itemId`
* @param {TreeViewItemId} itemId The id of the item to get the id attribute of.
* @param {string | undefined} idAttribute The id attribute of the item if explicitly defined by the user.
* @returns {string} The id attribute of the item.
*/
getTreeItemIdAttribute: (itemId: TreeViewItemId, idAttribute: string | undefined) => string;
}

export interface UseTreeViewIdParameters {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,51 @@ export interface UseTreeViewItemsPublicAPI<R extends {}> {
* @param {string} itemId The id of the item to return.
* @returns {R} The item with the given id.
*/
getItem: (itemId: string) => R;
getItem: (itemId: TreeViewItemId) => R;
}

export interface UseTreeViewItemsInstance<R extends {}> extends UseTreeViewItemsPublicAPI<R> {
getItemMeta: (itemId: string) => TreeViewItemMeta;
/**
* Get the meta-information of an item.
* Check the `TreeViewItemMeta` type for more information.
* @param {TreeViewItemId} itemId The id of the item to get the meta-information of.
* @returns {TreeViewItemMeta} The meta-information of the item.
*/
getItemMeta: (itemId: TreeViewItemId) => TreeViewItemMeta;
/**
* Get the item that should be rendered.
* This method is only used on Rich Tree View components.
* Check the `TreeViewItemProps` type for more information.
* @returns {TreeViewItemProps[]} The items to render.
*/
getItemsToRender: () => TreeViewItemProps[];
getItemOrderedChildrenIds: (parentId: string | null) => string[];
isItemDisabled: (itemId: string) => itemId is string;
isItemNavigable: (itemId: string) => boolean;
getItemIndex: (itemId: string) => number;
/**
* Get the ids of a given item's children.
* Those ids are returned in the order they should be rendered.
* @param {TreeViewItemId | null} itemId The id of the item to get the children of.
* @returns {TreeViewItemId[]} The ids of the item's children.
*/
getItemOrderedChildrenIds: (itemId: TreeViewItemId | null) => TreeViewItemId[];
/**
* Check if a given item is disabled.
* An item is disabled if it was marked as disabled or if one of its ancestors is disabled.
* @param {TreeViewItemId} itemId The id of the item to check.
* @returns {boolean} `true` if the item is disabled, `false` otherwise.
*/
isItemDisabled: (itemId: TreeViewItemId) => boolean;
/**
* Check if a given item is navigable (i.e.: if it can be accessed through keyboard navigation).
* An item is navigable if it is not disabled or if the `disabledItemsFocusable` prop is `true`.
* @param {TreeViewItemId} itemId The id of the item to check.
* @returns {boolean} `true` if the item is navigable, `false` otherwise.
*/
isItemNavigable: (itemId: TreeViewItemId) => boolean;
/**
* Get the index of a given item in its parent's children list.
* @param {TreeViewItemId} itemId The id of the item to get the index of.
* @returns {number} The index of the item in its parent's children list.
*/
getItemIndex: (itemId: TreeViewItemId) => number;
/**
* Freeze any future update to the state based on the `items` prop.
* This is useful when `useTreeViewJSXItems` is used to avoid having conflicting sources of truth.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@ export const useTreeViewJSXItems: TreeViewPlugin<UseTreeViewJSXItemsSignature> =
},
};
});

return () => {
setState((prevState) => {
const newItemMetaMap = { ...prevState.items.itemMetaMap };
const newItemMap = { ...prevState.items.itemMap };
delete newItemMetaMap[item.id];
delete newItemMap[item.id];
return {
...prevState,
items: {
...prevState.items,
itemMetaMap: newItemMetaMap,
itemMap: newItemMap,
},
};
});
publishTreeViewEvent(instance, 'removeItem', { id: item.id });
};
});

const setJSXItemsOrderedChildrenIds = (parentId: string | null, orderedChildrenIds: string[]) => {
Expand All @@ -68,24 +86,6 @@ export const useTreeViewJSXItems: TreeViewPlugin<UseTreeViewJSXItemsSignature> =
}));
};

const removeJSXItem = useEventCallback((itemId: string) => {
setState((prevState) => {
const newItemMetaMap = { ...prevState.items.itemMetaMap };
const newItemMap = { ...prevState.items.itemMap };
delete newItemMetaMap[itemId];
delete newItemMap[itemId];
return {
...prevState,
items: {
...prevState.items,
itemMetaMap: newItemMetaMap,
itemMap: newItemMap,
},
};
});
publishTreeViewEvent(instance, 'removeItem', { id: itemId });
});

const mapFirstCharFromJSX = useEventCallback((itemId: string, firstChar: string) => {
instance.updateFirstCharMap((firstCharMap) => {
firstCharMap[itemId] = firstChar;
Expand All @@ -104,7 +104,6 @@ export const useTreeViewJSXItems: TreeViewPlugin<UseTreeViewJSXItemsSignature> =
return {
instance: {
insertJSXItem,
removeJSXItem,
setJSXItemsOrderedChildrenIds,
mapFirstCharFromJSX,
},
Expand Down Expand Up @@ -153,15 +152,13 @@ const useTreeViewJSXItemsItemPlugin: TreeViewItemPlugin<TreeItemProps | TreeItem
}, [instance, registerChild, unregisterChild, itemId, id]);

React.useEffect(() => {
instance.insertJSXItem({
return instance.insertJSXItem({
id: itemId,
idAttribute: id,
parentId,
expandable,
disabled,
});

return () => instance.removeJSXItem(itemId);
}, [instance, parentId, itemId, expandable, disabled, id]);

React.useEffect(() => {
Expand Down
Loading

0 comments on commit 1a453ab

Please sign in to comment.