@vaadin/side-nav 24.1.0 → 24.2.0-alpha1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/side-nav",
3
- "version": "24.1.0",
3
+ "version": "24.2.0-alpha1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -35,15 +35,15 @@
35
35
  "web-component"
36
36
  ],
37
37
  "dependencies": {
38
- "@vaadin/component-base": "~24.1.0",
39
- "@vaadin/vaadin-lumo-styles": "~24.1.0",
40
- "@vaadin/vaadin-material-styles": "~24.1.0",
41
- "@vaadin/vaadin-themable-mixin": "~24.1.0",
38
+ "@vaadin/component-base": "24.2.0-alpha1",
39
+ "@vaadin/vaadin-lumo-styles": "24.2.0-alpha1",
40
+ "@vaadin/vaadin-material-styles": "24.2.0-alpha1",
41
+ "@vaadin/vaadin-themable-mixin": "24.2.0-alpha1",
42
42
  "lit": "^2.0.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@esm-bundle/chai": "^4.3.4",
46
- "@vaadin/testing-helpers": "^0.4.0",
46
+ "@vaadin/testing-helpers": "^0.4.2",
47
47
  "lit": "^2.0.0",
48
48
  "sinon": "^13.0.2"
49
49
  },
@@ -51,5 +51,5 @@
51
51
  "web-types.json",
52
52
  "web-types.lit.json"
53
53
  ],
54
- "gitHead": "7fdfe7d5ceb4c305a894f8e9dc11e5b7d04cf1f2"
54
+ "gitHead": "0dbb118320203ab6c0c07450a3e718815367589f"
55
55
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2017 - 2023 Vaadin Ltd.
3
+ * Copyright (c) 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { css } from 'lit';
@@ -14,7 +14,7 @@ export const sideNavItemBaseStyles = css`
14
14
  display: none !important;
15
15
  }
16
16
 
17
- a {
17
+ [part='link'] {
18
18
  flex: auto;
19
19
  min-width: 0;
20
20
  display: flex;
@@ -30,6 +30,16 @@ export const sideNavItemBaseStyles = css`
30
30
  flex: none;
31
31
  }
32
32
 
33
+ [part='children'] {
34
+ padding: 0;
35
+ margin: 0;
36
+ list-style-type: none;
37
+ }
38
+
39
+ :host(:not([has-children])) button {
40
+ display: none !important;
41
+ }
42
+
33
43
  :host(:not([path])) a {
34
44
  position: relative;
35
45
  }
@@ -56,12 +66,6 @@ export const sideNavItemBaseStyles = css`
56
66
  text-overflow: ellipsis;
57
67
  white-space: nowrap;
58
68
  }
59
-
60
- slot[name='children'] {
61
- /* Needed to make role="list" work */
62
- display: block;
63
- width: 100%;
64
- }
65
69
  `;
66
70
 
67
71
  export const sideNavBaseStyles = css`
@@ -73,32 +77,24 @@ export const sideNavBaseStyles = css`
73
77
  display: none !important;
74
78
  }
75
79
 
76
- summary {
80
+ button {
77
81
  display: flex;
78
82
  align-items: center;
79
- justify-content: space-between;
80
- }
81
-
82
- summary ::slotted([slot='label']) {
83
- display: block;
84
- }
85
-
86
- summary::-webkit-details-marker {
87
- display: none;
88
- }
89
-
90
- summary::marker {
91
- content: '';
92
- }
93
-
94
- summary::after {
95
- display: inline-flex;
96
- align-items: center;
97
- justify-content: center;
83
+ justify-content: inherit;
84
+ width: 100%;
85
+ margin: 0;
86
+ padding: 0;
87
+ background-color: initial;
88
+ color: inherit;
89
+ border: initial;
90
+ outline: none;
91
+ font: inherit;
92
+ text-align: inherit;
98
93
  }
99
94
 
100
- slot {
101
- /* Needed to make role="list" work */
102
- display: block;
95
+ [part='children'] {
96
+ padding: 0;
97
+ margin: 0;
98
+ list-style-type: none;
103
99
  }
104
100
  `;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2017 - 2023 Vaadin Ltd.
3
+ * Copyright (c) 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { LitElement } from 'lit';
@@ -54,6 +54,17 @@ export type SideNavItemEventMap = HTMLElementEventMap & SideNavItemCustomEventMa
54
54
  * </vaadin-side-nav-item>
55
55
  * ```
56
56
  *
57
+ * ### Styling
58
+ *
59
+ * Part name | Description
60
+ * ----------------|----------------
61
+ * `content` | The element that wraps link and toggle button
62
+ * `children` | The element that wraps child items
63
+ * `link` | The clickable anchor used for navigation
64
+ * `toggle-button` | The toggle button
65
+ *
66
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
67
+ *
57
68
  * @fires {CustomEvent} expanded-changed - Fired when the `expanded` property changes.
58
69
  */
59
70
  declare class SideNavItem extends ElementMixin(ThemableMixin(PolylitMixin(LitElement))) {
@@ -62,6 +73,13 @@ declare class SideNavItem extends ElementMixin(ThemableMixin(PolylitMixin(LitEle
62
73
  */
63
74
  path: string | null | undefined;
64
75
 
76
+ /**
77
+ * A comma-separated list of alternative paths matching this item.
78
+ *
79
+ * @attr {string} path-aliases
80
+ */
81
+ pathAliases: string | null | undefined;
82
+
65
83
  /**
66
84
  * Whether to show the child items or not
67
85
  */
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2017 - 2023 Vaadin Ltd.
3
+ * Copyright (c) 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { html, LitElement } from 'lit';
7
7
  import { ifDefined } from 'lit/directives/if-defined.js';
8
8
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
9
9
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
10
+ import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
11
+ import { matchPaths } from '@vaadin/component-base/src/url-utils.js';
10
12
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
11
13
  import { sideNavItemBaseStyles } from './vaadin-side-nav-base-styles.js';
12
14
 
@@ -14,6 +16,31 @@ function isEnabled() {
14
16
  return window.Vaadin && window.Vaadin.featureFlags && !!window.Vaadin.featureFlags.sideNavComponent;
15
17
  }
16
18
 
19
+ /**
20
+ * A controller to manage the item content children slot.
21
+ */
22
+ class ChildrenController extends SlotController {
23
+ constructor(host) {
24
+ super(host, 'children', null, { observe: true, multiple: true });
25
+ }
26
+
27
+ /**
28
+ * @protected
29
+ * @override
30
+ */
31
+ initAddedNode() {
32
+ this.host.requestUpdate();
33
+ }
34
+
35
+ /**
36
+ * @protected
37
+ * @override
38
+ */
39
+ teardownNode() {
40
+ this.host.requestUpdate();
41
+ }
42
+ }
43
+
17
44
  /**
18
45
  * A navigation item to be used within `<vaadin-side-nav>`. Represents a navigation target.
19
46
  * Not intended to be used separately.
@@ -49,6 +76,17 @@ function isEnabled() {
49
76
  * </vaadin-side-nav-item>
50
77
  * ```
51
78
  *
79
+ * ### Styling
80
+ *
81
+ * Part name | Description
82
+ * ----------------|----------------
83
+ * `content` | The element that wraps link and toggle button
84
+ * `children` | The element that wraps child items
85
+ * `link` | The clickable anchor used for navigation
86
+ * `toggle-button` | The toggle button
87
+ *
88
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
89
+ *
52
90
  * @fires {CustomEvent} expanded-changed - Fired when the `expanded` property changes.
53
91
  *
54
92
  * @extends LitElement
@@ -68,6 +106,13 @@ class SideNavItem extends ElementMixin(ThemableMixin(PolylitMixin(LitElement)))
68
106
  */
69
107
  path: String,
70
108
 
109
+ /**
110
+ * A comma-separated list of alternative paths matching this item.
111
+ *
112
+ * @attr {string} path-aliases
113
+ */
114
+ pathAliases: String,
115
+
71
116
  /**
72
117
  * Whether to show the child items or not
73
118
  *
@@ -100,6 +145,12 @@ class SideNavItem extends ElementMixin(ThemableMixin(PolylitMixin(LitElement)))
100
145
  return sideNavItemBaseStyles;
101
146
  }
102
147
 
148
+ constructor() {
149
+ super();
150
+
151
+ this._childrenController = new ChildrenController(this);
152
+ }
153
+
103
154
  /** @protected */
104
155
  get _button() {
105
156
  return this.shadowRoot.querySelector('button');
@@ -110,6 +161,9 @@ class SideNavItem extends ElementMixin(ThemableMixin(PolylitMixin(LitElement)))
110
161
  * @override
111
162
  */
112
163
  firstUpdated() {
164
+ // Controller to detect whether the item has child items.
165
+ this.addController(this._childrenController);
166
+
113
167
  // By default, if the user hasn't provided a custom role,
114
168
  // the role attribute is set to "listitem".
115
169
  if (!this.hasAttribute('role')) {
@@ -124,9 +178,11 @@ class SideNavItem extends ElementMixin(ThemableMixin(PolylitMixin(LitElement)))
124
178
  updated(props) {
125
179
  super.updated(props);
126
180
 
127
- if (props.has('path')) {
181
+ if (props.has('path') || props.has('pathAliases')) {
128
182
  this.__updateActive();
129
183
  }
184
+
185
+ this.toggleAttribute('has-children', this._childrenController.nodes.length > 0);
130
186
  }
131
187
 
132
188
  /** @protected */
@@ -146,27 +202,44 @@ class SideNavItem extends ElementMixin(ThemableMixin(PolylitMixin(LitElement)))
146
202
  /** @protected */
147
203
  render() {
148
204
  return html`
149
- <a href="${ifDefined(this.path)}" part="item" aria-current="${this.active ? 'page' : 'false'}">
150
- <slot name="prefix"></slot>
151
- <slot></slot>
152
- <slot name="suffix"></slot>
205
+ <div part="content" @click="${this._onContentClick}">
206
+ <a href="${ifDefined(this.path)}" part="link" aria-current="${this.active ? 'page' : 'false'}">
207
+ <slot name="prefix"></slot>
208
+ <slot></slot>
209
+ <slot name="suffix"></slot>
210
+ </a>
153
211
  <button
154
212
  part="toggle-button"
155
- @click="${this.__toggleExpanded}"
156
- ?hidden="${!this.querySelector('[slot=children]')}"
213
+ @click="${this._onButtonClick}"
157
214
  aria-controls="children"
158
215
  aria-expanded="${this.expanded}"
159
216
  aria-label="Toggle child items"
160
217
  ></button>
161
- </a>
162
- <slot name="children" role="list" part="children" id="children" ?hidden="${!this.expanded}"></slot>
218
+ </div>
219
+ <ul part="children" ?hidden="${!this.expanded}">
220
+ <slot name="children"></slot>
221
+ </ul>
163
222
  `;
164
223
  }
165
224
 
166
225
  /** @private */
167
- __toggleExpanded(e) {
168
- e.preventDefault();
169
- e.stopPropagation();
226
+ _onButtonClick(event) {
227
+ // Prevent the event from being handled
228
+ // by the content click listener below
229
+ event.stopPropagation();
230
+ this.__toggleExpanded();
231
+ }
232
+
233
+ /** @private */
234
+ _onContentClick() {
235
+ // Toggle item expanded state unless the link has a non-empty path
236
+ if (this.path == null && this.hasAttribute('has-children')) {
237
+ this.__toggleExpanded();
238
+ }
239
+ }
240
+
241
+ /** @private */
242
+ __toggleExpanded() {
170
243
  this.expanded = !this.expanded;
171
244
  }
172
245
 
@@ -185,23 +258,16 @@ class SideNavItem extends ElementMixin(ThemableMixin(PolylitMixin(LitElement)))
185
258
 
186
259
  /** @private */
187
260
  __calculateActive() {
188
- const pathAbsolute = this.path.startsWith('/');
189
- // Absolute path or no base uri in use. No special comparison needed
190
- if (pathAbsolute) {
191
- // Compare an absolute view path
192
- return document.location.pathname === this.path;
261
+ if (this.path == null) {
262
+ return false;
193
263
  }
194
- const hasBaseUri = document.baseURI !== document.location.href;
195
- if (!hasBaseUri) {
196
- // Compare a relative view path (strip the starting slash)
197
- return document.location.pathname.substring(1) === this.path;
264
+ if (matchPaths(document.location.pathname, this.path)) {
265
+ return true;
198
266
  }
199
- const pathRelativeToRoot = document.location.pathname;
200
- const basePath = new URL(document.baseURI).pathname;
201
- const pathWithoutBase = pathRelativeToRoot.substring(basePath.length);
202
- const pathRelativeToBase =
203
- basePath !== pathRelativeToRoot && pathRelativeToRoot.startsWith(basePath) ? pathWithoutBase : pathRelativeToRoot;
204
- return pathRelativeToBase === this.path;
267
+ return (
268
+ this.pathAliases != null &&
269
+ this.pathAliases.split(',').some((alias) => matchPaths(document.location.pathname, alias))
270
+ );
205
271
  }
206
272
  }
207
273
 
@@ -4,6 +4,7 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { LitElement } from 'lit';
7
+ import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
7
8
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
8
9
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
9
10
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
@@ -48,9 +49,19 @@ export type SideNavEventMap = HTMLElementEventMap & SideNavCustomEventMap;
48
49
  * </vaadin-side-nav>
49
50
  * ```
50
51
  *
52
+ * ### Styling
53
+ *
54
+ * Part name | Description
55
+ * ----------------|----------------
56
+ * `label` | The label element
57
+ * `children` | The element that wraps child items
58
+ * `toggle-button` | The toggle button
59
+ *
60
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
61
+ *
51
62
  * @fires {CustomEvent} collapsed-changed - Fired when the `collapsed` property changes.
52
63
  */
53
- declare class SideNav extends ElementMixin(ThemableMixin(PolylitMixin(LitElement))) {
64
+ declare class SideNav extends FocusMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement)))) {
54
65
  /**
55
66
  * Whether the side nav is collapsible. When enabled, the toggle icon is shown.
56
67
  */
@@ -4,6 +4,8 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { html, LitElement } from 'lit';
7
+ import { ifDefined } from 'lit/directives/if-defined.js';
8
+ import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
7
9
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
8
10
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
9
11
  import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js';
@@ -43,6 +45,16 @@ function isEnabled() {
43
45
  * </vaadin-side-nav>
44
46
  * ```
45
47
  *
48
+ * ### Styling
49
+ *
50
+ * Part name | Description
51
+ * ----------------|----------------
52
+ * `label` | The label element
53
+ * `children` | The element that wraps child items
54
+ * `toggle-button` | The toggle button
55
+ *
56
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
57
+ *
46
58
  * @fires {CustomEvent} collapsed-changed - Fired when the `collapsed` property changes.
47
59
  *
48
60
  * @extends LitElement
@@ -50,11 +62,15 @@ function isEnabled() {
50
62
  * @mixes ThemableMixin
51
63
  * @mixes ElementMixin
52
64
  */
53
- class SideNav extends ElementMixin(ThemableMixin(PolylitMixin(LitElement))) {
65
+ class SideNav extends FocusMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement)))) {
54
66
  static get is() {
55
67
  return 'vaadin-side-nav';
56
68
  }
57
69
 
70
+ static get shadowRootOptions() {
71
+ return { ...LitElement.shadowRootOptions, delegatesFocus: true };
72
+ }
73
+
58
74
  static get properties() {
59
75
  return {
60
76
  /**
@@ -86,6 +102,17 @@ class SideNav extends ElementMixin(ThemableMixin(PolylitMixin(LitElement))) {
86
102
  return sideNavBaseStyles;
87
103
  }
88
104
 
105
+ constructor() {
106
+ super();
107
+
108
+ this._labelId = `side-nav-label-${generateUniqueId()}`;
109
+ }
110
+
111
+ /** @protected */
112
+ get focusElement() {
113
+ return this.shadowRoot.querySelector('button');
114
+ }
115
+
89
116
  /** @protected */
90
117
  firstUpdated() {
91
118
  // By default, if the user hasn't provided a custom role,
@@ -97,34 +124,55 @@ class SideNav extends ElementMixin(ThemableMixin(PolylitMixin(LitElement))) {
97
124
 
98
125
  /** @protected */
99
126
  render() {
100
- const label = this.querySelector('[slot="label"]');
101
- if (label && this.collapsible) {
102
- return html`
103
- <details ?open="${!this.collapsed}" @toggle="${this.__toggleCollapsed}">${this.__renderBody(label)}</details>
104
- `;
127
+ return html`
128
+ <button
129
+ part="label"
130
+ @click="${this._onLabelClick}"
131
+ aria-expanded="${ifDefined(this.collapsible ? !this.collapsed : null)}"
132
+ aria-controls="children"
133
+ >
134
+ <slot name="label" @slotchange="${this._onLabelSlotChange}"></slot>
135
+ <span part="toggle-button" aria-hidden="true"></span>
136
+ </button>
137
+ <ul id="children" part="children" ?hidden="${this.collapsed}" aria-hidden="${this.collapsed ? 'true' : 'false'}">
138
+ <slot></slot>
139
+ </ul>
140
+ `;
141
+ }
142
+
143
+ /**
144
+ * @param {Event} event
145
+ * @return {boolean}
146
+ * @protected
147
+ * @override
148
+ */
149
+ _shouldSetFocus(event) {
150
+ return event.composedPath()[0] === this.focusElement;
151
+ }
152
+
153
+ /** @private */
154
+ _onLabelClick() {
155
+ if (this.collapsible) {
156
+ this.__toggleCollapsed();
105
157
  }
106
- return this.__renderBody(label);
107
158
  }
108
159
 
109
160
  /** @private */
110
- __renderBody(label) {
161
+ _onLabelSlotChange() {
162
+ const label = this.querySelector('[slot="label"]');
111
163
  if (label) {
112
- if (!label.id) label.id = `side-nav-label-${generateUniqueId()}`;
164
+ if (!label.id) {
165
+ label.id = this._labelId;
166
+ }
113
167
  this.setAttribute('aria-labelledby', label.id);
114
168
  } else {
115
169
  this.removeAttribute('aria-labelledby');
116
170
  }
117
- return html`
118
- <summary part="label" ?hidden="${label == null}">
119
- <slot name="label" @slotchange="${() => this.requestUpdate()}"></slot>
120
- </summary>
121
- <slot role="list"></slot>
122
- `;
123
171
  }
124
172
 
125
173
  /** @private */
126
- __toggleCollapsed(e) {
127
- this.collapsed = !e.target.open;
174
+ __toggleCollapsed() {
175
+ this.collapsed = !this.collapsed;
128
176
  }
129
177
  }
130
178
 
@@ -0,0 +1,117 @@
1
+ import '@vaadin/vaadin-lumo-styles/color.js';
2
+ import '@vaadin/vaadin-lumo-styles/typography.js';
3
+ import '@vaadin/vaadin-lumo-styles/sizing.js';
4
+ import '@vaadin/vaadin-lumo-styles/spacing.js';
5
+ import '@vaadin/vaadin-lumo-styles/style.js';
6
+ import '@vaadin/vaadin-lumo-styles/font-icons.js';
7
+ import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
8
+
9
+ export const sideNavItemStyles = css`
10
+ [part='content'] {
11
+ display: flex;
12
+ align-items: center;
13
+ }
14
+
15
+ [part='link'] {
16
+ width: 100%;
17
+ gap: var(--lumo-space-xs);
18
+ padding: var(--lumo-space-s);
19
+ padding-inline-start: calc(var(--lumo-space-s) + var(--_child-indent, 0px));
20
+ border-radius: var(--lumo-border-radius-m);
21
+ transition: background-color 140ms, color 140ms;
22
+ cursor: var(--lumo-clickable-cursor, default);
23
+ min-height: var(--lumo-icon-size-m);
24
+ }
25
+
26
+ [part='toggle-button'] {
27
+ position: relative;
28
+ border: 0;
29
+ margin: calc((var(--lumo-icon-size-m) - var(--lumo-size-s)) / 2) 0;
30
+ margin-inline-end: calc(var(--lumo-space-xs) * -1);
31
+ padding: 0;
32
+ background: transparent;
33
+ font: inherit;
34
+ color: var(--lumo-tertiary-text-color);
35
+ width: var(--lumo-size-s);
36
+ height: var(--lumo-size-s);
37
+ cursor: var(--lumo-clickable-cursor, default);
38
+ transition: color 140ms;
39
+ }
40
+
41
+ :host([has-children]) [part='content'] {
42
+ padding-inline-end: var(--lumo-space-s);
43
+ }
44
+
45
+ @media (any-hover: hover) {
46
+ [part='link']:hover {
47
+ color: var(--lumo-header-text-color);
48
+ }
49
+
50
+ [part='toggle-button']:hover {
51
+ color: var(--lumo-body-text-color);
52
+ }
53
+ }
54
+
55
+ [part='link']:active:focus {
56
+ background-color: var(--lumo-contrast-5pct);
57
+ }
58
+
59
+ [part='toggle-button']::before {
60
+ font-family: lumo-icons;
61
+ content: var(--lumo-icons-dropdown);
62
+ font-size: 1.5em;
63
+ line-height: var(--lumo-size-s);
64
+ display: inline-block;
65
+ transform: rotate(-90deg);
66
+ transition: transform 140ms;
67
+ }
68
+
69
+ :host([expanded]) [part='toggle-button']::before {
70
+ transform: none;
71
+ }
72
+
73
+ @supports selector(:focus-visible) {
74
+ [part='link'],
75
+ [part='toggle-button'] {
76
+ outline: none;
77
+ }
78
+
79
+ [part='link']:focus-visible,
80
+ [part='toggle-button']:focus-visible {
81
+ border-radius: var(--lumo-border-radius-m);
82
+ box-shadow: 0 0 0 2px var(--lumo-primary-color-50pct);
83
+ }
84
+ }
85
+
86
+ [part='link']:active {
87
+ color: var(--lumo-header-text-color);
88
+ }
89
+
90
+ slot:not([name]) {
91
+ margin: 0 var(--lumo-space-s);
92
+ }
93
+
94
+ slot[name='prefix']::slotted(:is(vaadin-icon, [class*='icon'])) {
95
+ padding: 0.1em;
96
+ color: var(--lumo-contrast-60pct);
97
+ }
98
+
99
+ :host([active]) slot[name='prefix']::slotted(:is(vaadin-icon, [class*='icon'])) {
100
+ color: inherit;
101
+ }
102
+
103
+ slot[name='children'] {
104
+ --_child-indent: calc(var(--_child-indent-2, 0px) + var(--vaadin-side-nav-child-indent, var(--lumo-space-l)));
105
+ }
106
+
107
+ slot[name='children']::slotted(*) {
108
+ --_child-indent-2: var(--_child-indent);
109
+ }
110
+
111
+ :host([active]) [part='link'] {
112
+ color: var(--lumo-primary-text-color);
113
+ background-color: var(--lumo-primary-color-10pct);
114
+ }
115
+ `;
116
+
117
+ registerStyles('vaadin-side-nav-item', sideNavItemStyles, { moduleId: 'lumo-side-nav-item' });
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2017 - 2023 Vaadin Ltd.
3
+ * Copyright (c) 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import './vaadin-side-nav-styles.js';
6
+ import './vaadin-side-nav-item-styles.js';
7
7
  import '../../src/vaadin-side-nav-item.js';
@@ -6,104 +6,6 @@ import '@vaadin/vaadin-lumo-styles/style.js';
6
6
  import '@vaadin/vaadin-lumo-styles/font-icons.js';
7
7
  import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
8
8
 
9
- export const sideNavItemStyles = css`
10
- a {
11
- gap: var(--lumo-space-xs);
12
- padding: var(--lumo-space-s);
13
- padding-inline-start: calc(var(--lumo-space-s) + var(--_child-indent, 0px));
14
- border-radius: var(--lumo-border-radius-m);
15
- transition: background-color 140ms, color 140ms;
16
- cursor: var(--lumo-clickable-cursor, default);
17
- min-height: var(--lumo-icon-size-m);
18
- }
19
-
20
- button {
21
- border: 0;
22
- margin: calc((var(--lumo-icon-size-m) - var(--lumo-size-s)) / 2) 0;
23
- margin-inline-end: calc(var(--lumo-space-xs) * -1);
24
- padding: 0;
25
- background: transparent;
26
- font: inherit;
27
- color: var(--lumo-tertiary-text-color);
28
- width: var(--lumo-size-s);
29
- height: var(--lumo-size-s);
30
- cursor: var(--lumo-clickable-cursor, default);
31
- transition: color 140ms;
32
- }
33
-
34
- @media (any-hover: hover) {
35
- a:hover {
36
- color: var(--lumo-header-text-color);
37
- }
38
-
39
- button:hover {
40
- color: var(--lumo-body-text-color);
41
- }
42
- }
43
-
44
- a:active:focus {
45
- background-color: var(--lumo-contrast-5pct);
46
- }
47
-
48
- button::before {
49
- font-family: lumo-icons;
50
- content: var(--lumo-icons-dropdown);
51
- font-size: 1.5em;
52
- line-height: var(--lumo-size-s);
53
- display: inline-block;
54
- transform: rotate(-90deg);
55
- transition: transform 140ms;
56
- }
57
-
58
- :host([expanded]) button::before {
59
- transform: none;
60
- }
61
-
62
- @supports selector(:focus-visible) {
63
- a,
64
- button {
65
- outline: none;
66
- }
67
-
68
- a:focus-visible,
69
- button:focus-visible {
70
- border-radius: var(--lumo-border-radius-m);
71
- box-shadow: 0 0 0 2px var(--lumo-primary-color-50pct);
72
- }
73
- }
74
-
75
- a:active {
76
- color: var(--lumo-header-text-color);
77
- }
78
-
79
- slot:not([name]) {
80
- margin: 0 var(--lumo-space-xs);
81
- }
82
-
83
- slot[name='prefix']::slotted(:is(vaadin-icon, [class*='icon'])) {
84
- color: var(--lumo-contrast-60pct);
85
- }
86
-
87
- :host([active]) slot[name='prefix']::slotted(:is(vaadin-icon, [class*='icon'])) {
88
- color: inherit;
89
- }
90
-
91
- slot[name='children'] {
92
- --_child-indent: calc(var(--_child-indent-2, 0px) + var(--vaadin-side-nav-child-indent, var(--lumo-space-l)));
93
- }
94
-
95
- slot[name='children']::slotted(*) {
96
- --_child-indent-2: var(--_child-indent);
97
- }
98
-
99
- :host([active]) a {
100
- color: var(--lumo-primary-text-color);
101
- background-color: var(--lumo-primary-color-10pct);
102
- }
103
- `;
104
-
105
- registerStyles('vaadin-side-nav-item', sideNavItemStyles, { moduleId: 'lumo-side-nav-item' });
106
-
107
9
  export const sideNavStyles = css`
108
10
  :host {
109
11
  font-family: var(--lumo-font-family);
@@ -114,49 +16,58 @@ export const sideNavStyles = css`
114
16
  -webkit-tap-highlight-color: transparent;
115
17
  }
116
18
 
117
- summary {
118
- cursor: var(--lumo-clickable-cursor, default);
19
+ [part='label'] {
20
+ display: flex;
21
+ align-items: center;
22
+ width: 100%;
23
+ outline: none;
24
+ box-sizing: border-box;
119
25
  border-radius: var(--lumo-border-radius-m);
26
+ font-family: var(--lumo-font-family);
27
+ font-size: var(--lumo-font-size-s);
28
+ font-weight: 500;
29
+ line-height: var(--lumo-line-height-xs);
120
30
  }
121
31
 
122
- summary ::slotted([slot='label']) {
123
- font-size: var(--lumo-font-size-s);
32
+ [part='label'] ::slotted([slot='label']) {
124
33
  color: var(--lumo-secondary-text-color);
125
34
  margin: var(--lumo-space-s);
126
- border-radius: inherit;
127
35
  }
128
36
 
129
- summary::after {
130
- font-family: lumo-icons;
131
- color: var(--lumo-tertiary-text-color);
132
- font-size: var(--lumo-icon-size-m);
37
+ :host([focus-ring]) [part='label'] {
38
+ box-shadow: 0 0 0 2px var(--lumo-primary-color-50pct);
39
+ }
40
+
41
+ [part='toggle-button'] {
42
+ display: inline-flex;
43
+ align-items: center;
44
+ justify-content: center;
133
45
  width: var(--lumo-size-s);
134
46
  height: var(--lumo-size-s);
135
- transition: transform 140ms;
136
- margin: 0 var(--lumo-space-xs);
47
+ margin-inline-start: auto;
48
+ margin-inline-end: var(--lumo-space-xs);
49
+ font-size: var(--lumo-icon-size-m);
50
+ line-height: 1;
51
+ color: var(--lumo-contrast-60pct);
52
+ font-family: 'lumo-icons';
53
+ cursor: var(--lumo-clickable-cursor);
137
54
  }
138
55
 
139
- :host([collapsible]) summary::after {
140
- content: var(--lumo-icons-dropdown);
56
+ [part='toggle-button']::before {
57
+ content: var(--lumo-icons-angle-right);
141
58
  }
142
59
 
143
- @media (any-hover: hover) {
144
- summary:hover::after {
145
- color: var(--lumo-body-text-color);
146
- }
60
+ :host(:not([collapsible])) [part='toggle-button'] {
61
+ display: none !important;
147
62
  }
148
63
 
149
- :host([collapsed]) summary::after {
150
- transform: rotate(-90deg);
64
+ :host(:not([collapsed])) [part='toggle-button'] {
65
+ transform: rotate(90deg);
151
66
  }
152
67
 
153
- @supports selector(:focus-visible) {
154
- summary {
155
- outline: none;
156
- }
157
-
158
- summary:focus-visible {
159
- box-shadow: 0 0 0 2px var(--lumo-primary-color-50pct);
68
+ @media (any-hover: hover) {
69
+ [part='label']:hover [part='toggle-button'] {
70
+ color: var(--lumo-body-text-color);
160
71
  }
161
72
  }
162
73
  `;
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2017 - 2023 Vaadin Ltd.
3
+ * Copyright (c) 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import './vaadin-side-nav-item.js';
7
+ import './vaadin-side-nav-styles.js';
7
8
  import '../../src/vaadin-side-nav.js';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2017 - 2023 Vaadin Ltd.
3
+ * Copyright (c) 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import '../../src/vaadin-side-nav.js';
@@ -1,2 +1,2 @@
1
- import './theme/lumo/vaadin-side-nav.js';
1
+ import './theme/lumo/vaadin-side-nav-item.js';
2
2
  export * from './src/vaadin-side-nav-item.js';
package/web-types.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/side-nav",
4
- "version": "24.1.0",
4
+ "version": "24.2.0-alpha1",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
8
8
  "elements": [
9
9
  {
10
10
  "name": "vaadin-side-nav-item",
11
- "description": "A navigation item to be used within `<vaadin-side-nav>`. Represents a navigation target.\nNot intended to be used separately.\n\n```html\n<vaadin-side-nav-item>\n Item 1\n <vaadin-side-nav-item path=\"/path1\" slot=\"children\">\n Child item 1\n </vaadin-side-nav-item>\n <vaadin-side-nav-item path=\"/path2\" slot=\"children\">\n Child item 2\n </vaadin-side-nav-item>\n</vaadin-side-nav-item>\n```\n\n### Customization\n\nYou can configure the item by using `slot` names.\n\nSlot name | Description\n----------|-------------\n`prefix` | A slot for content before the label (e.g. an icon).\n`suffix` | A slot for content after the label (e.g. an icon).\n\n#### Example\n\n```html\n<vaadin-side-nav-item>\n <vaadin-icon icon=\"vaadin:chart\" slot=\"prefix\"></vaadin-icon>\n Item\n <span theme=\"badge primary\" slot=\"suffix\">Suffix</span>\n</vaadin-side-nav-item>\n```",
11
+ "description": "A navigation item to be used within `<vaadin-side-nav>`. Represents a navigation target.\nNot intended to be used separately.\n\n```html\n<vaadin-side-nav-item>\n Item 1\n <vaadin-side-nav-item path=\"/path1\" slot=\"children\">\n Child item 1\n </vaadin-side-nav-item>\n <vaadin-side-nav-item path=\"/path2\" slot=\"children\">\n Child item 2\n </vaadin-side-nav-item>\n</vaadin-side-nav-item>\n```\n\n### Customization\n\nYou can configure the item by using `slot` names.\n\nSlot name | Description\n----------|-------------\n`prefix` | A slot for content before the label (e.g. an icon).\n`suffix` | A slot for content after the label (e.g. an icon).\n\n#### Example\n\n```html\n<vaadin-side-nav-item>\n <vaadin-icon icon=\"vaadin:chart\" slot=\"prefix\"></vaadin-icon>\n Item\n <span theme=\"badge primary\" slot=\"suffix\">Suffix</span>\n</vaadin-side-nav-item>\n```\n\n### Styling\n\nPart name | Description\n----------------|----------------\n`content` | The element that wraps link and toggle button\n`children` | The element that wraps child items\n`link` | The clickable anchor used for navigation\n`toggle-button` | The toggle button\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
12
12
  "attributes": [
13
13
  {
14
14
  "name": "path",
@@ -21,6 +21,17 @@
21
21
  ]
22
22
  }
23
23
  },
24
+ {
25
+ "name": "path-aliases",
26
+ "description": "A comma-separated list of alternative paths matching this item.",
27
+ "value": {
28
+ "type": [
29
+ "string",
30
+ "null",
31
+ "undefined"
32
+ ]
33
+ }
34
+ },
24
35
  {
25
36
  "name": "expanded",
26
37
  "description": "Whether to show the child items or not",
@@ -55,6 +66,17 @@
55
66
  ]
56
67
  }
57
68
  },
69
+ {
70
+ "name": "pathAliases",
71
+ "description": "A comma-separated list of alternative paths matching this item.",
72
+ "value": {
73
+ "type": [
74
+ "string",
75
+ "null",
76
+ "undefined"
77
+ ]
78
+ }
79
+ },
58
80
  {
59
81
  "name": "expanded",
60
82
  "description": "Whether to show the child items or not",
@@ -75,7 +97,7 @@
75
97
  },
76
98
  {
77
99
  "name": "vaadin-side-nav",
78
- "description": "`<vaadin-side-nav>` is a Web Component for navigation menus.\n\n```html\n<vaadin-side-nav>\n <vaadin-side-nav-item>Item 1</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 2</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 3</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 4</vaadin-side-nav-item>\n</vaadin-side-nav>\n```\n\n### Customization\n\nYou can configure the component by using `slot` names.\n\nSlot name | Description\n----------|-------------\n`label` | The label (text) inside the side nav.\n\n#### Example\n\n```html\n<vaadin-side-nav>\n <span slot=\"label\">Main menu</span>\n <vaadin-side-nav-item>Item</vaadin-side-nav-item>\n</vaadin-side-nav>\n```",
100
+ "description": "`<vaadin-side-nav>` is a Web Component for navigation menus.\n\n```html\n<vaadin-side-nav>\n <vaadin-side-nav-item>Item 1</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 2</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 3</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 4</vaadin-side-nav-item>\n</vaadin-side-nav>\n```\n\n### Customization\n\nYou can configure the component by using `slot` names.\n\nSlot name | Description\n----------|-------------\n`label` | The label (text) inside the side nav.\n\n#### Example\n\n```html\n<vaadin-side-nav>\n <span slot=\"label\">Main menu</span>\n <vaadin-side-nav-item>Item</vaadin-side-nav-item>\n</vaadin-side-nav>\n```\n\n### Styling\n\nPart name | Description\n----------------|----------------\n`label` | The label element\n`children` | The element that wraps child items\n`toggle-button` | The toggle button\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
79
101
  "attributes": [
80
102
  {
81
103
  "name": "collapsible",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/side-nav",
4
- "version": "24.1.0",
4
+ "version": "24.2.0-alpha1",
5
5
  "description-markup": "markdown",
6
6
  "framework": "lit",
7
7
  "framework-config": {
@@ -16,7 +16,7 @@
16
16
  "elements": [
17
17
  {
18
18
  "name": "vaadin-side-nav-item",
19
- "description": "A navigation item to be used within `<vaadin-side-nav>`. Represents a navigation target.\nNot intended to be used separately.\n\n```html\n<vaadin-side-nav-item>\n Item 1\n <vaadin-side-nav-item path=\"/path1\" slot=\"children\">\n Child item 1\n </vaadin-side-nav-item>\n <vaadin-side-nav-item path=\"/path2\" slot=\"children\">\n Child item 2\n </vaadin-side-nav-item>\n</vaadin-side-nav-item>\n```\n\n### Customization\n\nYou can configure the item by using `slot` names.\n\nSlot name | Description\n----------|-------------\n`prefix` | A slot for content before the label (e.g. an icon).\n`suffix` | A slot for content after the label (e.g. an icon).\n\n#### Example\n\n```html\n<vaadin-side-nav-item>\n <vaadin-icon icon=\"vaadin:chart\" slot=\"prefix\"></vaadin-icon>\n Item\n <span theme=\"badge primary\" slot=\"suffix\">Suffix</span>\n</vaadin-side-nav-item>\n```",
19
+ "description": "A navigation item to be used within `<vaadin-side-nav>`. Represents a navigation target.\nNot intended to be used separately.\n\n```html\n<vaadin-side-nav-item>\n Item 1\n <vaadin-side-nav-item path=\"/path1\" slot=\"children\">\n Child item 1\n </vaadin-side-nav-item>\n <vaadin-side-nav-item path=\"/path2\" slot=\"children\">\n Child item 2\n </vaadin-side-nav-item>\n</vaadin-side-nav-item>\n```\n\n### Customization\n\nYou can configure the item by using `slot` names.\n\nSlot name | Description\n----------|-------------\n`prefix` | A slot for content before the label (e.g. an icon).\n`suffix` | A slot for content after the label (e.g. an icon).\n\n#### Example\n\n```html\n<vaadin-side-nav-item>\n <vaadin-icon icon=\"vaadin:chart\" slot=\"prefix\"></vaadin-icon>\n Item\n <span theme=\"badge primary\" slot=\"suffix\">Suffix</span>\n</vaadin-side-nav-item>\n```\n\n### Styling\n\nPart name | Description\n----------------|----------------\n`content` | The element that wraps link and toggle button\n`children` | The element that wraps child items\n`link` | The clickable anchor used for navigation\n`toggle-button` | The toggle button\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
20
20
  "extension": true,
21
21
  "attributes": [
22
22
  {
@@ -33,6 +33,13 @@
33
33
  "kind": "expression"
34
34
  }
35
35
  },
36
+ {
37
+ "name": ".pathAliases",
38
+ "description": "A comma-separated list of alternative paths matching this item.",
39
+ "value": {
40
+ "kind": "expression"
41
+ }
42
+ },
36
43
  {
37
44
  "name": "@expanded-changed",
38
45
  "description": "Fired when the `expanded` property changes.",
@@ -44,7 +51,7 @@
44
51
  },
45
52
  {
46
53
  "name": "vaadin-side-nav",
47
- "description": "`<vaadin-side-nav>` is a Web Component for navigation menus.\n\n```html\n<vaadin-side-nav>\n <vaadin-side-nav-item>Item 1</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 2</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 3</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 4</vaadin-side-nav-item>\n</vaadin-side-nav>\n```\n\n### Customization\n\nYou can configure the component by using `slot` names.\n\nSlot name | Description\n----------|-------------\n`label` | The label (text) inside the side nav.\n\n#### Example\n\n```html\n<vaadin-side-nav>\n <span slot=\"label\">Main menu</span>\n <vaadin-side-nav-item>Item</vaadin-side-nav-item>\n</vaadin-side-nav>\n```",
54
+ "description": "`<vaadin-side-nav>` is a Web Component for navigation menus.\n\n```html\n<vaadin-side-nav>\n <vaadin-side-nav-item>Item 1</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 2</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 3</vaadin-side-nav-item>\n <vaadin-side-nav-item>Item 4</vaadin-side-nav-item>\n</vaadin-side-nav>\n```\n\n### Customization\n\nYou can configure the component by using `slot` names.\n\nSlot name | Description\n----------|-------------\n`label` | The label (text) inside the side nav.\n\n#### Example\n\n```html\n<vaadin-side-nav>\n <span slot=\"label\">Main menu</span>\n <vaadin-side-nav-item>Item</vaadin-side-nav-item>\n</vaadin-side-nav>\n```\n\n### Styling\n\nPart name | Description\n----------------|----------------\n`label` | The label element\n`children` | The element that wraps child items\n`toggle-button` | The toggle button\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
48
55
  "extension": true,
49
56
  "attributes": [
50
57
  {