diff --git a/CHANGELOG.md b/CHANGELOG.md index 036c5d90..1638deaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ## [Unreleased] #### 🚀 New Features - Make tree- and list-like components more accessible: - * Change `ClassTree`, `InstancesSearch`, `ConnectionsMenu` and `SearchResults` to be "focus group" components with support for keyboard interaction (arrow keys to move focus or toggle tree items, space to select); + * Change `ClassTree`, `InstancesSearch`, `ConnectionsMenu`, `SearchResults` and `DropdownMenu` to be "focus group" components with support for keyboard interaction (arrow keys to move focus or toggle tree items, space to select); * Add proper `aria-*` attributes for "focus group" containers and children e.g. `tree` and `treeitem` roles; - Support creating standalone workspace state (context) separated from UI components: * Workspace state can be created with `createWorkspace()` and provided to the UI components via `WorkspaceProvider`; diff --git a/src/coreUtils/dom.ts b/src/coreUtils/dom.ts index c3fe774b..5f99fcf6 100644 --- a/src/coreUtils/dom.ts +++ b/src/coreUtils/dom.ts @@ -75,3 +75,18 @@ export function findPreviousWithin( } } while (current !== from); } + +export function findParentWithin( + from: Element, + parent: Element, + condition: (element: Element) => boolean +): Element | undefined { + let current: Element | null = from; + while (current && current !== parent) { + if (condition(current)) { + return current; + } + current = current.parentElement; + } + return undefined; +} diff --git a/src/widgets/connectionsMenu/connectionList.tsx b/src/widgets/connectionsMenu/connectionList.tsx index 51ad5d16..a2150545 100644 --- a/src/widgets/connectionsMenu/connectionList.tsx +++ b/src/widgets/connectionsMenu/connectionList.tsx @@ -7,9 +7,7 @@ import type { LinkTypeModel } from '../../data/model'; import { generate128BitID, makeCaseInsensitiveFilter } from '../../data/utils'; import { WithFetchStatus } from '../../editor/withFetchStatus'; -import { - AccessibleList, type ListRenderItem, type ListFocusableProps, -} from '../utility/accessibleList'; +import { FocusGroup, useFocusGroupItem } from '../utility/focusGroup'; import { highlightSubstring } from '../utility/listElementView'; import { useWorkspace } from '../../workspace/workspaceContext'; @@ -93,46 +91,6 @@ export function ConnectionsList(props: { } } - const renderItem = React.useCallback>( - ({item, focusProps}) => { - if (item.type === 'link') { - return ( - - ); - } else if (item.type === 'separator') { - return
; - } else if (item.type === 'probable-hint') { - return ( -
  • - {t.text('connections_menu.links.suggest_similar')} -
  • - ); - } - return null; - }, - [t, filterKey, onExpandLink, onMoveToFilter] - ); - - const rootProps = React.useMemo((): React.HTMLProps => ({ - /* For compatibility with React 19 typings */ - ref: scrolledListRef as React.RefObject, - className: `${CLASS_NAME}__links-root`, - role: 'list', - }), []); - const itemProps = React.useMemo((): React.HTMLProps => ({ - className: `${CLASS_NAME}__links-item`, - role: 'listitem', - }), []); - return (
    - + + {({ref, controller}) => ( +
      + {entries.map(item => { + if (item.type === 'link') { + return ( + + ); + } else if (item.type === 'separator') { + return ( +
      + ); + } else if (item.type === 'probable-hint') { + return ( +
    • + {t.text('connections_menu.links.suggest_similar')} +
    • + ); + } + return null; + })} +
    + )} +
    {entries.length === 0 ? (