Skip to content
Open
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
28 changes: 27 additions & 1 deletion src/base-styles.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
import {css} from 'lit';

export const visuallyHidden = css`
.visually-hidden:not(:focus-within) {
position: absolute !important;
width: 1px !important;
height: 1px !important;
clip: rect(0 0 0 0) !important;
clip-path: inset(50%) !important;
border: none !important;
overflow: hidden !important;
white-space: nowrap !important;
padding: 0 !important;
}
`

export const baseStyles = css`
:host *,
:host *::before,
:host *::after {
box-sizing: inherit;
}

[hidden] {
display: none !important;
}
:host {
box-sizing: border-box;

--ninja-width: 640px;
--ninja-backdrop-filter: none;
--ninja-overflow-background: rgba(255, 255, 255, 0.5);
Expand Down Expand Up @@ -133,10 +158,10 @@ export const baseStyles = css`
}

.group-header {
height: 1.375em;
line-height: 1.375em;
padding-left: 1.25em;
padding-top: 0.5em;
padding-bottom: 0.5em;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
Expand Down Expand Up @@ -181,4 +206,5 @@ export const baseStyles = css`
.ninja-examplekey.backspace {
opacity: 0.7;
}

`;
41 changes: 35 additions & 6 deletions src/ninja-header.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {LitElement, html, css, TemplateResult} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {ref, createRef} from 'lit/directives/ref.js';
import { visuallyHidden } from './base-styles';

@customElement('ninja-header')
export class NinjaHeader extends LitElement {
static override styles = css`
static override styles = [visuallyHidden, css`
:host {
flex: 1;
position: relative;
Expand All @@ -20,7 +21,7 @@ export class NinjaHeader extends LitElement {
background: transparent;
caret-color: var(--ninja-accent-color);
color: var(--ninja-text-color);
outline: none;
outline: transparent;
font-family: var(--ninja-font-family);
}
.search::placeholder {
Expand Down Expand Up @@ -53,7 +54,7 @@ export class NinjaHeader extends LitElement {
display: flex;
border-bottom: var(--ninja-separate-border);
}
`;
`];

@property()
placeholder = '';
Expand All @@ -62,7 +63,23 @@ export class NinjaHeader extends LitElement {
hideBreadcrumbs = false;

@property()
breadcrumbHome = 'Home';
breadcrumbHome = "Home";

/** Maps to `aria-expanded` */
@property({type: Boolean})
expanded = false;

/** Maps to `aria-controls` */
@property()
controls = '';

/** Maps to `aria-labelledby` on <input> */
@property()
searchLabel = '';

/** Maps to `aria-activedescendant` */
@property()
activeDescendant = ''

@property({type: Array})
breadcrumbs: string[] = [];
Expand All @@ -84,7 +101,7 @@ export class NinjaHeader extends LitElement {
</button>`
);
}
breadcrumbs = html`<div class="breadcrumb-list">
breadcrumbs = html`<div class="breadcrumb-list" id="breadcrumb-list">
<button
tabindex="-1"
@click=${() => this.selectParent()}
Expand All @@ -104,12 +121,24 @@ export class NinjaHeader extends LitElement {
type="text"
id="search"
spellcheck="false"
autocomplete="off"
autocomplete="none"
@input="${this._handleInput}"
${ref(this._inputRef)}
placeholder="${this.placeholder}"
class="search"
aria-labelledby="search-label"
aria-expanded="${this.expanded}"
aria-controls="${this.controls} breadcrumb-list"
aria-autocomplete="list"
aria-activedescendant="${this.activeDescendant}"
role="combobox"
/>

<div class="visually-hidden">
<slot name="search-label" id="search-label">
<span>${this.searchLabel}</span>
</slot>
</div>
</div>
`;
}
Expand Down
66 changes: 61 additions & 5 deletions src/ninja-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import {INinjaAction} from './interfaces/ininja-action.js';
import {NinjaHeader} from './ninja-header.js';
import {NinjaAction} from './ninja-action.js';
import {footerHtml} from './ninja-footer.js';
import {baseStyles} from './base-styles.js';
import {baseStyles,visuallyHidden} from './base-styles.js';

@customElement('ninja-keys')
export class NinjaKeys extends LitElement {
static override styles = [baseStyles];
static override styles = [visuallyHidden, baseStyles];

/**
* Search placeholder text
Expand All @@ -28,6 +28,12 @@ export class NinjaKeys extends LitElement {
*/
@property({type: Boolean}) disableHotkeys = false;

/** Maps to `aria-labelledby` for search input */
@property({attribute: "search-label"}) searchLabel = "Search for actions"

/** Maps to `aria-labelledby` for listbox */
@property({attribute: "listbox-label"}) listboxLabel = "List of actions"

/**
* Show or hide breadcrumbs on header
*/
Expand Down Expand Up @@ -99,9 +105,21 @@ export class NinjaKeys extends LitElement {
open(options: {parent?: string} = {}) {
this._bump = true;
this.visible = true;
this._headerRef.value!.focusSearch();
const header = this._headerRef.value
if (header) {
header.focusSearch();
header.expanded = true
header.controls = "actions-list"
}


if (this._actionMatches.length > 0) {
this._selected = this._actionMatches[0];

const header = this._headerRef.value
if (header && this._selected) {
header.activeDescendant = this._selected.id
}
}
this.setParent(options.parent);
}
Expand All @@ -112,6 +130,11 @@ export class NinjaKeys extends LitElement {
close() {
this._bump = false;
this.visible = false;

const header = this._headerRef.value
if (header) {
header.expanded = false
}
}

/**
Expand Down Expand Up @@ -171,8 +194,24 @@ export class NinjaKeys extends LitElement {
return path.reverse();
}

private __selected__?: null | undefined | INinjaAction
/**
* @private
*/
@state()
private _selected?: INinjaAction;
get _selected (): null | undefined | INinjaAction {
return this.__selected__
}

set _selected(action: null | undefined | INinjaAction) {
const header = this._headerRef.value
if (header && action) {
header.activeDescendant = action.id
}
const prevSelection = this.__selected__
this.__selected__ = action
this.requestUpdate("_selected", prevSelection)
}

override connectedCallback() {
super.connectedCallback();
Expand Down Expand Up @@ -394,7 +433,9 @@ export class NinjaKeys extends LitElement {
(action) => action.id,
(action) =>
html`<ninja-action
role="option"
exportparts="ninja-action,ninja-selected,ninja-icon"
aria-selected=${live(action.id === this._selected?.id)}
.selected=${live(action.id === this._selected?.id)}
.hotKeysJoinedView=${this.hotKeysJoinedView}
@mouseover=${(event: MouseEvent) =>
Expand Down Expand Up @@ -422,14 +463,29 @@ export class NinjaKeys extends LitElement {
.placeholder=${this.placeholder}
.hideBreadcrumbs=${this.hideBreadcrumbs}
.breadcrumbs=${this.breadcrumbs}
searchLabel=${this.searchLabel}
@change=${this._handleInput}
@setParent=${(event: CustomEvent<INinjaAction>) =>
this.setParent(event.detail.parent)}
@close=${this.close}
>
</ninja-header>
<div class="modal-body">
<div class="actions-list" part="actions-list">${itemTemplates}</div>
<div
id="actions-list"
class="actions-list"
role="listbox"
part="actions-list"
aria-labelledby="listbox-label"
>
${itemTemplates}
</div>

<div class="visually-hidden">
<slot id="listbox-label" name="listbox-label">
<span>${this.listboxLabel}</span>
</slot>
</div>
</div>
<slot name="footer"> ${footerHtml} </slot>
</div>
Expand Down