Visitar URL original
fix(devtools): make tab buttons visually accessible on small viewports · angular/angular@1750003 · GitHub
Skip to content

Commit 1750003

Browse files
authored
fix(devtools): make tab buttons visually accessible on small viewports
Reuse breadcrumbs scroll behavior (in the form of a reusable component) for the main tabs bar.
1 parent 7bbe263 commit 1750003

13 files changed

Lines changed: 263 additions & 171 deletions

File tree

‎devtools/cypress/integration/node-selection.e2e.js‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ describe('node selection', () => {
8282
.last()
8383
.click({force: true})
8484
.then(() => {
85-
cy.get('ng-breadcrumbs')
86-
.find('.breadcrumbs')
85+
cy.get('ng-breadcrumbs > ng-horizontal-scroller')
86+
.find('.content')
8787
.then((breadcrumbsContainer) => {
8888
const hasOverflowX = () =>
8989
breadcrumbsContainer[0].scrollWidth > breadcrumbsContainer[0].clientWidth;
@@ -98,16 +98,16 @@ describe('node selection', () => {
9898
.last()
9999
.click({force: true})
100100
.then(() => {
101-
cy.get('ng-breadcrumbs')
102-
.find('.breadcrumbs')
101+
cy.get('ng-breadcrumbs > ng-horizontal-scroller')
102+
.find('.content')
103103
.then((el) => {
104104
el[0].style.scrollBehavior = 'auto';
105105
})
106106
.then((breadcrumbsContainer) => {
107107
const scrollLeft = () => breadcrumbsContainer[0].scrollLeft;
108108
expect(scrollLeft()).to.eql(0);
109109

110-
cy.get('ng-breadcrumbs')
110+
cy.get('ng-breadcrumbs > ng-horizontal-scroller')
111111
.find('.scroll-button')
112112
.last()
113113
.click({force: true})
@@ -124,23 +124,23 @@ describe('node selection', () => {
124124
.last()
125125
.click({force: true})
126126
.then(() => {
127-
cy.get('ng-breadcrumbs')
128-
.find('.breadcrumbs')
127+
cy.get('ng-breadcrumbs > ng-horizontal-scroller')
128+
.find('.content')
129129
.then((el) => {
130130
el[0].style.scrollBehavior = 'auto';
131131
})
132132
.then((breadcrumbsContainer) => {
133133
const scrollLeft = () => breadcrumbsContainer[0].scrollLeft;
134134
expect(scrollLeft()).to.eql(0);
135135

136-
cy.get('ng-breadcrumbs')
136+
cy.get('ng-breadcrumbs > ng-horizontal-scroller')
137137
.find('.scroll-button')
138138
.last()
139139
.click({force: true})
140140
.then(() => {
141141
expect(scrollLeft()).to.be.greaterThan(0);
142142

143-
cy.get('ng-breadcrumbs')
143+
cy.get('ng-breadcrumbs > ng-horizontal-scroller')
144144
.find('.scroll-button')
145145
.first()
146146
.click({force: true})

‎devtools/projects/ng-devtools/src/lib/devtools-tabs/BUILD.bazel‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@ ng_project(
2020
":devtools_tabs_component_styles",
2121
],
2222
deps = [
23-
"//:node_modules/@angular/common",
2423
"//:node_modules/@angular/core",
2524
"//:node_modules/@angular/material",
2625
"//:node_modules/@types/chrome",
27-
"//:node_modules/rxjs",
2826
"//devtools/projects/ng-devtools/src/lib/application-environment",
2927
"//devtools/projects/ng-devtools/src/lib/application-providers:app_data",
3028
"//devtools/projects/ng-devtools/src/lib/application-providers:deep_link",
@@ -39,6 +37,7 @@ ng_project(
3937
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/tab-update",
4038
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/transfer-state",
4139
"//devtools/projects/ng-devtools/src/lib/shared/button",
40+
"//devtools/projects/ng-devtools/src/lib/shared/horizontal-scroller",
4241
"//devtools/projects/protocol",
4342
],
4443
)

‎devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.html‎

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,21 @@
3434
}
3535
</select>
3636

37-
<nav
38-
mat-tab-nav-bar
39-
mat-stretch-tabs="false"
40-
[disablePagination]="true"
41-
[color]="'accent'"
42-
[tabPanel]="tabPanel"
43-
>
44-
@for (tab of tabs(); track $index) {
45-
<a class="tab-link" mat-tab-link (click)="changeTab(tab)" [active]="activeTab() === tab">
46-
{{ tab }}
47-
</a>
48-
}
49-
</nav>
37+
<ng-horizontal-scroller [scrollStep]="150" buttonHidingStrategy="display-none">
38+
<nav
39+
mat-tab-nav-bar
40+
mat-stretch-tabs="false"
41+
[disablePagination]="true"
42+
[color]="'accent'"
43+
[tabPanel]="tabPanel"
44+
>
45+
@for (tab of tabs(); track $index) {
46+
<a class="tab-link" mat-tab-link (click)="changeTab(tab)" [active]="activeTab() === tab">
47+
{{ tab }}
48+
</a>
49+
}
50+
</nav>
51+
</ng-horizontal-scroller>
5052

5153
<div class="settings">
5254
<div class="ver-ruler"></div>

‎devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.scss‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,15 @@ $toolbar-height: 30px;
6363

6464
.frame-selector {
6565
width: 100px;
66+
flex: 0 0 100px;
6667
margin: 0 0.5rem;
6768
}
6869

70+
ng-horizontal-scroller {
71+
--horizontal-scroller-height: #{$toolbar-height};
72+
flex: 1;
73+
}
74+
6975
.tab-link {
7076
@extend %body-medium-01;
7177
min-width: unset;
@@ -74,7 +80,6 @@ $toolbar-height: 30px;
7480
}
7581

7682
.settings {
77-
flex: 1;
7883
display: flex;
7984
justify-content: end;
8085
align-items: center;

‎devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {SUPPORTED_APIS} from '../application-providers/supported_apis';
3535
import {ButtonComponent} from '../shared/button/button.component';
3636
import {APP_DATA} from '../application-providers/app_data';
3737
import {SettingsComponent} from './settings/settings.component';
38+
import {HorizontalScrollerComponent} from '../shared/horizontal-scroller/horizontal-scroller.component';
3839

3940
type Tab = 'Components' | 'Profiler' | 'Router Tree' | 'Injector Tree' | 'Transfer State';
4041

@@ -58,6 +59,7 @@ type Tab = 'Components' | 'Profiler' | 'Router Tree' | 'Injector Tree' | 'Transf
5859
TransferStateComponent,
5960
SettingsComponent,
6061
ButtonComponent,
62+
HorizontalScrollerComponent,
6163
],
6264
providers: [TabUpdate],
6365
host: {

‎devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/breadcrumbs/BUILD.bazel‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,8 @@ ng_project(
2020
":breadcrumbs_component_styles",
2121
],
2222
deps = [
23-
"//:node_modules/@angular/common",
2423
"//:node_modules/@angular/core",
25-
"//:node_modules/@angular/material",
26-
"//:node_modules/rxjs",
2724
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source",
28-
"//devtools/projects/ng-devtools/src/lib/shared/utils",
25+
"//devtools/projects/ng-devtools/src/lib/shared/horizontal-scroller",
2926
],
3027
)
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
<button
2-
class="scroll-button"
3-
[class.hidden]="!showScrollLeftButton()"
4-
(click)="scroll(-100)"
5-
aria-label="Scroll left"
6-
>
7-
<mat-icon> chevron_left </mat-icon>
8-
</button>
9-
<div class="breadcrumbs" #breadcrumbs (scroll)="updateScrollButtonVisibility()">
1+
<ng-horizontal-scroller #scroller>
102
@for (node of parents(); track $index) {
113
<button
124
(mouseover)="mouseOverNode.emit(node)"
@@ -19,12 +11,4 @@
1911
{{ node.original.component?.name || node.original.tagName }}
2012
</button>
2113
}
22-
</div>
23-
<button
24-
class="scroll-button"
25-
[class.hidden]="!showScrollRightButton()"
26-
(click)="scroll(100)"
27-
aria-label="Scroll right"
28-
>
29-
<mat-icon> chevron_right </mat-icon>
30-
</button>
14+
</ng-horizontal-scroller>
Lines changed: 11 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,23 @@
11
@use '../../../../../styles/typography';
22

33
:host {
4-
width: 100%;
5-
height: 24px;
6-
display: flex;
7-
flex-direction: row;
8-
box-sizing: border-box;
4+
display: block;
95

10-
.scroll-button {
11-
height: 100%;
12-
background-color: var(--septenary-contrast);
6+
.breadcrumb {
7+
@extend %monospaced;
8+
background-color: transparent;
9+
color: var(--color-tree-node-element-name);
10+
padding-inline: 0.5rem;
1311
border: none;
14-
cursor: pointer;
15-
color: var(--tertiary-contrast);
16-
display: flex;
17-
align-items: center;
18-
19-
&.hidden {
20-
visibility: hidden;
21-
}
22-
23-
mat-icon {
24-
font-size: 16px;
25-
width: 16px;
26-
height: 16px;
27-
}
28-
}
29-
30-
.breadcrumbs {
31-
overflow-x: auto;
32-
overflow-y: hidden;
33-
white-space: nowrap;
34-
display: inline-block;
3512
height: 100%;
36-
scroll-behavior: smooth;
37-
scrollbar-width: none;
38-
flex: 1;
13+
cursor: pointer;
3914

40-
&::-webkit-scrollbar {
41-
display: none;
15+
&:hover {
16+
background-color: var(--color-tree-node-hovered);
4217
}
4318

44-
.breadcrumb {
45-
@extend %monospaced;
46-
background-color: transparent;
47-
color: var(--color-tree-node-element-name);
48-
padding-inline: 0.5rem;
49-
border: none;
50-
height: 100%;
51-
cursor: pointer;
52-
53-
&:hover {
54-
background-color: var(--color-tree-node-hovered);
55-
}
56-
57-
&.selected {
58-
background-color: var(--color-tree-node-highlighted);
59-
}
19+
&.selected {
20+
background-color: var(--color-tree-node-highlighted);
6021
}
6122
}
6223
}

‎devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/breadcrumbs/breadcrumbs.component.ts‎

Lines changed: 10 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,97 +6,33 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {
10-
afterNextRender,
11-
afterRenderEffect,
12-
Component,
13-
computed,
14-
DestroyRef,
15-
ElementRef,
16-
input,
17-
output,
18-
signal,
19-
untracked,
20-
viewChild,
21-
} from '@angular/core';
22-
import {MatIcon} from '@angular/material/icon';
9+
import {afterRenderEffect, Component, input, output, untracked, viewChild} from '@angular/core';
2310

2411
import {FlatNode} from '../component-data-source';
25-
import {Debouncer} from '../../../../shared/utils/debouncer';
26-
27-
const RESIZE_DEBOUNCE = 100;
12+
import {HorizontalScrollerComponent} from '../../../../shared/horizontal-scroller/horizontal-scroller.component';
2813

2914
@Component({
3015
selector: 'ng-breadcrumbs',
3116
templateUrl: './breadcrumbs.component.html',
3217
styleUrls: ['./breadcrumbs.component.scss'],
33-
imports: [MatIcon],
18+
imports: [HorizontalScrollerComponent],
3419
})
3520
export class BreadcrumbsComponent {
36-
readonly parents = input.required<FlatNode[]>();
37-
readonly handleSelect = output<FlatNode>();
38-
readonly mouseOverNode = output<FlatNode>();
39-
readonly mouseLeaveNode = output<FlatNode>();
40-
41-
readonly breadcrumbsScrollContent = viewChild.required<ElementRef>('breadcrumbs');
42-
43-
readonly showScrollLeftButton = computed(() => {
44-
const value = this.breadcrumbsScrollLayout();
45-
return value && value.scrollLeft > 0;
46-
});
47-
48-
readonly showScrollRightButton = computed(() => {
49-
const value = this.breadcrumbsScrollLayout();
50-
if (!value) {
51-
return false;
52-
}
53-
const {clientWidth, scrollWidth, scrollLeft} = value;
54-
return scrollWidth > clientWidth && scrollLeft + clientWidth < scrollWidth;
55-
});
21+
protected readonly scoller = viewChild<HorizontalScrollerComponent>('scroller');
5622

57-
private readonly breadcrumbsScrollLayout = signal<
58-
| {
59-
clientWidth: number;
60-
scrollWidth: number;
61-
scrollLeft: number;
62-
}
63-
| undefined
64-
>(undefined);
65-
66-
constructor(destroyRef: DestroyRef) {
67-
const debouncer = new Debouncer();
68-
const observer = new ResizeObserver(
69-
debouncer.debounce(() => {
70-
this.updateScrollButtonVisibility();
71-
}, RESIZE_DEBOUNCE),
72-
);
73-
74-
afterNextRender(() => {
75-
observer.observe(this.breadcrumbsScrollContent().nativeElement);
76-
});
23+
protected readonly parents = input.required<FlatNode[]>();
24+
protected readonly handleSelect = output<FlatNode>();
25+
protected readonly mouseOverNode = output<FlatNode>();
26+
protected readonly mouseLeaveNode = output<FlatNode>();
7727

28+
constructor() {
7829
afterRenderEffect({
7930
read: () => {
8031
// We use the parents as a dependency to trigger a layout
8132
// update that would show the navigation buttons.
8233
this.parents();
83-
untracked(() => this.updateScrollButtonVisibility());
34+
untracked(() => this.scoller()?.updateScrollButtonVisibility());
8435
},
8536
});
86-
87-
destroyRef.onDestroy(() => {
88-
debouncer.cancel();
89-
observer.disconnect();
90-
});
91-
}
92-
93-
scroll(pixels: number): void {
94-
this.breadcrumbsScrollContent().nativeElement.scrollLeft += pixels;
95-
this.updateScrollButtonVisibility();
96-
}
97-
98-
updateScrollButtonVisibility(): void {
99-
const {clientWidth, scrollWidth, scrollLeft} = this.breadcrumbsScrollContent().nativeElement;
100-
this.breadcrumbsScrollLayout.set({clientWidth, scrollWidth, scrollLeft});
10137
}
10238
}

0 commit comments

Comments
 (0)