Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Skip Link to Notebook #6844

Merged
merged 7 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions packages/application/src/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell {
rootLayout.addWidget(hsplitPanel);

this.layout = rootLayout;

// Added Skip to Main Link
const skipLinkWidgetHandler = (this._skipLinkWidgetHandler =
new Private.SkipLinkWidgetHandler(this));

this.add(skipLinkWidgetHandler.skipLinkWidget, 'top', { rank: 0 });
this._skipLinkWidgetHandler.show();
}

/**
Expand Down Expand Up @@ -349,6 +356,7 @@ export class NotebookShell extends Widget implements JupyterFrontEnd.IShell {
private _rightHandler: SidePanelHandler;
private _spacer_top: Widget;
private _spacer_bottom: Widget;
private _skipLinkWidgetHandler: Private.SkipLinkWidgetHandler;
private _main: Panel;
private _translator: ITranslator = nullTranslator;
private _currentChanged = new Signal<this, void>(this);
Expand All @@ -364,3 +372,82 @@ export namespace Shell {
*/
export type Area = 'main' | 'top' | 'left' | 'right' | 'menu';
}

export namespace Private {
export class SkipLinkWidgetHandler {
/**
* Construct a new skipLink widget handler.
*/
constructor(shell: INotebookShell) {
const skipLinkWidget = (this._skipLinkWidget = new Widget());
const skipToMain = document.createElement('a');
skipToMain.href = '#first-cell';
skipToMain.tabIndex = 1;
skipToMain.text = 'Skip to Main';
skipToMain.className = 'skip-link';
skipToMain.addEventListener('click', this);
skipLinkWidget.addClass('jp-skiplink');
skipLinkWidget.id = 'jp-skiplink';
skipLinkWidget.node.appendChild(skipToMain);
}

handleEvent(event: Event): void {
switch (event.type) {
case 'click':
this._focusMain();
break;
}
}

private _focusMain() {
const input = document.querySelector(
'#main-panel .jp-InputArea-editor'
) as HTMLInputElement;
input.tabIndex = 1;
input.focus();
}

/**
* Get the input element managed by the handler.
*/
get skipLinkWidget(): Widget {
return this._skipLinkWidget;
}

/**
* Dispose of the handler and the resources it holds.
*/
dispose(): void {
if (this.isDisposed) {
return;
}
this._isDisposed = true;
this._skipLinkWidget.node.removeEventListener('click', this);
this._skipLinkWidget.dispose();
}

/**
* Hide the skipLink widget.
*/
hide(): void {
this._skipLinkWidget.hide();
}

/**
* Show the skipLink widget.
*/
show(): void {
this._skipLinkWidget.show();
}

/**
* Test whether the handler has been disposed.
*/
get isDisposed(): boolean {
return this._isDisposed;
}

private _skipLinkWidget: Widget;
private _isDisposed = false;
}
}
18 changes: 14 additions & 4 deletions packages/application/test/shell.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@ describe('Shell for notebooks', () => {
expect(shell).toBeInstanceOf(NotebookShell);
});

it('should make all areas empty initially', () => {
['main', 'top', 'left', 'right', 'menu'].forEach((area) => {
it('should make some areas empty initially', () => {
['main', 'left', 'right', 'menu'].forEach((area) => {
const widgets = Array.from(shell.widgets(area as Shell.Area));
expect(widgets.length).toEqual(0);
});
});

it('should have the skip link widget in the top area initially', () => {
const widgets = Array.from(shell.widgets('top'));
expect(widgets.length).toEqual(1);
});
});

describe('#widgets()', () => {
Expand Down Expand Up @@ -132,12 +137,17 @@ describe('Shell for tree view', () => {
expect(shell).toBeInstanceOf(NotebookShell);
});

it('should make all areas empty initially', () => {
['main', 'top', 'left', 'right', 'menu'].forEach((area) => {
it('should make some areas empty initially', () => {
['main', 'left', 'right', 'menu'].forEach((area) => {
const widgets = Array.from(shell.widgets(area as Shell.Area));
expect(widgets.length).toEqual(0);
});
});

it('should have the skip link widget in the top area initially', () => {
const widgets = Array.from(shell.widgets('top'));
expect(widgets.length).toEqual(1);
});
});

describe('#widgets()', () => {
Expand Down
24 changes: 24 additions & 0 deletions packages/notebook-extension/style/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,27 @@ body[data-notebook='notebooks']
overflow: hidden;
white-space: nowrap;
}

.jp-skiplink {
position: absolute;
top: -100em;
}

.jp-skiplink:focus-within {
position: absolute;
z-index: 10000;
top: 0;
left: 46%;
margin: 0 auto;
padding: 1em;
width: 15%;
box-shadow: var(--jp-elevation-z4);
border-radius: 4px;
background: var(--jp-layout-color0);
text-align: center;
}

.jp-skiplink:focus-within a {
text-decoration: underline;
color: var(--jp-content-link-color);
}