Skip to content

Commit

Permalink
[TreeView] Add JSDoc to every instance method
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviendelangle committed May 23, 2024
1 parent 4a77854 commit 2e17a85
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 55 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 some 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,28 @@ 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';
import { TreeViewItemId } from '../../../models';

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

let tabbableItemId: string | null | undefined;
if (Array.isArray(selectedItems)) {
tabbableItemId = selectedItems.find(isItemVisible);
} else if (selectedItems != null && isItemVisible(selectedItems)) {
tabbableItemId = selectedItems;
}
let ancestorId: TreeViewItemId | null = itemId;
while (ancestorId != null) {
ancestorId = instance.getItemMeta(ancestorId)?.parentId;
if (ancestorId != null && !instance.isItemExpanded(ancestorId)) {
return false;
}
}

return true;
});

if (tabbableItemId == null) {
tabbableItemId = getFirstNavigableItem(instance);
Expand All @@ -40,7 +46,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 +94,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 +112,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 +130,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 +145,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 id 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 disabled 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

0 comments on commit 2e17a85

Please sign in to comment.