@reliability-design/web 1.0.6 → 1.1.1
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 +4 -0
- package/dist/components/rel-alert/rel-alert.ts +83 -13
- package/dist/components/rel-avatar/rel-avatar.ts +3 -2
- package/dist/components/rel-badge/rel-badge.ts +6 -2
- package/dist/components/rel-button/rel-button.ts +64 -8
- package/dist/components/rel-calendar/rel-calendar.ts +3 -3
- package/dist/components/rel-carousel/rel-carousel.ts +10 -11
- package/dist/components/rel-checkbox/rel-checkbox.ts +33 -2
- package/dist/components/rel-chip/rel-chip.ts +63 -5
- package/dist/components/rel-divider/rel-divider.ts +9 -0
- package/dist/components/rel-drawer/rel-drawer-footer.ts +29 -0
- package/dist/components/rel-drawer/rel-drawer.ts +17 -26
- package/dist/components/rel-menu/rel-menu.ts +53 -21
- package/dist/components/rel-menubar/rel-menubar.ts +52 -23
- 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-pagination/rel-pagination.ts +11 -6
- package/dist/components/rel-popover/rel-popover.ts +16 -13
- package/dist/components/rel-radio/rel-radio.ts +18 -1
- package/dist/components/rel-resizable/rel-resizable-handle.ts +4 -8
- package/dist/components/rel-select/rel-select.ts +14 -0
- package/dist/components/rel-sheet/rel-sheet.ts +7 -6
- package/dist/components/rel-sidebar/rel-sidebar.ts +13 -5
- package/dist/components/rel-slider/rel-slider.ts +25 -2
- package/dist/components/rel-snackbar/rel-snackbar.ts +46 -16
- package/dist/components/rel-stepper/rel-stepper.ts +32 -21
- package/dist/components/rel-switch/rel-switch.ts +19 -6
- package/dist/components/rel-tab/rel-tab.ts +49 -15
- 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 +20 -20
- package/dist/components/rel-toast/rel-toast.ts +31 -10
- package/dist/components/rel-tooltip/rel-tooltip.ts +123 -80
- package/dist/components/rel-topbar/rel-topbar-actions.ts +23 -0
- package/dist/components/rel-topbar/rel-topbar-brand.ts +46 -0
- package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +33 -0
- package/dist/components/rel-topbar/rel-topbar-dropdown.ts +28 -0
- package/dist/components/rel-topbar/rel-topbar-item.ts +315 -0
- package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +23 -0
- package/dist/components/rel-topbar/rel-topbar-profile.ts +248 -0
- package/dist/components/rel-topbar/rel-topbar-utilities.ts +23 -0
- package/dist/components/rel-topbar/rel-topbar.ts +329 -0
- package/dist/index.d.mts +278 -289
- package/dist/index.d.ts +278 -289
- package/dist/index.js +4498 -5727
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4535 -5756
- package/dist/index.mjs.map +1 -1
- package/dist/styles/vars.css +346 -294
- package/dist/{vars-ITL2K3KO.css → vars-TBB4BMSD.css} +346 -294
- package/package.json +2 -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
|
|
|
@@ -9,7 +9,7 @@ type PageItem = { type: "page"; value: number } | { type: "ellipsis" } | { type:
|
|
|
9
9
|
export class RelPagination extends LitElement {
|
|
10
10
|
@property({ type: Number }) page?: number;
|
|
11
11
|
@property({ type: Number }) defaultPage = 1;
|
|
12
|
-
@property({ type: Number }) totalPages
|
|
12
|
+
@property({ type: Number }) totalPages = 0;
|
|
13
13
|
@property({ type: Number }) siblingCount = 1;
|
|
14
14
|
@property({ type: Number }) boundaryCount = 1;
|
|
15
15
|
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
@@ -87,8 +87,8 @@ export class RelPagination extends LitElement {
|
|
|
87
87
|
|
|
88
88
|
button[aria-current="page"],
|
|
89
89
|
a[aria-current="page"] {
|
|
90
|
-
background: var(--rel-
|
|
91
|
-
color: var(--rel-
|
|
90
|
+
background: var(--rel-color-bg-selected);
|
|
91
|
+
color: var(--rel-color-text-primary);
|
|
92
92
|
border-color: var(--rel-colors-gray-heading);
|
|
93
93
|
font-weight: var(--rel-fontWeights-semibold);
|
|
94
94
|
}
|
|
@@ -222,6 +222,11 @@ export class RelPagination extends LitElement {
|
|
|
222
222
|
);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
private getHref(page: number): string {
|
|
226
|
+
if (!this.hrefTemplate) return "#";
|
|
227
|
+
return this.hrefTemplate.replace(/\{page\}/, String(page));
|
|
228
|
+
}
|
|
229
|
+
|
|
225
230
|
private handleKeyDown(event: KeyboardEvent, callback: () => void) {
|
|
226
231
|
if (event.key === "Enter" || event.key === " ") {
|
|
227
232
|
event.preventDefault();
|
|
@@ -258,7 +263,7 @@ export class RelPagination extends LitElement {
|
|
|
258
263
|
const targetPage = current - 1;
|
|
259
264
|
|
|
260
265
|
if (useLinks) {
|
|
261
|
-
const href = disabled ? "#" : this.
|
|
266
|
+
const href = disabled ? "#" : this.getHref(targetPage);
|
|
262
267
|
return html`
|
|
263
268
|
<a
|
|
264
269
|
href="${href}"
|
|
@@ -295,7 +300,7 @@ export class RelPagination extends LitElement {
|
|
|
295
300
|
const targetPage = current + 1;
|
|
296
301
|
|
|
297
302
|
if (useLinks) {
|
|
298
|
-
const href = disabled ? "#" : this.
|
|
303
|
+
const href = disabled ? "#" : this.getHref(targetPage);
|
|
299
304
|
return html`
|
|
300
305
|
<a
|
|
301
306
|
href="${href}"
|
|
@@ -332,7 +337,7 @@ export class RelPagination extends LitElement {
|
|
|
332
337
|
const isCurrent = pageNum === current;
|
|
333
338
|
|
|
334
339
|
if (useLinks) {
|
|
335
|
-
const href = this.
|
|
340
|
+
const href = this.getHref(pageNum);
|
|
336
341
|
return html`
|
|
337
342
|
<a
|
|
338
343
|
href="${href}"
|