Skip to content

Commit 7043bdd

Browse files
committed
Make ClassTree, InstancesSearch, ConnectionsMenu and other components more accessible:
* Add accessible `TreeList` component to display trees and lists with proper ARIA attributes and keyboard navigation; * Re-implement `ClassTree`, `InstancesSearch` and `ConnectionsMenu` results using `TreeList` component; * Extend `ListElementView` to accept any other additional HTML props; * Fix partially or fully hidden outline for `WorkspaceLayoutItem` headers and `Navigator` toggle button; * Always display ungroup buttons on `StandardGroup` when the element is single-selected.
1 parent d8dd4ee commit 7043bdd

16 files changed

Lines changed: 1262 additions & 642 deletions

src/coreUtils/dom.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
export function findNextWithin(
2+
from: Element,
3+
parent: Element,
4+
condition: (element: Element) => boolean
5+
): Element | undefined {
6+
let current: Element = from;
7+
let allowDescent = true;
8+
do {
9+
while (true) {
10+
if (allowDescent && current.firstElementChild) {
11+
current = current.firstElementChild;
12+
break;
13+
} else if (current.nextElementSibling) {
14+
current = current.nextElementSibling;
15+
allowDescent = true;
16+
break;
17+
} else if (current.parentElement === parent) {
18+
if (parent.firstElementChild) {
19+
current = parent.firstElementChild;
20+
allowDescent = true;
21+
break;
22+
} else {
23+
return undefined;
24+
}
25+
} else if (current.parentElement) {
26+
current = current.parentElement;
27+
allowDescent = false;
28+
} else {
29+
return undefined;
30+
}
31+
}
32+
33+
if (condition(current)) {
34+
return current;
35+
}
36+
} while (current !== from);
37+
}
38+
39+
export function findPreviousWithin(
40+
from: Element,
41+
parent: Element,
42+
condition: (element: Element) => boolean
43+
): Element | undefined {
44+
let current: Element = from;
45+
let descent = false;
46+
do {
47+
while (true) {
48+
if (descent) {
49+
if (current.lastElementChild) {
50+
current = current.lastElementChild;
51+
} else {
52+
descent = false;
53+
break;
54+
}
55+
} else if (current.previousElementSibling) {
56+
current = current.previousElementSibling;
57+
descent = true;
58+
} else if (current.parentElement === parent) {
59+
if (parent.lastElementChild) {
60+
current = parent.lastElementChild;
61+
descent = true;
62+
} else {
63+
return undefined;
64+
}
65+
} else if (current.parentElement) {
66+
current = current.parentElement;
67+
break;
68+
} else {
69+
return undefined;
70+
}
71+
}
72+
73+
if (condition(current)) {
74+
return current;
75+
}
76+
} while (current !== from);
77+
}

src/widgets/classTree/classTree.tsx

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ import {
2828
} from '../../workspace/commandBusTopic';
2929
import { WorkspaceContext, useWorkspace } from '../../workspace/workspaceContext';
3030

31-
import { TreeNode } from './treeModel';
32-
import { ClassTreeContext, Forest } from './leaf';
31+
import { ClassTreeResults, type ClassTreeSelection, TreeNode } from './classTreeResults';
3332

3433
/**
3534
* Props for {@link ClassTree} component.
@@ -132,7 +131,7 @@ interface State {
132131
roots: ReadonlyArray<TreeNode>;
133132
filteredRoots: ReadonlyArray<TreeNode>;
134133
appliedSearchText?: string;
135-
selectedNode?: TreeNode;
134+
selection?: ClassTreeSelection;
136135
constructibleClasses: ReadonlyMap<ElementTypeIri, boolean>;
137136
showOnlyConstructible: boolean;
138137
}
@@ -177,7 +176,7 @@ class ClassTreeInner extends React.Component<ClassTreeInnerProps, State> {
177176
draggableItems = true, workspace: {editor}, translation: t,
178177
} = this.props;
179178
const {
180-
fetchedGraph, refreshingState, appliedSearchText, roots, filteredRoots, selectedNode,
179+
fetchedGraph, refreshingState, appliedSearchText, roots, filteredRoots, selection,
181180
constructibleClasses, showOnlyConstructible
182181
} = this.state;
183182
// highlight search term only if actual tree is already filtered by current or previous term:
@@ -217,35 +216,30 @@ class ClassTreeInner extends React.Component<ClassTreeInnerProps, State> {
217216
title={t.text('search_element_types.refresh_progress.title')}
218217
/>
219218
{fetchedGraph?.classTree ? (
220-
<ClassTreeContext.Provider
221-
value={{
222-
searchText,
223-
selectedNode,
224-
onSelect: this.onSelectNode,
225-
creatableClasses: editor.inAuthoringMode
226-
? constructibleClasses : EMPTY_CREATABLE_TYPES,
227-
onClickCreate: this.onCreateInstance,
228-
onDragCreate: this.onDragCreate,
229-
draggableItems,
230-
}}>
231-
<Forest className={`${CLASS_NAME}__tree reactodia-scrollable`}
232-
nodes={filteredRoots}
233-
root={true}
234-
footer={
235-
filteredRoots.length === 0 ? (
236-
<NoSearchResults className={`${CLASS_NAME}__no-results`}
237-
hasQuery={filteredRoots !== roots}
238-
minSearchTermLength={minSearchTermLength}
239-
message={
240-
roots.length === 0
241-
? t.text('search_element_types.no_results')
242-
: undefined
243-
}
244-
/>
245-
) : null
219+
<div className={`${CLASS_NAME}__tree reactodia-scrollable`} tabIndex={-1}>
220+
<ClassTreeResults nodes={filteredRoots}
221+
searchText={searchText}
222+
selection={selection}
223+
onSelect={this.onSelectNode}
224+
creatableClasses={
225+
editor.inAuthoringMode ? constructibleClasses : EMPTY_CREATABLE_TYPES
246226
}
227+
onClickCreate={this.onCreateInstance}
228+
onDragCreate={this.onDragCreate}
229+
draggableItems={draggableItems}
247230
/>
248-
</ClassTreeContext.Provider>
231+
{filteredRoots.length === 0 ? (
232+
<NoSearchResults className={`${CLASS_NAME}__no-results`}
233+
hasQuery={filteredRoots !== roots}
234+
minSearchTermLength={minSearchTermLength}
235+
message={
236+
roots.length === 0
237+
? t.text('search_element_types.no_results')
238+
: undefined
239+
}
240+
/>
241+
) : null}
242+
</div>
249243
) : (
250244
<div className={`${CLASS_NAME}__spinner`}>
251245
<HtmlSpinner width={30} height={30}
@@ -349,12 +343,12 @@ class ClassTreeInner extends React.Component<ClassTreeInnerProps, State> {
349343
));
350344
};
351345

352-
private onSelectNode = (node: TreeNode) => {
346+
private onSelectNode = (selection: ClassTreeSelection) => {
353347
const {workspace: {getCommandBus}} = this.props;
354-
this.setState({selectedNode: node}, () => {
348+
this.setState({selection}, () => {
355349
getCommandBus(InstancesSearchTopic)
356350
.trigger('setCriteria', {
357-
criteria: {elementType: node.iri},
351+
criteria: {elementType: selection.node.iri},
358352
});
359353
});
360354
};

0 commit comments

Comments
 (0)