@3mo/navigation 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/INavigation.d.ts +35 -0
- package/dist/INavigation.d.ts.map +1 -0
- package/dist/INavigation.js +2 -0
- package/dist/Navigation.d.ts +75 -0
- package/dist/Navigation.d.ts.map +1 -0
- package/dist/Navigation.js +304 -0
- package/dist/NavigationBar.d.ts +32 -0
- package/dist/NavigationBar.d.ts.map +1 -0
- package/dist/NavigationBar.js +90 -0
- package/dist/NavigationBarItem.d.ts +30 -0
- package/dist/NavigationBarItem.d.ts.map +1 -0
- package/dist/NavigationBarItem.js +108 -0
- package/dist/NavigationDrawer.d.ts +38 -0
- package/dist/NavigationDrawer.d.ts.map +1 -0
- package/dist/NavigationDrawer.js +89 -0
- package/dist/NavigationGroup.d.ts +22 -0
- package/dist/NavigationGroup.d.ts.map +1 -0
- package/dist/NavigationGroup.js +17 -0
- package/dist/NavigationLink.d.ts +24 -0
- package/dist/NavigationLink.d.ts.map +1 -0
- package/dist/NavigationLink.js +33 -0
- package/dist/NavigationPresentation.d.ts +8 -0
- package/dist/NavigationPresentation.d.ts.map +1 -0
- package/dist/NavigationPresentation.js +1 -0
- package/dist/NavigationRail.d.ts +52 -0
- package/dist/NavigationRail.d.ts.map +1 -0
- package/dist/NavigationRail.js +213 -0
- package/dist/NavigationRailItem.d.ts +28 -0
- package/dist/NavigationRailItem.d.ts.map +1 -0
- package/dist/NavigationRailItem.js +103 -0
- package/dist/NavigationTree.d.ts +33 -0
- package/dist/NavigationTree.d.ts.map +1 -0
- package/dist/NavigationTree.js +87 -0
- package/dist/NavigationTreeItem.d.ts +19 -0
- package/dist/NavigationTreeItem.d.ts.map +1 -0
- package/dist/NavigationTreeItem.js +115 -0
- package/dist/custom-elements.json +1208 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { bind, Component, component, css, event, html, ifDefined, property, repeat } from '@a11d/lit';
|
|
3
|
+
import { visibleNavigations } from './INavigation.js';
|
|
4
|
+
import '@3mo/menu';
|
|
5
|
+
/**
|
|
6
|
+
* A navigation of a navigation bar: a destination, or a group opening its destinations as a dropdown.
|
|
7
|
+
*
|
|
8
|
+
* @element mo-navigation-bar-item
|
|
9
|
+
*
|
|
10
|
+
* @attr navigation - The navigation this item stands for.
|
|
11
|
+
* @attr open - Whether the group's dropdown is open.
|
|
12
|
+
* @attr data-current - Whether this navigation, or one nested in it, is the page being shown.
|
|
13
|
+
* @fires invoke - Dispatched with the destination which was invoked.
|
|
14
|
+
*/
|
|
15
|
+
let NavigationBarItem = class NavigationBarItem extends Component {
|
|
16
|
+
constructor() {
|
|
17
|
+
super(...arguments);
|
|
18
|
+
this.open = false;
|
|
19
|
+
this.current = false;
|
|
20
|
+
this.tabIndex = 0;
|
|
21
|
+
}
|
|
22
|
+
get childNavigations() {
|
|
23
|
+
return visibleNavigations(this.navigation?.children);
|
|
24
|
+
}
|
|
25
|
+
static get styles() {
|
|
26
|
+
return css `
|
|
27
|
+
:host {
|
|
28
|
+
position: relative;
|
|
29
|
+
display: inline-block;
|
|
30
|
+
border-radius: var(--mo-border-radius);
|
|
31
|
+
padding: 0 0.5rem;
|
|
32
|
+
color: var(--mo-color-on-accent);
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
white-space: nowrap;
|
|
35
|
+
outline: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
:host([data-router-selected]), :host([data-current]), :host(:hover) {
|
|
39
|
+
background-color: color-mix(in srgb, var(--mo-color-background), transparent 88%);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
span {
|
|
43
|
+
line-height: 2rem;
|
|
44
|
+
font-weight: 500;
|
|
45
|
+
font-size: medium;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
mo-menu {
|
|
49
|
+
color: var(--mo-color-foreground);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
mo-focus-ring {
|
|
53
|
+
--mo-focus-ring-color: var(--mo-color-on-accent);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
mo-navigation-menu-item {
|
|
57
|
+
font-size: 14px;
|
|
58
|
+
min-width: 200px;
|
|
59
|
+
&[aria-current] {
|
|
60
|
+
background-color: var(--mo-color-selected);
|
|
61
|
+
color: var(--mo-color-on-selected);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
66
|
+
get template() {
|
|
67
|
+
return html `
|
|
68
|
+
<mo-focus-ring .control=${this} inward></mo-focus-ring>
|
|
69
|
+
<mo-flex id='button' direction='horizontal' alignItems='center' justifyContent='center' gap='2px'>
|
|
70
|
+
<span>${this.navigation?.label}</span>
|
|
71
|
+
${!this.childNavigations.length ? html.nothing : html `
|
|
72
|
+
<mo-icon icon=${this.open ? 'keyboard_arrow_up' : 'keyboard_arrow_down'} style='font-size: large'></mo-icon>
|
|
73
|
+
`}
|
|
74
|
+
</mo-flex>
|
|
75
|
+
${!this.childNavigations.length ? html.nothing : html `
|
|
76
|
+
<mo-menu target='button' .anchor=${this} ?open=${bind(this, 'open')}>
|
|
77
|
+
${repeat(this.childNavigations, child => child, child => this.menuItemTemplate(child))}
|
|
78
|
+
</mo-menu>
|
|
79
|
+
`}
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
menuItemTemplate(navigation) {
|
|
83
|
+
return html `
|
|
84
|
+
${navigation.hasSeparator !== true ? html.nothing : html `<mo-line></mo-line>`}
|
|
85
|
+
<mo-navigation-menu-item
|
|
86
|
+
icon=${ifDefined(navigation.icon)}
|
|
87
|
+
aria-current=${ifDefined(navigation.current === true ? 'page' : undefined)}
|
|
88
|
+
${navigation.link?.({ invocationHandler: () => { this.open = false; this.invoke.dispatch(navigation); } }) ?? html.nothing}
|
|
89
|
+
>${navigation.label}</mo-navigation-menu-item>
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
__decorate([
|
|
94
|
+
event({ bubbles: true, composed: true })
|
|
95
|
+
], NavigationBarItem.prototype, "invoke", void 0);
|
|
96
|
+
__decorate([
|
|
97
|
+
property({ type: Object })
|
|
98
|
+
], NavigationBarItem.prototype, "navigation", void 0);
|
|
99
|
+
__decorate([
|
|
100
|
+
property({ type: Boolean, reflect: true })
|
|
101
|
+
], NavigationBarItem.prototype, "open", void 0);
|
|
102
|
+
__decorate([
|
|
103
|
+
property({ type: Boolean, reflect: true, attribute: 'data-current' })
|
|
104
|
+
], NavigationBarItem.prototype, "current", void 0);
|
|
105
|
+
NavigationBarItem = __decorate([
|
|
106
|
+
component('mo-navigation-bar-item')
|
|
107
|
+
], NavigationBarItem);
|
|
108
|
+
export { NavigationBarItem };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Component, type HTMLTemplateResult, type PropertyValues } from '@a11d/lit';
|
|
2
|
+
import '@3mo/drawer';
|
|
3
|
+
import { type INavigation } from './INavigation.js';
|
|
4
|
+
import './NavigationTree.js';
|
|
5
|
+
/**
|
|
6
|
+
* Every navigation as a tree, in a modal drawer over the page.
|
|
7
|
+
*
|
|
8
|
+
* @element mo-navigation-drawer
|
|
9
|
+
*
|
|
10
|
+
* @attr navigations - The navigations to present.
|
|
11
|
+
* @attr open - Whether the drawer is open.
|
|
12
|
+
* @attr heading - Placed above the tree.
|
|
13
|
+
*
|
|
14
|
+
* @cssprop --mo-navigation-drawer-width - How wide the drawer is. Defaults to `292px`.
|
|
15
|
+
*
|
|
16
|
+
* @fires openChange - Dispatched with the new state whenever the drawer opens or closes.
|
|
17
|
+
* @fires invoke - Dispatched with the destination which was invoked, on its way up from the tree.
|
|
18
|
+
*/
|
|
19
|
+
export declare class NavigationDrawer extends Component {
|
|
20
|
+
readonly openChange: EventDispatcher<boolean>;
|
|
21
|
+
navigations: INavigation[];
|
|
22
|
+
heading?: string | HTMLTemplateResult;
|
|
23
|
+
open: boolean;
|
|
24
|
+
private dispatchedOpen;
|
|
25
|
+
protected updated(props: PropertyValues<this>): void;
|
|
26
|
+
/** Reaching a destination is what the drawer was opened for. */
|
|
27
|
+
protected handleInvoke(): void;
|
|
28
|
+
private get treeElement();
|
|
29
|
+
focus(options?: FocusOptions): void;
|
|
30
|
+
static get styles(): import("@a11d/lit").CSSResult;
|
|
31
|
+
protected get template(): HTMLTemplateResult;
|
|
32
|
+
}
|
|
33
|
+
declare global {
|
|
34
|
+
interface HTMLElementTagNameMap {
|
|
35
|
+
'mo-navigation-drawer': NavigationDrawer;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=NavigationDrawer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationDrawer.d.ts","sourceRoot":"","sources":["../NavigationDrawer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,SAAS,EAA+D,KAAK,kBAAkB,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAA;AACtJ,OAAO,aAAa,CAAA;AACpB,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,qBAAqB,CAAA;AAE5B;;;;;;;;;;;;;GAaG;AACH,qBACa,gBAAiB,SAAQ,SAAS;IACrC,QAAQ,CAAC,UAAU,EAAG,eAAe,CAAC,OAAO,CAAC,CAAA;IAE5B,WAAW,gBAA2B;IACrD,OAAO,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAAA;IAEsC,IAAI,UAAQ;IAGnG,OAAO,CAAC,cAAc,CAAQ;cAEX,OAAO,CAAC,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC;IAQtD,gEAAgE;IAEhE,SAAS,CAAC,YAAY;IAItB,OAAO,KAAK,WAAW,GAA0E;IAExF,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY;IAQrC,WAAoB,MAAM,kCAUzB;IAED,cAAuB,QAAQ,uBAW9B;CACD;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,sBAAsB,EAAE,gBAAgB,CAAA;KACxC;CACD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { bind, Component, component, css, event, eventListener, html, property, style } from '@a11d/lit';
|
|
3
|
+
import '@3mo/drawer';
|
|
4
|
+
import './NavigationTree.js';
|
|
5
|
+
/**
|
|
6
|
+
* Every navigation as a tree, in a modal drawer over the page.
|
|
7
|
+
*
|
|
8
|
+
* @element mo-navigation-drawer
|
|
9
|
+
*
|
|
10
|
+
* @attr navigations - The navigations to present.
|
|
11
|
+
* @attr open - Whether the drawer is open.
|
|
12
|
+
* @attr heading - Placed above the tree.
|
|
13
|
+
*
|
|
14
|
+
* @cssprop --mo-navigation-drawer-width - How wide the drawer is. Defaults to `292px`.
|
|
15
|
+
*
|
|
16
|
+
* @fires openChange - Dispatched with the new state whenever the drawer opens or closes.
|
|
17
|
+
* @fires invoke - Dispatched with the destination which was invoked, on its way up from the tree.
|
|
18
|
+
*/
|
|
19
|
+
let NavigationDrawer = class NavigationDrawer extends Component {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(...arguments);
|
|
22
|
+
this.navigations = new Array();
|
|
23
|
+
this.open = false;
|
|
24
|
+
/* The drawer writes its own state back through the binding, which can skip a property's update hook altogether. */
|
|
25
|
+
this.dispatchedOpen = false;
|
|
26
|
+
}
|
|
27
|
+
updated(props) {
|
|
28
|
+
super.updated(props);
|
|
29
|
+
if (this.dispatchedOpen !== this.open) {
|
|
30
|
+
this.dispatchedOpen = this.open;
|
|
31
|
+
this.openChange.dispatch(this.open);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Reaching a destination is what the drawer was opened for. */
|
|
35
|
+
handleInvoke() {
|
|
36
|
+
this.open = false;
|
|
37
|
+
}
|
|
38
|
+
get treeElement() { return this.renderRoot?.querySelector('mo-navigation-tree') ?? null; }
|
|
39
|
+
focus(options) {
|
|
40
|
+
if (this.treeElement) {
|
|
41
|
+
this.treeElement.focus(options);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
super.focus(options);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
static get styles() {
|
|
48
|
+
return css `
|
|
49
|
+
:host {
|
|
50
|
+
display: contents;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
mo-drawer {
|
|
54
|
+
--mo-drawer-width: var(--mo-navigation-drawer-width, 292px);
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
get template() {
|
|
59
|
+
return html `
|
|
60
|
+
<mo-drawer ?open=${bind(this, 'open')}>
|
|
61
|
+
<mo-flex ${style({ height: '100%' })}>
|
|
62
|
+
${!this.heading ? html.nothing : html `
|
|
63
|
+
<mo-flex direction='horizontal' alignItems='center' ${style({ padding: '24px' })}>${this.heading}</mo-flex>
|
|
64
|
+
`}
|
|
65
|
+
<mo-navigation-tree autofocus ${style({ flex: '1' })} .navigations=${this.navigations}></mo-navigation-tree>
|
|
66
|
+
</mo-flex>
|
|
67
|
+
</mo-drawer>
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
__decorate([
|
|
72
|
+
event()
|
|
73
|
+
], NavigationDrawer.prototype, "openChange", void 0);
|
|
74
|
+
__decorate([
|
|
75
|
+
property({ type: Array })
|
|
76
|
+
], NavigationDrawer.prototype, "navigations", void 0);
|
|
77
|
+
__decorate([
|
|
78
|
+
property()
|
|
79
|
+
], NavigationDrawer.prototype, "heading", void 0);
|
|
80
|
+
__decorate([
|
|
81
|
+
property({ type: Boolean, reflect: true, bindingDefault: true, event: 'openChange' })
|
|
82
|
+
], NavigationDrawer.prototype, "open", void 0);
|
|
83
|
+
__decorate([
|
|
84
|
+
eventListener('invoke')
|
|
85
|
+
], NavigationDrawer.prototype, "handleInvoke", null);
|
|
86
|
+
NavigationDrawer = __decorate([
|
|
87
|
+
component('mo-navigation-drawer')
|
|
88
|
+
], NavigationDrawer);
|
|
89
|
+
export { NavigationDrawer };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type INavigation, type NavigationOptions } from './INavigation.js';
|
|
2
|
+
/**
|
|
3
|
+
* A set of destinations presented together, and named by a label of its own.
|
|
4
|
+
*
|
|
5
|
+
* It has no destination and therefore no `link`: it is hidden once everything in it is, and current
|
|
6
|
+
* once anything in it is.
|
|
7
|
+
*/
|
|
8
|
+
export declare class NavigationGroup implements INavigation {
|
|
9
|
+
readonly options: NavigationOptions & {
|
|
10
|
+
readonly children: Array<INavigation>;
|
|
11
|
+
};
|
|
12
|
+
constructor(options: NavigationOptions & {
|
|
13
|
+
readonly children: Array<INavigation>;
|
|
14
|
+
});
|
|
15
|
+
get label(): string | import("lit-html").HTMLTemplateResult;
|
|
16
|
+
get icon(): import("@3mo/icon/MaterialIcon.js").MaterialIcon | undefined;
|
|
17
|
+
get hidden(): boolean;
|
|
18
|
+
get hasSeparator(): boolean | undefined;
|
|
19
|
+
get children(): INavigation[];
|
|
20
|
+
get current(): boolean;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=NavigationGroup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationGroup.d.ts","sourceRoot":"","sources":["../NavigationGroup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAE3E;;;;;GAKG;AACH,qBAAa,eAAgB,YAAW,WAAW;IACtC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG;QAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;KAAE;gBAAtE,OAAO,EAAE,iBAAiB,GAAG;QAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;KAAE;IAE3F,IAAI,KAAK,mDAAsC;IAC/C,IAAI,IAAI,iEAA+B;IACvC,IAAI,MAAM,YAAiH;IAC3H,IAAI,YAAY,wBAAuC;IACvD,IAAI,QAAQ,kBAAmC;IAC/C,IAAI,OAAO,YAAiE;CAC5E"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A set of destinations presented together, and named by a label of its own.
|
|
3
|
+
*
|
|
4
|
+
* It has no destination and therefore no `link`: it is hidden once everything in it is, and current
|
|
5
|
+
* once anything in it is.
|
|
6
|
+
*/
|
|
7
|
+
export class NavigationGroup {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
get label() { return this.options.label ?? ''; }
|
|
12
|
+
get icon() { return this.options.icon; }
|
|
13
|
+
get hidden() { return this.options.hidden || !this.children.length || this.children.every(child => child.hidden === true); }
|
|
14
|
+
get hasSeparator() { return this.options.hasSeparator; }
|
|
15
|
+
get children() { return this.options.children; }
|
|
16
|
+
get current() { return this.children.some(child => child.current === true); }
|
|
17
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type DirectiveResult } from '@a11d/lit';
|
|
2
|
+
import { routerLink, type RoutableComponent } from '@a11d/lit-application';
|
|
3
|
+
import { type INavigation, type NavigationInvocationOptions, type NavigationOptions } from './INavigation.js';
|
|
4
|
+
type RouterLinkParameters = Exclude<Parameters<typeof routerLink>[0], RoutableComponent>;
|
|
5
|
+
/**
|
|
6
|
+
* A destination of the application's router.
|
|
7
|
+
*
|
|
8
|
+
* Almost everything it reports is derived rather than stored: the label falls back to the component's
|
|
9
|
+
* own `label` metadata, `current` is answered by the router per read, and `link` hands `routerLink` the
|
|
10
|
+
* whole parameter set. Applications subclass it to layer their own gates onto `hidden` and `icon`.
|
|
11
|
+
*/
|
|
12
|
+
export declare class NavigationLink implements INavigation {
|
|
13
|
+
readonly options: NavigationOptions & RouterLinkParameters;
|
|
14
|
+
constructor(options: NavigationOptions & RouterLinkParameters);
|
|
15
|
+
get label(): import("lit-html").HTMLTemplateResult;
|
|
16
|
+
get icon(): import("@3mo/icon/MaterialIcon.js").MaterialIcon | undefined;
|
|
17
|
+
get hidden(): boolean;
|
|
18
|
+
get hasSeparator(): boolean | undefined;
|
|
19
|
+
get current(): boolean;
|
|
20
|
+
get routerLinkParameters(): RouterLinkParameters;
|
|
21
|
+
link(options?: NavigationInvocationOptions): DirectiveResult;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=NavigationLink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationLink.d.ts","sourceRoot":"","sources":["../NavigationLink.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAsB,MAAM,uBAAuB,CAAA;AAC9F,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,2BAA2B,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAE7G,KAAK,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAA;AAExF;;;;;;GAMG;AACH,qBAAa,cAAe,YAAW,WAAW;IACrC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,oBAAoB;gBAAjD,OAAO,EAAE,iBAAiB,GAAG,oBAAoB;IAEtE,IAAI,KAAK,0CAIR;IAED,IAAI,IAAI,iEAA+B;IACvC,IAAI,MAAM,YAA0C;IACpD,IAAI,YAAY,wBAAuC;IACvD,IAAI,OAAO,YAAiF;IAC5F,IAAI,oBAAoB,IAA4B,oBAAoB,CAAE;IAE1E,IAAI,CAAC,OAAO,CAAC,EAAE,2BAA2B,GAAG,eAAe;CAS5D"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { html } from '@a11d/lit';
|
|
2
|
+
import { routerLink, NavigationStrategy } from '@a11d/lit-application';
|
|
3
|
+
/**
|
|
4
|
+
* A destination of the application's router.
|
|
5
|
+
*
|
|
6
|
+
* Almost everything it reports is derived rather than stored: the label falls back to the component's
|
|
7
|
+
* own `label` metadata, `current` is answered by the router per read, and `link` hands `routerLink` the
|
|
8
|
+
* whole parameter set. Applications subclass it to layer their own gates onto `hidden` and `icon`.
|
|
9
|
+
*/
|
|
10
|
+
export class NavigationLink {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
get label() {
|
|
15
|
+
const value = this.options.label ?? label.get(this.options.component.constructor);
|
|
16
|
+
const appendedEllipsis = (this.options.navigationStrategy ?? NavigationStrategy.Page) === NavigationStrategy.Page ? '' : ' ...';
|
|
17
|
+
return html `${value}${appendedEllipsis}`;
|
|
18
|
+
}
|
|
19
|
+
get icon() { return this.options.icon; }
|
|
20
|
+
get hidden() { return this.options.hidden ?? false; }
|
|
21
|
+
get hasSeparator() { return this.options.hasSeparator; }
|
|
22
|
+
get current() { return this.options.component.urlMatches({ mode: this.options.matchMode }); }
|
|
23
|
+
get routerLinkParameters() { return this.options; }
|
|
24
|
+
link(options) {
|
|
25
|
+
return routerLink({
|
|
26
|
+
...this.routerLinkParameters,
|
|
27
|
+
invocationHandler: () => {
|
|
28
|
+
options?.invocationHandler?.();
|
|
29
|
+
this.routerLinkParameters.invocationHandler?.();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a set of navigations is presented:
|
|
3
|
+
* - `bar` — a horizontal row in the application's header, groups opening as dropdowns.
|
|
4
|
+
* - `rail` — a vertical strip at the leading edge, a group's children shown in a panel beside it.
|
|
5
|
+
* - `drawer` — a modal panel over the page, holding every navigation as a tree.
|
|
6
|
+
*/
|
|
7
|
+
export type NavigationPresentation = 'bar' | 'rail' | 'drawer';
|
|
8
|
+
//# sourceMappingURL=NavigationPresentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationPresentation.d.ts","sourceRoot":"","sources":["../NavigationPresentation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Component } from '@a11d/lit';
|
|
2
|
+
import { type INavigation } from './INavigation.js';
|
|
3
|
+
import './NavigationRailItem.js';
|
|
4
|
+
import './NavigationTree.js';
|
|
5
|
+
/**
|
|
6
|
+
* The navigations as a vertical strip at the leading edge, each one an icon with its label beneath it.
|
|
7
|
+
*
|
|
8
|
+
* A group's destinations are shown in a panel beside the strip: docked next to the page while there is
|
|
9
|
+
* room for both, and overlaid over the page while there is not.
|
|
10
|
+
*
|
|
11
|
+
* @element mo-navigation-rail
|
|
12
|
+
*
|
|
13
|
+
* @attr navigations - The navigations to present.
|
|
14
|
+
* @attr docked - Whether the panel stands beside the page rather than over it.
|
|
15
|
+
* @fires invoke - Dispatched with the destination which was invoked.
|
|
16
|
+
*
|
|
17
|
+
* @slot header - Placed above the navigations, e.g. a logo or a button which creates something.
|
|
18
|
+
* @slot footer - Placed below the navigations.
|
|
19
|
+
*
|
|
20
|
+
* @csspart strip - The strip of navigations.
|
|
21
|
+
* @csspart panel - The panel holding the destinations of the navigation being shown.
|
|
22
|
+
*
|
|
23
|
+
* @cssprop --mo-navigation-rail-size - How wide the strip is. Defaults to `5.5rem`.
|
|
24
|
+
* @cssprop --mo-navigation-rail-panel-size - How wide the panel is. Defaults to `17rem`.
|
|
25
|
+
*/
|
|
26
|
+
export declare class NavigationRail extends Component {
|
|
27
|
+
readonly invoke: EventDispatcher<INavigation>;
|
|
28
|
+
navigations: INavigation[];
|
|
29
|
+
docked: boolean;
|
|
30
|
+
private selection?;
|
|
31
|
+
get items(): import("./NavigationRailItem.js").NavigationRailItem[];
|
|
32
|
+
/** The navigation whose destinations the panel shows. */
|
|
33
|
+
get shownNavigation(): INavigation | undefined;
|
|
34
|
+
private get currentNavigation();
|
|
35
|
+
protected handleLocationChange(): void;
|
|
36
|
+
/** A destination was reached, from the strip or from the panel, so the panel has done its job. */
|
|
37
|
+
protected handleInvoke(): void;
|
|
38
|
+
protected handleDocumentPointerDown(event: PointerEvent): void;
|
|
39
|
+
protected handleKeyDown(event: KeyboardEvent): void;
|
|
40
|
+
focus(options?: FocusOptions): void;
|
|
41
|
+
static get styles(): import("@a11d/lit").CSSResult;
|
|
42
|
+
protected get template(): import("lit-html").HTMLTemplateResult;
|
|
43
|
+
private itemTemplate;
|
|
44
|
+
private get panelTemplate();
|
|
45
|
+
private handleItemClick;
|
|
46
|
+
}
|
|
47
|
+
declare global {
|
|
48
|
+
interface HTMLElementTagNameMap {
|
|
49
|
+
'mo-navigation-rail': NavigationRail;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=NavigationRail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationRail.d.ts","sourceRoot":"","sources":["../NavigationRail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAuE,MAAM,WAAW,CAAA;AAC1G,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,yBAAyB,CAAA;AAChC,OAAO,qBAAqB,CAAA;AAE5B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBACa,cAAe,SAAQ,SAAS;IACF,QAAQ,CAAC,MAAM,EAAG,eAAe,CAAC,WAAW,CAAC,CAAA;IAE7D,WAAW,gBAA2B;IACrB,MAAM,UAAQ;IAEjD,OAAO,CAAC,SAAS,CAAC,CAAa;IAExC,IAAI,KAAK,2DAER;IAED,yDAAyD;IACzD,IAAI,eAAe,4BAGlB;IAED,OAAO,KAAK,iBAAiB,GAE5B;IAGD,SAAS,CAAC,oBAAoB;IAI9B,kGAAkG;IAElG,SAAS,CAAC,YAAY;IAKtB,SAAS,CAAC,yBAAyB,CAAC,KAAK,EAAE,YAAY;IAOvD,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,aAAa;IAQnC,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY;IASrC,WAAoB,MAAM,kCAyEzB;IAED,cAAuB,QAAQ,0CAW9B;IAED,OAAO,CAAC,YAAY;IAYpB,OAAO,KAAK,aAAa,GAQxB;IAED,OAAO,CAAC,eAAe;CAOvB;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,oBAAoB,EAAE,cAAc,CAAA;KACpC;CACD"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { Component, component, css, event, eventListener, html, property, repeat, state } from '@a11d/lit';
|
|
3
|
+
import { visibleNavigations } from './INavigation.js';
|
|
4
|
+
import './NavigationRailItem.js';
|
|
5
|
+
import './NavigationTree.js';
|
|
6
|
+
/**
|
|
7
|
+
* The navigations as a vertical strip at the leading edge, each one an icon with its label beneath it.
|
|
8
|
+
*
|
|
9
|
+
* A group's destinations are shown in a panel beside the strip: docked next to the page while there is
|
|
10
|
+
* room for both, and overlaid over the page while there is not.
|
|
11
|
+
*
|
|
12
|
+
* @element mo-navigation-rail
|
|
13
|
+
*
|
|
14
|
+
* @attr navigations - The navigations to present.
|
|
15
|
+
* @attr docked - Whether the panel stands beside the page rather than over it.
|
|
16
|
+
* @fires invoke - Dispatched with the destination which was invoked.
|
|
17
|
+
*
|
|
18
|
+
* @slot header - Placed above the navigations, e.g. a logo or a button which creates something.
|
|
19
|
+
* @slot footer - Placed below the navigations.
|
|
20
|
+
*
|
|
21
|
+
* @csspart strip - The strip of navigations.
|
|
22
|
+
* @csspart panel - The panel holding the destinations of the navigation being shown.
|
|
23
|
+
*
|
|
24
|
+
* @cssprop --mo-navigation-rail-size - How wide the strip is. Defaults to `5.5rem`.
|
|
25
|
+
* @cssprop --mo-navigation-rail-panel-size - How wide the panel is. Defaults to `17rem`.
|
|
26
|
+
*/
|
|
27
|
+
let NavigationRail = class NavigationRail extends Component {
|
|
28
|
+
constructor() {
|
|
29
|
+
super(...arguments);
|
|
30
|
+
this.navigations = new Array();
|
|
31
|
+
this.docked = false;
|
|
32
|
+
}
|
|
33
|
+
get items() {
|
|
34
|
+
return [...this.renderRoot?.querySelectorAll('mo-navigation-rail-item') ?? []];
|
|
35
|
+
}
|
|
36
|
+
/** The navigation whose destinations the panel shows. */
|
|
37
|
+
get shownNavigation() {
|
|
38
|
+
const selection = this.selection && this.navigations.includes(this.selection) ? this.selection : undefined;
|
|
39
|
+
return selection ?? (this.docked ? this.currentNavigation : undefined);
|
|
40
|
+
}
|
|
41
|
+
get currentNavigation() {
|
|
42
|
+
return visibleNavigations(this.navigations).find(navigation => navigation.current === true && visibleNavigations(navigation.children).length > 0);
|
|
43
|
+
}
|
|
44
|
+
handleLocationChange() {
|
|
45
|
+
this.requestUpdate();
|
|
46
|
+
}
|
|
47
|
+
/** A destination was reached, from the strip or from the panel, so the panel has done its job. */
|
|
48
|
+
handleInvoke() {
|
|
49
|
+
this.selection = undefined;
|
|
50
|
+
}
|
|
51
|
+
handleDocumentPointerDown(event) {
|
|
52
|
+
if (!this.docked && this.selection && !event.composedPath().includes(this)) {
|
|
53
|
+
this.selection = undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
handleKeyDown(event) {
|
|
57
|
+
if (!this.docked && this.selection && event.key === 'Escape') {
|
|
58
|
+
const dismissed = this.selection;
|
|
59
|
+
this.selection = undefined;
|
|
60
|
+
this.items.find(item => item.navigation === dismissed)?.focus();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
focus(options) {
|
|
64
|
+
const item = this.items[0];
|
|
65
|
+
if (item) {
|
|
66
|
+
item.focus(options);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
super.focus(options);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
static get styles() {
|
|
73
|
+
return css `
|
|
74
|
+
:host {
|
|
75
|
+
position: relative;
|
|
76
|
+
z-index: 1;
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: row;
|
|
79
|
+
background: var(--mo-color-surface);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
[part=strip] {
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-direction: column;
|
|
85
|
+
inline-size: var(--mo-navigation-rail-size, 5.5rem);
|
|
86
|
+
border-inline-end: 1px solid var(--mo-color-transparent-gray-3);
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#items {
|
|
91
|
+
display: flex;
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
gap: 4px;
|
|
94
|
+
padding: 8px 6px;
|
|
95
|
+
flex: 1;
|
|
96
|
+
overflow-y: auto;
|
|
97
|
+
overflow-x: hidden;
|
|
98
|
+
scrollbar-width: thin;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
[part=panel] {
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
inline-size: var(--mo-navigation-rail-panel-size, 17rem);
|
|
105
|
+
background: var(--mo-color-surface);
|
|
106
|
+
border-inline-end: 1px solid var(--mo-color-transparent-gray-3);
|
|
107
|
+
overflow: hidden;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Without room for both, the panel leaves the layout and stands over the page instead. */
|
|
111
|
+
:host(:not([docked])) [part=panel] {
|
|
112
|
+
position: absolute;
|
|
113
|
+
inset-block: 0;
|
|
114
|
+
inset-inline-start: 100%;
|
|
115
|
+
box-shadow: var(--mo-shadow);
|
|
116
|
+
translate: 0;
|
|
117
|
+
opacity: 1;
|
|
118
|
+
transition: translate var(--mo-duration-quick, 250ms) cubic-bezier(0.2, 0, 0, 1), opacity var(--mo-duration-quick, 250ms);
|
|
119
|
+
|
|
120
|
+
/* An overlay is only ever inserted, so it comes in from the strip it belongs to and leaves at once. */
|
|
121
|
+
@starting-style {
|
|
122
|
+
translate: -1.5rem 0;
|
|
123
|
+
opacity: 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
:host(:not([docked])) [part=panel]:dir(rtl) {
|
|
128
|
+
@starting-style {
|
|
129
|
+
translate: 1.5rem 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#panel-heading {
|
|
134
|
+
padding: 20px 20px 8px;
|
|
135
|
+
font-size: 1.125rem;
|
|
136
|
+
font-weight: 500;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
mo-navigation-tree {
|
|
140
|
+
flex: 1;
|
|
141
|
+
overflow-y: auto;
|
|
142
|
+
scrollbar-width: thin;
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
get template() {
|
|
147
|
+
return html `
|
|
148
|
+
<nav part='strip' role='navigation'>
|
|
149
|
+
<slot name='header'></slot>
|
|
150
|
+
<div id='items'>
|
|
151
|
+
${repeat(visibleNavigations(this.navigations), navigation => navigation, navigation => this.itemTemplate(navigation))}
|
|
152
|
+
</div>
|
|
153
|
+
<slot name='footer'></slot>
|
|
154
|
+
</nav>
|
|
155
|
+
${this.panelTemplate}
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
itemTemplate(navigation) {
|
|
159
|
+
return html `
|
|
160
|
+
<mo-navigation-rail-item
|
|
161
|
+
.navigation=${navigation}
|
|
162
|
+
.current=${navigation.current === true}
|
|
163
|
+
.selected=${navigation === this.shownNavigation}
|
|
164
|
+
@click=${() => this.handleItemClick(navigation)}
|
|
165
|
+
${navigation.link?.({ invocationHandler: () => this.invoke.dispatch(navigation) })}
|
|
166
|
+
></mo-navigation-rail-item>
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
get panelTemplate() {
|
|
170
|
+
const navigation = this.shownNavigation;
|
|
171
|
+
return !navigation ? html.nothing : html `
|
|
172
|
+
<div part='panel'>
|
|
173
|
+
<div id='panel-heading'>${navigation.label}</div>
|
|
174
|
+
<mo-navigation-tree .navigations=${[...visibleNavigations(navigation.children)]}></mo-navigation-tree>
|
|
175
|
+
</div>
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
handleItemClick(navigation) {
|
|
179
|
+
if (visibleNavigations(navigation.children).length === 0) {
|
|
180
|
+
this.selection = undefined;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
this.selection = this.shownNavigation === navigation && !this.docked ? undefined : navigation;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
__decorate([
|
|
187
|
+
event({ bubbles: true, composed: true })
|
|
188
|
+
], NavigationRail.prototype, "invoke", void 0);
|
|
189
|
+
__decorate([
|
|
190
|
+
property({ type: Array })
|
|
191
|
+
], NavigationRail.prototype, "navigations", void 0);
|
|
192
|
+
__decorate([
|
|
193
|
+
property({ type: Boolean, reflect: true })
|
|
194
|
+
], NavigationRail.prototype, "docked", void 0);
|
|
195
|
+
__decorate([
|
|
196
|
+
state()
|
|
197
|
+
], NavigationRail.prototype, "selection", void 0);
|
|
198
|
+
__decorate([
|
|
199
|
+
eventListener({ target: window, type: 'popstate' })
|
|
200
|
+
], NavigationRail.prototype, "handleLocationChange", null);
|
|
201
|
+
__decorate([
|
|
202
|
+
eventListener('invoke')
|
|
203
|
+
], NavigationRail.prototype, "handleInvoke", null);
|
|
204
|
+
__decorate([
|
|
205
|
+
eventListener({ target: document, type: 'pointerdown' })
|
|
206
|
+
], NavigationRail.prototype, "handleDocumentPointerDown", null);
|
|
207
|
+
__decorate([
|
|
208
|
+
eventListener({ target: window, type: 'keydown' })
|
|
209
|
+
], NavigationRail.prototype, "handleKeyDown", null);
|
|
210
|
+
NavigationRail = __decorate([
|
|
211
|
+
component('mo-navigation-rail')
|
|
212
|
+
], NavigationRail);
|
|
213
|
+
export { NavigationRail };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Component } from '@a11d/lit';
|
|
2
|
+
import { type INavigation } from './INavigation.js';
|
|
3
|
+
/**
|
|
4
|
+
* A navigation of a navigation rail: an icon with its label beneath it.
|
|
5
|
+
*
|
|
6
|
+
* @element mo-navigation-rail-item
|
|
7
|
+
*
|
|
8
|
+
* @attr navigation - The navigation this item stands for.
|
|
9
|
+
* @attr data-current - Whether this navigation, or one nested in it, is the page being shown.
|
|
10
|
+
* @attr selected - Whether the rail is showing this navigation's destinations.
|
|
11
|
+
*
|
|
12
|
+
* @csspart indicator - The shape behind the icon, which marks the navigation the page belongs to.
|
|
13
|
+
* @csspart label - The label beneath the icon.
|
|
14
|
+
*/
|
|
15
|
+
export declare class NavigationRailItem extends Component {
|
|
16
|
+
navigation: INavigation;
|
|
17
|
+
current: boolean;
|
|
18
|
+
selected: boolean;
|
|
19
|
+
tabIndex: number;
|
|
20
|
+
static get styles(): import("@a11d/lit").CSSResult;
|
|
21
|
+
protected get template(): import("lit-html").HTMLTemplateResult;
|
|
22
|
+
}
|
|
23
|
+
declare global {
|
|
24
|
+
interface HTMLElementTagNameMap {
|
|
25
|
+
'mo-navigation-rail-item': NavigationRailItem;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=NavigationRailItem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationRailItem.d.ts","sourceRoot":"","sources":["../NavigationRailItem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkC,MAAM,WAAW,CAAA;AACrE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnD;;;;;;;;;;;GAWG;AACH,qBACa,kBAAmB,SAAQ,SAAS;IACpB,UAAU,EAAG,WAAW,CAAA;IACmB,OAAO,UAAQ;IAC1C,QAAQ,UAAQ;IAEnD,QAAQ,SAAI;IAErB,WAAoB,MAAM,kCA0DzB;IAED,cAAuB,QAAQ,0CAQ9B;CACD;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,yBAAyB,EAAE,kBAAkB,CAAA;KAC7C;CACD"}
|