@reliability-design/web 1.0.6 → 1.2.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/components/rel-accordion/rel-accordion.ts +20 -31
- package/dist/components/rel-alert/rel-alert.ts +81 -11
- package/dist/components/rel-avatar/rel-avatar.ts +6 -3
- package/dist/components/rel-badge/rel-badge.ts +8 -3
- package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +6 -6
- package/dist/components/rel-button/rel-button.ts +118 -16
- package/dist/components/rel-calendar/rel-calendar.ts +16 -9
- package/dist/components/rel-card/rel-card.ts +9 -17
- package/dist/components/rel-cardmedia/rel-cardmedia.ts +20 -23
- package/dist/components/rel-carousel/rel-carousel.ts +596 -469
- package/dist/components/rel-checkbox/rel-checkbox.ts +98 -15
- package/dist/components/rel-chip/rel-chip.ts +291 -186
- package/dist/components/rel-container/rel-container.ts +2 -2
- package/dist/components/rel-date-picker/rel-date-picker.ts +23 -19
- package/dist/components/rel-divider/rel-divider.ts +12 -3
- package/dist/components/rel-drawer/rel-drawer-footer.ts +27 -0
- package/dist/components/rel-drawer/rel-drawer.ts +57 -113
- package/dist/components/rel-kbd/rel-kbd.ts +94 -118
- package/dist/components/rel-layout/rel-layout.ts +49 -58
- package/dist/components/rel-menu/rel-menu.ts +359 -327
- package/dist/components/rel-menubar/rel-menubar-menu.css +3 -0
- package/dist/components/rel-menubar/rel-menubar-menu.ts +2 -1
- package/dist/components/rel-menubar/rel-menubar.ts +602 -576
- package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +240 -143
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +227 -331
- package/dist/components/rel-navbar/rel-navbar.ts +13 -0
- package/dist/components/rel-otp/rel-otp.ts +22 -25
- package/dist/components/rel-pagination/rel-pagination.ts +112 -63
- package/dist/components/rel-popover/rel-popover-content.ts +6 -5
- package/dist/components/rel-popover/rel-popover.ts +42 -20
- package/dist/components/rel-progress/rel-progress.ts +10 -9
- package/dist/components/rel-radio/rel-radio.ts +228 -155
- package/dist/components/rel-range-picker/rel-range-picker.css +349 -0
- package/dist/components/rel-range-picker/rel-range-picker.ts +1079 -0
- package/dist/components/rel-resizable/rel-resizable-handle.ts +12 -15
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +41 -20
- package/dist/components/rel-select/rel-select.ts +112 -27
- package/dist/components/rel-sheet/rel-sheet.ts +362 -683
- package/dist/components/rel-sidebar/rel-sidebar.ts +13 -5
- package/dist/components/rel-slider/rel-slider.ts +74 -29
- package/dist/components/rel-snackbar/rel-snackbar.ts +67 -37
- package/dist/components/rel-stepper/rel-stepper.ts +43 -31
- package/dist/components/rel-switch/rel-switch.ts +26 -12
- package/dist/components/rel-tab/rel-tab.ts +57 -37
- package/dist/components/rel-table/rel-table-body.ts +5 -0
- package/dist/components/rel-table/rel-table-caption.ts +6 -1
- package/dist/components/rel-table/rel-table-cell.ts +42 -49
- package/dist/components/rel-table/rel-table-footer.ts +5 -0
- package/dist/components/rel-table/rel-table-head.ts +13 -1
- package/dist/components/rel-table/rel-table-header.ts +5 -0
- package/dist/components/rel-table/rel-table-row.ts +7 -3
- package/dist/components/rel-table/rel-table.ts +8 -2
- package/dist/components/rel-textfield/rel-textfield.ts +153 -44
- package/dist/components/rel-toast/rel-toast.ts +56 -22
- package/dist/components/rel-tooltip/rel-tooltip.ts +52 -9
- package/dist/components/rel-topbar/rel-topbar-actions.ts +24 -0
- package/dist/components/rel-topbar/rel-topbar-brand.ts +47 -0
- package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +63 -0
- package/dist/components/rel-topbar/rel-topbar-dropdown.ts +28 -0
- package/dist/components/rel-topbar/rel-topbar-item.ts +320 -0
- package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +24 -0
- package/dist/components/rel-topbar/rel-topbar-profile.ts +222 -0
- package/dist/components/rel-topbar/rel-topbar-utilities.ts +24 -0
- package/dist/components/rel-topbar/rel-topbar.ts +438 -0
- package/dist/index.d.mts +375 -411
- package/dist/index.d.ts +375 -411
- package/dist/index.js +7587 -7980
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7647 -8023
- package/dist/index.mjs.map +1 -1
- package/dist/rel-menubar-menu-BEEQDAXW.css +3 -0
- package/dist/styles/vars-v2.css +525 -0
- package/dist/styles/vars.css +79 -27
- package/dist/{vars-ITL2K3KO.css → vars-I2PC74XD.css} +79 -27
- package/dist/vars-v2-VMGWTFJZ.css +525 -0
- package/package.json +3 -1
- package/dist/components/rel-quote-page/README.md +0 -166
- package/dist/components/rel-quote-page/rel-quote-page.ts +0 -816
|
@@ -1,331 +1,227 @@
|
|
|
1
|
-
import { LitElement, html, css } from "lit";
|
|
2
|
-
import { customElement, property, state, query } from "lit/decorators.js";
|
|
3
|
-
import { RelNavMenuItem } from "./rel-nav-menu-item";
|
|
4
|
-
|
|
5
|
-
type DensityType = "comfortable" | "compact";
|
|
6
|
-
|
|
7
|
-
@customElement("rel-nav-menu")
|
|
8
|
-
export class RelNavMenu extends LitElement {
|
|
9
|
-
@property({ type: String, reflect: true }) open = "";
|
|
10
|
-
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
11
|
-
@property({ type: String }) density: DensityType = "comfortable";
|
|
12
|
-
|
|
13
|
-
@state() private items: RelNavMenuItem[] = [];
|
|
14
|
-
@state() private focusedIndex = 0;
|
|
15
|
-
@state() private hoverSwitchEnabled = false;
|
|
16
|
-
|
|
17
|
-
@query(".nav-container") private container!: HTMLElement;
|
|
18
|
-
|
|
19
|
-
static styles = css`
|
|
20
|
-
:host {
|
|
21
|
-
display: block;
|
|
22
|
-
font-family: var(--rel-fontFamilies-primary);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.nav-container {
|
|
26
|
-
display: flex;
|
|
27
|
-
align-items: center;
|
|
28
|
-
gap: var(--rel-spacing-space-3xs);
|
|
29
|
-
padding: var(--rel-spacing-space-2xs);
|
|
30
|
-
background: var(--rel-colors-gray-light);
|
|
31
|
-
border-radius: var(--rel-borders-radius-border-radius-200);
|
|
32
|
-
border: var(--rel-borders-width-stroke-100) solid var(--rel-colors-gray-border);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.nav-container.compact {
|
|
36
|
-
padding: var(--rel-spacing-space-3xs);
|
|
37
|
-
gap: 0;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (!
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (this.open) {
|
|
118
|
-
this.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (this.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
this.
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
this.closePanel();
|
|
229
|
-
const openItem = this.items.find((i) => i.itemId === this.open);
|
|
230
|
-
if (openItem) {
|
|
231
|
-
const trigger = openItem.shadowRoot?.querySelector(".trigger") as HTMLElement;
|
|
232
|
-
trigger?.focus();
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
} else if (key === "ArrowDown" && this.open) {
|
|
236
|
-
event.preventDefault();
|
|
237
|
-
// Move focus into the panel
|
|
238
|
-
const panel = this.shadowRoot?.querySelector(
|
|
239
|
-
`.dropdown-panel[data-item-id="${this.open}"]`
|
|
240
|
-
) as HTMLElement;
|
|
241
|
-
if (panel) {
|
|
242
|
-
const firstFocusable = panel.querySelector("a, button") as HTMLElement;
|
|
243
|
-
firstFocusable?.focus();
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
private focusItem(index: number) {
|
|
249
|
-
const focusableItems = this.items.filter((item) => !item.disabled);
|
|
250
|
-
const item = focusableItems[index];
|
|
251
|
-
if (item) {
|
|
252
|
-
const trigger = item.shadowRoot?.querySelector(".trigger") as HTMLElement;
|
|
253
|
-
trigger?.focus();
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
private hasItemContent(item: RelNavMenuItem): boolean {
|
|
258
|
-
const slot = item.shadowRoot?.querySelector("slot");
|
|
259
|
-
if (!slot) return false;
|
|
260
|
-
const nodes = slot.assignedNodes({ flatten: true });
|
|
261
|
-
return nodes.some(
|
|
262
|
-
(node) => node.nodeType === Node.ELEMENT_NODE || node.textContent?.trim()
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
private renderDropdownPanel(item: RelNavMenuItem) {
|
|
267
|
-
if (!this.hasItemContent(item)) return null;
|
|
268
|
-
|
|
269
|
-
const isOpen = this.open === item.itemId;
|
|
270
|
-
|
|
271
|
-
return html`
|
|
272
|
-
<div
|
|
273
|
-
class="dropdown-panel ${isOpen ? "open" : ""}"
|
|
274
|
-
data-item-id="${item.itemId}"
|
|
275
|
-
id="${item.panelId}"
|
|
276
|
-
role="menu"
|
|
277
|
-
@click="${(e: Event) => e.stopPropagation()}"
|
|
278
|
-
>
|
|
279
|
-
${this.getItemContent(item)}
|
|
280
|
-
</div>
|
|
281
|
-
`;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
private getItemContent(item: RelNavMenuItem) {
|
|
285
|
-
const slot = item.shadowRoot?.querySelector("slot");
|
|
286
|
-
if (!slot) return null;
|
|
287
|
-
|
|
288
|
-
const nodes = slot.assignedNodes({ flatten: true });
|
|
289
|
-
const elements = nodes.filter((node) => node.nodeType === Node.ELEMENT_NODE) as Element[];
|
|
290
|
-
|
|
291
|
-
// Clone and return the content
|
|
292
|
-
return elements.map((el) => {
|
|
293
|
-
const clone = el.cloneNode(true);
|
|
294
|
-
return clone;
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
render() {
|
|
299
|
-
return html`
|
|
300
|
-
<nav
|
|
301
|
-
class="nav-container ${this.density === "compact" ? "compact" : ""}"
|
|
302
|
-
role="navigation"
|
|
303
|
-
@keydown="${this.handleKeyDown}"
|
|
304
|
-
>
|
|
305
|
-
<slot @slotchange="${this.handleSlotChange}"></slot>
|
|
306
|
-
</nav>
|
|
307
|
-
|
|
308
|
-
${this.items.map((item) => {
|
|
309
|
-
// Set up click and hover handlers by attaching to the trigger
|
|
310
|
-
this.updateComplete.then(() => {
|
|
311
|
-
const trigger = item.shadowRoot?.querySelector(".trigger") as HTMLElement;
|
|
312
|
-
if (trigger) {
|
|
313
|
-
// Remove old listeners if any
|
|
314
|
-
trigger.onclick = (e) => this.handleItemClick(item, e);
|
|
315
|
-
trigger.onmouseenter = () => this.handleItemMouseEnter(item);
|
|
316
|
-
|
|
317
|
-
// Update active state
|
|
318
|
-
trigger.classList.toggle("active", this.open === item.itemId);
|
|
319
|
-
|
|
320
|
-
// Update ARIA attributes
|
|
321
|
-
if (this.hasItemContent(item)) {
|
|
322
|
-
trigger.setAttribute("aria-expanded", this.open === item.itemId ? "true" : "false");
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
return this.renderDropdownPanel(item);
|
|
328
|
-
})}
|
|
329
|
-
`;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
1
|
+
import { LitElement, html, css } from "lit";
|
|
2
|
+
import { customElement, property, state, query } from "lit/decorators.js";
|
|
3
|
+
import { RelNavMenuItem } from "./rel-nav-menu-item";
|
|
4
|
+
|
|
5
|
+
type DensityType = "comfortable" | "compact";
|
|
6
|
+
|
|
7
|
+
@customElement("rel-nav-menu")
|
|
8
|
+
export class RelNavMenu extends LitElement {
|
|
9
|
+
@property({ type: String, reflect: true }) open = "";
|
|
10
|
+
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
11
|
+
@property({ type: String }) density: DensityType = "comfortable";
|
|
12
|
+
|
|
13
|
+
@state() private items: RelNavMenuItem[] = [];
|
|
14
|
+
@state() private focusedIndex = 0;
|
|
15
|
+
@state() private hoverSwitchEnabled = false;
|
|
16
|
+
|
|
17
|
+
@query(".nav-container") private container!: HTMLElement;
|
|
18
|
+
|
|
19
|
+
static styles = css`
|
|
20
|
+
:host {
|
|
21
|
+
display: block;
|
|
22
|
+
font-family: var(--rel-fontFamilies-primary);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.nav-container {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: var(--rel-spacing-space-3xs);
|
|
29
|
+
padding: var(--rel-spacing-space-2xs);
|
|
30
|
+
background: var(--rel-colors-gray-light);
|
|
31
|
+
border-radius: var(--rel-borders-radius-border-radius-200);
|
|
32
|
+
border: var(--rel-borders-width-stroke-100) solid var(--rel-colors-gray-border);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.nav-container.compact {
|
|
36
|
+
padding: var(--rel-spacing-space-3xs);
|
|
37
|
+
gap: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:host([disabled]) .nav-container {
|
|
41
|
+
opacity: 0.5;
|
|
42
|
+
pointer-events: none;
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
connectedCallback() {
|
|
49
|
+
super.connectedCallback();
|
|
50
|
+
this.discoverItems();
|
|
51
|
+
document.addEventListener("click", this.handleOutsideClick);
|
|
52
|
+
// Delegate item interactions via composed events — no shadow DOM mutation.
|
|
53
|
+
this.addEventListener("rel-nav-item-click", this.handleNavItemClick as EventListener);
|
|
54
|
+
this.addEventListener("rel-nav-item-hover", this.handleNavItemHover as EventListener);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
disconnectedCallback() {
|
|
58
|
+
super.disconnectedCallback();
|
|
59
|
+
document.removeEventListener("click", this.handleOutsideClick);
|
|
60
|
+
this.removeEventListener("rel-nav-item-click", this.handleNavItemClick as EventListener);
|
|
61
|
+
this.removeEventListener("rel-nav-item-hover", this.handleNavItemHover as EventListener);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Item discovery ─────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
private discoverItems() {
|
|
67
|
+
const slot = this.shadowRoot?.querySelector("slot");
|
|
68
|
+
if (slot) {
|
|
69
|
+
const nodes = slot.assignedElements() as RelNavMenuItem[];
|
|
70
|
+
this.items = nodes.filter((el) => el.tagName === "REL-NAV-MENU-ITEM");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private handleSlotChange() {
|
|
75
|
+
this.discoverItems();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Outside-click dismissal ────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
private handleOutsideClick = (e: MouseEvent) => {
|
|
81
|
+
const path = e.composedPath() as HTMLElement[];
|
|
82
|
+
const isInside = path.some(
|
|
83
|
+
(el) => el === this || el.tagName === "REL-NAV-MENU-ITEM"
|
|
84
|
+
);
|
|
85
|
+
if (!isInside && this.open) {
|
|
86
|
+
this.closePanel();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// ── Delegated item event handlers ──────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
private handleNavItemClick = (e: Event) => {
|
|
93
|
+
const { itemId, hasContent } = (e as CustomEvent).detail;
|
|
94
|
+
const item = this.items.find((i) => i.itemId === itemId);
|
|
95
|
+
if (!item || this.disabled || item.disabled) return;
|
|
96
|
+
// Plain link with no panel content — let the browser navigate.
|
|
97
|
+
if (item.href && !hasContent) return;
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
if (hasContent) this.openPanel(itemId);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
private handleNavItemHover = (e: Event) => {
|
|
103
|
+
const { itemId } = (e as CustomEvent).detail;
|
|
104
|
+
const item = this.items.find((i) => i.itemId === itemId);
|
|
105
|
+
if (!item || this.disabled || item.disabled) return;
|
|
106
|
+
if (this.open && this.hoverSwitchEnabled && item.hasContent) {
|
|
107
|
+
this.openPanel(itemId);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// ── Panel open/close ───────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
private openPanel(itemId: string) {
|
|
114
|
+
if (this.disabled) return;
|
|
115
|
+
|
|
116
|
+
// Close the currently open item before switching.
|
|
117
|
+
if (this.open && this.open !== itemId) {
|
|
118
|
+
const prev = this.items.find((i) => i.itemId === this.open);
|
|
119
|
+
if (prev) prev.open = false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const wasOpen = this.open === itemId;
|
|
123
|
+
this.open = wasOpen ? "" : itemId;
|
|
124
|
+
|
|
125
|
+
const item = this.items.find((i) => i.itemId === itemId);
|
|
126
|
+
if (item) item.open = !wasOpen;
|
|
127
|
+
|
|
128
|
+
if (!wasOpen) {
|
|
129
|
+
this.hoverSwitchEnabled = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.dispatchEvent(
|
|
133
|
+
new CustomEvent("rel-nav-open-change", {
|
|
134
|
+
detail: { openId: this.open },
|
|
135
|
+
bubbles: true,
|
|
136
|
+
composed: true,
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private closePanel() {
|
|
142
|
+
if (this.open) {
|
|
143
|
+
const openItem = this.items.find((i) => i.itemId === this.open);
|
|
144
|
+
if (openItem) openItem.open = false;
|
|
145
|
+
this.open = "";
|
|
146
|
+
this.hoverSwitchEnabled = false;
|
|
147
|
+
this.dispatchEvent(
|
|
148
|
+
new CustomEvent("rel-nav-open-change", {
|
|
149
|
+
detail: { openId: "" },
|
|
150
|
+
bubbles: true,
|
|
151
|
+
composed: true,
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Keyboard navigation ────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
private handleKeyDown(event: KeyboardEvent) {
|
|
160
|
+
if (this.disabled) return;
|
|
161
|
+
|
|
162
|
+
const { key } = event;
|
|
163
|
+
const focusableItems = this.items.filter((item) => !item.disabled);
|
|
164
|
+
|
|
165
|
+
if (key === "ArrowRight") {
|
|
166
|
+
event.preventDefault();
|
|
167
|
+
this.focusedIndex = Math.min(focusableItems.length - 1, this.focusedIndex + 1);
|
|
168
|
+
this.focusItem(this.focusedIndex);
|
|
169
|
+
} else if (key === "ArrowLeft") {
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
this.focusedIndex = Math.max(0, this.focusedIndex - 1);
|
|
172
|
+
this.focusItem(this.focusedIndex);
|
|
173
|
+
} else if (key === "Enter" || key === " ") {
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
const item = focusableItems[this.focusedIndex];
|
|
176
|
+
if (item && item.hasContent) {
|
|
177
|
+
this.openPanel(item.itemId);
|
|
178
|
+
}
|
|
179
|
+
} else if (key === "Escape") {
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
if (this.open) {
|
|
182
|
+
// Capture before closePanel() clears this.open.
|
|
183
|
+
const openItemId = this.open;
|
|
184
|
+
this.closePanel();
|
|
185
|
+
const openItem = this.items.find((i) => i.itemId === openItemId);
|
|
186
|
+
if (openItem) {
|
|
187
|
+
const trigger = openItem.shadowRoot?.querySelector(".trigger") as HTMLElement;
|
|
188
|
+
trigger?.focus();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} else if (key === "ArrowDown" && this.open) {
|
|
192
|
+
event.preventDefault();
|
|
193
|
+
// Move focus into the open panel (now inside the item's shadow root).
|
|
194
|
+
const openItem = this.items.find((i) => i.itemId === this.open);
|
|
195
|
+
if (openItem) {
|
|
196
|
+
const panel = openItem.shadowRoot?.querySelector(".panel") as HTMLElement;
|
|
197
|
+
if (panel) {
|
|
198
|
+
const firstFocusable = panel.querySelector("a, button") as HTMLElement;
|
|
199
|
+
firstFocusable?.focus();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private focusItem(index: number) {
|
|
206
|
+
const focusableItems = this.items.filter((item) => !item.disabled);
|
|
207
|
+
const item = focusableItems[index];
|
|
208
|
+
if (item) {
|
|
209
|
+
const trigger = item.shadowRoot?.querySelector(".trigger") as HTMLElement;
|
|
210
|
+
trigger?.focus();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ── Render ─────────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
render() {
|
|
217
|
+
return html`
|
|
218
|
+
<nav
|
|
219
|
+
class="nav-container ${this.density === "compact" ? "compact" : ""}"
|
|
220
|
+
role="navigation"
|
|
221
|
+
@keydown="${this.handleKeyDown}"
|
|
222
|
+
>
|
|
223
|
+
<slot @slotchange="${this.handleSlotChange}"></slot>
|
|
224
|
+
</nav>
|
|
225
|
+
`;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { LitElement, html, css } from "lit";
|
|
2
2
|
import { customElement, property } from "lit/decorators.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated rel-navbar is deprecated and will be removed in a future release.
|
|
6
|
+
* Use rel-topbar and its sub-components instead:
|
|
7
|
+
* rel-topbar, rel-topbar-brand, rel-topbar-actions, rel-topbar-utilities,
|
|
8
|
+
* rel-topbar-item, rel-topbar-dropdown, rel-topbar-dropdown-item
|
|
9
|
+
*/
|
|
4
10
|
@customElement("rel-navbar")
|
|
5
11
|
export class RelNavbar extends LitElement {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
console.warn(
|
|
15
|
+
"[rel-navbar] rel-navbar is deprecated. Use rel-topbar instead."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
6
19
|
@property({ type: Boolean, reflect: true })
|
|
7
20
|
sticky = false;
|
|
8
21
|
|