@reliability-design/web 1.4.4 → 1.4.5
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 +13 -1
- package/dist/components/rel-button/rel-button.ts +18 -2
- package/dist/components/rel-calendar/rel-calendar.ts +23 -1
- package/dist/components/rel-carousel/rel-carousel.ts +20 -0
- package/dist/components/rel-checkbox/rel-checkbox.ts +4 -1
- package/dist/components/rel-chip/rel-chip.ts +29 -0
- package/dist/components/rel-date-picker/rel-date-picker.ts +156 -13
- package/dist/components/rel-drawer/rel-drawer.ts +22 -58
- package/dist/components/rel-menu/rel-menu.ts +124 -15
- package/dist/components/rel-menubar/rel-menubar.ts +221 -63
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +39 -3
- package/dist/components/rel-otp/rel-otp.ts +3 -3
- package/dist/components/rel-popover/rel-popover.ts +31 -3
- package/dist/components/rel-range-picker/rel-range-picker.ts +134 -1
- package/dist/components/rel-resizable/rel-resizable-group.ts +20 -0
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +50 -12
- package/dist/components/rel-select/rel-select.ts +159 -11
- package/dist/components/rel-sheet/rel-sheet.ts +22 -1
- package/dist/components/rel-sidebar/rel-sidebar.ts +43 -39
- package/dist/components/rel-stepper/rel-stepper.ts +44 -1
- package/dist/components/rel-tab/rel-tab.ts +32 -7
- package/dist/components/rel-time-picker/rel-time-picker.ts +338 -206
- package/dist/components/rel-tooltip/rel-tooltip.ts +30 -0
- package/dist/components/rel-topbar/rel-topbar-actions.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar-brand.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar-dropdown.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar-item.ts +318 -319
- package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar-profile.ts +224 -224
- package/dist/components/rel-topbar/rel-topbar-utilities.ts +1 -1
- package/dist/components/rel-topbar/rel-topbar.ts +440 -440
- package/dist/index.d.mts +225 -17
- package/dist/index.d.ts +225 -17
- package/dist/index.js +1468 -344
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1721 -607
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
29
29
|
// src/index.ts
|
|
30
30
|
var index_exports = {};
|
|
31
31
|
__export(index_exports, {
|
|
32
|
+
FocusTrap: () => FocusTrap,
|
|
32
33
|
RelAccordion: () => RelAccordion,
|
|
33
34
|
RelAlert: () => RelAlert,
|
|
34
35
|
RelAutocomplete: () => RelAutocomplete,
|
|
@@ -89,7 +90,16 @@ __export(index_exports, {
|
|
|
89
90
|
RelTopbarProfile: () => RelTopbarProfile,
|
|
90
91
|
RelTopbarUtilities: () => RelTopbarUtilities,
|
|
91
92
|
RelUpload: () => RelUpload,
|
|
92
|
-
RelUploadActions: () => RelUploadActions
|
|
93
|
+
RelUploadActions: () => RelUploadActions,
|
|
94
|
+
bindShortcut: () => bindShortcut,
|
|
95
|
+
formatShortcutParts: () => formatShortcutParts,
|
|
96
|
+
getDeepActiveElement: () => getDeepActiveElement,
|
|
97
|
+
getFocusableElements: () => getFocusableElements,
|
|
98
|
+
isApplePlatform: () => isApplePlatform,
|
|
99
|
+
isEditableTarget: () => isEditableTarget,
|
|
100
|
+
matchesShortcut: () => matchesShortcut,
|
|
101
|
+
parseShortcut: () => parseShortcut,
|
|
102
|
+
trapTabKey: () => trapTabKey
|
|
93
103
|
});
|
|
94
104
|
module.exports = __toCommonJS(index_exports);
|
|
95
105
|
var import_vars = require("./vars-XNMTSRVN.css");
|
|
@@ -106,6 +116,8 @@ var RelAccordion = class extends import_lit.LitElement {
|
|
|
106
116
|
this.size = "md";
|
|
107
117
|
this.type = "default";
|
|
108
118
|
this.appearance = "shadow";
|
|
119
|
+
/** Stable id prefix wiring the header to its panel. */
|
|
120
|
+
this.accordionId = `rel-accordion-${Math.random().toString(36).slice(2)}`;
|
|
109
121
|
}
|
|
110
122
|
toggle() {
|
|
111
123
|
this.open = !this.open;
|
|
@@ -122,9 +134,11 @@ var RelAccordion = class extends import_lit.LitElement {
|
|
|
122
134
|
<div part="base" class="accordion">
|
|
123
135
|
<div
|
|
124
136
|
part="trigger" class="header"
|
|
137
|
+
id="${this.accordionId}-header"
|
|
125
138
|
role="button"
|
|
126
139
|
tabindex="0"
|
|
127
140
|
aria-expanded="${this.open}"
|
|
141
|
+
aria-controls="${this.accordionId}-body"
|
|
128
142
|
@click=${this.toggle}
|
|
129
143
|
@keydown=${(e) => {
|
|
130
144
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -147,7 +161,14 @@ var RelAccordion = class extends import_lit.LitElement {
|
|
|
147
161
|
</slot>
|
|
148
162
|
</div>
|
|
149
163
|
</div>
|
|
150
|
-
<div
|
|
164
|
+
<div
|
|
165
|
+
part="content"
|
|
166
|
+
class="body ${this.open ? "open" : "closed"}"
|
|
167
|
+
id="${this.accordionId}-body"
|
|
168
|
+
role="region"
|
|
169
|
+
aria-labelledby="${this.accordionId}-header"
|
|
170
|
+
?inert=${!this.open}
|
|
171
|
+
>
|
|
151
172
|
<slot></slot>
|
|
152
173
|
</div>
|
|
153
174
|
</div>
|
|
@@ -2837,9 +2858,12 @@ RelButton.styles = import_lit8.css`
|
|
|
2837
2858
|
}
|
|
2838
2859
|
}
|
|
2839
2860
|
|
|
2840
|
-
/* Sizes
|
|
2861
|
+
/* Sizes. Min-widths come from the Figma Button master (S/M/L =
|
|
2862
|
+
80/100/120) so short labels like "OK" keep a proper pill shape;
|
|
2863
|
+
--rel-button-min-width lets a consumer override per instance. */
|
|
2841
2864
|
.size-small {
|
|
2842
2865
|
height: 32px;
|
|
2866
|
+
min-width: var(--rel-button-min-width, 80px);
|
|
2843
2867
|
padding: 0 var(--rel-spacing-sm);
|
|
2844
2868
|
font-family: var(--rel-action-md-font-family);
|
|
2845
2869
|
font-weight: var(--rel-action-md-font-weight);
|
|
@@ -2850,6 +2874,7 @@ RelButton.styles = import_lit8.css`
|
|
|
2850
2874
|
|
|
2851
2875
|
.size-medium {
|
|
2852
2876
|
height: 40px;
|
|
2877
|
+
min-width: var(--rel-button-min-width, 100px);
|
|
2853
2878
|
gap: var(--rel-spacing-xs);
|
|
2854
2879
|
padding: 0 var(--rel-spacing-md);
|
|
2855
2880
|
font-family: var(--rel-action-lg-font-family);
|
|
@@ -2861,6 +2886,7 @@ RelButton.styles = import_lit8.css`
|
|
|
2861
2886
|
|
|
2862
2887
|
.size-large {
|
|
2863
2888
|
height: 48px;
|
|
2889
|
+
min-width: var(--rel-button-min-width, 120px);
|
|
2864
2890
|
padding: 0 var(--rel-spacing-lg);
|
|
2865
2891
|
font-family: var(--rel-action-xl-font-family);
|
|
2866
2892
|
font-weight: var(--rel-action-xl-font-weight);
|
|
@@ -2967,6 +2993,11 @@ RelButton.styles = import_lit8.css`
|
|
|
2967
2993
|
color: var(--rel-action-neutral-label-icons-ghost);
|
|
2968
2994
|
}
|
|
2969
2995
|
|
|
2996
|
+
/* Figma master: ghost S has a narrower 56px min than primary/outline. */
|
|
2997
|
+
.btn-text.size-small {
|
|
2998
|
+
min-width: var(--rel-button-min-width, 56px);
|
|
2999
|
+
}
|
|
3000
|
+
|
|
2970
3001
|
.btn-text.tone-critical {
|
|
2971
3002
|
color: var(--rel-action-danger-label-icons-ghost);
|
|
2972
3003
|
}
|
|
@@ -2975,6 +3006,8 @@ RelButton.styles = import_lit8.css`
|
|
|
2975
3006
|
background: transparent;
|
|
2976
3007
|
color: var(--rel-action-neutral-label-icons-link);
|
|
2977
3008
|
text-decoration: underline;
|
|
3009
|
+
/* Links are inline actions — no minimum footprint in the Figma master. */
|
|
3010
|
+
min-width: 0;
|
|
2978
3011
|
}
|
|
2979
3012
|
|
|
2980
3013
|
button.btn-link:hover:not(:disabled) {
|
|
@@ -3003,6 +3036,7 @@ RelButton.styles = import_lit8.css`
|
|
|
3003
3036
|
/* Icon Only as an independent axis: square button of the current style */
|
|
3004
3037
|
.btn-icon-only {
|
|
3005
3038
|
padding: 0;
|
|
3039
|
+
min-width: 0;
|
|
3006
3040
|
}
|
|
3007
3041
|
|
|
3008
3042
|
.btn-icon-only.size-small {
|
|
@@ -3021,6 +3055,7 @@ RelButton.styles = import_lit8.css`
|
|
|
3021
3055
|
width: 40px;
|
|
3022
3056
|
height: 40px;
|
|
3023
3057
|
padding: 0;
|
|
3058
|
+
min-width: 0;
|
|
3024
3059
|
border-radius: 50%;
|
|
3025
3060
|
}
|
|
3026
3061
|
.btn-icon.tone-default {
|
|
@@ -3065,7 +3100,9 @@ RelButton.styles = import_lit8.css`
|
|
|
3065
3100
|
.btn-text: ghost gets tone-accent hovers below (this generic grey overlay
|
|
3066
3101
|
has higher specificity than the per-tone ghost rules and was overriding
|
|
3067
3102
|
them, which is why every ghost hover looked grey). */
|
|
3068
|
-
|
|
3103
|
+
/* Link buttons change label color only (rule above) — the overlay fill
|
|
3104
|
+
here would make a hovered link read as a ghost button. */
|
|
3105
|
+
button:hover:not(:disabled):not(.btn-primary):not(.btn-default):not(.btn-icon):not(.btn-secondary):not(.btn-static):not(.btn-text):not(.btn-link) {
|
|
3069
3106
|
background: var(--rel-action-overlay-hover);
|
|
3070
3107
|
}
|
|
3071
3108
|
|
|
@@ -3417,9 +3454,28 @@ var RelCalendar = class extends import_lit9.LitElement {
|
|
|
3417
3454
|
});
|
|
3418
3455
|
}
|
|
3419
3456
|
}
|
|
3457
|
+
/**
|
|
3458
|
+
* Index of the single grid cell carrying tabindex="0". The old expression
|
|
3459
|
+
* (`isSelected || (index === 0 && !focusedDate)`) left the grid with ZERO
|
|
3460
|
+
* tab stops once the user arrowed around without a selection — Tab could
|
|
3461
|
+
* never re-enter the calendar.
|
|
3462
|
+
*/
|
|
3463
|
+
getRovingIndex(days) {
|
|
3464
|
+
const candidates = [
|
|
3465
|
+
this.focusedDate ? days.findIndex(
|
|
3466
|
+
(d) => d.isCurrentMonth && this.isSameDay(d.date, this.focusedDate)
|
|
3467
|
+
) : -1,
|
|
3468
|
+
days.findIndex((d) => d.isSelected),
|
|
3469
|
+
days.findIndex((d) => d.isCurrentMonth && d.isToday),
|
|
3470
|
+
days.findIndex((d) => d.isCurrentMonth && !d.isDisabled)
|
|
3471
|
+
];
|
|
3472
|
+
const found = candidates.find((i) => i >= 0);
|
|
3473
|
+
return found ?? 0;
|
|
3474
|
+
}
|
|
3420
3475
|
render() {
|
|
3421
3476
|
const days = this.getCalendarDays();
|
|
3422
3477
|
const weekdayLabels = this.getWeekdayLabels();
|
|
3478
|
+
const rovingIndex = this.getRovingIndex(days);
|
|
3423
3479
|
return import_lit9.html`
|
|
3424
3480
|
<div role="grid" aria-label="Calendar">
|
|
3425
3481
|
<div part="header" class="header">
|
|
@@ -3472,7 +3528,7 @@ var RelCalendar = class extends import_lit9.LitElement {
|
|
|
3472
3528
|
?disabled=${day.isDisabled}
|
|
3473
3529
|
@click=${() => this.selectDay(day)}
|
|
3474
3530
|
@keydown=${(e) => this.handleKeyDown(e, day, index)}
|
|
3475
|
-
tabindex="${
|
|
3531
|
+
tabindex="${index === rovingIndex ? 0 : -1}"
|
|
3476
3532
|
>
|
|
3477
3533
|
${day.day}
|
|
3478
3534
|
</button>
|
|
@@ -3772,12 +3828,23 @@ var RelCarousel = class extends import_lit11.LitElement {
|
|
|
3772
3828
|
// Number of clone slides prepended before the real slides
|
|
3773
3829
|
this.clonesBefore = 0;
|
|
3774
3830
|
this._silentJumping = false;
|
|
3831
|
+
this.handleFocusIn = () => {
|
|
3832
|
+
if (this.autoplay) this.stopAutoplay();
|
|
3833
|
+
};
|
|
3834
|
+
this.handleFocusOut = (e) => {
|
|
3835
|
+
if (!this.autoplay) return;
|
|
3836
|
+
const next = e.relatedTarget;
|
|
3837
|
+
if (next && (this.contains(next) || this.shadowRoot?.contains(next))) return;
|
|
3838
|
+
this.startAutoplay();
|
|
3839
|
+
};
|
|
3775
3840
|
}
|
|
3776
3841
|
connectedCallback() {
|
|
3777
3842
|
super.connectedCallback();
|
|
3778
3843
|
this.prefersReducedMotion = window.matchMedia(
|
|
3779
3844
|
"(prefers-reduced-motion: reduce)"
|
|
3780
3845
|
).matches;
|
|
3846
|
+
this.addEventListener("focusin", this.handleFocusIn);
|
|
3847
|
+
this.addEventListener("focusout", this.handleFocusOut);
|
|
3781
3848
|
if (this.autoplay) {
|
|
3782
3849
|
this.startAutoplay();
|
|
3783
3850
|
}
|
|
@@ -3809,6 +3876,8 @@ var RelCarousel = class extends import_lit11.LitElement {
|
|
|
3809
3876
|
}
|
|
3810
3877
|
disconnectedCallback() {
|
|
3811
3878
|
super.disconnectedCallback();
|
|
3879
|
+
this.removeEventListener("focusin", this.handleFocusIn);
|
|
3880
|
+
this.removeEventListener("focusout", this.handleFocusOut);
|
|
3812
3881
|
this._ro?.disconnect();
|
|
3813
3882
|
this.stopAutoplay();
|
|
3814
3883
|
if (this.animationId) {
|
|
@@ -4123,6 +4192,7 @@ var RelCarousel = class extends import_lit11.LitElement {
|
|
|
4123
4192
|
class="dot ${i === this.value ? "active" : ""}"
|
|
4124
4193
|
part="dot"
|
|
4125
4194
|
aria-label="Go to slide ${i + 1}"
|
|
4195
|
+
aria-current="${i === this.value ? "true" : import_lit11.nothing}"
|
|
4126
4196
|
@click=${() => this.goToSlide(i)}
|
|
4127
4197
|
></button>
|
|
4128
4198
|
`) : import_lit11.nothing}
|
|
@@ -4570,6 +4640,9 @@ var RelCheckbox = class extends import_lit13.LitElement {
|
|
|
4570
4640
|
<label class="row">
|
|
4571
4641
|
<input
|
|
4572
4642
|
type="checkbox"
|
|
4643
|
+
.checked=${this.status === "checked"}
|
|
4644
|
+
.indeterminate=${this.status === "indeterminate"}
|
|
4645
|
+
aria-checked="${this.status === "indeterminate" ? "mixed" : import_lit13.nothing}"
|
|
4573
4646
|
@click=${this.toggle}
|
|
4574
4647
|
@blur=${() => this._onTouched()}
|
|
4575
4648
|
?disabled=${this.state === "disabled"}
|
|
@@ -4908,6 +4981,109 @@ function formatDatePattern(date, pattern, locale = "en-US") {
|
|
|
4908
4981
|
);
|
|
4909
4982
|
}
|
|
4910
4983
|
|
|
4984
|
+
// src/utils/focus.ts
|
|
4985
|
+
var FOCUSABLE_SELECTOR = [
|
|
4986
|
+
'a[href]:not([tabindex="-1"])',
|
|
4987
|
+
'button:not([disabled]):not([tabindex="-1"])',
|
|
4988
|
+
'input:not([disabled]):not([tabindex="-1"])',
|
|
4989
|
+
'select:not([disabled]):not([tabindex="-1"])',
|
|
4990
|
+
'textarea:not([disabled]):not([tabindex="-1"])',
|
|
4991
|
+
'audio[controls]:not([tabindex="-1"])',
|
|
4992
|
+
'video[controls]:not([tabindex="-1"])',
|
|
4993
|
+
'[contenteditable]:not([contenteditable="false"]):not([tabindex="-1"])',
|
|
4994
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
4995
|
+
].join(",");
|
|
4996
|
+
function isVisible(el) {
|
|
4997
|
+
return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
|
|
4998
|
+
}
|
|
4999
|
+
function getFocusableElements(root) {
|
|
5000
|
+
const out = [];
|
|
5001
|
+
const visit = (el) => {
|
|
5002
|
+
if (el instanceof HTMLElement && el.matches(FOCUSABLE_SELECTOR) && isVisible(el)) {
|
|
5003
|
+
out.push(el);
|
|
5004
|
+
}
|
|
5005
|
+
if (el instanceof HTMLSlotElement) {
|
|
5006
|
+
el.assignedElements({ flatten: true }).forEach(visit);
|
|
5007
|
+
} else if (el.shadowRoot) {
|
|
5008
|
+
Array.from(el.shadowRoot.children).forEach(visit);
|
|
5009
|
+
} else {
|
|
5010
|
+
Array.from(el.children).forEach(visit);
|
|
5011
|
+
}
|
|
5012
|
+
};
|
|
5013
|
+
const start = root instanceof ShadowRoot ? Array.from(root.children) : root.shadowRoot ? Array.from(root.shadowRoot.children) : Array.from(root.children);
|
|
5014
|
+
start.forEach(visit);
|
|
5015
|
+
return out;
|
|
5016
|
+
}
|
|
5017
|
+
function getDeepActiveElement() {
|
|
5018
|
+
let active = document.activeElement;
|
|
5019
|
+
while (active?.shadowRoot?.activeElement) {
|
|
5020
|
+
active = active.shadowRoot.activeElement;
|
|
5021
|
+
}
|
|
5022
|
+
return active instanceof HTMLElement ? active : null;
|
|
5023
|
+
}
|
|
5024
|
+
function trapTabKey(e, container) {
|
|
5025
|
+
if (e.key !== "Tab") return;
|
|
5026
|
+
const focusable = getFocusableElements(container);
|
|
5027
|
+
if (focusable.length === 0) {
|
|
5028
|
+
e.preventDefault();
|
|
5029
|
+
return;
|
|
5030
|
+
}
|
|
5031
|
+
const first = focusable[0];
|
|
5032
|
+
const last = focusable[focusable.length - 1];
|
|
5033
|
+
const active = getDeepActiveElement();
|
|
5034
|
+
const inside = active ? focusable.includes(active) : false;
|
|
5035
|
+
if (e.shiftKey) {
|
|
5036
|
+
if (!inside || active === first) {
|
|
5037
|
+
e.preventDefault();
|
|
5038
|
+
last.focus();
|
|
5039
|
+
}
|
|
5040
|
+
} else {
|
|
5041
|
+
if (!inside || active === last) {
|
|
5042
|
+
e.preventDefault();
|
|
5043
|
+
first.focus();
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
5046
|
+
}
|
|
5047
|
+
var FocusTrap = class {
|
|
5048
|
+
constructor(getContainer) {
|
|
5049
|
+
this.getContainer = getContainer;
|
|
5050
|
+
this.previousFocus = null;
|
|
5051
|
+
this.active = false;
|
|
5052
|
+
this.onKeyDown = (e) => {
|
|
5053
|
+
const container = this.getContainer();
|
|
5054
|
+
if (container) trapTabKey(e, container);
|
|
5055
|
+
};
|
|
5056
|
+
}
|
|
5057
|
+
activate(options) {
|
|
5058
|
+
if (this.active) return;
|
|
5059
|
+
this.active = true;
|
|
5060
|
+
this.previousFocus = getDeepActiveElement();
|
|
5061
|
+
document.addEventListener("keydown", this.onKeyDown, true);
|
|
5062
|
+
requestAnimationFrame(() => {
|
|
5063
|
+
if (!this.active) return;
|
|
5064
|
+
const container = this.getContainer();
|
|
5065
|
+
if (!container) return;
|
|
5066
|
+
const target = options?.initialFocus ?? getFocusableElements(container)[0] ?? null;
|
|
5067
|
+
if (target) {
|
|
5068
|
+
target.focus();
|
|
5069
|
+
} else if (container instanceof HTMLElement) {
|
|
5070
|
+
container.tabIndex = -1;
|
|
5071
|
+
container.focus();
|
|
5072
|
+
}
|
|
5073
|
+
});
|
|
5074
|
+
}
|
|
5075
|
+
deactivate(options) {
|
|
5076
|
+
if (!this.active) return;
|
|
5077
|
+
this.active = false;
|
|
5078
|
+
document.removeEventListener("keydown", this.onKeyDown, true);
|
|
5079
|
+
const previous = this.previousFocus;
|
|
5080
|
+
this.previousFocus = null;
|
|
5081
|
+
if (options?.restoreFocus !== false && previous?.isConnected) {
|
|
5082
|
+
requestAnimationFrame(() => previous.focus());
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
};
|
|
5086
|
+
|
|
4911
5087
|
// src/components/rel-date-picker/rel-date-picker.ts
|
|
4912
5088
|
var RelDatePicker = class extends import_lit14.LitElement {
|
|
4913
5089
|
constructor() {
|
|
@@ -4945,9 +5121,58 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
4945
5121
|
}
|
|
4946
5122
|
};
|
|
4947
5123
|
this.handleEscapeKey = (e) => {
|
|
4948
|
-
if (
|
|
5124
|
+
if (!this.open) return;
|
|
5125
|
+
if (e.key === "Escape") {
|
|
4949
5126
|
this.closeCalendarPopover();
|
|
5127
|
+
return;
|
|
5128
|
+
}
|
|
5129
|
+
if (e.key === "Tab") {
|
|
5130
|
+
const popover = this.renderRoot.querySelector(".popover");
|
|
5131
|
+
if (popover) trapTabKey(e, popover);
|
|
5132
|
+
}
|
|
5133
|
+
};
|
|
5134
|
+
this.handleDropdownKeydown = (e) => {
|
|
5135
|
+
const menu = e.currentTarget;
|
|
5136
|
+
const items = Array.from(
|
|
5137
|
+
menu.querySelectorAll(".dropdown-item")
|
|
5138
|
+
);
|
|
5139
|
+
if (items.length === 0) return;
|
|
5140
|
+
if (e.key === "Escape") {
|
|
5141
|
+
e.preventDefault();
|
|
5142
|
+
e.stopPropagation();
|
|
5143
|
+
const trigger = menu.closest(".dropdown-container")?.querySelector(".dropdown-trigger");
|
|
5144
|
+
this.monthDropdownOpen = false;
|
|
5145
|
+
this.yearDropdownOpen = false;
|
|
5146
|
+
trigger?.focus();
|
|
5147
|
+
return;
|
|
5148
|
+
}
|
|
5149
|
+
if (e.key === "Tab") {
|
|
5150
|
+
this.monthDropdownOpen = false;
|
|
5151
|
+
this.yearDropdownOpen = false;
|
|
5152
|
+
return;
|
|
5153
|
+
}
|
|
5154
|
+
const idx = items.indexOf(getDeepActiveElement());
|
|
5155
|
+
let target;
|
|
5156
|
+
switch (e.key) {
|
|
5157
|
+
case "ArrowDown":
|
|
5158
|
+
target = Math.min(items.length - 1, idx + 1);
|
|
5159
|
+
break;
|
|
5160
|
+
case "ArrowUp":
|
|
5161
|
+
target = Math.max(0, idx - 1);
|
|
5162
|
+
break;
|
|
5163
|
+
case "Home":
|
|
5164
|
+
target = 0;
|
|
5165
|
+
break;
|
|
5166
|
+
case "End":
|
|
5167
|
+
target = items.length - 1;
|
|
5168
|
+
break;
|
|
5169
|
+
default:
|
|
5170
|
+
return;
|
|
4950
5171
|
}
|
|
5172
|
+
e.preventDefault();
|
|
5173
|
+
e.stopPropagation();
|
|
5174
|
+
items[target]?.focus();
|
|
5175
|
+
items[target]?.scrollIntoView({ block: "nearest" });
|
|
4951
5176
|
};
|
|
4952
5177
|
}
|
|
4953
5178
|
// minDate/maxDate supersede the deprecated min/max aliases
|
|
@@ -4984,10 +5209,12 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
4984
5209
|
}
|
|
4985
5210
|
if (changedProperties.has("open") && this.open) {
|
|
4986
5211
|
setTimeout(() => {
|
|
4987
|
-
const
|
|
5212
|
+
const target = this.calendarGrid?.querySelector(
|
|
5213
|
+
'.day-button[tabindex="0"]:not(:disabled)'
|
|
5214
|
+
) ?? this.calendarGrid?.querySelector(
|
|
4988
5215
|
".day-button:not(:disabled)"
|
|
4989
5216
|
);
|
|
4990
|
-
|
|
5217
|
+
target?.focus();
|
|
4991
5218
|
}, 100);
|
|
4992
5219
|
}
|
|
4993
5220
|
}
|
|
@@ -5285,27 +5512,67 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
5285
5512
|
e.stopPropagation();
|
|
5286
5513
|
this.monthDropdownOpen = !this.monthDropdownOpen;
|
|
5287
5514
|
this.yearDropdownOpen = false;
|
|
5515
|
+
if (this.monthDropdownOpen) this.focusOpenDropdownSelection();
|
|
5288
5516
|
}
|
|
5289
5517
|
toggleYearDropdown(e) {
|
|
5290
5518
|
e.stopPropagation();
|
|
5291
5519
|
this.yearDropdownOpen = !this.yearDropdownOpen;
|
|
5292
5520
|
this.monthDropdownOpen = false;
|
|
5521
|
+
if (this.yearDropdownOpen) this.focusOpenDropdownSelection();
|
|
5522
|
+
}
|
|
5523
|
+
handleDropdownTriggerKeydown(e, kind) {
|
|
5524
|
+
if (e.key !== "ArrowDown") return;
|
|
5525
|
+
e.preventDefault();
|
|
5526
|
+
e.stopPropagation();
|
|
5527
|
+
this.monthDropdownOpen = kind === "month";
|
|
5528
|
+
this.yearDropdownOpen = kind === "year";
|
|
5529
|
+
this.focusOpenDropdownSelection();
|
|
5530
|
+
}
|
|
5531
|
+
async focusOpenDropdownSelection() {
|
|
5532
|
+
await this.updateComplete;
|
|
5533
|
+
const menu = this.renderRoot.querySelector(".dropdown-menu.open");
|
|
5534
|
+
if (!menu) return;
|
|
5535
|
+
const target = menu.querySelector(".dropdown-item.selected") ?? menu.querySelector(".dropdown-item");
|
|
5536
|
+
target?.focus();
|
|
5537
|
+
target?.scrollIntoView({ block: "nearest" });
|
|
5293
5538
|
}
|
|
5294
5539
|
selectMonth(month, e) {
|
|
5295
5540
|
e.stopPropagation();
|
|
5541
|
+
const trigger = e.target.closest(".dropdown-container")?.querySelector(".dropdown-trigger");
|
|
5296
5542
|
this.viewMonth = month;
|
|
5297
5543
|
this.monthDropdownOpen = false;
|
|
5544
|
+
this.updateComplete.then(() => trigger?.focus());
|
|
5298
5545
|
}
|
|
5299
5546
|
selectYear(year, e) {
|
|
5300
5547
|
e.stopPropagation();
|
|
5548
|
+
const trigger = e.target.closest(".dropdown-container")?.querySelector(".dropdown-trigger");
|
|
5301
5549
|
this.viewYear = year;
|
|
5302
5550
|
this.yearDropdownOpen = false;
|
|
5551
|
+
this.updateComplete.then(() => trigger?.focus());
|
|
5552
|
+
}
|
|
5553
|
+
/**
|
|
5554
|
+
* Index of the single grid cell carrying tabindex="0". The old expression
|
|
5555
|
+
* (`isSelected || (index === 0 && !value)`) left the grid with ZERO tab
|
|
5556
|
+
* stops once the user arrowed around without a selection.
|
|
5557
|
+
*/
|
|
5558
|
+
getRovingIndex(days) {
|
|
5559
|
+
const candidates = [
|
|
5560
|
+
this.focusedDate ? days.findIndex(
|
|
5561
|
+
(d) => d.isCurrentMonth && this.isSameDay(d.date, this.focusedDate)
|
|
5562
|
+
) : -1,
|
|
5563
|
+
days.findIndex((d) => d.isSelected),
|
|
5564
|
+
days.findIndex((d) => d.isCurrentMonth && d.isToday),
|
|
5565
|
+
days.findIndex((d) => d.isCurrentMonth && !d.isDisabled)
|
|
5566
|
+
];
|
|
5567
|
+
const found = candidates.find((i) => i >= 0);
|
|
5568
|
+
return found ?? 0;
|
|
5303
5569
|
}
|
|
5304
5570
|
render() {
|
|
5305
5571
|
const days = this.getCalendarDays();
|
|
5306
5572
|
const weekdayLabels = this.getWeekdayLabels();
|
|
5307
5573
|
const months = this.getMonthsList();
|
|
5308
5574
|
const years = this.getYearsList();
|
|
5575
|
+
const rovingIndex = this.getRovingIndex(days);
|
|
5309
5576
|
const isError = this.error;
|
|
5310
5577
|
const belowText = isError && this.errorMessage ? this.errorMessage : this.helperText;
|
|
5311
5578
|
const describedById = belowText ? "date-picker-desc" : void 0;
|
|
@@ -5392,22 +5659,31 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
5392
5659
|
class="dropdown-trigger"
|
|
5393
5660
|
type="button"
|
|
5394
5661
|
@click=${this.toggleMonthDropdown}
|
|
5662
|
+
@keydown=${(e) => this.handleDropdownTriggerKeydown(e, "month")}
|
|
5395
5663
|
aria-label="Select month"
|
|
5664
|
+
aria-haspopup="listbox"
|
|
5396
5665
|
aria-expanded=${this.monthDropdownOpen}
|
|
5397
5666
|
>
|
|
5398
5667
|
${this.getMonthName()}
|
|
5399
5668
|
</button>
|
|
5400
|
-
<div
|
|
5669
|
+
<div
|
|
5670
|
+
class="dropdown-menu ${this.monthDropdownOpen ? "open" : ""}"
|
|
5671
|
+
role="listbox"
|
|
5672
|
+
aria-label="Month"
|
|
5673
|
+
@keydown=${this.handleDropdownKeydown}
|
|
5674
|
+
>
|
|
5401
5675
|
${months.map(
|
|
5402
5676
|
(m) => import_lit14.html`
|
|
5403
|
-
<
|
|
5677
|
+
<button
|
|
5404
5678
|
class="dropdown-item ${m.value === this.viewMonth ? "selected" : ""}"
|
|
5679
|
+
type="button"
|
|
5680
|
+
tabindex="-1"
|
|
5405
5681
|
@click=${(e) => this.selectMonth(m.value, e)}
|
|
5406
5682
|
role="option"
|
|
5407
5683
|
aria-selected=${m.value === this.viewMonth}
|
|
5408
5684
|
>
|
|
5409
5685
|
${m.label}
|
|
5410
|
-
</
|
|
5686
|
+
</button>
|
|
5411
5687
|
`
|
|
5412
5688
|
)}
|
|
5413
5689
|
</div>
|
|
@@ -5418,22 +5694,31 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
5418
5694
|
class="dropdown-trigger"
|
|
5419
5695
|
type="button"
|
|
5420
5696
|
@click=${this.toggleYearDropdown}
|
|
5697
|
+
@keydown=${(e) => this.handleDropdownTriggerKeydown(e, "year")}
|
|
5421
5698
|
aria-label="Select year"
|
|
5699
|
+
aria-haspopup="listbox"
|
|
5422
5700
|
aria-expanded=${this.yearDropdownOpen}
|
|
5423
5701
|
>
|
|
5424
5702
|
${this.viewYear}
|
|
5425
5703
|
</button>
|
|
5426
|
-
<div
|
|
5704
|
+
<div
|
|
5705
|
+
class="dropdown-menu ${this.yearDropdownOpen ? "open" : ""}"
|
|
5706
|
+
role="listbox"
|
|
5707
|
+
aria-label="Year"
|
|
5708
|
+
@keydown=${this.handleDropdownKeydown}
|
|
5709
|
+
>
|
|
5427
5710
|
${years.map(
|
|
5428
5711
|
(y) => import_lit14.html`
|
|
5429
|
-
<
|
|
5712
|
+
<button
|
|
5430
5713
|
class="dropdown-item ${y === this.viewYear ? "selected" : ""}"
|
|
5714
|
+
type="button"
|
|
5715
|
+
tabindex="-1"
|
|
5431
5716
|
@click=${(e) => this.selectYear(y, e)}
|
|
5432
5717
|
role="option"
|
|
5433
5718
|
aria-selected=${y === this.viewYear}
|
|
5434
5719
|
>
|
|
5435
5720
|
${y}
|
|
5436
|
-
</
|
|
5721
|
+
</button>
|
|
5437
5722
|
`
|
|
5438
5723
|
)}
|
|
5439
5724
|
</div>
|
|
@@ -5476,7 +5761,7 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
5476
5761
|
?disabled=${day.isDisabled}
|
|
5477
5762
|
@click=${() => this.selectDay(day)}
|
|
5478
5763
|
@keydown=${(e) => this.handleDayKeydown(e, day, index)}
|
|
5479
|
-
tabindex="${
|
|
5764
|
+
tabindex="${index === rovingIndex ? 0 : -1}"
|
|
5480
5765
|
>
|
|
5481
5766
|
${day.day}
|
|
5482
5767
|
</button>
|
|
@@ -5770,6 +6055,11 @@ RelDatePicker.styles = import_lit14.css`
|
|
|
5770
6055
|
}
|
|
5771
6056
|
|
|
5772
6057
|
.dropdown-item {
|
|
6058
|
+
display: block;
|
|
6059
|
+
width: 100%;
|
|
6060
|
+
border: none;
|
|
6061
|
+
background: transparent;
|
|
6062
|
+
text-align: left;
|
|
5773
6063
|
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
5774
6064
|
cursor: pointer;
|
|
5775
6065
|
color: var(--rel-text-default);
|
|
@@ -5781,6 +6071,11 @@ RelDatePicker.styles = import_lit14.css`
|
|
|
5781
6071
|
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
5782
6072
|
}
|
|
5783
6073
|
|
|
6074
|
+
.dropdown-item:focus-visible {
|
|
6075
|
+
outline: 2px solid var(--rel-border-focus);
|
|
6076
|
+
outline-offset: -2px;
|
|
6077
|
+
}
|
|
6078
|
+
|
|
5784
6079
|
.dropdown-item:hover {
|
|
5785
6080
|
background: var(--rel-bg-surface-elevated);
|
|
5786
6081
|
}
|
|
@@ -6009,6 +6304,7 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
6009
6304
|
this.viewMonth = (/* @__PURE__ */ new Date()).getMonth();
|
|
6010
6305
|
this.isSelectingRange = false;
|
|
6011
6306
|
this.hoverDate = null;
|
|
6307
|
+
this.focusedDate = null;
|
|
6012
6308
|
this.onReposition = () => {
|
|
6013
6309
|
if (this.open && this.mode === "popover") this.updatePopoverPosition();
|
|
6014
6310
|
};
|
|
@@ -6065,6 +6361,69 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
6065
6361
|
const qs = this.getQuickSelects().find((q) => q.label === label);
|
|
6066
6362
|
if (qs) this.selectQuickSelect(qs);
|
|
6067
6363
|
};
|
|
6364
|
+
this.handleGridKeydown = (e) => {
|
|
6365
|
+
const cell = e.target.closest?.(".day-cell");
|
|
6366
|
+
const iso = cell?.dataset.iso;
|
|
6367
|
+
if (!iso) return;
|
|
6368
|
+
const date = this.parseISODate(iso);
|
|
6369
|
+
if (!date) return;
|
|
6370
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
6371
|
+
e.preventDefault();
|
|
6372
|
+
const isDisabled = this.isDateDisabled(date);
|
|
6373
|
+
if (!isDisabled) {
|
|
6374
|
+
this.handleDayClick({
|
|
6375
|
+
date,
|
|
6376
|
+
day: date.getDate(),
|
|
6377
|
+
isCurrentMonth: true,
|
|
6378
|
+
isToday: false,
|
|
6379
|
+
isDraftFrom: false,
|
|
6380
|
+
isDraftTo: false,
|
|
6381
|
+
isInRange: false,
|
|
6382
|
+
isDisabled,
|
|
6383
|
+
isWeekend: false
|
|
6384
|
+
});
|
|
6385
|
+
}
|
|
6386
|
+
return;
|
|
6387
|
+
}
|
|
6388
|
+
const next = new Date(date);
|
|
6389
|
+
const dow = next.getDay();
|
|
6390
|
+
switch (e.key) {
|
|
6391
|
+
case "ArrowLeft":
|
|
6392
|
+
next.setDate(next.getDate() - 1);
|
|
6393
|
+
break;
|
|
6394
|
+
case "ArrowRight":
|
|
6395
|
+
next.setDate(next.getDate() + 1);
|
|
6396
|
+
break;
|
|
6397
|
+
case "ArrowUp":
|
|
6398
|
+
next.setDate(next.getDate() - 7);
|
|
6399
|
+
break;
|
|
6400
|
+
case "ArrowDown":
|
|
6401
|
+
next.setDate(next.getDate() + 7);
|
|
6402
|
+
break;
|
|
6403
|
+
case "Home":
|
|
6404
|
+
next.setDate(
|
|
6405
|
+
next.getDate() - (this.weekStart === "mon" ? dow === 0 ? 6 : dow - 1 : dow)
|
|
6406
|
+
);
|
|
6407
|
+
break;
|
|
6408
|
+
case "End":
|
|
6409
|
+
next.setDate(
|
|
6410
|
+
next.getDate() + (this.weekStart === "mon" ? dow === 0 ? 0 : 7 - dow : 6 - dow)
|
|
6411
|
+
);
|
|
6412
|
+
break;
|
|
6413
|
+
case "PageUp":
|
|
6414
|
+
if (e.shiftKey) next.setFullYear(next.getFullYear() - 1);
|
|
6415
|
+
else next.setMonth(next.getMonth() - 1);
|
|
6416
|
+
break;
|
|
6417
|
+
case "PageDown":
|
|
6418
|
+
if (e.shiftKey) next.setFullYear(next.getFullYear() + 1);
|
|
6419
|
+
else next.setMonth(next.getMonth() + 1);
|
|
6420
|
+
break;
|
|
6421
|
+
default:
|
|
6422
|
+
return;
|
|
6423
|
+
}
|
|
6424
|
+
e.preventDefault();
|
|
6425
|
+
this.moveGridFocusTo(next);
|
|
6426
|
+
};
|
|
6068
6427
|
}
|
|
6069
6428
|
// ── Lifecycle ──
|
|
6070
6429
|
connectedCallback() {
|
|
@@ -6149,6 +6508,7 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
6149
6508
|
this.draftActivePreset = null;
|
|
6150
6509
|
this.isSelectingRange = false;
|
|
6151
6510
|
this.hoverDate = null;
|
|
6511
|
+
this.focusedDate = null;
|
|
6152
6512
|
if (this.draftFrom) {
|
|
6153
6513
|
const date = this.parseISODate(this.draftFrom);
|
|
6154
6514
|
if (date) {
|
|
@@ -6447,10 +6807,45 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
6447
6807
|
if (!this.isSelectingRange || day.isDisabled) return;
|
|
6448
6808
|
this.hoverDate = this.formatISODate(day.date);
|
|
6449
6809
|
}
|
|
6810
|
+
// ── Grid keyboard navigation (APG grid pattern) ──
|
|
6811
|
+
/**
|
|
6812
|
+
* The cell that carries tabindex="0". Prefers the last roved date, then the
|
|
6813
|
+
* draft start, then today, then the first selectable day — always exactly
|
|
6814
|
+
* one Tab stop, even after arrowing away with nothing selected.
|
|
6815
|
+
*/
|
|
6816
|
+
getRovingIso(days) {
|
|
6817
|
+
const inView = days.filter((d) => d.isCurrentMonth);
|
|
6818
|
+
if (inView.length === 0) return "";
|
|
6819
|
+
const candidates = [
|
|
6820
|
+
this.focusedDate,
|
|
6821
|
+
this.draftFrom,
|
|
6822
|
+
this.formatISODate(/* @__PURE__ */ new Date())
|
|
6823
|
+
];
|
|
6824
|
+
for (const iso of candidates) {
|
|
6825
|
+
if (iso && inView.some((d) => this.formatISODate(d.date) === iso)) {
|
|
6826
|
+
return iso;
|
|
6827
|
+
}
|
|
6828
|
+
}
|
|
6829
|
+
const firstEnabled = inView.find((d) => !d.isDisabled) ?? inView[0];
|
|
6830
|
+
return this.formatISODate(firstEnabled.date);
|
|
6831
|
+
}
|
|
6832
|
+
async moveGridFocusTo(date) {
|
|
6833
|
+
this.focusedDate = this.formatISODate(date);
|
|
6834
|
+
if (date.getMonth() !== this.viewMonth || date.getFullYear() !== this.viewYear) {
|
|
6835
|
+
this.viewMonth = date.getMonth();
|
|
6836
|
+
this.viewYear = date.getFullYear();
|
|
6837
|
+
}
|
|
6838
|
+
if (this.isSelectingRange && !this.isDateDisabled(date)) {
|
|
6839
|
+
this.hoverDate = this.focusedDate;
|
|
6840
|
+
}
|
|
6841
|
+
await this.updateComplete;
|
|
6842
|
+
this.renderRoot.querySelector(`.day-cell[data-iso="${this.focusedDate}"]`)?.focus();
|
|
6843
|
+
}
|
|
6450
6844
|
// ── Render ──
|
|
6451
6845
|
render() {
|
|
6452
6846
|
const quickSelects = this.getQuickSelects();
|
|
6453
6847
|
const days = this.getCalendarDays();
|
|
6848
|
+
const rovingIso = this.getRovingIso(days);
|
|
6454
6849
|
const weekdayLabels = this.getWeekdayLabels();
|
|
6455
6850
|
const draftFromDisplay = this.draftFrom ? this.formatDisplayDate(this.draftFrom) : "";
|
|
6456
6851
|
const draftToDisplay = this.draftTo ? this.formatDisplayDate(this.draftTo) : "";
|
|
@@ -6579,8 +6974,10 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
6579
6974
|
@mouseleave=${() => {
|
|
6580
6975
|
this.hoverDate = null;
|
|
6581
6976
|
}}
|
|
6977
|
+
@keydown=${this.handleGridKeydown}
|
|
6582
6978
|
>
|
|
6583
6979
|
${days.map((day) => {
|
|
6980
|
+
const iso = this.formatISODate(day.date);
|
|
6584
6981
|
const cellClasses = [
|
|
6585
6982
|
"day-cell",
|
|
6586
6983
|
day.isInRange ? "in-range" : "",
|
|
@@ -6595,8 +6992,11 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
6595
6992
|
<div
|
|
6596
6993
|
class="${cellClasses}"
|
|
6597
6994
|
role="gridcell"
|
|
6598
|
-
|
|
6995
|
+
tabindex="${day.isCurrentMonth && iso === rovingIso ? 0 : -1}"
|
|
6996
|
+
data-iso="${iso}"
|
|
6997
|
+
aria-label="${iso}"
|
|
6599
6998
|
aria-selected="${day.isDraftFrom || day.isDraftTo || day.isInRange}"
|
|
6999
|
+
aria-disabled="${day.isDisabled ? "true" : import_lit15.nothing}"
|
|
6600
7000
|
@click=${() => this.handleDayClick(day)}
|
|
6601
7001
|
@mouseenter=${() => this.handleDayHover(day)}
|
|
6602
7002
|
>
|
|
@@ -6941,6 +7341,12 @@ RelRangePicker.styles = import_lit15.css`
|
|
|
6941
7341
|
cursor: pointer;
|
|
6942
7342
|
}
|
|
6943
7343
|
|
|
7344
|
+
.day-cell:focus-visible {
|
|
7345
|
+
outline: 2px solid var(--rel-border-focus);
|
|
7346
|
+
outline-offset: -2px;
|
|
7347
|
+
z-index: 2;
|
|
7348
|
+
}
|
|
7349
|
+
|
|
6944
7350
|
.day-cell.other-month {
|
|
6945
7351
|
opacity: 0.35;
|
|
6946
7352
|
pointer-events: none;
|
|
@@ -7116,6 +7522,9 @@ __decorateClass([
|
|
|
7116
7522
|
__decorateClass([
|
|
7117
7523
|
(0, import_decorators15.state)()
|
|
7118
7524
|
], RelRangePicker.prototype, "hoverDate", 2);
|
|
7525
|
+
__decorateClass([
|
|
7526
|
+
(0, import_decorators15.state)()
|
|
7527
|
+
], RelRangePicker.prototype, "focusedDate", 2);
|
|
7119
7528
|
__decorateClass([
|
|
7120
7529
|
(0, import_decorators15.query)(".trigger-button")
|
|
7121
7530
|
], RelRangePicker.prototype, "triggerButton", 2);
|
|
@@ -7145,6 +7554,33 @@ var RelChip = class extends import_lit16.LitElement {
|
|
|
7145
7554
|
this.removed = false;
|
|
7146
7555
|
this.draggableChip = false;
|
|
7147
7556
|
this.dragging = false;
|
|
7557
|
+
/** The chip body is announced as a button, so it must also act on keys:
|
|
7558
|
+
* Enter/Space re-dispatch as a host click (consumers' @click handlers
|
|
7559
|
+
* fire), Delete/Backspace remove when removable, and arrow keys ask the
|
|
7560
|
+
* container to reorder a draggable chip (`chip-move`) — the keyboard
|
|
7561
|
+
* alternative to pointer drag-and-drop. */
|
|
7562
|
+
this.handleKeydown = (e) => {
|
|
7563
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
7564
|
+
e.preventDefault();
|
|
7565
|
+
this.click();
|
|
7566
|
+
return;
|
|
7567
|
+
}
|
|
7568
|
+
if ((e.key === "Delete" || e.key === "Backspace") && this.removable) {
|
|
7569
|
+
e.preventDefault();
|
|
7570
|
+
this.handleRemove(e);
|
|
7571
|
+
return;
|
|
7572
|
+
}
|
|
7573
|
+
if (this.draggableChip && (e.key === "ArrowLeft" || e.key === "ArrowRight")) {
|
|
7574
|
+
e.preventDefault();
|
|
7575
|
+
this.dispatchEvent(
|
|
7576
|
+
new CustomEvent("chip-move", {
|
|
7577
|
+
detail: { direction: e.key === "ArrowLeft" ? -1 : 1 },
|
|
7578
|
+
bubbles: true,
|
|
7579
|
+
composed: true
|
|
7580
|
+
})
|
|
7581
|
+
);
|
|
7582
|
+
}
|
|
7583
|
+
};
|
|
7148
7584
|
this.onDragStart = (e) => {
|
|
7149
7585
|
if (!this.draggableChip) return;
|
|
7150
7586
|
this.dragging = true;
|
|
@@ -7198,6 +7634,7 @@ var RelChip = class extends import_lit16.LitElement {
|
|
|
7198
7634
|
role="button"
|
|
7199
7635
|
tabindex="0"
|
|
7200
7636
|
aria-label="${this.label}"
|
|
7637
|
+
@keydown=${this.handleKeydown}
|
|
7201
7638
|
>
|
|
7202
7639
|
${showAvatar ? import_lit16.html`<rel-avatar type="text" text=${this.avatar} size="xs"></rel-avatar>` : null}
|
|
7203
7640
|
${showLeadingIcon ? import_lit16.html`<rel-icon part="icon" class="chip-icon" name="${this.leadingIcon}"></rel-icon>` : null}
|
|
@@ -7789,26 +8226,8 @@ var RelDrawer = class extends import_lit20.LitElement {
|
|
|
7789
8226
|
this.close("escape");
|
|
7790
8227
|
return;
|
|
7791
8228
|
}
|
|
7792
|
-
if (e.key === "Tab" && this.modal) {
|
|
7793
|
-
|
|
7794
|
-
if (focusable.length === 0) {
|
|
7795
|
-
e.preventDefault();
|
|
7796
|
-
return;
|
|
7797
|
-
}
|
|
7798
|
-
const firstFocusable = focusable[0];
|
|
7799
|
-
const lastFocusable = focusable[focusable.length - 1];
|
|
7800
|
-
const activeElement = document.activeElement;
|
|
7801
|
-
if (e.shiftKey) {
|
|
7802
|
-
if (activeElement === firstFocusable) {
|
|
7803
|
-
e.preventDefault();
|
|
7804
|
-
lastFocusable.focus();
|
|
7805
|
-
}
|
|
7806
|
-
} else {
|
|
7807
|
-
if (activeElement === lastFocusable) {
|
|
7808
|
-
e.preventDefault();
|
|
7809
|
-
firstFocusable.focus();
|
|
7810
|
-
}
|
|
7811
|
-
}
|
|
8229
|
+
if (e.key === "Tab" && this.modal && this.panelElement) {
|
|
8230
|
+
trapTabKey(e, this.panelElement);
|
|
7812
8231
|
}
|
|
7813
8232
|
};
|
|
7814
8233
|
// Drag-to-close handlers
|
|
@@ -7867,12 +8286,19 @@ var RelDrawer = class extends import_lit20.LitElement {
|
|
|
7867
8286
|
}
|
|
7868
8287
|
}
|
|
7869
8288
|
handleOpen() {
|
|
7870
|
-
this.previousFocus =
|
|
8289
|
+
this.previousFocus = getDeepActiveElement();
|
|
7871
8290
|
this.lockScroll();
|
|
7872
8291
|
if (this.modal) {
|
|
7873
8292
|
this.updateComplete.then(() => {
|
|
7874
|
-
const
|
|
7875
|
-
|
|
8293
|
+
const panel = this.panelElement;
|
|
8294
|
+
if (!panel) return;
|
|
8295
|
+
const focusTarget = getFocusableElements(panel)[0];
|
|
8296
|
+
if (focusTarget) {
|
|
8297
|
+
focusTarget.focus();
|
|
8298
|
+
} else {
|
|
8299
|
+
panel.tabIndex = -1;
|
|
8300
|
+
panel.focus();
|
|
8301
|
+
}
|
|
7876
8302
|
});
|
|
7877
8303
|
}
|
|
7878
8304
|
document.addEventListener("keydown", this.handleKeydown);
|
|
@@ -7934,29 +8360,6 @@ var RelDrawer = class extends import_lit20.LitElement {
|
|
|
7934
8360
|
document.body.style.overflow = this.originalBodyOverflow;
|
|
7935
8361
|
document.body.style.paddingRight = this.originalBodyPaddingRight;
|
|
7936
8362
|
}
|
|
7937
|
-
getFocusableElements() {
|
|
7938
|
-
if (!this.panelElement) return [];
|
|
7939
|
-
const selectors = [
|
|
7940
|
-
"a[href]",
|
|
7941
|
-
"button:not([disabled])",
|
|
7942
|
-
"textarea:not([disabled])",
|
|
7943
|
-
"input:not([disabled])",
|
|
7944
|
-
"select:not([disabled])",
|
|
7945
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
7946
|
-
].join(",");
|
|
7947
|
-
const elements = Array.from(
|
|
7948
|
-
this.panelElement.querySelectorAll(selectors)
|
|
7949
|
-
);
|
|
7950
|
-
const slots = this.panelElement.querySelectorAll("slot");
|
|
7951
|
-
slots.forEach((slot) => {
|
|
7952
|
-
const assigned = slot.assignedElements({ flatten: true });
|
|
7953
|
-
assigned.forEach((el) => {
|
|
7954
|
-
const focusableInSlot = el.querySelectorAll(selectors);
|
|
7955
|
-
elements.push(...Array.from(focusableInSlot));
|
|
7956
|
-
});
|
|
7957
|
-
});
|
|
7958
|
-
return elements;
|
|
7959
|
-
}
|
|
7960
8363
|
handleOverlayClick(e) {
|
|
7961
8364
|
if (!this.dismissible || !this.closeOnBackdrop) return;
|
|
7962
8365
|
if (e.target === this.overlayElement) {
|
|
@@ -8967,12 +9370,16 @@ var RelMenu = class extends import_lit24.LitElement {
|
|
|
8967
9370
|
this.type = "default";
|
|
8968
9371
|
this.items = [];
|
|
8969
9372
|
this.focusedIndex = 0;
|
|
9373
|
+
this.subFocusedIndex = -1;
|
|
8970
9374
|
this._items = [];
|
|
9375
|
+
/** Stable id prefix for aria-activedescendant targets. */
|
|
9376
|
+
this.menuId = `rel-menu-${Math.random().toString(36).slice(2)}`;
|
|
8971
9377
|
/** Index of a submenu locked open by a click (survives hover-out). -1 = none. */
|
|
8972
9378
|
this.lockedIndex = -1;
|
|
8973
9379
|
this.handleDocumentClick = (e) => {
|
|
8974
9380
|
if (!e.composedPath().includes(this)) {
|
|
8975
9381
|
this.lockedIndex = -1;
|
|
9382
|
+
this.subFocusedIndex = -1;
|
|
8976
9383
|
this._items = this._items.map((i) => ({ ...i, open: false }));
|
|
8977
9384
|
}
|
|
8978
9385
|
};
|
|
@@ -9059,35 +9466,95 @@ var RelMenu = class extends import_lit24.LitElement {
|
|
|
9059
9466
|
})
|
|
9060
9467
|
);
|
|
9061
9468
|
}
|
|
9469
|
+
/** Keyboard-open locks the submenu (no hover-out to close it) and moves
|
|
9470
|
+
* virtual focus onto its first entry. */
|
|
9471
|
+
openSubmenuAt(index) {
|
|
9472
|
+
this.lockedIndex = index;
|
|
9473
|
+
this.subFocusedIndex = 0;
|
|
9474
|
+
this._items = this._items.map((i, idx) => ({ ...i, open: idx === index }));
|
|
9475
|
+
}
|
|
9476
|
+
closeSubmenus() {
|
|
9477
|
+
this.lockedIndex = -1;
|
|
9478
|
+
this.subFocusedIndex = -1;
|
|
9479
|
+
this._items = this._items.map((i) => ({ ...i, open: false }));
|
|
9480
|
+
}
|
|
9062
9481
|
onKeydown(ev) {
|
|
9063
9482
|
const last = this._items.length - 1;
|
|
9483
|
+
const focusedItem = this._items[this.focusedIndex];
|
|
9484
|
+
if (this.subFocusedIndex >= 0 && this.type === "arrow" && focusedItem?.open && focusedItem.submenu?.length) {
|
|
9485
|
+
const subLast = focusedItem.submenu.length - 1;
|
|
9486
|
+
switch (ev.key) {
|
|
9487
|
+
case "ArrowDown":
|
|
9488
|
+
this.subFocusedIndex = Math.min(subLast, this.subFocusedIndex + 1);
|
|
9489
|
+
ev.preventDefault();
|
|
9490
|
+
return;
|
|
9491
|
+
case "ArrowUp":
|
|
9492
|
+
this.subFocusedIndex = Math.max(0, this.subFocusedIndex - 1);
|
|
9493
|
+
ev.preventDefault();
|
|
9494
|
+
return;
|
|
9495
|
+
case "Home":
|
|
9496
|
+
this.subFocusedIndex = 0;
|
|
9497
|
+
ev.preventDefault();
|
|
9498
|
+
return;
|
|
9499
|
+
case "End":
|
|
9500
|
+
this.subFocusedIndex = subLast;
|
|
9501
|
+
ev.preventDefault();
|
|
9502
|
+
return;
|
|
9503
|
+
case "ArrowLeft":
|
|
9504
|
+
case "Escape":
|
|
9505
|
+
this.closeSubmenus();
|
|
9506
|
+
ev.preventDefault();
|
|
9507
|
+
ev.stopPropagation();
|
|
9508
|
+
return;
|
|
9509
|
+
case "Enter":
|
|
9510
|
+
case " ":
|
|
9511
|
+
this.onSubClick(this.focusedIndex, this.subFocusedIndex);
|
|
9512
|
+
ev.preventDefault();
|
|
9513
|
+
return;
|
|
9514
|
+
default:
|
|
9515
|
+
return;
|
|
9516
|
+
}
|
|
9517
|
+
}
|
|
9064
9518
|
if (ev.key === "ArrowDown") {
|
|
9065
9519
|
this.focusedIndex = Math.min(last, this.focusedIndex + 1);
|
|
9066
9520
|
ev.preventDefault();
|
|
9067
9521
|
} else if (ev.key === "ArrowUp") {
|
|
9068
9522
|
this.focusedIndex = Math.max(0, this.focusedIndex - 1);
|
|
9069
9523
|
ev.preventDefault();
|
|
9524
|
+
} else if (ev.key === "Home") {
|
|
9525
|
+
this.focusedIndex = 0;
|
|
9526
|
+
ev.preventDefault();
|
|
9527
|
+
} else if (ev.key === "End") {
|
|
9528
|
+
this.focusedIndex = last;
|
|
9529
|
+
ev.preventDefault();
|
|
9070
9530
|
} else if (ev.key === "ArrowRight") {
|
|
9071
9531
|
const item = this._items[this.focusedIndex];
|
|
9072
|
-
if (this.type === "arrow" && item?.submenu?.length) {
|
|
9073
|
-
this.
|
|
9074
|
-
|
|
9075
|
-
...i,
|
|
9076
|
-
open: idx === this.focusedIndex ? true : i.open
|
|
9077
|
-
}));
|
|
9532
|
+
if (this.type === "arrow" && item?.submenu?.length && !item.disabled) {
|
|
9533
|
+
this.openSubmenuAt(this.focusedIndex);
|
|
9534
|
+
ev.preventDefault();
|
|
9078
9535
|
}
|
|
9079
9536
|
} else if (ev.key === "ArrowLeft" || ev.key === "Escape") {
|
|
9080
|
-
this.
|
|
9081
|
-
|
|
9537
|
+
const anyOpen = this._items.some((i) => i.open);
|
|
9538
|
+
if (anyOpen) {
|
|
9539
|
+
this.closeSubmenus();
|
|
9540
|
+
ev.preventDefault();
|
|
9541
|
+
if (ev.key === "Escape") ev.stopPropagation();
|
|
9542
|
+
}
|
|
9082
9543
|
} else if (ev.key === "Enter" || ev.key === " ") {
|
|
9083
9544
|
const item = this._items[this.focusedIndex];
|
|
9084
|
-
this.
|
|
9545
|
+
if (this.type === "arrow" && item?.submenu?.length && !item.disabled) {
|
|
9546
|
+
this.openSubmenuAt(this.focusedIndex);
|
|
9547
|
+
} else {
|
|
9548
|
+
this.onItemClick(item, this.focusedIndex);
|
|
9549
|
+
}
|
|
9085
9550
|
ev.preventDefault();
|
|
9086
9551
|
}
|
|
9087
9552
|
}
|
|
9088
9553
|
renderMenuItem(item, index) {
|
|
9089
|
-
const isFocused = this.focusedIndex === index;
|
|
9554
|
+
const isFocused = this.focusedIndex === index && this.subFocusedIndex < 0;
|
|
9090
9555
|
const hasSubmenu = this.type === "arrow" && !!item.submenu?.length;
|
|
9556
|
+
const role = this.type === "checkbox" ? "menuitemcheckbox" : this.type === "select-option" ? "menuitemradio" : "menuitem";
|
|
9557
|
+
const ariaChecked = this.type === "checkbox" ? String(!!item.checked) : this.type === "select-option" ? String(!!item.active) : import_lit24.nothing;
|
|
9091
9558
|
const control = this.type === "checkbox" ? import_lit24.html`<rel-checkbox
|
|
9092
9559
|
class="menu-control"
|
|
9093
9560
|
label="${item.label}"
|
|
@@ -9105,8 +9572,13 @@ var RelMenu = class extends import_lit24.LitElement {
|
|
|
9105
9572
|
`;
|
|
9106
9573
|
return import_lit24.html`
|
|
9107
9574
|
<div
|
|
9575
|
+
id="${this.menuId}-item-${index}"
|
|
9108
9576
|
class="rel-menu-item ${item.active ? "active" : ""} ${item.disabled ? "disabled" : ""}"
|
|
9109
|
-
role="
|
|
9577
|
+
role="${role}"
|
|
9578
|
+
aria-checked="${ariaChecked}"
|
|
9579
|
+
aria-disabled="${item.disabled ? "true" : import_lit24.nothing}"
|
|
9580
|
+
aria-haspopup="${hasSubmenu ? "menu" : import_lit24.nothing}"
|
|
9581
|
+
aria-expanded="${hasSubmenu ? String(!!item.open) : import_lit24.nothing}"
|
|
9110
9582
|
?data-focused="${isFocused}"
|
|
9111
9583
|
@mouseenter="${() => this.onItemEnter(item, index)}"
|
|
9112
9584
|
@mouseleave="${() => this.onItemLeave(item, index)}"
|
|
@@ -9115,12 +9587,16 @@ var RelMenu = class extends import_lit24.LitElement {
|
|
|
9115
9587
|
${hasSubmenu ? import_lit24.html`
|
|
9116
9588
|
<div
|
|
9117
9589
|
part="panel" class="rel-submenu ${item.open ? "visible" : ""}"
|
|
9590
|
+
role="menu"
|
|
9118
9591
|
@click="${(e) => e.stopPropagation()}">
|
|
9119
9592
|
${item.submenu.map(
|
|
9120
9593
|
(sub, subIndex) => import_lit24.html`
|
|
9121
9594
|
<div
|
|
9122
|
-
|
|
9595
|
+
id="${this.menuId}-item-${index}-sub-${subIndex}"
|
|
9596
|
+
class="rel-menu-item ${sub.active ? "active" : ""} ${sub.disabled ? "disabled" : ""}"
|
|
9123
9597
|
role="menuitem"
|
|
9598
|
+
aria-disabled="${sub.disabled ? "true" : import_lit24.nothing}"
|
|
9599
|
+
?data-focused="${this.focusedIndex === index && this.subFocusedIndex === subIndex}"
|
|
9124
9600
|
@click="${(e) => this.onSubClick(index, subIndex, e)}">
|
|
9125
9601
|
<span class="menu-label">${sub.label}</span>
|
|
9126
9602
|
</div>
|
|
@@ -9132,12 +9608,14 @@ var RelMenu = class extends import_lit24.LitElement {
|
|
|
9132
9608
|
`;
|
|
9133
9609
|
}
|
|
9134
9610
|
render() {
|
|
9611
|
+
const activeDescendant = this._items.length === 0 ? import_lit24.nothing : this.subFocusedIndex >= 0 ? `${this.menuId}-item-${this.focusedIndex}-sub-${this.subFocusedIndex}` : `${this.menuId}-item-${this.focusedIndex}`;
|
|
9135
9612
|
return import_lit24.html`
|
|
9136
9613
|
<style>${RelMenu._css}</style>
|
|
9137
9614
|
<div
|
|
9138
9615
|
part="list" class="rel-menu ${this.type === "fixed" || this.type === "fit" ? this.type : ""}"
|
|
9139
9616
|
role="menu"
|
|
9140
9617
|
tabindex="0"
|
|
9618
|
+
aria-activedescendant="${activeDescendant}"
|
|
9141
9619
|
@keydown="${this.onKeydown}"
|
|
9142
9620
|
@click="${(e) => e.stopPropagation()}">
|
|
9143
9621
|
${this._items.map((item, index) => this.renderMenuItem(item, index))}
|
|
@@ -9227,7 +9705,10 @@ RelMenu._css = `
|
|
|
9227
9705
|
cursor: not-allowed;
|
|
9228
9706
|
}
|
|
9229
9707
|
|
|
9230
|
-
|
|
9708
|
+
/* [data-focused] (presence, not ="true"): the ?data-focused boolean
|
|
9709
|
+
binding renders the attribute with an EMPTY value, so a ="true"
|
|
9710
|
+
selector never matches. */
|
|
9711
|
+
.rel-menu-item[data-focused] {
|
|
9231
9712
|
outline: 2px solid var(--rel-border-focus);
|
|
9232
9713
|
outline-offset: -2px;
|
|
9233
9714
|
}
|
|
@@ -9318,6 +9799,9 @@ __decorateClass([
|
|
|
9318
9799
|
__decorateClass([
|
|
9319
9800
|
(0, import_decorators24.state)()
|
|
9320
9801
|
], RelMenu.prototype, "focusedIndex", 2);
|
|
9802
|
+
__decorateClass([
|
|
9803
|
+
(0, import_decorators24.state)()
|
|
9804
|
+
], RelMenu.prototype, "subFocusedIndex", 2);
|
|
9321
9805
|
__decorateClass([
|
|
9322
9806
|
(0, import_decorators24.state)()
|
|
9323
9807
|
], RelMenu.prototype, "_items", 2);
|
|
@@ -9328,16 +9812,159 @@ RelMenu = __decorateClass([
|
|
|
9328
9812
|
// src/components/rel-menubar/rel-menubar.ts
|
|
9329
9813
|
var import_lit25 = require("lit");
|
|
9330
9814
|
var import_decorators25 = require("lit/decorators.js");
|
|
9815
|
+
|
|
9816
|
+
// src/utils/shortcuts.ts
|
|
9817
|
+
function isApplePlatform() {
|
|
9818
|
+
if (typeof navigator === "undefined") return false;
|
|
9819
|
+
return /Mac|iPhone|iPad|iPod/i.test(navigator.platform || navigator.userAgent);
|
|
9820
|
+
}
|
|
9821
|
+
var KEY_ALIASES = {
|
|
9822
|
+
esc: "escape",
|
|
9823
|
+
space: " ",
|
|
9824
|
+
plus: "+",
|
|
9825
|
+
return: "enter",
|
|
9826
|
+
up: "arrowup",
|
|
9827
|
+
down: "arrowdown",
|
|
9828
|
+
left: "arrowleft",
|
|
9829
|
+
right: "arrowright"
|
|
9830
|
+
};
|
|
9831
|
+
function parseShortcut(shortcut) {
|
|
9832
|
+
const parsed = {
|
|
9833
|
+
key: "",
|
|
9834
|
+
ctrl: false,
|
|
9835
|
+
meta: false,
|
|
9836
|
+
alt: false,
|
|
9837
|
+
shift: false
|
|
9838
|
+
};
|
|
9839
|
+
const tokens = shortcut.split("+").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
9840
|
+
if (shortcut.endsWith("+") && !shortcut.endsWith("++")) tokens.push("+");
|
|
9841
|
+
for (const token of tokens) {
|
|
9842
|
+
switch (token) {
|
|
9843
|
+
case "mod":
|
|
9844
|
+
if (isApplePlatform()) parsed.meta = true;
|
|
9845
|
+
else parsed.ctrl = true;
|
|
9846
|
+
break;
|
|
9847
|
+
case "ctrl":
|
|
9848
|
+
case "control":
|
|
9849
|
+
parsed.ctrl = true;
|
|
9850
|
+
break;
|
|
9851
|
+
case "meta":
|
|
9852
|
+
case "cmd":
|
|
9853
|
+
case "command":
|
|
9854
|
+
case "win":
|
|
9855
|
+
parsed.meta = true;
|
|
9856
|
+
break;
|
|
9857
|
+
case "alt":
|
|
9858
|
+
case "option":
|
|
9859
|
+
parsed.alt = true;
|
|
9860
|
+
break;
|
|
9861
|
+
case "shift":
|
|
9862
|
+
parsed.shift = true;
|
|
9863
|
+
break;
|
|
9864
|
+
default:
|
|
9865
|
+
parsed.key = KEY_ALIASES[token] ?? token;
|
|
9866
|
+
}
|
|
9867
|
+
}
|
|
9868
|
+
return parsed;
|
|
9869
|
+
}
|
|
9870
|
+
function matchesShortcut(e, shortcut) {
|
|
9871
|
+
const parsed = parseShortcut(shortcut);
|
|
9872
|
+
if (!parsed.key) return false;
|
|
9873
|
+
return e.key.toLowerCase() === parsed.key && e.ctrlKey === parsed.ctrl && e.metaKey === parsed.meta && e.altKey === parsed.alt && e.shiftKey === parsed.shift;
|
|
9874
|
+
}
|
|
9875
|
+
function isEditableTarget(e) {
|
|
9876
|
+
const target = e.composedPath()[0];
|
|
9877
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
9878
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) {
|
|
9879
|
+
return true;
|
|
9880
|
+
}
|
|
9881
|
+
return target.isContentEditable;
|
|
9882
|
+
}
|
|
9883
|
+
function bindShortcut(shortcut, handler, options = {}) {
|
|
9884
|
+
const target = options.target ?? document;
|
|
9885
|
+
const listener = (event) => {
|
|
9886
|
+
const e = event;
|
|
9887
|
+
if (!matchesShortcut(e, shortcut)) return;
|
|
9888
|
+
if (!options.allowInEditable && isEditableTarget(e)) return;
|
|
9889
|
+
if (options.preventDefault !== false) e.preventDefault();
|
|
9890
|
+
handler(e);
|
|
9891
|
+
};
|
|
9892
|
+
target.addEventListener("keydown", listener);
|
|
9893
|
+
return () => target.removeEventListener("keydown", listener);
|
|
9894
|
+
}
|
|
9895
|
+
var APPLE_LABELS = {
|
|
9896
|
+
mod: "\u2318",
|
|
9897
|
+
meta: "\u2318",
|
|
9898
|
+
cmd: "\u2318",
|
|
9899
|
+
command: "\u2318",
|
|
9900
|
+
ctrl: "\u2303",
|
|
9901
|
+
control: "\u2303",
|
|
9902
|
+
alt: "\u2325",
|
|
9903
|
+
option: "\u2325",
|
|
9904
|
+
shift: "\u21E7"
|
|
9905
|
+
};
|
|
9906
|
+
var GENERIC_LABELS = {
|
|
9907
|
+
mod: "Ctrl",
|
|
9908
|
+
meta: "Win",
|
|
9909
|
+
cmd: "Win",
|
|
9910
|
+
command: "Win",
|
|
9911
|
+
ctrl: "Ctrl",
|
|
9912
|
+
control: "Ctrl",
|
|
9913
|
+
alt: "Alt",
|
|
9914
|
+
option: "Alt",
|
|
9915
|
+
shift: "Shift"
|
|
9916
|
+
};
|
|
9917
|
+
var KEY_LABELS = {
|
|
9918
|
+
arrowup: "\u2191",
|
|
9919
|
+
arrowdown: "\u2193",
|
|
9920
|
+
arrowleft: "\u2190",
|
|
9921
|
+
arrowright: "\u2192",
|
|
9922
|
+
escape: "Esc",
|
|
9923
|
+
" ": "Space"
|
|
9924
|
+
};
|
|
9925
|
+
function formatShortcutParts(shortcut) {
|
|
9926
|
+
const labels = isApplePlatform() ? APPLE_LABELS : GENERIC_LABELS;
|
|
9927
|
+
return shortcut.split("+").map((t) => t.trim()).filter(Boolean).map((token) => {
|
|
9928
|
+
const lower = token.toLowerCase();
|
|
9929
|
+
if (labels[lower]) return labels[lower];
|
|
9930
|
+
const key = KEY_ALIASES[lower] ?? lower;
|
|
9931
|
+
if (KEY_LABELS[key]) return KEY_LABELS[key];
|
|
9932
|
+
return key.length === 1 ? key.toUpperCase() : key[0].toUpperCase() + key.slice(1);
|
|
9933
|
+
});
|
|
9934
|
+
}
|
|
9935
|
+
|
|
9936
|
+
// src/components/rel-menubar/rel-menubar.ts
|
|
9331
9937
|
var import_rel_icon10 = require("@reliability-design/icons/rel-icon");
|
|
9332
9938
|
var RelMenubar = class extends import_lit25.LitElement {
|
|
9333
9939
|
constructor() {
|
|
9334
9940
|
super(...arguments);
|
|
9941
|
+
this.bindShortcuts = false;
|
|
9335
9942
|
this.menus = [];
|
|
9336
9943
|
this.openMenuIndex = -1;
|
|
9337
9944
|
this.focusedTriggerIndex = 0;
|
|
9338
9945
|
this.focusedItemIndex = -1;
|
|
9946
|
+
this.focusedSubItemIndex = -1;
|
|
9339
9947
|
this.openSubmenuElement = null;
|
|
9340
9948
|
this.hoverSwitchEnabled = false;
|
|
9949
|
+
/** Stable id prefix for aria-activedescendant targets (light DOM, so ids
|
|
9950
|
+
* must be unique per menubar instance on the page). */
|
|
9951
|
+
this.menubarId = `rel-menubar-${Math.random().toString(36).slice(2)}`;
|
|
9952
|
+
this.handleShortcutKeyDown = (e) => {
|
|
9953
|
+
if (!this.bindShortcuts || e.defaultPrevented) return;
|
|
9954
|
+
if (isEditableTarget(e)) return;
|
|
9955
|
+
for (const { menu } of this.menus) {
|
|
9956
|
+
for (const item of this.getFocusableItems(menu)) {
|
|
9957
|
+
const shortcut = item.getAttribute("data-shortcut");
|
|
9958
|
+
if (!shortcut) continue;
|
|
9959
|
+
if (item.disabled) continue;
|
|
9960
|
+
if (matchesShortcut(e, shortcut)) {
|
|
9961
|
+
e.preventDefault();
|
|
9962
|
+
this.handleItemClick(item);
|
|
9963
|
+
return;
|
|
9964
|
+
}
|
|
9965
|
+
}
|
|
9966
|
+
}
|
|
9967
|
+
};
|
|
9341
9968
|
this.handleOutsideClick = (e) => {
|
|
9342
9969
|
if (!e.composedPath().includes(this)) {
|
|
9343
9970
|
this.closeAllMenus();
|
|
@@ -9351,6 +9978,7 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9351
9978
|
super.connectedCallback();
|
|
9352
9979
|
this.discoverMenus();
|
|
9353
9980
|
document.addEventListener("click", this.handleOutsideClick);
|
|
9981
|
+
document.addEventListener("keydown", this.handleShortcutKeyDown);
|
|
9354
9982
|
this._onScroll = () => {
|
|
9355
9983
|
this._repositionOpenMenu();
|
|
9356
9984
|
this._repositionOpenSubmenu();
|
|
@@ -9360,6 +9988,7 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9360
9988
|
disconnectedCallback() {
|
|
9361
9989
|
super.disconnectedCallback();
|
|
9362
9990
|
document.removeEventListener("click", this.handleOutsideClick);
|
|
9991
|
+
document.removeEventListener("keydown", this.handleShortcutKeyDown);
|
|
9363
9992
|
if (this._onScroll) {
|
|
9364
9993
|
window.removeEventListener("scroll", this._onScroll, { capture: true });
|
|
9365
9994
|
}
|
|
@@ -9393,12 +10022,26 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9393
10022
|
closeAllMenus() {
|
|
9394
10023
|
this.openMenuIndex = -1;
|
|
9395
10024
|
this.openSubmenuElement = null;
|
|
10025
|
+
this.focusedItemIndex = -1;
|
|
10026
|
+
this.focusedSubItemIndex = -1;
|
|
9396
10027
|
this.hoverSwitchEnabled = false;
|
|
9397
10028
|
this.requestUpdate();
|
|
9398
10029
|
}
|
|
10030
|
+
/** Close everything and put DOM focus back on the trigger that owned the
|
|
10031
|
+
* open menu. (Capturing the index BEFORE closing — closeAllMenus resets
|
|
10032
|
+
* openMenuIndex to -1, which used to drop focus to <body>.) */
|
|
10033
|
+
closeAllMenusAndRefocusTrigger() {
|
|
10034
|
+
const idx = this.openMenuIndex;
|
|
10035
|
+
this.closeAllMenus();
|
|
10036
|
+
if (idx !== -1) {
|
|
10037
|
+
this.focusedTriggerIndex = idx;
|
|
10038
|
+
this.focusTrigger(idx);
|
|
10039
|
+
}
|
|
10040
|
+
}
|
|
9399
10041
|
openMenu(index, triggerElement) {
|
|
9400
10042
|
this.openMenuIndex = index;
|
|
9401
10043
|
this.focusedItemIndex = -1;
|
|
10044
|
+
this.focusedSubItemIndex = -1;
|
|
9402
10045
|
this.openSubmenuElement = null;
|
|
9403
10046
|
this.requestUpdate();
|
|
9404
10047
|
this.updateComplete.then(() => {
|
|
@@ -9440,27 +10083,20 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9440
10083
|
event.preventDefault();
|
|
9441
10084
|
this.focusedTriggerIndex = Math.max(0, this.focusedTriggerIndex - 1);
|
|
9442
10085
|
this.focusTrigger(this.focusedTriggerIndex);
|
|
9443
|
-
} else if (key === "Enter" || key === " ") {
|
|
10086
|
+
} else if (key === "Enter" || key === " " || key === "ArrowDown") {
|
|
9444
10087
|
event.preventDefault();
|
|
9445
10088
|
const trigger = this.querySelector(
|
|
9446
10089
|
`.menubar-trigger[data-index="${this.focusedTriggerIndex}"]`
|
|
9447
10090
|
);
|
|
9448
10091
|
if (trigger) {
|
|
9449
|
-
this.
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
);
|
|
9454
|
-
if (focusTarget) {
|
|
9455
|
-
focusTarget.focus();
|
|
9456
|
-
} else {
|
|
9457
|
-
this.focusMenuItem(0);
|
|
9458
|
-
}
|
|
9459
|
-
});
|
|
10092
|
+
if (this.openMenuIndex !== this.focusedTriggerIndex) {
|
|
10093
|
+
this.openMenu(this.focusedTriggerIndex, trigger);
|
|
10094
|
+
}
|
|
10095
|
+
this.updateComplete.then(() => this.focusMenuItem(0));
|
|
9460
10096
|
}
|
|
9461
|
-
} else if (key === "
|
|
10097
|
+
} else if (key === "Escape" && this.openMenuIndex !== -1) {
|
|
9462
10098
|
event.preventDefault();
|
|
9463
|
-
this.
|
|
10099
|
+
this.closeAllMenusAndRefocusTrigger();
|
|
9464
10100
|
}
|
|
9465
10101
|
}
|
|
9466
10102
|
focusTrigger(index) {
|
|
@@ -9473,6 +10109,36 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9473
10109
|
const { key } = event;
|
|
9474
10110
|
const menuContent = this.menus[this.openMenuIndex]?.menu;
|
|
9475
10111
|
if (!menuContent) return;
|
|
10112
|
+
if (this.openSubmenuElement) {
|
|
10113
|
+
const subItems = this.getSubmenuItems(this.openSubmenuElement);
|
|
10114
|
+
if (key === "ArrowDown") {
|
|
10115
|
+
event.preventDefault();
|
|
10116
|
+
this.focusedSubItemIndex = Math.min(
|
|
10117
|
+
subItems.length - 1,
|
|
10118
|
+
this.focusedSubItemIndex + 1
|
|
10119
|
+
);
|
|
10120
|
+
} else if (key === "ArrowUp") {
|
|
10121
|
+
event.preventDefault();
|
|
10122
|
+
this.focusedSubItemIndex = Math.max(0, this.focusedSubItemIndex - 1);
|
|
10123
|
+
} else if (key === "Home") {
|
|
10124
|
+
event.preventDefault();
|
|
10125
|
+
this.focusedSubItemIndex = 0;
|
|
10126
|
+
} else if (key === "End") {
|
|
10127
|
+
event.preventDefault();
|
|
10128
|
+
this.focusedSubItemIndex = subItems.length - 1;
|
|
10129
|
+
} else if (key === "ArrowLeft" || key === "Escape") {
|
|
10130
|
+
event.preventDefault();
|
|
10131
|
+
event.stopPropagation();
|
|
10132
|
+
this.openSubmenuElement = null;
|
|
10133
|
+
this.focusedSubItemIndex = -1;
|
|
10134
|
+
} else if (key === "Enter" || key === " ") {
|
|
10135
|
+
event.preventDefault();
|
|
10136
|
+
const subItem = subItems[this.focusedSubItemIndex];
|
|
10137
|
+
if (subItem) this.handleSubmenuItemClick(subItem);
|
|
10138
|
+
}
|
|
10139
|
+
this.requestUpdate();
|
|
10140
|
+
return;
|
|
10141
|
+
}
|
|
9476
10142
|
const items = this.getFocusableItems(menuContent);
|
|
9477
10143
|
if (key === "ArrowDown") {
|
|
9478
10144
|
event.preventDefault();
|
|
@@ -9482,30 +10148,56 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9482
10148
|
event.preventDefault();
|
|
9483
10149
|
this.focusedItemIndex = Math.max(0, this.focusedItemIndex - 1);
|
|
9484
10150
|
this.requestUpdate();
|
|
10151
|
+
} else if (key === "Home") {
|
|
10152
|
+
event.preventDefault();
|
|
10153
|
+
this.focusedItemIndex = 0;
|
|
10154
|
+
this.requestUpdate();
|
|
10155
|
+
} else if (key === "End") {
|
|
10156
|
+
event.preventDefault();
|
|
10157
|
+
this.focusedItemIndex = items.length - 1;
|
|
10158
|
+
this.requestUpdate();
|
|
9485
10159
|
} else if (key === "ArrowRight") {
|
|
9486
10160
|
event.preventDefault();
|
|
9487
10161
|
const currentItem = items[this.focusedItemIndex];
|
|
9488
10162
|
if (currentItem?.hasAttribute("data-submenu")) {
|
|
9489
|
-
this.
|
|
10163
|
+
const panel = this.querySelector(
|
|
10164
|
+
`.menubar-panel[data-menu-index="${this.openMenuIndex}"]`
|
|
10165
|
+
);
|
|
10166
|
+
const renderedTrigger = panel?.querySelector(
|
|
10167
|
+
`[data-focus-index="${this.focusedItemIndex}"]`
|
|
10168
|
+
);
|
|
10169
|
+
this.focusedSubItemIndex = 0;
|
|
10170
|
+
this.openSubmenu(currentItem, renderedTrigger ?? void 0);
|
|
9490
10171
|
}
|
|
9491
10172
|
} else if (key === "ArrowLeft") {
|
|
9492
10173
|
event.preventDefault();
|
|
9493
|
-
if (this.openSubmenuElement) {
|
|
9494
|
-
this.openSubmenuElement = null;
|
|
9495
|
-
this.requestUpdate();
|
|
9496
|
-
}
|
|
9497
10174
|
} else if (key === "Escape") {
|
|
9498
10175
|
event.preventDefault();
|
|
9499
|
-
this.
|
|
9500
|
-
this.focusTrigger(this.openMenuIndex);
|
|
10176
|
+
this.closeAllMenusAndRefocusTrigger();
|
|
9501
10177
|
} else if (key === "Enter" || key === " ") {
|
|
9502
10178
|
event.preventDefault();
|
|
9503
10179
|
const currentItem = items[this.focusedItemIndex];
|
|
9504
10180
|
if (currentItem) {
|
|
9505
|
-
|
|
10181
|
+
if (currentItem.hasAttribute("data-submenu")) {
|
|
10182
|
+
const panel = this.querySelector(
|
|
10183
|
+
`.menubar-panel[data-menu-index="${this.openMenuIndex}"]`
|
|
10184
|
+
);
|
|
10185
|
+
const renderedTrigger = panel?.querySelector(
|
|
10186
|
+
`[data-focus-index="${this.focusedItemIndex}"]`
|
|
10187
|
+
);
|
|
10188
|
+
this.focusedSubItemIndex = 0;
|
|
10189
|
+
this.openSubmenu(currentItem, renderedTrigger ?? void 0);
|
|
10190
|
+
} else {
|
|
10191
|
+
this.handleItemClick(currentItem);
|
|
10192
|
+
}
|
|
9506
10193
|
}
|
|
9507
10194
|
}
|
|
9508
10195
|
}
|
|
10196
|
+
getSubmenuItems(submenuElement) {
|
|
10197
|
+
return Array.from(submenuElement.children).filter(
|
|
10198
|
+
(el) => el.hasAttribute("data-item")
|
|
10199
|
+
);
|
|
10200
|
+
}
|
|
9509
10201
|
getFocusableItems(menuContent) {
|
|
9510
10202
|
return Array.from(menuContent.children).filter(
|
|
9511
10203
|
(el) => el.hasAttribute("data-item") || el.hasAttribute("data-checkbox") || el.hasAttribute("data-radio") || el.hasAttribute("data-submenu")
|
|
@@ -9542,27 +10234,41 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9542
10234
|
} else if (item.hasAttribute("data-submenu")) {
|
|
9543
10235
|
this.openSubmenu(item);
|
|
9544
10236
|
} else {
|
|
9545
|
-
this.
|
|
10237
|
+
this.dispatchSelectEvent(item);
|
|
10238
|
+
this.closeAllMenusAndRefocusTrigger();
|
|
9546
10239
|
}
|
|
9547
10240
|
}
|
|
9548
|
-
|
|
9549
|
-
this.
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
10241
|
+
dispatchSelectEvent(item) {
|
|
10242
|
+
this.dispatchEvent(
|
|
10243
|
+
new CustomEvent("rel-menubar-select", {
|
|
10244
|
+
detail: {
|
|
10245
|
+
label: item.textContent?.trim() ?? "",
|
|
10246
|
+
value: item.getAttribute("value") ?? void 0,
|
|
10247
|
+
shortcut: item.getAttribute("data-shortcut") ?? void 0
|
|
10248
|
+
},
|
|
10249
|
+
bubbles: true,
|
|
10250
|
+
composed: true
|
|
10251
|
+
})
|
|
10252
|
+
);
|
|
10253
|
+
}
|
|
10254
|
+
openSubmenu(submenuTrigger, renderedTrigger) {
|
|
10255
|
+
this.openSubmenuElement = submenuTrigger;
|
|
10256
|
+
this.requestUpdate();
|
|
10257
|
+
this.updateComplete.then(() => {
|
|
10258
|
+
const submenuPanel = this.querySelector(".submenu-panel.open");
|
|
10259
|
+
if (submenuPanel) {
|
|
10260
|
+
const positionTarget = renderedTrigger ?? submenuPanel.previousElementSibling;
|
|
10261
|
+
const rect = positionTarget?.getBoundingClientRect();
|
|
10262
|
+
if (rect) {
|
|
10263
|
+
submenuPanel.style.top = `${rect.top}px`;
|
|
10264
|
+
submenuPanel.style.left = `${rect.right + 4}px`;
|
|
10265
|
+
}
|
|
9561
10266
|
}
|
|
9562
10267
|
});
|
|
9563
10268
|
}
|
|
9564
|
-
handleSubmenuItemClick(
|
|
9565
|
-
this.
|
|
10269
|
+
handleSubmenuItemClick(item) {
|
|
10270
|
+
this.dispatchSelectEvent(item);
|
|
10271
|
+
this.closeAllMenusAndRefocusTrigger();
|
|
9566
10272
|
}
|
|
9567
10273
|
dispatchChangeEvent(type, labelOrName, value) {
|
|
9568
10274
|
const detail = type === "checkbox" ? { type, label: labelOrName, name: "", checked: value } : { type, label: "", name: labelOrName, value };
|
|
@@ -9574,16 +10280,21 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9574
10280
|
})
|
|
9575
10281
|
);
|
|
9576
10282
|
}
|
|
9577
|
-
renderMenuItem(item,
|
|
9578
|
-
const isFocused =
|
|
10283
|
+
renderMenuItem(item, focusIndex, menuIndex) {
|
|
10284
|
+
const isFocused = focusIndex >= 0 && focusIndex === this.focusedItemIndex;
|
|
10285
|
+
const itemId = focusIndex >= 0 ? `${this.menubarId}-m${menuIndex}-i${focusIndex}` : import_lit25.nothing;
|
|
9579
10286
|
if (item.hasAttribute("data-item")) {
|
|
9580
10287
|
const button = item;
|
|
9581
10288
|
const disabled = button.disabled;
|
|
9582
10289
|
const shortcut = button.getAttribute("data-shortcut") || "";
|
|
9583
10290
|
return import_lit25.html`
|
|
9584
10291
|
<div
|
|
10292
|
+
id="${itemId}"
|
|
9585
10293
|
class="menu-item ${isFocused ? "focused" : ""}"
|
|
10294
|
+
role="menuitem"
|
|
10295
|
+
data-focus-index="${focusIndex}"
|
|
9586
10296
|
data-disabled="${disabled}"
|
|
10297
|
+
aria-disabled="${disabled ? "true" : import_lit25.nothing}"
|
|
9587
10298
|
@click="${() => !disabled && this.handleItemClick(item)}"
|
|
9588
10299
|
>
|
|
9589
10300
|
<span>${button.textContent}</span>
|
|
@@ -9592,7 +10303,7 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9592
10303
|
`;
|
|
9593
10304
|
}
|
|
9594
10305
|
if (item.hasAttribute("data-separator")) {
|
|
9595
|
-
return import_lit25.html`<div class="menu-separator"></div>`;
|
|
10306
|
+
return import_lit25.html`<div class="menu-separator" role="separator"></div>`;
|
|
9596
10307
|
}
|
|
9597
10308
|
if (item.hasAttribute("data-checkbox")) {
|
|
9598
10309
|
const label = item;
|
|
@@ -9600,6 +10311,8 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9600
10311
|
const labelText = Array.from(label.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE).map((n) => n.textContent?.trim()).filter(Boolean).join(" ") || label.getAttribute("label") || "";
|
|
9601
10312
|
return import_lit25.html`
|
|
9602
10313
|
<rel-checkbox
|
|
10314
|
+
id="${itemId}"
|
|
10315
|
+
data-focus-index="${focusIndex}"
|
|
9603
10316
|
label=${labelText}
|
|
9604
10317
|
.status=${checkbox?.checked ? "checked" : "unchecked"}
|
|
9605
10318
|
@rel-change=${(e) => {
|
|
@@ -9611,17 +10324,21 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9611
10324
|
}
|
|
9612
10325
|
if (item.hasAttribute("data-submenu")) {
|
|
9613
10326
|
const label = item.getAttribute("label") || item.textContent?.trim() || "";
|
|
9614
|
-
const submenuId = `submenu-${index}`;
|
|
9615
10327
|
return import_lit25.html`
|
|
9616
10328
|
<div
|
|
10329
|
+
id="${itemId}"
|
|
9617
10330
|
class="menu-item menu-submenu-trigger ${isFocused ? "focused" : ""}"
|
|
9618
|
-
|
|
9619
|
-
|
|
10331
|
+
role="menuitem"
|
|
10332
|
+
data-focus-index="${focusIndex}"
|
|
10333
|
+
aria-haspopup="menu"
|
|
10334
|
+
aria-expanded="${this.openSubmenuElement === item}"
|
|
10335
|
+
@click="${(e) => this.openSubmenu(item, e.currentTarget)}"
|
|
10336
|
+
@mouseenter="${(e) => this.openSubmenu(item, e.currentTarget)}"
|
|
9620
10337
|
>
|
|
9621
10338
|
<span>${label}</span>
|
|
9622
10339
|
<rel-icon name="chevron-right"></rel-icon>
|
|
9623
10340
|
</div>
|
|
9624
|
-
${this.renderSubmenu(item
|
|
10341
|
+
${this.renderSubmenu(item)}
|
|
9625
10342
|
`;
|
|
9626
10343
|
}
|
|
9627
10344
|
if (item.hasAttribute("data-radio-group")) {
|
|
@@ -9646,24 +10363,23 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9646
10363
|
}
|
|
9647
10364
|
return null;
|
|
9648
10365
|
}
|
|
9649
|
-
renderSubmenu(submenuElement
|
|
10366
|
+
renderSubmenu(submenuElement) {
|
|
9650
10367
|
const isOpen = this.openSubmenuElement === submenuElement;
|
|
9651
|
-
const submenuItems =
|
|
10368
|
+
const submenuItems = this.getSubmenuItems(submenuElement);
|
|
9652
10369
|
return import_lit25.html`
|
|
9653
|
-
<div class="submenu-panel ${isOpen ? "open" : ""}"
|
|
9654
|
-
${submenuItems.map((subItem) => {
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
</
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
return null;
|
|
10370
|
+
<div class="submenu-panel ${isOpen ? "open" : ""}" role="menu">
|
|
10371
|
+
${submenuItems.map((subItem, subIdx) => {
|
|
10372
|
+
const button = subItem;
|
|
10373
|
+
return import_lit25.html`
|
|
10374
|
+
<div
|
|
10375
|
+
id="${isOpen ? `${this.menubarId}-sub-${subIdx}` : import_lit25.nothing}"
|
|
10376
|
+
class="menu-item ${isOpen && subIdx === this.focusedSubItemIndex ? "focused" : ""}"
|
|
10377
|
+
role="menuitem"
|
|
10378
|
+
@click="${() => this.handleSubmenuItemClick(subItem)}"
|
|
10379
|
+
>
|
|
10380
|
+
<span>${button.textContent}</span>
|
|
10381
|
+
</div>
|
|
10382
|
+
`;
|
|
9667
10383
|
})}
|
|
9668
10384
|
</div>
|
|
9669
10385
|
`;
|
|
@@ -9671,28 +10387,40 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9671
10387
|
renderMenuPanel(menuState, index) {
|
|
9672
10388
|
const isOpen = index === this.openMenuIndex;
|
|
9673
10389
|
const items = Array.from(menuState.menu.children);
|
|
10390
|
+
const focusables = this.getFocusableItems(menuState.menu);
|
|
10391
|
+
const activeDescendant = !isOpen ? import_lit25.nothing : this.focusedSubItemIndex >= 0 ? `${this.menubarId}-sub-${this.focusedSubItemIndex}` : this.focusedItemIndex >= 0 ? `${this.menubarId}-m${index}-i${this.focusedItemIndex}` : import_lit25.nothing;
|
|
9674
10392
|
return import_lit25.html`
|
|
9675
10393
|
<div
|
|
9676
10394
|
class="menubar-panel ${isOpen ? "open" : ""}"
|
|
9677
10395
|
data-menu-index="${index}"
|
|
9678
10396
|
tabindex="-1"
|
|
10397
|
+
role="menu"
|
|
10398
|
+
aria-activedescendant="${activeDescendant}"
|
|
9679
10399
|
@keydown="${this.handlePanelKeyDown}"
|
|
9680
10400
|
>
|
|
9681
|
-
${items.map(
|
|
10401
|
+
${items.map(
|
|
10402
|
+
(item) => this.renderMenuItem(item, focusables.indexOf(item), index)
|
|
10403
|
+
)}
|
|
9682
10404
|
</div>
|
|
9683
10405
|
`;
|
|
9684
10406
|
}
|
|
9685
10407
|
render() {
|
|
9686
10408
|
return import_lit25.html`
|
|
9687
10409
|
<style>${RelMenubar._css}</style>
|
|
9688
|
-
<div class="menubar-container">
|
|
10410
|
+
<div class="menubar-container" role="menubar">
|
|
9689
10411
|
${this.menus.map(
|
|
9690
10412
|
(menuState, index) => import_lit25.html`
|
|
9691
10413
|
<button
|
|
9692
10414
|
class="menubar-trigger ${index === this.openMenuIndex ? "active" : ""}"
|
|
9693
10415
|
data-index="${index}"
|
|
10416
|
+
role="menuitem"
|
|
10417
|
+
aria-haspopup="menu"
|
|
10418
|
+
aria-expanded="${index === this.openMenuIndex}"
|
|
9694
10419
|
@click="${(e) => this.handleTriggerClick(index, e)}"
|
|
9695
10420
|
@mouseenter="${(e) => this.handleTriggerMouseEnter(index, e)}"
|
|
10421
|
+
@focus="${() => {
|
|
10422
|
+
this.focusedTriggerIndex = index;
|
|
10423
|
+
}}"
|
|
9696
10424
|
@keydown="${this.handleTriggerKeyDown}"
|
|
9697
10425
|
>
|
|
9698
10426
|
${menuState.menu.label}
|
|
@@ -9863,6 +10591,9 @@ RelMenubar._css = `
|
|
|
9863
10591
|
cursor: pointer;
|
|
9864
10592
|
}
|
|
9865
10593
|
`;
|
|
10594
|
+
__decorateClass([
|
|
10595
|
+
(0, import_decorators25.property)({ type: Boolean, attribute: "bind-shortcuts" })
|
|
10596
|
+
], RelMenubar.prototype, "bindShortcuts", 2);
|
|
9866
10597
|
__decorateClass([
|
|
9867
10598
|
(0, import_decorators25.state)()
|
|
9868
10599
|
], RelMenubar.prototype, "menus", 2);
|
|
@@ -9875,6 +10606,9 @@ __decorateClass([
|
|
|
9875
10606
|
__decorateClass([
|
|
9876
10607
|
(0, import_decorators25.state)()
|
|
9877
10608
|
], RelMenubar.prototype, "focusedItemIndex", 2);
|
|
10609
|
+
__decorateClass([
|
|
10610
|
+
(0, import_decorators25.state)()
|
|
10611
|
+
], RelMenubar.prototype, "focusedSubItemIndex", 2);
|
|
9878
10612
|
__decorateClass([
|
|
9879
10613
|
(0, import_decorators25.state)()
|
|
9880
10614
|
], RelMenubar.prototype, "openSubmenuElement", 2);
|
|
@@ -10543,7 +11277,6 @@ RelTopbar.styles = import_lit34.css`
|
|
|
10543
11277
|
color: var(--rel-topbar-item-color);
|
|
10544
11278
|
|
|
10545
11279
|
|
|
10546
|
-
|
|
10547
11280
|
text-decoration: none;
|
|
10548
11281
|
font-family: var(--rel-body-md-medium-font-family);
|
|
10549
11282
|
font-weight: var(--rel-body-md-medium-font-weight);
|
|
@@ -10793,8 +11526,6 @@ RelTopbarItem.styles = import_lit35.css`
|
|
|
10793
11526
|
color: var(--_item-color);
|
|
10794
11527
|
|
|
10795
11528
|
|
|
10796
|
-
|
|
10797
|
-
|
|
10798
11529
|
text-decoration: none;
|
|
10799
11530
|
white-space: nowrap;
|
|
10800
11531
|
position: relative;
|
|
@@ -11026,9 +11757,7 @@ RelTopbarProfile.styles = import_lit36.css`
|
|
|
11026
11757
|
|
|
11027
11758
|
.name {
|
|
11028
11759
|
|
|
11029
|
-
|
|
11030
11760
|
color: var(--rel-text-default);
|
|
11031
|
-
|
|
11032
11761
|
white-space: nowrap;
|
|
11033
11762
|
overflow: hidden;
|
|
11034
11763
|
text-overflow: ellipsis;
|
|
@@ -11047,9 +11776,7 @@ RelTopbarProfile.styles = import_lit36.css`
|
|
|
11047
11776
|
|
|
11048
11777
|
.subtitle {
|
|
11049
11778
|
|
|
11050
|
-
|
|
11051
11779
|
color: var(--rel-text-placeholder);
|
|
11052
|
-
|
|
11053
11780
|
white-space: nowrap;
|
|
11054
11781
|
overflow: hidden;
|
|
11055
11782
|
text-overflow: ellipsis;
|
|
@@ -11074,9 +11801,7 @@ RelTopbarProfile.styles = import_lit36.css`
|
|
|
11074
11801
|
padding: 2px var(--rel-spacing-2xs, 4px);
|
|
11075
11802
|
border-radius: var(--rel-border-radius-xs, 2px);
|
|
11076
11803
|
|
|
11077
|
-
|
|
11078
11804
|
text-transform: uppercase;
|
|
11079
|
-
|
|
11080
11805
|
flex-shrink: 0;
|
|
11081
11806
|
font-family: var(--rel-body-xs-semibold-font-family);
|
|
11082
11807
|
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
@@ -11423,7 +12148,7 @@ var RelOtp = class extends import_lit37.LitElement {
|
|
|
11423
12148
|
data-index="${index}"
|
|
11424
12149
|
type="${this.mask ? "password" : "text"}"
|
|
11425
12150
|
inputmode="${this.getInputMode()}"
|
|
11426
|
-
autocomplete="
|
|
12151
|
+
autocomplete="one-time-code"
|
|
11427
12152
|
pattern="${this.pattern || ""}"
|
|
11428
12153
|
placeholder="${this.placeholder}"
|
|
11429
12154
|
maxlength="1"
|
|
@@ -11486,7 +12211,6 @@ RelOtp.styles = import_lit37.css`
|
|
|
11486
12211
|
align-items: center;
|
|
11487
12212
|
justify-content: center;
|
|
11488
12213
|
color: var(--rel-border-default);
|
|
11489
|
-
|
|
11490
12214
|
user-select: none;
|
|
11491
12215
|
padding: 0 var(--rel-spacing-3xs);
|
|
11492
12216
|
font-family: var(--rel-action-xl-font-family);
|
|
@@ -11507,7 +12231,6 @@ RelOtp.styles = import_lit37.css`
|
|
|
11507
12231
|
border-radius: var(--rel-border-radius-sm);
|
|
11508
12232
|
background: var(--rel-bg-neutral);
|
|
11509
12233
|
|
|
11510
|
-
|
|
11511
12234
|
text-align: center;
|
|
11512
12235
|
color: var(--rel-text-default);
|
|
11513
12236
|
outline: none;
|
|
@@ -12199,6 +12922,14 @@ var RelPopover = class extends import_lit39.LitElement {
|
|
|
12199
12922
|
e.stopPropagation();
|
|
12200
12923
|
this.setOpen(!this.activeOpen);
|
|
12201
12924
|
};
|
|
12925
|
+
this.onHostKeyDown = (e) => {
|
|
12926
|
+
if (this.disabled) return;
|
|
12927
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
12928
|
+
if (!this.triggerEl || !e.composedPath().includes(this.triggerEl)) return;
|
|
12929
|
+
e.preventDefault();
|
|
12930
|
+
e.stopPropagation();
|
|
12931
|
+
this.setOpen(!this.activeOpen);
|
|
12932
|
+
};
|
|
12202
12933
|
this.onOutsidePointerDown = (e) => {
|
|
12203
12934
|
if (!this.activeOpen) return;
|
|
12204
12935
|
const path = e.composedPath();
|
|
@@ -12272,10 +13003,12 @@ var RelPopover = class extends import_lit39.LitElement {
|
|
|
12272
13003
|
super.connectedCallback();
|
|
12273
13004
|
this.internalOpen = this.isControlled() ? !!this.open : this.defaultOpen;
|
|
12274
13005
|
this.addEventListener("pointerdown", this.onHostPointerDown, true);
|
|
13006
|
+
this.addEventListener("keydown", this.onHostKeyDown, true);
|
|
12275
13007
|
}
|
|
12276
13008
|
disconnectedCallback() {
|
|
12277
13009
|
super.disconnectedCallback();
|
|
12278
13010
|
this.removeEventListener("pointerdown", this.onHostPointerDown, true);
|
|
13011
|
+
this.removeEventListener("keydown", this.onHostKeyDown, true);
|
|
12279
13012
|
this.removeGlobalListeners();
|
|
12280
13013
|
this.hideNativePopover();
|
|
12281
13014
|
}
|
|
@@ -12316,7 +13049,7 @@ var RelPopover = class extends import_lit39.LitElement {
|
|
|
12316
13049
|
* OPEN / CLOSE
|
|
12317
13050
|
* ======================= */
|
|
12318
13051
|
onOpen() {
|
|
12319
|
-
this.returnFocusEl =
|
|
13052
|
+
this.returnFocusEl = getDeepActiveElement() ?? this.triggerEl;
|
|
12320
13053
|
this.addGlobalListeners();
|
|
12321
13054
|
this.showNativePopover();
|
|
12322
13055
|
requestAnimationFrame(() => {
|
|
@@ -12424,6 +13157,12 @@ var RelPopover = class extends import_lit39.LitElement {
|
|
|
12424
13157
|
this.triggerEl.setAttribute("aria-haspopup", "dialog");
|
|
12425
13158
|
this.triggerEl.setAttribute("aria-expanded", String(this.activeOpen));
|
|
12426
13159
|
this.triggerEl.setAttribute("aria-controls", this.uniqueId);
|
|
13160
|
+
if (this.triggerEl.tabIndex < 0 && !this.triggerEl.hasAttribute("tabindex") && getFocusableElements(this.triggerEl).length === 0) {
|
|
13161
|
+
this.triggerEl.setAttribute("tabindex", "0");
|
|
13162
|
+
if (!this.triggerEl.hasAttribute("role")) {
|
|
13163
|
+
this.triggerEl.setAttribute("role", "button");
|
|
13164
|
+
}
|
|
13165
|
+
}
|
|
12427
13166
|
}
|
|
12428
13167
|
/* =======================
|
|
12429
13168
|
* RENDER
|
|
@@ -12903,6 +13642,14 @@ var RelResizableGroup = class extends import_lit42.LitElement {
|
|
|
12903
13642
|
const delta = e.key === "ArrowUp" ? -step : step;
|
|
12904
13643
|
this.applyKeyboardResize(handleIndex, delta);
|
|
12905
13644
|
applied = true;
|
|
13645
|
+
} else if (e.key === "Home" || e.key === "End") {
|
|
13646
|
+
const leftPanel = this.panels[handleIndex];
|
|
13647
|
+
if (leftPanel && handleIndex + 1 < this.panels.length) {
|
|
13648
|
+
e.preventDefault();
|
|
13649
|
+
const target2 = e.key === "Home" ? leftPanel.minSize : leftPanel.maxSize;
|
|
13650
|
+
this.applyKeyboardResize(handleIndex, target2 - leftPanel.currentSize);
|
|
13651
|
+
applied = true;
|
|
13652
|
+
}
|
|
12906
13653
|
}
|
|
12907
13654
|
if (applied) {
|
|
12908
13655
|
this.dispatchResizeEvent("rel-resize-end");
|
|
@@ -12942,6 +13689,13 @@ var RelResizableGroup = class extends import_lit42.LitElement {
|
|
|
12942
13689
|
});
|
|
12943
13690
|
const sizes = this.calculateInitialSizes();
|
|
12944
13691
|
this.applySizes(sizes);
|
|
13692
|
+
this.handles.forEach((handle, idx) => {
|
|
13693
|
+
const left = sizes[idx];
|
|
13694
|
+
const right = sizes[idx + 1];
|
|
13695
|
+
if (left !== void 0 && right !== void 0 && left + right > 0) {
|
|
13696
|
+
handle.updateAriaValue(left / (left + right) * 100);
|
|
13697
|
+
}
|
|
13698
|
+
});
|
|
12945
13699
|
}
|
|
12946
13700
|
calculateInitialSizes() {
|
|
12947
13701
|
const sizes = [];
|
|
@@ -13320,6 +14074,26 @@ var RelSegmentedButton = class extends import_lit45.LitElement {
|
|
|
13320
14074
|
this.active = "";
|
|
13321
14075
|
this.size = "medium";
|
|
13322
14076
|
this.align = "left";
|
|
14077
|
+
/** APG radio-group navigation: arrows move focus AND select, the group is
|
|
14078
|
+
* one Tab stop (roving tabindex on the selected segment). */
|
|
14079
|
+
this.onKeyDown = (e) => {
|
|
14080
|
+
const prevKey = e.key === "ArrowLeft" || e.key === "ArrowUp";
|
|
14081
|
+
const nextKey = e.key === "ArrowRight" || e.key === "ArrowDown";
|
|
14082
|
+
if (!prevKey && !nextKey && e.key !== "Home" && e.key !== "End") return;
|
|
14083
|
+
const enabled = this.options.filter((o) => !o.disabled);
|
|
14084
|
+
if (enabled.length === 0) return;
|
|
14085
|
+
e.preventDefault();
|
|
14086
|
+
const currentIdx = enabled.findIndex((o) => o.value === this.active);
|
|
14087
|
+
let target;
|
|
14088
|
+
if (e.key === "Home") target = 0;
|
|
14089
|
+
else if (e.key === "End") target = enabled.length - 1;
|
|
14090
|
+
else if (prevKey) target = currentIdx <= 0 ? enabled.length - 1 : currentIdx - 1;
|
|
14091
|
+
else target = (currentIdx + 1) % enabled.length;
|
|
14092
|
+
this.setActive(enabled[target]);
|
|
14093
|
+
this.updateComplete.then(() => {
|
|
14094
|
+
this.shadowRoot?.querySelector('button[tabindex="0"]')?.focus();
|
|
14095
|
+
});
|
|
14096
|
+
};
|
|
13323
14097
|
}
|
|
13324
14098
|
setActive(option) {
|
|
13325
14099
|
if (!option.disabled) {
|
|
@@ -13333,7 +14107,7 @@ var RelSegmentedButton = class extends import_lit45.LitElement {
|
|
|
13333
14107
|
);
|
|
13334
14108
|
}
|
|
13335
14109
|
}
|
|
13336
|
-
renderSegment(option) {
|
|
14110
|
+
renderSegment(option, rovingValue) {
|
|
13337
14111
|
const isActive = this.active === option.value;
|
|
13338
14112
|
const classes = [
|
|
13339
14113
|
"segment",
|
|
@@ -13343,6 +14117,9 @@ var RelSegmentedButton = class extends import_lit45.LitElement {
|
|
|
13343
14117
|
return import_lit45.html`
|
|
13344
14118
|
<button
|
|
13345
14119
|
part="option" class="${classes}"
|
|
14120
|
+
role="radio"
|
|
14121
|
+
aria-checked="${isActive}"
|
|
14122
|
+
tabindex="${!option.disabled && option.value === rovingValue ? 0 : -1}"
|
|
13346
14123
|
?disabled="${option.disabled}"
|
|
13347
14124
|
@click="${() => this.setActive(option)}">
|
|
13348
14125
|
${option.label}
|
|
@@ -13355,9 +14132,15 @@ var RelSegmentedButton = class extends import_lit45.LitElement {
|
|
|
13355
14132
|
this.size,
|
|
13356
14133
|
`align-${this.align}`
|
|
13357
14134
|
].join(" ");
|
|
14135
|
+
const enabled = this.options.filter((o) => !o.disabled);
|
|
14136
|
+
const rovingValue = enabled.some((o) => o.value === this.active) ? this.active : enabled[0]?.value ?? null;
|
|
13358
14137
|
return import_lit45.html`
|
|
13359
|
-
<div
|
|
13360
|
-
${
|
|
14138
|
+
<div
|
|
14139
|
+
part="base" class="${containerClasses}"
|
|
14140
|
+
role="radiogroup"
|
|
14141
|
+
@keydown="${this.onKeyDown}"
|
|
14142
|
+
>
|
|
14143
|
+
${this.options.map((option) => this.renderSegment(option, rovingValue))}
|
|
13361
14144
|
</div>
|
|
13362
14145
|
`;
|
|
13363
14146
|
}
|
|
@@ -13519,6 +14302,8 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13519
14302
|
this.noSearch = false;
|
|
13520
14303
|
this.open = false;
|
|
13521
14304
|
this.highlightedIndex = -1;
|
|
14305
|
+
this.selectAllHighlighted = false;
|
|
14306
|
+
this.typeaheadBuffer = "";
|
|
13522
14307
|
this.panelStyle = "";
|
|
13523
14308
|
this.searchValue = "";
|
|
13524
14309
|
this.internalState = "default";
|
|
@@ -13567,6 +14352,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13567
14352
|
this.onSearchInput = (e) => {
|
|
13568
14353
|
this.searchValue = e.target.value;
|
|
13569
14354
|
this.highlightedIndex = this.filteredOptions.length > 0 ? 0 : -1;
|
|
14355
|
+
this.selectAllHighlighted = false;
|
|
13570
14356
|
requestAnimationFrame(() => this.updatePosition());
|
|
13571
14357
|
};
|
|
13572
14358
|
}
|
|
@@ -13694,6 +14480,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13694
14480
|
this.open = true;
|
|
13695
14481
|
this.searchValue = "";
|
|
13696
14482
|
this.highlightedIndex = -1;
|
|
14483
|
+
this.selectAllHighlighted = false;
|
|
13697
14484
|
this.addGlobalListeners();
|
|
13698
14485
|
requestAnimationFrame(() => {
|
|
13699
14486
|
this.showNativePopover();
|
|
@@ -13769,6 +14556,11 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13769
14556
|
}
|
|
13770
14557
|
onTriggerKeyDown(e) {
|
|
13771
14558
|
if (this.state === "disabled") return;
|
|
14559
|
+
if (this.multiple && (e.key === "Backspace" || e.key === "Delete") && this.selectedValues.length > 0) {
|
|
14560
|
+
e.preventDefault();
|
|
14561
|
+
this.removeChip(this.selectedValues[this.selectedValues.length - 1]);
|
|
14562
|
+
return;
|
|
14563
|
+
}
|
|
13772
14564
|
if (!this.open) {
|
|
13773
14565
|
if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
|
|
13774
14566
|
e.preventDefault();
|
|
@@ -13779,26 +14571,60 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13779
14571
|
if (this.noSearch) this.handleListNavigation(e);
|
|
13780
14572
|
}
|
|
13781
14573
|
onSearchKeyDown(e) {
|
|
14574
|
+
if (this.multiple && e.key === "Backspace" && this.searchValue === "" && this.selectedValues.length > 0) {
|
|
14575
|
+
this.removeChip(this.selectedValues[this.selectedValues.length - 1]);
|
|
14576
|
+
return;
|
|
14577
|
+
}
|
|
13782
14578
|
this.handleListNavigation(e);
|
|
13783
14579
|
}
|
|
13784
14580
|
handleListNavigation(e) {
|
|
13785
14581
|
const filtered = this.filteredOptions;
|
|
14582
|
+
const hasSelectAll = this.multiple && filtered.length > 0;
|
|
13786
14583
|
switch (e.key) {
|
|
13787
14584
|
case "ArrowDown":
|
|
13788
14585
|
e.preventDefault();
|
|
13789
|
-
|
|
14586
|
+
if (this.selectAllHighlighted) {
|
|
14587
|
+
this.selectAllHighlighted = false;
|
|
14588
|
+
this.highlightedIndex = filtered.length > 0 ? 0 : -1;
|
|
14589
|
+
} else if (this.highlightedIndex === -1 && hasSelectAll) {
|
|
14590
|
+
this.selectAllHighlighted = true;
|
|
14591
|
+
} else {
|
|
14592
|
+
this.highlightedIndex = Math.min(filtered.length - 1, this.highlightedIndex + 1);
|
|
14593
|
+
}
|
|
14594
|
+
this.scrollHighlightedIntoView();
|
|
13790
14595
|
break;
|
|
13791
14596
|
case "ArrowUp":
|
|
13792
14597
|
e.preventDefault();
|
|
13793
|
-
this.
|
|
14598
|
+
if (!this.selectAllHighlighted && this.highlightedIndex <= 0 && hasSelectAll) {
|
|
14599
|
+
this.highlightedIndex = -1;
|
|
14600
|
+
this.selectAllHighlighted = true;
|
|
14601
|
+
} else {
|
|
14602
|
+
this.highlightedIndex = Math.max(0, this.highlightedIndex - 1);
|
|
14603
|
+
}
|
|
14604
|
+
this.scrollHighlightedIntoView();
|
|
14605
|
+
break;
|
|
14606
|
+
case "Home":
|
|
14607
|
+
if (filtered.length === 0) break;
|
|
14608
|
+
e.preventDefault();
|
|
14609
|
+
this.selectAllHighlighted = false;
|
|
14610
|
+
this.highlightedIndex = 0;
|
|
14611
|
+
this.scrollHighlightedIntoView();
|
|
14612
|
+
break;
|
|
14613
|
+
case "End":
|
|
14614
|
+
if (filtered.length === 0) break;
|
|
14615
|
+
e.preventDefault();
|
|
14616
|
+
this.selectAllHighlighted = false;
|
|
14617
|
+
this.highlightedIndex = filtered.length - 1;
|
|
14618
|
+
this.scrollHighlightedIntoView();
|
|
14619
|
+
break;
|
|
14620
|
+
case " ":
|
|
14621
|
+
if (!this.noSearch) return;
|
|
14622
|
+
e.preventDefault();
|
|
14623
|
+
this.activateHighlighted();
|
|
13794
14624
|
break;
|
|
13795
14625
|
case "Enter":
|
|
13796
14626
|
e.preventDefault();
|
|
13797
|
-
|
|
13798
|
-
const opt = filtered[this.highlightedIndex];
|
|
13799
|
-
if (this.multiple) this.toggleMultiOption(opt);
|
|
13800
|
-
else this.selectSingleOption(opt);
|
|
13801
|
-
}
|
|
14627
|
+
this.activateHighlighted();
|
|
13802
14628
|
break;
|
|
13803
14629
|
case "Escape":
|
|
13804
14630
|
e.preventDefault();
|
|
@@ -13807,8 +14633,55 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13807
14633
|
case "Tab":
|
|
13808
14634
|
this.closeListbox();
|
|
13809
14635
|
break;
|
|
14636
|
+
default:
|
|
14637
|
+
this.handleTypeahead(e);
|
|
14638
|
+
break;
|
|
14639
|
+
}
|
|
14640
|
+
}
|
|
14641
|
+
/** APG typeahead for the no-search mode (with a search field, typing
|
|
14642
|
+
* filters instead). Accumulates printable keys for 500 ms and jumps the
|
|
14643
|
+
* highlight to the next option whose label starts with the buffer. */
|
|
14644
|
+
handleTypeahead(e) {
|
|
14645
|
+
if (!this.noSearch) return;
|
|
14646
|
+
if (e.key.length !== 1 || e.ctrlKey || e.metaKey || e.altKey) return;
|
|
14647
|
+
e.preventDefault();
|
|
14648
|
+
const isNewQuery = this.typeaheadBuffer === "";
|
|
14649
|
+
this.typeaheadBuffer += e.key.toLowerCase();
|
|
14650
|
+
clearTimeout(this.typeaheadTimer);
|
|
14651
|
+
this.typeaheadTimer = window.setTimeout(() => {
|
|
14652
|
+
this.typeaheadBuffer = "";
|
|
14653
|
+
}, 500);
|
|
14654
|
+
const filtered = this.filteredOptions;
|
|
14655
|
+
if (filtered.length === 0) return;
|
|
14656
|
+
const start = Math.max(0, this.highlightedIndex) + (isNewQuery ? 1 : 0);
|
|
14657
|
+
for (let offset = 0; offset < filtered.length; offset++) {
|
|
14658
|
+
const idx = (start + offset) % filtered.length;
|
|
14659
|
+
if (filtered[idx].label.toLowerCase().startsWith(this.typeaheadBuffer)) {
|
|
14660
|
+
this.selectAllHighlighted = false;
|
|
14661
|
+
this.highlightedIndex = idx;
|
|
14662
|
+
this.scrollHighlightedIntoView();
|
|
14663
|
+
return;
|
|
14664
|
+
}
|
|
13810
14665
|
}
|
|
13811
14666
|
}
|
|
14667
|
+
activateHighlighted() {
|
|
14668
|
+
if (this.selectAllHighlighted) {
|
|
14669
|
+
this.toggleSelectAll();
|
|
14670
|
+
return;
|
|
14671
|
+
}
|
|
14672
|
+
const filtered = this.filteredOptions;
|
|
14673
|
+
if (this.highlightedIndex >= 0 && filtered[this.highlightedIndex]) {
|
|
14674
|
+
const opt = filtered[this.highlightedIndex];
|
|
14675
|
+
if (this.multiple) this.toggleMultiOption(opt);
|
|
14676
|
+
else this.selectSingleOption(opt);
|
|
14677
|
+
}
|
|
14678
|
+
}
|
|
14679
|
+
scrollHighlightedIntoView() {
|
|
14680
|
+
if (this.highlightedIndex < 0) return;
|
|
14681
|
+
requestAnimationFrame(() => {
|
|
14682
|
+
this.renderRoot.querySelector(`#${this.uniqueId}-option-${this.highlightedIndex}`)?.scrollIntoView({ block: "nearest" });
|
|
14683
|
+
});
|
|
14684
|
+
}
|
|
13812
14685
|
renderChips() {
|
|
13813
14686
|
const values = this.selectedValues;
|
|
13814
14687
|
const visible = values.slice(0, Math.min(this.visibleChipCount, this.maxVisibleChips));
|
|
@@ -13886,7 +14759,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13886
14759
|
this.open ? "open" : ""
|
|
13887
14760
|
].filter(Boolean).join(" ");
|
|
13888
14761
|
const listboxId = `${this.uniqueId}-listbox`;
|
|
13889
|
-
const activeOptionId = this.open
|
|
14762
|
+
const activeOptionId = !this.open ? "" : this.selectAllHighlighted ? `${this.uniqueId}-select-all` : this.highlightedIndex >= 0 ? `${this.uniqueId}-option-${this.highlightedIndex}` : "";
|
|
13890
14763
|
const selectedSingle = opts.find((o) => o.value === this.selected);
|
|
13891
14764
|
return import_lit46.html`
|
|
13892
14765
|
<div class="${wrapperClasses}">
|
|
@@ -13964,7 +14837,17 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13964
14837
|
/>
|
|
13965
14838
|
</div>
|
|
13966
14839
|
</div>` : ""}
|
|
13967
|
-
${this.multiple && filtered.length > 0 ? import_lit46.html`<div
|
|
14840
|
+
${this.multiple && filtered.length > 0 ? import_lit46.html`<div
|
|
14841
|
+
id="${this.uniqueId}-select-all"
|
|
14842
|
+
class="select-all-row ${this.selectAllHighlighted ? "highlighted" : ""}"
|
|
14843
|
+
role="option"
|
|
14844
|
+
aria-selected="${this.selectAllStatus === "checked"}"
|
|
14845
|
+
@pointerenter="${() => {
|
|
14846
|
+
this.selectAllHighlighted = true;
|
|
14847
|
+
this.highlightedIndex = -1;
|
|
14848
|
+
}}"
|
|
14849
|
+
@pointerleave="${() => this.selectAllHighlighted = false}"
|
|
14850
|
+
>
|
|
13968
14851
|
<rel-checkbox
|
|
13969
14852
|
label="${this.selectAllLabel}"
|
|
13970
14853
|
status="${this.selectAllStatus}"
|
|
@@ -13989,7 +14872,10 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13989
14872
|
class="checkbox-option ${index === this.highlightedIndex ? "highlighted" : ""}"
|
|
13990
14873
|
role="option"
|
|
13991
14874
|
aria-selected="${this.selectedValues.includes(option.value)}"
|
|
13992
|
-
@pointerenter="${() =>
|
|
14875
|
+
@pointerenter="${() => {
|
|
14876
|
+
this.highlightedIndex = index;
|
|
14877
|
+
this.selectAllHighlighted = false;
|
|
14878
|
+
}}"
|
|
13993
14879
|
>
|
|
13994
14880
|
<rel-checkbox
|
|
13995
14881
|
label="${option.label}"
|
|
@@ -14488,6 +15374,10 @@ RelSelect.styles = import_lit46.css`
|
|
|
14488
15374
|
padding: 12px;
|
|
14489
15375
|
}
|
|
14490
15376
|
|
|
15377
|
+
.select-all-row.highlighted {
|
|
15378
|
+
background: var(--rel-select-option-hover, var(--rel-bg-neutral-elevated-1));
|
|
15379
|
+
}
|
|
15380
|
+
|
|
14491
15381
|
.select-empty {
|
|
14492
15382
|
padding: var(--rel-spacing-xs) 12px;
|
|
14493
15383
|
color: var(--rel-select-placeholder-color, var(--rel-text-body));
|
|
@@ -14562,6 +15452,9 @@ __decorateClass([
|
|
|
14562
15452
|
__decorateClass([
|
|
14563
15453
|
(0, import_decorators46.state)()
|
|
14564
15454
|
], RelSelect.prototype, "highlightedIndex", 2);
|
|
15455
|
+
__decorateClass([
|
|
15456
|
+
(0, import_decorators46.state)()
|
|
15457
|
+
], RelSelect.prototype, "selectAllHighlighted", 2);
|
|
14565
15458
|
__decorateClass([
|
|
14566
15459
|
(0, import_decorators46.state)()
|
|
14567
15460
|
], RelSelect.prototype, "panelStyle", 2);
|
|
@@ -14759,6 +15652,10 @@ var RelModal = class extends import_lit48.LitElement {
|
|
|
14759
15652
|
if (e.key === "Escape" && this.dismissible) {
|
|
14760
15653
|
e.preventDefault();
|
|
14761
15654
|
this.open = false;
|
|
15655
|
+
return;
|
|
15656
|
+
}
|
|
15657
|
+
if (e.key === "Tab" && this.modal && this.panelElement) {
|
|
15658
|
+
trapTabKey(e, this.panelElement);
|
|
14762
15659
|
}
|
|
14763
15660
|
};
|
|
14764
15661
|
this.handleOverlayClick = (e) => {
|
|
@@ -14809,9 +15706,18 @@ var RelModal = class extends import_lit48.LitElement {
|
|
|
14809
15706
|
}
|
|
14810
15707
|
}
|
|
14811
15708
|
handleOpen() {
|
|
14812
|
-
this.previousFocus =
|
|
15709
|
+
this.previousFocus = getDeepActiveElement();
|
|
14813
15710
|
this.lockScroll();
|
|
14814
15711
|
document.addEventListener("keydown", this.handleKeydown);
|
|
15712
|
+
if (this.modal) {
|
|
15713
|
+
this.updateComplete.then(() => {
|
|
15714
|
+
const panel = this.panelElement;
|
|
15715
|
+
if (!panel) return;
|
|
15716
|
+
const focusTarget = getFocusableElements(panel)[0];
|
|
15717
|
+
if (focusTarget) focusTarget.focus();
|
|
15718
|
+
else panel.focus();
|
|
15719
|
+
});
|
|
15720
|
+
}
|
|
14815
15721
|
this.dispatchEvent(
|
|
14816
15722
|
new CustomEvent("rel-open-change", {
|
|
14817
15723
|
detail: true,
|
|
@@ -15171,6 +16077,9 @@ __decorateClass([
|
|
|
15171
16077
|
__decorateClass([
|
|
15172
16078
|
(0, import_decorators48.query)(".overlay")
|
|
15173
16079
|
], RelModal.prototype, "overlayElement", 2);
|
|
16080
|
+
__decorateClass([
|
|
16081
|
+
(0, import_decorators48.query)(".panel")
|
|
16082
|
+
], RelModal.prototype, "panelElement", 2);
|
|
15174
16083
|
RelModal = __decorateClass([
|
|
15175
16084
|
(0, import_decorators48.customElement)("rel-modal")
|
|
15176
16085
|
], RelModal);
|
|
@@ -16186,6 +17095,28 @@ var RelStepper = class extends import_lit51.LitElement {
|
|
|
16186
17095
|
if (this.isDisabled(step)) classes.push("disabled");
|
|
16187
17096
|
return classes.join(" ");
|
|
16188
17097
|
}
|
|
17098
|
+
onStepKeyDown(e, step, index) {
|
|
17099
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
17100
|
+
e.preventDefault();
|
|
17101
|
+
this.onStepClick(step, index);
|
|
17102
|
+
return;
|
|
17103
|
+
}
|
|
17104
|
+
const prevKey = e.key === "ArrowLeft" || e.key === "ArrowUp";
|
|
17105
|
+
const nextKey = e.key === "ArrowRight" || e.key === "ArrowDown";
|
|
17106
|
+
if (!prevKey && !nextKey && e.key !== "Home" && e.key !== "End") return;
|
|
17107
|
+
const steps = Array.from(
|
|
17108
|
+
this.renderRoot.querySelectorAll(".rel-step:not(.disabled)")
|
|
17109
|
+
);
|
|
17110
|
+
if (steps.length === 0) return;
|
|
17111
|
+
e.preventDefault();
|
|
17112
|
+
const current = steps.indexOf(e.currentTarget);
|
|
17113
|
+
let target;
|
|
17114
|
+
if (e.key === "Home") target = 0;
|
|
17115
|
+
else if (e.key === "End") target = steps.length - 1;
|
|
17116
|
+
else if (prevKey) target = (current - 1 + steps.length) % steps.length;
|
|
17117
|
+
else target = (current + 1) % steps.length;
|
|
17118
|
+
steps[target]?.focus();
|
|
17119
|
+
}
|
|
16189
17120
|
onStepClick(step, index) {
|
|
16190
17121
|
if (step.status === "disabled") return;
|
|
16191
17122
|
this.dispatchEvent(
|
|
@@ -16238,7 +17169,12 @@ var RelStepper = class extends import_lit51.LitElement {
|
|
|
16238
17169
|
(step, idx) => import_lit51.html`
|
|
16239
17170
|
<div
|
|
16240
17171
|
class="${this.getStepClasses(step)}"
|
|
17172
|
+
role="button"
|
|
17173
|
+
tabindex="${this.isDisabled(step) ? -1 : 0}"
|
|
17174
|
+
aria-disabled="${this.isDisabled(step) ? "true" : import_lit51.nothing}"
|
|
17175
|
+
aria-current="${this.isProgress(step) ? "step" : import_lit51.nothing}"
|
|
16241
17176
|
@click="${() => this.onStepClick(step, idx)}"
|
|
17177
|
+
@keydown="${(e) => this.onStepKeyDown(e, step, idx)}"
|
|
16242
17178
|
>
|
|
16243
17179
|
${isVertical && isTextRight ? import_lit51.html`
|
|
16244
17180
|
<!-- Indicator with connector below -->
|
|
@@ -16325,6 +17261,17 @@ RelStepper.styles = import_lit51.css`
|
|
|
16325
17261
|
.rel-step {
|
|
16326
17262
|
display: flex;
|
|
16327
17263
|
position: relative;
|
|
17264
|
+
cursor: pointer;
|
|
17265
|
+
}
|
|
17266
|
+
|
|
17267
|
+
.rel-step.disabled {
|
|
17268
|
+
cursor: not-allowed;
|
|
17269
|
+
}
|
|
17270
|
+
|
|
17271
|
+
.rel-step:focus-visible {
|
|
17272
|
+
outline: 2px solid var(--rel-border-focus);
|
|
17273
|
+
outline-offset: 2px;
|
|
17274
|
+
border-radius: var(--rel-border-radius-xs);
|
|
16328
17275
|
}
|
|
16329
17276
|
|
|
16330
17277
|
.rel-stepper.horizontal.text-below .rel-step {
|
|
@@ -16901,6 +17848,8 @@ var RelTab = class extends import_lit53.LitElement {
|
|
|
16901
17848
|
this.activeIndex = 0;
|
|
16902
17849
|
this.size = "md";
|
|
16903
17850
|
this._tabItems = [];
|
|
17851
|
+
/** Stable id prefix linking tabs to the shared tabpanel. */
|
|
17852
|
+
this.tabsId = `rel-tab-${Math.random().toString(36).slice(2)}`;
|
|
16904
17853
|
}
|
|
16905
17854
|
_onSlotChange(e) {
|
|
16906
17855
|
const slot = e.target;
|
|
@@ -16944,12 +17893,24 @@ var RelTab = class extends import_lit53.LitElement {
|
|
|
16944
17893
|
next = (next + dir + tabs.length) % tabs.length;
|
|
16945
17894
|
if (!tabs[next]?.disabled) break;
|
|
16946
17895
|
}
|
|
16947
|
-
this.
|
|
16948
|
-
|
|
16949
|
-
const tabEls = this.shadowRoot?.querySelectorAll("[role=tab]");
|
|
16950
|
-
tabEls?.[next]?.focus();
|
|
16951
|
-
});
|
|
17896
|
+
this.selectAndFocusTab(next);
|
|
17897
|
+
return;
|
|
16952
17898
|
}
|
|
17899
|
+
if (event.key === "Home" || event.key === "End") {
|
|
17900
|
+
event.preventDefault();
|
|
17901
|
+
const tabs = this._effectiveTabs;
|
|
17902
|
+
const order = [...tabs.keys()];
|
|
17903
|
+
if (event.key === "End") order.reverse();
|
|
17904
|
+
const next = order.find((i) => !tabs[i]?.disabled);
|
|
17905
|
+
if (next !== void 0) this.selectAndFocusTab(next);
|
|
17906
|
+
}
|
|
17907
|
+
}
|
|
17908
|
+
selectAndFocusTab(index) {
|
|
17909
|
+
this.selectTab(index);
|
|
17910
|
+
this.updateComplete.then(() => {
|
|
17911
|
+
const tabEls = this.shadowRoot?.querySelectorAll("[role=tab]");
|
|
17912
|
+
tabEls?.[index]?.focus();
|
|
17913
|
+
});
|
|
16953
17914
|
}
|
|
16954
17915
|
render() {
|
|
16955
17916
|
const tabs = this._effectiveTabs;
|
|
@@ -16960,9 +17921,11 @@ var RelTab = class extends import_lit53.LitElement {
|
|
|
16960
17921
|
(tab, i) => import_lit53.html`
|
|
16961
17922
|
<div
|
|
16962
17923
|
part="item" class="tab ${i === this.activeIndex ? "active" : ""} ${tab.disabled ? "disabled" : ""}"
|
|
17924
|
+
id="${this.tabsId}-tab-${i}"
|
|
16963
17925
|
role="tab"
|
|
16964
17926
|
aria-selected="${i === this.activeIndex}"
|
|
16965
17927
|
aria-disabled="${tab.disabled ?? false}"
|
|
17928
|
+
aria-controls="${this.tabsId}-panel"
|
|
16966
17929
|
tabindex="${tab.disabled ? -1 : i === this.activeIndex ? 0 : -1}"
|
|
16967
17930
|
@click="${() => this.selectTab(i)}"
|
|
16968
17931
|
@keydown="${(e) => this.onKeyDown(e, i)}"
|
|
@@ -16974,7 +17937,13 @@ var RelTab = class extends import_lit53.LitElement {
|
|
|
16974
17937
|
`
|
|
16975
17938
|
)}
|
|
16976
17939
|
</div>
|
|
16977
|
-
<div
|
|
17940
|
+
<div
|
|
17941
|
+
class="tab-content"
|
|
17942
|
+
id="${this.tabsId}-panel"
|
|
17943
|
+
role="tabpanel"
|
|
17944
|
+
aria-labelledby="${this.tabsId}-tab-${this.activeIndex}"
|
|
17945
|
+
tabindex="0"
|
|
17946
|
+
>
|
|
16978
17947
|
<slot></slot>
|
|
16979
17948
|
</div>
|
|
16980
17949
|
`;
|
|
@@ -17005,7 +17974,6 @@ RelTab.styles = import_lit53.css`
|
|
|
17005
17974
|
padding: var(--rel-spacing-sm) var(--rel-spacing-md);
|
|
17006
17975
|
cursor: pointer;
|
|
17007
17976
|
color: var(--rel-text-body);
|
|
17008
|
-
|
|
17009
17977
|
transition: color 0.2s ease;
|
|
17010
17978
|
display: flex;
|
|
17011
17979
|
align-items: center;
|
|
@@ -17123,7 +18091,8 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17123
18091
|
this.required = false;
|
|
17124
18092
|
this.readonly = false;
|
|
17125
18093
|
this.format = "12h";
|
|
17126
|
-
this.
|
|
18094
|
+
this.seconds = false;
|
|
18095
|
+
this.minuteStep = 5;
|
|
17127
18096
|
this.minTime = null;
|
|
17128
18097
|
this.maxTime = null;
|
|
17129
18098
|
this.open = false;
|
|
@@ -17135,6 +18104,8 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17135
18104
|
this.fullWidth = false;
|
|
17136
18105
|
this.draftHour = null;
|
|
17137
18106
|
this.draftMinute = null;
|
|
18107
|
+
this.draftSecond = null;
|
|
18108
|
+
this.openUp = false;
|
|
17138
18109
|
this.handleClickOutside = (e) => {
|
|
17139
18110
|
if (!this.open) return;
|
|
17140
18111
|
const path = e.composedPath();
|
|
@@ -17159,40 +18130,58 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17159
18130
|
document.removeEventListener("keydown", this.handleEscapeKey);
|
|
17160
18131
|
}
|
|
17161
18132
|
updated(changedProperties) {
|
|
17162
|
-
if (changedProperties.has("open")
|
|
18133
|
+
if (changedProperties.has("open")) {
|
|
18134
|
+
if (!this.open) {
|
|
18135
|
+
this.openUp = false;
|
|
18136
|
+
return;
|
|
18137
|
+
}
|
|
17163
18138
|
this.resetDraftFromValue();
|
|
17164
18139
|
setTimeout(() => {
|
|
18140
|
+
this.updatePanelPlacement();
|
|
17165
18141
|
this.scrollColumnsToSelection();
|
|
17166
18142
|
const target = this.columnsElement?.querySelector(
|
|
17167
18143
|
".option-button.selected:not(:disabled)"
|
|
17168
18144
|
) ?? this.columnsElement?.querySelector(
|
|
17169
18145
|
".option-button:not(:disabled)"
|
|
17170
18146
|
);
|
|
17171
|
-
target?.focus();
|
|
18147
|
+
target?.focus({ preventScroll: true });
|
|
17172
18148
|
}, 0);
|
|
17173
18149
|
}
|
|
17174
18150
|
}
|
|
18151
|
+
/** Open upward when the panel doesn't fit below but does fit above. */
|
|
18152
|
+
updatePanelPlacement() {
|
|
18153
|
+
const panel = this.renderRoot.querySelector(".popover");
|
|
18154
|
+
if (!panel) return;
|
|
18155
|
+
const rect = this.getBoundingClientRect();
|
|
18156
|
+
const panelHeight = panel.offsetHeight;
|
|
18157
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
18158
|
+
this.openUp = spaceBelow < panelHeight + 8 && rect.top > panelHeight + 8;
|
|
18159
|
+
}
|
|
17175
18160
|
// ── Parsing / formatting ──
|
|
17176
18161
|
parseTime(time) {
|
|
17177
18162
|
if (!time) return null;
|
|
17178
|
-
const match = time.match(/^(\d{1,2}):(\d{2})
|
|
18163
|
+
const match = time.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
|
|
17179
18164
|
if (!match) return null;
|
|
17180
18165
|
const hour = parseInt(match[1], 10);
|
|
17181
18166
|
const minute = parseInt(match[2], 10);
|
|
17182
|
-
|
|
17183
|
-
|
|
18167
|
+
const second = match[3] ? parseInt(match[3], 10) : 0;
|
|
18168
|
+
if (hour > 23 || minute > 59 || second > 59) return null;
|
|
18169
|
+
return { hour, minute, second };
|
|
17184
18170
|
}
|
|
17185
|
-
formatValue(hour, minute) {
|
|
17186
|
-
|
|
18171
|
+
formatValue(hour, minute, second = 0) {
|
|
18172
|
+
const hh = String(hour).padStart(2, "0");
|
|
18173
|
+
const mm = String(minute).padStart(2, "0");
|
|
18174
|
+
return this.seconds ? `${hh}:${mm}:${String(second).padStart(2, "0")}` : `${hh}:${mm}`;
|
|
17187
18175
|
}
|
|
17188
|
-
formatDisplay(hour, minute) {
|
|
18176
|
+
formatDisplay(hour, minute, second = 0) {
|
|
17189
18177
|
const mm = String(minute).padStart(2, "0");
|
|
18178
|
+
const ss = this.seconds ? `:${String(second).padStart(2, "0")}` : "";
|
|
17190
18179
|
if (this.format === "24h") {
|
|
17191
|
-
return `${String(hour).padStart(2, "0")}:${mm}`;
|
|
18180
|
+
return `${String(hour).padStart(2, "0")}:${mm}${ss}`;
|
|
17192
18181
|
}
|
|
17193
18182
|
const period = hour < 12 ? "AM" : "PM";
|
|
17194
18183
|
const hour12 = hour % 12 === 0 ? 12 : hour % 12;
|
|
17195
|
-
return `${hour12}:${mm} ${period}`;
|
|
18184
|
+
return `${hour12}:${mm}${ss} ${period}`;
|
|
17196
18185
|
}
|
|
17197
18186
|
toMinutes(hour, minute) {
|
|
17198
18187
|
return hour * 60 + minute;
|
|
@@ -17242,10 +18231,17 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17242
18231
|
}
|
|
17243
18232
|
const period = this.draftPeriod ?? "AM";
|
|
17244
18233
|
const base = period === "AM" ? 0 : 12;
|
|
17245
|
-
return Array.from({ length: 12 }, (_, i) =>
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
18234
|
+
return Array.from({ length: 12 }, (_, i) => {
|
|
18235
|
+
const display = i + 1;
|
|
18236
|
+
return {
|
|
18237
|
+
label: String(display).padStart(2, "0"),
|
|
18238
|
+
hour24: base + display % 12
|
|
18239
|
+
};
|
|
18240
|
+
});
|
|
18241
|
+
}
|
|
18242
|
+
/** Seconds shown in the seconds column (always step 1). */
|
|
18243
|
+
getSecondOptions() {
|
|
18244
|
+
return Array.from({ length: 60 }, (_, s) => s);
|
|
17249
18245
|
}
|
|
17250
18246
|
get draftPeriod() {
|
|
17251
18247
|
if (this.draftHour === null) return null;
|
|
@@ -17255,6 +18251,7 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17255
18251
|
const parsed = this.parseTime(this.value);
|
|
17256
18252
|
this.draftHour = parsed ? parsed.hour : null;
|
|
17257
18253
|
this.draftMinute = parsed ? parsed.minute : null;
|
|
18254
|
+
this.draftSecond = parsed ? parsed.second : null;
|
|
17258
18255
|
}
|
|
17259
18256
|
/** Snap the draft minute to the nearest valid step minute for the hour. */
|
|
17260
18257
|
clampDraftToRange() {
|
|
@@ -17297,7 +18294,11 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17297
18294
|
}
|
|
17298
18295
|
applySelection() {
|
|
17299
18296
|
if (this.draftHour === null) return;
|
|
17300
|
-
const newValue = this.formatValue(
|
|
18297
|
+
const newValue = this.formatValue(
|
|
18298
|
+
this.draftHour,
|
|
18299
|
+
this.draftMinute ?? 0,
|
|
18300
|
+
this.draftSecond ?? 0
|
|
18301
|
+
);
|
|
17301
18302
|
this.value = newValue;
|
|
17302
18303
|
this.dispatchEvent(
|
|
17303
18304
|
new CustomEvent("rel-change", {
|
|
@@ -17313,6 +18314,7 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17313
18314
|
const now = /* @__PURE__ */ new Date();
|
|
17314
18315
|
const step = this.minuteStepSafe;
|
|
17315
18316
|
this.draftHour = now.getHours();
|
|
18317
|
+
this.draftSecond = this.seconds ? now.getSeconds() : null;
|
|
17316
18318
|
this.draftMinute = Math.round(now.getMinutes() / step) * step;
|
|
17317
18319
|
if (this.draftMinute >= 60) {
|
|
17318
18320
|
this.draftMinute = 0;
|
|
@@ -17335,6 +18337,7 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17335
18337
|
this.value = null;
|
|
17336
18338
|
this.draftHour = null;
|
|
17337
18339
|
this.draftMinute = null;
|
|
18340
|
+
this.draftSecond = null;
|
|
17338
18341
|
this.dispatchEvent(
|
|
17339
18342
|
new CustomEvent("rel-change", {
|
|
17340
18343
|
detail: { value: null },
|
|
@@ -17363,6 +18366,20 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17363
18366
|
if (!this.isTimeInRange(this.draftHour, minute)) return;
|
|
17364
18367
|
this.draftMinute = minute;
|
|
17365
18368
|
}
|
|
18369
|
+
selectSecond(second) {
|
|
18370
|
+
if (this.draftHour === null) {
|
|
18371
|
+
for (let h = 0; h < 24; h++) {
|
|
18372
|
+
if (this.isHourEnabled(h)) {
|
|
18373
|
+
this.draftHour = h;
|
|
18374
|
+
break;
|
|
18375
|
+
}
|
|
18376
|
+
}
|
|
18377
|
+
if (this.draftHour === null) return;
|
|
18378
|
+
}
|
|
18379
|
+
if (this.draftMinute === null) this.draftMinute = 0;
|
|
18380
|
+
this.draftSecond = second;
|
|
18381
|
+
this.clampDraftToRange();
|
|
18382
|
+
}
|
|
17366
18383
|
selectPeriod(period) {
|
|
17367
18384
|
if (!this.isPeriodEnabled(period)) return;
|
|
17368
18385
|
const base = period === "AM" ? 0 : 12;
|
|
@@ -17380,9 +18397,27 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17380
18397
|
this.clampDraftToRange();
|
|
17381
18398
|
this.scrollColumnsToSelection();
|
|
17382
18399
|
}
|
|
18400
|
+
/**
|
|
18401
|
+
* Scrolls the column's own scroll container only. scrollIntoView is off
|
|
18402
|
+
* limits here: it walks ALL scrollable ancestors, so it yanked the page
|
|
18403
|
+
* up/down whenever a column recentred (user-reported jump bug).
|
|
18404
|
+
*/
|
|
18405
|
+
scrollOptionInColumn(btn, block) {
|
|
18406
|
+
const list = btn.parentElement;
|
|
18407
|
+
if (!list) return;
|
|
18408
|
+
const top = btn.offsetTop;
|
|
18409
|
+
const height = btn.offsetHeight;
|
|
18410
|
+
if (block === "center") {
|
|
18411
|
+
list.scrollTop = top - (list.clientHeight - height) / 2;
|
|
18412
|
+
} else if (top < list.scrollTop) {
|
|
18413
|
+
list.scrollTop = top;
|
|
18414
|
+
} else if (top + height > list.scrollTop + list.clientHeight) {
|
|
18415
|
+
list.scrollTop = top + height - list.clientHeight;
|
|
18416
|
+
}
|
|
18417
|
+
}
|
|
17383
18418
|
scrollColumnsToSelection() {
|
|
17384
18419
|
this.updateComplete.then(() => {
|
|
17385
|
-
this.columnsElement?.querySelectorAll(".option-button.selected").forEach((btn) =>
|
|
18420
|
+
this.columnsElement?.querySelectorAll(".option-button.selected").forEach((btn) => this.scrollOptionInColumn(btn, "center"));
|
|
17386
18421
|
});
|
|
17387
18422
|
}
|
|
17388
18423
|
// ── Keyboard ──
|
|
@@ -17439,19 +18474,23 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17439
18474
|
}
|
|
17440
18475
|
if (target && target !== buttons[index]) {
|
|
17441
18476
|
target.click();
|
|
17442
|
-
target.focus();
|
|
17443
|
-
|
|
18477
|
+
target.focus({ preventScroll: true });
|
|
18478
|
+
this.scrollOptionInColumn(target, "nearest");
|
|
17444
18479
|
}
|
|
17445
18480
|
}
|
|
17446
18481
|
focusAdjacentColumn(from, direction) {
|
|
17447
|
-
const order =
|
|
18482
|
+
const order = ["hour", "minute"];
|
|
18483
|
+
if (this.seconds) order.push("second");
|
|
18484
|
+
if (this.format === "12h") order.push("period");
|
|
17448
18485
|
const nextIndex = order.indexOf(from) + direction;
|
|
17449
18486
|
if (nextIndex < 0 || nextIndex >= order.length) return;
|
|
17450
18487
|
const lists = this.columnsElement?.querySelectorAll(".column-list");
|
|
17451
18488
|
const list = lists?.[nextIndex];
|
|
17452
18489
|
const target = list?.querySelector(".option-button.selected:not(:disabled)") ?? list?.querySelector(".option-button:not(:disabled)");
|
|
17453
|
-
target
|
|
17454
|
-
|
|
18490
|
+
if (target) {
|
|
18491
|
+
target.focus({ preventScroll: true });
|
|
18492
|
+
this.scrollOptionInColumn(target, "nearest");
|
|
18493
|
+
}
|
|
17455
18494
|
}
|
|
17456
18495
|
// ── Render ──
|
|
17457
18496
|
render() {
|
|
@@ -17459,10 +18498,10 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17459
18498
|
const belowText = isError && this.errorMessage ? this.errorMessage : this.helperText;
|
|
17460
18499
|
const describedById = belowText ? "time-picker-desc" : void 0;
|
|
17461
18500
|
const parsedValue = this.parseTime(this.value);
|
|
17462
|
-
const displayText = parsedValue ? this.formatDisplay(parsedValue.hour, parsedValue.minute) : "";
|
|
17463
|
-
const draftDisplay = this.draftHour !== null ? this.formatDisplay(this.draftHour, this.draftMinute ?? 0) : "";
|
|
18501
|
+
const displayText = parsedValue ? this.formatDisplay(parsedValue.hour, parsedValue.minute, parsedValue.second) : "";
|
|
17464
18502
|
const hourOptions = this.getHourOptions();
|
|
17465
18503
|
const minuteOptions = this.getMinuteOptions();
|
|
18504
|
+
const secondOptions = this.seconds ? this.getSecondOptions() : [];
|
|
17466
18505
|
const draftPeriod = this.draftPeriod;
|
|
17467
18506
|
const wrapperClasses = [
|
|
17468
18507
|
"time-picker-wrapper",
|
|
@@ -17527,104 +18566,106 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17527
18566
|
|
|
17528
18567
|
<div
|
|
17529
18568
|
part="panel"
|
|
17530
|
-
class="popover ${this.open ? "open" : ""}"
|
|
18569
|
+
class="popover ${this.open ? "open" : ""} ${this.openUp ? "open-up" : ""}"
|
|
17531
18570
|
role="dialog"
|
|
17532
18571
|
aria-label="Choose time"
|
|
17533
18572
|
>
|
|
17534
|
-
<div class="time-readout">
|
|
17535
|
-
<span class="time-readout-label">Time</span>
|
|
17536
|
-
<span class="time-readout-value ${draftDisplay ? "" : "empty"}">
|
|
17537
|
-
${draftDisplay || (this.format === "24h" ? "--:--" : "--:-- --")}
|
|
17538
|
-
</span>
|
|
17539
|
-
</div>
|
|
17540
|
-
|
|
17541
18573
|
<div class="columns">
|
|
17542
|
-
<div class="column">
|
|
17543
|
-
|
|
17544
|
-
<div class="column-list" role="listbox" aria-label="Hours">
|
|
17545
|
-
${hourOptions.map(
|
|
18574
|
+
<div class="column-list" role="listbox" aria-label="Hours">
|
|
18575
|
+
${hourOptions.map(
|
|
17546
18576
|
(opt, i) => import_lit54.html`
|
|
17547
|
-
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
|
|
17554
|
-
|
|
17555
|
-
|
|
17556
|
-
|
|
17557
|
-
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
|
|
18577
|
+
<button
|
|
18578
|
+
part="option"
|
|
18579
|
+
class="option-button ${this.draftHour === opt.hour24 ? "selected" : ""}"
|
|
18580
|
+
type="button"
|
|
18581
|
+
role="option"
|
|
18582
|
+
aria-selected=${this.draftHour === opt.hour24}
|
|
18583
|
+
?disabled=${!this.isHourEnabled(opt.hour24)}
|
|
18584
|
+
@click=${() => this.selectHour(opt.hour24)}
|
|
18585
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "hour")}
|
|
18586
|
+
tabindex=${this.draftHour === opt.hour24 || this.draftHour === null && i === 0 ? 0 : -1}
|
|
18587
|
+
>
|
|
18588
|
+
${opt.label}
|
|
18589
|
+
</button>
|
|
18590
|
+
`
|
|
17561
18591
|
)}
|
|
17562
|
-
</div>
|
|
17563
18592
|
</div>
|
|
17564
18593
|
|
|
17565
|
-
<div class="column">
|
|
17566
|
-
|
|
17567
|
-
|
|
17568
|
-
|
|
18594
|
+
<div class="column-divider" aria-hidden="true"></div>
|
|
18595
|
+
|
|
18596
|
+
<div class="column-list" role="listbox" aria-label="Minutes">
|
|
18597
|
+
${minuteOptions.map(
|
|
17569
18598
|
(m, i) => import_lit54.html`
|
|
17570
|
-
|
|
17571
|
-
|
|
17572
|
-
|
|
17573
|
-
|
|
17574
|
-
|
|
17575
|
-
|
|
17576
|
-
|
|
17577
|
-
|
|
17578
|
-
|
|
17579
|
-
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
|
|
17583
|
-
|
|
18599
|
+
<button
|
|
18600
|
+
part="option"
|
|
18601
|
+
class="option-button ${this.draftMinute === m ? "selected" : ""}"
|
|
18602
|
+
type="button"
|
|
18603
|
+
role="option"
|
|
18604
|
+
aria-selected=${this.draftMinute === m}
|
|
18605
|
+
?disabled=${this.draftHour !== null && !this.isTimeInRange(this.draftHour, m)}
|
|
18606
|
+
@click=${() => this.selectMinute(m)}
|
|
18607
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "minute")}
|
|
18608
|
+
tabindex=${this.draftMinute === m || this.draftMinute === null && i === 0 ? 0 : -1}
|
|
18609
|
+
>
|
|
18610
|
+
${String(m).padStart(2, "0")}
|
|
18611
|
+
</button>
|
|
18612
|
+
`
|
|
17584
18613
|
)}
|
|
17585
|
-
</div>
|
|
17586
18614
|
</div>
|
|
17587
18615
|
|
|
18616
|
+
${this.seconds ? import_lit54.html`
|
|
18617
|
+
<div class="column-divider" aria-hidden="true"></div>
|
|
18618
|
+
<div class="column-list" role="listbox" aria-label="Seconds">
|
|
18619
|
+
${secondOptions.map(
|
|
18620
|
+
(s, i) => import_lit54.html`
|
|
18621
|
+
<button
|
|
18622
|
+
part="option"
|
|
18623
|
+
class="option-button ${this.draftSecond === s ? "selected" : ""}"
|
|
18624
|
+
type="button"
|
|
18625
|
+
role="option"
|
|
18626
|
+
aria-selected=${this.draftSecond === s}
|
|
18627
|
+
@click=${() => this.selectSecond(s)}
|
|
18628
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "second")}
|
|
18629
|
+
tabindex=${this.draftSecond === s || this.draftSecond === null && i === 0 ? 0 : -1}
|
|
18630
|
+
>
|
|
18631
|
+
${String(s).padStart(2, "0")}
|
|
18632
|
+
</button>
|
|
18633
|
+
`
|
|
18634
|
+
)}
|
|
18635
|
+
</div>
|
|
18636
|
+
` : import_lit54.nothing}
|
|
17588
18637
|
${this.format === "12h" ? import_lit54.html`
|
|
17589
|
-
<div class="column">
|
|
17590
|
-
|
|
17591
|
-
|
|
17592
|
-
${["AM", "PM"].map(
|
|
18638
|
+
<div class="column-divider" aria-hidden="true"></div>
|
|
18639
|
+
<div class="column-list" role="listbox" aria-label="AM or PM">
|
|
18640
|
+
${["AM", "PM"].map(
|
|
17593
18641
|
(p) => import_lit54.html`
|
|
17594
|
-
|
|
17595
|
-
|
|
17596
|
-
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
17600
|
-
|
|
17601
|
-
|
|
17602
|
-
|
|
17603
|
-
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
18642
|
+
<button
|
|
18643
|
+
part="option"
|
|
18644
|
+
class="option-button ${draftPeriod === p ? "selected" : ""}"
|
|
18645
|
+
type="button"
|
|
18646
|
+
role="option"
|
|
18647
|
+
aria-selected=${draftPeriod === p}
|
|
18648
|
+
?disabled=${!this.isPeriodEnabled(p)}
|
|
18649
|
+
@click=${() => this.selectPeriod(p)}
|
|
18650
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "period")}
|
|
18651
|
+
tabindex=${draftPeriod === p || draftPeriod === null && p === "AM" ? 0 : -1}
|
|
18652
|
+
>
|
|
18653
|
+
${p}
|
|
18654
|
+
</button>
|
|
18655
|
+
`
|
|
17608
18656
|
)}
|
|
17609
|
-
</div>
|
|
17610
18657
|
</div>
|
|
17611
18658
|
` : import_lit54.nothing}
|
|
17612
18659
|
</div>
|
|
17613
18660
|
|
|
17614
18661
|
<div part="footer" class="time-footer">
|
|
17615
|
-
<rel-button
|
|
17616
|
-
variant="secondary"
|
|
17617
|
-
size="small"
|
|
17618
|
-
border="corner-radius"
|
|
17619
|
-
@click=${() => this.selectNow()}
|
|
17620
|
-
>
|
|
18662
|
+
<rel-button variant="link" size="small" @click=${() => this.selectNow()}>
|
|
17621
18663
|
Now
|
|
17622
18664
|
</rel-button>
|
|
17623
18665
|
<div class="footer-right">
|
|
17624
18666
|
<rel-button
|
|
17625
|
-
variant="
|
|
18667
|
+
variant="ghost"
|
|
17626
18668
|
size="small"
|
|
17627
|
-
border="corner-radius"
|
|
17628
18669
|
@click=${() => this.cancelPopover()}
|
|
17629
18670
|
>
|
|
17630
18671
|
Cancel
|
|
@@ -17633,11 +18674,10 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17633
18674
|
variant="primary"
|
|
17634
18675
|
tone="default"
|
|
17635
18676
|
size="small"
|
|
17636
|
-
border="corner-radius"
|
|
17637
18677
|
?disabled=${this.draftHour === null}
|
|
17638
18678
|
@click=${() => this.applySelection()}
|
|
17639
18679
|
>
|
|
17640
|
-
|
|
18680
|
+
OK
|
|
17641
18681
|
</rel-button>
|
|
17642
18682
|
</div>
|
|
17643
18683
|
</div>
|
|
@@ -17821,16 +18861,19 @@ RelTimePicker.styles = import_lit54.css`
|
|
|
17821
18861
|
height: 16px;
|
|
17822
18862
|
}
|
|
17823
18863
|
|
|
18864
|
+
/* Panel (Figma "Time Picker"): fixed width per size, full-bleed content. */
|
|
17824
18865
|
.popover {
|
|
17825
18866
|
position: absolute;
|
|
17826
18867
|
top: calc(100% + 4px);
|
|
17827
18868
|
left: 0;
|
|
17828
18869
|
z-index: 1000;
|
|
17829
18870
|
background: var(--rel-bg-surface);
|
|
17830
|
-
border: 1px solid var(--rel-border-
|
|
18871
|
+
border: 1px solid var(--rel-border-subtle);
|
|
17831
18872
|
border-radius: var(--rel-border-radius-sm);
|
|
17832
18873
|
box-shadow: var(--rel-shadows-shadow-300);
|
|
17833
|
-
|
|
18874
|
+
overflow: hidden;
|
|
18875
|
+
min-width: 220px;
|
|
18876
|
+
width: 250px;
|
|
17834
18877
|
display: none;
|
|
17835
18878
|
}
|
|
17836
18879
|
|
|
@@ -17838,94 +18881,79 @@ RelTimePicker.styles = import_lit54.css`
|
|
|
17838
18881
|
display: block;
|
|
17839
18882
|
}
|
|
17840
18883
|
|
|
17841
|
-
/*
|
|
17842
|
-
.
|
|
17843
|
-
|
|
17844
|
-
|
|
17845
|
-
gap: var(--rel-spacing-sm);
|
|
17846
|
-
padding-bottom: var(--rel-spacing-sm);
|
|
17847
|
-
margin-bottom: var(--rel-spacing-sm);
|
|
17848
|
-
border-bottom: 1px solid var(--rel-border-subtle);
|
|
17849
|
-
}
|
|
17850
|
-
|
|
17851
|
-
.time-readout-label {
|
|
17852
|
-
color: var(--rel-text-placeholder);
|
|
17853
|
-
text-transform: uppercase;
|
|
17854
|
-
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17855
|
-
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17856
|
-
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17857
|
-
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17858
|
-
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
17859
|
-
}
|
|
17860
|
-
|
|
17861
|
-
.time-readout-value {
|
|
17862
|
-
color: var(--rel-text-default);
|
|
17863
|
-
font-family: var(--rel-body-md-semibold-font-family);
|
|
17864
|
-
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
17865
|
-
font-size: var(--rel-body-md-semibold-font-size);
|
|
17866
|
-
line-height: var(--rel-body-md-semibold-line-height);
|
|
17867
|
-
letter-spacing: var(--rel-body-md-semibold-letter-spacing);
|
|
17868
|
-
}
|
|
17869
|
-
|
|
17870
|
-
.time-readout-value.empty {
|
|
17871
|
-
color: var(--rel-text-placeholder);
|
|
18884
|
+
/* Flip above the field when the viewport has no room below. */
|
|
18885
|
+
.popover.open-up {
|
|
18886
|
+
top: auto;
|
|
18887
|
+
bottom: calc(100% + 4px);
|
|
17872
18888
|
}
|
|
17873
18889
|
|
|
18890
|
+
/* Full-bleed column row with 1px dividers (Figma "Columns"). */
|
|
17874
18891
|
.columns {
|
|
17875
18892
|
display: flex;
|
|
17876
|
-
|
|
17877
|
-
|
|
17878
|
-
|
|
17879
|
-
/* Columns share the panel width equally, so the two-column 24h layout
|
|
17880
|
-
fills the same footprint the footer dictates instead of bunching left. */
|
|
17881
|
-
.column {
|
|
17882
|
-
display: flex;
|
|
17883
|
-
flex-direction: column;
|
|
17884
|
-
flex: 1;
|
|
17885
|
-
min-width: 64px;
|
|
18893
|
+
align-items: stretch;
|
|
18894
|
+
height: 260px;
|
|
17886
18895
|
}
|
|
17887
18896
|
|
|
17888
|
-
.column-
|
|
17889
|
-
|
|
17890
|
-
|
|
17891
|
-
|
|
17892
|
-
text-transform: uppercase;
|
|
17893
|
-
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17894
|
-
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17895
|
-
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17896
|
-
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17897
|
-
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
18897
|
+
.column-divider {
|
|
18898
|
+
flex-shrink: 0;
|
|
18899
|
+
width: 1px;
|
|
18900
|
+
background: var(--rel-border-subtle);
|
|
17898
18901
|
}
|
|
17899
18902
|
|
|
18903
|
+
/* Each column scrolls independently. The 4px rail is always reserved
|
|
18904
|
+
(scrollbar-gutter) so columns stay aligned whether or not they
|
|
18905
|
+
overflow; the thumb only appears on overflow. */
|
|
17900
18906
|
.column-list {
|
|
18907
|
+
flex: 1;
|
|
18908
|
+
min-width: 0;
|
|
17901
18909
|
display: flex;
|
|
17902
18910
|
flex-direction: column;
|
|
17903
|
-
|
|
17904
|
-
|
|
18911
|
+
/* Positioned so option offsetTop is list-relative — the in-column
|
|
18912
|
+
scroll math depends on it (we never scrollIntoView: it scrolls every
|
|
18913
|
+
ancestor, page included). */
|
|
18914
|
+
position: relative;
|
|
17905
18915
|
overflow-y: auto;
|
|
17906
|
-
padding: var(--rel-spacing-
|
|
18916
|
+
padding: var(--rel-spacing-2xs);
|
|
17907
18917
|
scrollbar-width: thin;
|
|
18918
|
+
scrollbar-color: var(--rel-border-default) transparent;
|
|
18919
|
+
scrollbar-gutter: stable;
|
|
17908
18920
|
}
|
|
17909
18921
|
|
|
18922
|
+
.column-list::-webkit-scrollbar {
|
|
18923
|
+
width: 4px;
|
|
18924
|
+
}
|
|
18925
|
+
|
|
18926
|
+
.column-list::-webkit-scrollbar-track {
|
|
18927
|
+
background: transparent;
|
|
18928
|
+
}
|
|
18929
|
+
|
|
18930
|
+
.column-list::-webkit-scrollbar-thumb {
|
|
18931
|
+
background: var(--rel-border-default);
|
|
18932
|
+
border-radius: var(--rel-border-radius-full-rounded);
|
|
18933
|
+
}
|
|
18934
|
+
|
|
18935
|
+
/* Option cell (Figma ".Time Option"): selected uses the neutral
|
|
18936
|
+
elevated-2 fill + medium weight, NOT the brand color. */
|
|
17910
18937
|
.option-button {
|
|
17911
18938
|
border: none;
|
|
17912
18939
|
background: transparent;
|
|
17913
|
-
border-radius: var(--rel-border-radius-
|
|
18940
|
+
border-radius: var(--rel-border-radius-xs);
|
|
17914
18941
|
color: var(--rel-text-default);
|
|
17915
18942
|
cursor: pointer;
|
|
17916
|
-
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
17917
|
-
text-align: center;
|
|
17918
18943
|
flex-shrink: 0;
|
|
17919
|
-
|
|
17920
|
-
|
|
17921
|
-
|
|
17922
|
-
|
|
17923
|
-
|
|
17924
|
-
|
|
18944
|
+
height: 36px;
|
|
18945
|
+
padding: 0 var(--rel-spacing-xs);
|
|
18946
|
+
text-align: center;
|
|
18947
|
+
transition: background 0.15s ease;
|
|
18948
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
18949
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
18950
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
18951
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
18952
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
17925
18953
|
}
|
|
17926
18954
|
|
|
17927
18955
|
.option-button:hover:not(:disabled):not(.selected) {
|
|
17928
|
-
background: var(--rel-bg-
|
|
18956
|
+
background: var(--rel-bg-neutral-elevated-1);
|
|
17929
18957
|
}
|
|
17930
18958
|
|
|
17931
18959
|
.option-button:focus-visible {
|
|
@@ -17935,9 +18963,9 @@ RelTimePicker.styles = import_lit54.css`
|
|
|
17935
18963
|
}
|
|
17936
18964
|
|
|
17937
18965
|
.option-button.selected {
|
|
17938
|
-
background: var(--rel-
|
|
17939
|
-
color: var(--rel-
|
|
17940
|
-
font-weight: var(--rel-body-md-
|
|
18966
|
+
background: var(--rel-bg-neutral-elevated-2);
|
|
18967
|
+
color: var(--rel-text-default);
|
|
18968
|
+
font-weight: var(--rel-body-md-medium-font-weight);
|
|
17941
18969
|
}
|
|
17942
18970
|
|
|
17943
18971
|
.option-button:disabled {
|
|
@@ -17950,14 +18978,15 @@ RelTimePicker.styles = import_lit54.css`
|
|
|
17950
18978
|
background: transparent;
|
|
17951
18979
|
}
|
|
17952
18980
|
|
|
17953
|
-
/* Footer (
|
|
18981
|
+
/* Footer (Figma "Footer"): 8px padding + 8px extra left so the "Now"
|
|
18982
|
+
link aligns with the buttons' inner padding. Buttons are always S. */
|
|
17954
18983
|
.time-footer {
|
|
17955
18984
|
display: flex;
|
|
17956
18985
|
align-items: center;
|
|
17957
18986
|
justify-content: space-between;
|
|
17958
|
-
gap: var(--rel-spacing-
|
|
17959
|
-
padding
|
|
17960
|
-
|
|
18987
|
+
gap: var(--rel-spacing-xs);
|
|
18988
|
+
padding: var(--rel-spacing-xs);
|
|
18989
|
+
padding-left: calc(var(--rel-spacing-xs) * 2);
|
|
17961
18990
|
border-top: 1px solid var(--rel-border-subtle);
|
|
17962
18991
|
}
|
|
17963
18992
|
|
|
@@ -17967,6 +18996,56 @@ RelTimePicker.styles = import_lit54.css`
|
|
|
17967
18996
|
gap: var(--rel-spacing-xs);
|
|
17968
18997
|
}
|
|
17969
18998
|
|
|
18999
|
+
/* The design narrows OK below the DS small min-width (60 vs 80) so the
|
|
19000
|
+
compact footer fits the 220px panel. */
|
|
19001
|
+
.footer-right rel-button[variant="primary"] {
|
|
19002
|
+
--rel-button-min-width: 60px;
|
|
19003
|
+
}
|
|
19004
|
+
|
|
19005
|
+
/* Panel size variants (Figma S/M/L): width 220/250/280, option height
|
|
19006
|
+
32/36/40, body-sm/md/lg type. Medium is the base above. */
|
|
19007
|
+
.time-picker-wrapper.small .popover {
|
|
19008
|
+
width: 220px;
|
|
19009
|
+
}
|
|
19010
|
+
|
|
19011
|
+
.time-picker-wrapper.small .columns {
|
|
19012
|
+
height: 232px;
|
|
19013
|
+
}
|
|
19014
|
+
|
|
19015
|
+
.time-picker-wrapper.small .option-button {
|
|
19016
|
+
height: 32px;
|
|
19017
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
19018
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
19019
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
19020
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
19021
|
+
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
19022
|
+
}
|
|
19023
|
+
|
|
19024
|
+
.time-picker-wrapper.small .option-button.selected {
|
|
19025
|
+
font-weight: var(--rel-body-sm-medium-font-weight);
|
|
19026
|
+
}
|
|
19027
|
+
|
|
19028
|
+
.time-picker-wrapper.large .popover {
|
|
19029
|
+
width: 280px;
|
|
19030
|
+
}
|
|
19031
|
+
|
|
19032
|
+
.time-picker-wrapper.large .columns {
|
|
19033
|
+
height: 288px;
|
|
19034
|
+
}
|
|
19035
|
+
|
|
19036
|
+
.time-picker-wrapper.large .option-button {
|
|
19037
|
+
height: 40px;
|
|
19038
|
+
font-family: var(--rel-body-lg-regular-font-family);
|
|
19039
|
+
font-weight: var(--rel-body-lg-regular-font-weight);
|
|
19040
|
+
font-size: var(--rel-body-lg-regular-font-size);
|
|
19041
|
+
line-height: var(--rel-body-lg-regular-line-height);
|
|
19042
|
+
letter-spacing: var(--rel-body-lg-regular-letter-spacing);
|
|
19043
|
+
}
|
|
19044
|
+
|
|
19045
|
+
.time-picker-wrapper.large .option-button.selected {
|
|
19046
|
+
font-weight: var(--rel-body-lg-medium-font-weight);
|
|
19047
|
+
}
|
|
19048
|
+
|
|
17970
19049
|
@media (prefers-reduced-motion: reduce) {
|
|
17971
19050
|
.trigger-button,
|
|
17972
19051
|
.icon-button,
|
|
@@ -17996,6 +19075,9 @@ __decorateClass([
|
|
|
17996
19075
|
__decorateClass([
|
|
17997
19076
|
(0, import_decorators54.property)({ type: String })
|
|
17998
19077
|
], RelTimePicker.prototype, "format", 2);
|
|
19078
|
+
__decorateClass([
|
|
19079
|
+
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
19080
|
+
], RelTimePicker.prototype, "seconds", 2);
|
|
17999
19081
|
__decorateClass([
|
|
18000
19082
|
(0, import_decorators54.property)({ type: Number, attribute: "minute-step" })
|
|
18001
19083
|
], RelTimePicker.prototype, "minuteStep", 2);
|
|
@@ -18041,6 +19123,12 @@ __decorateClass([
|
|
|
18041
19123
|
__decorateClass([
|
|
18042
19124
|
(0, import_decorators54.state)()
|
|
18043
19125
|
], RelTimePicker.prototype, "draftMinute", 2);
|
|
19126
|
+
__decorateClass([
|
|
19127
|
+
(0, import_decorators54.state)()
|
|
19128
|
+
], RelTimePicker.prototype, "draftSecond", 2);
|
|
19129
|
+
__decorateClass([
|
|
19130
|
+
(0, import_decorators54.state)()
|
|
19131
|
+
], RelTimePicker.prototype, "openUp", 2);
|
|
18044
19132
|
__decorateClass([
|
|
18045
19133
|
(0, import_decorators54.query)(".trigger-button")
|
|
18046
19134
|
], RelTimePicker.prototype, "triggerButton", 2);
|
|
@@ -18279,6 +19367,26 @@ var RelTooltip = class extends import_lit56.LitElement {
|
|
|
18279
19367
|
this.type = "single-line";
|
|
18280
19368
|
// Stable id so aria-describedby can reference the bubble across renders.
|
|
18281
19369
|
this.uniqueId = Math.random().toString(36).slice(2);
|
|
19370
|
+
/** APG: Escape dismisses the tooltip. Not swallowed — an enclosing dialog
|
|
19371
|
+
* may legitimately close on the same keystroke. */
|
|
19372
|
+
this.onKeydown = (e) => {
|
|
19373
|
+
if (e.key === "Escape") this.toggleAttribute("data-dismissed", true);
|
|
19374
|
+
};
|
|
19375
|
+
this.clearDismissed = () => {
|
|
19376
|
+
this.removeAttribute("data-dismissed");
|
|
19377
|
+
};
|
|
19378
|
+
}
|
|
19379
|
+
connectedCallback() {
|
|
19380
|
+
super.connectedCallback();
|
|
19381
|
+
this.addEventListener("keydown", this.onKeydown);
|
|
19382
|
+
this.addEventListener("mouseleave", this.clearDismissed);
|
|
19383
|
+
this.addEventListener("focusout", this.clearDismissed);
|
|
19384
|
+
}
|
|
19385
|
+
disconnectedCallback() {
|
|
19386
|
+
super.disconnectedCallback();
|
|
19387
|
+
this.removeEventListener("keydown", this.onKeydown);
|
|
19388
|
+
this.removeEventListener("mouseleave", this.clearDismissed);
|
|
19389
|
+
this.removeEventListener("focusout", this.clearDismissed);
|
|
18282
19390
|
}
|
|
18283
19391
|
firstUpdated() {
|
|
18284
19392
|
this.linkTrigger();
|
|
@@ -18345,6 +19453,12 @@ RelTooltip.styles = import_lit56.css`
|
|
|
18345
19453
|
opacity: 1;
|
|
18346
19454
|
}
|
|
18347
19455
|
|
|
19456
|
+
/* Escape-dismissed (APG): stays hidden until hover/focus leaves and
|
|
19457
|
+
returns. Declared after the hover/focus rules so source order wins. */
|
|
19458
|
+
:host([data-dismissed]) .tooltip-bubble {
|
|
19459
|
+
opacity: 0;
|
|
19460
|
+
}
|
|
19461
|
+
|
|
18348
19462
|
/* Positioning */
|
|
18349
19463
|
.tooltip-bubble.above {
|
|
18350
19464
|
bottom: calc(100% + 6px);
|
|
@@ -19341,6 +20455,7 @@ RelUploadActions = __decorateClass([
|
|
|
19341
20455
|
__reExport(index_exports, require("@reliability-design/icons"), module.exports);
|
|
19342
20456
|
// Annotate the CommonJS export names for ESM import in node:
|
|
19343
20457
|
0 && (module.exports = {
|
|
20458
|
+
FocusTrap,
|
|
19344
20459
|
RelAccordion,
|
|
19345
20460
|
RelAlert,
|
|
19346
20461
|
RelAutocomplete,
|
|
@@ -19402,6 +20517,15 @@ __reExport(index_exports, require("@reliability-design/icons"), module.exports);
|
|
|
19402
20517
|
RelTopbarUtilities,
|
|
19403
20518
|
RelUpload,
|
|
19404
20519
|
RelUploadActions,
|
|
20520
|
+
bindShortcut,
|
|
20521
|
+
formatShortcutParts,
|
|
20522
|
+
getDeepActiveElement,
|
|
20523
|
+
getFocusableElements,
|
|
20524
|
+
isApplePlatform,
|
|
20525
|
+
isEditableTarget,
|
|
20526
|
+
matchesShortcut,
|
|
20527
|
+
parseShortcut,
|
|
20528
|
+
trapTabKey,
|
|
19405
20529
|
...require("@reliability-design/icons")
|
|
19406
20530
|
});
|
|
19407
20531
|
//# sourceMappingURL=index.js.map
|