@reliability-design/web 1.3.1 → 1.4.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/CHANGELOG.md +22 -0
- package/dist/components/rel-accordion/README.md +13 -13
- package/dist/components/rel-accordion/rel-accordion.ts +80 -45
- package/dist/components/rel-alert/README.md +15 -15
- package/dist/components/rel-alert/rel-alert.ts +149 -158
- package/dist/components/rel-avatar/README.md +15 -15
- package/dist/components/rel-avatar/rel-avatar.ts +42 -38
- package/dist/components/rel-badge/README.md +5 -5
- package/dist/components/rel-badge/rel-badge.ts +46 -11
- package/dist/components/rel-breadcrumb/README.md +7 -7
- package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +16 -13
- package/dist/components/rel-button/README.md +20 -20
- package/dist/components/rel-button/rel-button.ts +292 -105
- package/dist/components/rel-calendar/README.md +5 -5
- package/dist/components/rel-calendar/rel-calendar.ts +68 -45
- package/dist/components/rel-card/README.md +6 -6
- package/dist/components/rel-card/rel-card.ts +88 -35
- package/dist/components/rel-cardmedia/README.md +9 -9
- package/dist/components/rel-cardmedia/rel-cardmedia.ts +27 -20
- package/dist/components/rel-carousel/README.md +3 -3
- package/dist/components/rel-carousel/rel-carousel.ts +174 -90
- package/dist/components/rel-chart/README.md +8 -8
- package/dist/components/rel-chart/rel-chart.ts +59 -41
- package/dist/components/rel-checkbox/README.md +7 -7
- package/dist/components/rel-checkbox/rel-checkbox.ts +61 -61
- package/dist/components/rel-chip/README.md +16 -16
- package/dist/components/rel-chip/rel-chip.ts +143 -111
- package/dist/components/rel-container/README.md +11 -11
- package/dist/components/rel-container/rel-container.ts +35 -9
- package/dist/components/rel-date-picker/README.md +43 -7
- package/dist/components/rel-date-picker/date-format.test.ts +43 -0
- package/dist/components/rel-date-picker/date-format.ts +47 -0
- package/dist/components/rel-date-picker/rel-date-picker.ts +154 -82
- package/dist/components/rel-divider/README.md +9 -9
- package/dist/components/rel-divider/rel-divider.ts +10 -9
- package/dist/components/rel-drawer/README.md +1 -1
- package/dist/components/rel-drawer/rel-drawer-footer.ts +1 -1
- package/dist/components/rel-drawer/rel-drawer.ts +88 -25
- package/dist/components/rel-kbd/README.md +20 -20
- package/dist/components/rel-kbd/rel-kbd.ts +119 -25
- package/dist/components/rel-layout/README.md +1 -1
- package/dist/components/rel-layout/rel-layout.ts +2 -2
- package/dist/components/rel-menu/README.md +15 -15
- package/dist/components/rel-menu/rel-menu.ts +155 -88
- package/dist/components/rel-menubar/README.md +20 -20
- package/dist/components/rel-menubar/rel-menubar.ts +47 -31
- package/dist/components/rel-nav-menu/README.md +21 -21
- package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +17 -14
- package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +33 -14
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +14 -7
- package/dist/components/rel-navbar/README.md +7 -7
- package/dist/components/rel-navbar/rel-navbar.ts +12 -11
- package/dist/components/rel-otp/README.md +3 -3
- package/dist/components/rel-otp/rel-otp.ts +46 -30
- package/dist/components/rel-pagination/README.md +18 -18
- package/dist/components/rel-pagination/rel-pagination.ts +141 -90
- package/dist/components/rel-popover/README.md +13 -13
- package/dist/components/rel-popover/rel-popover-content.ts +39 -14
- package/dist/components/rel-popover/rel-popover.ts +18 -7
- package/dist/components/rel-progress/README.md +5 -5
- package/dist/components/rel-progress/rel-progress.ts +16 -12
- package/dist/components/rel-radio/README.md +9 -9
- package/dist/components/rel-radio/rel-radio.ts +64 -50
- package/dist/components/rel-range-picker/rel-range-picker.ts +120 -117
- package/dist/components/rel-resizable/README.md +4 -4
- package/dist/components/rel-resizable/rel-resizable-group.ts +15 -4
- package/dist/components/rel-resizable/rel-resizable-handle.ts +13 -9
- package/dist/components/rel-resizable/rel-resizable-panel.ts +11 -0
- package/dist/components/rel-segmented-button/README.md +10 -10
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +39 -40
- package/dist/components/rel-select/README.md +14 -14
- package/dist/components/rel-select/rel-select.ts +120 -188
- package/dist/components/rel-sheet/README.md +10 -10
- package/dist/components/rel-sheet/rel-sheet.ts +82 -23
- package/dist/components/rel-sidebar/README.md +5 -5
- package/dist/components/rel-sidebar/rel-sidebar.ts +75 -60
- package/dist/components/rel-skeleton/rel-skeleton.ts +8 -13
- package/dist/components/rel-slider/README.md +10 -10
- package/dist/components/rel-slider/rel-slider.ts +230 -57
- package/dist/components/rel-snackbar/README.md +9 -9
- package/dist/components/rel-snackbar/rel-snackbar.ts +44 -32
- package/dist/components/rel-stepper/README.md +10 -10
- package/dist/components/rel-stepper/rel-stepper.ts +110 -46
- package/dist/components/rel-switch/README.md +5 -5
- package/dist/components/rel-switch/rel-switch.ts +23 -12
- package/dist/components/rel-tab/README.md +13 -13
- package/dist/components/rel-tab/rel-tab.ts +63 -19
- package/dist/components/rel-table/README.md +7 -7
- package/dist/components/rel-table/rel-table-body.ts +2 -1
- package/dist/components/rel-table/rel-table-caption.ts +10 -7
- package/dist/components/rel-table/rel-table-cell.ts +7 -6
- package/dist/components/rel-table/rel-table-footer.ts +4 -3
- package/dist/components/rel-table/rel-table-head.ts +11 -9
- package/dist/components/rel-table/rel-table-header.ts +2 -1
- package/dist/components/rel-table/rel-table-row.ts +5 -4
- package/dist/components/rel-table/rel-table.ts +16 -7
- package/dist/components/rel-textfield/README.md +11 -11
- package/dist/components/rel-textfield/rel-textfield.ts +218 -92
- package/dist/components/rel-toast/README.md +11 -11
- package/dist/components/rel-toast/rel-toast.ts +33 -30
- package/dist/components/rel-tooltip/README.md +7 -7
- package/dist/components/rel-tooltip/rel-tooltip.ts +160 -123
- package/dist/components/rel-topbar/rel-topbar-actions.ts +1 -2
- package/dist/components/rel-topbar/rel-topbar-brand.ts +8 -5
- package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +10 -8
- package/dist/components/rel-topbar/rel-topbar-dropdown.ts +4 -5
- package/dist/components/rel-topbar/rel-topbar-item.ts +23 -22
- package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +0 -1
- package/dist/components/rel-topbar/rel-topbar-profile.ts +68 -50
- package/dist/components/rel-topbar/rel-topbar-utilities.ts +1 -2
- package/dist/components/rel-topbar/rel-topbar.ts +31 -29
- package/dist/index.d.mts +159 -39
- package/dist/index.d.ts +159 -39
- package/dist/index.js +3600 -2269
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3930 -2600
- package/dist/index.mjs.map +1 -1
- package/dist/styles/vars.css +866 -363
- package/dist/vars-BYCEEKWJ.css +866 -0
- package/package.json +5 -3
- package/dist/components/rel-range-picker/rel-range-picker.css +0 -349
- package/dist/styles/vars-v2.css +0 -525
- package/dist/vars-F54UGCHM.css +0 -363
- package/dist/vars-v2-VMGWTFJZ.css +0 -525
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { LitElement, html } from "lit";
|
|
2
2
|
import { customElement, property, state } from "lit/decorators.js";
|
|
3
|
-
import "
|
|
3
|
+
import "@reliability-design/icons/rel-icon";
|
|
4
|
+
import "../rel-checkbox/rel-checkbox.js";
|
|
5
|
+
import "../rel-radio/rel-radio.js";
|
|
4
6
|
|
|
5
|
-
export type MenuType = "default" | "checkbox" | "arrow";
|
|
7
|
+
export type MenuType = "default" | "checkbox" | "arrow" | "select-option" | "fixed" | "fit";
|
|
6
8
|
|
|
7
9
|
export interface MenuItem {
|
|
8
10
|
label: string;
|
|
@@ -40,27 +42,36 @@ export class RelMenu extends LitElement {
|
|
|
40
42
|
|
|
41
43
|
public static readonly _css = `
|
|
42
44
|
:host {
|
|
45
|
+
color: var(--rel-text-default);
|
|
43
46
|
display: inline-block;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
.rel-menu {
|
|
47
50
|
position: relative;
|
|
48
51
|
display: inline-block;
|
|
49
|
-
background: var(--rel-
|
|
50
|
-
color: var(--rel-
|
|
51
|
-
border-radius: var(--rel-
|
|
52
|
+
background: var(--rel-bg-neutral);
|
|
53
|
+
color: var(--rel-text-default);
|
|
54
|
+
border-radius: var(--rel-border-radius-md);
|
|
52
55
|
box-shadow: var(--rel-shadows-shadow-300);
|
|
53
56
|
min-width: 220px;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
/* No vertical padding: items fill edge-to-edge so a first/last-item hover
|
|
58
|
+
reaches the menu's top/bottom. NOT overflow:hidden — that clips the
|
|
59
|
+
type=arrow flyout (position:absolute at left:100%). Instead the first
|
|
60
|
+
and last items carry the outer corner radius so hover backgrounds clip
|
|
61
|
+
to the rounded shape without any overflow. */
|
|
62
|
+
padding: 0;
|
|
57
63
|
outline: none;
|
|
58
|
-
overflow:
|
|
64
|
+
overflow: visible;
|
|
65
|
+
font-family: var(--rel-body-lg-regular-font-family);
|
|
66
|
+
font-weight: var(--rel-body-lg-regular-font-weight);
|
|
67
|
+
font-size: var(--rel-body-lg-regular-font-size);
|
|
68
|
+
line-height: var(--rel-body-lg-regular-line-height);
|
|
69
|
+
letter-spacing: var(--rel-body-lg-regular-letter-spacing);
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
.rel-menu:focus-visible,
|
|
62
73
|
:host(:focus-visible) .rel-menu {
|
|
63
|
-
outline: 2px solid var(--rel-
|
|
74
|
+
outline: 2px solid var(--rel-border-focus);
|
|
64
75
|
outline-offset: 2px;
|
|
65
76
|
}
|
|
66
77
|
|
|
@@ -68,75 +79,117 @@ export class RelMenu extends LitElement {
|
|
|
68
79
|
position: relative;
|
|
69
80
|
display: flex;
|
|
70
81
|
align-items: center;
|
|
71
|
-
gap: var(--rel-spacing-
|
|
72
|
-
padding: var(--rel-spacing-
|
|
73
|
-
line-height: var(--rel-
|
|
82
|
+
gap: var(--rel-spacing-xs);
|
|
83
|
+
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
84
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
74
85
|
cursor: pointer;
|
|
75
86
|
transition: background 0.15s ease, color 0.15s ease;
|
|
76
87
|
user-select: none;
|
|
77
88
|
}
|
|
78
89
|
|
|
90
|
+
/* First/last items carry the menu's outer radius so their hover fill clips
|
|
91
|
+
to the rounded corners (replaces the removed overflow:hidden). */
|
|
92
|
+
.rel-menu > .rel-menu-item:first-child {
|
|
93
|
+
border-top-left-radius: var(--rel-border-radius-md);
|
|
94
|
+
border-top-right-radius: var(--rel-border-radius-md);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.rel-menu > .rel-menu-item:last-child {
|
|
98
|
+
border-bottom-left-radius: var(--rel-border-radius-md);
|
|
99
|
+
border-bottom-right-radius: var(--rel-border-radius-md);
|
|
100
|
+
}
|
|
101
|
+
|
|
79
102
|
.rel-menu-item:hover {
|
|
80
|
-
background: var(--rel-
|
|
103
|
+
background: var(--rel-bg-neutral-elevated-1);
|
|
81
104
|
}
|
|
82
105
|
|
|
83
106
|
.rel-menu-item:active {
|
|
84
|
-
background: var(--rel-
|
|
107
|
+
background: var(--rel-bg-neutral-elevated-1);
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
.rel-menu-item.active {
|
|
88
|
-
background: var(--rel-
|
|
89
|
-
font-weight: var(--rel-
|
|
111
|
+
background: var(--rel-bg-neutral-elevated-2);
|
|
112
|
+
font-weight: var(--rel-body-lg-medium-font-weight);
|
|
90
113
|
}
|
|
91
114
|
|
|
92
115
|
.rel-menu-item.disabled {
|
|
93
116
|
background-color: transparent;
|
|
94
|
-
color: var(--rel-
|
|
117
|
+
color: var(--rel-text-disabled);
|
|
118
|
+
/* Keep the item pointer-interactive so cursor:not-allowed actually
|
|
119
|
+
renders; clicks are already neutralised by the item.disabled guard in
|
|
120
|
+
onItemClick/onItemEnter. pointer-events:none here would make the row
|
|
121
|
+
transparent to the pointer and suppress the cursor. */
|
|
95
122
|
cursor: not-allowed;
|
|
96
123
|
}
|
|
97
124
|
|
|
98
125
|
.rel-menu-item[data-focused="true"] {
|
|
99
|
-
outline: 2px solid var(--rel-
|
|
126
|
+
outline: 2px solid var(--rel-border-focus);
|
|
100
127
|
outline-offset: -2px;
|
|
101
128
|
}
|
|
102
129
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
130
|
+
/* Width modes (Figma Type=Fixed / Type=Fit) */
|
|
131
|
+
.rel-menu.fixed {
|
|
132
|
+
width: var(--rel-menu-width, 300px);
|
|
133
|
+
min-width: 0;
|
|
134
|
+
box-sizing: border-box;
|
|
108
135
|
}
|
|
109
136
|
|
|
110
|
-
.menu
|
|
111
|
-
width:
|
|
112
|
-
|
|
113
|
-
cursor: pointer;
|
|
137
|
+
.rel-menu.fit {
|
|
138
|
+
min-width: 0;
|
|
139
|
+
width: max-content;
|
|
114
140
|
}
|
|
115
141
|
|
|
116
|
-
|
|
117
|
-
|
|
142
|
+
/* Checkbox (Type=Checkbox) / radio (Type=Select Option) rows render the real
|
|
143
|
+
rel-checkbox / rel-radio. pointer-events:none keeps the whole row the click
|
|
144
|
+
target so onItemClick stays the single source of truth. */
|
|
145
|
+
.menu-control {
|
|
146
|
+
pointer-events: none;
|
|
147
|
+
width: 100%;
|
|
118
148
|
}
|
|
119
149
|
|
|
120
150
|
.menu-label {
|
|
121
151
|
flex: 1 1 auto;
|
|
122
152
|
}
|
|
123
153
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
154
|
+
/* Submenu affordance — same rel-icon chevron the menubar uses. */
|
|
155
|
+
.submenu-chevron {
|
|
156
|
+
margin-left: auto;
|
|
157
|
+
display: inline-flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
opacity: 0.6;
|
|
160
|
+
pointer-events: none;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Shortcut / hint text (Figma body-sm-regular, caption tone) */
|
|
164
|
+
.menu-shortcut,
|
|
165
|
+
::slotted([slot="shortcut"]) {
|
|
166
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
167
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
168
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
169
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
170
|
+
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
171
|
+
color: var(--rel-text-caption);
|
|
172
|
+
margin-left: auto;
|
|
173
|
+
padding-left: var(--rel-spacing-md);
|
|
128
174
|
}
|
|
129
175
|
|
|
176
|
+
|
|
130
177
|
.rel-submenu {
|
|
178
|
+
border: 1px solid var(--rel-border-subtle);
|
|
131
179
|
position: absolute;
|
|
132
|
-
top: calc(-1 * var(--rel-spacing-
|
|
133
|
-
|
|
180
|
+
top: calc(-1 * var(--rel-spacing-3xs));
|
|
181
|
+
/* flush against the trigger (no gap) so moving the pointer into the flyout
|
|
182
|
+
never crosses a dead zone that would fire the trigger's mouseleave */
|
|
183
|
+
left: 100%;
|
|
134
184
|
min-width: 200px;
|
|
135
|
-
background: var(--rel-
|
|
136
|
-
color: var(--rel-
|
|
137
|
-
border-radius: var(--rel-
|
|
185
|
+
background: var(--rel-bg-neutral);
|
|
186
|
+
color: var(--rel-text-default);
|
|
187
|
+
border-radius: var(--rel-border-radius-md);
|
|
138
188
|
box-shadow: var(--rel-shadows-shadow-300);
|
|
139
|
-
padding:
|
|
189
|
+
/* padding:0 + overflow:hidden — safe here (a submenu has no further nested
|
|
190
|
+
flyout to clip) — so sub-item hovers fill fully and clip to the corners */
|
|
191
|
+
padding: 0;
|
|
192
|
+
overflow: hidden;
|
|
140
193
|
visibility: hidden;
|
|
141
194
|
pointer-events: none;
|
|
142
195
|
z-index: 20;
|
|
@@ -148,7 +201,7 @@ export class RelMenu extends LitElement {
|
|
|
148
201
|
}
|
|
149
202
|
|
|
150
203
|
.rel-submenu .rel-menu-item {
|
|
151
|
-
padding: var(--rel-spacing-
|
|
204
|
+
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
152
205
|
}
|
|
153
206
|
`;
|
|
154
207
|
|
|
@@ -172,20 +225,35 @@ export class RelMenu extends LitElement {
|
|
|
172
225
|
}
|
|
173
226
|
}
|
|
174
227
|
|
|
228
|
+
/** Index of a submenu locked open by a click (survives hover-out). -1 = none. */
|
|
229
|
+
private lockedIndex = -1;
|
|
230
|
+
|
|
175
231
|
private handleDocumentClick = (e: MouseEvent) => {
|
|
176
|
-
|
|
232
|
+
// composedPath (NOT e.target): document-level e.target is retargeted to the
|
|
233
|
+
// shadow host when this menu lives inside a shadow root, so this.contains()
|
|
234
|
+
// wrongly reads an inside-click as outside and closes an open submenu on the
|
|
235
|
+
// click that opened it. composedPath pierces shadow boundaries.
|
|
236
|
+
if (!e.composedPath().includes(this)) {
|
|
237
|
+
this.lockedIndex = -1;
|
|
177
238
|
this._items = this._items.map(i => ({ ...i, open: false }));
|
|
178
239
|
}
|
|
179
240
|
};
|
|
180
241
|
|
|
181
242
|
private onItemEnter(item: MenuItem, index: number) {
|
|
243
|
+
if (item.disabled) return;
|
|
182
244
|
if (this.type === "arrow" && item.submenu?.length) {
|
|
183
|
-
|
|
245
|
+
// Hover opens this submenu; a click-locked one also stays open.
|
|
246
|
+
this._items = this._items.map((i, idx) => ({ ...i, open: idx === index || idx === this.lockedIndex }));
|
|
184
247
|
}
|
|
185
248
|
}
|
|
186
249
|
|
|
187
|
-
private onItemLeave(_item: MenuItem) {
|
|
188
|
-
|
|
250
|
+
private onItemLeave(_item: MenuItem, index: number) {
|
|
251
|
+
if (this.type !== "arrow") return;
|
|
252
|
+
// Hover-out closes the submenu UNLESS it was locked open by a click. Moving
|
|
253
|
+
// into the flyout doesn't fire this (the flyout is a DOM descendant of the
|
|
254
|
+
// item and sits flush at left:100%, so there's no dead-zone gap).
|
|
255
|
+
if (this.lockedIndex === index) return;
|
|
256
|
+
this._items = this._items.map((i, idx) => ({ ...i, open: idx === this.lockedIndex }));
|
|
189
257
|
}
|
|
190
258
|
|
|
191
259
|
private onItemClick(item: MenuItem, index: number, ev?: Event) {
|
|
@@ -208,11 +276,10 @@ export class RelMenu extends LitElement {
|
|
|
208
276
|
}
|
|
209
277
|
|
|
210
278
|
if (this.type === "arrow" && item.submenu?.length) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}));
|
|
279
|
+
// Click LOCKS the submenu open (click again to unlock/close).
|
|
280
|
+
const lock = this.lockedIndex !== index;
|
|
281
|
+
this.lockedIndex = lock ? index : -1;
|
|
282
|
+
this._items = this._items.map((i, idx) => ({ ...i, open: idx === index ? lock : false }));
|
|
216
283
|
return;
|
|
217
284
|
}
|
|
218
285
|
|
|
@@ -230,6 +297,7 @@ export class RelMenu extends LitElement {
|
|
|
230
297
|
|
|
231
298
|
private onSubClick(parentIndex: number, subIndex: number, ev?: Event) {
|
|
232
299
|
ev?.stopPropagation();
|
|
300
|
+
this.lockedIndex = -1;
|
|
233
301
|
this._items = this._items.map((i, idx) => ({
|
|
234
302
|
...i,
|
|
235
303
|
active: false,
|
|
@@ -251,21 +319,6 @@ export class RelMenu extends LitElement {
|
|
|
251
319
|
);
|
|
252
320
|
}
|
|
253
321
|
|
|
254
|
-
private toggleCheckbox(item: MenuItem, ev: Event) {
|
|
255
|
-
ev.stopPropagation();
|
|
256
|
-
if (!item.disabled) {
|
|
257
|
-
item.checked = !item.checked;
|
|
258
|
-
this.dispatchEvent(
|
|
259
|
-
new CustomEvent("rel-check", {
|
|
260
|
-
detail: { item, checked: item.checked },
|
|
261
|
-
bubbles: true,
|
|
262
|
-
composed: true,
|
|
263
|
-
})
|
|
264
|
-
);
|
|
265
|
-
this.requestUpdate();
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
322
|
private onKeydown(ev: KeyboardEvent) {
|
|
270
323
|
const last = this._items.length - 1;
|
|
271
324
|
|
|
@@ -278,12 +331,15 @@ export class RelMenu extends LitElement {
|
|
|
278
331
|
} else if (ev.key === "ArrowRight") {
|
|
279
332
|
const item = this._items[this.focusedIndex];
|
|
280
333
|
if (this.type === "arrow" && item?.submenu?.length) {
|
|
334
|
+
// keyboard-open locks (no hover-out to close it)
|
|
335
|
+
this.lockedIndex = this.focusedIndex;
|
|
281
336
|
this._items = this._items.map((i, idx) => ({
|
|
282
337
|
...i,
|
|
283
338
|
open: idx === this.focusedIndex ? true : i.open,
|
|
284
339
|
}));
|
|
285
340
|
}
|
|
286
341
|
} else if (ev.key === "ArrowLeft" || ev.key === "Escape") {
|
|
342
|
+
this.lockedIndex = -1;
|
|
287
343
|
this._items = this._items.map(i => ({ ...i, open: false }));
|
|
288
344
|
} else if (ev.key === "Enter" || ev.key === " ") {
|
|
289
345
|
const item = this._items[this.focusedIndex];
|
|
@@ -294,6 +350,34 @@ export class RelMenu extends LitElement {
|
|
|
294
350
|
|
|
295
351
|
private renderMenuItem(item: MenuItem, index: number) {
|
|
296
352
|
const isFocused = this.focusedIndex === index;
|
|
353
|
+
const hasSubmenu = this.type === "arrow" && !!item.submenu?.length;
|
|
354
|
+
|
|
355
|
+
// Checkbox / select-option rows delegate their control to the real
|
|
356
|
+
// rel-checkbox / rel-radio components (pointer-events:none so the whole ROW
|
|
357
|
+
// stays the click target — onItemClick is the single source of truth and
|
|
358
|
+
// the control just reflects item state).
|
|
359
|
+
const control =
|
|
360
|
+
this.type === "checkbox"
|
|
361
|
+
? html`<rel-checkbox
|
|
362
|
+
class="menu-control"
|
|
363
|
+
label="${item.label}"
|
|
364
|
+
status="${item.checked ? "checked" : "unchecked"}"
|
|
365
|
+
state="${item.disabled ? "disabled" : "default"}"
|
|
366
|
+
></rel-checkbox>`
|
|
367
|
+
: this.type === "select-option"
|
|
368
|
+
? html`<rel-radio
|
|
369
|
+
class="menu-control"
|
|
370
|
+
.options="${[{ value: item.label, label: item.label, disabled: !!item.disabled }]}"
|
|
371
|
+
.selectedValue="${item.active ? item.label : null}"
|
|
372
|
+
></rel-radio>`
|
|
373
|
+
: html`
|
|
374
|
+
<span class="menu-label">${item.label}</span>
|
|
375
|
+
${hasSubmenu
|
|
376
|
+
? html`<span class="submenu-chevron"
|
|
377
|
+
><rel-icon name="chevron-right" size="sm"></rel-icon
|
|
378
|
+
></span>`
|
|
379
|
+
: ""}
|
|
380
|
+
`;
|
|
297
381
|
|
|
298
382
|
return html`
|
|
299
383
|
<div
|
|
@@ -301,32 +385,15 @@ export class RelMenu extends LitElement {
|
|
|
301
385
|
role="menuitem"
|
|
302
386
|
?data-focused="${isFocused}"
|
|
303
387
|
@mouseenter="${() => this.onItemEnter(item, index)}"
|
|
304
|
-
@mouseleave="${() => this.onItemLeave(item)}"
|
|
388
|
+
@mouseleave="${() => this.onItemLeave(item, index)}"
|
|
305
389
|
@click="${(e: Event) => this.onItemClick(item, index, e)}">
|
|
306
|
-
${
|
|
307
|
-
|
|
308
|
-
<div class="menu-check-row">
|
|
309
|
-
<input
|
|
310
|
-
class="menu-checkbox"
|
|
311
|
-
type="checkbox"
|
|
312
|
-
.checked="${item.checked || false}"
|
|
313
|
-
?disabled="${item.disabled}"
|
|
314
|
-
@click="${(e: Event) => this.toggleCheckbox(item, e)}" />
|
|
315
|
-
<span class="menu-label">${item.label}</span>
|
|
316
|
-
</div>
|
|
317
|
-
`
|
|
318
|
-
: html`
|
|
319
|
-
<span class="menu-label">${item.label}</span>
|
|
320
|
-
${this.type === "arrow" && item.submenu?.length
|
|
321
|
-
? html`<span class="chevron">▸</span>`
|
|
322
|
-
: ""}
|
|
323
|
-
`}
|
|
324
|
-
${this.type === "arrow" && item.submenu?.length
|
|
390
|
+
${control}
|
|
391
|
+
${hasSubmenu
|
|
325
392
|
? html`
|
|
326
393
|
<div
|
|
327
|
-
class="rel-submenu ${item.open ? "visible" : ""}"
|
|
394
|
+
part="panel" class="rel-submenu ${item.open ? "visible" : ""}"
|
|
328
395
|
@click="${(e: Event) => e.stopPropagation()}">
|
|
329
|
-
${item.submenu
|
|
396
|
+
${item.submenu!.map(
|
|
330
397
|
(sub, subIndex) => html`
|
|
331
398
|
<div
|
|
332
399
|
class="rel-menu-item ${sub.active ? "active" : ""}"
|
|
@@ -347,7 +414,7 @@ export class RelMenu extends LitElement {
|
|
|
347
414
|
return html`
|
|
348
415
|
<style>${RelMenu._css}</style>
|
|
349
416
|
<div
|
|
350
|
-
class="rel-menu"
|
|
417
|
+
part="list" class="rel-menu ${this.type === "fixed" || this.type === "fit" ? this.type : ""}"
|
|
351
418
|
role="menu"
|
|
352
419
|
tabindex="0"
|
|
353
420
|
@keydown="${this.onKeydown}"
|
|
@@ -224,32 +224,32 @@ The menubar provides comprehensive keyboard support:
|
|
|
224
224
|
The component uses design tokens from the Reliability Design System:
|
|
225
225
|
|
|
226
226
|
### Colors
|
|
227
|
-
- `--rel-
|
|
228
|
-
- `--rel-
|
|
229
|
-
- `--rel-
|
|
230
|
-
- `--rel-
|
|
231
|
-
- `--rel-
|
|
232
|
-
- `--rel-
|
|
233
|
-
- `--rel-
|
|
227
|
+
- `--rel-bg-surface-elevated` - Menubar background
|
|
228
|
+
- `--rel-bg-neutral-elevated-1` - Trigger and item hover state
|
|
229
|
+
- `--rel-border-default` - Borders
|
|
230
|
+
- `--rel-border-default` - Separators
|
|
231
|
+
- `--rel-text-label` - Text color
|
|
232
|
+
- `--rel-bg-surface` - Panel background
|
|
233
|
+
- `--rel-bg-info` - Focus outline and radio selected state
|
|
234
234
|
|
|
235
235
|
### Typography
|
|
236
|
-
- `--rel-
|
|
237
|
-
- `--rel-
|
|
238
|
-
- `--rel-
|
|
239
|
-
- `--rel-
|
|
236
|
+
- `--rel-fonts-font-family` - Font family (Poppins)
|
|
237
|
+
- `--rel-body-sm-regular-font-size` - Default text size
|
|
238
|
+
- `--rel-body-xs-regular-font-size` - Shortcut text size
|
|
239
|
+
- `--rel-body-sm-regular-line-height` - Line height
|
|
240
240
|
|
|
241
241
|
### Spacing
|
|
242
|
-
- `--rel-spacing-
|
|
243
|
-
- `--rel-spacing-
|
|
244
|
-
- `--rel-spacing-
|
|
245
|
-
- `--rel-spacing-
|
|
246
|
-
- `--rel-spacing-
|
|
242
|
+
- `--rel-spacing-3xs` (2px) - Menubar gap and panel padding
|
|
243
|
+
- `--rel-spacing-2xs` (4px) - Trigger padding vertical
|
|
244
|
+
- `--rel-spacing-xs` (8px) - Item padding vertical
|
|
245
|
+
- `--rel-spacing-sm` (12px) - Trigger and item horizontal padding
|
|
246
|
+
- `--rel-spacing-md` (16px) - Item padding and radio offset
|
|
247
247
|
|
|
248
248
|
### Borders
|
|
249
|
-
- `--rel-
|
|
250
|
-
- `--rel-
|
|
251
|
-
- `--rel-
|
|
252
|
-
- `--rel-
|
|
249
|
+
- `--rel-stroke-sm` (1px) - Border width
|
|
250
|
+
- `--rel-stroke-md` (1.5px) - Radio border
|
|
251
|
+
- `--rel-border-radius-xs` (2px) - Trigger border radius
|
|
252
|
+
- `--rel-border-radius-sm` (8px) - Panel border radius
|
|
253
253
|
|
|
254
254
|
### Shadows
|
|
255
255
|
- `--rel-shadows-shadow-300` - Panel drop shadow
|
|
@@ -4,7 +4,6 @@ import { RelMenubarMenu } from "./rel-menubar-menu";
|
|
|
4
4
|
import "@reliability-design/icons/rel-icon";
|
|
5
5
|
import "../rel-checkbox/rel-checkbox.js";
|
|
6
6
|
import "../rel-radio/rel-radio.js";
|
|
7
|
-
import "../../styles/vars-v2.css";
|
|
8
7
|
|
|
9
8
|
interface MenuState {
|
|
10
9
|
menu: RelMenubarMenu;
|
|
@@ -24,27 +23,36 @@ export class RelMenubar extends LitElement {
|
|
|
24
23
|
private _onScroll?: () => void;
|
|
25
24
|
|
|
26
25
|
public static readonly _css = `
|
|
27
|
-
|
|
26
|
+
/* This component renders into LIGHT DOM (createRenderRoot returns this), so
|
|
27
|
+
:host / <slot> don't work here — the raw <rel-menubar-menu> definitions
|
|
28
|
+
are data holders that must be hidden with a plain selector. Relying on the
|
|
29
|
+
global rel-menubar-menu.css is not enough: it can't cross a consumer's
|
|
30
|
+
shadow boundary AND it's a no-op in the tsup-built package (css:"copy"). */
|
|
31
|
+
rel-menubar {
|
|
32
|
+
color: var(--rel-text-default);
|
|
28
33
|
display: block;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
rel-menubar-menu {
|
|
32
37
|
display: none;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
.menubar-container {
|
|
36
41
|
display: flex;
|
|
37
42
|
align-items: stretch;
|
|
38
|
-
background: var(--rel-
|
|
39
|
-
border: var(--rel-
|
|
40
|
-
border-radius: var(--rel-
|
|
43
|
+
background: var(--rel-bg-surface-elevated);
|
|
44
|
+
border: var(--rel-stroke-sm) solid var(--rel-border-default);
|
|
45
|
+
border-radius: var(--rel-border-radius-sm);
|
|
41
46
|
overflow: hidden;
|
|
42
47
|
padding: 0;
|
|
43
|
-
gap: var(--rel-spacing-
|
|
44
|
-
font-family: var(--rel-fontFamilies-primary);
|
|
45
|
-
font-size: var(--rel-fontSizes-sm);
|
|
48
|
+
gap: var(--rel-spacing-3xs);
|
|
46
49
|
height: 40px;
|
|
47
50
|
width: fit-content;
|
|
51
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
52
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
53
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
54
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
55
|
+
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
.menubar-trigger {
|
|
@@ -52,13 +60,13 @@ export class RelMenubar extends LitElement {
|
|
|
52
60
|
align-items: center;
|
|
53
61
|
align-self: stretch;
|
|
54
62
|
height: 100%;
|
|
55
|
-
padding: 0 var(--rel-spacing-
|
|
63
|
+
padding: 0 var(--rel-spacing-sm);
|
|
56
64
|
background: transparent;
|
|
57
65
|
border: none;
|
|
58
66
|
cursor: pointer;
|
|
59
67
|
font-family: inherit;
|
|
60
68
|
font-size: inherit;
|
|
61
|
-
color: var(--rel-
|
|
69
|
+
color: var(--rel-text-label);
|
|
62
70
|
transition: background 0.15s ease;
|
|
63
71
|
user-select: none;
|
|
64
72
|
outline: none;
|
|
@@ -66,20 +74,20 @@ export class RelMenubar extends LitElement {
|
|
|
66
74
|
|
|
67
75
|
.menubar-trigger:hover,
|
|
68
76
|
.menubar-trigger.active {
|
|
69
|
-
background: var(--rel-
|
|
77
|
+
background: var(--rel-bg-neutral-elevated-2);
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
.menubar-trigger:focus-visible {
|
|
73
|
-
outline: 2px solid var(--rel-
|
|
81
|
+
outline: 2px solid var(--rel-border-focus);
|
|
74
82
|
outline-offset: 2px;
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
.menubar-panel {
|
|
78
86
|
position: fixed;
|
|
79
87
|
min-width: 220px;
|
|
80
|
-
background: var(--rel-
|
|
81
|
-
border: var(--rel-
|
|
82
|
-
border-radius: var(--rel-
|
|
88
|
+
background: var(--rel-bg-surface);
|
|
89
|
+
border: var(--rel-stroke-sm) solid var(--rel-border-default);
|
|
90
|
+
border-radius: var(--rel-border-radius-sm);
|
|
83
91
|
box-shadow: var(--rel-shadows-shadow-300);
|
|
84
92
|
padding: 0;
|
|
85
93
|
overflow: hidden;
|
|
@@ -96,18 +104,18 @@ export class RelMenubar extends LitElement {
|
|
|
96
104
|
display: flex;
|
|
97
105
|
align-items: center;
|
|
98
106
|
justify-content: space-between;
|
|
99
|
-
gap: var(--rel-spacing-
|
|
100
|
-
padding: var(--rel-spacing-
|
|
107
|
+
gap: var(--rel-spacing-sm);
|
|
108
|
+
padding: var(--rel-spacing-xs) var(--rel-spacing-md);
|
|
101
109
|
cursor: pointer;
|
|
102
110
|
transition: background 0.15s ease;
|
|
103
111
|
user-select: none;
|
|
104
|
-
color: var(--rel-
|
|
105
|
-
line-height: var(--rel-
|
|
112
|
+
color: var(--rel-text-label);
|
|
113
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
.menu-item:hover,
|
|
109
117
|
.menu-item.focused {
|
|
110
|
-
background: var(--rel-
|
|
118
|
+
background: var(--rel-bg-neutral-elevated-2);
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
.menu-item[data-disabled="true"] {
|
|
@@ -116,8 +124,8 @@ export class RelMenubar extends LitElement {
|
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
.menu-separator {
|
|
119
|
-
height: var(--rel-
|
|
120
|
-
background: var(--rel-
|
|
127
|
+
height: var(--rel-stroke-sm);
|
|
128
|
+
background: var(--rel-border-default);
|
|
121
129
|
margin: 0;
|
|
122
130
|
padding: 0;
|
|
123
131
|
}
|
|
@@ -138,9 +146,9 @@ export class RelMenubar extends LitElement {
|
|
|
138
146
|
.submenu-panel {
|
|
139
147
|
position: fixed;
|
|
140
148
|
min-width: 200px;
|
|
141
|
-
background: var(--rel-
|
|
142
|
-
border: var(--rel-
|
|
143
|
-
border-radius: var(--rel-
|
|
149
|
+
background: var(--rel-bg-surface);
|
|
150
|
+
border: var(--rel-stroke-sm) solid var(--rel-border-default);
|
|
151
|
+
border-radius: var(--rel-border-radius-sm);
|
|
144
152
|
box-shadow: var(--rel-shadows-shadow-300);
|
|
145
153
|
padding: 0;
|
|
146
154
|
overflow: hidden;
|
|
@@ -154,17 +162,21 @@ export class RelMenubar extends LitElement {
|
|
|
154
162
|
|
|
155
163
|
.menu-shortcut {
|
|
156
164
|
opacity: 0.6;
|
|
157
|
-
font-size: var(--rel-fontSizes-xs);
|
|
158
165
|
margin-left: auto;
|
|
159
166
|
display: flex;
|
|
160
167
|
align-items: center;
|
|
161
|
-
gap: var(--rel-spacing-
|
|
168
|
+
gap: var(--rel-spacing-3xs);
|
|
169
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
170
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
171
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
172
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
173
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
162
174
|
}
|
|
163
175
|
|
|
164
176
|
.menubar-panel rel-checkbox {
|
|
165
177
|
display: block;
|
|
166
178
|
width: 100%;
|
|
167
|
-
padding: var(--rel-spacing-
|
|
179
|
+
padding: var(--rel-spacing-xs, 8px) var(--rel-spacing-md, 16px);
|
|
168
180
|
box-sizing: border-box;
|
|
169
181
|
cursor: pointer;
|
|
170
182
|
}
|
|
@@ -224,7 +236,12 @@ export class RelMenubar extends LitElement {
|
|
|
224
236
|
}
|
|
225
237
|
|
|
226
238
|
private handleOutsideClick = (e: MouseEvent) => {
|
|
227
|
-
|
|
239
|
+
// composedPath (NOT e.target): a document-level listener sees e.target
|
|
240
|
+
// retargeted to the shadow host when the menubar lives inside a shadow root
|
|
241
|
+
// (e.g. any consumer component), so this.contains(e.target) is false for a
|
|
242
|
+
// click that is actually INSIDE the menubar — closing the menu on the very
|
|
243
|
+
// click that opened it. composedPath pierces shadow boundaries.
|
|
244
|
+
if (!e.composedPath().includes(this)) {
|
|
228
245
|
this.closeAllMenus();
|
|
229
246
|
}
|
|
230
247
|
};
|
|
@@ -581,7 +598,6 @@ export class RelMenubar extends LitElement {
|
|
|
581
598
|
render() {
|
|
582
599
|
return html`
|
|
583
600
|
<style>${RelMenubar._css}</style>
|
|
584
|
-
<slot></slot>
|
|
585
601
|
<div class="menubar-container">
|
|
586
602
|
${this.menus.map(
|
|
587
603
|
(menuState, index) => html`
|