@reliability-design/web 1.4.3 → 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-autocomplete/README.md +1 -0
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +56 -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/README.md +69 -22
- package/dist/components/rel-checkbox/rel-checkbox.ts +119 -9
- 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-pagination/README.md +14 -0
- package/dist/components/rel-pagination/rel-pagination.ts +30 -14
- package/dist/components/rel-popover/rel-popover.ts +31 -3
- package/dist/components/rel-range-picker/rel-range-picker.ts +251 -3
- 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/README.md +1 -0
- package/dist/components/rel-select/rel-select.ts +1385 -1170
- 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/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +134 -0
- package/dist/components/rel-upload/rel-upload-actions.ts +163 -0
- package/dist/components/rel-upload/rel-upload.ts +943 -0
- package/dist/index.d.mts +491 -18
- package/dist/index.d.ts +491 -18
- package/dist/index.js +2748 -410
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2953 -627
- 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,
|
|
@@ -87,7 +88,18 @@ __export(index_exports, {
|
|
|
87
88
|
RelTopbarItem: () => RelTopbarItem,
|
|
88
89
|
RelTopbarMobilePanel: () => RelTopbarMobilePanel,
|
|
89
90
|
RelTopbarProfile: () => RelTopbarProfile,
|
|
90
|
-
RelTopbarUtilities: () => RelTopbarUtilities
|
|
91
|
+
RelTopbarUtilities: () => RelTopbarUtilities,
|
|
92
|
+
RelUpload: () => RelUpload,
|
|
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
|
|
91
103
|
});
|
|
92
104
|
module.exports = __toCommonJS(index_exports);
|
|
93
105
|
var import_vars = require("./vars-XNMTSRVN.css");
|
|
@@ -104,6 +116,8 @@ var RelAccordion = class extends import_lit.LitElement {
|
|
|
104
116
|
this.size = "md";
|
|
105
117
|
this.type = "default";
|
|
106
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)}`;
|
|
107
121
|
}
|
|
108
122
|
toggle() {
|
|
109
123
|
this.open = !this.open;
|
|
@@ -120,9 +134,11 @@ var RelAccordion = class extends import_lit.LitElement {
|
|
|
120
134
|
<div part="base" class="accordion">
|
|
121
135
|
<div
|
|
122
136
|
part="trigger" class="header"
|
|
137
|
+
id="${this.accordionId}-header"
|
|
123
138
|
role="button"
|
|
124
139
|
tabindex="0"
|
|
125
140
|
aria-expanded="${this.open}"
|
|
141
|
+
aria-controls="${this.accordionId}-body"
|
|
126
142
|
@click=${this.toggle}
|
|
127
143
|
@keydown=${(e) => {
|
|
128
144
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -145,7 +161,14 @@ var RelAccordion = class extends import_lit.LitElement {
|
|
|
145
161
|
</slot>
|
|
146
162
|
</div>
|
|
147
163
|
</div>
|
|
148
|
-
<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
|
+
>
|
|
149
172
|
<slot></slot>
|
|
150
173
|
</div>
|
|
151
174
|
</div>
|
|
@@ -1648,6 +1671,14 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1648
1671
|
if (left + t.width > window.innerWidth - 8) {
|
|
1649
1672
|
left = Math.max(8, window.innerWidth - t.width - 8);
|
|
1650
1673
|
}
|
|
1674
|
+
this.panelEl.style.top = `${top}px`;
|
|
1675
|
+
this.panelEl.style.left = `${left}px`;
|
|
1676
|
+
this.panelEl.style.width = `${t.width}px`;
|
|
1677
|
+
const applied = this.panelEl.getBoundingClientRect();
|
|
1678
|
+
top -= applied.top - top;
|
|
1679
|
+
left -= applied.left - left;
|
|
1680
|
+
this.panelEl.style.top = `${top}px`;
|
|
1681
|
+
this.panelEl.style.left = `${left}px`;
|
|
1651
1682
|
this.panelStyle = `top:${top}px;left:${left}px;width:${t.width}px;`;
|
|
1652
1683
|
};
|
|
1653
1684
|
}
|
|
@@ -1659,6 +1690,7 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1659
1690
|
disconnectedCallback() {
|
|
1660
1691
|
super.disconnectedCallback();
|
|
1661
1692
|
this.removeGlobalListeners();
|
|
1693
|
+
this.hideNativePopover();
|
|
1662
1694
|
clearTimeout(this.debounceTimer);
|
|
1663
1695
|
}
|
|
1664
1696
|
willUpdate(changed) {
|
|
@@ -1718,13 +1750,40 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1718
1750
|
this.open = true;
|
|
1719
1751
|
this.highlightedIndex = -1;
|
|
1720
1752
|
this.addGlobalListeners();
|
|
1721
|
-
requestAnimationFrame(() =>
|
|
1753
|
+
requestAnimationFrame(() => {
|
|
1754
|
+
this.showNativePopover();
|
|
1755
|
+
this.updatePosition();
|
|
1756
|
+
});
|
|
1722
1757
|
}
|
|
1723
1758
|
closePanel() {
|
|
1724
1759
|
if (!this.open) return;
|
|
1725
1760
|
this.open = false;
|
|
1726
1761
|
this.highlightedIndex = -1;
|
|
1727
1762
|
this.removeGlobalListeners();
|
|
1763
|
+
this.hideNativePopover();
|
|
1764
|
+
}
|
|
1765
|
+
/* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
1766
|
+
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
1767
|
+
* descendants, breaking viewport-based coordinates and letting the
|
|
1768
|
+
* panel's overflow:hidden clip the suggestions. Promoting the panel to
|
|
1769
|
+
* the browser top layer sidesteps both — same fix as rel-popover.
|
|
1770
|
+
* `manual` keeps light-dismiss and Escape handling under this
|
|
1771
|
+
* component's own control. */
|
|
1772
|
+
showNativePopover() {
|
|
1773
|
+
const p = this.panelEl;
|
|
1774
|
+
if (!p || typeof p.showPopover !== "function") return;
|
|
1775
|
+
try {
|
|
1776
|
+
if (!p.matches(":popover-open")) p.showPopover();
|
|
1777
|
+
} catch {
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
hideNativePopover() {
|
|
1781
|
+
const p = this.panelEl;
|
|
1782
|
+
if (!p || typeof p.hidePopover !== "function") return;
|
|
1783
|
+
try {
|
|
1784
|
+
if (p.matches(":popover-open")) p.hidePopover();
|
|
1785
|
+
} catch {
|
|
1786
|
+
}
|
|
1728
1787
|
}
|
|
1729
1788
|
/** Re-evaluates whether the panel should be showing for the current state. */
|
|
1730
1789
|
syncPanel() {
|
|
@@ -1760,17 +1819,17 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1760
1819
|
this.syncPanel();
|
|
1761
1820
|
}
|
|
1762
1821
|
}
|
|
1763
|
-
async runSearch(
|
|
1822
|
+
async runSearch(query12) {
|
|
1764
1823
|
const seq = ++this.searchSeq;
|
|
1765
1824
|
try {
|
|
1766
|
-
const results = await this.searchFn(
|
|
1825
|
+
const results = await this.searchFn(query12);
|
|
1767
1826
|
if (seq !== this.searchSeq) return;
|
|
1768
1827
|
this.remoteItems = results ?? [];
|
|
1769
1828
|
} catch (err) {
|
|
1770
1829
|
if (seq !== this.searchSeq) return;
|
|
1771
1830
|
this.remoteItems = [];
|
|
1772
1831
|
this.dispatchEvent(
|
|
1773
|
-
new CustomEvent("rel-search-error", { detail: { query:
|
|
1832
|
+
new CustomEvent("rel-search-error", { detail: { query: query12, error: err }, bubbles: true, composed: true })
|
|
1774
1833
|
);
|
|
1775
1834
|
} finally {
|
|
1776
1835
|
if (seq === this.searchSeq) {
|
|
@@ -1940,6 +1999,7 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1940
1999
|
id="${panelId}"
|
|
1941
2000
|
part="panel list"
|
|
1942
2001
|
class="ac-panel"
|
|
2002
|
+
popover="manual"
|
|
1943
2003
|
role="listbox"
|
|
1944
2004
|
aria-label="${this.accessibleName || "Suggestions"}"
|
|
1945
2005
|
style="${this.panelStyle}"
|
|
@@ -1994,6 +2054,15 @@ RelAutocomplete.styles = import_lit4.css`
|
|
|
1994
2054
|
position: fixed;
|
|
1995
2055
|
z-index: 1001;
|
|
1996
2056
|
display: none;
|
|
2057
|
+
/* Reset native [popover] UA styles (the panel is promoted to the
|
|
2058
|
+
top layer so transformed ancestors — e.g. a centered rel-modal
|
|
2059
|
+
panel — can't hijack its containing block or clip it). */
|
|
2060
|
+
inset: auto;
|
|
2061
|
+
margin: 0;
|
|
2062
|
+
padding: 0;
|
|
2063
|
+
width: auto;
|
|
2064
|
+
height: auto;
|
|
2065
|
+
color: inherit;
|
|
1997
2066
|
box-sizing: border-box;
|
|
1998
2067
|
background: var(--rel-autocomplete-bg, var(--rel-bg-neutral));
|
|
1999
2068
|
border: 1px solid var(--rel-autocomplete-menu-border, var(--rel-border-default));
|
|
@@ -2789,9 +2858,12 @@ RelButton.styles = import_lit8.css`
|
|
|
2789
2858
|
}
|
|
2790
2859
|
}
|
|
2791
2860
|
|
|
2792
|
-
/* 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. */
|
|
2793
2864
|
.size-small {
|
|
2794
2865
|
height: 32px;
|
|
2866
|
+
min-width: var(--rel-button-min-width, 80px);
|
|
2795
2867
|
padding: 0 var(--rel-spacing-sm);
|
|
2796
2868
|
font-family: var(--rel-action-md-font-family);
|
|
2797
2869
|
font-weight: var(--rel-action-md-font-weight);
|
|
@@ -2802,6 +2874,7 @@ RelButton.styles = import_lit8.css`
|
|
|
2802
2874
|
|
|
2803
2875
|
.size-medium {
|
|
2804
2876
|
height: 40px;
|
|
2877
|
+
min-width: var(--rel-button-min-width, 100px);
|
|
2805
2878
|
gap: var(--rel-spacing-xs);
|
|
2806
2879
|
padding: 0 var(--rel-spacing-md);
|
|
2807
2880
|
font-family: var(--rel-action-lg-font-family);
|
|
@@ -2813,6 +2886,7 @@ RelButton.styles = import_lit8.css`
|
|
|
2813
2886
|
|
|
2814
2887
|
.size-large {
|
|
2815
2888
|
height: 48px;
|
|
2889
|
+
min-width: var(--rel-button-min-width, 120px);
|
|
2816
2890
|
padding: 0 var(--rel-spacing-lg);
|
|
2817
2891
|
font-family: var(--rel-action-xl-font-family);
|
|
2818
2892
|
font-weight: var(--rel-action-xl-font-weight);
|
|
@@ -2919,6 +2993,11 @@ RelButton.styles = import_lit8.css`
|
|
|
2919
2993
|
color: var(--rel-action-neutral-label-icons-ghost);
|
|
2920
2994
|
}
|
|
2921
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
|
+
|
|
2922
3001
|
.btn-text.tone-critical {
|
|
2923
3002
|
color: var(--rel-action-danger-label-icons-ghost);
|
|
2924
3003
|
}
|
|
@@ -2927,6 +3006,8 @@ RelButton.styles = import_lit8.css`
|
|
|
2927
3006
|
background: transparent;
|
|
2928
3007
|
color: var(--rel-action-neutral-label-icons-link);
|
|
2929
3008
|
text-decoration: underline;
|
|
3009
|
+
/* Links are inline actions — no minimum footprint in the Figma master. */
|
|
3010
|
+
min-width: 0;
|
|
2930
3011
|
}
|
|
2931
3012
|
|
|
2932
3013
|
button.btn-link:hover:not(:disabled) {
|
|
@@ -2955,6 +3036,7 @@ RelButton.styles = import_lit8.css`
|
|
|
2955
3036
|
/* Icon Only as an independent axis: square button of the current style */
|
|
2956
3037
|
.btn-icon-only {
|
|
2957
3038
|
padding: 0;
|
|
3039
|
+
min-width: 0;
|
|
2958
3040
|
}
|
|
2959
3041
|
|
|
2960
3042
|
.btn-icon-only.size-small {
|
|
@@ -2973,6 +3055,7 @@ RelButton.styles = import_lit8.css`
|
|
|
2973
3055
|
width: 40px;
|
|
2974
3056
|
height: 40px;
|
|
2975
3057
|
padding: 0;
|
|
3058
|
+
min-width: 0;
|
|
2976
3059
|
border-radius: 50%;
|
|
2977
3060
|
}
|
|
2978
3061
|
.btn-icon.tone-default {
|
|
@@ -3017,7 +3100,9 @@ RelButton.styles = import_lit8.css`
|
|
|
3017
3100
|
.btn-text: ghost gets tone-accent hovers below (this generic grey overlay
|
|
3018
3101
|
has higher specificity than the per-tone ghost rules and was overriding
|
|
3019
3102
|
them, which is why every ghost hover looked grey). */
|
|
3020
|
-
|
|
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) {
|
|
3021
3106
|
background: var(--rel-action-overlay-hover);
|
|
3022
3107
|
}
|
|
3023
3108
|
|
|
@@ -3369,9 +3454,28 @@ var RelCalendar = class extends import_lit9.LitElement {
|
|
|
3369
3454
|
});
|
|
3370
3455
|
}
|
|
3371
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
|
+
}
|
|
3372
3475
|
render() {
|
|
3373
3476
|
const days = this.getCalendarDays();
|
|
3374
3477
|
const weekdayLabels = this.getWeekdayLabels();
|
|
3478
|
+
const rovingIndex = this.getRovingIndex(days);
|
|
3375
3479
|
return import_lit9.html`
|
|
3376
3480
|
<div role="grid" aria-label="Calendar">
|
|
3377
3481
|
<div part="header" class="header">
|
|
@@ -3424,7 +3528,7 @@ var RelCalendar = class extends import_lit9.LitElement {
|
|
|
3424
3528
|
?disabled=${day.isDisabled}
|
|
3425
3529
|
@click=${() => this.selectDay(day)}
|
|
3426
3530
|
@keydown=${(e) => this.handleKeyDown(e, day, index)}
|
|
3427
|
-
tabindex="${
|
|
3531
|
+
tabindex="${index === rovingIndex ? 0 : -1}"
|
|
3428
3532
|
>
|
|
3429
3533
|
${day.day}
|
|
3430
3534
|
</button>
|
|
@@ -3724,12 +3828,23 @@ var RelCarousel = class extends import_lit11.LitElement {
|
|
|
3724
3828
|
// Number of clone slides prepended before the real slides
|
|
3725
3829
|
this.clonesBefore = 0;
|
|
3726
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
|
+
};
|
|
3727
3840
|
}
|
|
3728
3841
|
connectedCallback() {
|
|
3729
3842
|
super.connectedCallback();
|
|
3730
3843
|
this.prefersReducedMotion = window.matchMedia(
|
|
3731
3844
|
"(prefers-reduced-motion: reduce)"
|
|
3732
3845
|
).matches;
|
|
3846
|
+
this.addEventListener("focusin", this.handleFocusIn);
|
|
3847
|
+
this.addEventListener("focusout", this.handleFocusOut);
|
|
3733
3848
|
if (this.autoplay) {
|
|
3734
3849
|
this.startAutoplay();
|
|
3735
3850
|
}
|
|
@@ -3761,6 +3876,8 @@ var RelCarousel = class extends import_lit11.LitElement {
|
|
|
3761
3876
|
}
|
|
3762
3877
|
disconnectedCallback() {
|
|
3763
3878
|
super.disconnectedCallback();
|
|
3879
|
+
this.removeEventListener("focusin", this.handleFocusIn);
|
|
3880
|
+
this.removeEventListener("focusout", this.handleFocusOut);
|
|
3764
3881
|
this._ro?.disconnect();
|
|
3765
3882
|
this.stopAutoplay();
|
|
3766
3883
|
if (this.animationId) {
|
|
@@ -4075,6 +4192,7 @@ var RelCarousel = class extends import_lit11.LitElement {
|
|
|
4075
4192
|
class="dot ${i === this.value ? "active" : ""}"
|
|
4076
4193
|
part="dot"
|
|
4077
4194
|
aria-label="Go to slide ${i + 1}"
|
|
4195
|
+
aria-current="${i === this.value ? "true" : import_lit11.nothing}"
|
|
4078
4196
|
@click=${() => this.goToSlide(i)}
|
|
4079
4197
|
></button>
|
|
4080
4198
|
`) : import_lit11.nothing}
|
|
@@ -4467,6 +4585,9 @@ var RelCheckbox = class extends import_lit13.LitElement {
|
|
|
4467
4585
|
this.state = "default";
|
|
4468
4586
|
this.status = "unchecked";
|
|
4469
4587
|
this.value = "";
|
|
4588
|
+
this.size = "medium";
|
|
4589
|
+
this.density = "compact";
|
|
4590
|
+
this.alignment = "leading";
|
|
4470
4591
|
this._onChange = () => {
|
|
4471
4592
|
};
|
|
4472
4593
|
this._onTouched = () => {
|
|
@@ -4478,6 +4599,17 @@ var RelCheckbox = class extends import_lit13.LitElement {
|
|
|
4478
4599
|
set checked(val) {
|
|
4479
4600
|
this.status = val ? "checked" : "unchecked";
|
|
4480
4601
|
}
|
|
4602
|
+
willUpdate(changed) {
|
|
4603
|
+
if ((changed.has("alignment") || changed.has("density")) && this.alignment === "trailing" && this.density === "compact") {
|
|
4604
|
+
console.warn(
|
|
4605
|
+
'[rel-checkbox] density="compact" has no trailing layout; rendering comfortable density.'
|
|
4606
|
+
);
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
/** Trailing has no compact layout in the spec — trailing wins. */
|
|
4610
|
+
get effectiveDensity() {
|
|
4611
|
+
return this.alignment === "trailing" ? "comfortable" : this.density;
|
|
4612
|
+
}
|
|
4481
4613
|
toggle() {
|
|
4482
4614
|
if (this.state === "disabled") return;
|
|
4483
4615
|
this.status = this.status === "checked" ? "unchecked" : "checked";
|
|
@@ -4508,6 +4640,9 @@ var RelCheckbox = class extends import_lit13.LitElement {
|
|
|
4508
4640
|
<label class="row">
|
|
4509
4641
|
<input
|
|
4510
4642
|
type="checkbox"
|
|
4643
|
+
.checked=${this.status === "checked"}
|
|
4644
|
+
.indeterminate=${this.status === "indeterminate"}
|
|
4645
|
+
aria-checked="${this.status === "indeterminate" ? "mixed" : import_lit13.nothing}"
|
|
4511
4646
|
@click=${this.toggle}
|
|
4512
4647
|
@blur=${() => this._onTouched()}
|
|
4513
4648
|
?disabled=${this.state === "disabled"}
|
|
@@ -4525,12 +4660,22 @@ var RelCheckbox = class extends import_lit13.LitElement {
|
|
|
4525
4660
|
</svg>
|
|
4526
4661
|
</span>
|
|
4527
4662
|
|
|
4528
|
-
|
|
4663
|
+
<span class="content">
|
|
4664
|
+
<span class="label-line">
|
|
4665
|
+
${this.icon ? import_lit13.html`<rel-icon
|
|
4666
|
+
name="${this.icon}"
|
|
4667
|
+
size="${this.size === "small" ? "sm" : "md"}"
|
|
4668
|
+
class="icon-prefix"
|
|
4669
|
+
></rel-icon>` : null}
|
|
4670
|
+
|
|
4671
|
+
<span part="label" class="label">${this.label}</span>
|
|
4672
|
+
</span>
|
|
4529
4673
|
|
|
4530
|
-
|
|
4674
|
+
${this.effectiveDensity === "comfortable" && this.supportingtext ? import_lit13.html`<span part="helper" class="text">${this.supportingtext}</span>` : null}
|
|
4675
|
+
</span>
|
|
4531
4676
|
</label>
|
|
4532
4677
|
|
|
4533
|
-
${this.supportingtext ? import_lit13.html`<span part="helper" class="text">${this.supportingtext}</span>` : null}
|
|
4678
|
+
${this.effectiveDensity === "compact" && this.supportingtext ? import_lit13.html`<span part="helper" class="text text-below">${this.supportingtext}</span>` : null}
|
|
4534
4679
|
</div>
|
|
4535
4680
|
`;
|
|
4536
4681
|
}
|
|
@@ -4563,6 +4708,29 @@ RelCheckbox.styles = import_lit13.css`
|
|
|
4563
4708
|
cursor: pointer;
|
|
4564
4709
|
}
|
|
4565
4710
|
|
|
4711
|
+
/* Trailing alignment — box moves to the far edge, label stack to the
|
|
4712
|
+
start. row-reverse keeps the box first in DOM order (input + .box
|
|
4713
|
+
adjacency drives the focus ring) while space-between stretches the
|
|
4714
|
+
row to its container. */
|
|
4715
|
+
:host([alignment="trailing"]) .row {
|
|
4716
|
+
flex-direction: row-reverse;
|
|
4717
|
+
justify-content: space-between;
|
|
4718
|
+
}
|
|
4719
|
+
|
|
4720
|
+
/* Comfortable density — label and supporting text stack beside the box. */
|
|
4721
|
+
.content {
|
|
4722
|
+
display: flex;
|
|
4723
|
+
flex-direction: column;
|
|
4724
|
+
gap: var(--rel-spacing-3xs);
|
|
4725
|
+
min-width: 0;
|
|
4726
|
+
}
|
|
4727
|
+
|
|
4728
|
+
.label-line {
|
|
4729
|
+
display: flex;
|
|
4730
|
+
align-items: center;
|
|
4731
|
+
gap: var(--rel-spacing-xs);
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4566
4734
|
input {
|
|
4567
4735
|
position: absolute;
|
|
4568
4736
|
inset: 0;
|
|
@@ -4705,7 +4873,6 @@ RelCheckbox.styles = import_lit13.css`
|
|
|
4705
4873
|
|
|
4706
4874
|
.text {
|
|
4707
4875
|
color: var(--rel-text-caption);
|
|
4708
|
-
padding-left: 28px;
|
|
4709
4876
|
|
|
4710
4877
|
font-family: var(--rel-body-sm-regular-font-family);
|
|
4711
4878
|
|
|
@@ -4718,6 +4885,44 @@ RelCheckbox.styles = import_lit13.css`
|
|
|
4718
4885
|
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
4719
4886
|
|
|
4720
4887
|
}
|
|
4888
|
+
|
|
4889
|
+
/* Compact density — supporting text sits under the whole row, indented
|
|
4890
|
+
past the box (box width + row gap, matching the pre-existing 28px). */
|
|
4891
|
+
.text-below {
|
|
4892
|
+
padding-left: 28px;
|
|
4893
|
+
}
|
|
4894
|
+
|
|
4895
|
+
/* Size variant (medium is the base scale above). */
|
|
4896
|
+
:host([size="small"]) .box {
|
|
4897
|
+
width: 14px;
|
|
4898
|
+
height: 14px;
|
|
4899
|
+
border-radius: 3px;
|
|
4900
|
+
}
|
|
4901
|
+
|
|
4902
|
+
:host([size="small"]) svg {
|
|
4903
|
+
width: 10px;
|
|
4904
|
+
height: 10px;
|
|
4905
|
+
}
|
|
4906
|
+
|
|
4907
|
+
:host([size="small"]) .label {
|
|
4908
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
4909
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
4910
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
4911
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
4912
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
4913
|
+
}
|
|
4914
|
+
|
|
4915
|
+
:host([size="small"]) .text {
|
|
4916
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
4917
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
4918
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
4919
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
4920
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
4921
|
+
}
|
|
4922
|
+
|
|
4923
|
+
:host([size="small"]) .text-below {
|
|
4924
|
+
padding-left: 24px;
|
|
4925
|
+
}
|
|
4721
4926
|
`;
|
|
4722
4927
|
__decorateClass([
|
|
4723
4928
|
(0, import_decorators13.property)({ type: String })
|
|
@@ -4737,6 +4942,15 @@ __decorateClass([
|
|
|
4737
4942
|
__decorateClass([
|
|
4738
4943
|
(0, import_decorators13.property)({ type: String })
|
|
4739
4944
|
], RelCheckbox.prototype, "value", 2);
|
|
4945
|
+
__decorateClass([
|
|
4946
|
+
(0, import_decorators13.property)({ type: String, reflect: true })
|
|
4947
|
+
], RelCheckbox.prototype, "size", 2);
|
|
4948
|
+
__decorateClass([
|
|
4949
|
+
(0, import_decorators13.property)({ type: String, reflect: true })
|
|
4950
|
+
], RelCheckbox.prototype, "density", 2);
|
|
4951
|
+
__decorateClass([
|
|
4952
|
+
(0, import_decorators13.property)({ type: String, reflect: true })
|
|
4953
|
+
], RelCheckbox.prototype, "alignment", 2);
|
|
4740
4954
|
RelCheckbox = __decorateClass([
|
|
4741
4955
|
(0, import_decorators13.customElement)("rel-checkbox")
|
|
4742
4956
|
], RelCheckbox);
|
|
@@ -4767,6 +4981,109 @@ function formatDatePattern(date, pattern, locale = "en-US") {
|
|
|
4767
4981
|
);
|
|
4768
4982
|
}
|
|
4769
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
|
+
|
|
4770
5087
|
// src/components/rel-date-picker/rel-date-picker.ts
|
|
4771
5088
|
var RelDatePicker = class extends import_lit14.LitElement {
|
|
4772
5089
|
constructor() {
|
|
@@ -4804,9 +5121,58 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
4804
5121
|
}
|
|
4805
5122
|
};
|
|
4806
5123
|
this.handleEscapeKey = (e) => {
|
|
4807
|
-
if (
|
|
5124
|
+
if (!this.open) return;
|
|
5125
|
+
if (e.key === "Escape") {
|
|
4808
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;
|
|
4809
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;
|
|
5171
|
+
}
|
|
5172
|
+
e.preventDefault();
|
|
5173
|
+
e.stopPropagation();
|
|
5174
|
+
items[target]?.focus();
|
|
5175
|
+
items[target]?.scrollIntoView({ block: "nearest" });
|
|
4810
5176
|
};
|
|
4811
5177
|
}
|
|
4812
5178
|
// minDate/maxDate supersede the deprecated min/max aliases
|
|
@@ -4843,10 +5209,12 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
4843
5209
|
}
|
|
4844
5210
|
if (changedProperties.has("open") && this.open) {
|
|
4845
5211
|
setTimeout(() => {
|
|
4846
|
-
const
|
|
5212
|
+
const target = this.calendarGrid?.querySelector(
|
|
5213
|
+
'.day-button[tabindex="0"]:not(:disabled)'
|
|
5214
|
+
) ?? this.calendarGrid?.querySelector(
|
|
4847
5215
|
".day-button:not(:disabled)"
|
|
4848
5216
|
);
|
|
4849
|
-
|
|
5217
|
+
target?.focus();
|
|
4850
5218
|
}, 100);
|
|
4851
5219
|
}
|
|
4852
5220
|
}
|
|
@@ -5144,27 +5512,67 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
5144
5512
|
e.stopPropagation();
|
|
5145
5513
|
this.monthDropdownOpen = !this.monthDropdownOpen;
|
|
5146
5514
|
this.yearDropdownOpen = false;
|
|
5515
|
+
if (this.monthDropdownOpen) this.focusOpenDropdownSelection();
|
|
5147
5516
|
}
|
|
5148
5517
|
toggleYearDropdown(e) {
|
|
5149
5518
|
e.stopPropagation();
|
|
5150
5519
|
this.yearDropdownOpen = !this.yearDropdownOpen;
|
|
5151
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" });
|
|
5152
5538
|
}
|
|
5153
5539
|
selectMonth(month, e) {
|
|
5154
5540
|
e.stopPropagation();
|
|
5541
|
+
const trigger = e.target.closest(".dropdown-container")?.querySelector(".dropdown-trigger");
|
|
5155
5542
|
this.viewMonth = month;
|
|
5156
5543
|
this.monthDropdownOpen = false;
|
|
5544
|
+
this.updateComplete.then(() => trigger?.focus());
|
|
5157
5545
|
}
|
|
5158
5546
|
selectYear(year, e) {
|
|
5159
5547
|
e.stopPropagation();
|
|
5548
|
+
const trigger = e.target.closest(".dropdown-container")?.querySelector(".dropdown-trigger");
|
|
5160
5549
|
this.viewYear = year;
|
|
5161
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;
|
|
5162
5569
|
}
|
|
5163
5570
|
render() {
|
|
5164
5571
|
const days = this.getCalendarDays();
|
|
5165
5572
|
const weekdayLabels = this.getWeekdayLabels();
|
|
5166
5573
|
const months = this.getMonthsList();
|
|
5167
5574
|
const years = this.getYearsList();
|
|
5575
|
+
const rovingIndex = this.getRovingIndex(days);
|
|
5168
5576
|
const isError = this.error;
|
|
5169
5577
|
const belowText = isError && this.errorMessage ? this.errorMessage : this.helperText;
|
|
5170
5578
|
const describedById = belowText ? "date-picker-desc" : void 0;
|
|
@@ -5251,22 +5659,31 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
5251
5659
|
class="dropdown-trigger"
|
|
5252
5660
|
type="button"
|
|
5253
5661
|
@click=${this.toggleMonthDropdown}
|
|
5662
|
+
@keydown=${(e) => this.handleDropdownTriggerKeydown(e, "month")}
|
|
5254
5663
|
aria-label="Select month"
|
|
5664
|
+
aria-haspopup="listbox"
|
|
5255
5665
|
aria-expanded=${this.monthDropdownOpen}
|
|
5256
5666
|
>
|
|
5257
5667
|
${this.getMonthName()}
|
|
5258
5668
|
</button>
|
|
5259
|
-
<div
|
|
5669
|
+
<div
|
|
5670
|
+
class="dropdown-menu ${this.monthDropdownOpen ? "open" : ""}"
|
|
5671
|
+
role="listbox"
|
|
5672
|
+
aria-label="Month"
|
|
5673
|
+
@keydown=${this.handleDropdownKeydown}
|
|
5674
|
+
>
|
|
5260
5675
|
${months.map(
|
|
5261
5676
|
(m) => import_lit14.html`
|
|
5262
|
-
<
|
|
5677
|
+
<button
|
|
5263
5678
|
class="dropdown-item ${m.value === this.viewMonth ? "selected" : ""}"
|
|
5679
|
+
type="button"
|
|
5680
|
+
tabindex="-1"
|
|
5264
5681
|
@click=${(e) => this.selectMonth(m.value, e)}
|
|
5265
5682
|
role="option"
|
|
5266
5683
|
aria-selected=${m.value === this.viewMonth}
|
|
5267
5684
|
>
|
|
5268
5685
|
${m.label}
|
|
5269
|
-
</
|
|
5686
|
+
</button>
|
|
5270
5687
|
`
|
|
5271
5688
|
)}
|
|
5272
5689
|
</div>
|
|
@@ -5277,22 +5694,31 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
5277
5694
|
class="dropdown-trigger"
|
|
5278
5695
|
type="button"
|
|
5279
5696
|
@click=${this.toggleYearDropdown}
|
|
5697
|
+
@keydown=${(e) => this.handleDropdownTriggerKeydown(e, "year")}
|
|
5280
5698
|
aria-label="Select year"
|
|
5699
|
+
aria-haspopup="listbox"
|
|
5281
5700
|
aria-expanded=${this.yearDropdownOpen}
|
|
5282
5701
|
>
|
|
5283
5702
|
${this.viewYear}
|
|
5284
5703
|
</button>
|
|
5285
|
-
<div
|
|
5704
|
+
<div
|
|
5705
|
+
class="dropdown-menu ${this.yearDropdownOpen ? "open" : ""}"
|
|
5706
|
+
role="listbox"
|
|
5707
|
+
aria-label="Year"
|
|
5708
|
+
@keydown=${this.handleDropdownKeydown}
|
|
5709
|
+
>
|
|
5286
5710
|
${years.map(
|
|
5287
5711
|
(y) => import_lit14.html`
|
|
5288
|
-
<
|
|
5712
|
+
<button
|
|
5289
5713
|
class="dropdown-item ${y === this.viewYear ? "selected" : ""}"
|
|
5714
|
+
type="button"
|
|
5715
|
+
tabindex="-1"
|
|
5290
5716
|
@click=${(e) => this.selectYear(y, e)}
|
|
5291
5717
|
role="option"
|
|
5292
5718
|
aria-selected=${y === this.viewYear}
|
|
5293
5719
|
>
|
|
5294
5720
|
${y}
|
|
5295
|
-
</
|
|
5721
|
+
</button>
|
|
5296
5722
|
`
|
|
5297
5723
|
)}
|
|
5298
5724
|
</div>
|
|
@@ -5335,7 +5761,7 @@ var RelDatePicker = class extends import_lit14.LitElement {
|
|
|
5335
5761
|
?disabled=${day.isDisabled}
|
|
5336
5762
|
@click=${() => this.selectDay(day)}
|
|
5337
5763
|
@keydown=${(e) => this.handleDayKeydown(e, day, index)}
|
|
5338
|
-
tabindex="${
|
|
5764
|
+
tabindex="${index === rovingIndex ? 0 : -1}"
|
|
5339
5765
|
>
|
|
5340
5766
|
${day.day}
|
|
5341
5767
|
</button>
|
|
@@ -5629,6 +6055,11 @@ RelDatePicker.styles = import_lit14.css`
|
|
|
5629
6055
|
}
|
|
5630
6056
|
|
|
5631
6057
|
.dropdown-item {
|
|
6058
|
+
display: block;
|
|
6059
|
+
width: 100%;
|
|
6060
|
+
border: none;
|
|
6061
|
+
background: transparent;
|
|
6062
|
+
text-align: left;
|
|
5632
6063
|
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
5633
6064
|
cursor: pointer;
|
|
5634
6065
|
color: var(--rel-text-default);
|
|
@@ -5640,6 +6071,11 @@ RelDatePicker.styles = import_lit14.css`
|
|
|
5640
6071
|
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
5641
6072
|
}
|
|
5642
6073
|
|
|
6074
|
+
.dropdown-item:focus-visible {
|
|
6075
|
+
outline: 2px solid var(--rel-border-focus);
|
|
6076
|
+
outline-offset: -2px;
|
|
6077
|
+
}
|
|
6078
|
+
|
|
5643
6079
|
.dropdown-item:hover {
|
|
5644
6080
|
background: var(--rel-bg-surface-elevated);
|
|
5645
6081
|
}
|
|
@@ -5868,6 +6304,32 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
5868
6304
|
this.viewMonth = (/* @__PURE__ */ new Date()).getMonth();
|
|
5869
6305
|
this.isSelectingRange = false;
|
|
5870
6306
|
this.hoverDate = null;
|
|
6307
|
+
this.focusedDate = null;
|
|
6308
|
+
this.onReposition = () => {
|
|
6309
|
+
if (this.open && this.mode === "popover") this.updatePopoverPosition();
|
|
6310
|
+
};
|
|
6311
|
+
this.updatePopoverPosition = () => {
|
|
6312
|
+
const panel = this.popoverEl;
|
|
6313
|
+
if (!panel || !this.open || this.mode !== "popover") return;
|
|
6314
|
+
const t = this.getBoundingClientRect();
|
|
6315
|
+
const c = panel.getBoundingClientRect();
|
|
6316
|
+
let top = t.bottom + 4;
|
|
6317
|
+
if (top + c.height > window.innerHeight - 8) {
|
|
6318
|
+
top = Math.max(8, t.top - c.height - 4);
|
|
6319
|
+
}
|
|
6320
|
+
let left = t.left;
|
|
6321
|
+
if (left + c.width > window.innerWidth - 8) {
|
|
6322
|
+
left = Math.max(8, window.innerWidth - c.width - 8);
|
|
6323
|
+
}
|
|
6324
|
+
panel.style.position = "fixed";
|
|
6325
|
+
panel.style.top = `${top}px`;
|
|
6326
|
+
panel.style.left = `${left}px`;
|
|
6327
|
+
const applied = panel.getBoundingClientRect();
|
|
6328
|
+
top -= applied.top - top;
|
|
6329
|
+
left -= applied.left - left;
|
|
6330
|
+
panel.style.top = `${top}px`;
|
|
6331
|
+
panel.style.left = `${left}px`;
|
|
6332
|
+
};
|
|
5871
6333
|
// ── Open / close ──
|
|
5872
6334
|
this.handleClickOutside = (e) => {
|
|
5873
6335
|
if (!this.open) return;
|
|
@@ -5899,6 +6361,69 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
5899
6361
|
const qs = this.getQuickSelects().find((q) => q.label === label);
|
|
5900
6362
|
if (qs) this.selectQuickSelect(qs);
|
|
5901
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
|
+
};
|
|
5902
6427
|
}
|
|
5903
6428
|
// ── Lifecycle ──
|
|
5904
6429
|
connectedCallback() {
|
|
@@ -5910,14 +6435,64 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
5910
6435
|
super.disconnectedCallback();
|
|
5911
6436
|
document.removeEventListener("click", this.handleClickOutside);
|
|
5912
6437
|
document.removeEventListener("keydown", this.handleGlobalKeydown);
|
|
6438
|
+
window.removeEventListener("resize", this.onReposition);
|
|
6439
|
+
window.removeEventListener("scroll", this.onReposition, true);
|
|
6440
|
+
this.hideTopLayer();
|
|
5913
6441
|
}
|
|
5914
|
-
updated() {
|
|
6442
|
+
updated(changed) {
|
|
5915
6443
|
this.style.width = this.width;
|
|
5916
6444
|
this.style.minWidth = this.minWidth;
|
|
5917
6445
|
this.style.maxWidth = this.maxWidth;
|
|
5918
6446
|
this.style.height = this.height;
|
|
5919
6447
|
this.style.minHeight = this.minHeight;
|
|
5920
6448
|
this.style.maxHeight = this.maxHeight;
|
|
6449
|
+
if (changed.has("open")) {
|
|
6450
|
+
if (this.open) {
|
|
6451
|
+
this.showTopLayer();
|
|
6452
|
+
window.addEventListener("resize", this.onReposition);
|
|
6453
|
+
window.addEventListener("scroll", this.onReposition, true);
|
|
6454
|
+
} else {
|
|
6455
|
+
window.removeEventListener("resize", this.onReposition);
|
|
6456
|
+
window.removeEventListener("scroll", this.onReposition, true);
|
|
6457
|
+
this.hideTopLayer();
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
}
|
|
6461
|
+
/* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
6462
|
+
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
6463
|
+
* descendants, and its overflow:hidden clips position:absolute ones —
|
|
6464
|
+
* either way the picker shell breaks inside a modal. Promoting shell
|
|
6465
|
+
* (and popup-mode backdrop) to the browser top layer sidesteps both —
|
|
6466
|
+
* same fix as rel-popover / rel-autocomplete / rel-select. `manual`
|
|
6467
|
+
* keeps outside-click and Escape handling under this component's own
|
|
6468
|
+
* control. While in the top layer the CSS anchoring (absolute, below
|
|
6469
|
+
* the trigger) is meaningless, so popover mode gets inline fixed
|
|
6470
|
+
* coordinates from updatePopoverPosition(). */
|
|
6471
|
+
showTopLayer() {
|
|
6472
|
+
for (const el of [this.mode === "popup" ? this.backdropEl : void 0, this.popoverEl]) {
|
|
6473
|
+
if (!el || typeof el.showPopover !== "function") continue;
|
|
6474
|
+
try {
|
|
6475
|
+
if (!el.matches(":popover-open")) el.showPopover();
|
|
6476
|
+
} catch {
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
6479
|
+
if (this.mode === "popover") {
|
|
6480
|
+
requestAnimationFrame(() => this.updatePopoverPosition());
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
hideTopLayer() {
|
|
6484
|
+
for (const el of [this.popoverEl, this.backdropEl]) {
|
|
6485
|
+
if (!el || typeof el.hidePopover !== "function") continue;
|
|
6486
|
+
try {
|
|
6487
|
+
if (el.matches(":popover-open")) el.hidePopover();
|
|
6488
|
+
} catch {
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
if (this.popoverEl) {
|
|
6492
|
+
this.popoverEl.style.position = "";
|
|
6493
|
+
this.popoverEl.style.top = "";
|
|
6494
|
+
this.popoverEl.style.left = "";
|
|
6495
|
+
}
|
|
5921
6496
|
}
|
|
5922
6497
|
toggleOpen() {
|
|
5923
6498
|
if (this.disabled) return;
|
|
@@ -5933,6 +6508,7 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
5933
6508
|
this.draftActivePreset = null;
|
|
5934
6509
|
this.isSelectingRange = false;
|
|
5935
6510
|
this.hoverDate = null;
|
|
6511
|
+
this.focusedDate = null;
|
|
5936
6512
|
if (this.draftFrom) {
|
|
5937
6513
|
const date = this.parseISODate(this.draftFrom);
|
|
5938
6514
|
if (date) {
|
|
@@ -6231,10 +6807,45 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
6231
6807
|
if (!this.isSelectingRange || day.isDisabled) return;
|
|
6232
6808
|
this.hoverDate = this.formatISODate(day.date);
|
|
6233
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
|
+
}
|
|
6234
6844
|
// ── Render ──
|
|
6235
6845
|
render() {
|
|
6236
6846
|
const quickSelects = this.getQuickSelects();
|
|
6237
6847
|
const days = this.getCalendarDays();
|
|
6848
|
+
const rovingIso = this.getRovingIso(days);
|
|
6238
6849
|
const weekdayLabels = this.getWeekdayLabels();
|
|
6239
6850
|
const draftFromDisplay = this.draftFrom ? this.formatDisplayDate(this.draftFrom) : "";
|
|
6240
6851
|
const draftToDisplay = this.draftTo ? this.formatDisplayDate(this.draftTo) : "";
|
|
@@ -6273,10 +6884,11 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
6273
6884
|
</button>
|
|
6274
6885
|
`}
|
|
6275
6886
|
|
|
6276
|
-
${isPopup && this.open ? import_lit15.html`<div part="overlay" class="backdrop" @click=${this.cancelPopover}></div>` : import_lit15.nothing}
|
|
6887
|
+
${isPopup && this.open ? import_lit15.html`<div part="overlay" class="backdrop" popover="manual" @click=${this.cancelPopover}></div>` : import_lit15.nothing}
|
|
6277
6888
|
|
|
6278
6889
|
<div
|
|
6279
6890
|
part="panel" class="popover ${this.open ? "open" : ""} ${isPopup ? "popup" : ""}"
|
|
6891
|
+
popover="manual"
|
|
6280
6892
|
role="dialog"
|
|
6281
6893
|
aria-label="Select date range"
|
|
6282
6894
|
>
|
|
@@ -6362,8 +6974,10 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
6362
6974
|
@mouseleave=${() => {
|
|
6363
6975
|
this.hoverDate = null;
|
|
6364
6976
|
}}
|
|
6977
|
+
@keydown=${this.handleGridKeydown}
|
|
6365
6978
|
>
|
|
6366
6979
|
${days.map((day) => {
|
|
6980
|
+
const iso = this.formatISODate(day.date);
|
|
6367
6981
|
const cellClasses = [
|
|
6368
6982
|
"day-cell",
|
|
6369
6983
|
day.isInRange ? "in-range" : "",
|
|
@@ -6378,8 +6992,11 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
6378
6992
|
<div
|
|
6379
6993
|
class="${cellClasses}"
|
|
6380
6994
|
role="gridcell"
|
|
6381
|
-
|
|
6995
|
+
tabindex="${day.isCurrentMonth && iso === rovingIso ? 0 : -1}"
|
|
6996
|
+
data-iso="${iso}"
|
|
6997
|
+
aria-label="${iso}"
|
|
6382
6998
|
aria-selected="${day.isDraftFrom || day.isDraftTo || day.isInRange}"
|
|
6999
|
+
aria-disabled="${day.isDisabled ? "true" : import_lit15.nothing}"
|
|
6383
7000
|
@click=${() => this.handleDayClick(day)}
|
|
6384
7001
|
@mouseenter=${() => this.handleDayHover(day)}
|
|
6385
7002
|
>
|
|
@@ -6487,6 +7104,17 @@ RelRangePicker.styles = import_lit15.css`
|
|
|
6487
7104
|
|
|
6488
7105
|
/* ── Popover shell ── */
|
|
6489
7106
|
.popover {
|
|
7107
|
+
/* Reset native [popover] UA styles first (the shell is promoted to
|
|
7108
|
+
the top layer so transformed / overflow:hidden ancestors — e.g. a
|
|
7109
|
+
centered rel-modal panel — can't hijack or clip it). While in the
|
|
7110
|
+
top layer, JS overrides the anchored position below with inline
|
|
7111
|
+
fixed coordinates; the CSS anchoring is the no-Popover-API
|
|
7112
|
+
fallback. */
|
|
7113
|
+
margin: 0;
|
|
7114
|
+
padding: 0;
|
|
7115
|
+
width: auto;
|
|
7116
|
+
height: auto;
|
|
7117
|
+
color: inherit;
|
|
6490
7118
|
position: absolute;
|
|
6491
7119
|
top: calc(100% + 4px);
|
|
6492
7120
|
left: 0;
|
|
@@ -6511,9 +7139,16 @@ RelRangePicker.styles = import_lit15.css`
|
|
|
6511
7139
|
.backdrop {
|
|
6512
7140
|
position: fixed;
|
|
6513
7141
|
inset: 0;
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
7142
|
+
/* Reset native [popover] UA styles — the backdrop is promoted to the
|
|
7143
|
+
top layer right before the panel so the panel stacks above it. */
|
|
7144
|
+
margin: 0;
|
|
7145
|
+
padding: 0;
|
|
7146
|
+
width: auto;
|
|
7147
|
+
height: auto;
|
|
7148
|
+
border: none;
|
|
7149
|
+
z-index: 2147483646;
|
|
7150
|
+
background: rgba(0,0,0,0.4);
|
|
7151
|
+
}
|
|
6517
7152
|
|
|
6518
7153
|
/* ── Two-panel wrapper ── */
|
|
6519
7154
|
.picker-wrapper {
|
|
@@ -6706,6 +7341,12 @@ RelRangePicker.styles = import_lit15.css`
|
|
|
6706
7341
|
cursor: pointer;
|
|
6707
7342
|
}
|
|
6708
7343
|
|
|
7344
|
+
.day-cell:focus-visible {
|
|
7345
|
+
outline: 2px solid var(--rel-border-focus);
|
|
7346
|
+
outline-offset: -2px;
|
|
7347
|
+
z-index: 2;
|
|
7348
|
+
}
|
|
7349
|
+
|
|
6709
7350
|
.day-cell.other-month {
|
|
6710
7351
|
opacity: 0.35;
|
|
6711
7352
|
pointer-events: none;
|
|
@@ -6881,9 +7522,18 @@ __decorateClass([
|
|
|
6881
7522
|
__decorateClass([
|
|
6882
7523
|
(0, import_decorators15.state)()
|
|
6883
7524
|
], RelRangePicker.prototype, "hoverDate", 2);
|
|
7525
|
+
__decorateClass([
|
|
7526
|
+
(0, import_decorators15.state)()
|
|
7527
|
+
], RelRangePicker.prototype, "focusedDate", 2);
|
|
6884
7528
|
__decorateClass([
|
|
6885
7529
|
(0, import_decorators15.query)(".trigger-button")
|
|
6886
7530
|
], RelRangePicker.prototype, "triggerButton", 2);
|
|
7531
|
+
__decorateClass([
|
|
7532
|
+
(0, import_decorators15.query)(".popover")
|
|
7533
|
+
], RelRangePicker.prototype, "popoverEl", 2);
|
|
7534
|
+
__decorateClass([
|
|
7535
|
+
(0, import_decorators15.query)(".backdrop")
|
|
7536
|
+
], RelRangePicker.prototype, "backdropEl", 2);
|
|
6887
7537
|
RelRangePicker = __decorateClass([
|
|
6888
7538
|
(0, import_decorators15.customElement)("rel-range-picker")
|
|
6889
7539
|
], RelRangePicker);
|
|
@@ -6904,6 +7554,33 @@ var RelChip = class extends import_lit16.LitElement {
|
|
|
6904
7554
|
this.removed = false;
|
|
6905
7555
|
this.draggableChip = false;
|
|
6906
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
|
+
};
|
|
6907
7584
|
this.onDragStart = (e) => {
|
|
6908
7585
|
if (!this.draggableChip) return;
|
|
6909
7586
|
this.dragging = true;
|
|
@@ -6957,6 +7634,7 @@ var RelChip = class extends import_lit16.LitElement {
|
|
|
6957
7634
|
role="button"
|
|
6958
7635
|
tabindex="0"
|
|
6959
7636
|
aria-label="${this.label}"
|
|
7637
|
+
@keydown=${this.handleKeydown}
|
|
6960
7638
|
>
|
|
6961
7639
|
${showAvatar ? import_lit16.html`<rel-avatar type="text" text=${this.avatar} size="xs"></rel-avatar>` : null}
|
|
6962
7640
|
${showLeadingIcon ? import_lit16.html`<rel-icon part="icon" class="chip-icon" name="${this.leadingIcon}"></rel-icon>` : null}
|
|
@@ -7548,26 +8226,8 @@ var RelDrawer = class extends import_lit20.LitElement {
|
|
|
7548
8226
|
this.close("escape");
|
|
7549
8227
|
return;
|
|
7550
8228
|
}
|
|
7551
|
-
if (e.key === "Tab" && this.modal) {
|
|
7552
|
-
|
|
7553
|
-
if (focusable.length === 0) {
|
|
7554
|
-
e.preventDefault();
|
|
7555
|
-
return;
|
|
7556
|
-
}
|
|
7557
|
-
const firstFocusable = focusable[0];
|
|
7558
|
-
const lastFocusable = focusable[focusable.length - 1];
|
|
7559
|
-
const activeElement = document.activeElement;
|
|
7560
|
-
if (e.shiftKey) {
|
|
7561
|
-
if (activeElement === firstFocusable) {
|
|
7562
|
-
e.preventDefault();
|
|
7563
|
-
lastFocusable.focus();
|
|
7564
|
-
}
|
|
7565
|
-
} else {
|
|
7566
|
-
if (activeElement === lastFocusable) {
|
|
7567
|
-
e.preventDefault();
|
|
7568
|
-
firstFocusable.focus();
|
|
7569
|
-
}
|
|
7570
|
-
}
|
|
8229
|
+
if (e.key === "Tab" && this.modal && this.panelElement) {
|
|
8230
|
+
trapTabKey(e, this.panelElement);
|
|
7571
8231
|
}
|
|
7572
8232
|
};
|
|
7573
8233
|
// Drag-to-close handlers
|
|
@@ -7626,12 +8286,19 @@ var RelDrawer = class extends import_lit20.LitElement {
|
|
|
7626
8286
|
}
|
|
7627
8287
|
}
|
|
7628
8288
|
handleOpen() {
|
|
7629
|
-
this.previousFocus =
|
|
8289
|
+
this.previousFocus = getDeepActiveElement();
|
|
7630
8290
|
this.lockScroll();
|
|
7631
8291
|
if (this.modal) {
|
|
7632
8292
|
this.updateComplete.then(() => {
|
|
7633
|
-
const
|
|
7634
|
-
|
|
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
|
+
}
|
|
7635
8302
|
});
|
|
7636
8303
|
}
|
|
7637
8304
|
document.addEventListener("keydown", this.handleKeydown);
|
|
@@ -7693,29 +8360,6 @@ var RelDrawer = class extends import_lit20.LitElement {
|
|
|
7693
8360
|
document.body.style.overflow = this.originalBodyOverflow;
|
|
7694
8361
|
document.body.style.paddingRight = this.originalBodyPaddingRight;
|
|
7695
8362
|
}
|
|
7696
|
-
getFocusableElements() {
|
|
7697
|
-
if (!this.panelElement) return [];
|
|
7698
|
-
const selectors = [
|
|
7699
|
-
"a[href]",
|
|
7700
|
-
"button:not([disabled])",
|
|
7701
|
-
"textarea:not([disabled])",
|
|
7702
|
-
"input:not([disabled])",
|
|
7703
|
-
"select:not([disabled])",
|
|
7704
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
7705
|
-
].join(",");
|
|
7706
|
-
const elements = Array.from(
|
|
7707
|
-
this.panelElement.querySelectorAll(selectors)
|
|
7708
|
-
);
|
|
7709
|
-
const slots = this.panelElement.querySelectorAll("slot");
|
|
7710
|
-
slots.forEach((slot) => {
|
|
7711
|
-
const assigned = slot.assignedElements({ flatten: true });
|
|
7712
|
-
assigned.forEach((el) => {
|
|
7713
|
-
const focusableInSlot = el.querySelectorAll(selectors);
|
|
7714
|
-
elements.push(...Array.from(focusableInSlot));
|
|
7715
|
-
});
|
|
7716
|
-
});
|
|
7717
|
-
return elements;
|
|
7718
|
-
}
|
|
7719
8363
|
handleOverlayClick(e) {
|
|
7720
8364
|
if (!this.dismissible || !this.closeOnBackdrop) return;
|
|
7721
8365
|
if (e.target === this.overlayElement) {
|
|
@@ -8726,12 +9370,16 @@ var RelMenu = class extends import_lit24.LitElement {
|
|
|
8726
9370
|
this.type = "default";
|
|
8727
9371
|
this.items = [];
|
|
8728
9372
|
this.focusedIndex = 0;
|
|
9373
|
+
this.subFocusedIndex = -1;
|
|
8729
9374
|
this._items = [];
|
|
9375
|
+
/** Stable id prefix for aria-activedescendant targets. */
|
|
9376
|
+
this.menuId = `rel-menu-${Math.random().toString(36).slice(2)}`;
|
|
8730
9377
|
/** Index of a submenu locked open by a click (survives hover-out). -1 = none. */
|
|
8731
9378
|
this.lockedIndex = -1;
|
|
8732
9379
|
this.handleDocumentClick = (e) => {
|
|
8733
9380
|
if (!e.composedPath().includes(this)) {
|
|
8734
9381
|
this.lockedIndex = -1;
|
|
9382
|
+
this.subFocusedIndex = -1;
|
|
8735
9383
|
this._items = this._items.map((i) => ({ ...i, open: false }));
|
|
8736
9384
|
}
|
|
8737
9385
|
};
|
|
@@ -8818,35 +9466,95 @@ var RelMenu = class extends import_lit24.LitElement {
|
|
|
8818
9466
|
})
|
|
8819
9467
|
);
|
|
8820
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
|
+
}
|
|
8821
9481
|
onKeydown(ev) {
|
|
8822
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
|
+
}
|
|
8823
9518
|
if (ev.key === "ArrowDown") {
|
|
8824
9519
|
this.focusedIndex = Math.min(last, this.focusedIndex + 1);
|
|
8825
9520
|
ev.preventDefault();
|
|
8826
9521
|
} else if (ev.key === "ArrowUp") {
|
|
8827
9522
|
this.focusedIndex = Math.max(0, this.focusedIndex - 1);
|
|
8828
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();
|
|
8829
9530
|
} else if (ev.key === "ArrowRight") {
|
|
8830
9531
|
const item = this._items[this.focusedIndex];
|
|
8831
|
-
if (this.type === "arrow" && item?.submenu?.length) {
|
|
8832
|
-
this.
|
|
8833
|
-
|
|
8834
|
-
...i,
|
|
8835
|
-
open: idx === this.focusedIndex ? true : i.open
|
|
8836
|
-
}));
|
|
9532
|
+
if (this.type === "arrow" && item?.submenu?.length && !item.disabled) {
|
|
9533
|
+
this.openSubmenuAt(this.focusedIndex);
|
|
9534
|
+
ev.preventDefault();
|
|
8837
9535
|
}
|
|
8838
9536
|
} else if (ev.key === "ArrowLeft" || ev.key === "Escape") {
|
|
8839
|
-
this.
|
|
8840
|
-
|
|
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
|
+
}
|
|
8841
9543
|
} else if (ev.key === "Enter" || ev.key === " ") {
|
|
8842
9544
|
const item = this._items[this.focusedIndex];
|
|
8843
|
-
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
|
+
}
|
|
8844
9550
|
ev.preventDefault();
|
|
8845
9551
|
}
|
|
8846
9552
|
}
|
|
8847
9553
|
renderMenuItem(item, index) {
|
|
8848
|
-
const isFocused = this.focusedIndex === index;
|
|
9554
|
+
const isFocused = this.focusedIndex === index && this.subFocusedIndex < 0;
|
|
8849
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;
|
|
8850
9558
|
const control = this.type === "checkbox" ? import_lit24.html`<rel-checkbox
|
|
8851
9559
|
class="menu-control"
|
|
8852
9560
|
label="${item.label}"
|
|
@@ -8864,8 +9572,13 @@ var RelMenu = class extends import_lit24.LitElement {
|
|
|
8864
9572
|
`;
|
|
8865
9573
|
return import_lit24.html`
|
|
8866
9574
|
<div
|
|
9575
|
+
id="${this.menuId}-item-${index}"
|
|
8867
9576
|
class="rel-menu-item ${item.active ? "active" : ""} ${item.disabled ? "disabled" : ""}"
|
|
8868
|
-
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}"
|
|
8869
9582
|
?data-focused="${isFocused}"
|
|
8870
9583
|
@mouseenter="${() => this.onItemEnter(item, index)}"
|
|
8871
9584
|
@mouseleave="${() => this.onItemLeave(item, index)}"
|
|
@@ -8874,12 +9587,16 @@ var RelMenu = class extends import_lit24.LitElement {
|
|
|
8874
9587
|
${hasSubmenu ? import_lit24.html`
|
|
8875
9588
|
<div
|
|
8876
9589
|
part="panel" class="rel-submenu ${item.open ? "visible" : ""}"
|
|
9590
|
+
role="menu"
|
|
8877
9591
|
@click="${(e) => e.stopPropagation()}">
|
|
8878
9592
|
${item.submenu.map(
|
|
8879
9593
|
(sub, subIndex) => import_lit24.html`
|
|
8880
9594
|
<div
|
|
8881
|
-
|
|
9595
|
+
id="${this.menuId}-item-${index}-sub-${subIndex}"
|
|
9596
|
+
class="rel-menu-item ${sub.active ? "active" : ""} ${sub.disabled ? "disabled" : ""}"
|
|
8882
9597
|
role="menuitem"
|
|
9598
|
+
aria-disabled="${sub.disabled ? "true" : import_lit24.nothing}"
|
|
9599
|
+
?data-focused="${this.focusedIndex === index && this.subFocusedIndex === subIndex}"
|
|
8883
9600
|
@click="${(e) => this.onSubClick(index, subIndex, e)}">
|
|
8884
9601
|
<span class="menu-label">${sub.label}</span>
|
|
8885
9602
|
</div>
|
|
@@ -8891,12 +9608,14 @@ var RelMenu = class extends import_lit24.LitElement {
|
|
|
8891
9608
|
`;
|
|
8892
9609
|
}
|
|
8893
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}`;
|
|
8894
9612
|
return import_lit24.html`
|
|
8895
9613
|
<style>${RelMenu._css}</style>
|
|
8896
9614
|
<div
|
|
8897
9615
|
part="list" class="rel-menu ${this.type === "fixed" || this.type === "fit" ? this.type : ""}"
|
|
8898
9616
|
role="menu"
|
|
8899
9617
|
tabindex="0"
|
|
9618
|
+
aria-activedescendant="${activeDescendant}"
|
|
8900
9619
|
@keydown="${this.onKeydown}"
|
|
8901
9620
|
@click="${(e) => e.stopPropagation()}">
|
|
8902
9621
|
${this._items.map((item, index) => this.renderMenuItem(item, index))}
|
|
@@ -8986,7 +9705,10 @@ RelMenu._css = `
|
|
|
8986
9705
|
cursor: not-allowed;
|
|
8987
9706
|
}
|
|
8988
9707
|
|
|
8989
|
-
|
|
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] {
|
|
8990
9712
|
outline: 2px solid var(--rel-border-focus);
|
|
8991
9713
|
outline-offset: -2px;
|
|
8992
9714
|
}
|
|
@@ -9077,6 +9799,9 @@ __decorateClass([
|
|
|
9077
9799
|
__decorateClass([
|
|
9078
9800
|
(0, import_decorators24.state)()
|
|
9079
9801
|
], RelMenu.prototype, "focusedIndex", 2);
|
|
9802
|
+
__decorateClass([
|
|
9803
|
+
(0, import_decorators24.state)()
|
|
9804
|
+
], RelMenu.prototype, "subFocusedIndex", 2);
|
|
9080
9805
|
__decorateClass([
|
|
9081
9806
|
(0, import_decorators24.state)()
|
|
9082
9807
|
], RelMenu.prototype, "_items", 2);
|
|
@@ -9087,16 +9812,159 @@ RelMenu = __decorateClass([
|
|
|
9087
9812
|
// src/components/rel-menubar/rel-menubar.ts
|
|
9088
9813
|
var import_lit25 = require("lit");
|
|
9089
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
|
|
9090
9937
|
var import_rel_icon10 = require("@reliability-design/icons/rel-icon");
|
|
9091
9938
|
var RelMenubar = class extends import_lit25.LitElement {
|
|
9092
9939
|
constructor() {
|
|
9093
9940
|
super(...arguments);
|
|
9941
|
+
this.bindShortcuts = false;
|
|
9094
9942
|
this.menus = [];
|
|
9095
9943
|
this.openMenuIndex = -1;
|
|
9096
9944
|
this.focusedTriggerIndex = 0;
|
|
9097
9945
|
this.focusedItemIndex = -1;
|
|
9946
|
+
this.focusedSubItemIndex = -1;
|
|
9098
9947
|
this.openSubmenuElement = null;
|
|
9099
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
|
+
};
|
|
9100
9968
|
this.handleOutsideClick = (e) => {
|
|
9101
9969
|
if (!e.composedPath().includes(this)) {
|
|
9102
9970
|
this.closeAllMenus();
|
|
@@ -9110,6 +9978,7 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9110
9978
|
super.connectedCallback();
|
|
9111
9979
|
this.discoverMenus();
|
|
9112
9980
|
document.addEventListener("click", this.handleOutsideClick);
|
|
9981
|
+
document.addEventListener("keydown", this.handleShortcutKeyDown);
|
|
9113
9982
|
this._onScroll = () => {
|
|
9114
9983
|
this._repositionOpenMenu();
|
|
9115
9984
|
this._repositionOpenSubmenu();
|
|
@@ -9119,6 +9988,7 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9119
9988
|
disconnectedCallback() {
|
|
9120
9989
|
super.disconnectedCallback();
|
|
9121
9990
|
document.removeEventListener("click", this.handleOutsideClick);
|
|
9991
|
+
document.removeEventListener("keydown", this.handleShortcutKeyDown);
|
|
9122
9992
|
if (this._onScroll) {
|
|
9123
9993
|
window.removeEventListener("scroll", this._onScroll, { capture: true });
|
|
9124
9994
|
}
|
|
@@ -9152,12 +10022,26 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9152
10022
|
closeAllMenus() {
|
|
9153
10023
|
this.openMenuIndex = -1;
|
|
9154
10024
|
this.openSubmenuElement = null;
|
|
10025
|
+
this.focusedItemIndex = -1;
|
|
10026
|
+
this.focusedSubItemIndex = -1;
|
|
9155
10027
|
this.hoverSwitchEnabled = false;
|
|
9156
10028
|
this.requestUpdate();
|
|
9157
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
|
+
}
|
|
9158
10041
|
openMenu(index, triggerElement) {
|
|
9159
10042
|
this.openMenuIndex = index;
|
|
9160
10043
|
this.focusedItemIndex = -1;
|
|
10044
|
+
this.focusedSubItemIndex = -1;
|
|
9161
10045
|
this.openSubmenuElement = null;
|
|
9162
10046
|
this.requestUpdate();
|
|
9163
10047
|
this.updateComplete.then(() => {
|
|
@@ -9199,27 +10083,20 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9199
10083
|
event.preventDefault();
|
|
9200
10084
|
this.focusedTriggerIndex = Math.max(0, this.focusedTriggerIndex - 1);
|
|
9201
10085
|
this.focusTrigger(this.focusedTriggerIndex);
|
|
9202
|
-
} else if (key === "Enter" || key === " ") {
|
|
10086
|
+
} else if (key === "Enter" || key === " " || key === "ArrowDown") {
|
|
9203
10087
|
event.preventDefault();
|
|
9204
10088
|
const trigger = this.querySelector(
|
|
9205
10089
|
`.menubar-trigger[data-index="${this.focusedTriggerIndex}"]`
|
|
9206
10090
|
);
|
|
9207
10091
|
if (trigger) {
|
|
9208
|
-
this.
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
);
|
|
9213
|
-
if (focusTarget) {
|
|
9214
|
-
focusTarget.focus();
|
|
9215
|
-
} else {
|
|
9216
|
-
this.focusMenuItem(0);
|
|
9217
|
-
}
|
|
9218
|
-
});
|
|
10092
|
+
if (this.openMenuIndex !== this.focusedTriggerIndex) {
|
|
10093
|
+
this.openMenu(this.focusedTriggerIndex, trigger);
|
|
10094
|
+
}
|
|
10095
|
+
this.updateComplete.then(() => this.focusMenuItem(0));
|
|
9219
10096
|
}
|
|
9220
|
-
} else if (key === "
|
|
10097
|
+
} else if (key === "Escape" && this.openMenuIndex !== -1) {
|
|
9221
10098
|
event.preventDefault();
|
|
9222
|
-
this.
|
|
10099
|
+
this.closeAllMenusAndRefocusTrigger();
|
|
9223
10100
|
}
|
|
9224
10101
|
}
|
|
9225
10102
|
focusTrigger(index) {
|
|
@@ -9232,6 +10109,36 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9232
10109
|
const { key } = event;
|
|
9233
10110
|
const menuContent = this.menus[this.openMenuIndex]?.menu;
|
|
9234
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
|
+
}
|
|
9235
10142
|
const items = this.getFocusableItems(menuContent);
|
|
9236
10143
|
if (key === "ArrowDown") {
|
|
9237
10144
|
event.preventDefault();
|
|
@@ -9241,30 +10148,56 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9241
10148
|
event.preventDefault();
|
|
9242
10149
|
this.focusedItemIndex = Math.max(0, this.focusedItemIndex - 1);
|
|
9243
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();
|
|
9244
10159
|
} else if (key === "ArrowRight") {
|
|
9245
10160
|
event.preventDefault();
|
|
9246
10161
|
const currentItem = items[this.focusedItemIndex];
|
|
9247
10162
|
if (currentItem?.hasAttribute("data-submenu")) {
|
|
9248
|
-
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);
|
|
9249
10171
|
}
|
|
9250
10172
|
} else if (key === "ArrowLeft") {
|
|
9251
10173
|
event.preventDefault();
|
|
9252
|
-
if (this.openSubmenuElement) {
|
|
9253
|
-
this.openSubmenuElement = null;
|
|
9254
|
-
this.requestUpdate();
|
|
9255
|
-
}
|
|
9256
10174
|
} else if (key === "Escape") {
|
|
9257
10175
|
event.preventDefault();
|
|
9258
|
-
this.
|
|
9259
|
-
this.focusTrigger(this.openMenuIndex);
|
|
10176
|
+
this.closeAllMenusAndRefocusTrigger();
|
|
9260
10177
|
} else if (key === "Enter" || key === " ") {
|
|
9261
10178
|
event.preventDefault();
|
|
9262
10179
|
const currentItem = items[this.focusedItemIndex];
|
|
9263
10180
|
if (currentItem) {
|
|
9264
|
-
|
|
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
|
+
}
|
|
9265
10193
|
}
|
|
9266
10194
|
}
|
|
9267
10195
|
}
|
|
10196
|
+
getSubmenuItems(submenuElement) {
|
|
10197
|
+
return Array.from(submenuElement.children).filter(
|
|
10198
|
+
(el) => el.hasAttribute("data-item")
|
|
10199
|
+
);
|
|
10200
|
+
}
|
|
9268
10201
|
getFocusableItems(menuContent) {
|
|
9269
10202
|
return Array.from(menuContent.children).filter(
|
|
9270
10203
|
(el) => el.hasAttribute("data-item") || el.hasAttribute("data-checkbox") || el.hasAttribute("data-radio") || el.hasAttribute("data-submenu")
|
|
@@ -9301,27 +10234,41 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9301
10234
|
} else if (item.hasAttribute("data-submenu")) {
|
|
9302
10235
|
this.openSubmenu(item);
|
|
9303
10236
|
} else {
|
|
9304
|
-
this.
|
|
10237
|
+
this.dispatchSelectEvent(item);
|
|
10238
|
+
this.closeAllMenusAndRefocusTrigger();
|
|
9305
10239
|
}
|
|
9306
10240
|
}
|
|
9307
|
-
|
|
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) {
|
|
9308
10255
|
this.openSubmenuElement = submenuTrigger;
|
|
9309
10256
|
this.requestUpdate();
|
|
9310
10257
|
this.updateComplete.then(() => {
|
|
9311
|
-
const
|
|
9312
|
-
const submenuPanel = this.querySelector(
|
|
9313
|
-
`.submenu-panel[data-submenu-id="${submenuId}"]`
|
|
9314
|
-
);
|
|
10258
|
+
const submenuPanel = this.querySelector(".submenu-panel.open");
|
|
9315
10259
|
if (submenuPanel) {
|
|
9316
|
-
const positionTarget = renderedTrigger ??
|
|
9317
|
-
const rect = positionTarget
|
|
9318
|
-
|
|
9319
|
-
|
|
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
|
+
}
|
|
9320
10266
|
}
|
|
9321
10267
|
});
|
|
9322
10268
|
}
|
|
9323
|
-
handleSubmenuItemClick(
|
|
9324
|
-
this.
|
|
10269
|
+
handleSubmenuItemClick(item) {
|
|
10270
|
+
this.dispatchSelectEvent(item);
|
|
10271
|
+
this.closeAllMenusAndRefocusTrigger();
|
|
9325
10272
|
}
|
|
9326
10273
|
dispatchChangeEvent(type, labelOrName, value) {
|
|
9327
10274
|
const detail = type === "checkbox" ? { type, label: labelOrName, name: "", checked: value } : { type, label: "", name: labelOrName, value };
|
|
@@ -9333,16 +10280,21 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9333
10280
|
})
|
|
9334
10281
|
);
|
|
9335
10282
|
}
|
|
9336
|
-
renderMenuItem(item,
|
|
9337
|
-
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;
|
|
9338
10286
|
if (item.hasAttribute("data-item")) {
|
|
9339
10287
|
const button = item;
|
|
9340
10288
|
const disabled = button.disabled;
|
|
9341
10289
|
const shortcut = button.getAttribute("data-shortcut") || "";
|
|
9342
10290
|
return import_lit25.html`
|
|
9343
10291
|
<div
|
|
10292
|
+
id="${itemId}"
|
|
9344
10293
|
class="menu-item ${isFocused ? "focused" : ""}"
|
|
10294
|
+
role="menuitem"
|
|
10295
|
+
data-focus-index="${focusIndex}"
|
|
9345
10296
|
data-disabled="${disabled}"
|
|
10297
|
+
aria-disabled="${disabled ? "true" : import_lit25.nothing}"
|
|
9346
10298
|
@click="${() => !disabled && this.handleItemClick(item)}"
|
|
9347
10299
|
>
|
|
9348
10300
|
<span>${button.textContent}</span>
|
|
@@ -9351,7 +10303,7 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9351
10303
|
`;
|
|
9352
10304
|
}
|
|
9353
10305
|
if (item.hasAttribute("data-separator")) {
|
|
9354
|
-
return import_lit25.html`<div class="menu-separator"></div>`;
|
|
10306
|
+
return import_lit25.html`<div class="menu-separator" role="separator"></div>`;
|
|
9355
10307
|
}
|
|
9356
10308
|
if (item.hasAttribute("data-checkbox")) {
|
|
9357
10309
|
const label = item;
|
|
@@ -9359,6 +10311,8 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9359
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") || "";
|
|
9360
10312
|
return import_lit25.html`
|
|
9361
10313
|
<rel-checkbox
|
|
10314
|
+
id="${itemId}"
|
|
10315
|
+
data-focus-index="${focusIndex}"
|
|
9362
10316
|
label=${labelText}
|
|
9363
10317
|
.status=${checkbox?.checked ? "checked" : "unchecked"}
|
|
9364
10318
|
@rel-change=${(e) => {
|
|
@@ -9370,17 +10324,21 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9370
10324
|
}
|
|
9371
10325
|
if (item.hasAttribute("data-submenu")) {
|
|
9372
10326
|
const label = item.getAttribute("label") || item.textContent?.trim() || "";
|
|
9373
|
-
const submenuId = `submenu-${index}`;
|
|
9374
10327
|
return import_lit25.html`
|
|
9375
10328
|
<div
|
|
10329
|
+
id="${itemId}"
|
|
9376
10330
|
class="menu-item menu-submenu-trigger ${isFocused ? "focused" : ""}"
|
|
9377
|
-
|
|
9378
|
-
|
|
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)}"
|
|
9379
10337
|
>
|
|
9380
10338
|
<span>${label}</span>
|
|
9381
10339
|
<rel-icon name="chevron-right"></rel-icon>
|
|
9382
10340
|
</div>
|
|
9383
|
-
${this.renderSubmenu(item
|
|
10341
|
+
${this.renderSubmenu(item)}
|
|
9384
10342
|
`;
|
|
9385
10343
|
}
|
|
9386
10344
|
if (item.hasAttribute("data-radio-group")) {
|
|
@@ -9405,24 +10363,23 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9405
10363
|
}
|
|
9406
10364
|
return null;
|
|
9407
10365
|
}
|
|
9408
|
-
renderSubmenu(submenuElement
|
|
10366
|
+
renderSubmenu(submenuElement) {
|
|
9409
10367
|
const isOpen = this.openSubmenuElement === submenuElement;
|
|
9410
|
-
const submenuItems =
|
|
10368
|
+
const submenuItems = this.getSubmenuItems(submenuElement);
|
|
9411
10369
|
return import_lit25.html`
|
|
9412
|
-
<div class="submenu-panel ${isOpen ? "open" : ""}"
|
|
9413
|
-
${submenuItems.map((subItem) => {
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
</
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
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
|
+
`;
|
|
9426
10383
|
})}
|
|
9427
10384
|
</div>
|
|
9428
10385
|
`;
|
|
@@ -9430,28 +10387,40 @@ var RelMenubar = class extends import_lit25.LitElement {
|
|
|
9430
10387
|
renderMenuPanel(menuState, index) {
|
|
9431
10388
|
const isOpen = index === this.openMenuIndex;
|
|
9432
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;
|
|
9433
10392
|
return import_lit25.html`
|
|
9434
10393
|
<div
|
|
9435
10394
|
class="menubar-panel ${isOpen ? "open" : ""}"
|
|
9436
10395
|
data-menu-index="${index}"
|
|
9437
10396
|
tabindex="-1"
|
|
10397
|
+
role="menu"
|
|
10398
|
+
aria-activedescendant="${activeDescendant}"
|
|
9438
10399
|
@keydown="${this.handlePanelKeyDown}"
|
|
9439
10400
|
>
|
|
9440
|
-
${items.map(
|
|
10401
|
+
${items.map(
|
|
10402
|
+
(item) => this.renderMenuItem(item, focusables.indexOf(item), index)
|
|
10403
|
+
)}
|
|
9441
10404
|
</div>
|
|
9442
10405
|
`;
|
|
9443
10406
|
}
|
|
9444
10407
|
render() {
|
|
9445
10408
|
return import_lit25.html`
|
|
9446
10409
|
<style>${RelMenubar._css}</style>
|
|
9447
|
-
<div class="menubar-container">
|
|
10410
|
+
<div class="menubar-container" role="menubar">
|
|
9448
10411
|
${this.menus.map(
|
|
9449
10412
|
(menuState, index) => import_lit25.html`
|
|
9450
10413
|
<button
|
|
9451
10414
|
class="menubar-trigger ${index === this.openMenuIndex ? "active" : ""}"
|
|
9452
10415
|
data-index="${index}"
|
|
10416
|
+
role="menuitem"
|
|
10417
|
+
aria-haspopup="menu"
|
|
10418
|
+
aria-expanded="${index === this.openMenuIndex}"
|
|
9453
10419
|
@click="${(e) => this.handleTriggerClick(index, e)}"
|
|
9454
10420
|
@mouseenter="${(e) => this.handleTriggerMouseEnter(index, e)}"
|
|
10421
|
+
@focus="${() => {
|
|
10422
|
+
this.focusedTriggerIndex = index;
|
|
10423
|
+
}}"
|
|
9455
10424
|
@keydown="${this.handleTriggerKeyDown}"
|
|
9456
10425
|
>
|
|
9457
10426
|
${menuState.menu.label}
|
|
@@ -9622,6 +10591,9 @@ RelMenubar._css = `
|
|
|
9622
10591
|
cursor: pointer;
|
|
9623
10592
|
}
|
|
9624
10593
|
`;
|
|
10594
|
+
__decorateClass([
|
|
10595
|
+
(0, import_decorators25.property)({ type: Boolean, attribute: "bind-shortcuts" })
|
|
10596
|
+
], RelMenubar.prototype, "bindShortcuts", 2);
|
|
9625
10597
|
__decorateClass([
|
|
9626
10598
|
(0, import_decorators25.state)()
|
|
9627
10599
|
], RelMenubar.prototype, "menus", 2);
|
|
@@ -9634,6 +10606,9 @@ __decorateClass([
|
|
|
9634
10606
|
__decorateClass([
|
|
9635
10607
|
(0, import_decorators25.state)()
|
|
9636
10608
|
], RelMenubar.prototype, "focusedItemIndex", 2);
|
|
10609
|
+
__decorateClass([
|
|
10610
|
+
(0, import_decorators25.state)()
|
|
10611
|
+
], RelMenubar.prototype, "focusedSubItemIndex", 2);
|
|
9637
10612
|
__decorateClass([
|
|
9638
10613
|
(0, import_decorators25.state)()
|
|
9639
10614
|
], RelMenubar.prototype, "openSubmenuElement", 2);
|
|
@@ -10302,7 +11277,6 @@ RelTopbar.styles = import_lit34.css`
|
|
|
10302
11277
|
color: var(--rel-topbar-item-color);
|
|
10303
11278
|
|
|
10304
11279
|
|
|
10305
|
-
|
|
10306
11280
|
text-decoration: none;
|
|
10307
11281
|
font-family: var(--rel-body-md-medium-font-family);
|
|
10308
11282
|
font-weight: var(--rel-body-md-medium-font-weight);
|
|
@@ -10552,8 +11526,6 @@ RelTopbarItem.styles = import_lit35.css`
|
|
|
10552
11526
|
color: var(--_item-color);
|
|
10553
11527
|
|
|
10554
11528
|
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
11529
|
text-decoration: none;
|
|
10558
11530
|
white-space: nowrap;
|
|
10559
11531
|
position: relative;
|
|
@@ -10785,9 +11757,7 @@ RelTopbarProfile.styles = import_lit36.css`
|
|
|
10785
11757
|
|
|
10786
11758
|
.name {
|
|
10787
11759
|
|
|
10788
|
-
|
|
10789
11760
|
color: var(--rel-text-default);
|
|
10790
|
-
|
|
10791
11761
|
white-space: nowrap;
|
|
10792
11762
|
overflow: hidden;
|
|
10793
11763
|
text-overflow: ellipsis;
|
|
@@ -10806,9 +11776,7 @@ RelTopbarProfile.styles = import_lit36.css`
|
|
|
10806
11776
|
|
|
10807
11777
|
.subtitle {
|
|
10808
11778
|
|
|
10809
|
-
|
|
10810
11779
|
color: var(--rel-text-placeholder);
|
|
10811
|
-
|
|
10812
11780
|
white-space: nowrap;
|
|
10813
11781
|
overflow: hidden;
|
|
10814
11782
|
text-overflow: ellipsis;
|
|
@@ -10833,9 +11801,7 @@ RelTopbarProfile.styles = import_lit36.css`
|
|
|
10833
11801
|
padding: 2px var(--rel-spacing-2xs, 4px);
|
|
10834
11802
|
border-radius: var(--rel-border-radius-xs, 2px);
|
|
10835
11803
|
|
|
10836
|
-
|
|
10837
11804
|
text-transform: uppercase;
|
|
10838
|
-
|
|
10839
11805
|
flex-shrink: 0;
|
|
10840
11806
|
font-family: var(--rel-body-xs-semibold-font-family);
|
|
10841
11807
|
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
@@ -11182,7 +12148,7 @@ var RelOtp = class extends import_lit37.LitElement {
|
|
|
11182
12148
|
data-index="${index}"
|
|
11183
12149
|
type="${this.mask ? "password" : "text"}"
|
|
11184
12150
|
inputmode="${this.getInputMode()}"
|
|
11185
|
-
autocomplete="
|
|
12151
|
+
autocomplete="one-time-code"
|
|
11186
12152
|
pattern="${this.pattern || ""}"
|
|
11187
12153
|
placeholder="${this.placeholder}"
|
|
11188
12154
|
maxlength="1"
|
|
@@ -11245,7 +12211,6 @@ RelOtp.styles = import_lit37.css`
|
|
|
11245
12211
|
align-items: center;
|
|
11246
12212
|
justify-content: center;
|
|
11247
12213
|
color: var(--rel-border-default);
|
|
11248
|
-
|
|
11249
12214
|
user-select: none;
|
|
11250
12215
|
padding: 0 var(--rel-spacing-3xs);
|
|
11251
12216
|
font-family: var(--rel-action-xl-font-family);
|
|
@@ -11266,7 +12231,6 @@ RelOtp.styles = import_lit37.css`
|
|
|
11266
12231
|
border-radius: var(--rel-border-radius-sm);
|
|
11267
12232
|
background: var(--rel-bg-neutral);
|
|
11268
12233
|
|
|
11269
|
-
|
|
11270
12234
|
text-align: center;
|
|
11271
12235
|
color: var(--rel-text-default);
|
|
11272
12236
|
outline: none;
|
|
@@ -11488,6 +12452,14 @@ var RelPagination = class extends import_lit38.LitElement {
|
|
|
11488
12452
|
}
|
|
11489
12453
|
return pages;
|
|
11490
12454
|
}
|
|
12455
|
+
/** Abbreviate large page numbers (368178 → "368k") so buttons stay compact.
|
|
12456
|
+
Exact below 10,000; floored to k up to 1M; then M with one decimal. */
|
|
12457
|
+
formatPageLabel(n) {
|
|
12458
|
+
if (n < 1e4) return String(n);
|
|
12459
|
+
if (n < 1e6) return `${Math.floor(n / 1e3)}k`;
|
|
12460
|
+
const m = Math.floor(n / 1e5) / 10;
|
|
12461
|
+
return `${Number.isInteger(m) ? m.toFixed(0) : m}M`;
|
|
12462
|
+
}
|
|
11491
12463
|
handlePageChange(newPage) {
|
|
11492
12464
|
const total = this.computedTotalPages;
|
|
11493
12465
|
if (this.disabled || newPage < 1 || newPage > total || newPage === this.activePage) return;
|
|
@@ -11533,15 +12505,20 @@ var RelPagination = class extends import_lit38.LitElement {
|
|
|
11533
12505
|
<div class="pag-stack">
|
|
11534
12506
|
${this.renderNavBtn("prev")}
|
|
11535
12507
|
<div class="pag-numbers">
|
|
11536
|
-
${pages.map(
|
|
11537
|
-
(p
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
12508
|
+
${pages.map((p) => {
|
|
12509
|
+
if (p === -1) return import_lit38.html`<span class="pag-ellipsis">...</span>`;
|
|
12510
|
+
const isNear = Math.abs(p - current) <= 1;
|
|
12511
|
+
const isEdge = p === 1 || p === total;
|
|
12512
|
+
const label = isEdge && !isNear ? this.formatPageLabel(p) : String(p);
|
|
12513
|
+
return import_lit38.html`<button
|
|
12514
|
+
type="button"
|
|
12515
|
+
class="pag-page-btn ${isNear ? "near" : ""} ${p === 1 || p === total ? "edge" : ""}"
|
|
12516
|
+
aria-label="Page ${p}"
|
|
12517
|
+
title="${label === String(p) ? import_lit38.nothing : `Page ${p}`}"
|
|
12518
|
+
aria-current="${p === current ? "page" : "false"}"
|
|
12519
|
+
@click="${() => this.handlePageChange(p)}"
|
|
12520
|
+
>${label}</button>`;
|
|
12521
|
+
})}
|
|
11545
12522
|
</div>
|
|
11546
12523
|
${this.renderNavBtn("next")}
|
|
11547
12524
|
</div>
|
|
@@ -11694,9 +12671,10 @@ RelPagination.styles = import_lit38.css`
|
|
|
11694
12671
|
display: inline-flex;
|
|
11695
12672
|
align-items: center;
|
|
11696
12673
|
justify-content: center;
|
|
11697
|
-
width:
|
|
12674
|
+
width: auto;
|
|
12675
|
+
min-width: 40px;
|
|
11698
12676
|
height: 40px;
|
|
11699
|
-
padding: var(--rel-spacing-xs);
|
|
12677
|
+
padding: 0 var(--rel-spacing-xs);
|
|
11700
12678
|
border-radius: var(--rel-border-radius-sm);
|
|
11701
12679
|
border: 1px solid var(--rel-border-default);
|
|
11702
12680
|
background: var(--rel-bg-neutral);
|
|
@@ -11944,6 +12922,14 @@ var RelPopover = class extends import_lit39.LitElement {
|
|
|
11944
12922
|
e.stopPropagation();
|
|
11945
12923
|
this.setOpen(!this.activeOpen);
|
|
11946
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
|
+
};
|
|
11947
12933
|
this.onOutsidePointerDown = (e) => {
|
|
11948
12934
|
if (!this.activeOpen) return;
|
|
11949
12935
|
const path = e.composedPath();
|
|
@@ -12017,10 +13003,12 @@ var RelPopover = class extends import_lit39.LitElement {
|
|
|
12017
13003
|
super.connectedCallback();
|
|
12018
13004
|
this.internalOpen = this.isControlled() ? !!this.open : this.defaultOpen;
|
|
12019
13005
|
this.addEventListener("pointerdown", this.onHostPointerDown, true);
|
|
13006
|
+
this.addEventListener("keydown", this.onHostKeyDown, true);
|
|
12020
13007
|
}
|
|
12021
13008
|
disconnectedCallback() {
|
|
12022
13009
|
super.disconnectedCallback();
|
|
12023
13010
|
this.removeEventListener("pointerdown", this.onHostPointerDown, true);
|
|
13011
|
+
this.removeEventListener("keydown", this.onHostKeyDown, true);
|
|
12024
13012
|
this.removeGlobalListeners();
|
|
12025
13013
|
this.hideNativePopover();
|
|
12026
13014
|
}
|
|
@@ -12061,7 +13049,7 @@ var RelPopover = class extends import_lit39.LitElement {
|
|
|
12061
13049
|
* OPEN / CLOSE
|
|
12062
13050
|
* ======================= */
|
|
12063
13051
|
onOpen() {
|
|
12064
|
-
this.returnFocusEl =
|
|
13052
|
+
this.returnFocusEl = getDeepActiveElement() ?? this.triggerEl;
|
|
12065
13053
|
this.addGlobalListeners();
|
|
12066
13054
|
this.showNativePopover();
|
|
12067
13055
|
requestAnimationFrame(() => {
|
|
@@ -12169,6 +13157,12 @@ var RelPopover = class extends import_lit39.LitElement {
|
|
|
12169
13157
|
this.triggerEl.setAttribute("aria-haspopup", "dialog");
|
|
12170
13158
|
this.triggerEl.setAttribute("aria-expanded", String(this.activeOpen));
|
|
12171
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
|
+
}
|
|
12172
13166
|
}
|
|
12173
13167
|
/* =======================
|
|
12174
13168
|
* RENDER
|
|
@@ -12648,6 +13642,14 @@ var RelResizableGroup = class extends import_lit42.LitElement {
|
|
|
12648
13642
|
const delta = e.key === "ArrowUp" ? -step : step;
|
|
12649
13643
|
this.applyKeyboardResize(handleIndex, delta);
|
|
12650
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
|
+
}
|
|
12651
13653
|
}
|
|
12652
13654
|
if (applied) {
|
|
12653
13655
|
this.dispatchResizeEvent("rel-resize-end");
|
|
@@ -12687,6 +13689,13 @@ var RelResizableGroup = class extends import_lit42.LitElement {
|
|
|
12687
13689
|
});
|
|
12688
13690
|
const sizes = this.calculateInitialSizes();
|
|
12689
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
|
+
});
|
|
12690
13699
|
}
|
|
12691
13700
|
calculateInitialSizes() {
|
|
12692
13701
|
const sizes = [];
|
|
@@ -13065,6 +14074,26 @@ var RelSegmentedButton = class extends import_lit45.LitElement {
|
|
|
13065
14074
|
this.active = "";
|
|
13066
14075
|
this.size = "medium";
|
|
13067
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
|
+
};
|
|
13068
14097
|
}
|
|
13069
14098
|
setActive(option) {
|
|
13070
14099
|
if (!option.disabled) {
|
|
@@ -13078,7 +14107,7 @@ var RelSegmentedButton = class extends import_lit45.LitElement {
|
|
|
13078
14107
|
);
|
|
13079
14108
|
}
|
|
13080
14109
|
}
|
|
13081
|
-
renderSegment(option) {
|
|
14110
|
+
renderSegment(option, rovingValue) {
|
|
13082
14111
|
const isActive = this.active === option.value;
|
|
13083
14112
|
const classes = [
|
|
13084
14113
|
"segment",
|
|
@@ -13088,6 +14117,9 @@ var RelSegmentedButton = class extends import_lit45.LitElement {
|
|
|
13088
14117
|
return import_lit45.html`
|
|
13089
14118
|
<button
|
|
13090
14119
|
part="option" class="${classes}"
|
|
14120
|
+
role="radio"
|
|
14121
|
+
aria-checked="${isActive}"
|
|
14122
|
+
tabindex="${!option.disabled && option.value === rovingValue ? 0 : -1}"
|
|
13091
14123
|
?disabled="${option.disabled}"
|
|
13092
14124
|
@click="${() => this.setActive(option)}">
|
|
13093
14125
|
${option.label}
|
|
@@ -13100,9 +14132,15 @@ var RelSegmentedButton = class extends import_lit45.LitElement {
|
|
|
13100
14132
|
this.size,
|
|
13101
14133
|
`align-${this.align}`
|
|
13102
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;
|
|
13103
14137
|
return import_lit45.html`
|
|
13104
|
-
<div
|
|
13105
|
-
${
|
|
14138
|
+
<div
|
|
14139
|
+
part="base" class="${containerClasses}"
|
|
14140
|
+
role="radiogroup"
|
|
14141
|
+
@keydown="${this.onKeyDown}"
|
|
14142
|
+
>
|
|
14143
|
+
${this.options.map((option) => this.renderSegment(option, rovingValue))}
|
|
13106
14144
|
</div>
|
|
13107
14145
|
`;
|
|
13108
14146
|
}
|
|
@@ -13264,6 +14302,8 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13264
14302
|
this.noSearch = false;
|
|
13265
14303
|
this.open = false;
|
|
13266
14304
|
this.highlightedIndex = -1;
|
|
14305
|
+
this.selectAllHighlighted = false;
|
|
14306
|
+
this.typeaheadBuffer = "";
|
|
13267
14307
|
this.panelStyle = "";
|
|
13268
14308
|
this.searchValue = "";
|
|
13269
14309
|
this.internalState = "default";
|
|
@@ -13299,11 +14339,20 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13299
14339
|
if (left + t.width > window.innerWidth - 8) {
|
|
13300
14340
|
left = Math.max(8, window.innerWidth - t.width - 8);
|
|
13301
14341
|
}
|
|
14342
|
+
this.listboxEl.style.top = `${top}px`;
|
|
14343
|
+
this.listboxEl.style.left = `${left}px`;
|
|
14344
|
+
this.listboxEl.style.width = `${t.width}px`;
|
|
14345
|
+
const applied = this.listboxEl.getBoundingClientRect();
|
|
14346
|
+
top -= applied.top - top;
|
|
14347
|
+
left -= applied.left - left;
|
|
14348
|
+
this.listboxEl.style.top = `${top}px`;
|
|
14349
|
+
this.listboxEl.style.left = `${left}px`;
|
|
13302
14350
|
this.panelStyle = `top:${top}px;left:${left}px;width:${t.width}px;`;
|
|
13303
14351
|
};
|
|
13304
14352
|
this.onSearchInput = (e) => {
|
|
13305
14353
|
this.searchValue = e.target.value;
|
|
13306
14354
|
this.highlightedIndex = this.filteredOptions.length > 0 ? 0 : -1;
|
|
14355
|
+
this.selectAllHighlighted = false;
|
|
13307
14356
|
requestAnimationFrame(() => this.updatePosition());
|
|
13308
14357
|
};
|
|
13309
14358
|
}
|
|
@@ -13316,6 +14365,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13316
14365
|
disconnectedCallback() {
|
|
13317
14366
|
super.disconnectedCallback();
|
|
13318
14367
|
this.removeGlobalListeners();
|
|
14368
|
+
this.hideNativePopover();
|
|
13319
14369
|
this.chipResizeObserver?.disconnect();
|
|
13320
14370
|
}
|
|
13321
14371
|
firstUpdated() {
|
|
@@ -13430,8 +14480,10 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13430
14480
|
this.open = true;
|
|
13431
14481
|
this.searchValue = "";
|
|
13432
14482
|
this.highlightedIndex = -1;
|
|
14483
|
+
this.selectAllHighlighted = false;
|
|
13433
14484
|
this.addGlobalListeners();
|
|
13434
14485
|
requestAnimationFrame(() => {
|
|
14486
|
+
this.showNativePopover();
|
|
13435
14487
|
this.updatePosition();
|
|
13436
14488
|
if (!this.noSearch) {
|
|
13437
14489
|
this.searchInputEl?.focus({ preventScroll: true });
|
|
@@ -13442,8 +14494,32 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13442
14494
|
if (!this.open) return;
|
|
13443
14495
|
this.open = false;
|
|
13444
14496
|
this.removeGlobalListeners();
|
|
14497
|
+
this.hideNativePopover();
|
|
13445
14498
|
this.triggerEl?.focus();
|
|
13446
14499
|
}
|
|
14500
|
+
/* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
14501
|
+
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
14502
|
+
* descendants, breaking viewport-based coordinates and letting the
|
|
14503
|
+
* panel's overflow:hidden clip the listbox. Promoting the listbox to
|
|
14504
|
+
* the browser top layer sidesteps both — same fix as rel-popover /
|
|
14505
|
+
* rel-autocomplete. `manual` keeps light-dismiss and Escape handling
|
|
14506
|
+
* under this component's own control. */
|
|
14507
|
+
showNativePopover() {
|
|
14508
|
+
const p = this.listboxEl;
|
|
14509
|
+
if (!p || typeof p.showPopover !== "function") return;
|
|
14510
|
+
try {
|
|
14511
|
+
if (!p.matches(":popover-open")) p.showPopover();
|
|
14512
|
+
} catch {
|
|
14513
|
+
}
|
|
14514
|
+
}
|
|
14515
|
+
hideNativePopover() {
|
|
14516
|
+
const p = this.listboxEl;
|
|
14517
|
+
if (!p || typeof p.hidePopover !== "function") return;
|
|
14518
|
+
try {
|
|
14519
|
+
if (p.matches(":popover-open")) p.hidePopover();
|
|
14520
|
+
} catch {
|
|
14521
|
+
}
|
|
14522
|
+
}
|
|
13447
14523
|
selectSingleOption(option) {
|
|
13448
14524
|
this.selected = option.value;
|
|
13449
14525
|
if (this.state === "error") this.state = "default";
|
|
@@ -13480,6 +14556,11 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13480
14556
|
}
|
|
13481
14557
|
onTriggerKeyDown(e) {
|
|
13482
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
|
+
}
|
|
13483
14564
|
if (!this.open) {
|
|
13484
14565
|
if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
|
|
13485
14566
|
e.preventDefault();
|
|
@@ -13490,26 +14571,60 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13490
14571
|
if (this.noSearch) this.handleListNavigation(e);
|
|
13491
14572
|
}
|
|
13492
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
|
+
}
|
|
13493
14578
|
this.handleListNavigation(e);
|
|
13494
14579
|
}
|
|
13495
14580
|
handleListNavigation(e) {
|
|
13496
14581
|
const filtered = this.filteredOptions;
|
|
14582
|
+
const hasSelectAll = this.multiple && filtered.length > 0;
|
|
13497
14583
|
switch (e.key) {
|
|
13498
14584
|
case "ArrowDown":
|
|
13499
14585
|
e.preventDefault();
|
|
13500
|
-
|
|
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();
|
|
13501
14595
|
break;
|
|
13502
14596
|
case "ArrowUp":
|
|
13503
14597
|
e.preventDefault();
|
|
13504
|
-
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();
|
|
13505
14624
|
break;
|
|
13506
14625
|
case "Enter":
|
|
13507
14626
|
e.preventDefault();
|
|
13508
|
-
|
|
13509
|
-
const opt = filtered[this.highlightedIndex];
|
|
13510
|
-
if (this.multiple) this.toggleMultiOption(opt);
|
|
13511
|
-
else this.selectSingleOption(opt);
|
|
13512
|
-
}
|
|
14627
|
+
this.activateHighlighted();
|
|
13513
14628
|
break;
|
|
13514
14629
|
case "Escape":
|
|
13515
14630
|
e.preventDefault();
|
|
@@ -13518,8 +14633,55 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13518
14633
|
case "Tab":
|
|
13519
14634
|
this.closeListbox();
|
|
13520
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
|
+
}
|
|
14665
|
+
}
|
|
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);
|
|
13521
14677
|
}
|
|
13522
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
|
+
}
|
|
13523
14685
|
renderChips() {
|
|
13524
14686
|
const values = this.selectedValues;
|
|
13525
14687
|
const visible = values.slice(0, Math.min(this.visibleChipCount, this.maxVisibleChips));
|
|
@@ -13597,7 +14759,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13597
14759
|
this.open ? "open" : ""
|
|
13598
14760
|
].filter(Boolean).join(" ");
|
|
13599
14761
|
const listboxId = `${this.uniqueId}-listbox`;
|
|
13600
|
-
const activeOptionId = this.open
|
|
14762
|
+
const activeOptionId = !this.open ? "" : this.selectAllHighlighted ? `${this.uniqueId}-select-all` : this.highlightedIndex >= 0 ? `${this.uniqueId}-option-${this.highlightedIndex}` : "";
|
|
13601
14763
|
const selectedSingle = opts.find((o) => o.value === this.selected);
|
|
13602
14764
|
return import_lit46.html`
|
|
13603
14765
|
<div class="${wrapperClasses}">
|
|
@@ -13654,39 +14816,52 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13654
14816
|
<div
|
|
13655
14817
|
id="${listboxId}"
|
|
13656
14818
|
part="panel list" class="select-listbox"
|
|
14819
|
+
popover="manual"
|
|
13657
14820
|
role="listbox"
|
|
13658
14821
|
aria-multiselectable="${this.multiple}"
|
|
13659
14822
|
aria-label="${accessibleName || "Options"}"
|
|
13660
14823
|
style="${this.panelStyle}"
|
|
13661
14824
|
?data-open="${this.open}"
|
|
13662
14825
|
>
|
|
13663
|
-
${!this.noSearch ? import_lit46.html`<div class="select-
|
|
13664
|
-
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
|
|
13668
|
-
|
|
13669
|
-
|
|
13670
|
-
|
|
13671
|
-
|
|
13672
|
-
|
|
13673
|
-
|
|
13674
|
-
|
|
13675
|
-
|
|
13676
|
-
|
|
13677
|
-
|
|
13678
|
-
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
14826
|
+
${!this.noSearch || this.multiple && filtered.length > 0 ? import_lit46.html`<div class="select-header">
|
|
14827
|
+
${!this.noSearch ? import_lit46.html`<div class="select-search">
|
|
14828
|
+
<div class="search-input-wrap">
|
|
14829
|
+
<rel-icon name="search" size="${this.searchIconSize}" class="icon"></rel-icon>
|
|
14830
|
+
<input
|
|
14831
|
+
part="input" class="search-input"
|
|
14832
|
+
type="text"
|
|
14833
|
+
placeholder="${this.searchPlaceholder}"
|
|
14834
|
+
.value="${this.searchValue}"
|
|
14835
|
+
@input="${this.onSearchInput}"
|
|
14836
|
+
@keydown="${(e) => this.onSearchKeyDown(e)}"
|
|
14837
|
+
/>
|
|
14838
|
+
</div>
|
|
14839
|
+
</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
|
+
>
|
|
14851
|
+
<rel-checkbox
|
|
14852
|
+
label="${this.selectAllLabel}"
|
|
14853
|
+
status="${this.selectAllStatus}"
|
|
14854
|
+
@rel-change=${(e) => {
|
|
13685
14855
|
e.stopPropagation();
|
|
13686
14856
|
this.toggleSelectAll();
|
|
13687
14857
|
}}
|
|
13688
|
-
|
|
13689
|
-
|
|
14858
|
+
></rel-checkbox>
|
|
14859
|
+
</div>` : ""}
|
|
14860
|
+
</div>` : ""}
|
|
14861
|
+
|
|
14862
|
+
${filtered.length === 0 ? import_lit46.html`<div class="select-empty">
|
|
14863
|
+
${opts.length === 0 ? "No options available" : "No matches found"}
|
|
14864
|
+
</div>` : this.multiple ? import_lit46.html`
|
|
13690
14865
|
<div class="select-options">
|
|
13691
14866
|
${(0, import_repeat2.repeat)(
|
|
13692
14867
|
filtered,
|
|
@@ -13697,7 +14872,10 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13697
14872
|
class="checkbox-option ${index === this.highlightedIndex ? "highlighted" : ""}"
|
|
13698
14873
|
role="option"
|
|
13699
14874
|
aria-selected="${this.selectedValues.includes(option.value)}"
|
|
13700
|
-
@pointerenter="${() =>
|
|
14875
|
+
@pointerenter="${() => {
|
|
14876
|
+
this.highlightedIndex = index;
|
|
14877
|
+
this.selectAllHighlighted = false;
|
|
14878
|
+
}}"
|
|
13701
14879
|
>
|
|
13702
14880
|
<rel-checkbox
|
|
13703
14881
|
label="${option.label}"
|
|
@@ -14029,6 +15207,15 @@ RelSelect.styles = import_lit46.css`
|
|
|
14029
15207
|
position: fixed;
|
|
14030
15208
|
z-index: 1001;
|
|
14031
15209
|
display: none;
|
|
15210
|
+
/* Reset native [popover] UA styles (the listbox is promoted to the
|
|
15211
|
+
top layer so transformed ancestors — e.g. a centered rel-modal
|
|
15212
|
+
panel — can't hijack its containing block or clip it). */
|
|
15213
|
+
inset: auto;
|
|
15214
|
+
margin: 0;
|
|
15215
|
+
padding: 0;
|
|
15216
|
+
width: auto;
|
|
15217
|
+
height: auto;
|
|
15218
|
+
color: inherit;
|
|
14032
15219
|
box-sizing: border-box;
|
|
14033
15220
|
background: var(--rel-select-bg, var(--rel-bg-neutral));
|
|
14034
15221
|
border: 1px solid var(--rel-select-menu-border, var(--rel-border-default));
|
|
@@ -14045,10 +15232,18 @@ RelSelect.styles = import_lit46.css`
|
|
|
14045
15232
|
display: block;
|
|
14046
15233
|
}
|
|
14047
15234
|
|
|
14048
|
-
.
|
|
15235
|
+
/* Search and Select All pin together while the options scroll beneath.
|
|
15236
|
+
z-index is load-bearing: multiselect rows contain rel-checkbox, whose
|
|
15237
|
+
.row wrapper is position:relative (Tab-scroll fix), so without it the
|
|
15238
|
+
positioned rows paint over the sticky header in DOM order. */
|
|
15239
|
+
.select-header {
|
|
14049
15240
|
position: sticky;
|
|
14050
15241
|
top: 0;
|
|
15242
|
+
z-index: 1;
|
|
14051
15243
|
background: var(--rel-select-bg, var(--rel-bg-neutral));
|
|
15244
|
+
}
|
|
15245
|
+
|
|
15246
|
+
.select-search {
|
|
14052
15247
|
padding: var(--rel-spacing-xs);
|
|
14053
15248
|
border-bottom: 1px solid var(--rel-select-divider, var(--rel-border-subtle));
|
|
14054
15249
|
}
|
|
@@ -14179,6 +15374,10 @@ RelSelect.styles = import_lit46.css`
|
|
|
14179
15374
|
padding: 12px;
|
|
14180
15375
|
}
|
|
14181
15376
|
|
|
15377
|
+
.select-all-row.highlighted {
|
|
15378
|
+
background: var(--rel-select-option-hover, var(--rel-bg-neutral-elevated-1));
|
|
15379
|
+
}
|
|
15380
|
+
|
|
14182
15381
|
.select-empty {
|
|
14183
15382
|
padding: var(--rel-spacing-xs) 12px;
|
|
14184
15383
|
color: var(--rel-select-placeholder-color, var(--rel-text-body));
|
|
@@ -14253,6 +15452,9 @@ __decorateClass([
|
|
|
14253
15452
|
__decorateClass([
|
|
14254
15453
|
(0, import_decorators46.state)()
|
|
14255
15454
|
], RelSelect.prototype, "highlightedIndex", 2);
|
|
15455
|
+
__decorateClass([
|
|
15456
|
+
(0, import_decorators46.state)()
|
|
15457
|
+
], RelSelect.prototype, "selectAllHighlighted", 2);
|
|
14256
15458
|
__decorateClass([
|
|
14257
15459
|
(0, import_decorators46.state)()
|
|
14258
15460
|
], RelSelect.prototype, "panelStyle", 2);
|
|
@@ -14450,6 +15652,10 @@ var RelModal = class extends import_lit48.LitElement {
|
|
|
14450
15652
|
if (e.key === "Escape" && this.dismissible) {
|
|
14451
15653
|
e.preventDefault();
|
|
14452
15654
|
this.open = false;
|
|
15655
|
+
return;
|
|
15656
|
+
}
|
|
15657
|
+
if (e.key === "Tab" && this.modal && this.panelElement) {
|
|
15658
|
+
trapTabKey(e, this.panelElement);
|
|
14453
15659
|
}
|
|
14454
15660
|
};
|
|
14455
15661
|
this.handleOverlayClick = (e) => {
|
|
@@ -14500,9 +15706,18 @@ var RelModal = class extends import_lit48.LitElement {
|
|
|
14500
15706
|
}
|
|
14501
15707
|
}
|
|
14502
15708
|
handleOpen() {
|
|
14503
|
-
this.previousFocus =
|
|
15709
|
+
this.previousFocus = getDeepActiveElement();
|
|
14504
15710
|
this.lockScroll();
|
|
14505
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
|
+
}
|
|
14506
15721
|
this.dispatchEvent(
|
|
14507
15722
|
new CustomEvent("rel-open-change", {
|
|
14508
15723
|
detail: true,
|
|
@@ -14862,6 +16077,9 @@ __decorateClass([
|
|
|
14862
16077
|
__decorateClass([
|
|
14863
16078
|
(0, import_decorators48.query)(".overlay")
|
|
14864
16079
|
], RelModal.prototype, "overlayElement", 2);
|
|
16080
|
+
__decorateClass([
|
|
16081
|
+
(0, import_decorators48.query)(".panel")
|
|
16082
|
+
], RelModal.prototype, "panelElement", 2);
|
|
14865
16083
|
RelModal = __decorateClass([
|
|
14866
16084
|
(0, import_decorators48.customElement)("rel-modal")
|
|
14867
16085
|
], RelModal);
|
|
@@ -15877,6 +17095,28 @@ var RelStepper = class extends import_lit51.LitElement {
|
|
|
15877
17095
|
if (this.isDisabled(step)) classes.push("disabled");
|
|
15878
17096
|
return classes.join(" ");
|
|
15879
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
|
+
}
|
|
15880
17120
|
onStepClick(step, index) {
|
|
15881
17121
|
if (step.status === "disabled") return;
|
|
15882
17122
|
this.dispatchEvent(
|
|
@@ -15929,7 +17169,12 @@ var RelStepper = class extends import_lit51.LitElement {
|
|
|
15929
17169
|
(step, idx) => import_lit51.html`
|
|
15930
17170
|
<div
|
|
15931
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}"
|
|
15932
17176
|
@click="${() => this.onStepClick(step, idx)}"
|
|
17177
|
+
@keydown="${(e) => this.onStepKeyDown(e, step, idx)}"
|
|
15933
17178
|
>
|
|
15934
17179
|
${isVertical && isTextRight ? import_lit51.html`
|
|
15935
17180
|
<!-- Indicator with connector below -->
|
|
@@ -16016,6 +17261,17 @@ RelStepper.styles = import_lit51.css`
|
|
|
16016
17261
|
.rel-step {
|
|
16017
17262
|
display: flex;
|
|
16018
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);
|
|
16019
17275
|
}
|
|
16020
17276
|
|
|
16021
17277
|
.rel-stepper.horizontal.text-below .rel-step {
|
|
@@ -16592,6 +17848,8 @@ var RelTab = class extends import_lit53.LitElement {
|
|
|
16592
17848
|
this.activeIndex = 0;
|
|
16593
17849
|
this.size = "md";
|
|
16594
17850
|
this._tabItems = [];
|
|
17851
|
+
/** Stable id prefix linking tabs to the shared tabpanel. */
|
|
17852
|
+
this.tabsId = `rel-tab-${Math.random().toString(36).slice(2)}`;
|
|
16595
17853
|
}
|
|
16596
17854
|
_onSlotChange(e) {
|
|
16597
17855
|
const slot = e.target;
|
|
@@ -16635,13 +17893,25 @@ var RelTab = class extends import_lit53.LitElement {
|
|
|
16635
17893
|
next = (next + dir + tabs.length) % tabs.length;
|
|
16636
17894
|
if (!tabs[next]?.disabled) break;
|
|
16637
17895
|
}
|
|
16638
|
-
this.
|
|
16639
|
-
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
17896
|
+
this.selectAndFocusTab(next);
|
|
17897
|
+
return;
|
|
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);
|
|
16643
17906
|
}
|
|
16644
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
|
+
});
|
|
17914
|
+
}
|
|
16645
17915
|
render() {
|
|
16646
17916
|
const tabs = this._effectiveTabs;
|
|
16647
17917
|
return import_lit53.html`
|
|
@@ -16651,9 +17921,11 @@ var RelTab = class extends import_lit53.LitElement {
|
|
|
16651
17921
|
(tab, i) => import_lit53.html`
|
|
16652
17922
|
<div
|
|
16653
17923
|
part="item" class="tab ${i === this.activeIndex ? "active" : ""} ${tab.disabled ? "disabled" : ""}"
|
|
17924
|
+
id="${this.tabsId}-tab-${i}"
|
|
16654
17925
|
role="tab"
|
|
16655
17926
|
aria-selected="${i === this.activeIndex}"
|
|
16656
17927
|
aria-disabled="${tab.disabled ?? false}"
|
|
17928
|
+
aria-controls="${this.tabsId}-panel"
|
|
16657
17929
|
tabindex="${tab.disabled ? -1 : i === this.activeIndex ? 0 : -1}"
|
|
16658
17930
|
@click="${() => this.selectTab(i)}"
|
|
16659
17931
|
@keydown="${(e) => this.onKeyDown(e, i)}"
|
|
@@ -16665,7 +17937,13 @@ var RelTab = class extends import_lit53.LitElement {
|
|
|
16665
17937
|
`
|
|
16666
17938
|
)}
|
|
16667
17939
|
</div>
|
|
16668
|
-
<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
|
+
>
|
|
16669
17947
|
<slot></slot>
|
|
16670
17948
|
</div>
|
|
16671
17949
|
`;
|
|
@@ -16696,7 +17974,6 @@ RelTab.styles = import_lit53.css`
|
|
|
16696
17974
|
padding: var(--rel-spacing-sm) var(--rel-spacing-md);
|
|
16697
17975
|
cursor: pointer;
|
|
16698
17976
|
color: var(--rel-text-body);
|
|
16699
|
-
|
|
16700
17977
|
transition: color 0.2s ease;
|
|
16701
17978
|
display: flex;
|
|
16702
17979
|
align-items: center;
|
|
@@ -16814,7 +18091,8 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
16814
18091
|
this.required = false;
|
|
16815
18092
|
this.readonly = false;
|
|
16816
18093
|
this.format = "12h";
|
|
16817
|
-
this.
|
|
18094
|
+
this.seconds = false;
|
|
18095
|
+
this.minuteStep = 5;
|
|
16818
18096
|
this.minTime = null;
|
|
16819
18097
|
this.maxTime = null;
|
|
16820
18098
|
this.open = false;
|
|
@@ -16826,6 +18104,8 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
16826
18104
|
this.fullWidth = false;
|
|
16827
18105
|
this.draftHour = null;
|
|
16828
18106
|
this.draftMinute = null;
|
|
18107
|
+
this.draftSecond = null;
|
|
18108
|
+
this.openUp = false;
|
|
16829
18109
|
this.handleClickOutside = (e) => {
|
|
16830
18110
|
if (!this.open) return;
|
|
16831
18111
|
const path = e.composedPath();
|
|
@@ -16850,40 +18130,58 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
16850
18130
|
document.removeEventListener("keydown", this.handleEscapeKey);
|
|
16851
18131
|
}
|
|
16852
18132
|
updated(changedProperties) {
|
|
16853
|
-
if (changedProperties.has("open")
|
|
18133
|
+
if (changedProperties.has("open")) {
|
|
18134
|
+
if (!this.open) {
|
|
18135
|
+
this.openUp = false;
|
|
18136
|
+
return;
|
|
18137
|
+
}
|
|
16854
18138
|
this.resetDraftFromValue();
|
|
16855
18139
|
setTimeout(() => {
|
|
18140
|
+
this.updatePanelPlacement();
|
|
16856
18141
|
this.scrollColumnsToSelection();
|
|
16857
18142
|
const target = this.columnsElement?.querySelector(
|
|
16858
18143
|
".option-button.selected:not(:disabled)"
|
|
16859
18144
|
) ?? this.columnsElement?.querySelector(
|
|
16860
18145
|
".option-button:not(:disabled)"
|
|
16861
18146
|
);
|
|
16862
|
-
target?.focus();
|
|
18147
|
+
target?.focus({ preventScroll: true });
|
|
16863
18148
|
}, 0);
|
|
16864
18149
|
}
|
|
16865
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
|
+
}
|
|
16866
18160
|
// ── Parsing / formatting ──
|
|
16867
18161
|
parseTime(time) {
|
|
16868
18162
|
if (!time) return null;
|
|
16869
|
-
const match = time.match(/^(\d{1,2}):(\d{2})
|
|
18163
|
+
const match = time.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
|
|
16870
18164
|
if (!match) return null;
|
|
16871
18165
|
const hour = parseInt(match[1], 10);
|
|
16872
18166
|
const minute = parseInt(match[2], 10);
|
|
16873
|
-
|
|
16874
|
-
|
|
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 };
|
|
16875
18170
|
}
|
|
16876
|
-
formatValue(hour, minute) {
|
|
16877
|
-
|
|
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}`;
|
|
16878
18175
|
}
|
|
16879
|
-
formatDisplay(hour, minute) {
|
|
18176
|
+
formatDisplay(hour, minute, second = 0) {
|
|
16880
18177
|
const mm = String(minute).padStart(2, "0");
|
|
18178
|
+
const ss = this.seconds ? `:${String(second).padStart(2, "0")}` : "";
|
|
16881
18179
|
if (this.format === "24h") {
|
|
16882
|
-
return `${String(hour).padStart(2, "0")}:${mm}`;
|
|
18180
|
+
return `${String(hour).padStart(2, "0")}:${mm}${ss}`;
|
|
16883
18181
|
}
|
|
16884
18182
|
const period = hour < 12 ? "AM" : "PM";
|
|
16885
18183
|
const hour12 = hour % 12 === 0 ? 12 : hour % 12;
|
|
16886
|
-
return `${hour12}:${mm} ${period}`;
|
|
18184
|
+
return `${hour12}:${mm}${ss} ${period}`;
|
|
16887
18185
|
}
|
|
16888
18186
|
toMinutes(hour, minute) {
|
|
16889
18187
|
return hour * 60 + minute;
|
|
@@ -16933,10 +18231,17 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
16933
18231
|
}
|
|
16934
18232
|
const period = this.draftPeriod ?? "AM";
|
|
16935
18233
|
const base = period === "AM" ? 0 : 12;
|
|
16936
|
-
return Array.from({ length: 12 }, (_, i) =>
|
|
16937
|
-
|
|
16938
|
-
|
|
16939
|
-
|
|
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);
|
|
16940
18245
|
}
|
|
16941
18246
|
get draftPeriod() {
|
|
16942
18247
|
if (this.draftHour === null) return null;
|
|
@@ -16946,6 +18251,7 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
16946
18251
|
const parsed = this.parseTime(this.value);
|
|
16947
18252
|
this.draftHour = parsed ? parsed.hour : null;
|
|
16948
18253
|
this.draftMinute = parsed ? parsed.minute : null;
|
|
18254
|
+
this.draftSecond = parsed ? parsed.second : null;
|
|
16949
18255
|
}
|
|
16950
18256
|
/** Snap the draft minute to the nearest valid step minute for the hour. */
|
|
16951
18257
|
clampDraftToRange() {
|
|
@@ -16988,7 +18294,11 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
16988
18294
|
}
|
|
16989
18295
|
applySelection() {
|
|
16990
18296
|
if (this.draftHour === null) return;
|
|
16991
|
-
const newValue = this.formatValue(
|
|
18297
|
+
const newValue = this.formatValue(
|
|
18298
|
+
this.draftHour,
|
|
18299
|
+
this.draftMinute ?? 0,
|
|
18300
|
+
this.draftSecond ?? 0
|
|
18301
|
+
);
|
|
16992
18302
|
this.value = newValue;
|
|
16993
18303
|
this.dispatchEvent(
|
|
16994
18304
|
new CustomEvent("rel-change", {
|
|
@@ -17004,6 +18314,7 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17004
18314
|
const now = /* @__PURE__ */ new Date();
|
|
17005
18315
|
const step = this.minuteStepSafe;
|
|
17006
18316
|
this.draftHour = now.getHours();
|
|
18317
|
+
this.draftSecond = this.seconds ? now.getSeconds() : null;
|
|
17007
18318
|
this.draftMinute = Math.round(now.getMinutes() / step) * step;
|
|
17008
18319
|
if (this.draftMinute >= 60) {
|
|
17009
18320
|
this.draftMinute = 0;
|
|
@@ -17026,6 +18337,7 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17026
18337
|
this.value = null;
|
|
17027
18338
|
this.draftHour = null;
|
|
17028
18339
|
this.draftMinute = null;
|
|
18340
|
+
this.draftSecond = null;
|
|
17029
18341
|
this.dispatchEvent(
|
|
17030
18342
|
new CustomEvent("rel-change", {
|
|
17031
18343
|
detail: { value: null },
|
|
@@ -17054,6 +18366,20 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17054
18366
|
if (!this.isTimeInRange(this.draftHour, minute)) return;
|
|
17055
18367
|
this.draftMinute = minute;
|
|
17056
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
|
+
}
|
|
17057
18383
|
selectPeriod(period) {
|
|
17058
18384
|
if (!this.isPeriodEnabled(period)) return;
|
|
17059
18385
|
const base = period === "AM" ? 0 : 12;
|
|
@@ -17071,9 +18397,27 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17071
18397
|
this.clampDraftToRange();
|
|
17072
18398
|
this.scrollColumnsToSelection();
|
|
17073
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
|
+
}
|
|
17074
18418
|
scrollColumnsToSelection() {
|
|
17075
18419
|
this.updateComplete.then(() => {
|
|
17076
|
-
this.columnsElement?.querySelectorAll(".option-button.selected").forEach((btn) =>
|
|
18420
|
+
this.columnsElement?.querySelectorAll(".option-button.selected").forEach((btn) => this.scrollOptionInColumn(btn, "center"));
|
|
17077
18421
|
});
|
|
17078
18422
|
}
|
|
17079
18423
|
// ── Keyboard ──
|
|
@@ -17130,19 +18474,23 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17130
18474
|
}
|
|
17131
18475
|
if (target && target !== buttons[index]) {
|
|
17132
18476
|
target.click();
|
|
17133
|
-
target.focus();
|
|
17134
|
-
|
|
18477
|
+
target.focus({ preventScroll: true });
|
|
18478
|
+
this.scrollOptionInColumn(target, "nearest");
|
|
17135
18479
|
}
|
|
17136
18480
|
}
|
|
17137
18481
|
focusAdjacentColumn(from, direction) {
|
|
17138
|
-
const order =
|
|
18482
|
+
const order = ["hour", "minute"];
|
|
18483
|
+
if (this.seconds) order.push("second");
|
|
18484
|
+
if (this.format === "12h") order.push("period");
|
|
17139
18485
|
const nextIndex = order.indexOf(from) + direction;
|
|
17140
18486
|
if (nextIndex < 0 || nextIndex >= order.length) return;
|
|
17141
18487
|
const lists = this.columnsElement?.querySelectorAll(".column-list");
|
|
17142
18488
|
const list = lists?.[nextIndex];
|
|
17143
18489
|
const target = list?.querySelector(".option-button.selected:not(:disabled)") ?? list?.querySelector(".option-button:not(:disabled)");
|
|
17144
|
-
target
|
|
17145
|
-
|
|
18490
|
+
if (target) {
|
|
18491
|
+
target.focus({ preventScroll: true });
|
|
18492
|
+
this.scrollOptionInColumn(target, "nearest");
|
|
18493
|
+
}
|
|
17146
18494
|
}
|
|
17147
18495
|
// ── Render ──
|
|
17148
18496
|
render() {
|
|
@@ -17150,10 +18498,10 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17150
18498
|
const belowText = isError && this.errorMessage ? this.errorMessage : this.helperText;
|
|
17151
18499
|
const describedById = belowText ? "time-picker-desc" : void 0;
|
|
17152
18500
|
const parsedValue = this.parseTime(this.value);
|
|
17153
|
-
const displayText = parsedValue ? this.formatDisplay(parsedValue.hour, parsedValue.minute) : "";
|
|
17154
|
-
const draftDisplay = this.draftHour !== null ? this.formatDisplay(this.draftHour, this.draftMinute ?? 0) : "";
|
|
18501
|
+
const displayText = parsedValue ? this.formatDisplay(parsedValue.hour, parsedValue.minute, parsedValue.second) : "";
|
|
17155
18502
|
const hourOptions = this.getHourOptions();
|
|
17156
18503
|
const minuteOptions = this.getMinuteOptions();
|
|
18504
|
+
const secondOptions = this.seconds ? this.getSecondOptions() : [];
|
|
17157
18505
|
const draftPeriod = this.draftPeriod;
|
|
17158
18506
|
const wrapperClasses = [
|
|
17159
18507
|
"time-picker-wrapper",
|
|
@@ -17218,104 +18566,106 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17218
18566
|
|
|
17219
18567
|
<div
|
|
17220
18568
|
part="panel"
|
|
17221
|
-
class="popover ${this.open ? "open" : ""}"
|
|
18569
|
+
class="popover ${this.open ? "open" : ""} ${this.openUp ? "open-up" : ""}"
|
|
17222
18570
|
role="dialog"
|
|
17223
18571
|
aria-label="Choose time"
|
|
17224
18572
|
>
|
|
17225
|
-
<div class="time-readout">
|
|
17226
|
-
<span class="time-readout-label">Time</span>
|
|
17227
|
-
<span class="time-readout-value ${draftDisplay ? "" : "empty"}">
|
|
17228
|
-
${draftDisplay || (this.format === "24h" ? "--:--" : "--:-- --")}
|
|
17229
|
-
</span>
|
|
17230
|
-
</div>
|
|
17231
|
-
|
|
17232
18573
|
<div class="columns">
|
|
17233
|
-
<div class="column">
|
|
17234
|
-
|
|
17235
|
-
<div class="column-list" role="listbox" aria-label="Hours">
|
|
17236
|
-
${hourOptions.map(
|
|
18574
|
+
<div class="column-list" role="listbox" aria-label="Hours">
|
|
18575
|
+
${hourOptions.map(
|
|
17237
18576
|
(opt, i) => import_lit54.html`
|
|
17238
|
-
|
|
17239
|
-
|
|
17240
|
-
|
|
17241
|
-
|
|
17242
|
-
|
|
17243
|
-
|
|
17244
|
-
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
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
|
+
`
|
|
17252
18591
|
)}
|
|
17253
|
-
</div>
|
|
17254
18592
|
</div>
|
|
17255
18593
|
|
|
17256
|
-
<div class="column">
|
|
17257
|
-
|
|
17258
|
-
|
|
17259
|
-
|
|
18594
|
+
<div class="column-divider" aria-hidden="true"></div>
|
|
18595
|
+
|
|
18596
|
+
<div class="column-list" role="listbox" aria-label="Minutes">
|
|
18597
|
+
${minuteOptions.map(
|
|
17260
18598
|
(m, i) => import_lit54.html`
|
|
17261
|
-
|
|
17262
|
-
|
|
17263
|
-
|
|
17264
|
-
|
|
17265
|
-
|
|
17266
|
-
|
|
17267
|
-
|
|
17268
|
-
|
|
17269
|
-
|
|
17270
|
-
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
|
|
17274
|
-
|
|
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
|
+
`
|
|
17275
18613
|
)}
|
|
17276
|
-
</div>
|
|
17277
18614
|
</div>
|
|
17278
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}
|
|
17279
18637
|
${this.format === "12h" ? import_lit54.html`
|
|
17280
|
-
<div class="column">
|
|
17281
|
-
|
|
17282
|
-
|
|
17283
|
-
${["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(
|
|
17284
18641
|
(p) => import_lit54.html`
|
|
17285
|
-
|
|
17286
|
-
|
|
17287
|
-
|
|
17288
|
-
|
|
17289
|
-
|
|
17290
|
-
|
|
17291
|
-
|
|
17292
|
-
|
|
17293
|
-
|
|
17294
|
-
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
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
|
+
`
|
|
17299
18656
|
)}
|
|
17300
|
-
</div>
|
|
17301
18657
|
</div>
|
|
17302
18658
|
` : import_lit54.nothing}
|
|
17303
18659
|
</div>
|
|
17304
18660
|
|
|
17305
18661
|
<div part="footer" class="time-footer">
|
|
17306
|
-
<rel-button
|
|
17307
|
-
variant="secondary"
|
|
17308
|
-
size="small"
|
|
17309
|
-
border="corner-radius"
|
|
17310
|
-
@click=${() => this.selectNow()}
|
|
17311
|
-
>
|
|
18662
|
+
<rel-button variant="link" size="small" @click=${() => this.selectNow()}>
|
|
17312
18663
|
Now
|
|
17313
18664
|
</rel-button>
|
|
17314
18665
|
<div class="footer-right">
|
|
17315
18666
|
<rel-button
|
|
17316
|
-
variant="
|
|
18667
|
+
variant="ghost"
|
|
17317
18668
|
size="small"
|
|
17318
|
-
border="corner-radius"
|
|
17319
18669
|
@click=${() => this.cancelPopover()}
|
|
17320
18670
|
>
|
|
17321
18671
|
Cancel
|
|
@@ -17324,11 +18674,10 @@ var RelTimePicker = class extends import_lit54.LitElement {
|
|
|
17324
18674
|
variant="primary"
|
|
17325
18675
|
tone="default"
|
|
17326
18676
|
size="small"
|
|
17327
|
-
border="corner-radius"
|
|
17328
18677
|
?disabled=${this.draftHour === null}
|
|
17329
18678
|
@click=${() => this.applySelection()}
|
|
17330
18679
|
>
|
|
17331
|
-
|
|
18680
|
+
OK
|
|
17332
18681
|
</rel-button>
|
|
17333
18682
|
</div>
|
|
17334
18683
|
</div>
|
|
@@ -17512,16 +18861,19 @@ RelTimePicker.styles = import_lit54.css`
|
|
|
17512
18861
|
height: 16px;
|
|
17513
18862
|
}
|
|
17514
18863
|
|
|
18864
|
+
/* Panel (Figma "Time Picker"): fixed width per size, full-bleed content. */
|
|
17515
18865
|
.popover {
|
|
17516
18866
|
position: absolute;
|
|
17517
18867
|
top: calc(100% + 4px);
|
|
17518
18868
|
left: 0;
|
|
17519
18869
|
z-index: 1000;
|
|
17520
18870
|
background: var(--rel-bg-surface);
|
|
17521
|
-
border: 1px solid var(--rel-border-
|
|
18871
|
+
border: 1px solid var(--rel-border-subtle);
|
|
17522
18872
|
border-radius: var(--rel-border-radius-sm);
|
|
17523
18873
|
box-shadow: var(--rel-shadows-shadow-300);
|
|
17524
|
-
|
|
18874
|
+
overflow: hidden;
|
|
18875
|
+
min-width: 220px;
|
|
18876
|
+
width: 250px;
|
|
17525
18877
|
display: none;
|
|
17526
18878
|
}
|
|
17527
18879
|
|
|
@@ -17529,94 +18881,79 @@ RelTimePicker.styles = import_lit54.css`
|
|
|
17529
18881
|
display: block;
|
|
17530
18882
|
}
|
|
17531
18883
|
|
|
17532
|
-
/*
|
|
17533
|
-
.
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
gap: var(--rel-spacing-sm);
|
|
17537
|
-
padding-bottom: var(--rel-spacing-sm);
|
|
17538
|
-
margin-bottom: var(--rel-spacing-sm);
|
|
17539
|
-
border-bottom: 1px solid var(--rel-border-subtle);
|
|
17540
|
-
}
|
|
17541
|
-
|
|
17542
|
-
.time-readout-label {
|
|
17543
|
-
color: var(--rel-text-placeholder);
|
|
17544
|
-
text-transform: uppercase;
|
|
17545
|
-
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17546
|
-
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17547
|
-
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17548
|
-
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17549
|
-
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
17550
|
-
}
|
|
17551
|
-
|
|
17552
|
-
.time-readout-value {
|
|
17553
|
-
color: var(--rel-text-default);
|
|
17554
|
-
font-family: var(--rel-body-md-semibold-font-family);
|
|
17555
|
-
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
17556
|
-
font-size: var(--rel-body-md-semibold-font-size);
|
|
17557
|
-
line-height: var(--rel-body-md-semibold-line-height);
|
|
17558
|
-
letter-spacing: var(--rel-body-md-semibold-letter-spacing);
|
|
17559
|
-
}
|
|
17560
|
-
|
|
17561
|
-
.time-readout-value.empty {
|
|
17562
|
-
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);
|
|
17563
18888
|
}
|
|
17564
18889
|
|
|
18890
|
+
/* Full-bleed column row with 1px dividers (Figma "Columns"). */
|
|
17565
18891
|
.columns {
|
|
17566
18892
|
display: flex;
|
|
17567
|
-
|
|
17568
|
-
|
|
17569
|
-
|
|
17570
|
-
/* Columns share the panel width equally, so the two-column 24h layout
|
|
17571
|
-
fills the same footprint the footer dictates instead of bunching left. */
|
|
17572
|
-
.column {
|
|
17573
|
-
display: flex;
|
|
17574
|
-
flex-direction: column;
|
|
17575
|
-
flex: 1;
|
|
17576
|
-
min-width: 64px;
|
|
18893
|
+
align-items: stretch;
|
|
18894
|
+
height: 260px;
|
|
17577
18895
|
}
|
|
17578
18896
|
|
|
17579
|
-
.column-
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
|
|
17583
|
-
text-transform: uppercase;
|
|
17584
|
-
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17585
|
-
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17586
|
-
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17587
|
-
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17588
|
-
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);
|
|
17589
18901
|
}
|
|
17590
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. */
|
|
17591
18906
|
.column-list {
|
|
18907
|
+
flex: 1;
|
|
18908
|
+
min-width: 0;
|
|
17592
18909
|
display: flex;
|
|
17593
18910
|
flex-direction: column;
|
|
17594
|
-
|
|
17595
|
-
|
|
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;
|
|
17596
18915
|
overflow-y: auto;
|
|
17597
|
-
padding: var(--rel-spacing-
|
|
18916
|
+
padding: var(--rel-spacing-2xs);
|
|
17598
18917
|
scrollbar-width: thin;
|
|
18918
|
+
scrollbar-color: var(--rel-border-default) transparent;
|
|
18919
|
+
scrollbar-gutter: stable;
|
|
18920
|
+
}
|
|
18921
|
+
|
|
18922
|
+
.column-list::-webkit-scrollbar {
|
|
18923
|
+
width: 4px;
|
|
18924
|
+
}
|
|
18925
|
+
|
|
18926
|
+
.column-list::-webkit-scrollbar-track {
|
|
18927
|
+
background: transparent;
|
|
17599
18928
|
}
|
|
17600
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. */
|
|
17601
18937
|
.option-button {
|
|
17602
18938
|
border: none;
|
|
17603
18939
|
background: transparent;
|
|
17604
|
-
border-radius: var(--rel-border-radius-
|
|
18940
|
+
border-radius: var(--rel-border-radius-xs);
|
|
17605
18941
|
color: var(--rel-text-default);
|
|
17606
18942
|
cursor: pointer;
|
|
17607
|
-
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
17608
|
-
text-align: center;
|
|
17609
18943
|
flex-shrink: 0;
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
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);
|
|
17616
18953
|
}
|
|
17617
18954
|
|
|
17618
18955
|
.option-button:hover:not(:disabled):not(.selected) {
|
|
17619
|
-
background: var(--rel-bg-
|
|
18956
|
+
background: var(--rel-bg-neutral-elevated-1);
|
|
17620
18957
|
}
|
|
17621
18958
|
|
|
17622
18959
|
.option-button:focus-visible {
|
|
@@ -17626,9 +18963,9 @@ RelTimePicker.styles = import_lit54.css`
|
|
|
17626
18963
|
}
|
|
17627
18964
|
|
|
17628
18965
|
.option-button.selected {
|
|
17629
|
-
background: var(--rel-
|
|
17630
|
-
color: var(--rel-
|
|
17631
|
-
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);
|
|
17632
18969
|
}
|
|
17633
18970
|
|
|
17634
18971
|
.option-button:disabled {
|
|
@@ -17641,14 +18978,15 @@ RelTimePicker.styles = import_lit54.css`
|
|
|
17641
18978
|
background: transparent;
|
|
17642
18979
|
}
|
|
17643
18980
|
|
|
17644
|
-
/* 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. */
|
|
17645
18983
|
.time-footer {
|
|
17646
18984
|
display: flex;
|
|
17647
18985
|
align-items: center;
|
|
17648
18986
|
justify-content: space-between;
|
|
17649
|
-
gap: var(--rel-spacing-
|
|
17650
|
-
padding
|
|
17651
|
-
|
|
18987
|
+
gap: var(--rel-spacing-xs);
|
|
18988
|
+
padding: var(--rel-spacing-xs);
|
|
18989
|
+
padding-left: calc(var(--rel-spacing-xs) * 2);
|
|
17652
18990
|
border-top: 1px solid var(--rel-border-subtle);
|
|
17653
18991
|
}
|
|
17654
18992
|
|
|
@@ -17658,35 +18996,88 @@ RelTimePicker.styles = import_lit54.css`
|
|
|
17658
18996
|
gap: var(--rel-spacing-xs);
|
|
17659
18997
|
}
|
|
17660
18998
|
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
transition: none;
|
|
17666
|
-
}
|
|
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;
|
|
17667
19003
|
}
|
|
17668
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
|
|
17673
|
-
|
|
17674
|
-
|
|
17675
|
-
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17680
|
-
|
|
17681
|
-
|
|
17682
|
-
|
|
17683
|
-
|
|
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
|
+
|
|
19049
|
+
@media (prefers-reduced-motion: reduce) {
|
|
19050
|
+
.trigger-button,
|
|
19051
|
+
.icon-button,
|
|
19052
|
+
.option-button {
|
|
19053
|
+
transition: none;
|
|
19054
|
+
}
|
|
19055
|
+
}
|
|
19056
|
+
`;
|
|
19057
|
+
__decorateClass([
|
|
19058
|
+
(0, import_decorators54.property)({ type: String, reflect: true })
|
|
19059
|
+
], RelTimePicker.prototype, "value", 2);
|
|
19060
|
+
__decorateClass([
|
|
19061
|
+
(0, import_decorators54.property)({ type: String })
|
|
19062
|
+
], RelTimePicker.prototype, "placeholder", 2);
|
|
19063
|
+
__decorateClass([
|
|
19064
|
+
(0, import_decorators54.property)({ type: String })
|
|
19065
|
+
], RelTimePicker.prototype, "label", 2);
|
|
19066
|
+
__decorateClass([
|
|
19067
|
+
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
19068
|
+
], RelTimePicker.prototype, "disabled", 2);
|
|
19069
|
+
__decorateClass([
|
|
19070
|
+
(0, import_decorators54.property)({ type: Boolean })
|
|
19071
|
+
], RelTimePicker.prototype, "required", 2);
|
|
17684
19072
|
__decorateClass([
|
|
17685
19073
|
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
17686
19074
|
], RelTimePicker.prototype, "readonly", 2);
|
|
17687
19075
|
__decorateClass([
|
|
17688
19076
|
(0, import_decorators54.property)({ type: String })
|
|
17689
19077
|
], RelTimePicker.prototype, "format", 2);
|
|
19078
|
+
__decorateClass([
|
|
19079
|
+
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
19080
|
+
], RelTimePicker.prototype, "seconds", 2);
|
|
17690
19081
|
__decorateClass([
|
|
17691
19082
|
(0, import_decorators54.property)({ type: Number, attribute: "minute-step" })
|
|
17692
19083
|
], RelTimePicker.prototype, "minuteStep", 2);
|
|
@@ -17732,6 +19123,12 @@ __decorateClass([
|
|
|
17732
19123
|
__decorateClass([
|
|
17733
19124
|
(0, import_decorators54.state)()
|
|
17734
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);
|
|
17735
19132
|
__decorateClass([
|
|
17736
19133
|
(0, import_decorators54.query)(".trigger-button")
|
|
17737
19134
|
], RelTimePicker.prototype, "triggerButton", 2);
|
|
@@ -17970,6 +19367,26 @@ var RelTooltip = class extends import_lit56.LitElement {
|
|
|
17970
19367
|
this.type = "single-line";
|
|
17971
19368
|
// Stable id so aria-describedby can reference the bubble across renders.
|
|
17972
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);
|
|
17973
19390
|
}
|
|
17974
19391
|
firstUpdated() {
|
|
17975
19392
|
this.linkTrigger();
|
|
@@ -18036,6 +19453,12 @@ RelTooltip.styles = import_lit56.css`
|
|
|
18036
19453
|
opacity: 1;
|
|
18037
19454
|
}
|
|
18038
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
|
+
|
|
18039
19462
|
/* Positioning */
|
|
18040
19463
|
.tooltip-bubble.above {
|
|
18041
19464
|
bottom: calc(100% + 6px);
|
|
@@ -18125,10 +19548,914 @@ RelTooltip = __decorateClass([
|
|
|
18125
19548
|
(0, import_decorators56.customElement)("rel-tooltip")
|
|
18126
19549
|
], RelTooltip);
|
|
18127
19550
|
|
|
19551
|
+
// src/components/rel-upload/rel-upload.ts
|
|
19552
|
+
var import_lit57 = require("lit");
|
|
19553
|
+
var import_decorators57 = require("lit/decorators.js");
|
|
19554
|
+
var import_rel_icon17 = require("@reliability-design/icons/rel-icon");
|
|
19555
|
+
var uid = 0;
|
|
19556
|
+
var RelUpload = class extends import_lit57.LitElement {
|
|
19557
|
+
constructor() {
|
|
19558
|
+
super(...arguments);
|
|
19559
|
+
this.accept = "";
|
|
19560
|
+
this.maxSize = "";
|
|
19561
|
+
this.multiple = false;
|
|
19562
|
+
this.disabled = false;
|
|
19563
|
+
this.hint = "";
|
|
19564
|
+
this.dropzoneTitle = "Drag and drop your files here";
|
|
19565
|
+
this.browseLabel = "Browse files";
|
|
19566
|
+
this.files = [];
|
|
19567
|
+
this.concurrency = 3;
|
|
19568
|
+
this.delayThreshold = 15e3;
|
|
19569
|
+
this.dragOver = false;
|
|
19570
|
+
this.delayed = false;
|
|
19571
|
+
this.lastSignature = "";
|
|
19572
|
+
this.openPicker = () => {
|
|
19573
|
+
if (this.disabled) return;
|
|
19574
|
+
this.inputEl?.click();
|
|
19575
|
+
};
|
|
19576
|
+
this.onInputChange = (e) => {
|
|
19577
|
+
const input = e.target;
|
|
19578
|
+
this.ingest(input.files);
|
|
19579
|
+
input.value = "";
|
|
19580
|
+
};
|
|
19581
|
+
this.onDragOver = (e) => {
|
|
19582
|
+
if (this.disabled) return;
|
|
19583
|
+
e.preventDefault();
|
|
19584
|
+
this.dragOver = true;
|
|
19585
|
+
};
|
|
19586
|
+
this.onDragLeave = (e) => {
|
|
19587
|
+
e.preventDefault();
|
|
19588
|
+
this.dragOver = false;
|
|
19589
|
+
};
|
|
19590
|
+
this.onDrop = (e) => {
|
|
19591
|
+
e.preventDefault();
|
|
19592
|
+
this.dragOver = false;
|
|
19593
|
+
if (this.disabled) return;
|
|
19594
|
+
this.ingest(e.dataTransfer?.files ?? null);
|
|
19595
|
+
};
|
|
19596
|
+
}
|
|
19597
|
+
// ── derived ───────────────────────────────────────────────────────────────
|
|
19598
|
+
/**
|
|
19599
|
+
* Aggregate state. Read-only; corresponds to the Figma modal variants.
|
|
19600
|
+
*
|
|
19601
|
+
* `failed` means there is nothing left to send AND something errored — while
|
|
19602
|
+
* any row is still `ready` the primary action is Upload, not Retry.
|
|
19603
|
+
*/
|
|
19604
|
+
get phase() {
|
|
19605
|
+
if (this.files.length === 0) return "empty";
|
|
19606
|
+
if (this.files.some((f) => f.status === "uploading")) {
|
|
19607
|
+
return this.delayed ? "delayed" : "uploading";
|
|
19608
|
+
}
|
|
19609
|
+
if (this.pending.length > 0) return "selected";
|
|
19610
|
+
if (this.files.some((f) => f.status === "error")) return "failed";
|
|
19611
|
+
return "selected";
|
|
19612
|
+
}
|
|
19613
|
+
/** Files still eligible to be sent. */
|
|
19614
|
+
get pending() {
|
|
19615
|
+
return this.files.filter((f) => f.status === "ready");
|
|
19616
|
+
}
|
|
19617
|
+
get failedFiles() {
|
|
19618
|
+
return this.files.filter((f) => f.status === "error");
|
|
19619
|
+
}
|
|
19620
|
+
/**
|
|
19621
|
+
* Failed rows worth sending again. A file rejected by `accept`/`max-size`
|
|
19622
|
+
* will fail validation every time, so retrying it is pointless — re-check
|
|
19623
|
+
* rather than re-queueing it blindly.
|
|
19624
|
+
*/
|
|
19625
|
+
get retryable() {
|
|
19626
|
+
const maxBytes = this.parseMaxBytes();
|
|
19627
|
+
return this.failedFiles.filter(
|
|
19628
|
+
(f) => !!f.file && this.matchesAccept(f.file) && f.file.size <= maxBytes
|
|
19629
|
+
);
|
|
19630
|
+
}
|
|
19631
|
+
get canUpload() {
|
|
19632
|
+
if (this.disabled) return false;
|
|
19633
|
+
const phase = this.phase;
|
|
19634
|
+
if (phase === "uploading" || phase === "delayed") return false;
|
|
19635
|
+
if (phase === "failed") return this.retryable.length > 0;
|
|
19636
|
+
return this.pending.length > 0;
|
|
19637
|
+
}
|
|
19638
|
+
/** Footer button label — "Upload", "Upload 3 files" or "Retry upload". */
|
|
19639
|
+
get primaryLabel() {
|
|
19640
|
+
if (this.phase === "failed") return "Retry upload";
|
|
19641
|
+
const n = this.files.length;
|
|
19642
|
+
return n > 1 ? `Upload ${n} files` : "Upload";
|
|
19643
|
+
}
|
|
19644
|
+
/** The snapshot `<rel-upload-actions>` renders from. */
|
|
19645
|
+
get state() {
|
|
19646
|
+
return {
|
|
19647
|
+
phase: this.phase,
|
|
19648
|
+
total: this.files.length,
|
|
19649
|
+
failed: this.failedFiles.length,
|
|
19650
|
+
canUpload: this.canUpload,
|
|
19651
|
+
primaryLabel: this.primaryLabel
|
|
19652
|
+
};
|
|
19653
|
+
}
|
|
19654
|
+
get listHeading() {
|
|
19655
|
+
const n = this.files.length;
|
|
19656
|
+
switch (this.phase) {
|
|
19657
|
+
case "uploading":
|
|
19658
|
+
case "delayed":
|
|
19659
|
+
return `Uploading ${n} ${n === 1 ? "file" : "files"}`;
|
|
19660
|
+
case "failed":
|
|
19661
|
+
return `${n} ${n === 1 ? "file" : "files"} - ${this.failedFiles.length} failed`;
|
|
19662
|
+
default:
|
|
19663
|
+
return n === 1 ? "Selected file (1)" : `Selected files (${n})`;
|
|
19664
|
+
}
|
|
19665
|
+
}
|
|
19666
|
+
get hintText() {
|
|
19667
|
+
if (this.hint) return this.hint;
|
|
19668
|
+
const formats = this.acceptDisplay;
|
|
19669
|
+
const size = this.maxSize ? ` - up to ${this.maxSize} per file` : "";
|
|
19670
|
+
return `${formats}${size}`;
|
|
19671
|
+
}
|
|
19672
|
+
get acceptDisplay() {
|
|
19673
|
+
const tokens = this.accept.split(",").map((s) => s.trim()).filter(Boolean).map((tok) => {
|
|
19674
|
+
if (tok.startsWith(".")) return tok.slice(1).toUpperCase();
|
|
19675
|
+
if (tok.endsWith("/*")) return tok.slice(0, -2).toUpperCase();
|
|
19676
|
+
return (tok.split("/").pop() ?? tok).toUpperCase();
|
|
19677
|
+
});
|
|
19678
|
+
if (tokens.length === 0) return "Any file type";
|
|
19679
|
+
if (tokens.length === 1) return tokens[0];
|
|
19680
|
+
return `${tokens.slice(0, -1).join(", ")} or ${tokens[tokens.length - 1]}`;
|
|
19681
|
+
}
|
|
19682
|
+
get alertContent() {
|
|
19683
|
+
if (this.phase === "delayed") {
|
|
19684
|
+
return {
|
|
19685
|
+
type: "warning",
|
|
19686
|
+
message1: "This is taking longer than usual",
|
|
19687
|
+
message2: "Large files can take a few minutes on a slow connection. You can leave this open or come back later."
|
|
19688
|
+
};
|
|
19689
|
+
}
|
|
19690
|
+
const failed = this.failedFiles.length;
|
|
19691
|
+
if (failed > 0 && this.phase !== "uploading") {
|
|
19692
|
+
const limit = this.maxSize ? `under ${this.maxSize} and ` : "";
|
|
19693
|
+
return {
|
|
19694
|
+
type: "error",
|
|
19695
|
+
message1: failed === 1 && this.files.length === 1 ? "We couldn't upload your file" : `We couldn't upload ${failed} of your files`,
|
|
19696
|
+
message2: `Check that each file is ${limit}in a supported format, then try again.`
|
|
19697
|
+
};
|
|
19698
|
+
}
|
|
19699
|
+
return null;
|
|
19700
|
+
}
|
|
19701
|
+
// ── validation ────────────────────────────────────────────────────────────
|
|
19702
|
+
parseMaxBytes() {
|
|
19703
|
+
const s = this.maxSize.trim().toLowerCase();
|
|
19704
|
+
if (!s) return Infinity;
|
|
19705
|
+
const m = s.match(/^([\d.]+)\s*(b|kb|mb|gb)?$/);
|
|
19706
|
+
if (!m) return Infinity;
|
|
19707
|
+
const n = parseFloat(m[1]);
|
|
19708
|
+
const unit = m[2] || "b";
|
|
19709
|
+
const mult = unit === "gb" ? 1e9 : unit === "mb" ? 1e6 : unit === "kb" ? 1e3 : 1;
|
|
19710
|
+
return n * mult;
|
|
19711
|
+
}
|
|
19712
|
+
matchesAccept(file) {
|
|
19713
|
+
const raw = this.accept.trim();
|
|
19714
|
+
if (!raw) return true;
|
|
19715
|
+
const tokens = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
19716
|
+
const name = file.name.toLowerCase();
|
|
19717
|
+
const type = (file.type || "").toLowerCase();
|
|
19718
|
+
return tokens.some((tok) => {
|
|
19719
|
+
if (tok.startsWith(".")) return name.endsWith(tok);
|
|
19720
|
+
if (tok.endsWith("/*")) return type.startsWith(tok.slice(0, -1));
|
|
19721
|
+
return type === tok;
|
|
19722
|
+
});
|
|
19723
|
+
}
|
|
19724
|
+
static fmtSize(bytes) {
|
|
19725
|
+
if (bytes < 1e3) return `${bytes} B`;
|
|
19726
|
+
if (bytes < 1e6) return `${(bytes / 1e3).toFixed(0)} KB`;
|
|
19727
|
+
return `${(bytes / 1e6).toFixed(1)} MB`;
|
|
19728
|
+
}
|
|
19729
|
+
static ext(name) {
|
|
19730
|
+
const i = name.lastIndexOf(".");
|
|
19731
|
+
return i > -1 ? name.slice(i + 1).toUpperCase() : "FILE";
|
|
19732
|
+
}
|
|
19733
|
+
/**
|
|
19734
|
+
* Validate and append. Invalid files are kept as `error` rows rather than
|
|
19735
|
+
* dropped — that is how the Figma Failure state surfaces them.
|
|
19736
|
+
*/
|
|
19737
|
+
ingest(list) {
|
|
19738
|
+
if (!list || list.length === 0) return;
|
|
19739
|
+
const maxBytes = this.parseMaxBytes();
|
|
19740
|
+
const incoming = this.multiple ? Array.from(list) : Array.from(list).slice(0, 1);
|
|
19741
|
+
const added = [];
|
|
19742
|
+
const rejected = [];
|
|
19743
|
+
for (const f of incoming) {
|
|
19744
|
+
const entry = {
|
|
19745
|
+
id: `rel-upload-${++uid}`,
|
|
19746
|
+
name: f.name,
|
|
19747
|
+
size: f.size,
|
|
19748
|
+
status: "ready",
|
|
19749
|
+
file: f
|
|
19750
|
+
};
|
|
19751
|
+
if (!this.matchesAccept(f)) {
|
|
19752
|
+
entry.status = "error";
|
|
19753
|
+
entry.error = "unsupported file type";
|
|
19754
|
+
rejected.push({ name: f.name, reason: "format", message: `${f.name}: unsupported file type` });
|
|
19755
|
+
} else if (f.size > maxBytes) {
|
|
19756
|
+
entry.status = "error";
|
|
19757
|
+
entry.error = `file exceeds the ${this.maxSize} limit`;
|
|
19758
|
+
rejected.push({
|
|
19759
|
+
name: f.name,
|
|
19760
|
+
reason: "size",
|
|
19761
|
+
message: `${f.name}: exceeds ${this.maxSize}`
|
|
19762
|
+
});
|
|
19763
|
+
}
|
|
19764
|
+
added.push(entry);
|
|
19765
|
+
}
|
|
19766
|
+
const names = new Set(added.map((f) => f.name));
|
|
19767
|
+
const kept = this.multiple ? this.files.filter((f) => !names.has(f.name)) : [];
|
|
19768
|
+
this.files = [...kept, ...added];
|
|
19769
|
+
if (rejected.length) this.emit("rel-invalid", { rejected });
|
|
19770
|
+
this.emit("rel-files-added", { added, rejected });
|
|
19771
|
+
this.emit("rel-change", { files: this.files });
|
|
19772
|
+
this.notify();
|
|
19773
|
+
}
|
|
19774
|
+
// ── upload orchestration (self-driving mode) ──────────────────────────────
|
|
19775
|
+
patch(id, partial) {
|
|
19776
|
+
this.files = this.files.map((f) => f.id === id ? { ...f, ...partial } : f);
|
|
19777
|
+
this.notify();
|
|
19778
|
+
}
|
|
19779
|
+
startDelayTimer() {
|
|
19780
|
+
this.stopDelayTimer();
|
|
19781
|
+
if (this.delayThreshold <= 0) return;
|
|
19782
|
+
this.delayTimer = setTimeout(() => {
|
|
19783
|
+
if (this.files.some((f) => f.status === "uploading")) {
|
|
19784
|
+
this.delayed = true;
|
|
19785
|
+
this.notify();
|
|
19786
|
+
}
|
|
19787
|
+
}, this.delayThreshold);
|
|
19788
|
+
}
|
|
19789
|
+
stopDelayTimer() {
|
|
19790
|
+
if (this.delayTimer) clearTimeout(this.delayTimer);
|
|
19791
|
+
this.delayTimer = void 0;
|
|
19792
|
+
}
|
|
19793
|
+
async runBatch(targets) {
|
|
19794
|
+
const uploader = this.uploader;
|
|
19795
|
+
if (!uploader || targets.length === 0) return;
|
|
19796
|
+
this.aborter = new AbortController();
|
|
19797
|
+
const signal = this.aborter.signal;
|
|
19798
|
+
this.startDelayTimer();
|
|
19799
|
+
const queue = [...targets];
|
|
19800
|
+
const workers = Array.from({ length: Math.max(1, this.concurrency) }, async () => {
|
|
19801
|
+
while (queue.length) {
|
|
19802
|
+
if (signal.aborted) return;
|
|
19803
|
+
const item = queue.shift();
|
|
19804
|
+
if (!item || !item.file) continue;
|
|
19805
|
+
this.patch(item.id, { status: "uploading", progress: 0, loaded: 0, error: void 0 });
|
|
19806
|
+
try {
|
|
19807
|
+
await uploader(item.file, {
|
|
19808
|
+
file: item,
|
|
19809
|
+
signal,
|
|
19810
|
+
onProgress: (loaded, total) => {
|
|
19811
|
+
const t = total ?? item.size;
|
|
19812
|
+
const progress = t > 0 ? Math.min(100, Math.round(loaded / t * 100)) : 0;
|
|
19813
|
+
this.patch(item.id, { loaded, progress });
|
|
19814
|
+
this.emit("rel-progress", { id: item.id, progress, loaded, total: t });
|
|
19815
|
+
}
|
|
19816
|
+
});
|
|
19817
|
+
if (signal.aborted) {
|
|
19818
|
+
this.patch(item.id, { status: "ready", progress: void 0, loaded: void 0 });
|
|
19819
|
+
return;
|
|
19820
|
+
}
|
|
19821
|
+
this.patch(item.id, { status: "success", progress: 100, loaded: item.size });
|
|
19822
|
+
this.emit("rel-file-complete", { id: item.id, file: item.file });
|
|
19823
|
+
} catch (err) {
|
|
19824
|
+
if (signal.aborted) {
|
|
19825
|
+
this.patch(item.id, { status: "ready", progress: void 0, loaded: void 0 });
|
|
19826
|
+
return;
|
|
19827
|
+
}
|
|
19828
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
19829
|
+
this.patch(item.id, { status: "error", error: message, progress: void 0 });
|
|
19830
|
+
this.emit("rel-file-error", { id: item.id, file: item.file, error: message });
|
|
19831
|
+
}
|
|
19832
|
+
}
|
|
19833
|
+
});
|
|
19834
|
+
await Promise.all(workers);
|
|
19835
|
+
this.stopDelayTimer();
|
|
19836
|
+
this.delayed = false;
|
|
19837
|
+
this.aborter = void 0;
|
|
19838
|
+
if (!signal.aborted) {
|
|
19839
|
+
this.emit("rel-complete", {
|
|
19840
|
+
succeeded: this.files.filter((f) => f.status === "success"),
|
|
19841
|
+
failed: this.failedFiles
|
|
19842
|
+
});
|
|
19843
|
+
}
|
|
19844
|
+
this.notify();
|
|
19845
|
+
}
|
|
19846
|
+
// ── public API ────────────────────────────────────────────────────────────
|
|
19847
|
+
/** Start (or resume) the batch. Emits `rel-upload`; runs it when `uploader` is set. */
|
|
19848
|
+
upload() {
|
|
19849
|
+
if (!this.canUpload) return;
|
|
19850
|
+
const targets = this.pending;
|
|
19851
|
+
this.emit("rel-upload", { files: targets });
|
|
19852
|
+
if (this.uploader) void this.runBatch(targets);
|
|
19853
|
+
else this.notify();
|
|
19854
|
+
}
|
|
19855
|
+
/** Re-queue every retryable failed row and send again. */
|
|
19856
|
+
retry() {
|
|
19857
|
+
const targets = this.retryable;
|
|
19858
|
+
if (targets.length === 0) return;
|
|
19859
|
+
const ids = new Set(targets.map((f) => f.id));
|
|
19860
|
+
this.files = this.files.map(
|
|
19861
|
+
(f) => ids.has(f.id) ? { ...f, status: "ready", error: void 0, progress: void 0 } : f
|
|
19862
|
+
);
|
|
19863
|
+
const requeued = this.files.filter((f) => ids.has(f.id));
|
|
19864
|
+
this.emit("rel-retry", { files: requeued });
|
|
19865
|
+
if (this.uploader) void this.runBatch(requeued);
|
|
19866
|
+
else this.notify();
|
|
19867
|
+
}
|
|
19868
|
+
/** Abort anything in flight and emit `rel-cancel`. Does not clear the list. */
|
|
19869
|
+
cancel() {
|
|
19870
|
+
this.aborter?.abort();
|
|
19871
|
+
this.stopDelayTimer();
|
|
19872
|
+
this.delayed = false;
|
|
19873
|
+
this.emit("rel-cancel");
|
|
19874
|
+
this.notify();
|
|
19875
|
+
}
|
|
19876
|
+
/** Remove one row by id. */
|
|
19877
|
+
removeFile(id) {
|
|
19878
|
+
const target = this.files.find((f) => f.id === id);
|
|
19879
|
+
this.files = this.files.filter((f) => f.id !== id);
|
|
19880
|
+
this.emit("rel-remove", { id, file: target?.file });
|
|
19881
|
+
this.emit("rel-change", { files: this.files });
|
|
19882
|
+
this.notify();
|
|
19883
|
+
}
|
|
19884
|
+
/** Clear everything and abort in-flight transfers. */
|
|
19885
|
+
reset() {
|
|
19886
|
+
this.aborter?.abort();
|
|
19887
|
+
this.stopDelayTimer();
|
|
19888
|
+
this.files = [];
|
|
19889
|
+
this.delayed = false;
|
|
19890
|
+
if (this.inputEl) this.inputEl.value = "";
|
|
19891
|
+
this.emit("rel-change", { files: this.files });
|
|
19892
|
+
this.notify();
|
|
19893
|
+
}
|
|
19894
|
+
// ── events ────────────────────────────────────────────────────────────────
|
|
19895
|
+
emit(type, detail) {
|
|
19896
|
+
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
19897
|
+
}
|
|
19898
|
+
/**
|
|
19899
|
+
* Emit `rel-state` only when the derived snapshot actually changes, so
|
|
19900
|
+
* progress ticks don't spam `<rel-upload-actions>`.
|
|
19901
|
+
*/
|
|
19902
|
+
notify() {
|
|
19903
|
+
const s = this.state;
|
|
19904
|
+
const signature = `${s.phase}|${s.total}|${s.failed}|${s.canUpload}|${s.primaryLabel}`;
|
|
19905
|
+
if (signature === this.lastSignature) return;
|
|
19906
|
+
this.lastSignature = signature;
|
|
19907
|
+
this.emit("rel-state", s);
|
|
19908
|
+
}
|
|
19909
|
+
connectedCallback() {
|
|
19910
|
+
super.connectedCallback();
|
|
19911
|
+
queueMicrotask(() => this.emit("rel-state", this.state));
|
|
19912
|
+
}
|
|
19913
|
+
disconnectedCallback() {
|
|
19914
|
+
super.disconnectedCallback();
|
|
19915
|
+
this.stopDelayTimer();
|
|
19916
|
+
this.aborter?.abort();
|
|
19917
|
+
}
|
|
19918
|
+
updated() {
|
|
19919
|
+
this.notify();
|
|
19920
|
+
}
|
|
19921
|
+
// ── render ────────────────────────────────────────────────────────────────
|
|
19922
|
+
rowMeta(f) {
|
|
19923
|
+
switch (f.status) {
|
|
19924
|
+
case "uploading": {
|
|
19925
|
+
const pct = f.progress ?? 0;
|
|
19926
|
+
const loaded = RelUpload.fmtSize(f.loaded ?? 0);
|
|
19927
|
+
return `${pct}% - ${loaded} of ${RelUpload.fmtSize(f.size)}`;
|
|
19928
|
+
}
|
|
19929
|
+
case "success":
|
|
19930
|
+
return `${RelUpload.ext(f.name)} - ${RelUpload.fmtSize(f.size)} - Uploaded`;
|
|
19931
|
+
case "error":
|
|
19932
|
+
return `Upload failed - ${f.error ?? "please try again"}`;
|
|
19933
|
+
default: {
|
|
19934
|
+
const base = `${RelUpload.ext(f.name)} - ${RelUpload.fmtSize(f.size)}`;
|
|
19935
|
+
const busy = this.phase === "uploading" || this.phase === "delayed";
|
|
19936
|
+
return busy ? `${base} - Waiting` : base;
|
|
19937
|
+
}
|
|
19938
|
+
}
|
|
19939
|
+
}
|
|
19940
|
+
renderRow(f) {
|
|
19941
|
+
return import_lit57.html`
|
|
19942
|
+
<div class="row ${f.status}">
|
|
19943
|
+
<rel-icon name="document" size="md"></rel-icon>
|
|
19944
|
+
|
|
19945
|
+
<div class="row-content">
|
|
19946
|
+
<span class="row-name" title=${f.name}>${f.name}</span>
|
|
19947
|
+
${f.status === "uploading" ? import_lit57.html`<rel-progress
|
|
19948
|
+
mode="linear-determinate"
|
|
19949
|
+
.value=${f.progress ?? 0}
|
|
19950
|
+
></rel-progress>` : import_lit57.nothing}
|
|
19951
|
+
<span class="row-meta">${this.rowMeta(f)}</span>
|
|
19952
|
+
</div>
|
|
19953
|
+
|
|
19954
|
+
<div class="row-trailing">
|
|
19955
|
+
${f.status === "success" ? import_lit57.html`<rel-icon
|
|
19956
|
+
class="status-success"
|
|
19957
|
+
name="check-circle"
|
|
19958
|
+
size="md"
|
|
19959
|
+
role="img"
|
|
19960
|
+
aria-label="Uploaded"
|
|
19961
|
+
></rel-icon>` : import_lit57.nothing}
|
|
19962
|
+
${f.status === "error" ? import_lit57.html`<rel-icon
|
|
19963
|
+
class="status-error"
|
|
19964
|
+
name="error"
|
|
19965
|
+
size="md"
|
|
19966
|
+
role="img"
|
|
19967
|
+
aria-label="Failed"
|
|
19968
|
+
></rel-icon>` : import_lit57.nothing}
|
|
19969
|
+
<button
|
|
19970
|
+
class="remove"
|
|
19971
|
+
aria-label="Remove ${f.name}"
|
|
19972
|
+
?disabled=${this.disabled}
|
|
19973
|
+
@click=${() => this.removeFile(f.id)}
|
|
19974
|
+
>
|
|
19975
|
+
<rel-icon name="delete" size="md"></rel-icon>
|
|
19976
|
+
</button>
|
|
19977
|
+
</div>
|
|
19978
|
+
</div>
|
|
19979
|
+
`;
|
|
19980
|
+
}
|
|
19981
|
+
render() {
|
|
19982
|
+
const alert = this.alertContent;
|
|
19983
|
+
return import_lit57.html`
|
|
19984
|
+
<div class="body">
|
|
19985
|
+
${alert ? import_lit57.html`<rel-alert
|
|
19986
|
+
type=${alert.type}
|
|
19987
|
+
appearance="subtle"
|
|
19988
|
+
message1=${alert.message1}
|
|
19989
|
+
message2=${alert.message2}
|
|
19990
|
+
></rel-alert>` : import_lit57.nothing}
|
|
19991
|
+
|
|
19992
|
+
<div
|
|
19993
|
+
class="dropzone ${this.dragOver ? "dragover" : ""}"
|
|
19994
|
+
@click=${this.openPicker}
|
|
19995
|
+
@dragover=${this.onDragOver}
|
|
19996
|
+
@dragleave=${this.onDragLeave}
|
|
19997
|
+
@drop=${this.onDrop}
|
|
19998
|
+
>
|
|
19999
|
+
<span class="icon-badge">
|
|
20000
|
+
<rel-icon name="upload" size="md"></rel-icon>
|
|
20001
|
+
</span>
|
|
20002
|
+
|
|
20003
|
+
<div class="dz-copy">
|
|
20004
|
+
<p class="dz-title">${this.dropzoneTitle}</p>
|
|
20005
|
+
<p class="dz-hint">${this.hintText}</p>
|
|
20006
|
+
</div>
|
|
20007
|
+
|
|
20008
|
+
<button
|
|
20009
|
+
class="browse"
|
|
20010
|
+
type="button"
|
|
20011
|
+
?disabled=${this.disabled}
|
|
20012
|
+
@click=${(e) => {
|
|
20013
|
+
e.stopPropagation();
|
|
20014
|
+
this.openPicker();
|
|
20015
|
+
}}
|
|
20016
|
+
>
|
|
20017
|
+
${this.browseLabel}
|
|
20018
|
+
</button>
|
|
20019
|
+
</div>
|
|
20020
|
+
|
|
20021
|
+
${this.files.length ? import_lit57.html`<div class="file-list">
|
|
20022
|
+
<p class="list-heading">${this.listHeading}</p>
|
|
20023
|
+
<div class="rows" role="list">${this.files.map((f) => this.renderRow(f))}</div>
|
|
20024
|
+
</div>` : import_lit57.nothing}
|
|
20025
|
+
|
|
20026
|
+
<input
|
|
20027
|
+
class="file-input"
|
|
20028
|
+
type="file"
|
|
20029
|
+
?multiple=${this.multiple}
|
|
20030
|
+
accept=${this.accept || import_lit57.nothing}
|
|
20031
|
+
@change=${this.onInputChange}
|
|
20032
|
+
/>
|
|
20033
|
+
</div>
|
|
20034
|
+
`;
|
|
20035
|
+
}
|
|
20036
|
+
};
|
|
20037
|
+
RelUpload.styles = import_lit57.css`
|
|
20038
|
+
:host {
|
|
20039
|
+
display: block;
|
|
20040
|
+
box-sizing: border-box;
|
|
20041
|
+
color: var(--rel-text-default);
|
|
20042
|
+
font-family: var(--rel-fonts-font-family);
|
|
20043
|
+
}
|
|
20044
|
+
|
|
20045
|
+
:host([disabled]) {
|
|
20046
|
+
opacity: 0.6;
|
|
20047
|
+
}
|
|
20048
|
+
|
|
20049
|
+
:host([disabled]) .body {
|
|
20050
|
+
pointer-events: none;
|
|
20051
|
+
}
|
|
20052
|
+
|
|
20053
|
+
.body {
|
|
20054
|
+
display: flex;
|
|
20055
|
+
flex-direction: column;
|
|
20056
|
+
gap: var(--rel-spacing-lg, 20px);
|
|
20057
|
+
}
|
|
20058
|
+
|
|
20059
|
+
/* ── Dropzone ─────────────────────────────────────────────────────────── */
|
|
20060
|
+
|
|
20061
|
+
.dropzone {
|
|
20062
|
+
box-sizing: border-box;
|
|
20063
|
+
display: flex;
|
|
20064
|
+
flex-direction: column;
|
|
20065
|
+
align-items: center;
|
|
20066
|
+
justify-content: center;
|
|
20067
|
+
gap: var(--rel-spacing-md, 16px);
|
|
20068
|
+
padding: var(--rel-spacing-2xl, 32px) var(--rel-spacing-xl, 24px);
|
|
20069
|
+
background: var(--rel-bg-surface);
|
|
20070
|
+
border: 1px dashed var(--rel-border-accent);
|
|
20071
|
+
border-radius: var(--rel-border-radius-md, 16px);
|
|
20072
|
+
text-align: center;
|
|
20073
|
+
cursor: pointer;
|
|
20074
|
+
transition: border-color 0.15s ease, background 0.15s ease;
|
|
20075
|
+
}
|
|
20076
|
+
|
|
20077
|
+
/* Design gap: no drag-over frame exists in Figma. */
|
|
20078
|
+
.dropzone.dragover {
|
|
20079
|
+
border-style: solid;
|
|
20080
|
+
border-color: var(--rel-border-brand);
|
|
20081
|
+
background: color-mix(in srgb, var(--rel-bg-brand) 8%, var(--rel-bg-surface));
|
|
20082
|
+
}
|
|
20083
|
+
|
|
20084
|
+
.icon-badge {
|
|
20085
|
+
display: inline-flex;
|
|
20086
|
+
align-items: center;
|
|
20087
|
+
justify-content: center;
|
|
20088
|
+
padding: var(--rel-spacing-sm, 12px);
|
|
20089
|
+
background: var(--rel-bg-surface);
|
|
20090
|
+
border: 1px solid var(--rel-border-subtle);
|
|
20091
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
20092
|
+
}
|
|
20093
|
+
|
|
20094
|
+
.dz-copy {
|
|
20095
|
+
display: flex;
|
|
20096
|
+
flex-direction: column;
|
|
20097
|
+
align-items: center;
|
|
20098
|
+
gap: var(--rel-spacing-2xs, 4px);
|
|
20099
|
+
}
|
|
20100
|
+
|
|
20101
|
+
.dz-title {
|
|
20102
|
+
margin: 0;
|
|
20103
|
+
color: var(--rel-text-default);
|
|
20104
|
+
font-family: var(--rel-body-lg-medium-font-family);
|
|
20105
|
+
font-weight: var(--rel-body-lg-medium-font-weight);
|
|
20106
|
+
font-size: var(--rel-body-lg-medium-font-size);
|
|
20107
|
+
line-height: var(--rel-body-lg-medium-line-height);
|
|
20108
|
+
}
|
|
20109
|
+
|
|
20110
|
+
.dz-hint {
|
|
20111
|
+
margin: 0;
|
|
20112
|
+
color: var(--rel-text-caption);
|
|
20113
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
20114
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
20115
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
20116
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
20117
|
+
}
|
|
20118
|
+
|
|
20119
|
+
/* The dropzone is a mouse affordance only — the Browse button is the sole
|
|
20120
|
+
focusable control, so there are no nested interactive elements. */
|
|
20121
|
+
.browse {
|
|
20122
|
+
display: inline-flex;
|
|
20123
|
+
align-items: center;
|
|
20124
|
+
gap: var(--rel-spacing-xs, 8px);
|
|
20125
|
+
padding: var(--rel-spacing-xs, 8px) var(--rel-spacing-md, 16px);
|
|
20126
|
+
background: transparent;
|
|
20127
|
+
color: var(--rel-text-default);
|
|
20128
|
+
border: 1px solid var(--rel-action-neutral-border-outline, var(--rel-border-accent));
|
|
20129
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
20130
|
+
cursor: pointer;
|
|
20131
|
+
font-family: var(--rel-body-md-semibold-font-family);
|
|
20132
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
20133
|
+
font-size: var(--rel-body-md-semibold-font-size);
|
|
20134
|
+
line-height: var(--rel-body-md-semibold-line-height);
|
|
20135
|
+
}
|
|
20136
|
+
|
|
20137
|
+
.browse:hover:not(:disabled) {
|
|
20138
|
+
background: var(--rel-action-neutral-outline-hover, var(--rel-bg-surface-elevated));
|
|
20139
|
+
}
|
|
20140
|
+
|
|
20141
|
+
.browse:focus-visible {
|
|
20142
|
+
outline: 2px solid var(--rel-border-focus);
|
|
20143
|
+
outline-offset: 2px;
|
|
20144
|
+
}
|
|
20145
|
+
|
|
20146
|
+
.browse:disabled {
|
|
20147
|
+
cursor: not-allowed;
|
|
20148
|
+
}
|
|
20149
|
+
|
|
20150
|
+
/* ── File list ────────────────────────────────────────────────────────── */
|
|
20151
|
+
|
|
20152
|
+
.file-list {
|
|
20153
|
+
display: flex;
|
|
20154
|
+
flex-direction: column;
|
|
20155
|
+
gap: var(--rel-spacing-sm, 12px);
|
|
20156
|
+
}
|
|
20157
|
+
|
|
20158
|
+
.list-heading {
|
|
20159
|
+
margin: 0;
|
|
20160
|
+
color: var(--rel-text-default);
|
|
20161
|
+
font-family: var(--rel-body-md-semibold-font-family);
|
|
20162
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
20163
|
+
font-size: var(--rel-body-md-semibold-font-size);
|
|
20164
|
+
line-height: var(--rel-body-md-semibold-line-height);
|
|
20165
|
+
}
|
|
20166
|
+
|
|
20167
|
+
.rows {
|
|
20168
|
+
display: flex;
|
|
20169
|
+
flex-direction: column;
|
|
20170
|
+
gap: var(--rel-spacing-xs, 8px);
|
|
20171
|
+
}
|
|
20172
|
+
|
|
20173
|
+
.row {
|
|
20174
|
+
display: flex;
|
|
20175
|
+
align-items: center;
|
|
20176
|
+
gap: var(--rel-spacing-sm, 12px);
|
|
20177
|
+
box-sizing: border-box;
|
|
20178
|
+
padding: var(--rel-spacing-sm, 12px) var(--rel-spacing-md, 16px);
|
|
20179
|
+
background: var(--rel-bg-surface);
|
|
20180
|
+
border: 1px solid var(--rel-border-default);
|
|
20181
|
+
border-radius: var(--rel-border-radius-sm, 8px);
|
|
20182
|
+
}
|
|
20183
|
+
|
|
20184
|
+
.row.error {
|
|
20185
|
+
background: var(--rel-bg-danger-light);
|
|
20186
|
+
border-color: var(--rel-border-danger);
|
|
20187
|
+
}
|
|
20188
|
+
|
|
20189
|
+
.row > rel-icon {
|
|
20190
|
+
flex-shrink: 0;
|
|
20191
|
+
}
|
|
20192
|
+
|
|
20193
|
+
.row-content {
|
|
20194
|
+
display: flex;
|
|
20195
|
+
flex-direction: column;
|
|
20196
|
+
gap: var(--rel-spacing-3xs, 2px);
|
|
20197
|
+
flex: 1;
|
|
20198
|
+
min-width: 0;
|
|
20199
|
+
}
|
|
20200
|
+
|
|
20201
|
+
.row.uploading .row-content {
|
|
20202
|
+
gap: var(--rel-spacing-2xs, 4px);
|
|
20203
|
+
}
|
|
20204
|
+
|
|
20205
|
+
.row-name {
|
|
20206
|
+
overflow: hidden;
|
|
20207
|
+
text-overflow: ellipsis;
|
|
20208
|
+
white-space: nowrap;
|
|
20209
|
+
color: var(--rel-text-default);
|
|
20210
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
20211
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
20212
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
20213
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
20214
|
+
}
|
|
20215
|
+
|
|
20216
|
+
.row-meta {
|
|
20217
|
+
color: var(--rel-text-caption);
|
|
20218
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
20219
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
20220
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
20221
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
20222
|
+
}
|
|
20223
|
+
|
|
20224
|
+
.row.success .row-meta {
|
|
20225
|
+
color: var(--rel-text-success);
|
|
20226
|
+
}
|
|
20227
|
+
|
|
20228
|
+
.row.error .row-meta {
|
|
20229
|
+
color: var(--rel-text-danger);
|
|
20230
|
+
}
|
|
20231
|
+
|
|
20232
|
+
/* Figma's 4px rounded track; rel-progress ships a 6px square one. */
|
|
20233
|
+
rel-progress::part(track) {
|
|
20234
|
+
height: 4px;
|
|
20235
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
20236
|
+
}
|
|
20237
|
+
|
|
20238
|
+
rel-progress::part(indicator) {
|
|
20239
|
+
background: var(--rel-bg-success);
|
|
20240
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
20241
|
+
}
|
|
20242
|
+
|
|
20243
|
+
.row-trailing {
|
|
20244
|
+
display: flex;
|
|
20245
|
+
align-items: center;
|
|
20246
|
+
gap: var(--rel-spacing-sm, 12px);
|
|
20247
|
+
flex-shrink: 0;
|
|
20248
|
+
}
|
|
20249
|
+
|
|
20250
|
+
.status-success {
|
|
20251
|
+
color: var(--rel-text-success);
|
|
20252
|
+
}
|
|
20253
|
+
|
|
20254
|
+
.status-error {
|
|
20255
|
+
color: var(--rel-text-danger);
|
|
20256
|
+
}
|
|
20257
|
+
|
|
20258
|
+
.remove {
|
|
20259
|
+
display: inline-flex;
|
|
20260
|
+
align-items: center;
|
|
20261
|
+
justify-content: center;
|
|
20262
|
+
padding: 0;
|
|
20263
|
+
background: none;
|
|
20264
|
+
border: none;
|
|
20265
|
+
cursor: pointer;
|
|
20266
|
+
color: var(--rel-text-default);
|
|
20267
|
+
}
|
|
20268
|
+
|
|
20269
|
+
.remove:focus-visible {
|
|
20270
|
+
outline: 2px solid var(--rel-border-focus);
|
|
20271
|
+
outline-offset: 2px;
|
|
20272
|
+
border-radius: var(--rel-border-radius-xs, 4px);
|
|
20273
|
+
}
|
|
20274
|
+
|
|
20275
|
+
.file-input {
|
|
20276
|
+
display: none;
|
|
20277
|
+
}
|
|
20278
|
+
|
|
20279
|
+
@media (prefers-reduced-motion: reduce) {
|
|
20280
|
+
.dropzone {
|
|
20281
|
+
transition: none;
|
|
20282
|
+
}
|
|
20283
|
+
}
|
|
20284
|
+
`;
|
|
20285
|
+
__decorateClass([
|
|
20286
|
+
(0, import_decorators57.property)({ type: String })
|
|
20287
|
+
], RelUpload.prototype, "accept", 2);
|
|
20288
|
+
__decorateClass([
|
|
20289
|
+
(0, import_decorators57.property)({ type: String, attribute: "max-size" })
|
|
20290
|
+
], RelUpload.prototype, "maxSize", 2);
|
|
20291
|
+
__decorateClass([
|
|
20292
|
+
(0, import_decorators57.property)({ type: Boolean })
|
|
20293
|
+
], RelUpload.prototype, "multiple", 2);
|
|
20294
|
+
__decorateClass([
|
|
20295
|
+
(0, import_decorators57.property)({ type: Boolean, reflect: true })
|
|
20296
|
+
], RelUpload.prototype, "disabled", 2);
|
|
20297
|
+
__decorateClass([
|
|
20298
|
+
(0, import_decorators57.property)({ type: String })
|
|
20299
|
+
], RelUpload.prototype, "hint", 2);
|
|
20300
|
+
__decorateClass([
|
|
20301
|
+
(0, import_decorators57.property)({ type: String, attribute: "dropzone-title" })
|
|
20302
|
+
], RelUpload.prototype, "dropzoneTitle", 2);
|
|
20303
|
+
__decorateClass([
|
|
20304
|
+
(0, import_decorators57.property)({ type: String, attribute: "browse-label" })
|
|
20305
|
+
], RelUpload.prototype, "browseLabel", 2);
|
|
20306
|
+
__decorateClass([
|
|
20307
|
+
(0, import_decorators57.property)({ attribute: false })
|
|
20308
|
+
], RelUpload.prototype, "files", 2);
|
|
20309
|
+
__decorateClass([
|
|
20310
|
+
(0, import_decorators57.property)({ attribute: false })
|
|
20311
|
+
], RelUpload.prototype, "uploader", 2);
|
|
20312
|
+
__decorateClass([
|
|
20313
|
+
(0, import_decorators57.property)({ type: Number })
|
|
20314
|
+
], RelUpload.prototype, "concurrency", 2);
|
|
20315
|
+
__decorateClass([
|
|
20316
|
+
(0, import_decorators57.property)({ type: Number, attribute: "delay-threshold" })
|
|
20317
|
+
], RelUpload.prototype, "delayThreshold", 2);
|
|
20318
|
+
__decorateClass([
|
|
20319
|
+
(0, import_decorators57.state)()
|
|
20320
|
+
], RelUpload.prototype, "dragOver", 2);
|
|
20321
|
+
__decorateClass([
|
|
20322
|
+
(0, import_decorators57.state)()
|
|
20323
|
+
], RelUpload.prototype, "delayed", 2);
|
|
20324
|
+
__decorateClass([
|
|
20325
|
+
(0, import_decorators57.query)(".file-input")
|
|
20326
|
+
], RelUpload.prototype, "inputEl", 2);
|
|
20327
|
+
RelUpload = __decorateClass([
|
|
20328
|
+
(0, import_decorators57.customElement)("rel-upload")
|
|
20329
|
+
], RelUpload);
|
|
20330
|
+
|
|
20331
|
+
// src/components/rel-upload/rel-upload-actions.ts
|
|
20332
|
+
var import_lit58 = require("lit");
|
|
20333
|
+
var import_decorators58 = require("lit/decorators.js");
|
|
20334
|
+
var import_rel_icon18 = require("@reliability-design/icons/rel-icon");
|
|
20335
|
+
var EMPTY_STATE = {
|
|
20336
|
+
phase: "empty",
|
|
20337
|
+
total: 0,
|
|
20338
|
+
failed: 0,
|
|
20339
|
+
canUpload: false,
|
|
20340
|
+
primaryLabel: "Upload"
|
|
20341
|
+
};
|
|
20342
|
+
var RelUploadActions = class extends import_lit58.LitElement {
|
|
20343
|
+
constructor() {
|
|
20344
|
+
super(...arguments);
|
|
20345
|
+
this.for = "";
|
|
20346
|
+
this.cancelLabel = "Cancel";
|
|
20347
|
+
this.noCancel = false;
|
|
20348
|
+
this.snapshot = EMPTY_STATE;
|
|
20349
|
+
this.target = null;
|
|
20350
|
+
this.onState = (e) => {
|
|
20351
|
+
if (!this.target) this.resolveTarget();
|
|
20352
|
+
if (e.target !== this.target) return;
|
|
20353
|
+
this.snapshot = e.detail;
|
|
20354
|
+
};
|
|
20355
|
+
this.onCancel = () => {
|
|
20356
|
+
this.target?.cancel();
|
|
20357
|
+
this.dispatchEvent(new CustomEvent("rel-cancel", { bubbles: true, composed: true }));
|
|
20358
|
+
};
|
|
20359
|
+
this.onPrimary = () => {
|
|
20360
|
+
if (!this.target) return;
|
|
20361
|
+
if (this.snapshot.phase === "failed") this.target.retry();
|
|
20362
|
+
else this.target.upload();
|
|
20363
|
+
};
|
|
20364
|
+
}
|
|
20365
|
+
connectedCallback() {
|
|
20366
|
+
super.connectedCallback();
|
|
20367
|
+
queueMicrotask(() => this.resolveTarget());
|
|
20368
|
+
this.root.addEventListener("rel-state", this.onState);
|
|
20369
|
+
}
|
|
20370
|
+
disconnectedCallback() {
|
|
20371
|
+
super.disconnectedCallback();
|
|
20372
|
+
this.root.removeEventListener("rel-state", this.onState);
|
|
20373
|
+
this.target = null;
|
|
20374
|
+
}
|
|
20375
|
+
updated(changed) {
|
|
20376
|
+
if (changed.has("for")) this.resolveTarget();
|
|
20377
|
+
}
|
|
20378
|
+
/**
|
|
20379
|
+
* Listen on our own root rather than `document`: a shared root means no
|
|
20380
|
+
* shadow boundary sits between us and the sibling `<rel-upload>`, so
|
|
20381
|
+
* `event.target` is never retargeted away from it.
|
|
20382
|
+
*/
|
|
20383
|
+
get root() {
|
|
20384
|
+
const node = this.getRootNode();
|
|
20385
|
+
return node instanceof ShadowRoot ? node : this.ownerDocument;
|
|
20386
|
+
}
|
|
20387
|
+
resolveTarget() {
|
|
20388
|
+
const root = this.getRootNode();
|
|
20389
|
+
let found = null;
|
|
20390
|
+
if (this.for) {
|
|
20391
|
+
found = root.querySelector(`#${CSS.escape(this.for)}`);
|
|
20392
|
+
} else {
|
|
20393
|
+
found = this.parentElement?.querySelector("rel-upload") ?? root.querySelector("rel-upload");
|
|
20394
|
+
}
|
|
20395
|
+
this.target = found;
|
|
20396
|
+
if (found) this.snapshot = found.state;
|
|
20397
|
+
}
|
|
20398
|
+
render() {
|
|
20399
|
+
const { phase, canUpload, primaryLabel } = this.snapshot;
|
|
20400
|
+
const busy = phase === "uploading" || phase === "delayed";
|
|
20401
|
+
return import_lit58.html`
|
|
20402
|
+
<div class="actions" part="base">
|
|
20403
|
+
${this.noCancel ? import_lit58.nothing : import_lit58.html`<rel-button part="cancel" variant="secondary" @click=${this.onCancel}>
|
|
20404
|
+
${this.cancelLabel}
|
|
20405
|
+
</rel-button>`}
|
|
20406
|
+
|
|
20407
|
+
<rel-button
|
|
20408
|
+
part="primary"
|
|
20409
|
+
variant="primary"
|
|
20410
|
+
?loading=${busy}
|
|
20411
|
+
?disabled=${!canUpload}
|
|
20412
|
+
aria-label=${busy ? "Uploading" : primaryLabel}
|
|
20413
|
+
@click=${this.onPrimary}
|
|
20414
|
+
>
|
|
20415
|
+
${busy ? import_lit58.nothing : import_lit58.html`${phase === "failed" ? import_lit58.html`<rel-icon name="refresh" size="sm"></rel-icon>` : import_lit58.nothing}${primaryLabel}`}
|
|
20416
|
+
</rel-button>
|
|
20417
|
+
</div>
|
|
20418
|
+
`;
|
|
20419
|
+
}
|
|
20420
|
+
};
|
|
20421
|
+
RelUploadActions.styles = import_lit58.css`
|
|
20422
|
+
:host {
|
|
20423
|
+
display: block;
|
|
20424
|
+
font-family: var(--rel-fonts-font-family);
|
|
20425
|
+
}
|
|
20426
|
+
|
|
20427
|
+
.actions {
|
|
20428
|
+
display: flex;
|
|
20429
|
+
align-items: center;
|
|
20430
|
+
justify-content: flex-end;
|
|
20431
|
+
gap: var(--rel-spacing-xs, 8px);
|
|
20432
|
+
}
|
|
20433
|
+
|
|
20434
|
+
rel-button rel-icon {
|
|
20435
|
+
margin-inline-end: var(--rel-spacing-xs, 8px);
|
|
20436
|
+
}
|
|
20437
|
+
`;
|
|
20438
|
+
__decorateClass([
|
|
20439
|
+
(0, import_decorators58.property)({ type: String })
|
|
20440
|
+
], RelUploadActions.prototype, "for", 2);
|
|
20441
|
+
__decorateClass([
|
|
20442
|
+
(0, import_decorators58.property)({ type: String, attribute: "cancel-label" })
|
|
20443
|
+
], RelUploadActions.prototype, "cancelLabel", 2);
|
|
20444
|
+
__decorateClass([
|
|
20445
|
+
(0, import_decorators58.property)({ type: Boolean, attribute: "no-cancel" })
|
|
20446
|
+
], RelUploadActions.prototype, "noCancel", 2);
|
|
20447
|
+
__decorateClass([
|
|
20448
|
+
(0, import_decorators58.state)()
|
|
20449
|
+
], RelUploadActions.prototype, "snapshot", 2);
|
|
20450
|
+
RelUploadActions = __decorateClass([
|
|
20451
|
+
(0, import_decorators58.customElement)("rel-upload-actions")
|
|
20452
|
+
], RelUploadActions);
|
|
20453
|
+
|
|
18128
20454
|
// src/index.ts
|
|
18129
20455
|
__reExport(index_exports, require("@reliability-design/icons"), module.exports);
|
|
18130
20456
|
// Annotate the CommonJS export names for ESM import in node:
|
|
18131
20457
|
0 && (module.exports = {
|
|
20458
|
+
FocusTrap,
|
|
18132
20459
|
RelAccordion,
|
|
18133
20460
|
RelAlert,
|
|
18134
20461
|
RelAutocomplete,
|
|
@@ -18188,6 +20515,17 @@ __reExport(index_exports, require("@reliability-design/icons"), module.exports);
|
|
|
18188
20515
|
RelTopbarMobilePanel,
|
|
18189
20516
|
RelTopbarProfile,
|
|
18190
20517
|
RelTopbarUtilities,
|
|
20518
|
+
RelUpload,
|
|
20519
|
+
RelUploadActions,
|
|
20520
|
+
bindShortcut,
|
|
20521
|
+
formatShortcutParts,
|
|
20522
|
+
getDeepActiveElement,
|
|
20523
|
+
getFocusableElements,
|
|
20524
|
+
isApplePlatform,
|
|
20525
|
+
isEditableTarget,
|
|
20526
|
+
matchesShortcut,
|
|
20527
|
+
parseShortcut,
|
|
20528
|
+
trapTabKey,
|
|
18191
20529
|
...require("@reliability-design/icons")
|
|
18192
20530
|
});
|
|
18193
20531
|
//# sourceMappingURL=index.js.map
|