@reliability-design/web 1.4.2 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/dist/components/rel-autocomplete/README.md +4 -1
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +80 -3
- package/dist/components/rel-button/rel-button.ts +2 -2
- package/dist/components/rel-checkbox/README.md +69 -22
- package/dist/components/rel-checkbox/rel-checkbox.ts +116 -9
- package/dist/components/rel-pagination/README.md +14 -0
- package/dist/components/rel-pagination/rel-pagination.ts +30 -14
- package/dist/components/rel-radio/README.md +16 -1
- package/dist/components/rel-radio/rel-radio.ts +52 -1
- package/dist/components/rel-range-picker/rel-range-picker.ts +117 -2
- package/dist/components/rel-select/README.md +7 -0
- package/dist/components/rel-select/rel-select.ts +1237 -1119
- package/dist/components/rel-textfield/README.md +16 -0
- package/dist/components/rel-textfield/rel-textfield.ts +104 -6
- package/dist/components/rel-time-picker/README.md +199 -0
- package/dist/components/rel-time-picker/rel-time-picker.ts +994 -0
- 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 +453 -39
- package/dist/index.d.ts +453 -39
- package/dist/index.js +2659 -339
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2663 -346
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ __export(index_exports, {
|
|
|
76
76
|
RelTab: () => RelTab,
|
|
77
77
|
RelTabItem: () => RelTabItem,
|
|
78
78
|
RelTextfield: () => RelTextfield,
|
|
79
|
+
RelTimePicker: () => RelTimePicker,
|
|
79
80
|
RelToast: () => RelToast,
|
|
80
81
|
RelTooltip: () => RelTooltip,
|
|
81
82
|
RelTopbar: () => RelTopbar,
|
|
@@ -86,7 +87,9 @@ __export(index_exports, {
|
|
|
86
87
|
RelTopbarItem: () => RelTopbarItem,
|
|
87
88
|
RelTopbarMobilePanel: () => RelTopbarMobilePanel,
|
|
88
89
|
RelTopbarProfile: () => RelTopbarProfile,
|
|
89
|
-
RelTopbarUtilities: () => RelTopbarUtilities
|
|
90
|
+
RelTopbarUtilities: () => RelTopbarUtilities,
|
|
91
|
+
RelUpload: () => RelUpload,
|
|
92
|
+
RelUploadActions: () => RelUploadActions
|
|
90
93
|
});
|
|
91
94
|
module.exports = __toCommonJS(index_exports);
|
|
92
95
|
var import_vars = require("./vars-XNMTSRVN.css");
|
|
@@ -760,6 +763,7 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
760
763
|
this.internalState = "default";
|
|
761
764
|
this._wasError = false;
|
|
762
765
|
this.uniqueId = `textfield-${Math.random().toString(36).slice(2)}`;
|
|
766
|
+
this._warnedNoName = false;
|
|
763
767
|
}
|
|
764
768
|
get isDisabled() {
|
|
765
769
|
return this.disabled || this.state === "disabled";
|
|
@@ -775,6 +779,15 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
775
779
|
get helper() {
|
|
776
780
|
return this.helperText ?? this.supportingText;
|
|
777
781
|
}
|
|
782
|
+
/** labelStyle supersedes the deprecated floatingLabel boolean. */
|
|
783
|
+
get resolvedLabelStyle() {
|
|
784
|
+
if (this.labelStyle) return this.labelStyle;
|
|
785
|
+
return this.floatingLabel ? "floating" : "default";
|
|
786
|
+
}
|
|
787
|
+
/** True when no visible label renders and the input needs an ARIA name. */
|
|
788
|
+
get labelHidden() {
|
|
789
|
+
return this.resolvedLabelStyle === "none";
|
|
790
|
+
}
|
|
778
791
|
connectedCallback() {
|
|
779
792
|
super.connectedCallback();
|
|
780
793
|
const leftIcon = this.getAttribute("lefticon") || this.getAttribute("leftIcon");
|
|
@@ -782,6 +795,20 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
782
795
|
const rightIcon = this.getAttribute("righticon") || this.getAttribute("rightIcon");
|
|
783
796
|
if (rightIcon) this.rightIcon = rightIcon;
|
|
784
797
|
}
|
|
798
|
+
willUpdate(changed) {
|
|
799
|
+
if (changed.has("labelStyle") && this.labelStyle) {
|
|
800
|
+
this.floatingLabel = this.labelStyle === "floating";
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
updated() {
|
|
804
|
+
if (this._warnedNoName || !this.labelHidden) return;
|
|
805
|
+
if (this.fieldAriaLabel || this.fieldAriaLabelledby || this.getAttribute("aria-label")) return;
|
|
806
|
+
this._warnedNoName = true;
|
|
807
|
+
console.warn(
|
|
808
|
+
'<rel-textfield label-style="none"> has no accessible name. Set `field-aria-label` or `field-aria-labelledby` \u2014 a placeholder is not a valid naming mechanism.',
|
|
809
|
+
this
|
|
810
|
+
);
|
|
811
|
+
}
|
|
785
812
|
/** Focuses the inner input/textarea (the host itself is not focusable). */
|
|
786
813
|
focus(options) {
|
|
787
814
|
this.renderRoot.querySelector("input, textarea")?.focus(options);
|
|
@@ -849,15 +876,20 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
849
876
|
!this.multiline && leadingIcon ? "has-left-icon" : "",
|
|
850
877
|
!this.multiline && this.rightIcon && this.variant !== "legend" ? "has-right-icon" : ""
|
|
851
878
|
].filter(Boolean).join(" ");
|
|
879
|
+
const labelStyle = this.resolvedLabelStyle;
|
|
880
|
+
const labelHidden = labelStyle === "none";
|
|
852
881
|
const labelTemplate = (extraClass = "") => {
|
|
853
|
-
if (!this.label) return "";
|
|
882
|
+
if (!this.label || labelHidden) return "";
|
|
854
883
|
return import_lit3.html`<label
|
|
855
884
|
part="label"
|
|
856
885
|
class="textfield-label${extraClass}"
|
|
857
886
|
for="${inputId}"
|
|
858
887
|
>${this.label}${this.required ? import_lit3.html`<span class="required-mark" aria-hidden="true">*</span>` : ""}</label>`;
|
|
859
888
|
};
|
|
860
|
-
const
|
|
889
|
+
const legendLabelledby = this.variant === "legend" && this.label && !labelHidden ? legendId : void 0;
|
|
890
|
+
const ariaLabelledby = this.fieldAriaLabelledby ?? legendLabelledby;
|
|
891
|
+
const hasVisibleLabel = !labelHidden && !!this.label;
|
|
892
|
+
const ariaLabel = !hasVisibleLabel && !ariaLabelledby ? this.fieldAriaLabel : void 0;
|
|
861
893
|
const inputTemplate = this.multiline ? import_lit3.html`<textarea part="input"
|
|
862
894
|
id="${inputId}"
|
|
863
895
|
class="${inputClasses}"
|
|
@@ -871,6 +903,7 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
871
903
|
?required="${this.required}"
|
|
872
904
|
aria-invalid="${isError}"
|
|
873
905
|
aria-required="${this.required}"
|
|
906
|
+
aria-label="${ariaLabel ?? import_lit3.nothing}"
|
|
874
907
|
aria-labelledby="${ariaLabelledby ?? import_lit3.nothing}"
|
|
875
908
|
aria-describedby="${describedById ?? import_lit3.nothing}"
|
|
876
909
|
@focus="${this.handleFocus}"
|
|
@@ -889,6 +922,7 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
889
922
|
?required="${this.required}"
|
|
890
923
|
aria-invalid="${isError}"
|
|
891
924
|
aria-required="${this.required}"
|
|
925
|
+
aria-label="${ariaLabel ?? import_lit3.nothing}"
|
|
892
926
|
aria-labelledby="${ariaLabelledby ?? import_lit3.nothing}"
|
|
893
927
|
aria-describedby="${describedById ?? import_lit3.nothing}"
|
|
894
928
|
@focus="${this.handleFocus}"
|
|
@@ -910,7 +944,7 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
910
944
|
const fieldsetClass = ["field-fieldset", this.multiline ? "multiline" : ""].filter(Boolean).join(" ");
|
|
911
945
|
return import_lit3.html`
|
|
912
946
|
<div class="${wrapperClasses}">
|
|
913
|
-
<fieldset part="control" class="${fieldsetClass}"><legend class="field-legend" id="${legendId}">${this.label ?? ""}${this.required ? import_lit3.html`<span class="required-mark" aria-hidden="true">*</span>` : ""}</legend>
|
|
947
|
+
<fieldset part="control" class="${fieldsetClass}"><legend class="field-legend" id="${legendId}">${labelHidden ? "" : this.label ?? ""}${this.required && !labelHidden ? import_lit3.html`<span class="required-mark" aria-hidden="true">*</span>` : ""}</legend>
|
|
914
948
|
${!this.multiline ? leadingIconTemplate : ""}
|
|
915
949
|
${inputTemplate}
|
|
916
950
|
</fieldset>
|
|
@@ -920,10 +954,10 @@ var RelTextfield = class extends import_lit3.LitElement {
|
|
|
920
954
|
}
|
|
921
955
|
return import_lit3.html`
|
|
922
956
|
<div class="${wrapperClasses}">
|
|
923
|
-
${
|
|
957
|
+
${labelHidden ? import_lit3.html`<div class="label-reserve" aria-hidden="true"></div>` : labelStyle !== "floating" ? labelTemplate() : ""}
|
|
924
958
|
|
|
925
959
|
<div part="control" class="${["textfield-container", this.isDisabled ? "disabled" : "", this.multiline ? "multiline" : ""].filter(Boolean).join(" ")}">
|
|
926
|
-
${
|
|
960
|
+
${labelStyle === "floating" ? labelTemplate(leadingIcon && !this.multiline ? " has-left-icon" : "") : ""}
|
|
927
961
|
${!this.multiline ? leadingIconTemplate : ""}
|
|
928
962
|
|
|
929
963
|
${inputTemplate}
|
|
@@ -982,6 +1016,22 @@ RelTextfield.styles = import_lit3.css`
|
|
|
982
1016
|
margin-left: 2px;
|
|
983
1017
|
}
|
|
984
1018
|
|
|
1019
|
+
/* ── labelStyle="none" ──
|
|
1020
|
+
No label renders. An empty spacer holds the label row's full height so the
|
|
1021
|
+
input box still bottom-aligns with labelled siblings in a form row.
|
|
1022
|
+
The reserve covers label line-height + the wrapper gap, and the negative
|
|
1023
|
+
margin cancels the flex gap that would otherwise sit below it — so
|
|
1024
|
+
--rel-field-label-reserve: 0 collapses the row to genuinely zero
|
|
1025
|
+
(toolbars, table cells), not just to the leftover gap. */
|
|
1026
|
+
.label-reserve {
|
|
1027
|
+
flex: none;
|
|
1028
|
+
height: var(
|
|
1029
|
+
--rel-field-label-reserve,
|
|
1030
|
+
calc(var(--rel-body-sm-medium-line-height) + var(--rel-spacing-2xs))
|
|
1031
|
+
);
|
|
1032
|
+
margin-bottom: calc(-1 * var(--rel-spacing-2xs));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
985
1035
|
/* Floating label variant */
|
|
986
1036
|
:host([floatinglabel]) .textfield-wrapper {
|
|
987
1037
|
gap: 0;
|
|
@@ -1358,6 +1408,12 @@ RelTextfield.styles = import_lit3.css`
|
|
|
1358
1408
|
box-sizing: border-box;
|
|
1359
1409
|
}
|
|
1360
1410
|
|
|
1411
|
+
/* labelStyle="none" + legend: nothing to seat in the notch, so drop the
|
|
1412
|
+
legend entirely and let the top border run continuous. */
|
|
1413
|
+
:host([label-style="none"][variant="legend"]) .field-legend {
|
|
1414
|
+
display: none;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1361
1417
|
/* Prefix icon (legend): vertically centered; input inset via has-left-icon */
|
|
1362
1418
|
:host([variant="legend"]) .icon.left {
|
|
1363
1419
|
top: 50%;
|
|
@@ -1473,9 +1529,18 @@ __decorateClass([
|
|
|
1473
1529
|
__decorateClass([
|
|
1474
1530
|
(0, import_decorators3.property)({ type: String })
|
|
1475
1531
|
], RelTextfield.prototype, "size", 2);
|
|
1532
|
+
__decorateClass([
|
|
1533
|
+
(0, import_decorators3.property)({ type: String, attribute: "label-style", reflect: true })
|
|
1534
|
+
], RelTextfield.prototype, "labelStyle", 2);
|
|
1476
1535
|
__decorateClass([
|
|
1477
1536
|
(0, import_decorators3.property)({ type: Boolean, reflect: true })
|
|
1478
1537
|
], RelTextfield.prototype, "floatingLabel", 2);
|
|
1538
|
+
__decorateClass([
|
|
1539
|
+
(0, import_decorators3.property)({ type: String, attribute: "field-aria-label" })
|
|
1540
|
+
], RelTextfield.prototype, "fieldAriaLabel", 2);
|
|
1541
|
+
__decorateClass([
|
|
1542
|
+
(0, import_decorators3.property)({ type: String, attribute: "field-aria-labelledby" })
|
|
1543
|
+
], RelTextfield.prototype, "fieldAriaLabelledby", 2);
|
|
1479
1544
|
__decorateClass([
|
|
1480
1545
|
(0, import_decorators3.property)({ type: String, reflect: true })
|
|
1481
1546
|
], RelTextfield.prototype, "variant", 2);
|
|
@@ -1585,12 +1650,26 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1585
1650
|
if (left + t.width > window.innerWidth - 8) {
|
|
1586
1651
|
left = Math.max(8, window.innerWidth - t.width - 8);
|
|
1587
1652
|
}
|
|
1653
|
+
this.panelEl.style.top = `${top}px`;
|
|
1654
|
+
this.panelEl.style.left = `${left}px`;
|
|
1655
|
+
this.panelEl.style.width = `${t.width}px`;
|
|
1656
|
+
const applied = this.panelEl.getBoundingClientRect();
|
|
1657
|
+
top -= applied.top - top;
|
|
1658
|
+
left -= applied.left - left;
|
|
1659
|
+
this.panelEl.style.top = `${top}px`;
|
|
1660
|
+
this.panelEl.style.left = `${left}px`;
|
|
1588
1661
|
this.panelStyle = `top:${top}px;left:${left}px;width:${t.width}px;`;
|
|
1589
1662
|
};
|
|
1590
1663
|
}
|
|
1664
|
+
/** Name used for the suggestion panel — the visible label, or fieldAriaLabel
|
|
1665
|
+
* once labelStyle="none" hides it. Mirrors rel-textfield's own resolution. */
|
|
1666
|
+
get accessibleName() {
|
|
1667
|
+
return (this.labelStyle === "none" ? this.fieldAriaLabel : this.label) || "";
|
|
1668
|
+
}
|
|
1591
1669
|
disconnectedCallback() {
|
|
1592
1670
|
super.disconnectedCallback();
|
|
1593
1671
|
this.removeGlobalListeners();
|
|
1672
|
+
this.hideNativePopover();
|
|
1594
1673
|
clearTimeout(this.debounceTimer);
|
|
1595
1674
|
}
|
|
1596
1675
|
willUpdate(changed) {
|
|
@@ -1650,13 +1729,40 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1650
1729
|
this.open = true;
|
|
1651
1730
|
this.highlightedIndex = -1;
|
|
1652
1731
|
this.addGlobalListeners();
|
|
1653
|
-
requestAnimationFrame(() =>
|
|
1732
|
+
requestAnimationFrame(() => {
|
|
1733
|
+
this.showNativePopover();
|
|
1734
|
+
this.updatePosition();
|
|
1735
|
+
});
|
|
1654
1736
|
}
|
|
1655
1737
|
closePanel() {
|
|
1656
1738
|
if (!this.open) return;
|
|
1657
1739
|
this.open = false;
|
|
1658
1740
|
this.highlightedIndex = -1;
|
|
1659
1741
|
this.removeGlobalListeners();
|
|
1742
|
+
this.hideNativePopover();
|
|
1743
|
+
}
|
|
1744
|
+
/* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
1745
|
+
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
1746
|
+
* descendants, breaking viewport-based coordinates and letting the
|
|
1747
|
+
* panel's overflow:hidden clip the suggestions. Promoting the panel to
|
|
1748
|
+
* the browser top layer sidesteps both — same fix as rel-popover.
|
|
1749
|
+
* `manual` keeps light-dismiss and Escape handling under this
|
|
1750
|
+
* component's own control. */
|
|
1751
|
+
showNativePopover() {
|
|
1752
|
+
const p = this.panelEl;
|
|
1753
|
+
if (!p || typeof p.showPopover !== "function") return;
|
|
1754
|
+
try {
|
|
1755
|
+
if (!p.matches(":popover-open")) p.showPopover();
|
|
1756
|
+
} catch {
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
hideNativePopover() {
|
|
1760
|
+
const p = this.panelEl;
|
|
1761
|
+
if (!p || typeof p.hidePopover !== "function") return;
|
|
1762
|
+
try {
|
|
1763
|
+
if (p.matches(":popover-open")) p.hidePopover();
|
|
1764
|
+
} catch {
|
|
1765
|
+
}
|
|
1660
1766
|
}
|
|
1661
1767
|
/** Re-evaluates whether the panel should be showing for the current state. */
|
|
1662
1768
|
syncPanel() {
|
|
@@ -1692,17 +1798,17 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1692
1798
|
this.syncPanel();
|
|
1693
1799
|
}
|
|
1694
1800
|
}
|
|
1695
|
-
async runSearch(
|
|
1801
|
+
async runSearch(query12) {
|
|
1696
1802
|
const seq = ++this.searchSeq;
|
|
1697
1803
|
try {
|
|
1698
|
-
const results = await this.searchFn(
|
|
1804
|
+
const results = await this.searchFn(query12);
|
|
1699
1805
|
if (seq !== this.searchSeq) return;
|
|
1700
1806
|
this.remoteItems = results ?? [];
|
|
1701
1807
|
} catch (err) {
|
|
1702
1808
|
if (seq !== this.searchSeq) return;
|
|
1703
1809
|
this.remoteItems = [];
|
|
1704
1810
|
this.dispatchEvent(
|
|
1705
|
-
new CustomEvent("rel-search-error", { detail: { query:
|
|
1811
|
+
new CustomEvent("rel-search-error", { detail: { query: query12, error: err }, bubbles: true, composed: true })
|
|
1706
1812
|
);
|
|
1707
1813
|
} finally {
|
|
1708
1814
|
if (seq === this.searchSeq) {
|
|
@@ -1848,7 +1954,10 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1848
1954
|
.value="${this.inputText}"
|
|
1849
1955
|
.size="${this.size}"
|
|
1850
1956
|
.variant="${this.variant}"
|
|
1957
|
+
.labelStyle="${this.labelStyle}"
|
|
1851
1958
|
.floatingLabel="${this.floatingLabel}"
|
|
1959
|
+
.fieldAriaLabel="${this.fieldAriaLabel}"
|
|
1960
|
+
.fieldAriaLabelledby="${this.fieldAriaLabelledby}"
|
|
1852
1961
|
.prefixIcon="${this.prefixIcon}"
|
|
1853
1962
|
.disabled="${this.disabled}"
|
|
1854
1963
|
.readonly="${this.readonly}"
|
|
@@ -1869,8 +1978,9 @@ var RelAutocomplete = class extends import_lit4.LitElement {
|
|
|
1869
1978
|
id="${panelId}"
|
|
1870
1979
|
part="panel list"
|
|
1871
1980
|
class="ac-panel"
|
|
1981
|
+
popover="manual"
|
|
1872
1982
|
role="listbox"
|
|
1873
|
-
aria-label="${this.
|
|
1983
|
+
aria-label="${this.accessibleName || "Suggestions"}"
|
|
1874
1984
|
style="${this.panelStyle}"
|
|
1875
1985
|
?data-open="${this.open}"
|
|
1876
1986
|
>
|
|
@@ -1923,6 +2033,15 @@ RelAutocomplete.styles = import_lit4.css`
|
|
|
1923
2033
|
position: fixed;
|
|
1924
2034
|
z-index: 1001;
|
|
1925
2035
|
display: none;
|
|
2036
|
+
/* Reset native [popover] UA styles (the panel is promoted to the
|
|
2037
|
+
top layer so transformed ancestors — e.g. a centered rel-modal
|
|
2038
|
+
panel — can't hijack its containing block or clip it). */
|
|
2039
|
+
inset: auto;
|
|
2040
|
+
margin: 0;
|
|
2041
|
+
padding: 0;
|
|
2042
|
+
width: auto;
|
|
2043
|
+
height: auto;
|
|
2044
|
+
color: inherit;
|
|
1926
2045
|
box-sizing: border-box;
|
|
1927
2046
|
background: var(--rel-autocomplete-bg, var(--rel-bg-neutral));
|
|
1928
2047
|
border: 1px solid var(--rel-autocomplete-menu-border, var(--rel-border-default));
|
|
@@ -2049,9 +2168,18 @@ __decorateClass([
|
|
|
2049
2168
|
__decorateClass([
|
|
2050
2169
|
(0, import_decorators4.property)({ type: String })
|
|
2051
2170
|
], RelAutocomplete.prototype, "variant", 2);
|
|
2171
|
+
__decorateClass([
|
|
2172
|
+
(0, import_decorators4.property)({ type: String, attribute: "label-style", reflect: true })
|
|
2173
|
+
], RelAutocomplete.prototype, "labelStyle", 2);
|
|
2052
2174
|
__decorateClass([
|
|
2053
2175
|
(0, import_decorators4.property)({ type: Boolean, attribute: "floating-label" })
|
|
2054
2176
|
], RelAutocomplete.prototype, "floatingLabel", 2);
|
|
2177
|
+
__decorateClass([
|
|
2178
|
+
(0, import_decorators4.property)({ type: String, attribute: "field-aria-label" })
|
|
2179
|
+
], RelAutocomplete.prototype, "fieldAriaLabel", 2);
|
|
2180
|
+
__decorateClass([
|
|
2181
|
+
(0, import_decorators4.property)({ type: String, attribute: "field-aria-labelledby" })
|
|
2182
|
+
], RelAutocomplete.prototype, "fieldAriaLabelledby", 2);
|
|
2055
2183
|
__decorateClass([
|
|
2056
2184
|
(0, import_decorators4.property)({ type: String, attribute: "prefix-icon" })
|
|
2057
2185
|
], RelAutocomplete.prototype, "prefixIcon", 2);
|
|
@@ -2763,8 +2891,8 @@ RelButton.styles = import_lit8.css`
|
|
|
2763
2891
|
pointer-events:none on the HOST would make it transparent to the
|
|
2764
2892
|
pointer and suppress the cursor entirely. */
|
|
2765
2893
|
cursor: not-allowed;
|
|
2766
|
-
/* The disabled tokens are full-contrast pairs (light:
|
|
2767
|
-
|
|
2894
|
+
/* The disabled tokens are full-contrast pairs (light: near-black on
|
|
2895
|
+
gray-300, dark: white on gray-700), so on their own they render a
|
|
2768
2896
|
crisp button that reads as enabled. Dulling happens here instead —
|
|
2769
2897
|
same opacity mechanism the rest of the library uses. */
|
|
2770
2898
|
opacity: 0.5;
|
|
@@ -4387,6 +4515,9 @@ var RelCheckbox = class extends import_lit13.LitElement {
|
|
|
4387
4515
|
this.state = "default";
|
|
4388
4516
|
this.status = "unchecked";
|
|
4389
4517
|
this.value = "";
|
|
4518
|
+
this.size = "medium";
|
|
4519
|
+
this.density = "compact";
|
|
4520
|
+
this.alignment = "leading";
|
|
4390
4521
|
this._onChange = () => {
|
|
4391
4522
|
};
|
|
4392
4523
|
this._onTouched = () => {
|
|
@@ -4398,6 +4529,17 @@ var RelCheckbox = class extends import_lit13.LitElement {
|
|
|
4398
4529
|
set checked(val) {
|
|
4399
4530
|
this.status = val ? "checked" : "unchecked";
|
|
4400
4531
|
}
|
|
4532
|
+
willUpdate(changed) {
|
|
4533
|
+
if ((changed.has("alignment") || changed.has("density")) && this.alignment === "trailing" && this.density === "compact") {
|
|
4534
|
+
console.warn(
|
|
4535
|
+
'[rel-checkbox] density="compact" has no trailing layout; rendering comfortable density.'
|
|
4536
|
+
);
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
/** Trailing has no compact layout in the spec — trailing wins. */
|
|
4540
|
+
get effectiveDensity() {
|
|
4541
|
+
return this.alignment === "trailing" ? "comfortable" : this.density;
|
|
4542
|
+
}
|
|
4401
4543
|
toggle() {
|
|
4402
4544
|
if (this.state === "disabled") return;
|
|
4403
4545
|
this.status = this.status === "checked" ? "unchecked" : "checked";
|
|
@@ -4445,12 +4587,22 @@ var RelCheckbox = class extends import_lit13.LitElement {
|
|
|
4445
4587
|
</svg>
|
|
4446
4588
|
</span>
|
|
4447
4589
|
|
|
4448
|
-
|
|
4590
|
+
<span class="content">
|
|
4591
|
+
<span class="label-line">
|
|
4592
|
+
${this.icon ? import_lit13.html`<rel-icon
|
|
4593
|
+
name="${this.icon}"
|
|
4594
|
+
size="${this.size === "small" ? "sm" : "md"}"
|
|
4595
|
+
class="icon-prefix"
|
|
4596
|
+
></rel-icon>` : null}
|
|
4449
4597
|
|
|
4450
|
-
|
|
4598
|
+
<span part="label" class="label">${this.label}</span>
|
|
4599
|
+
</span>
|
|
4600
|
+
|
|
4601
|
+
${this.effectiveDensity === "comfortable" && this.supportingtext ? import_lit13.html`<span part="helper" class="text">${this.supportingtext}</span>` : null}
|
|
4602
|
+
</span>
|
|
4451
4603
|
</label>
|
|
4452
4604
|
|
|
4453
|
-
${this.supportingtext ? import_lit13.html`<span part="helper" class="text">${this.supportingtext}</span>` : null}
|
|
4605
|
+
${this.effectiveDensity === "compact" && this.supportingtext ? import_lit13.html`<span part="helper" class="text text-below">${this.supportingtext}</span>` : null}
|
|
4454
4606
|
</div>
|
|
4455
4607
|
`;
|
|
4456
4608
|
}
|
|
@@ -4483,6 +4635,29 @@ RelCheckbox.styles = import_lit13.css`
|
|
|
4483
4635
|
cursor: pointer;
|
|
4484
4636
|
}
|
|
4485
4637
|
|
|
4638
|
+
/* Trailing alignment — box moves to the far edge, label stack to the
|
|
4639
|
+
start. row-reverse keeps the box first in DOM order (input + .box
|
|
4640
|
+
adjacency drives the focus ring) while space-between stretches the
|
|
4641
|
+
row to its container. */
|
|
4642
|
+
:host([alignment="trailing"]) .row {
|
|
4643
|
+
flex-direction: row-reverse;
|
|
4644
|
+
justify-content: space-between;
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4647
|
+
/* Comfortable density — label and supporting text stack beside the box. */
|
|
4648
|
+
.content {
|
|
4649
|
+
display: flex;
|
|
4650
|
+
flex-direction: column;
|
|
4651
|
+
gap: var(--rel-spacing-3xs);
|
|
4652
|
+
min-width: 0;
|
|
4653
|
+
}
|
|
4654
|
+
|
|
4655
|
+
.label-line {
|
|
4656
|
+
display: flex;
|
|
4657
|
+
align-items: center;
|
|
4658
|
+
gap: var(--rel-spacing-xs);
|
|
4659
|
+
}
|
|
4660
|
+
|
|
4486
4661
|
input {
|
|
4487
4662
|
position: absolute;
|
|
4488
4663
|
inset: 0;
|
|
@@ -4625,7 +4800,6 @@ RelCheckbox.styles = import_lit13.css`
|
|
|
4625
4800
|
|
|
4626
4801
|
.text {
|
|
4627
4802
|
color: var(--rel-text-caption);
|
|
4628
|
-
padding-left: 28px;
|
|
4629
4803
|
|
|
4630
4804
|
font-family: var(--rel-body-sm-regular-font-family);
|
|
4631
4805
|
|
|
@@ -4638,6 +4812,44 @@ RelCheckbox.styles = import_lit13.css`
|
|
|
4638
4812
|
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
4639
4813
|
|
|
4640
4814
|
}
|
|
4815
|
+
|
|
4816
|
+
/* Compact density — supporting text sits under the whole row, indented
|
|
4817
|
+
past the box (box width + row gap, matching the pre-existing 28px). */
|
|
4818
|
+
.text-below {
|
|
4819
|
+
padding-left: 28px;
|
|
4820
|
+
}
|
|
4821
|
+
|
|
4822
|
+
/* Size variant (medium is the base scale above). */
|
|
4823
|
+
:host([size="small"]) .box {
|
|
4824
|
+
width: 14px;
|
|
4825
|
+
height: 14px;
|
|
4826
|
+
border-radius: 3px;
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4829
|
+
:host([size="small"]) svg {
|
|
4830
|
+
width: 10px;
|
|
4831
|
+
height: 10px;
|
|
4832
|
+
}
|
|
4833
|
+
|
|
4834
|
+
:host([size="small"]) .label {
|
|
4835
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
4836
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
4837
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
4838
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
4839
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
:host([size="small"]) .text {
|
|
4843
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
4844
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
4845
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
4846
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
4847
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
4848
|
+
}
|
|
4849
|
+
|
|
4850
|
+
:host([size="small"]) .text-below {
|
|
4851
|
+
padding-left: 24px;
|
|
4852
|
+
}
|
|
4641
4853
|
`;
|
|
4642
4854
|
__decorateClass([
|
|
4643
4855
|
(0, import_decorators13.property)({ type: String })
|
|
@@ -4657,6 +4869,15 @@ __decorateClass([
|
|
|
4657
4869
|
__decorateClass([
|
|
4658
4870
|
(0, import_decorators13.property)({ type: String })
|
|
4659
4871
|
], RelCheckbox.prototype, "value", 2);
|
|
4872
|
+
__decorateClass([
|
|
4873
|
+
(0, import_decorators13.property)({ type: String, reflect: true })
|
|
4874
|
+
], RelCheckbox.prototype, "size", 2);
|
|
4875
|
+
__decorateClass([
|
|
4876
|
+
(0, import_decorators13.property)({ type: String, reflect: true })
|
|
4877
|
+
], RelCheckbox.prototype, "density", 2);
|
|
4878
|
+
__decorateClass([
|
|
4879
|
+
(0, import_decorators13.property)({ type: String, reflect: true })
|
|
4880
|
+
], RelCheckbox.prototype, "alignment", 2);
|
|
4660
4881
|
RelCheckbox = __decorateClass([
|
|
4661
4882
|
(0, import_decorators13.customElement)("rel-checkbox")
|
|
4662
4883
|
], RelCheckbox);
|
|
@@ -5788,6 +6009,31 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
5788
6009
|
this.viewMonth = (/* @__PURE__ */ new Date()).getMonth();
|
|
5789
6010
|
this.isSelectingRange = false;
|
|
5790
6011
|
this.hoverDate = null;
|
|
6012
|
+
this.onReposition = () => {
|
|
6013
|
+
if (this.open && this.mode === "popover") this.updatePopoverPosition();
|
|
6014
|
+
};
|
|
6015
|
+
this.updatePopoverPosition = () => {
|
|
6016
|
+
const panel = this.popoverEl;
|
|
6017
|
+
if (!panel || !this.open || this.mode !== "popover") return;
|
|
6018
|
+
const t = this.getBoundingClientRect();
|
|
6019
|
+
const c = panel.getBoundingClientRect();
|
|
6020
|
+
let top = t.bottom + 4;
|
|
6021
|
+
if (top + c.height > window.innerHeight - 8) {
|
|
6022
|
+
top = Math.max(8, t.top - c.height - 4);
|
|
6023
|
+
}
|
|
6024
|
+
let left = t.left;
|
|
6025
|
+
if (left + c.width > window.innerWidth - 8) {
|
|
6026
|
+
left = Math.max(8, window.innerWidth - c.width - 8);
|
|
6027
|
+
}
|
|
6028
|
+
panel.style.position = "fixed";
|
|
6029
|
+
panel.style.top = `${top}px`;
|
|
6030
|
+
panel.style.left = `${left}px`;
|
|
6031
|
+
const applied = panel.getBoundingClientRect();
|
|
6032
|
+
top -= applied.top - top;
|
|
6033
|
+
left -= applied.left - left;
|
|
6034
|
+
panel.style.top = `${top}px`;
|
|
6035
|
+
panel.style.left = `${left}px`;
|
|
6036
|
+
};
|
|
5791
6037
|
// ── Open / close ──
|
|
5792
6038
|
this.handleClickOutside = (e) => {
|
|
5793
6039
|
if (!this.open) return;
|
|
@@ -5830,14 +6076,64 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
5830
6076
|
super.disconnectedCallback();
|
|
5831
6077
|
document.removeEventListener("click", this.handleClickOutside);
|
|
5832
6078
|
document.removeEventListener("keydown", this.handleGlobalKeydown);
|
|
6079
|
+
window.removeEventListener("resize", this.onReposition);
|
|
6080
|
+
window.removeEventListener("scroll", this.onReposition, true);
|
|
6081
|
+
this.hideTopLayer();
|
|
5833
6082
|
}
|
|
5834
|
-
updated() {
|
|
6083
|
+
updated(changed) {
|
|
5835
6084
|
this.style.width = this.width;
|
|
5836
6085
|
this.style.minWidth = this.minWidth;
|
|
5837
6086
|
this.style.maxWidth = this.maxWidth;
|
|
5838
6087
|
this.style.height = this.height;
|
|
5839
6088
|
this.style.minHeight = this.minHeight;
|
|
5840
6089
|
this.style.maxHeight = this.maxHeight;
|
|
6090
|
+
if (changed.has("open")) {
|
|
6091
|
+
if (this.open) {
|
|
6092
|
+
this.showTopLayer();
|
|
6093
|
+
window.addEventListener("resize", this.onReposition);
|
|
6094
|
+
window.addEventListener("scroll", this.onReposition, true);
|
|
6095
|
+
} else {
|
|
6096
|
+
window.removeEventListener("resize", this.onReposition);
|
|
6097
|
+
window.removeEventListener("scroll", this.onReposition, true);
|
|
6098
|
+
this.hideTopLayer();
|
|
6099
|
+
}
|
|
6100
|
+
}
|
|
6101
|
+
}
|
|
6102
|
+
/* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
6103
|
+
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
6104
|
+
* descendants, and its overflow:hidden clips position:absolute ones —
|
|
6105
|
+
* either way the picker shell breaks inside a modal. Promoting shell
|
|
6106
|
+
* (and popup-mode backdrop) to the browser top layer sidesteps both —
|
|
6107
|
+
* same fix as rel-popover / rel-autocomplete / rel-select. `manual`
|
|
6108
|
+
* keeps outside-click and Escape handling under this component's own
|
|
6109
|
+
* control. While in the top layer the CSS anchoring (absolute, below
|
|
6110
|
+
* the trigger) is meaningless, so popover mode gets inline fixed
|
|
6111
|
+
* coordinates from updatePopoverPosition(). */
|
|
6112
|
+
showTopLayer() {
|
|
6113
|
+
for (const el of [this.mode === "popup" ? this.backdropEl : void 0, this.popoverEl]) {
|
|
6114
|
+
if (!el || typeof el.showPopover !== "function") continue;
|
|
6115
|
+
try {
|
|
6116
|
+
if (!el.matches(":popover-open")) el.showPopover();
|
|
6117
|
+
} catch {
|
|
6118
|
+
}
|
|
6119
|
+
}
|
|
6120
|
+
if (this.mode === "popover") {
|
|
6121
|
+
requestAnimationFrame(() => this.updatePopoverPosition());
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
6124
|
+
hideTopLayer() {
|
|
6125
|
+
for (const el of [this.popoverEl, this.backdropEl]) {
|
|
6126
|
+
if (!el || typeof el.hidePopover !== "function") continue;
|
|
6127
|
+
try {
|
|
6128
|
+
if (el.matches(":popover-open")) el.hidePopover();
|
|
6129
|
+
} catch {
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
6132
|
+
if (this.popoverEl) {
|
|
6133
|
+
this.popoverEl.style.position = "";
|
|
6134
|
+
this.popoverEl.style.top = "";
|
|
6135
|
+
this.popoverEl.style.left = "";
|
|
6136
|
+
}
|
|
5841
6137
|
}
|
|
5842
6138
|
toggleOpen() {
|
|
5843
6139
|
if (this.disabled) return;
|
|
@@ -6193,10 +6489,11 @@ var RelRangePicker = class extends import_lit15.LitElement {
|
|
|
6193
6489
|
</button>
|
|
6194
6490
|
`}
|
|
6195
6491
|
|
|
6196
|
-
${isPopup && this.open ? import_lit15.html`<div part="overlay" class="backdrop" @click=${this.cancelPopover}></div>` : import_lit15.nothing}
|
|
6492
|
+
${isPopup && this.open ? import_lit15.html`<div part="overlay" class="backdrop" popover="manual" @click=${this.cancelPopover}></div>` : import_lit15.nothing}
|
|
6197
6493
|
|
|
6198
6494
|
<div
|
|
6199
6495
|
part="panel" class="popover ${this.open ? "open" : ""} ${isPopup ? "popup" : ""}"
|
|
6496
|
+
popover="manual"
|
|
6200
6497
|
role="dialog"
|
|
6201
6498
|
aria-label="Select date range"
|
|
6202
6499
|
>
|
|
@@ -6407,6 +6704,17 @@ RelRangePicker.styles = import_lit15.css`
|
|
|
6407
6704
|
|
|
6408
6705
|
/* ── Popover shell ── */
|
|
6409
6706
|
.popover {
|
|
6707
|
+
/* Reset native [popover] UA styles first (the shell is promoted to
|
|
6708
|
+
the top layer so transformed / overflow:hidden ancestors — e.g. a
|
|
6709
|
+
centered rel-modal panel — can't hijack or clip it). While in the
|
|
6710
|
+
top layer, JS overrides the anchored position below with inline
|
|
6711
|
+
fixed coordinates; the CSS anchoring is the no-Popover-API
|
|
6712
|
+
fallback. */
|
|
6713
|
+
margin: 0;
|
|
6714
|
+
padding: 0;
|
|
6715
|
+
width: auto;
|
|
6716
|
+
height: auto;
|
|
6717
|
+
color: inherit;
|
|
6410
6718
|
position: absolute;
|
|
6411
6719
|
top: calc(100% + 4px);
|
|
6412
6720
|
left: 0;
|
|
@@ -6431,6 +6739,13 @@ RelRangePicker.styles = import_lit15.css`
|
|
|
6431
6739
|
.backdrop {
|
|
6432
6740
|
position: fixed;
|
|
6433
6741
|
inset: 0;
|
|
6742
|
+
/* Reset native [popover] UA styles — the backdrop is promoted to the
|
|
6743
|
+
top layer right before the panel so the panel stacks above it. */
|
|
6744
|
+
margin: 0;
|
|
6745
|
+
padding: 0;
|
|
6746
|
+
width: auto;
|
|
6747
|
+
height: auto;
|
|
6748
|
+
border: none;
|
|
6434
6749
|
z-index: 2147483646;
|
|
6435
6750
|
background: rgba(0,0,0,0.4);
|
|
6436
6751
|
}
|
|
@@ -6804,6 +7119,12 @@ __decorateClass([
|
|
|
6804
7119
|
__decorateClass([
|
|
6805
7120
|
(0, import_decorators15.query)(".trigger-button")
|
|
6806
7121
|
], RelRangePicker.prototype, "triggerButton", 2);
|
|
7122
|
+
__decorateClass([
|
|
7123
|
+
(0, import_decorators15.query)(".popover")
|
|
7124
|
+
], RelRangePicker.prototype, "popoverEl", 2);
|
|
7125
|
+
__decorateClass([
|
|
7126
|
+
(0, import_decorators15.query)(".backdrop")
|
|
7127
|
+
], RelRangePicker.prototype, "backdropEl", 2);
|
|
6807
7128
|
RelRangePicker = __decorateClass([
|
|
6808
7129
|
(0, import_decorators15.customElement)("rel-range-picker")
|
|
6809
7130
|
], RelRangePicker);
|
|
@@ -8309,6 +8630,8 @@ var RelRadio = class extends import_lit23.LitElement {
|
|
|
8309
8630
|
super(...arguments);
|
|
8310
8631
|
this.options = [];
|
|
8311
8632
|
this.name = "radio-group";
|
|
8633
|
+
this.orientation = "vertical";
|
|
8634
|
+
this.size = "medium";
|
|
8312
8635
|
this.selectedValue = null;
|
|
8313
8636
|
this.disabled = false;
|
|
8314
8637
|
this.error = false;
|
|
@@ -8374,7 +8697,12 @@ var RelRadio = class extends import_lit23.LitElement {
|
|
|
8374
8697
|
}
|
|
8375
8698
|
render() {
|
|
8376
8699
|
return import_lit23.html`
|
|
8377
|
-
<div
|
|
8700
|
+
<div
|
|
8701
|
+
part="base"
|
|
8702
|
+
class="rel-radio-group"
|
|
8703
|
+
role="radiogroup"
|
|
8704
|
+
aria-orientation="${this.orientation}"
|
|
8705
|
+
>
|
|
8378
8706
|
${this.options.map((o) => this.renderOption(o))}
|
|
8379
8707
|
</div>
|
|
8380
8708
|
${this.error && this.errorMessage ? import_lit23.html`<div class="error-message">${this.errorMessage}</div>` : ""}
|
|
@@ -8395,6 +8723,15 @@ RelRadio.styles = import_lit23.css`
|
|
|
8395
8723
|
gap: var(--rel-spacing-xs);
|
|
8396
8724
|
}
|
|
8397
8725
|
|
|
8726
|
+
/* Horizontal orientation — options flow in a wrapping row. Wider column
|
|
8727
|
+
gap than the vertical rhythm so adjacent option labels read as separate
|
|
8728
|
+
choices; row gap keeps the vertical value for wrapped lines. */
|
|
8729
|
+
:host([orientation="horizontal"]) .rel-radio-group {
|
|
8730
|
+
flex-direction: row;
|
|
8731
|
+
flex-wrap: wrap;
|
|
8732
|
+
gap: var(--rel-spacing-xs) var(--rel-spacing-xl);
|
|
8733
|
+
}
|
|
8734
|
+
|
|
8398
8735
|
.rel-radio-option {
|
|
8399
8736
|
display: inline-flex;
|
|
8400
8737
|
flex-direction: column;
|
|
@@ -8497,6 +8834,34 @@ RelRadio.styles = import_lit23.css`
|
|
|
8497
8834
|
|
|
8498
8835
|
}
|
|
8499
8836
|
|
|
8837
|
+
/* Size variant (medium is the base scale above). Supporting-text indent =
|
|
8838
|
+
circle width + the radio-main gap, so it stays aligned under the label. */
|
|
8839
|
+
:host([size="small"]) .radio-custom {
|
|
8840
|
+
width: 16px;
|
|
8841
|
+
height: 16px;
|
|
8842
|
+
}
|
|
8843
|
+
|
|
8844
|
+
:host([size="small"]) input:checked + .radio-custom::after {
|
|
8845
|
+
inset: 3px;
|
|
8846
|
+
}
|
|
8847
|
+
|
|
8848
|
+
:host([size="small"]) .radio-label {
|
|
8849
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
8850
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
8851
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
8852
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
8853
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
8854
|
+
}
|
|
8855
|
+
|
|
8856
|
+
:host([size="small"]) .radio-supporting {
|
|
8857
|
+
margin-left: 24px;
|
|
8858
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
8859
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
8860
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
8861
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
8862
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
8863
|
+
}
|
|
8864
|
+
|
|
8500
8865
|
/* Error state (Figma State=Error, both Is Checked values) */
|
|
8501
8866
|
.rel-radio-option.error-unchecked .radio-custom,
|
|
8502
8867
|
.rel-radio-option.error-checked .radio-custom {
|
|
@@ -8540,6 +8905,12 @@ __decorateClass([
|
|
|
8540
8905
|
__decorateClass([
|
|
8541
8906
|
(0, import_decorators23.property)({ type: String })
|
|
8542
8907
|
], RelRadio.prototype, "name", 2);
|
|
8908
|
+
__decorateClass([
|
|
8909
|
+
(0, import_decorators23.property)({ type: String, reflect: true })
|
|
8910
|
+
], RelRadio.prototype, "orientation", 2);
|
|
8911
|
+
__decorateClass([
|
|
8912
|
+
(0, import_decorators23.property)({ type: String, reflect: true })
|
|
8913
|
+
], RelRadio.prototype, "size", 2);
|
|
8543
8914
|
__decorateClass([
|
|
8544
8915
|
(0, import_decorators23.property)({ type: String, reflect: true })
|
|
8545
8916
|
], RelRadio.prototype, "selectedValue", 2);
|
|
@@ -11358,6 +11729,14 @@ var RelPagination = class extends import_lit38.LitElement {
|
|
|
11358
11729
|
}
|
|
11359
11730
|
return pages;
|
|
11360
11731
|
}
|
|
11732
|
+
/** Abbreviate large page numbers (368178 → "368k") so buttons stay compact.
|
|
11733
|
+
Exact below 10,000; floored to k up to 1M; then M with one decimal. */
|
|
11734
|
+
formatPageLabel(n) {
|
|
11735
|
+
if (n < 1e4) return String(n);
|
|
11736
|
+
if (n < 1e6) return `${Math.floor(n / 1e3)}k`;
|
|
11737
|
+
const m = Math.floor(n / 1e5) / 10;
|
|
11738
|
+
return `${Number.isInteger(m) ? m.toFixed(0) : m}M`;
|
|
11739
|
+
}
|
|
11361
11740
|
handlePageChange(newPage) {
|
|
11362
11741
|
const total = this.computedTotalPages;
|
|
11363
11742
|
if (this.disabled || newPage < 1 || newPage > total || newPage === this.activePage) return;
|
|
@@ -11403,15 +11782,20 @@ var RelPagination = class extends import_lit38.LitElement {
|
|
|
11403
11782
|
<div class="pag-stack">
|
|
11404
11783
|
${this.renderNavBtn("prev")}
|
|
11405
11784
|
<div class="pag-numbers">
|
|
11406
|
-
${pages.map(
|
|
11407
|
-
(p
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11785
|
+
${pages.map((p) => {
|
|
11786
|
+
if (p === -1) return import_lit38.html`<span class="pag-ellipsis">...</span>`;
|
|
11787
|
+
const isNear = Math.abs(p - current) <= 1;
|
|
11788
|
+
const isEdge = p === 1 || p === total;
|
|
11789
|
+
const label = isEdge && !isNear ? this.formatPageLabel(p) : String(p);
|
|
11790
|
+
return import_lit38.html`<button
|
|
11791
|
+
type="button"
|
|
11792
|
+
class="pag-page-btn ${isNear ? "near" : ""} ${p === 1 || p === total ? "edge" : ""}"
|
|
11793
|
+
aria-label="Page ${p}"
|
|
11794
|
+
title="${label === String(p) ? import_lit38.nothing : `Page ${p}`}"
|
|
11795
|
+
aria-current="${p === current ? "page" : "false"}"
|
|
11796
|
+
@click="${() => this.handlePageChange(p)}"
|
|
11797
|
+
>${label}</button>`;
|
|
11798
|
+
})}
|
|
11415
11799
|
</div>
|
|
11416
11800
|
${this.renderNavBtn("next")}
|
|
11417
11801
|
</div>
|
|
@@ -11564,9 +11948,10 @@ RelPagination.styles = import_lit38.css`
|
|
|
11564
11948
|
display: inline-flex;
|
|
11565
11949
|
align-items: center;
|
|
11566
11950
|
justify-content: center;
|
|
11567
|
-
width:
|
|
11951
|
+
width: auto;
|
|
11952
|
+
min-width: 40px;
|
|
11568
11953
|
height: 40px;
|
|
11569
|
-
padding: var(--rel-spacing-xs);
|
|
11954
|
+
padding: 0 var(--rel-spacing-xs);
|
|
11570
11955
|
border-radius: var(--rel-border-radius-sm);
|
|
11571
11956
|
border: 1px solid var(--rel-border-default);
|
|
11572
11957
|
background: var(--rel-bg-neutral);
|
|
@@ -13139,6 +13524,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13139
13524
|
this.internalState = "default";
|
|
13140
13525
|
this.visibleChipCount = Infinity;
|
|
13141
13526
|
this.uniqueId = `select-${Math.random().toString(36).slice(2)}`;
|
|
13527
|
+
this._warnedNoName = false;
|
|
13142
13528
|
this.onOutsidePointerDown = (e) => {
|
|
13143
13529
|
if (!this.open) return;
|
|
13144
13530
|
const path = e.composedPath();
|
|
@@ -13168,6 +13554,14 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13168
13554
|
if (left + t.width > window.innerWidth - 8) {
|
|
13169
13555
|
left = Math.max(8, window.innerWidth - t.width - 8);
|
|
13170
13556
|
}
|
|
13557
|
+
this.listboxEl.style.top = `${top}px`;
|
|
13558
|
+
this.listboxEl.style.left = `${left}px`;
|
|
13559
|
+
this.listboxEl.style.width = `${t.width}px`;
|
|
13560
|
+
const applied = this.listboxEl.getBoundingClientRect();
|
|
13561
|
+
top -= applied.top - top;
|
|
13562
|
+
left -= applied.left - left;
|
|
13563
|
+
this.listboxEl.style.top = `${top}px`;
|
|
13564
|
+
this.listboxEl.style.left = `${left}px`;
|
|
13171
13565
|
this.panelStyle = `top:${top}px;left:${left}px;width:${t.width}px;`;
|
|
13172
13566
|
};
|
|
13173
13567
|
this.onSearchInput = (e) => {
|
|
@@ -13185,6 +13579,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13185
13579
|
disconnectedCallback() {
|
|
13186
13580
|
super.disconnectedCallback();
|
|
13187
13581
|
this.removeGlobalListeners();
|
|
13582
|
+
this.hideNativePopover();
|
|
13188
13583
|
this.chipResizeObserver?.disconnect();
|
|
13189
13584
|
}
|
|
13190
13585
|
firstUpdated() {
|
|
@@ -13197,6 +13592,13 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13197
13592
|
}
|
|
13198
13593
|
}
|
|
13199
13594
|
updated(changed) {
|
|
13595
|
+
if (!this._warnedNoName && this.labelStyle === "none" && !this.fieldAriaLabel && !this.fieldAriaLabelledby && !this.getAttribute("aria-label")) {
|
|
13596
|
+
this._warnedNoName = true;
|
|
13597
|
+
console.warn(
|
|
13598
|
+
'<rel-select label-style="none"> has no accessible name. Set `field-aria-label` or `field-aria-labelledby` \u2014 a placeholder is not a valid naming mechanism.',
|
|
13599
|
+
this
|
|
13600
|
+
);
|
|
13601
|
+
}
|
|
13200
13602
|
if (changed.has("selectedValues") || changed.has("options") || changed.has("multiple") || changed.has("size")) {
|
|
13201
13603
|
requestAnimationFrame(() => this.computeVisibleChips());
|
|
13202
13604
|
}
|
|
@@ -13294,6 +13696,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13294
13696
|
this.highlightedIndex = -1;
|
|
13295
13697
|
this.addGlobalListeners();
|
|
13296
13698
|
requestAnimationFrame(() => {
|
|
13699
|
+
this.showNativePopover();
|
|
13297
13700
|
this.updatePosition();
|
|
13298
13701
|
if (!this.noSearch) {
|
|
13299
13702
|
this.searchInputEl?.focus({ preventScroll: true });
|
|
@@ -13304,8 +13707,32 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13304
13707
|
if (!this.open) return;
|
|
13305
13708
|
this.open = false;
|
|
13306
13709
|
this.removeGlobalListeners();
|
|
13710
|
+
this.hideNativePopover();
|
|
13307
13711
|
this.triggerEl?.focus();
|
|
13308
13712
|
}
|
|
13713
|
+
/* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
13714
|
+
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
13715
|
+
* descendants, breaking viewport-based coordinates and letting the
|
|
13716
|
+
* panel's overflow:hidden clip the listbox. Promoting the listbox to
|
|
13717
|
+
* the browser top layer sidesteps both — same fix as rel-popover /
|
|
13718
|
+
* rel-autocomplete. `manual` keeps light-dismiss and Escape handling
|
|
13719
|
+
* under this component's own control. */
|
|
13720
|
+
showNativePopover() {
|
|
13721
|
+
const p = this.listboxEl;
|
|
13722
|
+
if (!p || typeof p.showPopover !== "function") return;
|
|
13723
|
+
try {
|
|
13724
|
+
if (!p.matches(":popover-open")) p.showPopover();
|
|
13725
|
+
} catch {
|
|
13726
|
+
}
|
|
13727
|
+
}
|
|
13728
|
+
hideNativePopover() {
|
|
13729
|
+
const p = this.listboxEl;
|
|
13730
|
+
if (!p || typeof p.hidePopover !== "function") return;
|
|
13731
|
+
try {
|
|
13732
|
+
if (p.matches(":popover-open")) p.hidePopover();
|
|
13733
|
+
} catch {
|
|
13734
|
+
}
|
|
13735
|
+
}
|
|
13309
13736
|
selectSingleOption(option) {
|
|
13310
13737
|
this.selected = option.value;
|
|
13311
13738
|
if (this.state === "error") this.state = "default";
|
|
@@ -13445,7 +13872,9 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13445
13872
|
const opts = this.normalizedOptions;
|
|
13446
13873
|
const filtered = this.filteredOptions;
|
|
13447
13874
|
const isDisabled = this.state === "disabled";
|
|
13875
|
+
const labelHidden = this.labelStyle === "none";
|
|
13448
13876
|
const showFloatingChrome = this.labelStyle === "floating" && !!this.label;
|
|
13877
|
+
const accessibleName = (labelHidden ? this.fieldAriaLabel : this.label) || "";
|
|
13449
13878
|
const wrapperClasses = [
|
|
13450
13879
|
"select-wrapper",
|
|
13451
13880
|
this.size,
|
|
@@ -13461,7 +13890,7 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13461
13890
|
const selectedSingle = opts.find((o) => o.value === this.selected);
|
|
13462
13891
|
return import_lit46.html`
|
|
13463
13892
|
<div class="${wrapperClasses}">
|
|
13464
|
-
${!showFloatingChrome && this.label ? import_lit46.html`<label part="label" class="select-label" for="${this.uniqueId}">${this.label}</label>` : ""}
|
|
13893
|
+
${labelHidden ? import_lit46.html`<div class="label-reserve" aria-hidden="true"></div>` : !showFloatingChrome && this.label ? import_lit46.html`<label part="label" class="select-label" for="${this.uniqueId}">${this.label}</label>` : ""}
|
|
13465
13894
|
|
|
13466
13895
|
<div class="select-container">
|
|
13467
13896
|
${showFloatingChrome ? import_lit46.html`<span class="floating-label${this.leftIcon ? " has-left-icon" : ""}"
|
|
@@ -13478,7 +13907,8 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13478
13907
|
aria-controls="${listboxId}"
|
|
13479
13908
|
aria-activedescendant="${activeOptionId}"
|
|
13480
13909
|
aria-invalid="${this.state === "error"}"
|
|
13481
|
-
aria-label="${this.
|
|
13910
|
+
aria-label="${this.fieldAriaLabelledby ? import_lit46.nothing : accessibleName || this.placeholder}"
|
|
13911
|
+
aria-labelledby="${this.fieldAriaLabelledby ?? import_lit46.nothing}"
|
|
13482
13912
|
?disabled="${isDisabled}"
|
|
13483
13913
|
@click="${this.onTriggerClick}"
|
|
13484
13914
|
@keydown="${this.onTriggerKeyDown}"
|
|
@@ -13513,39 +13943,42 @@ var RelSelect = class extends import_lit46.LitElement {
|
|
|
13513
13943
|
<div
|
|
13514
13944
|
id="${listboxId}"
|
|
13515
13945
|
part="panel list" class="select-listbox"
|
|
13946
|
+
popover="manual"
|
|
13516
13947
|
role="listbox"
|
|
13517
13948
|
aria-multiselectable="${this.multiple}"
|
|
13518
|
-
aria-label="${
|
|
13949
|
+
aria-label="${accessibleName || "Options"}"
|
|
13519
13950
|
style="${this.panelStyle}"
|
|
13520
13951
|
?data-open="${this.open}"
|
|
13521
13952
|
>
|
|
13522
|
-
${!this.noSearch ? import_lit46.html`<div class="select-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13953
|
+
${!this.noSearch || this.multiple && filtered.length > 0 ? import_lit46.html`<div class="select-header">
|
|
13954
|
+
${!this.noSearch ? import_lit46.html`<div class="select-search">
|
|
13955
|
+
<div class="search-input-wrap">
|
|
13956
|
+
<rel-icon name="search" size="${this.searchIconSize}" class="icon"></rel-icon>
|
|
13957
|
+
<input
|
|
13958
|
+
part="input" class="search-input"
|
|
13959
|
+
type="text"
|
|
13960
|
+
placeholder="${this.searchPlaceholder}"
|
|
13961
|
+
.value="${this.searchValue}"
|
|
13962
|
+
@input="${this.onSearchInput}"
|
|
13963
|
+
@keydown="${(e) => this.onSearchKeyDown(e)}"
|
|
13964
|
+
/>
|
|
13965
|
+
</div>
|
|
13966
|
+
</div>` : ""}
|
|
13967
|
+
${this.multiple && filtered.length > 0 ? import_lit46.html`<div class="select-all-row">
|
|
13968
|
+
<rel-checkbox
|
|
13969
|
+
label="${this.selectAllLabel}"
|
|
13970
|
+
status="${this.selectAllStatus}"
|
|
13971
|
+
@rel-change=${(e) => {
|
|
13972
|
+
e.stopPropagation();
|
|
13973
|
+
this.toggleSelectAll();
|
|
13974
|
+
}}
|
|
13975
|
+
></rel-checkbox>
|
|
13976
|
+
</div>` : ""}
|
|
13534
13977
|
</div>` : ""}
|
|
13535
13978
|
|
|
13536
13979
|
${filtered.length === 0 ? import_lit46.html`<div class="select-empty">
|
|
13537
13980
|
${opts.length === 0 ? "No options available" : "No matches found"}
|
|
13538
13981
|
</div>` : this.multiple ? import_lit46.html`
|
|
13539
|
-
<div class="select-all-row">
|
|
13540
|
-
<rel-checkbox
|
|
13541
|
-
label="${this.selectAllLabel}"
|
|
13542
|
-
status="${this.selectAllStatus}"
|
|
13543
|
-
@rel-change=${(e) => {
|
|
13544
|
-
e.stopPropagation();
|
|
13545
|
-
this.toggleSelectAll();
|
|
13546
|
-
}}
|
|
13547
|
-
></rel-checkbox>
|
|
13548
|
-
</div>
|
|
13549
13982
|
<div class="select-options">
|
|
13550
13983
|
${(0, import_repeat2.repeat)(
|
|
13551
13984
|
filtered,
|
|
@@ -13644,6 +14077,22 @@ RelSelect.styles = import_lit46.css`
|
|
|
13644
14077
|
|
|
13645
14078
|
}
|
|
13646
14079
|
|
|
14080
|
+
/* ── labelStyle="none" ──
|
|
14081
|
+
No label renders. An empty spacer holds the label row's full height so the
|
|
14082
|
+
trigger still bottom-aligns with labelled siblings in a form row.
|
|
14083
|
+
The reserve covers label line-height + the wrapper gap, and the negative
|
|
14084
|
+
margin cancels the flex gap that would otherwise sit below it — so
|
|
14085
|
+
--rel-field-label-reserve: 0 collapses the row to genuinely zero
|
|
14086
|
+
(toolbars, table cells), not just to the leftover gap. */
|
|
14087
|
+
.label-reserve {
|
|
14088
|
+
flex: none;
|
|
14089
|
+
height: var(
|
|
14090
|
+
--rel-field-label-reserve,
|
|
14091
|
+
calc(var(--rel-body-sm-regular-line-height) + var(--rel-spacing-2xs))
|
|
14092
|
+
);
|
|
14093
|
+
margin-bottom: calc(-1 * var(--rel-spacing-2xs));
|
|
14094
|
+
}
|
|
14095
|
+
|
|
13647
14096
|
/* Error only colors the border/focus ring and the supporting text below
|
|
13648
14097
|
the field — label, floating label, placeholder, and icons stay normal. */
|
|
13649
14098
|
|
|
@@ -13872,6 +14321,15 @@ RelSelect.styles = import_lit46.css`
|
|
|
13872
14321
|
position: fixed;
|
|
13873
14322
|
z-index: 1001;
|
|
13874
14323
|
display: none;
|
|
14324
|
+
/* Reset native [popover] UA styles (the listbox is promoted to the
|
|
14325
|
+
top layer so transformed ancestors — e.g. a centered rel-modal
|
|
14326
|
+
panel — can't hijack its containing block or clip it). */
|
|
14327
|
+
inset: auto;
|
|
14328
|
+
margin: 0;
|
|
14329
|
+
padding: 0;
|
|
14330
|
+
width: auto;
|
|
14331
|
+
height: auto;
|
|
14332
|
+
color: inherit;
|
|
13875
14333
|
box-sizing: border-box;
|
|
13876
14334
|
background: var(--rel-select-bg, var(--rel-bg-neutral));
|
|
13877
14335
|
border: 1px solid var(--rel-select-menu-border, var(--rel-border-default));
|
|
@@ -13888,10 +14346,18 @@ RelSelect.styles = import_lit46.css`
|
|
|
13888
14346
|
display: block;
|
|
13889
14347
|
}
|
|
13890
14348
|
|
|
13891
|
-
.
|
|
14349
|
+
/* Search and Select All pin together while the options scroll beneath.
|
|
14350
|
+
z-index is load-bearing: multiselect rows contain rel-checkbox, whose
|
|
14351
|
+
.row wrapper is position:relative (Tab-scroll fix), so without it the
|
|
14352
|
+
positioned rows paint over the sticky header in DOM order. */
|
|
14353
|
+
.select-header {
|
|
13892
14354
|
position: sticky;
|
|
13893
14355
|
top: 0;
|
|
14356
|
+
z-index: 1;
|
|
13894
14357
|
background: var(--rel-select-bg, var(--rel-bg-neutral));
|
|
14358
|
+
}
|
|
14359
|
+
|
|
14360
|
+
.select-search {
|
|
13895
14361
|
padding: var(--rel-spacing-xs);
|
|
13896
14362
|
border-bottom: 1px solid var(--rel-select-divider, var(--rel-border-subtle));
|
|
13897
14363
|
}
|
|
@@ -14037,8 +14503,14 @@ __decorateClass([
|
|
|
14037
14503
|
(0, import_decorators46.property)({ type: String })
|
|
14038
14504
|
], RelSelect.prototype, "label", 2);
|
|
14039
14505
|
__decorateClass([
|
|
14040
|
-
(0, import_decorators46.property)({ type: String, attribute: "label-style" })
|
|
14506
|
+
(0, import_decorators46.property)({ type: String, attribute: "label-style", reflect: true })
|
|
14041
14507
|
], RelSelect.prototype, "labelStyle", 2);
|
|
14508
|
+
__decorateClass([
|
|
14509
|
+
(0, import_decorators46.property)({ type: String, attribute: "field-aria-label" })
|
|
14510
|
+
], RelSelect.prototype, "fieldAriaLabel", 2);
|
|
14511
|
+
__decorateClass([
|
|
14512
|
+
(0, import_decorators46.property)({ type: String, attribute: "field-aria-labelledby" })
|
|
14513
|
+
], RelSelect.prototype, "fieldAriaLabelledby", 2);
|
|
14042
14514
|
__decorateClass([
|
|
14043
14515
|
(0, import_decorators46.property)({ type: Array })
|
|
14044
14516
|
], RelSelect.prototype, "options", 2);
|
|
@@ -16637,111 +17109,627 @@ RelTab = __decorateClass([
|
|
|
16637
17109
|
(0, import_decorators53.customElement)("rel-tab")
|
|
16638
17110
|
], RelTab);
|
|
16639
17111
|
|
|
16640
|
-
// src/components/rel-
|
|
17112
|
+
// src/components/rel-time-picker/rel-time-picker.ts
|
|
16641
17113
|
var import_lit54 = require("lit");
|
|
16642
17114
|
var import_decorators54 = require("lit/decorators.js");
|
|
16643
|
-
var
|
|
17115
|
+
var import_rel_icon16 = require("@reliability-design/icons/rel-icon");
|
|
17116
|
+
var RelTimePicker = class extends import_lit54.LitElement {
|
|
16644
17117
|
constructor() {
|
|
16645
17118
|
super(...arguments);
|
|
16646
|
-
this.
|
|
16647
|
-
this.
|
|
16648
|
-
this.
|
|
16649
|
-
this.
|
|
16650
|
-
this.
|
|
16651
|
-
this.
|
|
16652
|
-
|
|
16653
|
-
|
|
16654
|
-
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
|
|
16659
|
-
|
|
16660
|
-
|
|
16661
|
-
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
17119
|
+
this.value = null;
|
|
17120
|
+
this.placeholder = "Select time";
|
|
17121
|
+
this.label = null;
|
|
17122
|
+
this.disabled = false;
|
|
17123
|
+
this.required = false;
|
|
17124
|
+
this.readonly = false;
|
|
17125
|
+
this.format = "12h";
|
|
17126
|
+
this.minuteStep = 1;
|
|
17127
|
+
this.minTime = null;
|
|
17128
|
+
this.maxTime = null;
|
|
17129
|
+
this.open = false;
|
|
17130
|
+
this.clearable = false;
|
|
17131
|
+
this.error = false;
|
|
17132
|
+
this.invalid = false;
|
|
17133
|
+
this.size = "medium";
|
|
17134
|
+
this.variant = "outlined";
|
|
17135
|
+
this.fullWidth = false;
|
|
17136
|
+
this.draftHour = null;
|
|
17137
|
+
this.draftMinute = null;
|
|
17138
|
+
this.handleClickOutside = (e) => {
|
|
17139
|
+
if (!this.open) return;
|
|
17140
|
+
const path = e.composedPath();
|
|
17141
|
+
if (!path.includes(this)) {
|
|
17142
|
+
this.cancelPopover();
|
|
16666
17143
|
}
|
|
16667
|
-
}
|
|
17144
|
+
};
|
|
17145
|
+
this.handleEscapeKey = (e) => {
|
|
17146
|
+
if (e.key === "Escape" && this.open) {
|
|
17147
|
+
this.cancelPopover();
|
|
17148
|
+
}
|
|
17149
|
+
};
|
|
17150
|
+
}
|
|
17151
|
+
connectedCallback() {
|
|
17152
|
+
super.connectedCallback();
|
|
17153
|
+
document.addEventListener("click", this.handleClickOutside);
|
|
17154
|
+
document.addEventListener("keydown", this.handleEscapeKey);
|
|
16668
17155
|
}
|
|
16669
17156
|
disconnectedCallback() {
|
|
16670
17157
|
super.disconnectedCallback();
|
|
16671
|
-
|
|
17158
|
+
document.removeEventListener("click", this.handleClickOutside);
|
|
17159
|
+
document.removeEventListener("keydown", this.handleEscapeKey);
|
|
16672
17160
|
}
|
|
16673
|
-
|
|
16674
|
-
this.
|
|
17161
|
+
updated(changedProperties) {
|
|
17162
|
+
if (changedProperties.has("open") && this.open) {
|
|
17163
|
+
this.resetDraftFromValue();
|
|
17164
|
+
setTimeout(() => {
|
|
17165
|
+
this.scrollColumnsToSelection();
|
|
17166
|
+
const target = this.columnsElement?.querySelector(
|
|
17167
|
+
".option-button.selected:not(:disabled)"
|
|
17168
|
+
) ?? this.columnsElement?.querySelector(
|
|
17169
|
+
".option-button:not(:disabled)"
|
|
17170
|
+
);
|
|
17171
|
+
target?.focus();
|
|
17172
|
+
}, 0);
|
|
17173
|
+
}
|
|
16675
17174
|
}
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
17175
|
+
// ── Parsing / formatting ──
|
|
17176
|
+
parseTime(time) {
|
|
17177
|
+
if (!time) return null;
|
|
17178
|
+
const match = time.match(/^(\d{1,2}):(\d{2})$/);
|
|
17179
|
+
if (!match) return null;
|
|
17180
|
+
const hour = parseInt(match[1], 10);
|
|
17181
|
+
const minute = parseInt(match[2], 10);
|
|
17182
|
+
if (hour > 23 || minute > 59) return null;
|
|
17183
|
+
return { hour, minute };
|
|
17184
|
+
}
|
|
17185
|
+
formatValue(hour, minute) {
|
|
17186
|
+
return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
17187
|
+
}
|
|
17188
|
+
formatDisplay(hour, minute) {
|
|
17189
|
+
const mm = String(minute).padStart(2, "0");
|
|
17190
|
+
if (this.format === "24h") {
|
|
17191
|
+
return `${String(hour).padStart(2, "0")}:${mm}`;
|
|
17192
|
+
}
|
|
17193
|
+
const period = hour < 12 ? "AM" : "PM";
|
|
17194
|
+
const hour12 = hour % 12 === 0 ? 12 : hour % 12;
|
|
17195
|
+
return `${hour12}:${mm} ${period}`;
|
|
17196
|
+
}
|
|
17197
|
+
toMinutes(hour, minute) {
|
|
17198
|
+
return hour * 60 + minute;
|
|
17199
|
+
}
|
|
17200
|
+
get minuteStepSafe() {
|
|
17201
|
+
const step = Math.floor(this.minuteStep);
|
|
17202
|
+
return step >= 1 && step <= 60 ? step : 1;
|
|
17203
|
+
}
|
|
17204
|
+
get minBoundMinutes() {
|
|
17205
|
+
const parsed = this.parseTime(this.minTime);
|
|
17206
|
+
return parsed ? this.toMinutes(parsed.hour, parsed.minute) : 0;
|
|
17207
|
+
}
|
|
17208
|
+
get maxBoundMinutes() {
|
|
17209
|
+
const parsed = this.parseTime(this.maxTime);
|
|
17210
|
+
return parsed ? this.toMinutes(parsed.hour, parsed.minute) : 23 * 60 + 59;
|
|
17211
|
+
}
|
|
17212
|
+
isTimeInRange(hour, minute) {
|
|
17213
|
+
const total = this.toMinutes(hour, minute);
|
|
17214
|
+
return total >= this.minBoundMinutes && total <= this.maxBoundMinutes;
|
|
17215
|
+
}
|
|
17216
|
+
/** Minutes shown in the minutes column (respecting minuteStep). */
|
|
17217
|
+
getMinuteOptions() {
|
|
17218
|
+
const minutes = [];
|
|
17219
|
+
for (let m = 0; m < 60; m += this.minuteStepSafe) {
|
|
17220
|
+
minutes.push(m);
|
|
17221
|
+
}
|
|
17222
|
+
return minutes;
|
|
17223
|
+
}
|
|
17224
|
+
/** An hour is enabled if at least one of its step minutes is in range. */
|
|
17225
|
+
isHourEnabled(hour) {
|
|
17226
|
+
return this.getMinuteOptions().some((m) => this.isTimeInRange(hour, m));
|
|
17227
|
+
}
|
|
17228
|
+
isPeriodEnabled(period) {
|
|
17229
|
+
const start = period === "AM" ? 0 : 12;
|
|
17230
|
+
for (let h = start; h < start + 12; h++) {
|
|
17231
|
+
if (this.isHourEnabled(h)) return true;
|
|
17232
|
+
}
|
|
17233
|
+
return false;
|
|
17234
|
+
}
|
|
17235
|
+
/** Hour column entries: display label + the 0–23 hour it maps to. */
|
|
17236
|
+
getHourOptions() {
|
|
17237
|
+
if (this.format === "24h") {
|
|
17238
|
+
return Array.from({ length: 24 }, (_, h) => ({
|
|
17239
|
+
label: String(h).padStart(2, "0"),
|
|
17240
|
+
hour24: h
|
|
17241
|
+
}));
|
|
17242
|
+
}
|
|
17243
|
+
const period = this.draftPeriod ?? "AM";
|
|
17244
|
+
const base = period === "AM" ? 0 : 12;
|
|
17245
|
+
return Array.from({ length: 12 }, (_, i) => ({
|
|
17246
|
+
label: i === 0 ? "12" : String(i).padStart(2, "0"),
|
|
17247
|
+
hour24: base + i
|
|
17248
|
+
}));
|
|
17249
|
+
}
|
|
17250
|
+
get draftPeriod() {
|
|
17251
|
+
if (this.draftHour === null) return null;
|
|
17252
|
+
return this.draftHour < 12 ? "AM" : "PM";
|
|
17253
|
+
}
|
|
17254
|
+
resetDraftFromValue() {
|
|
17255
|
+
const parsed = this.parseTime(this.value);
|
|
17256
|
+
this.draftHour = parsed ? parsed.hour : null;
|
|
17257
|
+
this.draftMinute = parsed ? parsed.minute : null;
|
|
17258
|
+
}
|
|
17259
|
+
/** Snap the draft minute to the nearest valid step minute for the hour. */
|
|
17260
|
+
clampDraftToRange() {
|
|
17261
|
+
if (this.draftHour === null || this.draftMinute === null) return;
|
|
17262
|
+
if (this.isTimeInRange(this.draftHour, this.draftMinute)) return;
|
|
17263
|
+
const hour = this.draftHour;
|
|
17264
|
+
const valid = this.getMinuteOptions().filter((m) => this.isTimeInRange(hour, m));
|
|
17265
|
+
if (valid.length === 0) {
|
|
17266
|
+
this.draftMinute = null;
|
|
17267
|
+
return;
|
|
16682
17268
|
}
|
|
17269
|
+
const current = this.draftMinute;
|
|
17270
|
+
this.draftMinute = valid.reduce(
|
|
17271
|
+
(best, m) => Math.abs(m - current) < Math.abs(best - current) ? m : best
|
|
17272
|
+
);
|
|
17273
|
+
}
|
|
17274
|
+
// ── Open / close / commit ──
|
|
17275
|
+
toggleTimePopover() {
|
|
17276
|
+
if (this.disabled || this.readonly) return;
|
|
17277
|
+
this.open = !this.open;
|
|
16683
17278
|
this.dispatchEvent(
|
|
16684
|
-
new CustomEvent("
|
|
17279
|
+
new CustomEvent("rel-open-change", {
|
|
17280
|
+
detail: { open: this.open },
|
|
16685
17281
|
bubbles: true,
|
|
16686
17282
|
composed: true
|
|
16687
17283
|
})
|
|
16688
17284
|
);
|
|
16689
17285
|
}
|
|
16690
|
-
|
|
16691
|
-
|
|
17286
|
+
/** Close discarding the draft (Cancel / Escape / outside click). */
|
|
17287
|
+
cancelPopover() {
|
|
17288
|
+
this.open = false;
|
|
17289
|
+
this.triggerButton?.focus();
|
|
16692
17290
|
this.dispatchEvent(
|
|
16693
|
-
new CustomEvent("
|
|
17291
|
+
new CustomEvent("rel-open-change", {
|
|
17292
|
+
detail: { open: false },
|
|
16694
17293
|
bubbles: true,
|
|
16695
17294
|
composed: true
|
|
16696
17295
|
})
|
|
16697
17296
|
);
|
|
16698
|
-
this.close();
|
|
16699
17297
|
}
|
|
17298
|
+
applySelection() {
|
|
17299
|
+
if (this.draftHour === null) return;
|
|
17300
|
+
const newValue = this.formatValue(this.draftHour, this.draftMinute ?? 0);
|
|
17301
|
+
this.value = newValue;
|
|
17302
|
+
this.dispatchEvent(
|
|
17303
|
+
new CustomEvent("rel-change", {
|
|
17304
|
+
detail: { value: newValue },
|
|
17305
|
+
bubbles: true,
|
|
17306
|
+
composed: true
|
|
17307
|
+
})
|
|
17308
|
+
);
|
|
17309
|
+
this.cancelPopover();
|
|
17310
|
+
}
|
|
17311
|
+
/** Set the draft to the current time, snapped to the minute step and range. */
|
|
17312
|
+
selectNow() {
|
|
17313
|
+
const now = /* @__PURE__ */ new Date();
|
|
17314
|
+
const step = this.minuteStepSafe;
|
|
17315
|
+
this.draftHour = now.getHours();
|
|
17316
|
+
this.draftMinute = Math.round(now.getMinutes() / step) * step;
|
|
17317
|
+
if (this.draftMinute >= 60) {
|
|
17318
|
+
this.draftMinute = 0;
|
|
17319
|
+
this.draftHour = (this.draftHour + 1) % 24;
|
|
17320
|
+
}
|
|
17321
|
+
if (!this.isHourEnabled(this.draftHour)) {
|
|
17322
|
+
const enabled = [];
|
|
17323
|
+
for (let h = 0; h < 24; h++) if (this.isHourEnabled(h)) enabled.push(h);
|
|
17324
|
+
if (enabled.length === 0) return;
|
|
17325
|
+
const currentHour = this.draftHour;
|
|
17326
|
+
this.draftHour = enabled.reduce(
|
|
17327
|
+
(best, h) => Math.abs(h - currentHour) < Math.abs(best - currentHour) ? h : best
|
|
17328
|
+
);
|
|
17329
|
+
}
|
|
17330
|
+
this.clampDraftToRange();
|
|
17331
|
+
this.scrollColumnsToSelection();
|
|
17332
|
+
}
|
|
17333
|
+
clearValue(e) {
|
|
17334
|
+
e.stopPropagation();
|
|
17335
|
+
this.value = null;
|
|
17336
|
+
this.draftHour = null;
|
|
17337
|
+
this.draftMinute = null;
|
|
17338
|
+
this.dispatchEvent(
|
|
17339
|
+
new CustomEvent("rel-change", {
|
|
17340
|
+
detail: { value: null },
|
|
17341
|
+
bubbles: true,
|
|
17342
|
+
composed: true
|
|
17343
|
+
})
|
|
17344
|
+
);
|
|
17345
|
+
}
|
|
17346
|
+
// ── Column selection ──
|
|
17347
|
+
selectHour(hour24) {
|
|
17348
|
+
if (!this.isHourEnabled(hour24)) return;
|
|
17349
|
+
this.draftHour = hour24;
|
|
17350
|
+
if (this.draftMinute === null) this.draftMinute = 0;
|
|
17351
|
+
this.clampDraftToRange();
|
|
17352
|
+
}
|
|
17353
|
+
selectMinute(minute) {
|
|
17354
|
+
if (this.draftHour === null) {
|
|
17355
|
+
for (let h = 0; h < 24; h++) {
|
|
17356
|
+
if (this.isHourEnabled(h)) {
|
|
17357
|
+
this.draftHour = h;
|
|
17358
|
+
break;
|
|
17359
|
+
}
|
|
17360
|
+
}
|
|
17361
|
+
if (this.draftHour === null) return;
|
|
17362
|
+
}
|
|
17363
|
+
if (!this.isTimeInRange(this.draftHour, minute)) return;
|
|
17364
|
+
this.draftMinute = minute;
|
|
17365
|
+
}
|
|
17366
|
+
selectPeriod(period) {
|
|
17367
|
+
if (!this.isPeriodEnabled(period)) return;
|
|
17368
|
+
const base = period === "AM" ? 0 : 12;
|
|
17369
|
+
const hour12 = this.draftHour === null ? 0 : this.draftHour % 12;
|
|
17370
|
+
this.draftHour = base + hour12;
|
|
17371
|
+
if (this.draftMinute === null) this.draftMinute = 0;
|
|
17372
|
+
if (!this.isHourEnabled(this.draftHour)) {
|
|
17373
|
+
const enabled = [];
|
|
17374
|
+
for (let h = base; h < base + 12; h++) if (this.isHourEnabled(h)) enabled.push(h);
|
|
17375
|
+
const currentHour = this.draftHour;
|
|
17376
|
+
this.draftHour = enabled.reduce(
|
|
17377
|
+
(best, h) => Math.abs(h - currentHour) < Math.abs(best - currentHour) ? h : best
|
|
17378
|
+
);
|
|
17379
|
+
}
|
|
17380
|
+
this.clampDraftToRange();
|
|
17381
|
+
this.scrollColumnsToSelection();
|
|
17382
|
+
}
|
|
17383
|
+
scrollColumnsToSelection() {
|
|
17384
|
+
this.updateComplete.then(() => {
|
|
17385
|
+
this.columnsElement?.querySelectorAll(".option-button.selected").forEach((btn) => btn.scrollIntoView({ block: "center" }));
|
|
17386
|
+
});
|
|
17387
|
+
}
|
|
17388
|
+
// ── Keyboard ──
|
|
17389
|
+
handleTriggerKeydown(e) {
|
|
17390
|
+
if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
|
|
17391
|
+
e.preventDefault();
|
|
17392
|
+
if (!this.open) {
|
|
17393
|
+
this.toggleTimePopover();
|
|
17394
|
+
}
|
|
17395
|
+
}
|
|
17396
|
+
}
|
|
17397
|
+
/**
|
|
17398
|
+
* Column navigation: Up/Down move within the column (and select), Left/Right
|
|
17399
|
+
* jump between columns, Home/End go to the first/last option, Enter applies.
|
|
17400
|
+
*/
|
|
17401
|
+
handleOptionKeydown(e, column) {
|
|
17402
|
+
const list = e.currentTarget.closest(".column-list");
|
|
17403
|
+
if (!list) return;
|
|
17404
|
+
const buttons = Array.from(
|
|
17405
|
+
list.querySelectorAll(".option-button:not(:disabled)")
|
|
17406
|
+
);
|
|
17407
|
+
const index = buttons.indexOf(e.currentTarget);
|
|
17408
|
+
let target;
|
|
17409
|
+
switch (e.key) {
|
|
17410
|
+
case "ArrowUp":
|
|
17411
|
+
e.preventDefault();
|
|
17412
|
+
target = buttons[Math.max(0, index - 1)];
|
|
17413
|
+
break;
|
|
17414
|
+
case "ArrowDown":
|
|
17415
|
+
e.preventDefault();
|
|
17416
|
+
target = buttons[Math.min(buttons.length - 1, index + 1)];
|
|
17417
|
+
break;
|
|
17418
|
+
case "Home":
|
|
17419
|
+
e.preventDefault();
|
|
17420
|
+
target = buttons[0];
|
|
17421
|
+
break;
|
|
17422
|
+
case "End":
|
|
17423
|
+
e.preventDefault();
|
|
17424
|
+
target = buttons[buttons.length - 1];
|
|
17425
|
+
break;
|
|
17426
|
+
case "ArrowLeft":
|
|
17427
|
+
case "ArrowRight": {
|
|
17428
|
+
e.preventDefault();
|
|
17429
|
+
this.focusAdjacentColumn(column, e.key === "ArrowRight" ? 1 : -1);
|
|
17430
|
+
return;
|
|
17431
|
+
}
|
|
17432
|
+
case "Enter":
|
|
17433
|
+
e.preventDefault();
|
|
17434
|
+
e.currentTarget.click();
|
|
17435
|
+
this.applySelection();
|
|
17436
|
+
return;
|
|
17437
|
+
default:
|
|
17438
|
+
return;
|
|
17439
|
+
}
|
|
17440
|
+
if (target && target !== buttons[index]) {
|
|
17441
|
+
target.click();
|
|
17442
|
+
target.focus();
|
|
17443
|
+
target.scrollIntoView({ block: "nearest" });
|
|
17444
|
+
}
|
|
17445
|
+
}
|
|
17446
|
+
focusAdjacentColumn(from, direction) {
|
|
17447
|
+
const order = this.format === "12h" ? ["hour", "minute", "period"] : ["hour", "minute"];
|
|
17448
|
+
const nextIndex = order.indexOf(from) + direction;
|
|
17449
|
+
if (nextIndex < 0 || nextIndex >= order.length) return;
|
|
17450
|
+
const lists = this.columnsElement?.querySelectorAll(".column-list");
|
|
17451
|
+
const list = lists?.[nextIndex];
|
|
17452
|
+
const target = list?.querySelector(".option-button.selected:not(:disabled)") ?? list?.querySelector(".option-button:not(:disabled)");
|
|
17453
|
+
target?.focus();
|
|
17454
|
+
target?.scrollIntoView({ block: "nearest" });
|
|
17455
|
+
}
|
|
17456
|
+
// ── Render ──
|
|
16700
17457
|
render() {
|
|
17458
|
+
const isError = this.error || this.invalid;
|
|
17459
|
+
const belowText = isError && this.errorMessage ? this.errorMessage : this.helperText;
|
|
17460
|
+
const describedById = belowText ? "time-picker-desc" : void 0;
|
|
17461
|
+
const parsedValue = this.parseTime(this.value);
|
|
17462
|
+
const displayText = parsedValue ? this.formatDisplay(parsedValue.hour, parsedValue.minute) : "";
|
|
17463
|
+
const draftDisplay = this.draftHour !== null ? this.formatDisplay(this.draftHour, this.draftMinute ?? 0) : "";
|
|
17464
|
+
const hourOptions = this.getHourOptions();
|
|
17465
|
+
const minuteOptions = this.getMinuteOptions();
|
|
17466
|
+
const draftPeriod = this.draftPeriod;
|
|
17467
|
+
const wrapperClasses = [
|
|
17468
|
+
"time-picker-wrapper",
|
|
17469
|
+
this.size,
|
|
17470
|
+
isError ? "error" : "",
|
|
17471
|
+
this.readonly ? "readonly" : ""
|
|
17472
|
+
].filter(Boolean).join(" ");
|
|
16701
17473
|
return import_lit54.html`
|
|
16702
|
-
<div part="base" class="
|
|
16703
|
-
|
|
17474
|
+
<div part="base" class="${wrapperClasses}">
|
|
17475
|
+
${this.label ? import_lit54.html`<label part="label" class="label" id="time-picker-label"
|
|
17476
|
+
>${this.label}${this.required ? import_lit54.html`<span class="required-mark" aria-hidden="true">*</span>` : import_lit54.nothing}</label
|
|
17477
|
+
>` : import_lit54.nothing}
|
|
16704
17478
|
|
|
16705
|
-
${this.
|
|
16706
|
-
|
|
16707
|
-
|
|
17479
|
+
${this.name ? import_lit54.html`<input type="hidden" name=${this.name} .value=${this.value ?? ""} />` : import_lit54.nothing}
|
|
17480
|
+
|
|
17481
|
+
<button
|
|
17482
|
+
part="trigger"
|
|
17483
|
+
class="trigger-button ${isError ? "error" : ""}"
|
|
17484
|
+
type="button"
|
|
17485
|
+
?disabled=${this.disabled}
|
|
17486
|
+
@click=${this.toggleTimePopover}
|
|
17487
|
+
@keydown=${this.handleTriggerKeydown}
|
|
17488
|
+
@focus=${() => this.dispatchEvent(
|
|
17489
|
+
new CustomEvent("rel-focus", { bubbles: true, composed: true })
|
|
17490
|
+
)}
|
|
17491
|
+
@blur=${() => this.dispatchEvent(
|
|
17492
|
+
new CustomEvent("rel-blur", { bubbles: true, composed: true })
|
|
17493
|
+
)}
|
|
17494
|
+
aria-label=${this.label || "Select time"}
|
|
17495
|
+
aria-haspopup="dialog"
|
|
17496
|
+
aria-expanded=${this.open}
|
|
17497
|
+
aria-required=${this.required}
|
|
17498
|
+
aria-invalid=${isError}
|
|
17499
|
+
aria-readonly=${this.readonly}
|
|
17500
|
+
aria-describedby=${describedById ?? import_lit54.nothing}
|
|
17501
|
+
aria-labelledby=${this.label ? "time-picker-label" : import_lit54.nothing}
|
|
17502
|
+
>
|
|
17503
|
+
<span part="value" class="trigger-content ${displayText ? "" : "placeholder"}">
|
|
17504
|
+
${displayText || this.placeholder}
|
|
17505
|
+
</span>
|
|
17506
|
+
<span class="trigger-icons">
|
|
17507
|
+
${this.clearable && this.value ? import_lit54.html`
|
|
17508
|
+
<button
|
|
17509
|
+
class="icon-button"
|
|
17510
|
+
type="button"
|
|
17511
|
+
aria-label="Clear time"
|
|
17512
|
+
@click=${this.clearValue}
|
|
17513
|
+
>
|
|
17514
|
+
<svg viewBox="0 0 16 16" fill="currentColor">
|
|
17515
|
+
<path
|
|
17516
|
+
d="M4 4l8 8M12 4l-8 8"
|
|
17517
|
+
stroke="currentColor"
|
|
17518
|
+
stroke-width="2"
|
|
17519
|
+
stroke-linecap="round"
|
|
17520
|
+
/>
|
|
17521
|
+
</svg>
|
|
17522
|
+
</button>
|
|
17523
|
+
` : import_lit54.nothing}
|
|
17524
|
+
<rel-icon part="icon" name="time-filled" size="sm"></rel-icon>
|
|
17525
|
+
</span>
|
|
17526
|
+
</button>
|
|
17527
|
+
|
|
17528
|
+
<div
|
|
17529
|
+
part="panel"
|
|
17530
|
+
class="popover ${this.open ? "open" : ""}"
|
|
17531
|
+
role="dialog"
|
|
17532
|
+
aria-label="Choose time"
|
|
17533
|
+
>
|
|
17534
|
+
<div class="time-readout">
|
|
17535
|
+
<span class="time-readout-label">Time</span>
|
|
17536
|
+
<span class="time-readout-value ${draftDisplay ? "" : "empty"}">
|
|
17537
|
+
${draftDisplay || (this.format === "24h" ? "--:--" : "--:-- --")}
|
|
17538
|
+
</span>
|
|
17539
|
+
</div>
|
|
17540
|
+
|
|
17541
|
+
<div class="columns">
|
|
17542
|
+
<div class="column">
|
|
17543
|
+
<div class="column-header" aria-hidden="true">Hour</div>
|
|
17544
|
+
<div class="column-list" role="listbox" aria-label="Hours">
|
|
17545
|
+
${hourOptions.map(
|
|
17546
|
+
(opt, i) => import_lit54.html`
|
|
17547
|
+
<button
|
|
17548
|
+
part="option"
|
|
17549
|
+
class="option-button ${this.draftHour === opt.hour24 ? "selected" : ""}"
|
|
17550
|
+
type="button"
|
|
17551
|
+
role="option"
|
|
17552
|
+
aria-selected=${this.draftHour === opt.hour24}
|
|
17553
|
+
?disabled=${!this.isHourEnabled(opt.hour24)}
|
|
17554
|
+
@click=${() => this.selectHour(opt.hour24)}
|
|
17555
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "hour")}
|
|
17556
|
+
tabindex=${this.draftHour === opt.hour24 || this.draftHour === null && i === 0 ? 0 : -1}
|
|
17557
|
+
>
|
|
17558
|
+
${opt.label}
|
|
17559
|
+
</button>
|
|
17560
|
+
`
|
|
17561
|
+
)}
|
|
17562
|
+
</div>
|
|
17563
|
+
</div>
|
|
17564
|
+
|
|
17565
|
+
<div class="column">
|
|
17566
|
+
<div class="column-header" aria-hidden="true">Min</div>
|
|
17567
|
+
<div class="column-list" role="listbox" aria-label="Minutes">
|
|
17568
|
+
${minuteOptions.map(
|
|
17569
|
+
(m, i) => import_lit54.html`
|
|
17570
|
+
<button
|
|
17571
|
+
part="option"
|
|
17572
|
+
class="option-button ${this.draftMinute === m ? "selected" : ""}"
|
|
17573
|
+
type="button"
|
|
17574
|
+
role="option"
|
|
17575
|
+
aria-selected=${this.draftMinute === m}
|
|
17576
|
+
?disabled=${this.draftHour !== null && !this.isTimeInRange(this.draftHour, m)}
|
|
17577
|
+
@click=${() => this.selectMinute(m)}
|
|
17578
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "minute")}
|
|
17579
|
+
tabindex=${this.draftMinute === m || this.draftMinute === null && i === 0 ? 0 : -1}
|
|
17580
|
+
>
|
|
17581
|
+
${String(m).padStart(2, "0")}
|
|
17582
|
+
</button>
|
|
17583
|
+
`
|
|
17584
|
+
)}
|
|
17585
|
+
</div>
|
|
17586
|
+
</div>
|
|
17587
|
+
|
|
17588
|
+
${this.format === "12h" ? import_lit54.html`
|
|
17589
|
+
<div class="column">
|
|
17590
|
+
<div class="column-header" aria-hidden="true">AM/PM</div>
|
|
17591
|
+
<div class="column-list" role="listbox" aria-label="AM or PM">
|
|
17592
|
+
${["AM", "PM"].map(
|
|
17593
|
+
(p) => import_lit54.html`
|
|
17594
|
+
<button
|
|
17595
|
+
part="option"
|
|
17596
|
+
class="option-button ${draftPeriod === p ? "selected" : ""}"
|
|
17597
|
+
type="button"
|
|
17598
|
+
role="option"
|
|
17599
|
+
aria-selected=${draftPeriod === p}
|
|
17600
|
+
?disabled=${!this.isPeriodEnabled(p)}
|
|
17601
|
+
@click=${() => this.selectPeriod(p)}
|
|
17602
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "period")}
|
|
17603
|
+
tabindex=${draftPeriod === p || draftPeriod === null && p === "AM" ? 0 : -1}
|
|
17604
|
+
>
|
|
17605
|
+
${p}
|
|
17606
|
+
</button>
|
|
17607
|
+
`
|
|
17608
|
+
)}
|
|
17609
|
+
</div>
|
|
17610
|
+
</div>
|
|
17611
|
+
` : import_lit54.nothing}
|
|
17612
|
+
</div>
|
|
17613
|
+
|
|
17614
|
+
<div part="footer" class="time-footer">
|
|
17615
|
+
<rel-button
|
|
17616
|
+
variant="secondary"
|
|
17617
|
+
size="small"
|
|
17618
|
+
border="corner-radius"
|
|
17619
|
+
@click=${() => this.selectNow()}
|
|
16708
17620
|
>
|
|
16709
|
-
|
|
16710
|
-
</button
|
|
17621
|
+
Now
|
|
17622
|
+
</rel-button>
|
|
17623
|
+
<div class="footer-right">
|
|
17624
|
+
<rel-button
|
|
17625
|
+
variant="secondary"
|
|
17626
|
+
size="small"
|
|
17627
|
+
border="corner-radius"
|
|
17628
|
+
@click=${() => this.cancelPopover()}
|
|
17629
|
+
>
|
|
17630
|
+
Cancel
|
|
17631
|
+
</rel-button>
|
|
17632
|
+
<rel-button
|
|
17633
|
+
variant="primary"
|
|
17634
|
+
tone="default"
|
|
17635
|
+
size="small"
|
|
17636
|
+
border="corner-radius"
|
|
17637
|
+
?disabled=${this.draftHour === null}
|
|
17638
|
+
@click=${() => this.applySelection()}
|
|
17639
|
+
>
|
|
17640
|
+
Apply
|
|
17641
|
+
</rel-button>
|
|
17642
|
+
</div>
|
|
17643
|
+
</div>
|
|
17644
|
+
</div>
|
|
16711
17645
|
|
|
16712
|
-
|
|
17646
|
+
${belowText ? import_lit54.html`<div
|
|
17647
|
+
part="helper"
|
|
17648
|
+
class="time-picker-support ${isError ? "error" : ""}"
|
|
17649
|
+
id=${describedById}
|
|
17650
|
+
>
|
|
17651
|
+
${belowText}
|
|
17652
|
+
</div>` : import_lit54.nothing}
|
|
16713
17653
|
</div>
|
|
16714
17654
|
`;
|
|
16715
17655
|
}
|
|
16716
17656
|
};
|
|
16717
|
-
|
|
17657
|
+
RelTimePicker.styles = import_lit54.css`
|
|
16718
17658
|
:host {
|
|
16719
17659
|
color: var(--rel-text-default);
|
|
16720
|
-
|
|
16721
|
-
bottom: 24px;
|
|
16722
|
-
left: 50%;
|
|
16723
|
-
transform: translateX(-50%);
|
|
16724
|
-
display: flex;
|
|
16725
|
-
justify-content: center;
|
|
16726
|
-
width: 100%;
|
|
16727
|
-
pointer-events: none;
|
|
16728
|
-
z-index: var(--rel-z-toast, 9000);
|
|
17660
|
+
display: block;
|
|
16729
17661
|
font-family: var(--rel-fonts-font-family);
|
|
17662
|
+
position: relative;
|
|
16730
17663
|
}
|
|
16731
17664
|
|
|
16732
|
-
/*
|
|
16733
|
-
|
|
16734
|
-
display: inline-
|
|
16735
|
-
|
|
17665
|
+
/* Intrinsic width by default; set fullWidth to fill the parent. */
|
|
17666
|
+
:host(:not([fullwidth])) {
|
|
17667
|
+
display: inline-block;
|
|
17668
|
+
width: auto;
|
|
17669
|
+
}
|
|
17670
|
+
|
|
17671
|
+
:host([disabled]) {
|
|
17672
|
+
opacity: 0.6;
|
|
17673
|
+
cursor: not-allowed;
|
|
17674
|
+
}
|
|
17675
|
+
|
|
17676
|
+
:host([disabled]) * {
|
|
17677
|
+
pointer-events: none;
|
|
17678
|
+
}
|
|
17679
|
+
|
|
17680
|
+
:host([readonly]) .trigger-button {
|
|
17681
|
+
cursor: default;
|
|
17682
|
+
}
|
|
17683
|
+
|
|
17684
|
+
.time-picker-wrapper {
|
|
17685
|
+
display: flex;
|
|
17686
|
+
flex-direction: column;
|
|
16736
17687
|
gap: var(--rel-spacing-xs);
|
|
16737
|
-
|
|
16738
|
-
color: var(--rel-text-inverse);
|
|
17688
|
+
}
|
|
16739
17689
|
|
|
16740
|
-
|
|
17690
|
+
.label {
|
|
17691
|
+
color: var(--rel-text-label);
|
|
17692
|
+
font-family: var(--rel-body-sm-medium-font-family);
|
|
17693
|
+
font-weight: var(--rel-body-sm-medium-font-weight);
|
|
17694
|
+
font-size: var(--rel-body-sm-medium-font-size);
|
|
17695
|
+
line-height: var(--rel-body-sm-medium-line-height);
|
|
17696
|
+
letter-spacing: var(--rel-body-sm-medium-letter-spacing);
|
|
17697
|
+
}
|
|
17698
|
+
|
|
17699
|
+
.required-mark {
|
|
17700
|
+
color: var(--rel-text-danger);
|
|
17701
|
+
margin-left: 2px;
|
|
17702
|
+
}
|
|
17703
|
+
|
|
17704
|
+
/* Support text (helperText / errorMessage) */
|
|
17705
|
+
.time-picker-support {
|
|
17706
|
+
color: var(--rel-text-placeholder);
|
|
17707
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
17708
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
17709
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
17710
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
17711
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
17712
|
+
}
|
|
17713
|
+
|
|
17714
|
+
.time-picker-support.error {
|
|
17715
|
+
color: var(--rel-text-danger);
|
|
17716
|
+
}
|
|
17717
|
+
|
|
17718
|
+
.trigger-button {
|
|
17719
|
+
width: 100%;
|
|
17720
|
+
min-width: 160px;
|
|
17721
|
+
display: flex;
|
|
17722
|
+
align-items: center;
|
|
17723
|
+
justify-content: space-between;
|
|
17724
|
+
gap: var(--rel-spacing-xs);
|
|
16741
17725
|
padding: var(--rel-spacing-sm) var(--rel-spacing-md);
|
|
16742
|
-
|
|
16743
|
-
|
|
16744
|
-
|
|
17726
|
+
background: var(--rel-bg-surface);
|
|
17727
|
+
border: 1px solid var(--rel-border-default);
|
|
17728
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17729
|
+
color: var(--rel-text-default);
|
|
17730
|
+
cursor: pointer;
|
|
17731
|
+
transition: all 0.15s ease;
|
|
17732
|
+
text-align: left;
|
|
16745
17733
|
font-family: var(--rel-body-md-regular-font-family);
|
|
16746
17734
|
font-weight: var(--rel-body-md-regular-font-weight);
|
|
16747
17735
|
font-size: var(--rel-body-md-regular-font-size);
|
|
@@ -16749,276 +17737,1605 @@ RelToast.styles = import_lit54.css`
|
|
|
16749
17737
|
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
16750
17738
|
}
|
|
16751
17739
|
|
|
16752
|
-
.
|
|
16753
|
-
|
|
16754
|
-
|
|
17740
|
+
.trigger-button:hover:not(:disabled) {
|
|
17741
|
+
border-color: var(--rel-border-strong);
|
|
17742
|
+
}
|
|
17743
|
+
|
|
17744
|
+
.trigger-button:focus-visible {
|
|
17745
|
+
outline: 2px solid var(--rel-border-focus);
|
|
17746
|
+
outline-offset: 2px;
|
|
17747
|
+
border-color: var(--rel-border-focus);
|
|
17748
|
+
}
|
|
17749
|
+
|
|
17750
|
+
.trigger-button:disabled {
|
|
17751
|
+
cursor: not-allowed;
|
|
17752
|
+
background: var(--rel-bg-surface-elevated);
|
|
17753
|
+
}
|
|
17754
|
+
|
|
17755
|
+
/* Error state */
|
|
17756
|
+
.trigger-button.error,
|
|
17757
|
+
.trigger-button.error:hover:not(:disabled) {
|
|
17758
|
+
border-color: var(--rel-border-danger);
|
|
17759
|
+
}
|
|
17760
|
+
|
|
17761
|
+
.trigger-button.error:focus-visible {
|
|
17762
|
+
outline-color: var(--rel-border-danger);
|
|
17763
|
+
border-color: var(--rel-border-danger);
|
|
17764
|
+
}
|
|
17765
|
+
|
|
17766
|
+
/* Size variants (medium is the base .trigger-button style) */
|
|
17767
|
+
.time-picker-wrapper.small .trigger-button {
|
|
17768
|
+
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
17769
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
17770
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
17771
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
17772
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
17773
|
+
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
17774
|
+
}
|
|
17775
|
+
|
|
17776
|
+
.time-picker-wrapper.large .trigger-button {
|
|
17777
|
+
padding: var(--rel-spacing-md) var(--rel-spacing-md);
|
|
17778
|
+
font-family: var(--rel-body-lg-regular-font-family);
|
|
17779
|
+
font-weight: var(--rel-body-lg-regular-font-weight);
|
|
17780
|
+
font-size: var(--rel-body-lg-regular-font-size);
|
|
17781
|
+
line-height: var(--rel-body-lg-regular-line-height);
|
|
17782
|
+
letter-spacing: var(--rel-body-lg-regular-letter-spacing);
|
|
17783
|
+
}
|
|
17784
|
+
|
|
17785
|
+
.trigger-content {
|
|
17786
|
+
flex: 1;
|
|
17787
|
+
color: var(--rel-text-default);
|
|
17788
|
+
}
|
|
17789
|
+
|
|
17790
|
+
.trigger-content.placeholder {
|
|
17791
|
+
color: var(--rel-text-placeholder);
|
|
17792
|
+
}
|
|
17793
|
+
|
|
17794
|
+
.trigger-icons {
|
|
17795
|
+
display: flex;
|
|
17796
|
+
align-items: center;
|
|
17797
|
+
gap: var(--rel-spacing-2xs);
|
|
17798
|
+
color: var(--rel-text-placeholder);
|
|
17799
|
+
}
|
|
17800
|
+
|
|
17801
|
+
.icon-button {
|
|
17802
|
+
background: none;
|
|
17803
|
+
border: none;
|
|
17804
|
+
padding: var(--rel-spacing-3xs);
|
|
17805
|
+
cursor: pointer;
|
|
17806
|
+
color: var(--rel-text-placeholder);
|
|
17807
|
+
display: flex;
|
|
17808
|
+
align-items: center;
|
|
17809
|
+
justify-content: center;
|
|
17810
|
+
border-radius: var(--rel-border-radius-xs);
|
|
17811
|
+
transition: all 0.15s ease;
|
|
17812
|
+
}
|
|
17813
|
+
|
|
17814
|
+
.icon-button:hover {
|
|
17815
|
+
background: var(--rel-bg-surface-elevated);
|
|
17816
|
+
color: var(--rel-text-default);
|
|
17817
|
+
}
|
|
17818
|
+
|
|
17819
|
+
.icon-button svg {
|
|
17820
|
+
width: 16px;
|
|
17821
|
+
height: 16px;
|
|
17822
|
+
}
|
|
17823
|
+
|
|
17824
|
+
.popover {
|
|
17825
|
+
position: absolute;
|
|
17826
|
+
top: calc(100% + 4px);
|
|
17827
|
+
left: 0;
|
|
17828
|
+
z-index: 1000;
|
|
17829
|
+
background: var(--rel-bg-surface);
|
|
17830
|
+
border: 1px solid var(--rel-border-default);
|
|
17831
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17832
|
+
box-shadow: var(--rel-shadows-shadow-300);
|
|
17833
|
+
padding: var(--rel-spacing-md);
|
|
17834
|
+
display: none;
|
|
17835
|
+
}
|
|
17836
|
+
|
|
17837
|
+
.popover.open {
|
|
17838
|
+
display: block;
|
|
17839
|
+
}
|
|
17840
|
+
|
|
17841
|
+
/* Draft readout (mirrors the range-picker FROM/TO block) */
|
|
17842
|
+
.time-readout {
|
|
17843
|
+
display: flex;
|
|
17844
|
+
align-items: baseline;
|
|
17845
|
+
gap: var(--rel-spacing-sm);
|
|
17846
|
+
padding-bottom: var(--rel-spacing-sm);
|
|
17847
|
+
margin-bottom: var(--rel-spacing-sm);
|
|
17848
|
+
border-bottom: 1px solid var(--rel-border-subtle);
|
|
17849
|
+
}
|
|
17850
|
+
|
|
17851
|
+
.time-readout-label {
|
|
17852
|
+
color: var(--rel-text-placeholder);
|
|
17853
|
+
text-transform: uppercase;
|
|
17854
|
+
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17855
|
+
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17856
|
+
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17857
|
+
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17858
|
+
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
17859
|
+
}
|
|
17860
|
+
|
|
17861
|
+
.time-readout-value {
|
|
17862
|
+
color: var(--rel-text-default);
|
|
17863
|
+
font-family: var(--rel-body-md-semibold-font-family);
|
|
17864
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
17865
|
+
font-size: var(--rel-body-md-semibold-font-size);
|
|
17866
|
+
line-height: var(--rel-body-md-semibold-line-height);
|
|
17867
|
+
letter-spacing: var(--rel-body-md-semibold-letter-spacing);
|
|
17868
|
+
}
|
|
17869
|
+
|
|
17870
|
+
.time-readout-value.empty {
|
|
17871
|
+
color: var(--rel-text-placeholder);
|
|
17872
|
+
}
|
|
17873
|
+
|
|
17874
|
+
.columns {
|
|
17875
|
+
display: flex;
|
|
17876
|
+
gap: var(--rel-spacing-xs);
|
|
17877
|
+
}
|
|
17878
|
+
|
|
17879
|
+
/* Columns share the panel width equally, so the two-column 24h layout
|
|
17880
|
+
fills the same footprint the footer dictates instead of bunching left. */
|
|
17881
|
+
.column {
|
|
17882
|
+
display: flex;
|
|
17883
|
+
flex-direction: column;
|
|
17884
|
+
flex: 1;
|
|
17885
|
+
min-width: 64px;
|
|
17886
|
+
}
|
|
17887
|
+
|
|
17888
|
+
.column-header {
|
|
17889
|
+
text-align: center;
|
|
17890
|
+
color: var(--rel-text-placeholder);
|
|
17891
|
+
padding: var(--rel-spacing-xs);
|
|
17892
|
+
text-transform: uppercase;
|
|
17893
|
+
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17894
|
+
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17895
|
+
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17896
|
+
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17897
|
+
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
17898
|
+
}
|
|
17899
|
+
|
|
17900
|
+
.column-list {
|
|
17901
|
+
display: flex;
|
|
17902
|
+
flex-direction: column;
|
|
17903
|
+
gap: var(--rel-spacing-3xs);
|
|
17904
|
+
max-height: 224px;
|
|
17905
|
+
overflow-y: auto;
|
|
17906
|
+
padding: var(--rel-spacing-3xs);
|
|
17907
|
+
scrollbar-width: thin;
|
|
17908
|
+
}
|
|
17909
|
+
|
|
17910
|
+
.option-button {
|
|
17911
|
+
border: none;
|
|
17912
|
+
background: transparent;
|
|
17913
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17914
|
+
color: var(--rel-text-default);
|
|
17915
|
+
cursor: pointer;
|
|
17916
|
+
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
17917
|
+
text-align: center;
|
|
17918
|
+
flex-shrink: 0;
|
|
17919
|
+
transition: all 0.15s ease;
|
|
17920
|
+
font-family: var(--rel-body-sm-medium-font-family);
|
|
17921
|
+
font-weight: var(--rel-body-sm-medium-font-weight);
|
|
17922
|
+
font-size: var(--rel-body-sm-medium-font-size);
|
|
17923
|
+
line-height: var(--rel-body-sm-medium-line-height);
|
|
17924
|
+
letter-spacing: var(--rel-body-sm-medium-letter-spacing);
|
|
17925
|
+
}
|
|
17926
|
+
|
|
17927
|
+
.option-button:hover:not(:disabled):not(.selected) {
|
|
17928
|
+
background: var(--rel-bg-surface-elevated);
|
|
17929
|
+
}
|
|
17930
|
+
|
|
17931
|
+
.option-button:focus-visible {
|
|
17932
|
+
outline: 2px solid var(--rel-border-focus);
|
|
17933
|
+
outline-offset: -2px;
|
|
17934
|
+
z-index: 1;
|
|
17935
|
+
}
|
|
17936
|
+
|
|
17937
|
+
.option-button.selected {
|
|
17938
|
+
background: var(--rel-action-brand-fill);
|
|
17939
|
+
color: var(--rel-action-brand-label-icons-primary);
|
|
17940
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
17941
|
+
}
|
|
17942
|
+
|
|
17943
|
+
.option-button:disabled {
|
|
17944
|
+
color: var(--rel-text-disabled);
|
|
17945
|
+
cursor: not-allowed;
|
|
17946
|
+
opacity: 0.5;
|
|
17947
|
+
}
|
|
17948
|
+
|
|
17949
|
+
.option-button:disabled:hover {
|
|
17950
|
+
background: transparent;
|
|
17951
|
+
}
|
|
17952
|
+
|
|
17953
|
+
/* Footer (mirrors the range-picker calendar-footer) */
|
|
17954
|
+
.time-footer {
|
|
17955
|
+
display: flex;
|
|
17956
|
+
align-items: center;
|
|
17957
|
+
justify-content: space-between;
|
|
17958
|
+
gap: var(--rel-spacing-sm);
|
|
17959
|
+
padding-top: var(--rel-spacing-sm);
|
|
17960
|
+
margin-top: var(--rel-spacing-sm);
|
|
17961
|
+
border-top: 1px solid var(--rel-border-subtle);
|
|
17962
|
+
}
|
|
17963
|
+
|
|
17964
|
+
.footer-right {
|
|
17965
|
+
display: flex;
|
|
17966
|
+
align-items: center;
|
|
17967
|
+
gap: var(--rel-spacing-xs);
|
|
17968
|
+
}
|
|
17969
|
+
|
|
17970
|
+
@media (prefers-reduced-motion: reduce) {
|
|
17971
|
+
.trigger-button,
|
|
17972
|
+
.icon-button,
|
|
17973
|
+
.option-button {
|
|
17974
|
+
transition: none;
|
|
17975
|
+
}
|
|
17976
|
+
}
|
|
17977
|
+
`;
|
|
17978
|
+
__decorateClass([
|
|
17979
|
+
(0, import_decorators54.property)({ type: String, reflect: true })
|
|
17980
|
+
], RelTimePicker.prototype, "value", 2);
|
|
17981
|
+
__decorateClass([
|
|
17982
|
+
(0, import_decorators54.property)({ type: String })
|
|
17983
|
+
], RelTimePicker.prototype, "placeholder", 2);
|
|
17984
|
+
__decorateClass([
|
|
17985
|
+
(0, import_decorators54.property)({ type: String })
|
|
17986
|
+
], RelTimePicker.prototype, "label", 2);
|
|
17987
|
+
__decorateClass([
|
|
17988
|
+
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
17989
|
+
], RelTimePicker.prototype, "disabled", 2);
|
|
17990
|
+
__decorateClass([
|
|
17991
|
+
(0, import_decorators54.property)({ type: Boolean })
|
|
17992
|
+
], RelTimePicker.prototype, "required", 2);
|
|
17993
|
+
__decorateClass([
|
|
17994
|
+
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
17995
|
+
], RelTimePicker.prototype, "readonly", 2);
|
|
17996
|
+
__decorateClass([
|
|
17997
|
+
(0, import_decorators54.property)({ type: String })
|
|
17998
|
+
], RelTimePicker.prototype, "format", 2);
|
|
17999
|
+
__decorateClass([
|
|
18000
|
+
(0, import_decorators54.property)({ type: Number, attribute: "minute-step" })
|
|
18001
|
+
], RelTimePicker.prototype, "minuteStep", 2);
|
|
18002
|
+
__decorateClass([
|
|
18003
|
+
(0, import_decorators54.property)({ type: String, attribute: "min-time" })
|
|
18004
|
+
], RelTimePicker.prototype, "minTime", 2);
|
|
18005
|
+
__decorateClass([
|
|
18006
|
+
(0, import_decorators54.property)({ type: String, attribute: "max-time" })
|
|
18007
|
+
], RelTimePicker.prototype, "maxTime", 2);
|
|
18008
|
+
__decorateClass([
|
|
18009
|
+
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
18010
|
+
], RelTimePicker.prototype, "open", 2);
|
|
18011
|
+
__decorateClass([
|
|
18012
|
+
(0, import_decorators54.property)({ type: Boolean })
|
|
18013
|
+
], RelTimePicker.prototype, "clearable", 2);
|
|
18014
|
+
__decorateClass([
|
|
18015
|
+
(0, import_decorators54.property)({ type: Boolean })
|
|
18016
|
+
], RelTimePicker.prototype, "error", 2);
|
|
18017
|
+
__decorateClass([
|
|
18018
|
+
(0, import_decorators54.property)({ type: Boolean })
|
|
18019
|
+
], RelTimePicker.prototype, "invalid", 2);
|
|
18020
|
+
__decorateClass([
|
|
18021
|
+
(0, import_decorators54.property)({ type: String, attribute: "error-message" })
|
|
18022
|
+
], RelTimePicker.prototype, "errorMessage", 2);
|
|
18023
|
+
__decorateClass([
|
|
18024
|
+
(0, import_decorators54.property)({ type: String, attribute: "helper-text" })
|
|
18025
|
+
], RelTimePicker.prototype, "helperText", 2);
|
|
18026
|
+
__decorateClass([
|
|
18027
|
+
(0, import_decorators54.property)({ type: String })
|
|
18028
|
+
], RelTimePicker.prototype, "name", 2);
|
|
18029
|
+
__decorateClass([
|
|
18030
|
+
(0, import_decorators54.property)({ type: String })
|
|
18031
|
+
], RelTimePicker.prototype, "size", 2);
|
|
18032
|
+
__decorateClass([
|
|
18033
|
+
(0, import_decorators54.property)({ type: String, reflect: true })
|
|
18034
|
+
], RelTimePicker.prototype, "variant", 2);
|
|
18035
|
+
__decorateClass([
|
|
18036
|
+
(0, import_decorators54.property)({ type: Boolean, reflect: true })
|
|
18037
|
+
], RelTimePicker.prototype, "fullWidth", 2);
|
|
18038
|
+
__decorateClass([
|
|
18039
|
+
(0, import_decorators54.state)()
|
|
18040
|
+
], RelTimePicker.prototype, "draftHour", 2);
|
|
18041
|
+
__decorateClass([
|
|
18042
|
+
(0, import_decorators54.state)()
|
|
18043
|
+
], RelTimePicker.prototype, "draftMinute", 2);
|
|
18044
|
+
__decorateClass([
|
|
18045
|
+
(0, import_decorators54.query)(".trigger-button")
|
|
18046
|
+
], RelTimePicker.prototype, "triggerButton", 2);
|
|
18047
|
+
__decorateClass([
|
|
18048
|
+
(0, import_decorators54.query)(".columns")
|
|
18049
|
+
], RelTimePicker.prototype, "columnsElement", 2);
|
|
18050
|
+
RelTimePicker = __decorateClass([
|
|
18051
|
+
(0, import_decorators54.customElement)("rel-time-picker")
|
|
18052
|
+
], RelTimePicker);
|
|
18053
|
+
|
|
18054
|
+
// src/components/rel-toast/rel-toast.ts
|
|
18055
|
+
var import_lit55 = require("lit");
|
|
18056
|
+
var import_decorators55 = require("lit/decorators.js");
|
|
18057
|
+
var RelToast = class extends import_lit55.LitElement {
|
|
18058
|
+
constructor() {
|
|
18059
|
+
super(...arguments);
|
|
18060
|
+
this.message = "";
|
|
18061
|
+
this.type = "default";
|
|
18062
|
+
this.duration = 4e3;
|
|
18063
|
+
this.show = false;
|
|
18064
|
+
this.visible = false;
|
|
18065
|
+
this._dismissTimer = null;
|
|
18066
|
+
}
|
|
18067
|
+
willUpdate(changed) {
|
|
18068
|
+
if (!changed.has("show")) return;
|
|
18069
|
+
if (this.show) {
|
|
18070
|
+
this.visible = true;
|
|
18071
|
+
if (this._dismissTimer) clearTimeout(this._dismissTimer);
|
|
18072
|
+
if (this.duration > 0) {
|
|
18073
|
+
this._dismissTimer = setTimeout(() => this.close(), this.duration);
|
|
18074
|
+
}
|
|
18075
|
+
} else {
|
|
18076
|
+
this.visible = false;
|
|
18077
|
+
if (this._dismissTimer) {
|
|
18078
|
+
clearTimeout(this._dismissTimer);
|
|
18079
|
+
this._dismissTimer = null;
|
|
18080
|
+
}
|
|
18081
|
+
}
|
|
18082
|
+
}
|
|
18083
|
+
disconnectedCallback() {
|
|
18084
|
+
super.disconnectedCallback();
|
|
18085
|
+
if (this._dismissTimer) clearTimeout(this._dismissTimer);
|
|
18086
|
+
}
|
|
18087
|
+
open() {
|
|
18088
|
+
this.show = true;
|
|
18089
|
+
}
|
|
18090
|
+
close() {
|
|
18091
|
+
this.show = false;
|
|
18092
|
+
this.visible = false;
|
|
18093
|
+
if (this._dismissTimer) {
|
|
18094
|
+
clearTimeout(this._dismissTimer);
|
|
18095
|
+
this._dismissTimer = null;
|
|
18096
|
+
}
|
|
18097
|
+
this.dispatchEvent(
|
|
18098
|
+
new CustomEvent("dismissed", {
|
|
18099
|
+
bubbles: true,
|
|
18100
|
+
composed: true
|
|
18101
|
+
})
|
|
18102
|
+
);
|
|
18103
|
+
}
|
|
18104
|
+
handleActionClick(event) {
|
|
18105
|
+
event.stopPropagation();
|
|
18106
|
+
this.dispatchEvent(
|
|
18107
|
+
new CustomEvent("action", {
|
|
18108
|
+
bubbles: true,
|
|
18109
|
+
composed: true
|
|
18110
|
+
})
|
|
18111
|
+
);
|
|
18112
|
+
this.close();
|
|
18113
|
+
}
|
|
18114
|
+
render() {
|
|
18115
|
+
return import_lit55.html`
|
|
18116
|
+
<div part="base" class="toast ${this.type} ${this.visible ? "" : "hidden"}" role="status" aria-live="polite">
|
|
18117
|
+
<span part="label" class="toast-message">${this.message}</span>
|
|
18118
|
+
|
|
18119
|
+
${this.actionLabel ? import_lit55.html`<button
|
|
18120
|
+
part="action" class="toast-action"
|
|
18121
|
+
@click="${this.handleActionClick}"
|
|
18122
|
+
>
|
|
18123
|
+
${this.actionLabel}
|
|
18124
|
+
</button>` : ""}
|
|
18125
|
+
|
|
18126
|
+
<button part="close-button" class="toast-close" @click="${this.close}" aria-label="Dismiss">✕</button>
|
|
18127
|
+
</div>
|
|
18128
|
+
`;
|
|
18129
|
+
}
|
|
18130
|
+
};
|
|
18131
|
+
RelToast.styles = import_lit55.css`
|
|
18132
|
+
:host {
|
|
18133
|
+
color: var(--rel-text-default);
|
|
18134
|
+
position: fixed;
|
|
18135
|
+
bottom: 24px;
|
|
18136
|
+
left: 50%;
|
|
18137
|
+
transform: translateX(-50%);
|
|
18138
|
+
display: flex;
|
|
18139
|
+
justify-content: center;
|
|
18140
|
+
width: 100%;
|
|
18141
|
+
pointer-events: none;
|
|
18142
|
+
z-index: var(--rel-z-toast, 9000);
|
|
18143
|
+
font-family: var(--rel-fonts-font-family);
|
|
18144
|
+
}
|
|
18145
|
+
|
|
18146
|
+
/* Toast container */
|
|
18147
|
+
.toast {
|
|
18148
|
+
display: inline-flex;
|
|
18149
|
+
align-items: center;
|
|
18150
|
+
gap: var(--rel-spacing-xs);
|
|
18151
|
+
background-color: var(--rel-bg-neutral-inverse-1);
|
|
18152
|
+
color: var(--rel-text-inverse);
|
|
18153
|
+
|
|
18154
|
+
border-radius: var(--rel-border-radius-sm);
|
|
18155
|
+
padding: var(--rel-spacing-sm) var(--rel-spacing-md);
|
|
18156
|
+
box-shadow: var(--rel-shadows-shadow-300);
|
|
18157
|
+
pointer-events: all;
|
|
18158
|
+
animation: fadeInUp 0.25s ease;
|
|
18159
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
18160
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
18161
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
18162
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
18163
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
18164
|
+
}
|
|
18165
|
+
|
|
18166
|
+
.toast.error {
|
|
18167
|
+
background-color: var(--rel-bg-danger);
|
|
18168
|
+
color: var(--rel-text-static-light);
|
|
18169
|
+
}
|
|
18170
|
+
|
|
18171
|
+
.toast.hidden {
|
|
18172
|
+
display: none;
|
|
18173
|
+
}
|
|
18174
|
+
|
|
18175
|
+
/* Message */
|
|
18176
|
+
.toast-message {
|
|
18177
|
+
flex: 1;
|
|
18178
|
+
}
|
|
18179
|
+
|
|
18180
|
+
/* Action button */
|
|
18181
|
+
.toast-action {
|
|
18182
|
+
background: transparent;
|
|
18183
|
+
border: none;
|
|
18184
|
+
color: var(--rel-action-neutral-label-icons-fill-inverse);
|
|
18185
|
+
font-family: var(--rel-action-md-font-family);
|
|
18186
|
+
font-weight: var(--rel-action-md-font-weight);
|
|
18187
|
+
font-size: var(--rel-action-md-font-size);
|
|
18188
|
+
line-height: var(--rel-action-md-line-height);
|
|
18189
|
+
letter-spacing: var(--rel-action-md-letter-spacing);
|
|
18190
|
+
cursor: pointer;
|
|
18191
|
+
transition: opacity 0.2s;
|
|
18192
|
+
}
|
|
18193
|
+
|
|
18194
|
+
.toast-action:hover {
|
|
18195
|
+
background: var(--rel-bg-neutral);
|
|
18196
|
+
color: var(--rel-text-default);
|
|
18197
|
+
opacity: 0.75;
|
|
18198
|
+
}
|
|
18199
|
+
|
|
18200
|
+
.toast-action:focus-visible {
|
|
18201
|
+
outline: 2px solid var(--rel-text-static-light);
|
|
18202
|
+
outline-offset: 2px;
|
|
18203
|
+
border-radius: var(--rel-border-radius-xs);
|
|
18204
|
+
}
|
|
18205
|
+
|
|
18206
|
+
/* Close button */
|
|
18207
|
+
.toast-close {
|
|
18208
|
+
background: transparent;
|
|
18209
|
+
color: var(--rel-action-neutral-label-icons-ghost);
|
|
18210
|
+
border: none;
|
|
18211
|
+
color: inherit;
|
|
18212
|
+
font-size: 1rem;
|
|
18213
|
+
cursor: pointer;
|
|
18214
|
+
line-height: 1;
|
|
18215
|
+
padding: var(--rel-spacing-2xs);
|
|
18216
|
+
margin-left: var(--rel-spacing-2xs);
|
|
18217
|
+
}
|
|
18218
|
+
|
|
18219
|
+
.toast-close:hover {
|
|
18220
|
+
opacity: 0.75;
|
|
18221
|
+
}
|
|
18222
|
+
|
|
18223
|
+
.toast-close:focus-visible {
|
|
18224
|
+
outline: 2px solid var(--rel-text-static-light);
|
|
18225
|
+
outline-offset: 2px;
|
|
18226
|
+
border-radius: var(--rel-border-radius-xs);
|
|
18227
|
+
}
|
|
18228
|
+
|
|
18229
|
+
|
|
18230
|
+
@media (prefers-reduced-motion: reduce) {
|
|
18231
|
+
.toast {
|
|
18232
|
+
animation: none;
|
|
18233
|
+
}
|
|
18234
|
+
}
|
|
18235
|
+
|
|
18236
|
+
/* Animations */
|
|
18237
|
+
@keyframes fadeInUp {
|
|
18238
|
+
from {
|
|
18239
|
+
opacity: 0;
|
|
18240
|
+
transform: translateY(20px);
|
|
18241
|
+
}
|
|
18242
|
+
to {
|
|
18243
|
+
opacity: 1;
|
|
18244
|
+
transform: translateY(0);
|
|
18245
|
+
}
|
|
18246
|
+
}
|
|
18247
|
+
`;
|
|
18248
|
+
__decorateClass([
|
|
18249
|
+
(0, import_decorators55.property)({ type: String })
|
|
18250
|
+
], RelToast.prototype, "message", 2);
|
|
18251
|
+
__decorateClass([
|
|
18252
|
+
(0, import_decorators55.property)({ type: String })
|
|
18253
|
+
], RelToast.prototype, "type", 2);
|
|
18254
|
+
__decorateClass([
|
|
18255
|
+
(0, import_decorators55.property)({ type: String })
|
|
18256
|
+
], RelToast.prototype, "actionLabel", 2);
|
|
18257
|
+
__decorateClass([
|
|
18258
|
+
(0, import_decorators55.property)({ type: Number })
|
|
18259
|
+
], RelToast.prototype, "duration", 2);
|
|
18260
|
+
__decorateClass([
|
|
18261
|
+
(0, import_decorators55.property)({ type: Boolean, reflect: true })
|
|
18262
|
+
], RelToast.prototype, "show", 2);
|
|
18263
|
+
__decorateClass([
|
|
18264
|
+
(0, import_decorators55.state)()
|
|
18265
|
+
], RelToast.prototype, "visible", 2);
|
|
18266
|
+
RelToast = __decorateClass([
|
|
18267
|
+
(0, import_decorators55.customElement)("rel-toast")
|
|
18268
|
+
], RelToast);
|
|
18269
|
+
|
|
18270
|
+
// src/components/rel-tooltip/rel-tooltip.ts
|
|
18271
|
+
var import_lit56 = require("lit");
|
|
18272
|
+
var import_decorators56 = require("lit/decorators.js");
|
|
18273
|
+
var RelTooltip = class extends import_lit56.LitElement {
|
|
18274
|
+
constructor() {
|
|
18275
|
+
super(...arguments);
|
|
18276
|
+
this.text = "";
|
|
18277
|
+
this.position = "above";
|
|
18278
|
+
this.showTail = false;
|
|
18279
|
+
this.type = "single-line";
|
|
18280
|
+
// Stable id so aria-describedby can reference the bubble across renders.
|
|
18281
|
+
this.uniqueId = Math.random().toString(36).slice(2);
|
|
18282
|
+
}
|
|
18283
|
+
firstUpdated() {
|
|
18284
|
+
this.linkTrigger();
|
|
18285
|
+
}
|
|
18286
|
+
linkTrigger() {
|
|
18287
|
+
const slot = this.shadowRoot?.querySelector("slot");
|
|
18288
|
+
if (!slot) return;
|
|
18289
|
+
const trigger = slot.assignedElements()[0];
|
|
18290
|
+
if (trigger) {
|
|
18291
|
+
trigger.setAttribute("aria-describedby", `tooltip-${this.uniqueId}`);
|
|
18292
|
+
}
|
|
18293
|
+
}
|
|
18294
|
+
render() {
|
|
18295
|
+
return import_lit56.html`
|
|
18296
|
+
<slot @slotchange="${this.linkTrigger}"></slot>
|
|
18297
|
+
<div
|
|
18298
|
+
part="panel" class="tooltip-bubble ${this.position}"
|
|
18299
|
+
id="tooltip-${this.uniqueId}"
|
|
18300
|
+
role="tooltip"
|
|
18301
|
+
>
|
|
18302
|
+
${this.text}
|
|
18303
|
+
${this.showTail ? import_lit56.html`<span part="indicator" class="tooltip-tail"></span>` : ""}
|
|
18304
|
+
</div>
|
|
18305
|
+
`;
|
|
18306
|
+
}
|
|
18307
|
+
};
|
|
18308
|
+
RelTooltip.styles = import_lit56.css`
|
|
18309
|
+
:host {
|
|
18310
|
+
color: var(--rel-text-default);
|
|
18311
|
+
display: inline-block;
|
|
18312
|
+
position: relative;
|
|
18313
|
+
}
|
|
18314
|
+
|
|
18315
|
+
/* Tooltip container */
|
|
18316
|
+
.tooltip-bubble {
|
|
18317
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
18318
|
+
font-weight: var(--rel-body-md-medium-font-weight);
|
|
18319
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
18320
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
18321
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
18322
|
+
position: absolute;
|
|
18323
|
+
background-color: var(--rel-bg-neutral-inverse-2);
|
|
18324
|
+
color: var(--rel-text-inverse);
|
|
18325
|
+
padding: var(--rel-spacing-xs, 8px)
|
|
18326
|
+
var(--rel-spacing-sm, 12px);
|
|
18327
|
+
border-radius: var(--rel-border-radius-sm, 8px);
|
|
18328
|
+
white-space: nowrap;
|
|
18329
|
+
z-index: 1000;
|
|
18330
|
+
opacity: 0;
|
|
18331
|
+
pointer-events: none;
|
|
18332
|
+
transition: opacity 0.2s ease;
|
|
18333
|
+
overflow: visible;
|
|
18334
|
+
}
|
|
18335
|
+
|
|
18336
|
+
/* Figma Type=Multi Line: wrap inside a bounded bubble */
|
|
18337
|
+
:host([type="multi-line"]) .tooltip-bubble {
|
|
18338
|
+
white-space: normal;
|
|
18339
|
+
max-width: 220px;
|
|
18340
|
+
width: max-content;
|
|
18341
|
+
}
|
|
18342
|
+
|
|
18343
|
+
:host(:hover) .tooltip-bubble,
|
|
18344
|
+
:host(:focus-within) .tooltip-bubble {
|
|
18345
|
+
opacity: 1;
|
|
18346
|
+
}
|
|
18347
|
+
|
|
18348
|
+
/* Positioning */
|
|
18349
|
+
.tooltip-bubble.above {
|
|
18350
|
+
bottom: calc(100% + 6px);
|
|
18351
|
+
left: 50%;
|
|
18352
|
+
transform: translateX(-50%);
|
|
18353
|
+
}
|
|
18354
|
+
|
|
18355
|
+
.tooltip-bubble.below {
|
|
18356
|
+
top: calc(100% + 6px);
|
|
18357
|
+
left: 50%;
|
|
18358
|
+
transform: translateX(-50%);
|
|
18359
|
+
}
|
|
18360
|
+
|
|
18361
|
+
.tooltip-bubble.left {
|
|
18362
|
+
right: calc(100% + 6px);
|
|
18363
|
+
top: 50%;
|
|
18364
|
+
transform: translateY(-50%);
|
|
18365
|
+
}
|
|
18366
|
+
|
|
18367
|
+
.tooltip-bubble.right {
|
|
18368
|
+
left: calc(100% + 6px);
|
|
18369
|
+
top: 50%;
|
|
18370
|
+
transform: translateY(-50%);
|
|
18371
|
+
}
|
|
18372
|
+
|
|
18373
|
+
/* Tail/Arrow */
|
|
18374
|
+
.tooltip-tail {
|
|
18375
|
+
position: absolute;
|
|
18376
|
+
width: 0;
|
|
18377
|
+
height: 0;
|
|
18378
|
+
display: block;
|
|
18379
|
+
}
|
|
18380
|
+
|
|
18381
|
+
.tooltip-bubble.above .tooltip-tail,
|
|
18382
|
+
.tooltip-bubble.below .tooltip-tail {
|
|
18383
|
+
left: 50%;
|
|
18384
|
+
transform: translateX(-50%);
|
|
18385
|
+
border-left: 6px solid transparent;
|
|
18386
|
+
border-right: 6px solid transparent;
|
|
18387
|
+
}
|
|
18388
|
+
|
|
18389
|
+
.tooltip-bubble.above .tooltip-tail {
|
|
18390
|
+
bottom: -6px;
|
|
18391
|
+
border-top: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
18392
|
+
border-bottom: none;
|
|
18393
|
+
}
|
|
18394
|
+
|
|
18395
|
+
.tooltip-bubble.below .tooltip-tail {
|
|
18396
|
+
top: -6px;
|
|
18397
|
+
border-bottom: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
18398
|
+
border-top: none;
|
|
18399
|
+
}
|
|
18400
|
+
|
|
18401
|
+
.tooltip-bubble.left .tooltip-tail,
|
|
18402
|
+
.tooltip-bubble.right .tooltip-tail {
|
|
18403
|
+
top: 50%;
|
|
18404
|
+
transform: translateY(-50%);
|
|
18405
|
+
border-top: 6px solid transparent;
|
|
18406
|
+
border-bottom: 6px solid transparent;
|
|
18407
|
+
}
|
|
18408
|
+
|
|
18409
|
+
.tooltip-bubble.left .tooltip-tail {
|
|
18410
|
+
right: -6px;
|
|
18411
|
+
border-left: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
18412
|
+
border-right: none;
|
|
18413
|
+
}
|
|
18414
|
+
|
|
18415
|
+
.tooltip-bubble.right .tooltip-tail {
|
|
18416
|
+
left: -6px;
|
|
18417
|
+
border-right: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
18418
|
+
border-left: none;
|
|
18419
|
+
}
|
|
18420
|
+
`;
|
|
18421
|
+
__decorateClass([
|
|
18422
|
+
(0, import_decorators56.property)({ type: String })
|
|
18423
|
+
], RelTooltip.prototype, "text", 2);
|
|
18424
|
+
__decorateClass([
|
|
18425
|
+
(0, import_decorators56.property)({ type: String })
|
|
18426
|
+
], RelTooltip.prototype, "position", 2);
|
|
18427
|
+
__decorateClass([
|
|
18428
|
+
(0, import_decorators56.property)({ type: Boolean, attribute: "show-tail" })
|
|
18429
|
+
], RelTooltip.prototype, "showTail", 2);
|
|
18430
|
+
__decorateClass([
|
|
18431
|
+
(0, import_decorators56.property)({ type: String, reflect: true })
|
|
18432
|
+
], RelTooltip.prototype, "type", 2);
|
|
18433
|
+
RelTooltip = __decorateClass([
|
|
18434
|
+
(0, import_decorators56.customElement)("rel-tooltip")
|
|
18435
|
+
], RelTooltip);
|
|
18436
|
+
|
|
18437
|
+
// src/components/rel-upload/rel-upload.ts
|
|
18438
|
+
var import_lit57 = require("lit");
|
|
18439
|
+
var import_decorators57 = require("lit/decorators.js");
|
|
18440
|
+
var import_rel_icon17 = require("@reliability-design/icons/rel-icon");
|
|
18441
|
+
var uid = 0;
|
|
18442
|
+
var RelUpload = class extends import_lit57.LitElement {
|
|
18443
|
+
constructor() {
|
|
18444
|
+
super(...arguments);
|
|
18445
|
+
this.accept = "";
|
|
18446
|
+
this.maxSize = "";
|
|
18447
|
+
this.multiple = false;
|
|
18448
|
+
this.disabled = false;
|
|
18449
|
+
this.hint = "";
|
|
18450
|
+
this.dropzoneTitle = "Drag and drop your files here";
|
|
18451
|
+
this.browseLabel = "Browse files";
|
|
18452
|
+
this.files = [];
|
|
18453
|
+
this.concurrency = 3;
|
|
18454
|
+
this.delayThreshold = 15e3;
|
|
18455
|
+
this.dragOver = false;
|
|
18456
|
+
this.delayed = false;
|
|
18457
|
+
this.lastSignature = "";
|
|
18458
|
+
this.openPicker = () => {
|
|
18459
|
+
if (this.disabled) return;
|
|
18460
|
+
this.inputEl?.click();
|
|
18461
|
+
};
|
|
18462
|
+
this.onInputChange = (e) => {
|
|
18463
|
+
const input = e.target;
|
|
18464
|
+
this.ingest(input.files);
|
|
18465
|
+
input.value = "";
|
|
18466
|
+
};
|
|
18467
|
+
this.onDragOver = (e) => {
|
|
18468
|
+
if (this.disabled) return;
|
|
18469
|
+
e.preventDefault();
|
|
18470
|
+
this.dragOver = true;
|
|
18471
|
+
};
|
|
18472
|
+
this.onDragLeave = (e) => {
|
|
18473
|
+
e.preventDefault();
|
|
18474
|
+
this.dragOver = false;
|
|
18475
|
+
};
|
|
18476
|
+
this.onDrop = (e) => {
|
|
18477
|
+
e.preventDefault();
|
|
18478
|
+
this.dragOver = false;
|
|
18479
|
+
if (this.disabled) return;
|
|
18480
|
+
this.ingest(e.dataTransfer?.files ?? null);
|
|
18481
|
+
};
|
|
18482
|
+
}
|
|
18483
|
+
// ── derived ───────────────────────────────────────────────────────────────
|
|
18484
|
+
/**
|
|
18485
|
+
* Aggregate state. Read-only; corresponds to the Figma modal variants.
|
|
18486
|
+
*
|
|
18487
|
+
* `failed` means there is nothing left to send AND something errored — while
|
|
18488
|
+
* any row is still `ready` the primary action is Upload, not Retry.
|
|
18489
|
+
*/
|
|
18490
|
+
get phase() {
|
|
18491
|
+
if (this.files.length === 0) return "empty";
|
|
18492
|
+
if (this.files.some((f) => f.status === "uploading")) {
|
|
18493
|
+
return this.delayed ? "delayed" : "uploading";
|
|
18494
|
+
}
|
|
18495
|
+
if (this.pending.length > 0) return "selected";
|
|
18496
|
+
if (this.files.some((f) => f.status === "error")) return "failed";
|
|
18497
|
+
return "selected";
|
|
18498
|
+
}
|
|
18499
|
+
/** Files still eligible to be sent. */
|
|
18500
|
+
get pending() {
|
|
18501
|
+
return this.files.filter((f) => f.status === "ready");
|
|
18502
|
+
}
|
|
18503
|
+
get failedFiles() {
|
|
18504
|
+
return this.files.filter((f) => f.status === "error");
|
|
18505
|
+
}
|
|
18506
|
+
/**
|
|
18507
|
+
* Failed rows worth sending again. A file rejected by `accept`/`max-size`
|
|
18508
|
+
* will fail validation every time, so retrying it is pointless — re-check
|
|
18509
|
+
* rather than re-queueing it blindly.
|
|
18510
|
+
*/
|
|
18511
|
+
get retryable() {
|
|
18512
|
+
const maxBytes = this.parseMaxBytes();
|
|
18513
|
+
return this.failedFiles.filter(
|
|
18514
|
+
(f) => !!f.file && this.matchesAccept(f.file) && f.file.size <= maxBytes
|
|
18515
|
+
);
|
|
18516
|
+
}
|
|
18517
|
+
get canUpload() {
|
|
18518
|
+
if (this.disabled) return false;
|
|
18519
|
+
const phase = this.phase;
|
|
18520
|
+
if (phase === "uploading" || phase === "delayed") return false;
|
|
18521
|
+
if (phase === "failed") return this.retryable.length > 0;
|
|
18522
|
+
return this.pending.length > 0;
|
|
18523
|
+
}
|
|
18524
|
+
/** Footer button label — "Upload", "Upload 3 files" or "Retry upload". */
|
|
18525
|
+
get primaryLabel() {
|
|
18526
|
+
if (this.phase === "failed") return "Retry upload";
|
|
18527
|
+
const n = this.files.length;
|
|
18528
|
+
return n > 1 ? `Upload ${n} files` : "Upload";
|
|
18529
|
+
}
|
|
18530
|
+
/** The snapshot `<rel-upload-actions>` renders from. */
|
|
18531
|
+
get state() {
|
|
18532
|
+
return {
|
|
18533
|
+
phase: this.phase,
|
|
18534
|
+
total: this.files.length,
|
|
18535
|
+
failed: this.failedFiles.length,
|
|
18536
|
+
canUpload: this.canUpload,
|
|
18537
|
+
primaryLabel: this.primaryLabel
|
|
18538
|
+
};
|
|
18539
|
+
}
|
|
18540
|
+
get listHeading() {
|
|
18541
|
+
const n = this.files.length;
|
|
18542
|
+
switch (this.phase) {
|
|
18543
|
+
case "uploading":
|
|
18544
|
+
case "delayed":
|
|
18545
|
+
return `Uploading ${n} ${n === 1 ? "file" : "files"}`;
|
|
18546
|
+
case "failed":
|
|
18547
|
+
return `${n} ${n === 1 ? "file" : "files"} - ${this.failedFiles.length} failed`;
|
|
18548
|
+
default:
|
|
18549
|
+
return n === 1 ? "Selected file (1)" : `Selected files (${n})`;
|
|
18550
|
+
}
|
|
18551
|
+
}
|
|
18552
|
+
get hintText() {
|
|
18553
|
+
if (this.hint) return this.hint;
|
|
18554
|
+
const formats = this.acceptDisplay;
|
|
18555
|
+
const size = this.maxSize ? ` - up to ${this.maxSize} per file` : "";
|
|
18556
|
+
return `${formats}${size}`;
|
|
18557
|
+
}
|
|
18558
|
+
get acceptDisplay() {
|
|
18559
|
+
const tokens = this.accept.split(",").map((s) => s.trim()).filter(Boolean).map((tok) => {
|
|
18560
|
+
if (tok.startsWith(".")) return tok.slice(1).toUpperCase();
|
|
18561
|
+
if (tok.endsWith("/*")) return tok.slice(0, -2).toUpperCase();
|
|
18562
|
+
return (tok.split("/").pop() ?? tok).toUpperCase();
|
|
18563
|
+
});
|
|
18564
|
+
if (tokens.length === 0) return "Any file type";
|
|
18565
|
+
if (tokens.length === 1) return tokens[0];
|
|
18566
|
+
return `${tokens.slice(0, -1).join(", ")} or ${tokens[tokens.length - 1]}`;
|
|
18567
|
+
}
|
|
18568
|
+
get alertContent() {
|
|
18569
|
+
if (this.phase === "delayed") {
|
|
18570
|
+
return {
|
|
18571
|
+
type: "warning",
|
|
18572
|
+
message1: "This is taking longer than usual",
|
|
18573
|
+
message2: "Large files can take a few minutes on a slow connection. You can leave this open or come back later."
|
|
18574
|
+
};
|
|
18575
|
+
}
|
|
18576
|
+
const failed = this.failedFiles.length;
|
|
18577
|
+
if (failed > 0 && this.phase !== "uploading") {
|
|
18578
|
+
const limit = this.maxSize ? `under ${this.maxSize} and ` : "";
|
|
18579
|
+
return {
|
|
18580
|
+
type: "error",
|
|
18581
|
+
message1: failed === 1 && this.files.length === 1 ? "We couldn't upload your file" : `We couldn't upload ${failed} of your files`,
|
|
18582
|
+
message2: `Check that each file is ${limit}in a supported format, then try again.`
|
|
18583
|
+
};
|
|
18584
|
+
}
|
|
18585
|
+
return null;
|
|
18586
|
+
}
|
|
18587
|
+
// ── validation ────────────────────────────────────────────────────────────
|
|
18588
|
+
parseMaxBytes() {
|
|
18589
|
+
const s = this.maxSize.trim().toLowerCase();
|
|
18590
|
+
if (!s) return Infinity;
|
|
18591
|
+
const m = s.match(/^([\d.]+)\s*(b|kb|mb|gb)?$/);
|
|
18592
|
+
if (!m) return Infinity;
|
|
18593
|
+
const n = parseFloat(m[1]);
|
|
18594
|
+
const unit = m[2] || "b";
|
|
18595
|
+
const mult = unit === "gb" ? 1e9 : unit === "mb" ? 1e6 : unit === "kb" ? 1e3 : 1;
|
|
18596
|
+
return n * mult;
|
|
18597
|
+
}
|
|
18598
|
+
matchesAccept(file) {
|
|
18599
|
+
const raw = this.accept.trim();
|
|
18600
|
+
if (!raw) return true;
|
|
18601
|
+
const tokens = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
18602
|
+
const name = file.name.toLowerCase();
|
|
18603
|
+
const type = (file.type || "").toLowerCase();
|
|
18604
|
+
return tokens.some((tok) => {
|
|
18605
|
+
if (tok.startsWith(".")) return name.endsWith(tok);
|
|
18606
|
+
if (tok.endsWith("/*")) return type.startsWith(tok.slice(0, -1));
|
|
18607
|
+
return type === tok;
|
|
18608
|
+
});
|
|
18609
|
+
}
|
|
18610
|
+
static fmtSize(bytes) {
|
|
18611
|
+
if (bytes < 1e3) return `${bytes} B`;
|
|
18612
|
+
if (bytes < 1e6) return `${(bytes / 1e3).toFixed(0)} KB`;
|
|
18613
|
+
return `${(bytes / 1e6).toFixed(1)} MB`;
|
|
18614
|
+
}
|
|
18615
|
+
static ext(name) {
|
|
18616
|
+
const i = name.lastIndexOf(".");
|
|
18617
|
+
return i > -1 ? name.slice(i + 1).toUpperCase() : "FILE";
|
|
18618
|
+
}
|
|
18619
|
+
/**
|
|
18620
|
+
* Validate and append. Invalid files are kept as `error` rows rather than
|
|
18621
|
+
* dropped — that is how the Figma Failure state surfaces them.
|
|
18622
|
+
*/
|
|
18623
|
+
ingest(list) {
|
|
18624
|
+
if (!list || list.length === 0) return;
|
|
18625
|
+
const maxBytes = this.parseMaxBytes();
|
|
18626
|
+
const incoming = this.multiple ? Array.from(list) : Array.from(list).slice(0, 1);
|
|
18627
|
+
const added = [];
|
|
18628
|
+
const rejected = [];
|
|
18629
|
+
for (const f of incoming) {
|
|
18630
|
+
const entry = {
|
|
18631
|
+
id: `rel-upload-${++uid}`,
|
|
18632
|
+
name: f.name,
|
|
18633
|
+
size: f.size,
|
|
18634
|
+
status: "ready",
|
|
18635
|
+
file: f
|
|
18636
|
+
};
|
|
18637
|
+
if (!this.matchesAccept(f)) {
|
|
18638
|
+
entry.status = "error";
|
|
18639
|
+
entry.error = "unsupported file type";
|
|
18640
|
+
rejected.push({ name: f.name, reason: "format", message: `${f.name}: unsupported file type` });
|
|
18641
|
+
} else if (f.size > maxBytes) {
|
|
18642
|
+
entry.status = "error";
|
|
18643
|
+
entry.error = `file exceeds the ${this.maxSize} limit`;
|
|
18644
|
+
rejected.push({
|
|
18645
|
+
name: f.name,
|
|
18646
|
+
reason: "size",
|
|
18647
|
+
message: `${f.name}: exceeds ${this.maxSize}`
|
|
18648
|
+
});
|
|
18649
|
+
}
|
|
18650
|
+
added.push(entry);
|
|
18651
|
+
}
|
|
18652
|
+
const names = new Set(added.map((f) => f.name));
|
|
18653
|
+
const kept = this.multiple ? this.files.filter((f) => !names.has(f.name)) : [];
|
|
18654
|
+
this.files = [...kept, ...added];
|
|
18655
|
+
if (rejected.length) this.emit("rel-invalid", { rejected });
|
|
18656
|
+
this.emit("rel-files-added", { added, rejected });
|
|
18657
|
+
this.emit("rel-change", { files: this.files });
|
|
18658
|
+
this.notify();
|
|
18659
|
+
}
|
|
18660
|
+
// ── upload orchestration (self-driving mode) ──────────────────────────────
|
|
18661
|
+
patch(id, partial) {
|
|
18662
|
+
this.files = this.files.map((f) => f.id === id ? { ...f, ...partial } : f);
|
|
18663
|
+
this.notify();
|
|
18664
|
+
}
|
|
18665
|
+
startDelayTimer() {
|
|
18666
|
+
this.stopDelayTimer();
|
|
18667
|
+
if (this.delayThreshold <= 0) return;
|
|
18668
|
+
this.delayTimer = setTimeout(() => {
|
|
18669
|
+
if (this.files.some((f) => f.status === "uploading")) {
|
|
18670
|
+
this.delayed = true;
|
|
18671
|
+
this.notify();
|
|
18672
|
+
}
|
|
18673
|
+
}, this.delayThreshold);
|
|
18674
|
+
}
|
|
18675
|
+
stopDelayTimer() {
|
|
18676
|
+
if (this.delayTimer) clearTimeout(this.delayTimer);
|
|
18677
|
+
this.delayTimer = void 0;
|
|
18678
|
+
}
|
|
18679
|
+
async runBatch(targets) {
|
|
18680
|
+
const uploader = this.uploader;
|
|
18681
|
+
if (!uploader || targets.length === 0) return;
|
|
18682
|
+
this.aborter = new AbortController();
|
|
18683
|
+
const signal = this.aborter.signal;
|
|
18684
|
+
this.startDelayTimer();
|
|
18685
|
+
const queue = [...targets];
|
|
18686
|
+
const workers = Array.from({ length: Math.max(1, this.concurrency) }, async () => {
|
|
18687
|
+
while (queue.length) {
|
|
18688
|
+
if (signal.aborted) return;
|
|
18689
|
+
const item = queue.shift();
|
|
18690
|
+
if (!item || !item.file) continue;
|
|
18691
|
+
this.patch(item.id, { status: "uploading", progress: 0, loaded: 0, error: void 0 });
|
|
18692
|
+
try {
|
|
18693
|
+
await uploader(item.file, {
|
|
18694
|
+
file: item,
|
|
18695
|
+
signal,
|
|
18696
|
+
onProgress: (loaded, total) => {
|
|
18697
|
+
const t = total ?? item.size;
|
|
18698
|
+
const progress = t > 0 ? Math.min(100, Math.round(loaded / t * 100)) : 0;
|
|
18699
|
+
this.patch(item.id, { loaded, progress });
|
|
18700
|
+
this.emit("rel-progress", { id: item.id, progress, loaded, total: t });
|
|
18701
|
+
}
|
|
18702
|
+
});
|
|
18703
|
+
if (signal.aborted) {
|
|
18704
|
+
this.patch(item.id, { status: "ready", progress: void 0, loaded: void 0 });
|
|
18705
|
+
return;
|
|
18706
|
+
}
|
|
18707
|
+
this.patch(item.id, { status: "success", progress: 100, loaded: item.size });
|
|
18708
|
+
this.emit("rel-file-complete", { id: item.id, file: item.file });
|
|
18709
|
+
} catch (err) {
|
|
18710
|
+
if (signal.aborted) {
|
|
18711
|
+
this.patch(item.id, { status: "ready", progress: void 0, loaded: void 0 });
|
|
18712
|
+
return;
|
|
18713
|
+
}
|
|
18714
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
18715
|
+
this.patch(item.id, { status: "error", error: message, progress: void 0 });
|
|
18716
|
+
this.emit("rel-file-error", { id: item.id, file: item.file, error: message });
|
|
18717
|
+
}
|
|
18718
|
+
}
|
|
18719
|
+
});
|
|
18720
|
+
await Promise.all(workers);
|
|
18721
|
+
this.stopDelayTimer();
|
|
18722
|
+
this.delayed = false;
|
|
18723
|
+
this.aborter = void 0;
|
|
18724
|
+
if (!signal.aborted) {
|
|
18725
|
+
this.emit("rel-complete", {
|
|
18726
|
+
succeeded: this.files.filter((f) => f.status === "success"),
|
|
18727
|
+
failed: this.failedFiles
|
|
18728
|
+
});
|
|
18729
|
+
}
|
|
18730
|
+
this.notify();
|
|
18731
|
+
}
|
|
18732
|
+
// ── public API ────────────────────────────────────────────────────────────
|
|
18733
|
+
/** Start (or resume) the batch. Emits `rel-upload`; runs it when `uploader` is set. */
|
|
18734
|
+
upload() {
|
|
18735
|
+
if (!this.canUpload) return;
|
|
18736
|
+
const targets = this.pending;
|
|
18737
|
+
this.emit("rel-upload", { files: targets });
|
|
18738
|
+
if (this.uploader) void this.runBatch(targets);
|
|
18739
|
+
else this.notify();
|
|
18740
|
+
}
|
|
18741
|
+
/** Re-queue every retryable failed row and send again. */
|
|
18742
|
+
retry() {
|
|
18743
|
+
const targets = this.retryable;
|
|
18744
|
+
if (targets.length === 0) return;
|
|
18745
|
+
const ids = new Set(targets.map((f) => f.id));
|
|
18746
|
+
this.files = this.files.map(
|
|
18747
|
+
(f) => ids.has(f.id) ? { ...f, status: "ready", error: void 0, progress: void 0 } : f
|
|
18748
|
+
);
|
|
18749
|
+
const requeued = this.files.filter((f) => ids.has(f.id));
|
|
18750
|
+
this.emit("rel-retry", { files: requeued });
|
|
18751
|
+
if (this.uploader) void this.runBatch(requeued);
|
|
18752
|
+
else this.notify();
|
|
18753
|
+
}
|
|
18754
|
+
/** Abort anything in flight and emit `rel-cancel`. Does not clear the list. */
|
|
18755
|
+
cancel() {
|
|
18756
|
+
this.aborter?.abort();
|
|
18757
|
+
this.stopDelayTimer();
|
|
18758
|
+
this.delayed = false;
|
|
18759
|
+
this.emit("rel-cancel");
|
|
18760
|
+
this.notify();
|
|
18761
|
+
}
|
|
18762
|
+
/** Remove one row by id. */
|
|
18763
|
+
removeFile(id) {
|
|
18764
|
+
const target = this.files.find((f) => f.id === id);
|
|
18765
|
+
this.files = this.files.filter((f) => f.id !== id);
|
|
18766
|
+
this.emit("rel-remove", { id, file: target?.file });
|
|
18767
|
+
this.emit("rel-change", { files: this.files });
|
|
18768
|
+
this.notify();
|
|
18769
|
+
}
|
|
18770
|
+
/** Clear everything and abort in-flight transfers. */
|
|
18771
|
+
reset() {
|
|
18772
|
+
this.aborter?.abort();
|
|
18773
|
+
this.stopDelayTimer();
|
|
18774
|
+
this.files = [];
|
|
18775
|
+
this.delayed = false;
|
|
18776
|
+
if (this.inputEl) this.inputEl.value = "";
|
|
18777
|
+
this.emit("rel-change", { files: this.files });
|
|
18778
|
+
this.notify();
|
|
18779
|
+
}
|
|
18780
|
+
// ── events ────────────────────────────────────────────────────────────────
|
|
18781
|
+
emit(type, detail) {
|
|
18782
|
+
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
18783
|
+
}
|
|
18784
|
+
/**
|
|
18785
|
+
* Emit `rel-state` only when the derived snapshot actually changes, so
|
|
18786
|
+
* progress ticks don't spam `<rel-upload-actions>`.
|
|
18787
|
+
*/
|
|
18788
|
+
notify() {
|
|
18789
|
+
const s = this.state;
|
|
18790
|
+
const signature = `${s.phase}|${s.total}|${s.failed}|${s.canUpload}|${s.primaryLabel}`;
|
|
18791
|
+
if (signature === this.lastSignature) return;
|
|
18792
|
+
this.lastSignature = signature;
|
|
18793
|
+
this.emit("rel-state", s);
|
|
18794
|
+
}
|
|
18795
|
+
connectedCallback() {
|
|
18796
|
+
super.connectedCallback();
|
|
18797
|
+
queueMicrotask(() => this.emit("rel-state", this.state));
|
|
18798
|
+
}
|
|
18799
|
+
disconnectedCallback() {
|
|
18800
|
+
super.disconnectedCallback();
|
|
18801
|
+
this.stopDelayTimer();
|
|
18802
|
+
this.aborter?.abort();
|
|
18803
|
+
}
|
|
18804
|
+
updated() {
|
|
18805
|
+
this.notify();
|
|
18806
|
+
}
|
|
18807
|
+
// ── render ────────────────────────────────────────────────────────────────
|
|
18808
|
+
rowMeta(f) {
|
|
18809
|
+
switch (f.status) {
|
|
18810
|
+
case "uploading": {
|
|
18811
|
+
const pct = f.progress ?? 0;
|
|
18812
|
+
const loaded = RelUpload.fmtSize(f.loaded ?? 0);
|
|
18813
|
+
return `${pct}% - ${loaded} of ${RelUpload.fmtSize(f.size)}`;
|
|
18814
|
+
}
|
|
18815
|
+
case "success":
|
|
18816
|
+
return `${RelUpload.ext(f.name)} - ${RelUpload.fmtSize(f.size)} - Uploaded`;
|
|
18817
|
+
case "error":
|
|
18818
|
+
return `Upload failed - ${f.error ?? "please try again"}`;
|
|
18819
|
+
default: {
|
|
18820
|
+
const base = `${RelUpload.ext(f.name)} - ${RelUpload.fmtSize(f.size)}`;
|
|
18821
|
+
const busy = this.phase === "uploading" || this.phase === "delayed";
|
|
18822
|
+
return busy ? `${base} - Waiting` : base;
|
|
18823
|
+
}
|
|
18824
|
+
}
|
|
18825
|
+
}
|
|
18826
|
+
renderRow(f) {
|
|
18827
|
+
return import_lit57.html`
|
|
18828
|
+
<div class="row ${f.status}">
|
|
18829
|
+
<rel-icon name="document" size="md"></rel-icon>
|
|
18830
|
+
|
|
18831
|
+
<div class="row-content">
|
|
18832
|
+
<span class="row-name" title=${f.name}>${f.name}</span>
|
|
18833
|
+
${f.status === "uploading" ? import_lit57.html`<rel-progress
|
|
18834
|
+
mode="linear-determinate"
|
|
18835
|
+
.value=${f.progress ?? 0}
|
|
18836
|
+
></rel-progress>` : import_lit57.nothing}
|
|
18837
|
+
<span class="row-meta">${this.rowMeta(f)}</span>
|
|
18838
|
+
</div>
|
|
18839
|
+
|
|
18840
|
+
<div class="row-trailing">
|
|
18841
|
+
${f.status === "success" ? import_lit57.html`<rel-icon
|
|
18842
|
+
class="status-success"
|
|
18843
|
+
name="check-circle"
|
|
18844
|
+
size="md"
|
|
18845
|
+
role="img"
|
|
18846
|
+
aria-label="Uploaded"
|
|
18847
|
+
></rel-icon>` : import_lit57.nothing}
|
|
18848
|
+
${f.status === "error" ? import_lit57.html`<rel-icon
|
|
18849
|
+
class="status-error"
|
|
18850
|
+
name="error"
|
|
18851
|
+
size="md"
|
|
18852
|
+
role="img"
|
|
18853
|
+
aria-label="Failed"
|
|
18854
|
+
></rel-icon>` : import_lit57.nothing}
|
|
18855
|
+
<button
|
|
18856
|
+
class="remove"
|
|
18857
|
+
aria-label="Remove ${f.name}"
|
|
18858
|
+
?disabled=${this.disabled}
|
|
18859
|
+
@click=${() => this.removeFile(f.id)}
|
|
18860
|
+
>
|
|
18861
|
+
<rel-icon name="delete" size="md"></rel-icon>
|
|
18862
|
+
</button>
|
|
18863
|
+
</div>
|
|
18864
|
+
</div>
|
|
18865
|
+
`;
|
|
18866
|
+
}
|
|
18867
|
+
render() {
|
|
18868
|
+
const alert = this.alertContent;
|
|
18869
|
+
return import_lit57.html`
|
|
18870
|
+
<div class="body">
|
|
18871
|
+
${alert ? import_lit57.html`<rel-alert
|
|
18872
|
+
type=${alert.type}
|
|
18873
|
+
appearance="subtle"
|
|
18874
|
+
message1=${alert.message1}
|
|
18875
|
+
message2=${alert.message2}
|
|
18876
|
+
></rel-alert>` : import_lit57.nothing}
|
|
18877
|
+
|
|
18878
|
+
<div
|
|
18879
|
+
class="dropzone ${this.dragOver ? "dragover" : ""}"
|
|
18880
|
+
@click=${this.openPicker}
|
|
18881
|
+
@dragover=${this.onDragOver}
|
|
18882
|
+
@dragleave=${this.onDragLeave}
|
|
18883
|
+
@drop=${this.onDrop}
|
|
18884
|
+
>
|
|
18885
|
+
<span class="icon-badge">
|
|
18886
|
+
<rel-icon name="upload" size="md"></rel-icon>
|
|
18887
|
+
</span>
|
|
18888
|
+
|
|
18889
|
+
<div class="dz-copy">
|
|
18890
|
+
<p class="dz-title">${this.dropzoneTitle}</p>
|
|
18891
|
+
<p class="dz-hint">${this.hintText}</p>
|
|
18892
|
+
</div>
|
|
18893
|
+
|
|
18894
|
+
<button
|
|
18895
|
+
class="browse"
|
|
18896
|
+
type="button"
|
|
18897
|
+
?disabled=${this.disabled}
|
|
18898
|
+
@click=${(e) => {
|
|
18899
|
+
e.stopPropagation();
|
|
18900
|
+
this.openPicker();
|
|
18901
|
+
}}
|
|
18902
|
+
>
|
|
18903
|
+
${this.browseLabel}
|
|
18904
|
+
</button>
|
|
18905
|
+
</div>
|
|
18906
|
+
|
|
18907
|
+
${this.files.length ? import_lit57.html`<div class="file-list">
|
|
18908
|
+
<p class="list-heading">${this.listHeading}</p>
|
|
18909
|
+
<div class="rows" role="list">${this.files.map((f) => this.renderRow(f))}</div>
|
|
18910
|
+
</div>` : import_lit57.nothing}
|
|
18911
|
+
|
|
18912
|
+
<input
|
|
18913
|
+
class="file-input"
|
|
18914
|
+
type="file"
|
|
18915
|
+
?multiple=${this.multiple}
|
|
18916
|
+
accept=${this.accept || import_lit57.nothing}
|
|
18917
|
+
@change=${this.onInputChange}
|
|
18918
|
+
/>
|
|
18919
|
+
</div>
|
|
18920
|
+
`;
|
|
18921
|
+
}
|
|
18922
|
+
};
|
|
18923
|
+
RelUpload.styles = import_lit57.css`
|
|
18924
|
+
:host {
|
|
18925
|
+
display: block;
|
|
18926
|
+
box-sizing: border-box;
|
|
18927
|
+
color: var(--rel-text-default);
|
|
18928
|
+
font-family: var(--rel-fonts-font-family);
|
|
18929
|
+
}
|
|
18930
|
+
|
|
18931
|
+
:host([disabled]) {
|
|
18932
|
+
opacity: 0.6;
|
|
18933
|
+
}
|
|
18934
|
+
|
|
18935
|
+
:host([disabled]) .body {
|
|
18936
|
+
pointer-events: none;
|
|
18937
|
+
}
|
|
18938
|
+
|
|
18939
|
+
.body {
|
|
18940
|
+
display: flex;
|
|
18941
|
+
flex-direction: column;
|
|
18942
|
+
gap: var(--rel-spacing-lg, 20px);
|
|
18943
|
+
}
|
|
18944
|
+
|
|
18945
|
+
/* ── Dropzone ─────────────────────────────────────────────────────────── */
|
|
18946
|
+
|
|
18947
|
+
.dropzone {
|
|
18948
|
+
box-sizing: border-box;
|
|
18949
|
+
display: flex;
|
|
18950
|
+
flex-direction: column;
|
|
18951
|
+
align-items: center;
|
|
18952
|
+
justify-content: center;
|
|
18953
|
+
gap: var(--rel-spacing-md, 16px);
|
|
18954
|
+
padding: var(--rel-spacing-2xl, 32px) var(--rel-spacing-xl, 24px);
|
|
18955
|
+
background: var(--rel-bg-surface);
|
|
18956
|
+
border: 1px dashed var(--rel-border-accent);
|
|
18957
|
+
border-radius: var(--rel-border-radius-md, 16px);
|
|
18958
|
+
text-align: center;
|
|
18959
|
+
cursor: pointer;
|
|
18960
|
+
transition: border-color 0.15s ease, background 0.15s ease;
|
|
18961
|
+
}
|
|
18962
|
+
|
|
18963
|
+
/* Design gap: no drag-over frame exists in Figma. */
|
|
18964
|
+
.dropzone.dragover {
|
|
18965
|
+
border-style: solid;
|
|
18966
|
+
border-color: var(--rel-border-brand);
|
|
18967
|
+
background: color-mix(in srgb, var(--rel-bg-brand) 8%, var(--rel-bg-surface));
|
|
18968
|
+
}
|
|
18969
|
+
|
|
18970
|
+
.icon-badge {
|
|
18971
|
+
display: inline-flex;
|
|
18972
|
+
align-items: center;
|
|
18973
|
+
justify-content: center;
|
|
18974
|
+
padding: var(--rel-spacing-sm, 12px);
|
|
18975
|
+
background: var(--rel-bg-surface);
|
|
18976
|
+
border: 1px solid var(--rel-border-subtle);
|
|
18977
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
18978
|
+
}
|
|
18979
|
+
|
|
18980
|
+
.dz-copy {
|
|
18981
|
+
display: flex;
|
|
18982
|
+
flex-direction: column;
|
|
18983
|
+
align-items: center;
|
|
18984
|
+
gap: var(--rel-spacing-2xs, 4px);
|
|
18985
|
+
}
|
|
18986
|
+
|
|
18987
|
+
.dz-title {
|
|
18988
|
+
margin: 0;
|
|
18989
|
+
color: var(--rel-text-default);
|
|
18990
|
+
font-family: var(--rel-body-lg-medium-font-family);
|
|
18991
|
+
font-weight: var(--rel-body-lg-medium-font-weight);
|
|
18992
|
+
font-size: var(--rel-body-lg-medium-font-size);
|
|
18993
|
+
line-height: var(--rel-body-lg-medium-line-height);
|
|
18994
|
+
}
|
|
18995
|
+
|
|
18996
|
+
.dz-hint {
|
|
18997
|
+
margin: 0;
|
|
18998
|
+
color: var(--rel-text-caption);
|
|
18999
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
19000
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
19001
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
19002
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
19003
|
+
}
|
|
19004
|
+
|
|
19005
|
+
/* The dropzone is a mouse affordance only — the Browse button is the sole
|
|
19006
|
+
focusable control, so there are no nested interactive elements. */
|
|
19007
|
+
.browse {
|
|
19008
|
+
display: inline-flex;
|
|
19009
|
+
align-items: center;
|
|
19010
|
+
gap: var(--rel-spacing-xs, 8px);
|
|
19011
|
+
padding: var(--rel-spacing-xs, 8px) var(--rel-spacing-md, 16px);
|
|
19012
|
+
background: transparent;
|
|
19013
|
+
color: var(--rel-text-default);
|
|
19014
|
+
border: 1px solid var(--rel-action-neutral-border-outline, var(--rel-border-accent));
|
|
19015
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
19016
|
+
cursor: pointer;
|
|
19017
|
+
font-family: var(--rel-body-md-semibold-font-family);
|
|
19018
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
19019
|
+
font-size: var(--rel-body-md-semibold-font-size);
|
|
19020
|
+
line-height: var(--rel-body-md-semibold-line-height);
|
|
19021
|
+
}
|
|
19022
|
+
|
|
19023
|
+
.browse:hover:not(:disabled) {
|
|
19024
|
+
background: var(--rel-action-neutral-outline-hover, var(--rel-bg-surface-elevated));
|
|
19025
|
+
}
|
|
19026
|
+
|
|
19027
|
+
.browse:focus-visible {
|
|
19028
|
+
outline: 2px solid var(--rel-border-focus);
|
|
19029
|
+
outline-offset: 2px;
|
|
19030
|
+
}
|
|
19031
|
+
|
|
19032
|
+
.browse:disabled {
|
|
19033
|
+
cursor: not-allowed;
|
|
19034
|
+
}
|
|
19035
|
+
|
|
19036
|
+
/* ── File list ────────────────────────────────────────────────────────── */
|
|
19037
|
+
|
|
19038
|
+
.file-list {
|
|
19039
|
+
display: flex;
|
|
19040
|
+
flex-direction: column;
|
|
19041
|
+
gap: var(--rel-spacing-sm, 12px);
|
|
19042
|
+
}
|
|
19043
|
+
|
|
19044
|
+
.list-heading {
|
|
19045
|
+
margin: 0;
|
|
19046
|
+
color: var(--rel-text-default);
|
|
19047
|
+
font-family: var(--rel-body-md-semibold-font-family);
|
|
19048
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
19049
|
+
font-size: var(--rel-body-md-semibold-font-size);
|
|
19050
|
+
line-height: var(--rel-body-md-semibold-line-height);
|
|
19051
|
+
}
|
|
19052
|
+
|
|
19053
|
+
.rows {
|
|
19054
|
+
display: flex;
|
|
19055
|
+
flex-direction: column;
|
|
19056
|
+
gap: var(--rel-spacing-xs, 8px);
|
|
19057
|
+
}
|
|
19058
|
+
|
|
19059
|
+
.row {
|
|
19060
|
+
display: flex;
|
|
19061
|
+
align-items: center;
|
|
19062
|
+
gap: var(--rel-spacing-sm, 12px);
|
|
19063
|
+
box-sizing: border-box;
|
|
19064
|
+
padding: var(--rel-spacing-sm, 12px) var(--rel-spacing-md, 16px);
|
|
19065
|
+
background: var(--rel-bg-surface);
|
|
19066
|
+
border: 1px solid var(--rel-border-default);
|
|
19067
|
+
border-radius: var(--rel-border-radius-sm, 8px);
|
|
19068
|
+
}
|
|
19069
|
+
|
|
19070
|
+
.row.error {
|
|
19071
|
+
background: var(--rel-bg-danger-light);
|
|
19072
|
+
border-color: var(--rel-border-danger);
|
|
19073
|
+
}
|
|
19074
|
+
|
|
19075
|
+
.row > rel-icon {
|
|
19076
|
+
flex-shrink: 0;
|
|
19077
|
+
}
|
|
19078
|
+
|
|
19079
|
+
.row-content {
|
|
19080
|
+
display: flex;
|
|
19081
|
+
flex-direction: column;
|
|
19082
|
+
gap: var(--rel-spacing-3xs, 2px);
|
|
19083
|
+
flex: 1;
|
|
19084
|
+
min-width: 0;
|
|
19085
|
+
}
|
|
19086
|
+
|
|
19087
|
+
.row.uploading .row-content {
|
|
19088
|
+
gap: var(--rel-spacing-2xs, 4px);
|
|
19089
|
+
}
|
|
19090
|
+
|
|
19091
|
+
.row-name {
|
|
19092
|
+
overflow: hidden;
|
|
19093
|
+
text-overflow: ellipsis;
|
|
19094
|
+
white-space: nowrap;
|
|
19095
|
+
color: var(--rel-text-default);
|
|
19096
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
19097
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
19098
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
19099
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
19100
|
+
}
|
|
19101
|
+
|
|
19102
|
+
.row-meta {
|
|
19103
|
+
color: var(--rel-text-caption);
|
|
19104
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
19105
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
19106
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
19107
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
19108
|
+
}
|
|
19109
|
+
|
|
19110
|
+
.row.success .row-meta {
|
|
19111
|
+
color: var(--rel-text-success);
|
|
19112
|
+
}
|
|
19113
|
+
|
|
19114
|
+
.row.error .row-meta {
|
|
19115
|
+
color: var(--rel-text-danger);
|
|
16755
19116
|
}
|
|
16756
19117
|
|
|
16757
|
-
.
|
|
16758
|
-
|
|
19118
|
+
/* Figma's 4px rounded track; rel-progress ships a 6px square one. */
|
|
19119
|
+
rel-progress::part(track) {
|
|
19120
|
+
height: 4px;
|
|
19121
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
16759
19122
|
}
|
|
16760
19123
|
|
|
16761
|
-
|
|
16762
|
-
|
|
16763
|
-
|
|
19124
|
+
rel-progress::part(indicator) {
|
|
19125
|
+
background: var(--rel-bg-success);
|
|
19126
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
16764
19127
|
}
|
|
16765
19128
|
|
|
16766
|
-
|
|
16767
|
-
|
|
16768
|
-
|
|
16769
|
-
|
|
16770
|
-
|
|
16771
|
-
font-family: var(--rel-action-md-font-family);
|
|
16772
|
-
font-weight: var(--rel-action-md-font-weight);
|
|
16773
|
-
font-size: var(--rel-action-md-font-size);
|
|
16774
|
-
line-height: var(--rel-action-md-line-height);
|
|
16775
|
-
letter-spacing: var(--rel-action-md-letter-spacing);
|
|
16776
|
-
cursor: pointer;
|
|
16777
|
-
transition: opacity 0.2s;
|
|
19129
|
+
.row-trailing {
|
|
19130
|
+
display: flex;
|
|
19131
|
+
align-items: center;
|
|
19132
|
+
gap: var(--rel-spacing-sm, 12px);
|
|
19133
|
+
flex-shrink: 0;
|
|
16778
19134
|
}
|
|
16779
19135
|
|
|
16780
|
-
.
|
|
16781
|
-
|
|
16782
|
-
color: var(--rel-text-default);
|
|
16783
|
-
opacity: 0.75;
|
|
19136
|
+
.status-success {
|
|
19137
|
+
color: var(--rel-text-success);
|
|
16784
19138
|
}
|
|
16785
19139
|
|
|
16786
|
-
.
|
|
16787
|
-
|
|
16788
|
-
outline-offset: 2px;
|
|
16789
|
-
border-radius: var(--rel-border-radius-xs);
|
|
19140
|
+
.status-error {
|
|
19141
|
+
color: var(--rel-text-danger);
|
|
16790
19142
|
}
|
|
16791
19143
|
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
19144
|
+
.remove {
|
|
19145
|
+
display: inline-flex;
|
|
19146
|
+
align-items: center;
|
|
19147
|
+
justify-content: center;
|
|
19148
|
+
padding: 0;
|
|
19149
|
+
background: none;
|
|
16796
19150
|
border: none;
|
|
16797
|
-
color: inherit;
|
|
16798
|
-
font-size: 1rem;
|
|
16799
19151
|
cursor: pointer;
|
|
16800
|
-
|
|
16801
|
-
padding: var(--rel-spacing-2xs);
|
|
16802
|
-
margin-left: var(--rel-spacing-2xs);
|
|
16803
|
-
}
|
|
16804
|
-
|
|
16805
|
-
.toast-close:hover {
|
|
16806
|
-
opacity: 0.75;
|
|
19152
|
+
color: var(--rel-text-default);
|
|
16807
19153
|
}
|
|
16808
19154
|
|
|
16809
|
-
.
|
|
16810
|
-
outline: 2px solid var(--rel-
|
|
19155
|
+
.remove:focus-visible {
|
|
19156
|
+
outline: 2px solid var(--rel-border-focus);
|
|
16811
19157
|
outline-offset: 2px;
|
|
16812
|
-
border-radius: var(--rel-border-radius-xs);
|
|
19158
|
+
border-radius: var(--rel-border-radius-xs, 4px);
|
|
16813
19159
|
}
|
|
16814
19160
|
|
|
16815
|
-
|
|
16816
|
-
|
|
16817
|
-
.toast {
|
|
16818
|
-
animation: none;
|
|
16819
|
-
}
|
|
19161
|
+
.file-input {
|
|
19162
|
+
display: none;
|
|
16820
19163
|
}
|
|
16821
19164
|
|
|
16822
|
-
|
|
16823
|
-
|
|
16824
|
-
|
|
16825
|
-
opacity: 0;
|
|
16826
|
-
transform: translateY(20px);
|
|
16827
|
-
}
|
|
16828
|
-
to {
|
|
16829
|
-
opacity: 1;
|
|
16830
|
-
transform: translateY(0);
|
|
19165
|
+
@media (prefers-reduced-motion: reduce) {
|
|
19166
|
+
.dropzone {
|
|
19167
|
+
transition: none;
|
|
16831
19168
|
}
|
|
16832
19169
|
}
|
|
16833
19170
|
`;
|
|
16834
19171
|
__decorateClass([
|
|
16835
|
-
(0,
|
|
16836
|
-
],
|
|
19172
|
+
(0, import_decorators57.property)({ type: String })
|
|
19173
|
+
], RelUpload.prototype, "accept", 2);
|
|
16837
19174
|
__decorateClass([
|
|
16838
|
-
(0,
|
|
16839
|
-
],
|
|
19175
|
+
(0, import_decorators57.property)({ type: String, attribute: "max-size" })
|
|
19176
|
+
], RelUpload.prototype, "maxSize", 2);
|
|
16840
19177
|
__decorateClass([
|
|
16841
|
-
(0,
|
|
16842
|
-
],
|
|
19178
|
+
(0, import_decorators57.property)({ type: Boolean })
|
|
19179
|
+
], RelUpload.prototype, "multiple", 2);
|
|
16843
19180
|
__decorateClass([
|
|
16844
|
-
(0,
|
|
16845
|
-
],
|
|
19181
|
+
(0, import_decorators57.property)({ type: Boolean, reflect: true })
|
|
19182
|
+
], RelUpload.prototype, "disabled", 2);
|
|
16846
19183
|
__decorateClass([
|
|
16847
|
-
(0,
|
|
16848
|
-
],
|
|
19184
|
+
(0, import_decorators57.property)({ type: String })
|
|
19185
|
+
], RelUpload.prototype, "hint", 2);
|
|
16849
19186
|
__decorateClass([
|
|
16850
|
-
(0,
|
|
16851
|
-
],
|
|
16852
|
-
|
|
16853
|
-
(0,
|
|
16854
|
-
],
|
|
19187
|
+
(0, import_decorators57.property)({ type: String, attribute: "dropzone-title" })
|
|
19188
|
+
], RelUpload.prototype, "dropzoneTitle", 2);
|
|
19189
|
+
__decorateClass([
|
|
19190
|
+
(0, import_decorators57.property)({ type: String, attribute: "browse-label" })
|
|
19191
|
+
], RelUpload.prototype, "browseLabel", 2);
|
|
19192
|
+
__decorateClass([
|
|
19193
|
+
(0, import_decorators57.property)({ attribute: false })
|
|
19194
|
+
], RelUpload.prototype, "files", 2);
|
|
19195
|
+
__decorateClass([
|
|
19196
|
+
(0, import_decorators57.property)({ attribute: false })
|
|
19197
|
+
], RelUpload.prototype, "uploader", 2);
|
|
19198
|
+
__decorateClass([
|
|
19199
|
+
(0, import_decorators57.property)({ type: Number })
|
|
19200
|
+
], RelUpload.prototype, "concurrency", 2);
|
|
19201
|
+
__decorateClass([
|
|
19202
|
+
(0, import_decorators57.property)({ type: Number, attribute: "delay-threshold" })
|
|
19203
|
+
], RelUpload.prototype, "delayThreshold", 2);
|
|
19204
|
+
__decorateClass([
|
|
19205
|
+
(0, import_decorators57.state)()
|
|
19206
|
+
], RelUpload.prototype, "dragOver", 2);
|
|
19207
|
+
__decorateClass([
|
|
19208
|
+
(0, import_decorators57.state)()
|
|
19209
|
+
], RelUpload.prototype, "delayed", 2);
|
|
19210
|
+
__decorateClass([
|
|
19211
|
+
(0, import_decorators57.query)(".file-input")
|
|
19212
|
+
], RelUpload.prototype, "inputEl", 2);
|
|
19213
|
+
RelUpload = __decorateClass([
|
|
19214
|
+
(0, import_decorators57.customElement)("rel-upload")
|
|
19215
|
+
], RelUpload);
|
|
16855
19216
|
|
|
16856
|
-
// src/components/rel-
|
|
16857
|
-
var
|
|
16858
|
-
var
|
|
16859
|
-
var
|
|
19217
|
+
// src/components/rel-upload/rel-upload-actions.ts
|
|
19218
|
+
var import_lit58 = require("lit");
|
|
19219
|
+
var import_decorators58 = require("lit/decorators.js");
|
|
19220
|
+
var import_rel_icon18 = require("@reliability-design/icons/rel-icon");
|
|
19221
|
+
var EMPTY_STATE = {
|
|
19222
|
+
phase: "empty",
|
|
19223
|
+
total: 0,
|
|
19224
|
+
failed: 0,
|
|
19225
|
+
canUpload: false,
|
|
19226
|
+
primaryLabel: "Upload"
|
|
19227
|
+
};
|
|
19228
|
+
var RelUploadActions = class extends import_lit58.LitElement {
|
|
16860
19229
|
constructor() {
|
|
16861
19230
|
super(...arguments);
|
|
16862
|
-
this.
|
|
16863
|
-
this.
|
|
16864
|
-
this.
|
|
16865
|
-
this.
|
|
16866
|
-
|
|
16867
|
-
this.
|
|
19231
|
+
this.for = "";
|
|
19232
|
+
this.cancelLabel = "Cancel";
|
|
19233
|
+
this.noCancel = false;
|
|
19234
|
+
this.snapshot = EMPTY_STATE;
|
|
19235
|
+
this.target = null;
|
|
19236
|
+
this.onState = (e) => {
|
|
19237
|
+
if (!this.target) this.resolveTarget();
|
|
19238
|
+
if (e.target !== this.target) return;
|
|
19239
|
+
this.snapshot = e.detail;
|
|
19240
|
+
};
|
|
19241
|
+
this.onCancel = () => {
|
|
19242
|
+
this.target?.cancel();
|
|
19243
|
+
this.dispatchEvent(new CustomEvent("rel-cancel", { bubbles: true, composed: true }));
|
|
19244
|
+
};
|
|
19245
|
+
this.onPrimary = () => {
|
|
19246
|
+
if (!this.target) return;
|
|
19247
|
+
if (this.snapshot.phase === "failed") this.target.retry();
|
|
19248
|
+
else this.target.upload();
|
|
19249
|
+
};
|
|
16868
19250
|
}
|
|
16869
|
-
|
|
16870
|
-
|
|
19251
|
+
connectedCallback() {
|
|
19252
|
+
super.connectedCallback();
|
|
19253
|
+
queueMicrotask(() => this.resolveTarget());
|
|
19254
|
+
this.root.addEventListener("rel-state", this.onState);
|
|
16871
19255
|
}
|
|
16872
|
-
|
|
16873
|
-
|
|
16874
|
-
|
|
16875
|
-
|
|
16876
|
-
|
|
16877
|
-
|
|
19256
|
+
disconnectedCallback() {
|
|
19257
|
+
super.disconnectedCallback();
|
|
19258
|
+
this.root.removeEventListener("rel-state", this.onState);
|
|
19259
|
+
this.target = null;
|
|
19260
|
+
}
|
|
19261
|
+
updated(changed) {
|
|
19262
|
+
if (changed.has("for")) this.resolveTarget();
|
|
19263
|
+
}
|
|
19264
|
+
/**
|
|
19265
|
+
* Listen on our own root rather than `document`: a shared root means no
|
|
19266
|
+
* shadow boundary sits between us and the sibling `<rel-upload>`, so
|
|
19267
|
+
* `event.target` is never retargeted away from it.
|
|
19268
|
+
*/
|
|
19269
|
+
get root() {
|
|
19270
|
+
const node = this.getRootNode();
|
|
19271
|
+
return node instanceof ShadowRoot ? node : this.ownerDocument;
|
|
19272
|
+
}
|
|
19273
|
+
resolveTarget() {
|
|
19274
|
+
const root = this.getRootNode();
|
|
19275
|
+
let found = null;
|
|
19276
|
+
if (this.for) {
|
|
19277
|
+
found = root.querySelector(`#${CSS.escape(this.for)}`);
|
|
19278
|
+
} else {
|
|
19279
|
+
found = this.parentElement?.querySelector("rel-upload") ?? root.querySelector("rel-upload");
|
|
16878
19280
|
}
|
|
19281
|
+
this.target = found;
|
|
19282
|
+
if (found) this.snapshot = found.state;
|
|
16879
19283
|
}
|
|
16880
19284
|
render() {
|
|
16881
|
-
|
|
16882
|
-
|
|
16883
|
-
|
|
16884
|
-
|
|
16885
|
-
|
|
16886
|
-
|
|
16887
|
-
|
|
16888
|
-
|
|
16889
|
-
|
|
19285
|
+
const { phase, canUpload, primaryLabel } = this.snapshot;
|
|
19286
|
+
const busy = phase === "uploading" || phase === "delayed";
|
|
19287
|
+
return import_lit58.html`
|
|
19288
|
+
<div class="actions" part="base">
|
|
19289
|
+
${this.noCancel ? import_lit58.nothing : import_lit58.html`<rel-button part="cancel" variant="secondary" @click=${this.onCancel}>
|
|
19290
|
+
${this.cancelLabel}
|
|
19291
|
+
</rel-button>`}
|
|
19292
|
+
|
|
19293
|
+
<rel-button
|
|
19294
|
+
part="primary"
|
|
19295
|
+
variant="primary"
|
|
19296
|
+
?loading=${busy}
|
|
19297
|
+
?disabled=${!canUpload}
|
|
19298
|
+
aria-label=${busy ? "Uploading" : primaryLabel}
|
|
19299
|
+
@click=${this.onPrimary}
|
|
19300
|
+
>
|
|
19301
|
+
${busy ? import_lit58.nothing : import_lit58.html`${phase === "failed" ? import_lit58.html`<rel-icon name="refresh" size="sm"></rel-icon>` : import_lit58.nothing}${primaryLabel}`}
|
|
19302
|
+
</rel-button>
|
|
16890
19303
|
</div>
|
|
16891
19304
|
`;
|
|
16892
19305
|
}
|
|
16893
19306
|
};
|
|
16894
|
-
|
|
19307
|
+
RelUploadActions.styles = import_lit58.css`
|
|
16895
19308
|
:host {
|
|
16896
|
-
color: var(--rel-text-default);
|
|
16897
|
-
display: inline-block;
|
|
16898
|
-
position: relative;
|
|
16899
|
-
}
|
|
16900
|
-
|
|
16901
|
-
/* Tooltip container */
|
|
16902
|
-
.tooltip-bubble {
|
|
16903
|
-
font-family: var(--rel-body-md-regular-font-family);
|
|
16904
|
-
font-weight: var(--rel-body-md-medium-font-weight);
|
|
16905
|
-
font-size: var(--rel-body-md-regular-font-size);
|
|
16906
|
-
line-height: var(--rel-body-md-regular-line-height);
|
|
16907
|
-
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
16908
|
-
position: absolute;
|
|
16909
|
-
background-color: var(--rel-bg-neutral-inverse-2);
|
|
16910
|
-
color: var(--rel-text-inverse);
|
|
16911
|
-
padding: var(--rel-spacing-xs, 8px)
|
|
16912
|
-
var(--rel-spacing-sm, 12px);
|
|
16913
|
-
border-radius: var(--rel-border-radius-sm, 8px);
|
|
16914
|
-
white-space: nowrap;
|
|
16915
|
-
z-index: 1000;
|
|
16916
|
-
opacity: 0;
|
|
16917
|
-
pointer-events: none;
|
|
16918
|
-
transition: opacity 0.2s ease;
|
|
16919
|
-
overflow: visible;
|
|
16920
|
-
}
|
|
16921
|
-
|
|
16922
|
-
/* Figma Type=Multi Line: wrap inside a bounded bubble */
|
|
16923
|
-
:host([type="multi-line"]) .tooltip-bubble {
|
|
16924
|
-
white-space: normal;
|
|
16925
|
-
max-width: 220px;
|
|
16926
|
-
width: max-content;
|
|
16927
|
-
}
|
|
16928
|
-
|
|
16929
|
-
:host(:hover) .tooltip-bubble,
|
|
16930
|
-
:host(:focus-within) .tooltip-bubble {
|
|
16931
|
-
opacity: 1;
|
|
16932
|
-
}
|
|
16933
|
-
|
|
16934
|
-
/* Positioning */
|
|
16935
|
-
.tooltip-bubble.above {
|
|
16936
|
-
bottom: calc(100% + 6px);
|
|
16937
|
-
left: 50%;
|
|
16938
|
-
transform: translateX(-50%);
|
|
16939
|
-
}
|
|
16940
|
-
|
|
16941
|
-
.tooltip-bubble.below {
|
|
16942
|
-
top: calc(100% + 6px);
|
|
16943
|
-
left: 50%;
|
|
16944
|
-
transform: translateX(-50%);
|
|
16945
|
-
}
|
|
16946
|
-
|
|
16947
|
-
.tooltip-bubble.left {
|
|
16948
|
-
right: calc(100% + 6px);
|
|
16949
|
-
top: 50%;
|
|
16950
|
-
transform: translateY(-50%);
|
|
16951
|
-
}
|
|
16952
|
-
|
|
16953
|
-
.tooltip-bubble.right {
|
|
16954
|
-
left: calc(100% + 6px);
|
|
16955
|
-
top: 50%;
|
|
16956
|
-
transform: translateY(-50%);
|
|
16957
|
-
}
|
|
16958
|
-
|
|
16959
|
-
/* Tail/Arrow */
|
|
16960
|
-
.tooltip-tail {
|
|
16961
|
-
position: absolute;
|
|
16962
|
-
width: 0;
|
|
16963
|
-
height: 0;
|
|
16964
19309
|
display: block;
|
|
19310
|
+
font-family: var(--rel-fonts-font-family);
|
|
16965
19311
|
}
|
|
16966
19312
|
|
|
16967
|
-
.
|
|
16968
|
-
|
|
16969
|
-
|
|
16970
|
-
|
|
16971
|
-
|
|
16972
|
-
border-right: 6px solid transparent;
|
|
16973
|
-
}
|
|
16974
|
-
|
|
16975
|
-
.tooltip-bubble.above .tooltip-tail {
|
|
16976
|
-
bottom: -6px;
|
|
16977
|
-
border-top: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
16978
|
-
border-bottom: none;
|
|
16979
|
-
}
|
|
16980
|
-
|
|
16981
|
-
.tooltip-bubble.below .tooltip-tail {
|
|
16982
|
-
top: -6px;
|
|
16983
|
-
border-bottom: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
16984
|
-
border-top: none;
|
|
16985
|
-
}
|
|
16986
|
-
|
|
16987
|
-
.tooltip-bubble.left .tooltip-tail,
|
|
16988
|
-
.tooltip-bubble.right .tooltip-tail {
|
|
16989
|
-
top: 50%;
|
|
16990
|
-
transform: translateY(-50%);
|
|
16991
|
-
border-top: 6px solid transparent;
|
|
16992
|
-
border-bottom: 6px solid transparent;
|
|
16993
|
-
}
|
|
16994
|
-
|
|
16995
|
-
.tooltip-bubble.left .tooltip-tail {
|
|
16996
|
-
right: -6px;
|
|
16997
|
-
border-left: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
16998
|
-
border-right: none;
|
|
19313
|
+
.actions {
|
|
19314
|
+
display: flex;
|
|
19315
|
+
align-items: center;
|
|
19316
|
+
justify-content: flex-end;
|
|
19317
|
+
gap: var(--rel-spacing-xs, 8px);
|
|
16999
19318
|
}
|
|
17000
19319
|
|
|
17001
|
-
|
|
17002
|
-
|
|
17003
|
-
border-right: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
17004
|
-
border-left: none;
|
|
19320
|
+
rel-button rel-icon {
|
|
19321
|
+
margin-inline-end: var(--rel-spacing-xs, 8px);
|
|
17005
19322
|
}
|
|
17006
19323
|
`;
|
|
17007
19324
|
__decorateClass([
|
|
17008
|
-
(0,
|
|
17009
|
-
],
|
|
19325
|
+
(0, import_decorators58.property)({ type: String })
|
|
19326
|
+
], RelUploadActions.prototype, "for", 2);
|
|
17010
19327
|
__decorateClass([
|
|
17011
|
-
(0,
|
|
17012
|
-
],
|
|
19328
|
+
(0, import_decorators58.property)({ type: String, attribute: "cancel-label" })
|
|
19329
|
+
], RelUploadActions.prototype, "cancelLabel", 2);
|
|
17013
19330
|
__decorateClass([
|
|
17014
|
-
(0,
|
|
17015
|
-
],
|
|
19331
|
+
(0, import_decorators58.property)({ type: Boolean, attribute: "no-cancel" })
|
|
19332
|
+
], RelUploadActions.prototype, "noCancel", 2);
|
|
17016
19333
|
__decorateClass([
|
|
17017
|
-
(0,
|
|
17018
|
-
],
|
|
17019
|
-
|
|
17020
|
-
(0,
|
|
17021
|
-
],
|
|
19334
|
+
(0, import_decorators58.state)()
|
|
19335
|
+
], RelUploadActions.prototype, "snapshot", 2);
|
|
19336
|
+
RelUploadActions = __decorateClass([
|
|
19337
|
+
(0, import_decorators58.customElement)("rel-upload-actions")
|
|
19338
|
+
], RelUploadActions);
|
|
17022
19339
|
|
|
17023
19340
|
// src/index.ts
|
|
17024
19341
|
__reExport(index_exports, require("@reliability-design/icons"), module.exports);
|
|
@@ -17071,6 +19388,7 @@ __reExport(index_exports, require("@reliability-design/icons"), module.exports);
|
|
|
17071
19388
|
RelTab,
|
|
17072
19389
|
RelTabItem,
|
|
17073
19390
|
RelTextfield,
|
|
19391
|
+
RelTimePicker,
|
|
17074
19392
|
RelToast,
|
|
17075
19393
|
RelTooltip,
|
|
17076
19394
|
RelTopbar,
|
|
@@ -17082,6 +19400,8 @@ __reExport(index_exports, require("@reliability-design/icons"), module.exports);
|
|
|
17082
19400
|
RelTopbarMobilePanel,
|
|
17083
19401
|
RelTopbarProfile,
|
|
17084
19402
|
RelTopbarUtilities,
|
|
19403
|
+
RelUpload,
|
|
19404
|
+
RelUploadActions,
|
|
17085
19405
|
...require("@reliability-design/icons")
|
|
17086
19406
|
});
|
|
17087
19407
|
//# sourceMappingURL=index.js.map
|