@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.mjs
CHANGED
|
@@ -681,6 +681,7 @@ var RelTextfield = class extends LitElement3 {
|
|
|
681
681
|
this.internalState = "default";
|
|
682
682
|
this._wasError = false;
|
|
683
683
|
this.uniqueId = `textfield-${Math.random().toString(36).slice(2)}`;
|
|
684
|
+
this._warnedNoName = false;
|
|
684
685
|
}
|
|
685
686
|
get isDisabled() {
|
|
686
687
|
return this.disabled || this.state === "disabled";
|
|
@@ -696,6 +697,15 @@ var RelTextfield = class extends LitElement3 {
|
|
|
696
697
|
get helper() {
|
|
697
698
|
return this.helperText ?? this.supportingText;
|
|
698
699
|
}
|
|
700
|
+
/** labelStyle supersedes the deprecated floatingLabel boolean. */
|
|
701
|
+
get resolvedLabelStyle() {
|
|
702
|
+
if (this.labelStyle) return this.labelStyle;
|
|
703
|
+
return this.floatingLabel ? "floating" : "default";
|
|
704
|
+
}
|
|
705
|
+
/** True when no visible label renders and the input needs an ARIA name. */
|
|
706
|
+
get labelHidden() {
|
|
707
|
+
return this.resolvedLabelStyle === "none";
|
|
708
|
+
}
|
|
699
709
|
connectedCallback() {
|
|
700
710
|
super.connectedCallback();
|
|
701
711
|
const leftIcon = this.getAttribute("lefticon") || this.getAttribute("leftIcon");
|
|
@@ -703,6 +713,20 @@ var RelTextfield = class extends LitElement3 {
|
|
|
703
713
|
const rightIcon = this.getAttribute("righticon") || this.getAttribute("rightIcon");
|
|
704
714
|
if (rightIcon) this.rightIcon = rightIcon;
|
|
705
715
|
}
|
|
716
|
+
willUpdate(changed) {
|
|
717
|
+
if (changed.has("labelStyle") && this.labelStyle) {
|
|
718
|
+
this.floatingLabel = this.labelStyle === "floating";
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
updated() {
|
|
722
|
+
if (this._warnedNoName || !this.labelHidden) return;
|
|
723
|
+
if (this.fieldAriaLabel || this.fieldAriaLabelledby || this.getAttribute("aria-label")) return;
|
|
724
|
+
this._warnedNoName = true;
|
|
725
|
+
console.warn(
|
|
726
|
+
'<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.',
|
|
727
|
+
this
|
|
728
|
+
);
|
|
729
|
+
}
|
|
706
730
|
/** Focuses the inner input/textarea (the host itself is not focusable). */
|
|
707
731
|
focus(options) {
|
|
708
732
|
this.renderRoot.querySelector("input, textarea")?.focus(options);
|
|
@@ -770,15 +794,20 @@ var RelTextfield = class extends LitElement3 {
|
|
|
770
794
|
!this.multiline && leadingIcon ? "has-left-icon" : "",
|
|
771
795
|
!this.multiline && this.rightIcon && this.variant !== "legend" ? "has-right-icon" : ""
|
|
772
796
|
].filter(Boolean).join(" ");
|
|
797
|
+
const labelStyle = this.resolvedLabelStyle;
|
|
798
|
+
const labelHidden = labelStyle === "none";
|
|
773
799
|
const labelTemplate = (extraClass = "") => {
|
|
774
|
-
if (!this.label) return "";
|
|
800
|
+
if (!this.label || labelHidden) return "";
|
|
775
801
|
return html3`<label
|
|
776
802
|
part="label"
|
|
777
803
|
class="textfield-label${extraClass}"
|
|
778
804
|
for="${inputId}"
|
|
779
805
|
>${this.label}${this.required ? html3`<span class="required-mark" aria-hidden="true">*</span>` : ""}</label>`;
|
|
780
806
|
};
|
|
781
|
-
const
|
|
807
|
+
const legendLabelledby = this.variant === "legend" && this.label && !labelHidden ? legendId : void 0;
|
|
808
|
+
const ariaLabelledby = this.fieldAriaLabelledby ?? legendLabelledby;
|
|
809
|
+
const hasVisibleLabel = !labelHidden && !!this.label;
|
|
810
|
+
const ariaLabel = !hasVisibleLabel && !ariaLabelledby ? this.fieldAriaLabel : void 0;
|
|
782
811
|
const inputTemplate = this.multiline ? html3`<textarea part="input"
|
|
783
812
|
id="${inputId}"
|
|
784
813
|
class="${inputClasses}"
|
|
@@ -792,6 +821,7 @@ var RelTextfield = class extends LitElement3 {
|
|
|
792
821
|
?required="${this.required}"
|
|
793
822
|
aria-invalid="${isError}"
|
|
794
823
|
aria-required="${this.required}"
|
|
824
|
+
aria-label="${ariaLabel ?? nothing}"
|
|
795
825
|
aria-labelledby="${ariaLabelledby ?? nothing}"
|
|
796
826
|
aria-describedby="${describedById ?? nothing}"
|
|
797
827
|
@focus="${this.handleFocus}"
|
|
@@ -810,6 +840,7 @@ var RelTextfield = class extends LitElement3 {
|
|
|
810
840
|
?required="${this.required}"
|
|
811
841
|
aria-invalid="${isError}"
|
|
812
842
|
aria-required="${this.required}"
|
|
843
|
+
aria-label="${ariaLabel ?? nothing}"
|
|
813
844
|
aria-labelledby="${ariaLabelledby ?? nothing}"
|
|
814
845
|
aria-describedby="${describedById ?? nothing}"
|
|
815
846
|
@focus="${this.handleFocus}"
|
|
@@ -831,7 +862,7 @@ var RelTextfield = class extends LitElement3 {
|
|
|
831
862
|
const fieldsetClass = ["field-fieldset", this.multiline ? "multiline" : ""].filter(Boolean).join(" ");
|
|
832
863
|
return html3`
|
|
833
864
|
<div class="${wrapperClasses}">
|
|
834
|
-
<fieldset part="control" class="${fieldsetClass}"><legend class="field-legend" id="${legendId}">${this.label ?? ""}${this.required ? html3`<span class="required-mark" aria-hidden="true">*</span>` : ""}</legend>
|
|
865
|
+
<fieldset part="control" class="${fieldsetClass}"><legend class="field-legend" id="${legendId}">${labelHidden ? "" : this.label ?? ""}${this.required && !labelHidden ? html3`<span class="required-mark" aria-hidden="true">*</span>` : ""}</legend>
|
|
835
866
|
${!this.multiline ? leadingIconTemplate : ""}
|
|
836
867
|
${inputTemplate}
|
|
837
868
|
</fieldset>
|
|
@@ -841,10 +872,10 @@ var RelTextfield = class extends LitElement3 {
|
|
|
841
872
|
}
|
|
842
873
|
return html3`
|
|
843
874
|
<div class="${wrapperClasses}">
|
|
844
|
-
${
|
|
875
|
+
${labelHidden ? html3`<div class="label-reserve" aria-hidden="true"></div>` : labelStyle !== "floating" ? labelTemplate() : ""}
|
|
845
876
|
|
|
846
877
|
<div part="control" class="${["textfield-container", this.isDisabled ? "disabled" : "", this.multiline ? "multiline" : ""].filter(Boolean).join(" ")}">
|
|
847
|
-
${
|
|
878
|
+
${labelStyle === "floating" ? labelTemplate(leadingIcon && !this.multiline ? " has-left-icon" : "") : ""}
|
|
848
879
|
${!this.multiline ? leadingIconTemplate : ""}
|
|
849
880
|
|
|
850
881
|
${inputTemplate}
|
|
@@ -903,6 +934,22 @@ RelTextfield.styles = css3`
|
|
|
903
934
|
margin-left: 2px;
|
|
904
935
|
}
|
|
905
936
|
|
|
937
|
+
/* ── labelStyle="none" ──
|
|
938
|
+
No label renders. An empty spacer holds the label row's full height so the
|
|
939
|
+
input box still bottom-aligns with labelled siblings in a form row.
|
|
940
|
+
The reserve covers label line-height + the wrapper gap, and the negative
|
|
941
|
+
margin cancels the flex gap that would otherwise sit below it — so
|
|
942
|
+
--rel-field-label-reserve: 0 collapses the row to genuinely zero
|
|
943
|
+
(toolbars, table cells), not just to the leftover gap. */
|
|
944
|
+
.label-reserve {
|
|
945
|
+
flex: none;
|
|
946
|
+
height: var(
|
|
947
|
+
--rel-field-label-reserve,
|
|
948
|
+
calc(var(--rel-body-sm-medium-line-height) + var(--rel-spacing-2xs))
|
|
949
|
+
);
|
|
950
|
+
margin-bottom: calc(-1 * var(--rel-spacing-2xs));
|
|
951
|
+
}
|
|
952
|
+
|
|
906
953
|
/* Floating label variant */
|
|
907
954
|
:host([floatinglabel]) .textfield-wrapper {
|
|
908
955
|
gap: 0;
|
|
@@ -1279,6 +1326,12 @@ RelTextfield.styles = css3`
|
|
|
1279
1326
|
box-sizing: border-box;
|
|
1280
1327
|
}
|
|
1281
1328
|
|
|
1329
|
+
/* labelStyle="none" + legend: nothing to seat in the notch, so drop the
|
|
1330
|
+
legend entirely and let the top border run continuous. */
|
|
1331
|
+
:host([label-style="none"][variant="legend"]) .field-legend {
|
|
1332
|
+
display: none;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1282
1335
|
/* Prefix icon (legend): vertically centered; input inset via has-left-icon */
|
|
1283
1336
|
:host([variant="legend"]) .icon.left {
|
|
1284
1337
|
top: 50%;
|
|
@@ -1394,9 +1447,18 @@ __decorateClass([
|
|
|
1394
1447
|
__decorateClass([
|
|
1395
1448
|
property3({ type: String })
|
|
1396
1449
|
], RelTextfield.prototype, "size", 2);
|
|
1450
|
+
__decorateClass([
|
|
1451
|
+
property3({ type: String, attribute: "label-style", reflect: true })
|
|
1452
|
+
], RelTextfield.prototype, "labelStyle", 2);
|
|
1397
1453
|
__decorateClass([
|
|
1398
1454
|
property3({ type: Boolean, reflect: true })
|
|
1399
1455
|
], RelTextfield.prototype, "floatingLabel", 2);
|
|
1456
|
+
__decorateClass([
|
|
1457
|
+
property3({ type: String, attribute: "field-aria-label" })
|
|
1458
|
+
], RelTextfield.prototype, "fieldAriaLabel", 2);
|
|
1459
|
+
__decorateClass([
|
|
1460
|
+
property3({ type: String, attribute: "field-aria-labelledby" })
|
|
1461
|
+
], RelTextfield.prototype, "fieldAriaLabelledby", 2);
|
|
1400
1462
|
__decorateClass([
|
|
1401
1463
|
property3({ type: String, reflect: true })
|
|
1402
1464
|
], RelTextfield.prototype, "variant", 2);
|
|
@@ -1506,12 +1568,26 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1506
1568
|
if (left + t.width > window.innerWidth - 8) {
|
|
1507
1569
|
left = Math.max(8, window.innerWidth - t.width - 8);
|
|
1508
1570
|
}
|
|
1571
|
+
this.panelEl.style.top = `${top}px`;
|
|
1572
|
+
this.panelEl.style.left = `${left}px`;
|
|
1573
|
+
this.panelEl.style.width = `${t.width}px`;
|
|
1574
|
+
const applied = this.panelEl.getBoundingClientRect();
|
|
1575
|
+
top -= applied.top - top;
|
|
1576
|
+
left -= applied.left - left;
|
|
1577
|
+
this.panelEl.style.top = `${top}px`;
|
|
1578
|
+
this.panelEl.style.left = `${left}px`;
|
|
1509
1579
|
this.panelStyle = `top:${top}px;left:${left}px;width:${t.width}px;`;
|
|
1510
1580
|
};
|
|
1511
1581
|
}
|
|
1582
|
+
/** Name used for the suggestion panel — the visible label, or fieldAriaLabel
|
|
1583
|
+
* once labelStyle="none" hides it. Mirrors rel-textfield's own resolution. */
|
|
1584
|
+
get accessibleName() {
|
|
1585
|
+
return (this.labelStyle === "none" ? this.fieldAriaLabel : this.label) || "";
|
|
1586
|
+
}
|
|
1512
1587
|
disconnectedCallback() {
|
|
1513
1588
|
super.disconnectedCallback();
|
|
1514
1589
|
this.removeGlobalListeners();
|
|
1590
|
+
this.hideNativePopover();
|
|
1515
1591
|
clearTimeout(this.debounceTimer);
|
|
1516
1592
|
}
|
|
1517
1593
|
willUpdate(changed) {
|
|
@@ -1571,13 +1647,40 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1571
1647
|
this.open = true;
|
|
1572
1648
|
this.highlightedIndex = -1;
|
|
1573
1649
|
this.addGlobalListeners();
|
|
1574
|
-
requestAnimationFrame(() =>
|
|
1650
|
+
requestAnimationFrame(() => {
|
|
1651
|
+
this.showNativePopover();
|
|
1652
|
+
this.updatePosition();
|
|
1653
|
+
});
|
|
1575
1654
|
}
|
|
1576
1655
|
closePanel() {
|
|
1577
1656
|
if (!this.open) return;
|
|
1578
1657
|
this.open = false;
|
|
1579
1658
|
this.highlightedIndex = -1;
|
|
1580
1659
|
this.removeGlobalListeners();
|
|
1660
|
+
this.hideNativePopover();
|
|
1661
|
+
}
|
|
1662
|
+
/* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
1663
|
+
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
1664
|
+
* descendants, breaking viewport-based coordinates and letting the
|
|
1665
|
+
* panel's overflow:hidden clip the suggestions. Promoting the panel to
|
|
1666
|
+
* the browser top layer sidesteps both — same fix as rel-popover.
|
|
1667
|
+
* `manual` keeps light-dismiss and Escape handling under this
|
|
1668
|
+
* component's own control. */
|
|
1669
|
+
showNativePopover() {
|
|
1670
|
+
const p = this.panelEl;
|
|
1671
|
+
if (!p || typeof p.showPopover !== "function") return;
|
|
1672
|
+
try {
|
|
1673
|
+
if (!p.matches(":popover-open")) p.showPopover();
|
|
1674
|
+
} catch {
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
hideNativePopover() {
|
|
1678
|
+
const p = this.panelEl;
|
|
1679
|
+
if (!p || typeof p.hidePopover !== "function") return;
|
|
1680
|
+
try {
|
|
1681
|
+
if (p.matches(":popover-open")) p.hidePopover();
|
|
1682
|
+
} catch {
|
|
1683
|
+
}
|
|
1581
1684
|
}
|
|
1582
1685
|
/** Re-evaluates whether the panel should be showing for the current state. */
|
|
1583
1686
|
syncPanel() {
|
|
@@ -1613,17 +1716,17 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1613
1716
|
this.syncPanel();
|
|
1614
1717
|
}
|
|
1615
1718
|
}
|
|
1616
|
-
async runSearch(
|
|
1719
|
+
async runSearch(query12) {
|
|
1617
1720
|
const seq = ++this.searchSeq;
|
|
1618
1721
|
try {
|
|
1619
|
-
const results = await this.searchFn(
|
|
1722
|
+
const results = await this.searchFn(query12);
|
|
1620
1723
|
if (seq !== this.searchSeq) return;
|
|
1621
1724
|
this.remoteItems = results ?? [];
|
|
1622
1725
|
} catch (err) {
|
|
1623
1726
|
if (seq !== this.searchSeq) return;
|
|
1624
1727
|
this.remoteItems = [];
|
|
1625
1728
|
this.dispatchEvent(
|
|
1626
|
-
new CustomEvent("rel-search-error", { detail: { query:
|
|
1729
|
+
new CustomEvent("rel-search-error", { detail: { query: query12, error: err }, bubbles: true, composed: true })
|
|
1627
1730
|
);
|
|
1628
1731
|
} finally {
|
|
1629
1732
|
if (seq === this.searchSeq) {
|
|
@@ -1769,7 +1872,10 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1769
1872
|
.value="${this.inputText}"
|
|
1770
1873
|
.size="${this.size}"
|
|
1771
1874
|
.variant="${this.variant}"
|
|
1875
|
+
.labelStyle="${this.labelStyle}"
|
|
1772
1876
|
.floatingLabel="${this.floatingLabel}"
|
|
1877
|
+
.fieldAriaLabel="${this.fieldAriaLabel}"
|
|
1878
|
+
.fieldAriaLabelledby="${this.fieldAriaLabelledby}"
|
|
1773
1879
|
.prefixIcon="${this.prefixIcon}"
|
|
1774
1880
|
.disabled="${this.disabled}"
|
|
1775
1881
|
.readonly="${this.readonly}"
|
|
@@ -1790,8 +1896,9 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1790
1896
|
id="${panelId}"
|
|
1791
1897
|
part="panel list"
|
|
1792
1898
|
class="ac-panel"
|
|
1899
|
+
popover="manual"
|
|
1793
1900
|
role="listbox"
|
|
1794
|
-
aria-label="${this.
|
|
1901
|
+
aria-label="${this.accessibleName || "Suggestions"}"
|
|
1795
1902
|
style="${this.panelStyle}"
|
|
1796
1903
|
?data-open="${this.open}"
|
|
1797
1904
|
>
|
|
@@ -1844,6 +1951,15 @@ RelAutocomplete.styles = css4`
|
|
|
1844
1951
|
position: fixed;
|
|
1845
1952
|
z-index: 1001;
|
|
1846
1953
|
display: none;
|
|
1954
|
+
/* Reset native [popover] UA styles (the panel is promoted to the
|
|
1955
|
+
top layer so transformed ancestors — e.g. a centered rel-modal
|
|
1956
|
+
panel — can't hijack its containing block or clip it). */
|
|
1957
|
+
inset: auto;
|
|
1958
|
+
margin: 0;
|
|
1959
|
+
padding: 0;
|
|
1960
|
+
width: auto;
|
|
1961
|
+
height: auto;
|
|
1962
|
+
color: inherit;
|
|
1847
1963
|
box-sizing: border-box;
|
|
1848
1964
|
background: var(--rel-autocomplete-bg, var(--rel-bg-neutral));
|
|
1849
1965
|
border: 1px solid var(--rel-autocomplete-menu-border, var(--rel-border-default));
|
|
@@ -1970,9 +2086,18 @@ __decorateClass([
|
|
|
1970
2086
|
__decorateClass([
|
|
1971
2087
|
property4({ type: String })
|
|
1972
2088
|
], RelAutocomplete.prototype, "variant", 2);
|
|
2089
|
+
__decorateClass([
|
|
2090
|
+
property4({ type: String, attribute: "label-style", reflect: true })
|
|
2091
|
+
], RelAutocomplete.prototype, "labelStyle", 2);
|
|
1973
2092
|
__decorateClass([
|
|
1974
2093
|
property4({ type: Boolean, attribute: "floating-label" })
|
|
1975
2094
|
], RelAutocomplete.prototype, "floatingLabel", 2);
|
|
2095
|
+
__decorateClass([
|
|
2096
|
+
property4({ type: String, attribute: "field-aria-label" })
|
|
2097
|
+
], RelAutocomplete.prototype, "fieldAriaLabel", 2);
|
|
2098
|
+
__decorateClass([
|
|
2099
|
+
property4({ type: String, attribute: "field-aria-labelledby" })
|
|
2100
|
+
], RelAutocomplete.prototype, "fieldAriaLabelledby", 2);
|
|
1976
2101
|
__decorateClass([
|
|
1977
2102
|
property4({ type: String, attribute: "prefix-icon" })
|
|
1978
2103
|
], RelAutocomplete.prototype, "prefixIcon", 2);
|
|
@@ -2684,8 +2809,8 @@ RelButton.styles = css8`
|
|
|
2684
2809
|
pointer-events:none on the HOST would make it transparent to the
|
|
2685
2810
|
pointer and suppress the cursor entirely. */
|
|
2686
2811
|
cursor: not-allowed;
|
|
2687
|
-
/* The disabled tokens are full-contrast pairs (light:
|
|
2688
|
-
|
|
2812
|
+
/* The disabled tokens are full-contrast pairs (light: near-black on
|
|
2813
|
+
gray-300, dark: white on gray-700), so on their own they render a
|
|
2689
2814
|
crisp button that reads as enabled. Dulling happens here instead —
|
|
2690
2815
|
same opacity mechanism the rest of the library uses. */
|
|
2691
2816
|
opacity: 0.5;
|
|
@@ -4308,6 +4433,9 @@ var RelCheckbox = class extends LitElement13 {
|
|
|
4308
4433
|
this.state = "default";
|
|
4309
4434
|
this.status = "unchecked";
|
|
4310
4435
|
this.value = "";
|
|
4436
|
+
this.size = "medium";
|
|
4437
|
+
this.density = "compact";
|
|
4438
|
+
this.alignment = "leading";
|
|
4311
4439
|
this._onChange = () => {
|
|
4312
4440
|
};
|
|
4313
4441
|
this._onTouched = () => {
|
|
@@ -4319,6 +4447,17 @@ var RelCheckbox = class extends LitElement13 {
|
|
|
4319
4447
|
set checked(val) {
|
|
4320
4448
|
this.status = val ? "checked" : "unchecked";
|
|
4321
4449
|
}
|
|
4450
|
+
willUpdate(changed) {
|
|
4451
|
+
if ((changed.has("alignment") || changed.has("density")) && this.alignment === "trailing" && this.density === "compact") {
|
|
4452
|
+
console.warn(
|
|
4453
|
+
'[rel-checkbox] density="compact" has no trailing layout; rendering comfortable density.'
|
|
4454
|
+
);
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
/** Trailing has no compact layout in the spec — trailing wins. */
|
|
4458
|
+
get effectiveDensity() {
|
|
4459
|
+
return this.alignment === "trailing" ? "comfortable" : this.density;
|
|
4460
|
+
}
|
|
4322
4461
|
toggle() {
|
|
4323
4462
|
if (this.state === "disabled") return;
|
|
4324
4463
|
this.status = this.status === "checked" ? "unchecked" : "checked";
|
|
@@ -4366,12 +4505,22 @@ var RelCheckbox = class extends LitElement13 {
|
|
|
4366
4505
|
</svg>
|
|
4367
4506
|
</span>
|
|
4368
4507
|
|
|
4369
|
-
|
|
4508
|
+
<span class="content">
|
|
4509
|
+
<span class="label-line">
|
|
4510
|
+
${this.icon ? html13`<rel-icon
|
|
4511
|
+
name="${this.icon}"
|
|
4512
|
+
size="${this.size === "small" ? "sm" : "md"}"
|
|
4513
|
+
class="icon-prefix"
|
|
4514
|
+
></rel-icon>` : null}
|
|
4370
4515
|
|
|
4371
|
-
|
|
4516
|
+
<span part="label" class="label">${this.label}</span>
|
|
4517
|
+
</span>
|
|
4518
|
+
|
|
4519
|
+
${this.effectiveDensity === "comfortable" && this.supportingtext ? html13`<span part="helper" class="text">${this.supportingtext}</span>` : null}
|
|
4520
|
+
</span>
|
|
4372
4521
|
</label>
|
|
4373
4522
|
|
|
4374
|
-
${this.supportingtext ? html13`<span part="helper" class="text">${this.supportingtext}</span>` : null}
|
|
4523
|
+
${this.effectiveDensity === "compact" && this.supportingtext ? html13`<span part="helper" class="text text-below">${this.supportingtext}</span>` : null}
|
|
4375
4524
|
</div>
|
|
4376
4525
|
`;
|
|
4377
4526
|
}
|
|
@@ -4404,6 +4553,29 @@ RelCheckbox.styles = css13`
|
|
|
4404
4553
|
cursor: pointer;
|
|
4405
4554
|
}
|
|
4406
4555
|
|
|
4556
|
+
/* Trailing alignment — box moves to the far edge, label stack to the
|
|
4557
|
+
start. row-reverse keeps the box first in DOM order (input + .box
|
|
4558
|
+
adjacency drives the focus ring) while space-between stretches the
|
|
4559
|
+
row to its container. */
|
|
4560
|
+
:host([alignment="trailing"]) .row {
|
|
4561
|
+
flex-direction: row-reverse;
|
|
4562
|
+
justify-content: space-between;
|
|
4563
|
+
}
|
|
4564
|
+
|
|
4565
|
+
/* Comfortable density — label and supporting text stack beside the box. */
|
|
4566
|
+
.content {
|
|
4567
|
+
display: flex;
|
|
4568
|
+
flex-direction: column;
|
|
4569
|
+
gap: var(--rel-spacing-3xs);
|
|
4570
|
+
min-width: 0;
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4573
|
+
.label-line {
|
|
4574
|
+
display: flex;
|
|
4575
|
+
align-items: center;
|
|
4576
|
+
gap: var(--rel-spacing-xs);
|
|
4577
|
+
}
|
|
4578
|
+
|
|
4407
4579
|
input {
|
|
4408
4580
|
position: absolute;
|
|
4409
4581
|
inset: 0;
|
|
@@ -4546,7 +4718,6 @@ RelCheckbox.styles = css13`
|
|
|
4546
4718
|
|
|
4547
4719
|
.text {
|
|
4548
4720
|
color: var(--rel-text-caption);
|
|
4549
|
-
padding-left: 28px;
|
|
4550
4721
|
|
|
4551
4722
|
font-family: var(--rel-body-sm-regular-font-family);
|
|
4552
4723
|
|
|
@@ -4559,6 +4730,44 @@ RelCheckbox.styles = css13`
|
|
|
4559
4730
|
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
4560
4731
|
|
|
4561
4732
|
}
|
|
4733
|
+
|
|
4734
|
+
/* Compact density — supporting text sits under the whole row, indented
|
|
4735
|
+
past the box (box width + row gap, matching the pre-existing 28px). */
|
|
4736
|
+
.text-below {
|
|
4737
|
+
padding-left: 28px;
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4740
|
+
/* Size variant (medium is the base scale above). */
|
|
4741
|
+
:host([size="small"]) .box {
|
|
4742
|
+
width: 14px;
|
|
4743
|
+
height: 14px;
|
|
4744
|
+
border-radius: 3px;
|
|
4745
|
+
}
|
|
4746
|
+
|
|
4747
|
+
:host([size="small"]) svg {
|
|
4748
|
+
width: 10px;
|
|
4749
|
+
height: 10px;
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4752
|
+
:host([size="small"]) .label {
|
|
4753
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
4754
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
4755
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
4756
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
4757
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
4758
|
+
}
|
|
4759
|
+
|
|
4760
|
+
:host([size="small"]) .text {
|
|
4761
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
4762
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
4763
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
4764
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
4765
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
4766
|
+
}
|
|
4767
|
+
|
|
4768
|
+
:host([size="small"]) .text-below {
|
|
4769
|
+
padding-left: 24px;
|
|
4770
|
+
}
|
|
4562
4771
|
`;
|
|
4563
4772
|
__decorateClass([
|
|
4564
4773
|
property13({ type: String })
|
|
@@ -4578,6 +4787,15 @@ __decorateClass([
|
|
|
4578
4787
|
__decorateClass([
|
|
4579
4788
|
property13({ type: String })
|
|
4580
4789
|
], RelCheckbox.prototype, "value", 2);
|
|
4790
|
+
__decorateClass([
|
|
4791
|
+
property13({ type: String, reflect: true })
|
|
4792
|
+
], RelCheckbox.prototype, "size", 2);
|
|
4793
|
+
__decorateClass([
|
|
4794
|
+
property13({ type: String, reflect: true })
|
|
4795
|
+
], RelCheckbox.prototype, "density", 2);
|
|
4796
|
+
__decorateClass([
|
|
4797
|
+
property13({ type: String, reflect: true })
|
|
4798
|
+
], RelCheckbox.prototype, "alignment", 2);
|
|
4581
4799
|
RelCheckbox = __decorateClass([
|
|
4582
4800
|
customElement13("rel-checkbox")
|
|
4583
4801
|
], RelCheckbox);
|
|
@@ -5709,6 +5927,31 @@ var RelRangePicker = class extends LitElement15 {
|
|
|
5709
5927
|
this.viewMonth = (/* @__PURE__ */ new Date()).getMonth();
|
|
5710
5928
|
this.isSelectingRange = false;
|
|
5711
5929
|
this.hoverDate = null;
|
|
5930
|
+
this.onReposition = () => {
|
|
5931
|
+
if (this.open && this.mode === "popover") this.updatePopoverPosition();
|
|
5932
|
+
};
|
|
5933
|
+
this.updatePopoverPosition = () => {
|
|
5934
|
+
const panel = this.popoverEl;
|
|
5935
|
+
if (!panel || !this.open || this.mode !== "popover") return;
|
|
5936
|
+
const t = this.getBoundingClientRect();
|
|
5937
|
+
const c = panel.getBoundingClientRect();
|
|
5938
|
+
let top = t.bottom + 4;
|
|
5939
|
+
if (top + c.height > window.innerHeight - 8) {
|
|
5940
|
+
top = Math.max(8, t.top - c.height - 4);
|
|
5941
|
+
}
|
|
5942
|
+
let left = t.left;
|
|
5943
|
+
if (left + c.width > window.innerWidth - 8) {
|
|
5944
|
+
left = Math.max(8, window.innerWidth - c.width - 8);
|
|
5945
|
+
}
|
|
5946
|
+
panel.style.position = "fixed";
|
|
5947
|
+
panel.style.top = `${top}px`;
|
|
5948
|
+
panel.style.left = `${left}px`;
|
|
5949
|
+
const applied = panel.getBoundingClientRect();
|
|
5950
|
+
top -= applied.top - top;
|
|
5951
|
+
left -= applied.left - left;
|
|
5952
|
+
panel.style.top = `${top}px`;
|
|
5953
|
+
panel.style.left = `${left}px`;
|
|
5954
|
+
};
|
|
5712
5955
|
// ── Open / close ──
|
|
5713
5956
|
this.handleClickOutside = (e) => {
|
|
5714
5957
|
if (!this.open) return;
|
|
@@ -5751,14 +5994,64 @@ var RelRangePicker = class extends LitElement15 {
|
|
|
5751
5994
|
super.disconnectedCallback();
|
|
5752
5995
|
document.removeEventListener("click", this.handleClickOutside);
|
|
5753
5996
|
document.removeEventListener("keydown", this.handleGlobalKeydown);
|
|
5997
|
+
window.removeEventListener("resize", this.onReposition);
|
|
5998
|
+
window.removeEventListener("scroll", this.onReposition, true);
|
|
5999
|
+
this.hideTopLayer();
|
|
5754
6000
|
}
|
|
5755
|
-
updated() {
|
|
6001
|
+
updated(changed) {
|
|
5756
6002
|
this.style.width = this.width;
|
|
5757
6003
|
this.style.minWidth = this.minWidth;
|
|
5758
6004
|
this.style.maxWidth = this.maxWidth;
|
|
5759
6005
|
this.style.height = this.height;
|
|
5760
6006
|
this.style.minHeight = this.minHeight;
|
|
5761
6007
|
this.style.maxHeight = this.maxHeight;
|
|
6008
|
+
if (changed.has("open")) {
|
|
6009
|
+
if (this.open) {
|
|
6010
|
+
this.showTopLayer();
|
|
6011
|
+
window.addEventListener("resize", this.onReposition);
|
|
6012
|
+
window.addEventListener("scroll", this.onReposition, true);
|
|
6013
|
+
} else {
|
|
6014
|
+
window.removeEventListener("resize", this.onReposition);
|
|
6015
|
+
window.removeEventListener("scroll", this.onReposition, true);
|
|
6016
|
+
this.hideTopLayer();
|
|
6017
|
+
}
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
/* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
6021
|
+
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
6022
|
+
* descendants, and its overflow:hidden clips position:absolute ones —
|
|
6023
|
+
* either way the picker shell breaks inside a modal. Promoting shell
|
|
6024
|
+
* (and popup-mode backdrop) to the browser top layer sidesteps both —
|
|
6025
|
+
* same fix as rel-popover / rel-autocomplete / rel-select. `manual`
|
|
6026
|
+
* keeps outside-click and Escape handling under this component's own
|
|
6027
|
+
* control. While in the top layer the CSS anchoring (absolute, below
|
|
6028
|
+
* the trigger) is meaningless, so popover mode gets inline fixed
|
|
6029
|
+
* coordinates from updatePopoverPosition(). */
|
|
6030
|
+
showTopLayer() {
|
|
6031
|
+
for (const el of [this.mode === "popup" ? this.backdropEl : void 0, this.popoverEl]) {
|
|
6032
|
+
if (!el || typeof el.showPopover !== "function") continue;
|
|
6033
|
+
try {
|
|
6034
|
+
if (!el.matches(":popover-open")) el.showPopover();
|
|
6035
|
+
} catch {
|
|
6036
|
+
}
|
|
6037
|
+
}
|
|
6038
|
+
if (this.mode === "popover") {
|
|
6039
|
+
requestAnimationFrame(() => this.updatePopoverPosition());
|
|
6040
|
+
}
|
|
6041
|
+
}
|
|
6042
|
+
hideTopLayer() {
|
|
6043
|
+
for (const el of [this.popoverEl, this.backdropEl]) {
|
|
6044
|
+
if (!el || typeof el.hidePopover !== "function") continue;
|
|
6045
|
+
try {
|
|
6046
|
+
if (el.matches(":popover-open")) el.hidePopover();
|
|
6047
|
+
} catch {
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
if (this.popoverEl) {
|
|
6051
|
+
this.popoverEl.style.position = "";
|
|
6052
|
+
this.popoverEl.style.top = "";
|
|
6053
|
+
this.popoverEl.style.left = "";
|
|
6054
|
+
}
|
|
5762
6055
|
}
|
|
5763
6056
|
toggleOpen() {
|
|
5764
6057
|
if (this.disabled) return;
|
|
@@ -6114,10 +6407,11 @@ var RelRangePicker = class extends LitElement15 {
|
|
|
6114
6407
|
</button>
|
|
6115
6408
|
`}
|
|
6116
6409
|
|
|
6117
|
-
${isPopup && this.open ? html15`<div part="overlay" class="backdrop" @click=${this.cancelPopover}></div>` : nothing7}
|
|
6410
|
+
${isPopup && this.open ? html15`<div part="overlay" class="backdrop" popover="manual" @click=${this.cancelPopover}></div>` : nothing7}
|
|
6118
6411
|
|
|
6119
6412
|
<div
|
|
6120
6413
|
part="panel" class="popover ${this.open ? "open" : ""} ${isPopup ? "popup" : ""}"
|
|
6414
|
+
popover="manual"
|
|
6121
6415
|
role="dialog"
|
|
6122
6416
|
aria-label="Select date range"
|
|
6123
6417
|
>
|
|
@@ -6328,6 +6622,17 @@ RelRangePicker.styles = css15`
|
|
|
6328
6622
|
|
|
6329
6623
|
/* ── Popover shell ── */
|
|
6330
6624
|
.popover {
|
|
6625
|
+
/* Reset native [popover] UA styles first (the shell is promoted to
|
|
6626
|
+
the top layer so transformed / overflow:hidden ancestors — e.g. a
|
|
6627
|
+
centered rel-modal panel — can't hijack or clip it). While in the
|
|
6628
|
+
top layer, JS overrides the anchored position below with inline
|
|
6629
|
+
fixed coordinates; the CSS anchoring is the no-Popover-API
|
|
6630
|
+
fallback. */
|
|
6631
|
+
margin: 0;
|
|
6632
|
+
padding: 0;
|
|
6633
|
+
width: auto;
|
|
6634
|
+
height: auto;
|
|
6635
|
+
color: inherit;
|
|
6331
6636
|
position: absolute;
|
|
6332
6637
|
top: calc(100% + 4px);
|
|
6333
6638
|
left: 0;
|
|
@@ -6352,6 +6657,13 @@ RelRangePicker.styles = css15`
|
|
|
6352
6657
|
.backdrop {
|
|
6353
6658
|
position: fixed;
|
|
6354
6659
|
inset: 0;
|
|
6660
|
+
/* Reset native [popover] UA styles — the backdrop is promoted to the
|
|
6661
|
+
top layer right before the panel so the panel stacks above it. */
|
|
6662
|
+
margin: 0;
|
|
6663
|
+
padding: 0;
|
|
6664
|
+
width: auto;
|
|
6665
|
+
height: auto;
|
|
6666
|
+
border: none;
|
|
6355
6667
|
z-index: 2147483646;
|
|
6356
6668
|
background: rgba(0,0,0,0.4);
|
|
6357
6669
|
}
|
|
@@ -6725,6 +7037,12 @@ __decorateClass([
|
|
|
6725
7037
|
__decorateClass([
|
|
6726
7038
|
query4(".trigger-button")
|
|
6727
7039
|
], RelRangePicker.prototype, "triggerButton", 2);
|
|
7040
|
+
__decorateClass([
|
|
7041
|
+
query4(".popover")
|
|
7042
|
+
], RelRangePicker.prototype, "popoverEl", 2);
|
|
7043
|
+
__decorateClass([
|
|
7044
|
+
query4(".backdrop")
|
|
7045
|
+
], RelRangePicker.prototype, "backdropEl", 2);
|
|
6728
7046
|
RelRangePicker = __decorateClass([
|
|
6729
7047
|
customElement15("rel-range-picker")
|
|
6730
7048
|
], RelRangePicker);
|
|
@@ -8230,6 +8548,8 @@ var RelRadio = class extends LitElement23 {
|
|
|
8230
8548
|
super(...arguments);
|
|
8231
8549
|
this.options = [];
|
|
8232
8550
|
this.name = "radio-group";
|
|
8551
|
+
this.orientation = "vertical";
|
|
8552
|
+
this.size = "medium";
|
|
8233
8553
|
this.selectedValue = null;
|
|
8234
8554
|
this.disabled = false;
|
|
8235
8555
|
this.error = false;
|
|
@@ -8295,7 +8615,12 @@ var RelRadio = class extends LitElement23 {
|
|
|
8295
8615
|
}
|
|
8296
8616
|
render() {
|
|
8297
8617
|
return html23`
|
|
8298
|
-
<div
|
|
8618
|
+
<div
|
|
8619
|
+
part="base"
|
|
8620
|
+
class="rel-radio-group"
|
|
8621
|
+
role="radiogroup"
|
|
8622
|
+
aria-orientation="${this.orientation}"
|
|
8623
|
+
>
|
|
8299
8624
|
${this.options.map((o) => this.renderOption(o))}
|
|
8300
8625
|
</div>
|
|
8301
8626
|
${this.error && this.errorMessage ? html23`<div class="error-message">${this.errorMessage}</div>` : ""}
|
|
@@ -8316,6 +8641,15 @@ RelRadio.styles = css23`
|
|
|
8316
8641
|
gap: var(--rel-spacing-xs);
|
|
8317
8642
|
}
|
|
8318
8643
|
|
|
8644
|
+
/* Horizontal orientation — options flow in a wrapping row. Wider column
|
|
8645
|
+
gap than the vertical rhythm so adjacent option labels read as separate
|
|
8646
|
+
choices; row gap keeps the vertical value for wrapped lines. */
|
|
8647
|
+
:host([orientation="horizontal"]) .rel-radio-group {
|
|
8648
|
+
flex-direction: row;
|
|
8649
|
+
flex-wrap: wrap;
|
|
8650
|
+
gap: var(--rel-spacing-xs) var(--rel-spacing-xl);
|
|
8651
|
+
}
|
|
8652
|
+
|
|
8319
8653
|
.rel-radio-option {
|
|
8320
8654
|
display: inline-flex;
|
|
8321
8655
|
flex-direction: column;
|
|
@@ -8418,6 +8752,34 @@ RelRadio.styles = css23`
|
|
|
8418
8752
|
|
|
8419
8753
|
}
|
|
8420
8754
|
|
|
8755
|
+
/* Size variant (medium is the base scale above). Supporting-text indent =
|
|
8756
|
+
circle width + the radio-main gap, so it stays aligned under the label. */
|
|
8757
|
+
:host([size="small"]) .radio-custom {
|
|
8758
|
+
width: 16px;
|
|
8759
|
+
height: 16px;
|
|
8760
|
+
}
|
|
8761
|
+
|
|
8762
|
+
:host([size="small"]) input:checked + .radio-custom::after {
|
|
8763
|
+
inset: 3px;
|
|
8764
|
+
}
|
|
8765
|
+
|
|
8766
|
+
:host([size="small"]) .radio-label {
|
|
8767
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
8768
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
8769
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
8770
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
8771
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
8772
|
+
}
|
|
8773
|
+
|
|
8774
|
+
:host([size="small"]) .radio-supporting {
|
|
8775
|
+
margin-left: 24px;
|
|
8776
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
8777
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
8778
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
8779
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
8780
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
8781
|
+
}
|
|
8782
|
+
|
|
8421
8783
|
/* Error state (Figma State=Error, both Is Checked values) */
|
|
8422
8784
|
.rel-radio-option.error-unchecked .radio-custom,
|
|
8423
8785
|
.rel-radio-option.error-checked .radio-custom {
|
|
@@ -8461,6 +8823,12 @@ __decorateClass([
|
|
|
8461
8823
|
__decorateClass([
|
|
8462
8824
|
property22({ type: String })
|
|
8463
8825
|
], RelRadio.prototype, "name", 2);
|
|
8826
|
+
__decorateClass([
|
|
8827
|
+
property22({ type: String, reflect: true })
|
|
8828
|
+
], RelRadio.prototype, "orientation", 2);
|
|
8829
|
+
__decorateClass([
|
|
8830
|
+
property22({ type: String, reflect: true })
|
|
8831
|
+
], RelRadio.prototype, "size", 2);
|
|
8464
8832
|
__decorateClass([
|
|
8465
8833
|
property22({ type: String, reflect: true })
|
|
8466
8834
|
], RelRadio.prototype, "selectedValue", 2);
|
|
@@ -11208,7 +11576,7 @@ RelOtp = __decorateClass([
|
|
|
11208
11576
|
], RelOtp);
|
|
11209
11577
|
|
|
11210
11578
|
// src/components/rel-pagination/rel-pagination.ts
|
|
11211
|
-
import { LitElement as LitElement38, html as html38, css as css35 } from "lit";
|
|
11579
|
+
import { LitElement as LitElement38, html as html38, css as css35, nothing as nothing9 } from "lit";
|
|
11212
11580
|
import { customElement as customElement38, property as property32, state as state14 } from "lit/decorators.js";
|
|
11213
11581
|
var RelPagination = class extends LitElement38 {
|
|
11214
11582
|
constructor() {
|
|
@@ -11279,6 +11647,14 @@ var RelPagination = class extends LitElement38 {
|
|
|
11279
11647
|
}
|
|
11280
11648
|
return pages;
|
|
11281
11649
|
}
|
|
11650
|
+
/** Abbreviate large page numbers (368178 → "368k") so buttons stay compact.
|
|
11651
|
+
Exact below 10,000; floored to k up to 1M; then M with one decimal. */
|
|
11652
|
+
formatPageLabel(n) {
|
|
11653
|
+
if (n < 1e4) return String(n);
|
|
11654
|
+
if (n < 1e6) return `${Math.floor(n / 1e3)}k`;
|
|
11655
|
+
const m = Math.floor(n / 1e5) / 10;
|
|
11656
|
+
return `${Number.isInteger(m) ? m.toFixed(0) : m}M`;
|
|
11657
|
+
}
|
|
11282
11658
|
handlePageChange(newPage) {
|
|
11283
11659
|
const total = this.computedTotalPages;
|
|
11284
11660
|
if (this.disabled || newPage < 1 || newPage > total || newPage === this.activePage) return;
|
|
@@ -11324,15 +11700,20 @@ var RelPagination = class extends LitElement38 {
|
|
|
11324
11700
|
<div class="pag-stack">
|
|
11325
11701
|
${this.renderNavBtn("prev")}
|
|
11326
11702
|
<div class="pag-numbers">
|
|
11327
|
-
${pages.map(
|
|
11328
|
-
(p
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
|
|
11335
|
-
|
|
11703
|
+
${pages.map((p) => {
|
|
11704
|
+
if (p === -1) return html38`<span class="pag-ellipsis">...</span>`;
|
|
11705
|
+
const isNear = Math.abs(p - current) <= 1;
|
|
11706
|
+
const isEdge = p === 1 || p === total;
|
|
11707
|
+
const label = isEdge && !isNear ? this.formatPageLabel(p) : String(p);
|
|
11708
|
+
return html38`<button
|
|
11709
|
+
type="button"
|
|
11710
|
+
class="pag-page-btn ${isNear ? "near" : ""} ${p === 1 || p === total ? "edge" : ""}"
|
|
11711
|
+
aria-label="Page ${p}"
|
|
11712
|
+
title="${label === String(p) ? nothing9 : `Page ${p}`}"
|
|
11713
|
+
aria-current="${p === current ? "page" : "false"}"
|
|
11714
|
+
@click="${() => this.handlePageChange(p)}"
|
|
11715
|
+
>${label}</button>`;
|
|
11716
|
+
})}
|
|
11336
11717
|
</div>
|
|
11337
11718
|
${this.renderNavBtn("next")}
|
|
11338
11719
|
</div>
|
|
@@ -11485,9 +11866,10 @@ RelPagination.styles = css35`
|
|
|
11485
11866
|
display: inline-flex;
|
|
11486
11867
|
align-items: center;
|
|
11487
11868
|
justify-content: center;
|
|
11488
|
-
width:
|
|
11869
|
+
width: auto;
|
|
11870
|
+
min-width: 40px;
|
|
11489
11871
|
height: 40px;
|
|
11490
|
-
padding: var(--rel-spacing-xs);
|
|
11872
|
+
padding: 0 var(--rel-spacing-xs);
|
|
11491
11873
|
border-radius: var(--rel-border-radius-sm);
|
|
11492
11874
|
border: 1px solid var(--rel-border-default);
|
|
11493
11875
|
background: var(--rel-bg-neutral);
|
|
@@ -13020,7 +13402,7 @@ RelSegmentedButton = __decorateClass([
|
|
|
13020
13402
|
], RelSegmentedButton);
|
|
13021
13403
|
|
|
13022
13404
|
// src/components/rel-select/rel-select.ts
|
|
13023
|
-
import { LitElement as LitElement46, html as html46, css as css43, nothing as
|
|
13405
|
+
import { LitElement as LitElement46, html as html46, css as css43, nothing as nothing10 } from "lit";
|
|
13024
13406
|
import { customElement as customElement46, property as property40, state as state16, query as query8 } from "lit/decorators.js";
|
|
13025
13407
|
import { repeat as repeat2 } from "lit/directives/repeat.js";
|
|
13026
13408
|
import "@reliability-design/icons/rel-icon";
|
|
@@ -13060,6 +13442,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13060
13442
|
this.internalState = "default";
|
|
13061
13443
|
this.visibleChipCount = Infinity;
|
|
13062
13444
|
this.uniqueId = `select-${Math.random().toString(36).slice(2)}`;
|
|
13445
|
+
this._warnedNoName = false;
|
|
13063
13446
|
this.onOutsidePointerDown = (e) => {
|
|
13064
13447
|
if (!this.open) return;
|
|
13065
13448
|
const path = e.composedPath();
|
|
@@ -13089,6 +13472,14 @@ var RelSelect = class extends LitElement46 {
|
|
|
13089
13472
|
if (left + t.width > window.innerWidth - 8) {
|
|
13090
13473
|
left = Math.max(8, window.innerWidth - t.width - 8);
|
|
13091
13474
|
}
|
|
13475
|
+
this.listboxEl.style.top = `${top}px`;
|
|
13476
|
+
this.listboxEl.style.left = `${left}px`;
|
|
13477
|
+
this.listboxEl.style.width = `${t.width}px`;
|
|
13478
|
+
const applied = this.listboxEl.getBoundingClientRect();
|
|
13479
|
+
top -= applied.top - top;
|
|
13480
|
+
left -= applied.left - left;
|
|
13481
|
+
this.listboxEl.style.top = `${top}px`;
|
|
13482
|
+
this.listboxEl.style.left = `${left}px`;
|
|
13092
13483
|
this.panelStyle = `top:${top}px;left:${left}px;width:${t.width}px;`;
|
|
13093
13484
|
};
|
|
13094
13485
|
this.onSearchInput = (e) => {
|
|
@@ -13106,6 +13497,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13106
13497
|
disconnectedCallback() {
|
|
13107
13498
|
super.disconnectedCallback();
|
|
13108
13499
|
this.removeGlobalListeners();
|
|
13500
|
+
this.hideNativePopover();
|
|
13109
13501
|
this.chipResizeObserver?.disconnect();
|
|
13110
13502
|
}
|
|
13111
13503
|
firstUpdated() {
|
|
@@ -13118,6 +13510,13 @@ var RelSelect = class extends LitElement46 {
|
|
|
13118
13510
|
}
|
|
13119
13511
|
}
|
|
13120
13512
|
updated(changed) {
|
|
13513
|
+
if (!this._warnedNoName && this.labelStyle === "none" && !this.fieldAriaLabel && !this.fieldAriaLabelledby && !this.getAttribute("aria-label")) {
|
|
13514
|
+
this._warnedNoName = true;
|
|
13515
|
+
console.warn(
|
|
13516
|
+
'<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.',
|
|
13517
|
+
this
|
|
13518
|
+
);
|
|
13519
|
+
}
|
|
13121
13520
|
if (changed.has("selectedValues") || changed.has("options") || changed.has("multiple") || changed.has("size")) {
|
|
13122
13521
|
requestAnimationFrame(() => this.computeVisibleChips());
|
|
13123
13522
|
}
|
|
@@ -13215,6 +13614,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13215
13614
|
this.highlightedIndex = -1;
|
|
13216
13615
|
this.addGlobalListeners();
|
|
13217
13616
|
requestAnimationFrame(() => {
|
|
13617
|
+
this.showNativePopover();
|
|
13218
13618
|
this.updatePosition();
|
|
13219
13619
|
if (!this.noSearch) {
|
|
13220
13620
|
this.searchInputEl?.focus({ preventScroll: true });
|
|
@@ -13225,8 +13625,32 @@ var RelSelect = class extends LitElement46 {
|
|
|
13225
13625
|
if (!this.open) return;
|
|
13226
13626
|
this.open = false;
|
|
13227
13627
|
this.removeGlobalListeners();
|
|
13628
|
+
this.hideNativePopover();
|
|
13228
13629
|
this.triggerEl?.focus();
|
|
13229
13630
|
}
|
|
13631
|
+
/* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
13632
|
+
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
13633
|
+
* descendants, breaking viewport-based coordinates and letting the
|
|
13634
|
+
* panel's overflow:hidden clip the listbox. Promoting the listbox to
|
|
13635
|
+
* the browser top layer sidesteps both — same fix as rel-popover /
|
|
13636
|
+
* rel-autocomplete. `manual` keeps light-dismiss and Escape handling
|
|
13637
|
+
* under this component's own control. */
|
|
13638
|
+
showNativePopover() {
|
|
13639
|
+
const p = this.listboxEl;
|
|
13640
|
+
if (!p || typeof p.showPopover !== "function") return;
|
|
13641
|
+
try {
|
|
13642
|
+
if (!p.matches(":popover-open")) p.showPopover();
|
|
13643
|
+
} catch {
|
|
13644
|
+
}
|
|
13645
|
+
}
|
|
13646
|
+
hideNativePopover() {
|
|
13647
|
+
const p = this.listboxEl;
|
|
13648
|
+
if (!p || typeof p.hidePopover !== "function") return;
|
|
13649
|
+
try {
|
|
13650
|
+
if (p.matches(":popover-open")) p.hidePopover();
|
|
13651
|
+
} catch {
|
|
13652
|
+
}
|
|
13653
|
+
}
|
|
13230
13654
|
selectSingleOption(option) {
|
|
13231
13655
|
this.selected = option.value;
|
|
13232
13656
|
if (this.state === "error") this.state = "default";
|
|
@@ -13331,7 +13755,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13331
13755
|
`;
|
|
13332
13756
|
}
|
|
13333
13757
|
)}
|
|
13334
|
-
${overflow > 0 ? html46`<rel-chip label="+${overflow}" size="sm" variant="fill" border="default"></rel-chip>` :
|
|
13758
|
+
${overflow > 0 ? html46`<rel-chip label="+${overflow}" size="sm" variant="fill" border="default"></rel-chip>` : nothing10}
|
|
13335
13759
|
</span>
|
|
13336
13760
|
`;
|
|
13337
13761
|
}
|
|
@@ -13366,7 +13790,9 @@ var RelSelect = class extends LitElement46 {
|
|
|
13366
13790
|
const opts = this.normalizedOptions;
|
|
13367
13791
|
const filtered = this.filteredOptions;
|
|
13368
13792
|
const isDisabled = this.state === "disabled";
|
|
13793
|
+
const labelHidden = this.labelStyle === "none";
|
|
13369
13794
|
const showFloatingChrome = this.labelStyle === "floating" && !!this.label;
|
|
13795
|
+
const accessibleName = (labelHidden ? this.fieldAriaLabel : this.label) || "";
|
|
13370
13796
|
const wrapperClasses = [
|
|
13371
13797
|
"select-wrapper",
|
|
13372
13798
|
this.size,
|
|
@@ -13382,7 +13808,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13382
13808
|
const selectedSingle = opts.find((o) => o.value === this.selected);
|
|
13383
13809
|
return html46`
|
|
13384
13810
|
<div class="${wrapperClasses}">
|
|
13385
|
-
${!showFloatingChrome && this.label ? html46`<label part="label" class="select-label" for="${this.uniqueId}">${this.label}</label>` : ""}
|
|
13811
|
+
${labelHidden ? html46`<div class="label-reserve" aria-hidden="true"></div>` : !showFloatingChrome && this.label ? html46`<label part="label" class="select-label" for="${this.uniqueId}">${this.label}</label>` : ""}
|
|
13386
13812
|
|
|
13387
13813
|
<div class="select-container">
|
|
13388
13814
|
${showFloatingChrome ? html46`<span class="floating-label${this.leftIcon ? " has-left-icon" : ""}"
|
|
@@ -13399,7 +13825,8 @@ var RelSelect = class extends LitElement46 {
|
|
|
13399
13825
|
aria-controls="${listboxId}"
|
|
13400
13826
|
aria-activedescendant="${activeOptionId}"
|
|
13401
13827
|
aria-invalid="${this.state === "error"}"
|
|
13402
|
-
aria-label="${this.
|
|
13828
|
+
aria-label="${this.fieldAriaLabelledby ? nothing10 : accessibleName || this.placeholder}"
|
|
13829
|
+
aria-labelledby="${this.fieldAriaLabelledby ?? nothing10}"
|
|
13403
13830
|
?disabled="${isDisabled}"
|
|
13404
13831
|
@click="${this.onTriggerClick}"
|
|
13405
13832
|
@keydown="${this.onTriggerKeyDown}"
|
|
@@ -13423,7 +13850,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13423
13850
|
></rel-icon>
|
|
13424
13851
|
</button>
|
|
13425
13852
|
|
|
13426
|
-
${this.multiple && this.selectedValues.length > 0 ? this.renderChipsMeasure() :
|
|
13853
|
+
${this.multiple && this.selectedValues.length > 0 ? this.renderChipsMeasure() : nothing10}
|
|
13427
13854
|
</div>
|
|
13428
13855
|
|
|
13429
13856
|
${this.supportingText ? html46`<span part="helper" class="select-support ${this.state === "error" ? "error" : ""}">
|
|
@@ -13434,39 +13861,42 @@ var RelSelect = class extends LitElement46 {
|
|
|
13434
13861
|
<div
|
|
13435
13862
|
id="${listboxId}"
|
|
13436
13863
|
part="panel list" class="select-listbox"
|
|
13864
|
+
popover="manual"
|
|
13437
13865
|
role="listbox"
|
|
13438
13866
|
aria-multiselectable="${this.multiple}"
|
|
13439
|
-
aria-label="${
|
|
13867
|
+
aria-label="${accessibleName || "Options"}"
|
|
13440
13868
|
style="${this.panelStyle}"
|
|
13441
13869
|
?data-open="${this.open}"
|
|
13442
13870
|
>
|
|
13443
|
-
${!this.noSearch ? html46`<div class="select-
|
|
13444
|
-
|
|
13445
|
-
|
|
13446
|
-
|
|
13447
|
-
|
|
13448
|
-
|
|
13449
|
-
|
|
13450
|
-
|
|
13451
|
-
|
|
13452
|
-
|
|
13453
|
-
|
|
13454
|
-
|
|
13871
|
+
${!this.noSearch || this.multiple && filtered.length > 0 ? html46`<div class="select-header">
|
|
13872
|
+
${!this.noSearch ? html46`<div class="select-search">
|
|
13873
|
+
<div class="search-input-wrap">
|
|
13874
|
+
<rel-icon name="search" size="${this.searchIconSize}" class="icon"></rel-icon>
|
|
13875
|
+
<input
|
|
13876
|
+
part="input" class="search-input"
|
|
13877
|
+
type="text"
|
|
13878
|
+
placeholder="${this.searchPlaceholder}"
|
|
13879
|
+
.value="${this.searchValue}"
|
|
13880
|
+
@input="${this.onSearchInput}"
|
|
13881
|
+
@keydown="${(e) => this.onSearchKeyDown(e)}"
|
|
13882
|
+
/>
|
|
13883
|
+
</div>
|
|
13884
|
+
</div>` : ""}
|
|
13885
|
+
${this.multiple && filtered.length > 0 ? html46`<div class="select-all-row">
|
|
13886
|
+
<rel-checkbox
|
|
13887
|
+
label="${this.selectAllLabel}"
|
|
13888
|
+
status="${this.selectAllStatus}"
|
|
13889
|
+
@rel-change=${(e) => {
|
|
13890
|
+
e.stopPropagation();
|
|
13891
|
+
this.toggleSelectAll();
|
|
13892
|
+
}}
|
|
13893
|
+
></rel-checkbox>
|
|
13894
|
+
</div>` : ""}
|
|
13455
13895
|
</div>` : ""}
|
|
13456
13896
|
|
|
13457
13897
|
${filtered.length === 0 ? html46`<div class="select-empty">
|
|
13458
13898
|
${opts.length === 0 ? "No options available" : "No matches found"}
|
|
13459
13899
|
</div>` : this.multiple ? html46`
|
|
13460
|
-
<div class="select-all-row">
|
|
13461
|
-
<rel-checkbox
|
|
13462
|
-
label="${this.selectAllLabel}"
|
|
13463
|
-
status="${this.selectAllStatus}"
|
|
13464
|
-
@rel-change=${(e) => {
|
|
13465
|
-
e.stopPropagation();
|
|
13466
|
-
this.toggleSelectAll();
|
|
13467
|
-
}}
|
|
13468
|
-
></rel-checkbox>
|
|
13469
|
-
</div>
|
|
13470
13900
|
<div class="select-options">
|
|
13471
13901
|
${repeat2(
|
|
13472
13902
|
filtered,
|
|
@@ -13513,7 +13943,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13513
13943
|
name="${option.icon}"
|
|
13514
13944
|
size="sm"
|
|
13515
13945
|
class="select-option-icon"
|
|
13516
|
-
></rel-icon>` :
|
|
13946
|
+
></rel-icon>` : nothing10}
|
|
13517
13947
|
<span class="select-option-label">${option.label}</span>
|
|
13518
13948
|
</span>
|
|
13519
13949
|
${option.value === this.selected ? html46`<rel-icon name="check" size="sm" class="select-option-check"></rel-icon>` : ""}
|
|
@@ -13565,6 +13995,22 @@ RelSelect.styles = css43`
|
|
|
13565
13995
|
|
|
13566
13996
|
}
|
|
13567
13997
|
|
|
13998
|
+
/* ── labelStyle="none" ──
|
|
13999
|
+
No label renders. An empty spacer holds the label row's full height so the
|
|
14000
|
+
trigger still bottom-aligns with labelled siblings in a form row.
|
|
14001
|
+
The reserve covers label line-height + the wrapper gap, and the negative
|
|
14002
|
+
margin cancels the flex gap that would otherwise sit below it — so
|
|
14003
|
+
--rel-field-label-reserve: 0 collapses the row to genuinely zero
|
|
14004
|
+
(toolbars, table cells), not just to the leftover gap. */
|
|
14005
|
+
.label-reserve {
|
|
14006
|
+
flex: none;
|
|
14007
|
+
height: var(
|
|
14008
|
+
--rel-field-label-reserve,
|
|
14009
|
+
calc(var(--rel-body-sm-regular-line-height) + var(--rel-spacing-2xs))
|
|
14010
|
+
);
|
|
14011
|
+
margin-bottom: calc(-1 * var(--rel-spacing-2xs));
|
|
14012
|
+
}
|
|
14013
|
+
|
|
13568
14014
|
/* Error only colors the border/focus ring and the supporting text below
|
|
13569
14015
|
the field — label, floating label, placeholder, and icons stay normal. */
|
|
13570
14016
|
|
|
@@ -13793,6 +14239,15 @@ RelSelect.styles = css43`
|
|
|
13793
14239
|
position: fixed;
|
|
13794
14240
|
z-index: 1001;
|
|
13795
14241
|
display: none;
|
|
14242
|
+
/* Reset native [popover] UA styles (the listbox is promoted to the
|
|
14243
|
+
top layer so transformed ancestors — e.g. a centered rel-modal
|
|
14244
|
+
panel — can't hijack its containing block or clip it). */
|
|
14245
|
+
inset: auto;
|
|
14246
|
+
margin: 0;
|
|
14247
|
+
padding: 0;
|
|
14248
|
+
width: auto;
|
|
14249
|
+
height: auto;
|
|
14250
|
+
color: inherit;
|
|
13796
14251
|
box-sizing: border-box;
|
|
13797
14252
|
background: var(--rel-select-bg, var(--rel-bg-neutral));
|
|
13798
14253
|
border: 1px solid var(--rel-select-menu-border, var(--rel-border-default));
|
|
@@ -13809,10 +14264,18 @@ RelSelect.styles = css43`
|
|
|
13809
14264
|
display: block;
|
|
13810
14265
|
}
|
|
13811
14266
|
|
|
13812
|
-
.
|
|
14267
|
+
/* Search and Select All pin together while the options scroll beneath.
|
|
14268
|
+
z-index is load-bearing: multiselect rows contain rel-checkbox, whose
|
|
14269
|
+
.row wrapper is position:relative (Tab-scroll fix), so without it the
|
|
14270
|
+
positioned rows paint over the sticky header in DOM order. */
|
|
14271
|
+
.select-header {
|
|
13813
14272
|
position: sticky;
|
|
13814
14273
|
top: 0;
|
|
14274
|
+
z-index: 1;
|
|
13815
14275
|
background: var(--rel-select-bg, var(--rel-bg-neutral));
|
|
14276
|
+
}
|
|
14277
|
+
|
|
14278
|
+
.select-search {
|
|
13816
14279
|
padding: var(--rel-spacing-xs);
|
|
13817
14280
|
border-bottom: 1px solid var(--rel-select-divider, var(--rel-border-subtle));
|
|
13818
14281
|
}
|
|
@@ -13958,8 +14421,14 @@ __decorateClass([
|
|
|
13958
14421
|
property40({ type: String })
|
|
13959
14422
|
], RelSelect.prototype, "label", 2);
|
|
13960
14423
|
__decorateClass([
|
|
13961
|
-
property40({ type: String, attribute: "label-style" })
|
|
14424
|
+
property40({ type: String, attribute: "label-style", reflect: true })
|
|
13962
14425
|
], RelSelect.prototype, "labelStyle", 2);
|
|
14426
|
+
__decorateClass([
|
|
14427
|
+
property40({ type: String, attribute: "field-aria-label" })
|
|
14428
|
+
], RelSelect.prototype, "fieldAriaLabel", 2);
|
|
14429
|
+
__decorateClass([
|
|
14430
|
+
property40({ type: String, attribute: "field-aria-labelledby" })
|
|
14431
|
+
], RelSelect.prototype, "fieldAriaLabelledby", 2);
|
|
13963
14432
|
__decorateClass([
|
|
13964
14433
|
property40({ type: Array })
|
|
13965
14434
|
], RelSelect.prototype, "options", 2);
|
|
@@ -16558,111 +17027,627 @@ RelTab = __decorateClass([
|
|
|
16558
17027
|
customElement53("rel-tab")
|
|
16559
17028
|
], RelTab);
|
|
16560
17029
|
|
|
16561
|
-
// src/components/rel-
|
|
16562
|
-
import { LitElement as LitElement54, html as html54, css as css51 } from "lit";
|
|
16563
|
-
import { customElement as customElement54, property as property48, state as state21 } from "lit/decorators.js";
|
|
16564
|
-
|
|
17030
|
+
// src/components/rel-time-picker/rel-time-picker.ts
|
|
17031
|
+
import { LitElement as LitElement54, html as html54, css as css51, nothing as nothing11 } from "lit";
|
|
17032
|
+
import { customElement as customElement54, property as property48, state as state21, query as query10 } from "lit/decorators.js";
|
|
17033
|
+
import "@reliability-design/icons/rel-icon";
|
|
17034
|
+
var RelTimePicker = class extends LitElement54 {
|
|
16565
17035
|
constructor() {
|
|
16566
17036
|
super(...arguments);
|
|
16567
|
-
this.
|
|
16568
|
-
this.
|
|
16569
|
-
this.
|
|
16570
|
-
this.
|
|
16571
|
-
this.
|
|
16572
|
-
this.
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
|
|
16586
|
-
|
|
17037
|
+
this.value = null;
|
|
17038
|
+
this.placeholder = "Select time";
|
|
17039
|
+
this.label = null;
|
|
17040
|
+
this.disabled = false;
|
|
17041
|
+
this.required = false;
|
|
17042
|
+
this.readonly = false;
|
|
17043
|
+
this.format = "12h";
|
|
17044
|
+
this.minuteStep = 1;
|
|
17045
|
+
this.minTime = null;
|
|
17046
|
+
this.maxTime = null;
|
|
17047
|
+
this.open = false;
|
|
17048
|
+
this.clearable = false;
|
|
17049
|
+
this.error = false;
|
|
17050
|
+
this.invalid = false;
|
|
17051
|
+
this.size = "medium";
|
|
17052
|
+
this.variant = "outlined";
|
|
17053
|
+
this.fullWidth = false;
|
|
17054
|
+
this.draftHour = null;
|
|
17055
|
+
this.draftMinute = null;
|
|
17056
|
+
this.handleClickOutside = (e) => {
|
|
17057
|
+
if (!this.open) return;
|
|
17058
|
+
const path = e.composedPath();
|
|
17059
|
+
if (!path.includes(this)) {
|
|
17060
|
+
this.cancelPopover();
|
|
16587
17061
|
}
|
|
16588
|
-
}
|
|
17062
|
+
};
|
|
17063
|
+
this.handleEscapeKey = (e) => {
|
|
17064
|
+
if (e.key === "Escape" && this.open) {
|
|
17065
|
+
this.cancelPopover();
|
|
17066
|
+
}
|
|
17067
|
+
};
|
|
17068
|
+
}
|
|
17069
|
+
connectedCallback() {
|
|
17070
|
+
super.connectedCallback();
|
|
17071
|
+
document.addEventListener("click", this.handleClickOutside);
|
|
17072
|
+
document.addEventListener("keydown", this.handleEscapeKey);
|
|
16589
17073
|
}
|
|
16590
17074
|
disconnectedCallback() {
|
|
16591
17075
|
super.disconnectedCallback();
|
|
16592
|
-
|
|
17076
|
+
document.removeEventListener("click", this.handleClickOutside);
|
|
17077
|
+
document.removeEventListener("keydown", this.handleEscapeKey);
|
|
16593
17078
|
}
|
|
16594
|
-
|
|
16595
|
-
this.
|
|
17079
|
+
updated(changedProperties) {
|
|
17080
|
+
if (changedProperties.has("open") && this.open) {
|
|
17081
|
+
this.resetDraftFromValue();
|
|
17082
|
+
setTimeout(() => {
|
|
17083
|
+
this.scrollColumnsToSelection();
|
|
17084
|
+
const target = this.columnsElement?.querySelector(
|
|
17085
|
+
".option-button.selected:not(:disabled)"
|
|
17086
|
+
) ?? this.columnsElement?.querySelector(
|
|
17087
|
+
".option-button:not(:disabled)"
|
|
17088
|
+
);
|
|
17089
|
+
target?.focus();
|
|
17090
|
+
}, 0);
|
|
17091
|
+
}
|
|
16596
17092
|
}
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
17093
|
+
// ── Parsing / formatting ──
|
|
17094
|
+
parseTime(time) {
|
|
17095
|
+
if (!time) return null;
|
|
17096
|
+
const match = time.match(/^(\d{1,2}):(\d{2})$/);
|
|
17097
|
+
if (!match) return null;
|
|
17098
|
+
const hour = parseInt(match[1], 10);
|
|
17099
|
+
const minute = parseInt(match[2], 10);
|
|
17100
|
+
if (hour > 23 || minute > 59) return null;
|
|
17101
|
+
return { hour, minute };
|
|
17102
|
+
}
|
|
17103
|
+
formatValue(hour, minute) {
|
|
17104
|
+
return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
17105
|
+
}
|
|
17106
|
+
formatDisplay(hour, minute) {
|
|
17107
|
+
const mm = String(minute).padStart(2, "0");
|
|
17108
|
+
if (this.format === "24h") {
|
|
17109
|
+
return `${String(hour).padStart(2, "0")}:${mm}`;
|
|
17110
|
+
}
|
|
17111
|
+
const period = hour < 12 ? "AM" : "PM";
|
|
17112
|
+
const hour12 = hour % 12 === 0 ? 12 : hour % 12;
|
|
17113
|
+
return `${hour12}:${mm} ${period}`;
|
|
17114
|
+
}
|
|
17115
|
+
toMinutes(hour, minute) {
|
|
17116
|
+
return hour * 60 + minute;
|
|
17117
|
+
}
|
|
17118
|
+
get minuteStepSafe() {
|
|
17119
|
+
const step = Math.floor(this.minuteStep);
|
|
17120
|
+
return step >= 1 && step <= 60 ? step : 1;
|
|
17121
|
+
}
|
|
17122
|
+
get minBoundMinutes() {
|
|
17123
|
+
const parsed = this.parseTime(this.minTime);
|
|
17124
|
+
return parsed ? this.toMinutes(parsed.hour, parsed.minute) : 0;
|
|
17125
|
+
}
|
|
17126
|
+
get maxBoundMinutes() {
|
|
17127
|
+
const parsed = this.parseTime(this.maxTime);
|
|
17128
|
+
return parsed ? this.toMinutes(parsed.hour, parsed.minute) : 23 * 60 + 59;
|
|
17129
|
+
}
|
|
17130
|
+
isTimeInRange(hour, minute) {
|
|
17131
|
+
const total = this.toMinutes(hour, minute);
|
|
17132
|
+
return total >= this.minBoundMinutes && total <= this.maxBoundMinutes;
|
|
17133
|
+
}
|
|
17134
|
+
/** Minutes shown in the minutes column (respecting minuteStep). */
|
|
17135
|
+
getMinuteOptions() {
|
|
17136
|
+
const minutes = [];
|
|
17137
|
+
for (let m = 0; m < 60; m += this.minuteStepSafe) {
|
|
17138
|
+
minutes.push(m);
|
|
17139
|
+
}
|
|
17140
|
+
return minutes;
|
|
17141
|
+
}
|
|
17142
|
+
/** An hour is enabled if at least one of its step minutes is in range. */
|
|
17143
|
+
isHourEnabled(hour) {
|
|
17144
|
+
return this.getMinuteOptions().some((m) => this.isTimeInRange(hour, m));
|
|
17145
|
+
}
|
|
17146
|
+
isPeriodEnabled(period) {
|
|
17147
|
+
const start = period === "AM" ? 0 : 12;
|
|
17148
|
+
for (let h = start; h < start + 12; h++) {
|
|
17149
|
+
if (this.isHourEnabled(h)) return true;
|
|
17150
|
+
}
|
|
17151
|
+
return false;
|
|
17152
|
+
}
|
|
17153
|
+
/** Hour column entries: display label + the 0–23 hour it maps to. */
|
|
17154
|
+
getHourOptions() {
|
|
17155
|
+
if (this.format === "24h") {
|
|
17156
|
+
return Array.from({ length: 24 }, (_, h) => ({
|
|
17157
|
+
label: String(h).padStart(2, "0"),
|
|
17158
|
+
hour24: h
|
|
17159
|
+
}));
|
|
17160
|
+
}
|
|
17161
|
+
const period = this.draftPeriod ?? "AM";
|
|
17162
|
+
const base = period === "AM" ? 0 : 12;
|
|
17163
|
+
return Array.from({ length: 12 }, (_, i) => ({
|
|
17164
|
+
label: i === 0 ? "12" : String(i).padStart(2, "0"),
|
|
17165
|
+
hour24: base + i
|
|
17166
|
+
}));
|
|
17167
|
+
}
|
|
17168
|
+
get draftPeriod() {
|
|
17169
|
+
if (this.draftHour === null) return null;
|
|
17170
|
+
return this.draftHour < 12 ? "AM" : "PM";
|
|
17171
|
+
}
|
|
17172
|
+
resetDraftFromValue() {
|
|
17173
|
+
const parsed = this.parseTime(this.value);
|
|
17174
|
+
this.draftHour = parsed ? parsed.hour : null;
|
|
17175
|
+
this.draftMinute = parsed ? parsed.minute : null;
|
|
17176
|
+
}
|
|
17177
|
+
/** Snap the draft minute to the nearest valid step minute for the hour. */
|
|
17178
|
+
clampDraftToRange() {
|
|
17179
|
+
if (this.draftHour === null || this.draftMinute === null) return;
|
|
17180
|
+
if (this.isTimeInRange(this.draftHour, this.draftMinute)) return;
|
|
17181
|
+
const hour = this.draftHour;
|
|
17182
|
+
const valid = this.getMinuteOptions().filter((m) => this.isTimeInRange(hour, m));
|
|
17183
|
+
if (valid.length === 0) {
|
|
17184
|
+
this.draftMinute = null;
|
|
17185
|
+
return;
|
|
16603
17186
|
}
|
|
17187
|
+
const current = this.draftMinute;
|
|
17188
|
+
this.draftMinute = valid.reduce(
|
|
17189
|
+
(best, m) => Math.abs(m - current) < Math.abs(best - current) ? m : best
|
|
17190
|
+
);
|
|
17191
|
+
}
|
|
17192
|
+
// ── Open / close / commit ──
|
|
17193
|
+
toggleTimePopover() {
|
|
17194
|
+
if (this.disabled || this.readonly) return;
|
|
17195
|
+
this.open = !this.open;
|
|
16604
17196
|
this.dispatchEvent(
|
|
16605
|
-
new CustomEvent("
|
|
17197
|
+
new CustomEvent("rel-open-change", {
|
|
17198
|
+
detail: { open: this.open },
|
|
16606
17199
|
bubbles: true,
|
|
16607
17200
|
composed: true
|
|
16608
17201
|
})
|
|
16609
17202
|
);
|
|
16610
17203
|
}
|
|
16611
|
-
|
|
16612
|
-
|
|
17204
|
+
/** Close discarding the draft (Cancel / Escape / outside click). */
|
|
17205
|
+
cancelPopover() {
|
|
17206
|
+
this.open = false;
|
|
17207
|
+
this.triggerButton?.focus();
|
|
16613
17208
|
this.dispatchEvent(
|
|
16614
|
-
new CustomEvent("
|
|
17209
|
+
new CustomEvent("rel-open-change", {
|
|
17210
|
+
detail: { open: false },
|
|
16615
17211
|
bubbles: true,
|
|
16616
17212
|
composed: true
|
|
16617
17213
|
})
|
|
16618
17214
|
);
|
|
16619
|
-
this.close();
|
|
16620
17215
|
}
|
|
17216
|
+
applySelection() {
|
|
17217
|
+
if (this.draftHour === null) return;
|
|
17218
|
+
const newValue = this.formatValue(this.draftHour, this.draftMinute ?? 0);
|
|
17219
|
+
this.value = newValue;
|
|
17220
|
+
this.dispatchEvent(
|
|
17221
|
+
new CustomEvent("rel-change", {
|
|
17222
|
+
detail: { value: newValue },
|
|
17223
|
+
bubbles: true,
|
|
17224
|
+
composed: true
|
|
17225
|
+
})
|
|
17226
|
+
);
|
|
17227
|
+
this.cancelPopover();
|
|
17228
|
+
}
|
|
17229
|
+
/** Set the draft to the current time, snapped to the minute step and range. */
|
|
17230
|
+
selectNow() {
|
|
17231
|
+
const now = /* @__PURE__ */ new Date();
|
|
17232
|
+
const step = this.minuteStepSafe;
|
|
17233
|
+
this.draftHour = now.getHours();
|
|
17234
|
+
this.draftMinute = Math.round(now.getMinutes() / step) * step;
|
|
17235
|
+
if (this.draftMinute >= 60) {
|
|
17236
|
+
this.draftMinute = 0;
|
|
17237
|
+
this.draftHour = (this.draftHour + 1) % 24;
|
|
17238
|
+
}
|
|
17239
|
+
if (!this.isHourEnabled(this.draftHour)) {
|
|
17240
|
+
const enabled = [];
|
|
17241
|
+
for (let h = 0; h < 24; h++) if (this.isHourEnabled(h)) enabled.push(h);
|
|
17242
|
+
if (enabled.length === 0) return;
|
|
17243
|
+
const currentHour = this.draftHour;
|
|
17244
|
+
this.draftHour = enabled.reduce(
|
|
17245
|
+
(best, h) => Math.abs(h - currentHour) < Math.abs(best - currentHour) ? h : best
|
|
17246
|
+
);
|
|
17247
|
+
}
|
|
17248
|
+
this.clampDraftToRange();
|
|
17249
|
+
this.scrollColumnsToSelection();
|
|
17250
|
+
}
|
|
17251
|
+
clearValue(e) {
|
|
17252
|
+
e.stopPropagation();
|
|
17253
|
+
this.value = null;
|
|
17254
|
+
this.draftHour = null;
|
|
17255
|
+
this.draftMinute = null;
|
|
17256
|
+
this.dispatchEvent(
|
|
17257
|
+
new CustomEvent("rel-change", {
|
|
17258
|
+
detail: { value: null },
|
|
17259
|
+
bubbles: true,
|
|
17260
|
+
composed: true
|
|
17261
|
+
})
|
|
17262
|
+
);
|
|
17263
|
+
}
|
|
17264
|
+
// ── Column selection ──
|
|
17265
|
+
selectHour(hour24) {
|
|
17266
|
+
if (!this.isHourEnabled(hour24)) return;
|
|
17267
|
+
this.draftHour = hour24;
|
|
17268
|
+
if (this.draftMinute === null) this.draftMinute = 0;
|
|
17269
|
+
this.clampDraftToRange();
|
|
17270
|
+
}
|
|
17271
|
+
selectMinute(minute) {
|
|
17272
|
+
if (this.draftHour === null) {
|
|
17273
|
+
for (let h = 0; h < 24; h++) {
|
|
17274
|
+
if (this.isHourEnabled(h)) {
|
|
17275
|
+
this.draftHour = h;
|
|
17276
|
+
break;
|
|
17277
|
+
}
|
|
17278
|
+
}
|
|
17279
|
+
if (this.draftHour === null) return;
|
|
17280
|
+
}
|
|
17281
|
+
if (!this.isTimeInRange(this.draftHour, minute)) return;
|
|
17282
|
+
this.draftMinute = minute;
|
|
17283
|
+
}
|
|
17284
|
+
selectPeriod(period) {
|
|
17285
|
+
if (!this.isPeriodEnabled(period)) return;
|
|
17286
|
+
const base = period === "AM" ? 0 : 12;
|
|
17287
|
+
const hour12 = this.draftHour === null ? 0 : this.draftHour % 12;
|
|
17288
|
+
this.draftHour = base + hour12;
|
|
17289
|
+
if (this.draftMinute === null) this.draftMinute = 0;
|
|
17290
|
+
if (!this.isHourEnabled(this.draftHour)) {
|
|
17291
|
+
const enabled = [];
|
|
17292
|
+
for (let h = base; h < base + 12; h++) if (this.isHourEnabled(h)) enabled.push(h);
|
|
17293
|
+
const currentHour = this.draftHour;
|
|
17294
|
+
this.draftHour = enabled.reduce(
|
|
17295
|
+
(best, h) => Math.abs(h - currentHour) < Math.abs(best - currentHour) ? h : best
|
|
17296
|
+
);
|
|
17297
|
+
}
|
|
17298
|
+
this.clampDraftToRange();
|
|
17299
|
+
this.scrollColumnsToSelection();
|
|
17300
|
+
}
|
|
17301
|
+
scrollColumnsToSelection() {
|
|
17302
|
+
this.updateComplete.then(() => {
|
|
17303
|
+
this.columnsElement?.querySelectorAll(".option-button.selected").forEach((btn) => btn.scrollIntoView({ block: "center" }));
|
|
17304
|
+
});
|
|
17305
|
+
}
|
|
17306
|
+
// ── Keyboard ──
|
|
17307
|
+
handleTriggerKeydown(e) {
|
|
17308
|
+
if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
|
|
17309
|
+
e.preventDefault();
|
|
17310
|
+
if (!this.open) {
|
|
17311
|
+
this.toggleTimePopover();
|
|
17312
|
+
}
|
|
17313
|
+
}
|
|
17314
|
+
}
|
|
17315
|
+
/**
|
|
17316
|
+
* Column navigation: Up/Down move within the column (and select), Left/Right
|
|
17317
|
+
* jump between columns, Home/End go to the first/last option, Enter applies.
|
|
17318
|
+
*/
|
|
17319
|
+
handleOptionKeydown(e, column) {
|
|
17320
|
+
const list = e.currentTarget.closest(".column-list");
|
|
17321
|
+
if (!list) return;
|
|
17322
|
+
const buttons = Array.from(
|
|
17323
|
+
list.querySelectorAll(".option-button:not(:disabled)")
|
|
17324
|
+
);
|
|
17325
|
+
const index = buttons.indexOf(e.currentTarget);
|
|
17326
|
+
let target;
|
|
17327
|
+
switch (e.key) {
|
|
17328
|
+
case "ArrowUp":
|
|
17329
|
+
e.preventDefault();
|
|
17330
|
+
target = buttons[Math.max(0, index - 1)];
|
|
17331
|
+
break;
|
|
17332
|
+
case "ArrowDown":
|
|
17333
|
+
e.preventDefault();
|
|
17334
|
+
target = buttons[Math.min(buttons.length - 1, index + 1)];
|
|
17335
|
+
break;
|
|
17336
|
+
case "Home":
|
|
17337
|
+
e.preventDefault();
|
|
17338
|
+
target = buttons[0];
|
|
17339
|
+
break;
|
|
17340
|
+
case "End":
|
|
17341
|
+
e.preventDefault();
|
|
17342
|
+
target = buttons[buttons.length - 1];
|
|
17343
|
+
break;
|
|
17344
|
+
case "ArrowLeft":
|
|
17345
|
+
case "ArrowRight": {
|
|
17346
|
+
e.preventDefault();
|
|
17347
|
+
this.focusAdjacentColumn(column, e.key === "ArrowRight" ? 1 : -1);
|
|
17348
|
+
return;
|
|
17349
|
+
}
|
|
17350
|
+
case "Enter":
|
|
17351
|
+
e.preventDefault();
|
|
17352
|
+
e.currentTarget.click();
|
|
17353
|
+
this.applySelection();
|
|
17354
|
+
return;
|
|
17355
|
+
default:
|
|
17356
|
+
return;
|
|
17357
|
+
}
|
|
17358
|
+
if (target && target !== buttons[index]) {
|
|
17359
|
+
target.click();
|
|
17360
|
+
target.focus();
|
|
17361
|
+
target.scrollIntoView({ block: "nearest" });
|
|
17362
|
+
}
|
|
17363
|
+
}
|
|
17364
|
+
focusAdjacentColumn(from, direction) {
|
|
17365
|
+
const order = this.format === "12h" ? ["hour", "minute", "period"] : ["hour", "minute"];
|
|
17366
|
+
const nextIndex = order.indexOf(from) + direction;
|
|
17367
|
+
if (nextIndex < 0 || nextIndex >= order.length) return;
|
|
17368
|
+
const lists = this.columnsElement?.querySelectorAll(".column-list");
|
|
17369
|
+
const list = lists?.[nextIndex];
|
|
17370
|
+
const target = list?.querySelector(".option-button.selected:not(:disabled)") ?? list?.querySelector(".option-button:not(:disabled)");
|
|
17371
|
+
target?.focus();
|
|
17372
|
+
target?.scrollIntoView({ block: "nearest" });
|
|
17373
|
+
}
|
|
17374
|
+
// ── Render ──
|
|
16621
17375
|
render() {
|
|
17376
|
+
const isError = this.error || this.invalid;
|
|
17377
|
+
const belowText = isError && this.errorMessage ? this.errorMessage : this.helperText;
|
|
17378
|
+
const describedById = belowText ? "time-picker-desc" : void 0;
|
|
17379
|
+
const parsedValue = this.parseTime(this.value);
|
|
17380
|
+
const displayText = parsedValue ? this.formatDisplay(parsedValue.hour, parsedValue.minute) : "";
|
|
17381
|
+
const draftDisplay = this.draftHour !== null ? this.formatDisplay(this.draftHour, this.draftMinute ?? 0) : "";
|
|
17382
|
+
const hourOptions = this.getHourOptions();
|
|
17383
|
+
const minuteOptions = this.getMinuteOptions();
|
|
17384
|
+
const draftPeriod = this.draftPeriod;
|
|
17385
|
+
const wrapperClasses = [
|
|
17386
|
+
"time-picker-wrapper",
|
|
17387
|
+
this.size,
|
|
17388
|
+
isError ? "error" : "",
|
|
17389
|
+
this.readonly ? "readonly" : ""
|
|
17390
|
+
].filter(Boolean).join(" ");
|
|
16622
17391
|
return html54`
|
|
16623
|
-
<div part="base" class="
|
|
16624
|
-
|
|
17392
|
+
<div part="base" class="${wrapperClasses}">
|
|
17393
|
+
${this.label ? html54`<label part="label" class="label" id="time-picker-label"
|
|
17394
|
+
>${this.label}${this.required ? html54`<span class="required-mark" aria-hidden="true">*</span>` : nothing11}</label
|
|
17395
|
+
>` : nothing11}
|
|
16625
17396
|
|
|
16626
|
-
${this.
|
|
16627
|
-
|
|
16628
|
-
|
|
17397
|
+
${this.name ? html54`<input type="hidden" name=${this.name} .value=${this.value ?? ""} />` : nothing11}
|
|
17398
|
+
|
|
17399
|
+
<button
|
|
17400
|
+
part="trigger"
|
|
17401
|
+
class="trigger-button ${isError ? "error" : ""}"
|
|
17402
|
+
type="button"
|
|
17403
|
+
?disabled=${this.disabled}
|
|
17404
|
+
@click=${this.toggleTimePopover}
|
|
17405
|
+
@keydown=${this.handleTriggerKeydown}
|
|
17406
|
+
@focus=${() => this.dispatchEvent(
|
|
17407
|
+
new CustomEvent("rel-focus", { bubbles: true, composed: true })
|
|
17408
|
+
)}
|
|
17409
|
+
@blur=${() => this.dispatchEvent(
|
|
17410
|
+
new CustomEvent("rel-blur", { bubbles: true, composed: true })
|
|
17411
|
+
)}
|
|
17412
|
+
aria-label=${this.label || "Select time"}
|
|
17413
|
+
aria-haspopup="dialog"
|
|
17414
|
+
aria-expanded=${this.open}
|
|
17415
|
+
aria-required=${this.required}
|
|
17416
|
+
aria-invalid=${isError}
|
|
17417
|
+
aria-readonly=${this.readonly}
|
|
17418
|
+
aria-describedby=${describedById ?? nothing11}
|
|
17419
|
+
aria-labelledby=${this.label ? "time-picker-label" : nothing11}
|
|
17420
|
+
>
|
|
17421
|
+
<span part="value" class="trigger-content ${displayText ? "" : "placeholder"}">
|
|
17422
|
+
${displayText || this.placeholder}
|
|
17423
|
+
</span>
|
|
17424
|
+
<span class="trigger-icons">
|
|
17425
|
+
${this.clearable && this.value ? html54`
|
|
17426
|
+
<button
|
|
17427
|
+
class="icon-button"
|
|
17428
|
+
type="button"
|
|
17429
|
+
aria-label="Clear time"
|
|
17430
|
+
@click=${this.clearValue}
|
|
17431
|
+
>
|
|
17432
|
+
<svg viewBox="0 0 16 16" fill="currentColor">
|
|
17433
|
+
<path
|
|
17434
|
+
d="M4 4l8 8M12 4l-8 8"
|
|
17435
|
+
stroke="currentColor"
|
|
17436
|
+
stroke-width="2"
|
|
17437
|
+
stroke-linecap="round"
|
|
17438
|
+
/>
|
|
17439
|
+
</svg>
|
|
17440
|
+
</button>
|
|
17441
|
+
` : nothing11}
|
|
17442
|
+
<rel-icon part="icon" name="time-filled" size="sm"></rel-icon>
|
|
17443
|
+
</span>
|
|
17444
|
+
</button>
|
|
17445
|
+
|
|
17446
|
+
<div
|
|
17447
|
+
part="panel"
|
|
17448
|
+
class="popover ${this.open ? "open" : ""}"
|
|
17449
|
+
role="dialog"
|
|
17450
|
+
aria-label="Choose time"
|
|
17451
|
+
>
|
|
17452
|
+
<div class="time-readout">
|
|
17453
|
+
<span class="time-readout-label">Time</span>
|
|
17454
|
+
<span class="time-readout-value ${draftDisplay ? "" : "empty"}">
|
|
17455
|
+
${draftDisplay || (this.format === "24h" ? "--:--" : "--:-- --")}
|
|
17456
|
+
</span>
|
|
17457
|
+
</div>
|
|
17458
|
+
|
|
17459
|
+
<div class="columns">
|
|
17460
|
+
<div class="column">
|
|
17461
|
+
<div class="column-header" aria-hidden="true">Hour</div>
|
|
17462
|
+
<div class="column-list" role="listbox" aria-label="Hours">
|
|
17463
|
+
${hourOptions.map(
|
|
17464
|
+
(opt, i) => html54`
|
|
17465
|
+
<button
|
|
17466
|
+
part="option"
|
|
17467
|
+
class="option-button ${this.draftHour === opt.hour24 ? "selected" : ""}"
|
|
17468
|
+
type="button"
|
|
17469
|
+
role="option"
|
|
17470
|
+
aria-selected=${this.draftHour === opt.hour24}
|
|
17471
|
+
?disabled=${!this.isHourEnabled(opt.hour24)}
|
|
17472
|
+
@click=${() => this.selectHour(opt.hour24)}
|
|
17473
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "hour")}
|
|
17474
|
+
tabindex=${this.draftHour === opt.hour24 || this.draftHour === null && i === 0 ? 0 : -1}
|
|
17475
|
+
>
|
|
17476
|
+
${opt.label}
|
|
17477
|
+
</button>
|
|
17478
|
+
`
|
|
17479
|
+
)}
|
|
17480
|
+
</div>
|
|
17481
|
+
</div>
|
|
17482
|
+
|
|
17483
|
+
<div class="column">
|
|
17484
|
+
<div class="column-header" aria-hidden="true">Min</div>
|
|
17485
|
+
<div class="column-list" role="listbox" aria-label="Minutes">
|
|
17486
|
+
${minuteOptions.map(
|
|
17487
|
+
(m, i) => html54`
|
|
17488
|
+
<button
|
|
17489
|
+
part="option"
|
|
17490
|
+
class="option-button ${this.draftMinute === m ? "selected" : ""}"
|
|
17491
|
+
type="button"
|
|
17492
|
+
role="option"
|
|
17493
|
+
aria-selected=${this.draftMinute === m}
|
|
17494
|
+
?disabled=${this.draftHour !== null && !this.isTimeInRange(this.draftHour, m)}
|
|
17495
|
+
@click=${() => this.selectMinute(m)}
|
|
17496
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "minute")}
|
|
17497
|
+
tabindex=${this.draftMinute === m || this.draftMinute === null && i === 0 ? 0 : -1}
|
|
17498
|
+
>
|
|
17499
|
+
${String(m).padStart(2, "0")}
|
|
17500
|
+
</button>
|
|
17501
|
+
`
|
|
17502
|
+
)}
|
|
17503
|
+
</div>
|
|
17504
|
+
</div>
|
|
17505
|
+
|
|
17506
|
+
${this.format === "12h" ? html54`
|
|
17507
|
+
<div class="column">
|
|
17508
|
+
<div class="column-header" aria-hidden="true">AM/PM</div>
|
|
17509
|
+
<div class="column-list" role="listbox" aria-label="AM or PM">
|
|
17510
|
+
${["AM", "PM"].map(
|
|
17511
|
+
(p) => html54`
|
|
17512
|
+
<button
|
|
17513
|
+
part="option"
|
|
17514
|
+
class="option-button ${draftPeriod === p ? "selected" : ""}"
|
|
17515
|
+
type="button"
|
|
17516
|
+
role="option"
|
|
17517
|
+
aria-selected=${draftPeriod === p}
|
|
17518
|
+
?disabled=${!this.isPeriodEnabled(p)}
|
|
17519
|
+
@click=${() => this.selectPeriod(p)}
|
|
17520
|
+
@keydown=${(e) => this.handleOptionKeydown(e, "period")}
|
|
17521
|
+
tabindex=${draftPeriod === p || draftPeriod === null && p === "AM" ? 0 : -1}
|
|
17522
|
+
>
|
|
17523
|
+
${p}
|
|
17524
|
+
</button>
|
|
17525
|
+
`
|
|
17526
|
+
)}
|
|
17527
|
+
</div>
|
|
17528
|
+
</div>
|
|
17529
|
+
` : nothing11}
|
|
17530
|
+
</div>
|
|
17531
|
+
|
|
17532
|
+
<div part="footer" class="time-footer">
|
|
17533
|
+
<rel-button
|
|
17534
|
+
variant="secondary"
|
|
17535
|
+
size="small"
|
|
17536
|
+
border="corner-radius"
|
|
17537
|
+
@click=${() => this.selectNow()}
|
|
16629
17538
|
>
|
|
16630
|
-
|
|
16631
|
-
</button
|
|
17539
|
+
Now
|
|
17540
|
+
</rel-button>
|
|
17541
|
+
<div class="footer-right">
|
|
17542
|
+
<rel-button
|
|
17543
|
+
variant="secondary"
|
|
17544
|
+
size="small"
|
|
17545
|
+
border="corner-radius"
|
|
17546
|
+
@click=${() => this.cancelPopover()}
|
|
17547
|
+
>
|
|
17548
|
+
Cancel
|
|
17549
|
+
</rel-button>
|
|
17550
|
+
<rel-button
|
|
17551
|
+
variant="primary"
|
|
17552
|
+
tone="default"
|
|
17553
|
+
size="small"
|
|
17554
|
+
border="corner-radius"
|
|
17555
|
+
?disabled=${this.draftHour === null}
|
|
17556
|
+
@click=${() => this.applySelection()}
|
|
17557
|
+
>
|
|
17558
|
+
Apply
|
|
17559
|
+
</rel-button>
|
|
17560
|
+
</div>
|
|
17561
|
+
</div>
|
|
17562
|
+
</div>
|
|
16632
17563
|
|
|
16633
|
-
|
|
17564
|
+
${belowText ? html54`<div
|
|
17565
|
+
part="helper"
|
|
17566
|
+
class="time-picker-support ${isError ? "error" : ""}"
|
|
17567
|
+
id=${describedById}
|
|
17568
|
+
>
|
|
17569
|
+
${belowText}
|
|
17570
|
+
</div>` : nothing11}
|
|
16634
17571
|
</div>
|
|
16635
17572
|
`;
|
|
16636
17573
|
}
|
|
16637
17574
|
};
|
|
16638
|
-
|
|
17575
|
+
RelTimePicker.styles = css51`
|
|
16639
17576
|
:host {
|
|
16640
17577
|
color: var(--rel-text-default);
|
|
16641
|
-
|
|
16642
|
-
bottom: 24px;
|
|
16643
|
-
left: 50%;
|
|
16644
|
-
transform: translateX(-50%);
|
|
16645
|
-
display: flex;
|
|
16646
|
-
justify-content: center;
|
|
16647
|
-
width: 100%;
|
|
16648
|
-
pointer-events: none;
|
|
16649
|
-
z-index: var(--rel-z-toast, 9000);
|
|
17578
|
+
display: block;
|
|
16650
17579
|
font-family: var(--rel-fonts-font-family);
|
|
17580
|
+
position: relative;
|
|
16651
17581
|
}
|
|
16652
17582
|
|
|
16653
|
-
/*
|
|
16654
|
-
|
|
16655
|
-
display: inline-
|
|
16656
|
-
|
|
17583
|
+
/* Intrinsic width by default; set fullWidth to fill the parent. */
|
|
17584
|
+
:host(:not([fullwidth])) {
|
|
17585
|
+
display: inline-block;
|
|
17586
|
+
width: auto;
|
|
17587
|
+
}
|
|
17588
|
+
|
|
17589
|
+
:host([disabled]) {
|
|
17590
|
+
opacity: 0.6;
|
|
17591
|
+
cursor: not-allowed;
|
|
17592
|
+
}
|
|
17593
|
+
|
|
17594
|
+
:host([disabled]) * {
|
|
17595
|
+
pointer-events: none;
|
|
17596
|
+
}
|
|
17597
|
+
|
|
17598
|
+
:host([readonly]) .trigger-button {
|
|
17599
|
+
cursor: default;
|
|
17600
|
+
}
|
|
17601
|
+
|
|
17602
|
+
.time-picker-wrapper {
|
|
17603
|
+
display: flex;
|
|
17604
|
+
flex-direction: column;
|
|
16657
17605
|
gap: var(--rel-spacing-xs);
|
|
16658
|
-
|
|
16659
|
-
color: var(--rel-text-inverse);
|
|
17606
|
+
}
|
|
16660
17607
|
|
|
16661
|
-
|
|
17608
|
+
.label {
|
|
17609
|
+
color: var(--rel-text-label);
|
|
17610
|
+
font-family: var(--rel-body-sm-medium-font-family);
|
|
17611
|
+
font-weight: var(--rel-body-sm-medium-font-weight);
|
|
17612
|
+
font-size: var(--rel-body-sm-medium-font-size);
|
|
17613
|
+
line-height: var(--rel-body-sm-medium-line-height);
|
|
17614
|
+
letter-spacing: var(--rel-body-sm-medium-letter-spacing);
|
|
17615
|
+
}
|
|
17616
|
+
|
|
17617
|
+
.required-mark {
|
|
17618
|
+
color: var(--rel-text-danger);
|
|
17619
|
+
margin-left: 2px;
|
|
17620
|
+
}
|
|
17621
|
+
|
|
17622
|
+
/* Support text (helperText / errorMessage) */
|
|
17623
|
+
.time-picker-support {
|
|
17624
|
+
color: var(--rel-text-placeholder);
|
|
17625
|
+
font-family: var(--rel-body-xs-regular-font-family);
|
|
17626
|
+
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
17627
|
+
font-size: var(--rel-body-xs-regular-font-size);
|
|
17628
|
+
line-height: var(--rel-body-xs-regular-line-height);
|
|
17629
|
+
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
17630
|
+
}
|
|
17631
|
+
|
|
17632
|
+
.time-picker-support.error {
|
|
17633
|
+
color: var(--rel-text-danger);
|
|
17634
|
+
}
|
|
17635
|
+
|
|
17636
|
+
.trigger-button {
|
|
17637
|
+
width: 100%;
|
|
17638
|
+
min-width: 160px;
|
|
17639
|
+
display: flex;
|
|
17640
|
+
align-items: center;
|
|
17641
|
+
justify-content: space-between;
|
|
17642
|
+
gap: var(--rel-spacing-xs);
|
|
16662
17643
|
padding: var(--rel-spacing-sm) var(--rel-spacing-md);
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
17644
|
+
background: var(--rel-bg-surface);
|
|
17645
|
+
border: 1px solid var(--rel-border-default);
|
|
17646
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17647
|
+
color: var(--rel-text-default);
|
|
17648
|
+
cursor: pointer;
|
|
17649
|
+
transition: all 0.15s ease;
|
|
17650
|
+
text-align: left;
|
|
16666
17651
|
font-family: var(--rel-body-md-regular-font-family);
|
|
16667
17652
|
font-weight: var(--rel-body-md-regular-font-weight);
|
|
16668
17653
|
font-size: var(--rel-body-md-regular-font-size);
|
|
@@ -16670,276 +17655,1605 @@ RelToast.styles = css51`
|
|
|
16670
17655
|
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
16671
17656
|
}
|
|
16672
17657
|
|
|
16673
|
-
.
|
|
16674
|
-
|
|
16675
|
-
|
|
17658
|
+
.trigger-button:hover:not(:disabled) {
|
|
17659
|
+
border-color: var(--rel-border-strong);
|
|
17660
|
+
}
|
|
17661
|
+
|
|
17662
|
+
.trigger-button:focus-visible {
|
|
17663
|
+
outline: 2px solid var(--rel-border-focus);
|
|
17664
|
+
outline-offset: 2px;
|
|
17665
|
+
border-color: var(--rel-border-focus);
|
|
17666
|
+
}
|
|
17667
|
+
|
|
17668
|
+
.trigger-button:disabled {
|
|
17669
|
+
cursor: not-allowed;
|
|
17670
|
+
background: var(--rel-bg-surface-elevated);
|
|
17671
|
+
}
|
|
17672
|
+
|
|
17673
|
+
/* Error state */
|
|
17674
|
+
.trigger-button.error,
|
|
17675
|
+
.trigger-button.error:hover:not(:disabled) {
|
|
17676
|
+
border-color: var(--rel-border-danger);
|
|
17677
|
+
}
|
|
17678
|
+
|
|
17679
|
+
.trigger-button.error:focus-visible {
|
|
17680
|
+
outline-color: var(--rel-border-danger);
|
|
17681
|
+
border-color: var(--rel-border-danger);
|
|
17682
|
+
}
|
|
17683
|
+
|
|
17684
|
+
/* Size variants (medium is the base .trigger-button style) */
|
|
17685
|
+
.time-picker-wrapper.small .trigger-button {
|
|
17686
|
+
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
17687
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
17688
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
17689
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
17690
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
17691
|
+
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
17692
|
+
}
|
|
17693
|
+
|
|
17694
|
+
.time-picker-wrapper.large .trigger-button {
|
|
17695
|
+
padding: var(--rel-spacing-md) var(--rel-spacing-md);
|
|
17696
|
+
font-family: var(--rel-body-lg-regular-font-family);
|
|
17697
|
+
font-weight: var(--rel-body-lg-regular-font-weight);
|
|
17698
|
+
font-size: var(--rel-body-lg-regular-font-size);
|
|
17699
|
+
line-height: var(--rel-body-lg-regular-line-height);
|
|
17700
|
+
letter-spacing: var(--rel-body-lg-regular-letter-spacing);
|
|
17701
|
+
}
|
|
17702
|
+
|
|
17703
|
+
.trigger-content {
|
|
17704
|
+
flex: 1;
|
|
17705
|
+
color: var(--rel-text-default);
|
|
17706
|
+
}
|
|
17707
|
+
|
|
17708
|
+
.trigger-content.placeholder {
|
|
17709
|
+
color: var(--rel-text-placeholder);
|
|
17710
|
+
}
|
|
17711
|
+
|
|
17712
|
+
.trigger-icons {
|
|
17713
|
+
display: flex;
|
|
17714
|
+
align-items: center;
|
|
17715
|
+
gap: var(--rel-spacing-2xs);
|
|
17716
|
+
color: var(--rel-text-placeholder);
|
|
17717
|
+
}
|
|
17718
|
+
|
|
17719
|
+
.icon-button {
|
|
17720
|
+
background: none;
|
|
17721
|
+
border: none;
|
|
17722
|
+
padding: var(--rel-spacing-3xs);
|
|
17723
|
+
cursor: pointer;
|
|
17724
|
+
color: var(--rel-text-placeholder);
|
|
17725
|
+
display: flex;
|
|
17726
|
+
align-items: center;
|
|
17727
|
+
justify-content: center;
|
|
17728
|
+
border-radius: var(--rel-border-radius-xs);
|
|
17729
|
+
transition: all 0.15s ease;
|
|
17730
|
+
}
|
|
17731
|
+
|
|
17732
|
+
.icon-button:hover {
|
|
17733
|
+
background: var(--rel-bg-surface-elevated);
|
|
17734
|
+
color: var(--rel-text-default);
|
|
17735
|
+
}
|
|
17736
|
+
|
|
17737
|
+
.icon-button svg {
|
|
17738
|
+
width: 16px;
|
|
17739
|
+
height: 16px;
|
|
17740
|
+
}
|
|
17741
|
+
|
|
17742
|
+
.popover {
|
|
17743
|
+
position: absolute;
|
|
17744
|
+
top: calc(100% + 4px);
|
|
17745
|
+
left: 0;
|
|
17746
|
+
z-index: 1000;
|
|
17747
|
+
background: var(--rel-bg-surface);
|
|
17748
|
+
border: 1px solid var(--rel-border-default);
|
|
17749
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17750
|
+
box-shadow: var(--rel-shadows-shadow-300);
|
|
17751
|
+
padding: var(--rel-spacing-md);
|
|
17752
|
+
display: none;
|
|
17753
|
+
}
|
|
17754
|
+
|
|
17755
|
+
.popover.open {
|
|
17756
|
+
display: block;
|
|
17757
|
+
}
|
|
17758
|
+
|
|
17759
|
+
/* Draft readout (mirrors the range-picker FROM/TO block) */
|
|
17760
|
+
.time-readout {
|
|
17761
|
+
display: flex;
|
|
17762
|
+
align-items: baseline;
|
|
17763
|
+
gap: var(--rel-spacing-sm);
|
|
17764
|
+
padding-bottom: var(--rel-spacing-sm);
|
|
17765
|
+
margin-bottom: var(--rel-spacing-sm);
|
|
17766
|
+
border-bottom: 1px solid var(--rel-border-subtle);
|
|
17767
|
+
}
|
|
17768
|
+
|
|
17769
|
+
.time-readout-label {
|
|
17770
|
+
color: var(--rel-text-placeholder);
|
|
17771
|
+
text-transform: uppercase;
|
|
17772
|
+
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17773
|
+
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17774
|
+
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17775
|
+
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17776
|
+
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
17777
|
+
}
|
|
17778
|
+
|
|
17779
|
+
.time-readout-value {
|
|
17780
|
+
color: var(--rel-text-default);
|
|
17781
|
+
font-family: var(--rel-body-md-semibold-font-family);
|
|
17782
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
17783
|
+
font-size: var(--rel-body-md-semibold-font-size);
|
|
17784
|
+
line-height: var(--rel-body-md-semibold-line-height);
|
|
17785
|
+
letter-spacing: var(--rel-body-md-semibold-letter-spacing);
|
|
17786
|
+
}
|
|
17787
|
+
|
|
17788
|
+
.time-readout-value.empty {
|
|
17789
|
+
color: var(--rel-text-placeholder);
|
|
17790
|
+
}
|
|
17791
|
+
|
|
17792
|
+
.columns {
|
|
17793
|
+
display: flex;
|
|
17794
|
+
gap: var(--rel-spacing-xs);
|
|
17795
|
+
}
|
|
17796
|
+
|
|
17797
|
+
/* Columns share the panel width equally, so the two-column 24h layout
|
|
17798
|
+
fills the same footprint the footer dictates instead of bunching left. */
|
|
17799
|
+
.column {
|
|
17800
|
+
display: flex;
|
|
17801
|
+
flex-direction: column;
|
|
17802
|
+
flex: 1;
|
|
17803
|
+
min-width: 64px;
|
|
17804
|
+
}
|
|
17805
|
+
|
|
17806
|
+
.column-header {
|
|
17807
|
+
text-align: center;
|
|
17808
|
+
color: var(--rel-text-placeholder);
|
|
17809
|
+
padding: var(--rel-spacing-xs);
|
|
17810
|
+
text-transform: uppercase;
|
|
17811
|
+
font-family: var(--rel-body-xs-semibold-font-family);
|
|
17812
|
+
font-weight: var(--rel-body-xs-semibold-font-weight);
|
|
17813
|
+
font-size: var(--rel-body-xs-semibold-font-size);
|
|
17814
|
+
line-height: var(--rel-body-xs-semibold-line-height);
|
|
17815
|
+
letter-spacing: var(--rel-body-xs-semibold-letter-spacing);
|
|
17816
|
+
}
|
|
17817
|
+
|
|
17818
|
+
.column-list {
|
|
17819
|
+
display: flex;
|
|
17820
|
+
flex-direction: column;
|
|
17821
|
+
gap: var(--rel-spacing-3xs);
|
|
17822
|
+
max-height: 224px;
|
|
17823
|
+
overflow-y: auto;
|
|
17824
|
+
padding: var(--rel-spacing-3xs);
|
|
17825
|
+
scrollbar-width: thin;
|
|
17826
|
+
}
|
|
17827
|
+
|
|
17828
|
+
.option-button {
|
|
17829
|
+
border: none;
|
|
17830
|
+
background: transparent;
|
|
17831
|
+
border-radius: var(--rel-border-radius-sm);
|
|
17832
|
+
color: var(--rel-text-default);
|
|
17833
|
+
cursor: pointer;
|
|
17834
|
+
padding: var(--rel-spacing-xs) var(--rel-spacing-sm);
|
|
17835
|
+
text-align: center;
|
|
17836
|
+
flex-shrink: 0;
|
|
17837
|
+
transition: all 0.15s ease;
|
|
17838
|
+
font-family: var(--rel-body-sm-medium-font-family);
|
|
17839
|
+
font-weight: var(--rel-body-sm-medium-font-weight);
|
|
17840
|
+
font-size: var(--rel-body-sm-medium-font-size);
|
|
17841
|
+
line-height: var(--rel-body-sm-medium-line-height);
|
|
17842
|
+
letter-spacing: var(--rel-body-sm-medium-letter-spacing);
|
|
17843
|
+
}
|
|
17844
|
+
|
|
17845
|
+
.option-button:hover:not(:disabled):not(.selected) {
|
|
17846
|
+
background: var(--rel-bg-surface-elevated);
|
|
17847
|
+
}
|
|
17848
|
+
|
|
17849
|
+
.option-button:focus-visible {
|
|
17850
|
+
outline: 2px solid var(--rel-border-focus);
|
|
17851
|
+
outline-offset: -2px;
|
|
17852
|
+
z-index: 1;
|
|
17853
|
+
}
|
|
17854
|
+
|
|
17855
|
+
.option-button.selected {
|
|
17856
|
+
background: var(--rel-action-brand-fill);
|
|
17857
|
+
color: var(--rel-action-brand-label-icons-primary);
|
|
17858
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
17859
|
+
}
|
|
17860
|
+
|
|
17861
|
+
.option-button:disabled {
|
|
17862
|
+
color: var(--rel-text-disabled);
|
|
17863
|
+
cursor: not-allowed;
|
|
17864
|
+
opacity: 0.5;
|
|
17865
|
+
}
|
|
17866
|
+
|
|
17867
|
+
.option-button:disabled:hover {
|
|
17868
|
+
background: transparent;
|
|
17869
|
+
}
|
|
17870
|
+
|
|
17871
|
+
/* Footer (mirrors the range-picker calendar-footer) */
|
|
17872
|
+
.time-footer {
|
|
17873
|
+
display: flex;
|
|
17874
|
+
align-items: center;
|
|
17875
|
+
justify-content: space-between;
|
|
17876
|
+
gap: var(--rel-spacing-sm);
|
|
17877
|
+
padding-top: var(--rel-spacing-sm);
|
|
17878
|
+
margin-top: var(--rel-spacing-sm);
|
|
17879
|
+
border-top: 1px solid var(--rel-border-subtle);
|
|
17880
|
+
}
|
|
17881
|
+
|
|
17882
|
+
.footer-right {
|
|
17883
|
+
display: flex;
|
|
17884
|
+
align-items: center;
|
|
17885
|
+
gap: var(--rel-spacing-xs);
|
|
17886
|
+
}
|
|
17887
|
+
|
|
17888
|
+
@media (prefers-reduced-motion: reduce) {
|
|
17889
|
+
.trigger-button,
|
|
17890
|
+
.icon-button,
|
|
17891
|
+
.option-button {
|
|
17892
|
+
transition: none;
|
|
17893
|
+
}
|
|
17894
|
+
}
|
|
17895
|
+
`;
|
|
17896
|
+
__decorateClass([
|
|
17897
|
+
property48({ type: String, reflect: true })
|
|
17898
|
+
], RelTimePicker.prototype, "value", 2);
|
|
17899
|
+
__decorateClass([
|
|
17900
|
+
property48({ type: String })
|
|
17901
|
+
], RelTimePicker.prototype, "placeholder", 2);
|
|
17902
|
+
__decorateClass([
|
|
17903
|
+
property48({ type: String })
|
|
17904
|
+
], RelTimePicker.prototype, "label", 2);
|
|
17905
|
+
__decorateClass([
|
|
17906
|
+
property48({ type: Boolean, reflect: true })
|
|
17907
|
+
], RelTimePicker.prototype, "disabled", 2);
|
|
17908
|
+
__decorateClass([
|
|
17909
|
+
property48({ type: Boolean })
|
|
17910
|
+
], RelTimePicker.prototype, "required", 2);
|
|
17911
|
+
__decorateClass([
|
|
17912
|
+
property48({ type: Boolean, reflect: true })
|
|
17913
|
+
], RelTimePicker.prototype, "readonly", 2);
|
|
17914
|
+
__decorateClass([
|
|
17915
|
+
property48({ type: String })
|
|
17916
|
+
], RelTimePicker.prototype, "format", 2);
|
|
17917
|
+
__decorateClass([
|
|
17918
|
+
property48({ type: Number, attribute: "minute-step" })
|
|
17919
|
+
], RelTimePicker.prototype, "minuteStep", 2);
|
|
17920
|
+
__decorateClass([
|
|
17921
|
+
property48({ type: String, attribute: "min-time" })
|
|
17922
|
+
], RelTimePicker.prototype, "minTime", 2);
|
|
17923
|
+
__decorateClass([
|
|
17924
|
+
property48({ type: String, attribute: "max-time" })
|
|
17925
|
+
], RelTimePicker.prototype, "maxTime", 2);
|
|
17926
|
+
__decorateClass([
|
|
17927
|
+
property48({ type: Boolean, reflect: true })
|
|
17928
|
+
], RelTimePicker.prototype, "open", 2);
|
|
17929
|
+
__decorateClass([
|
|
17930
|
+
property48({ type: Boolean })
|
|
17931
|
+
], RelTimePicker.prototype, "clearable", 2);
|
|
17932
|
+
__decorateClass([
|
|
17933
|
+
property48({ type: Boolean })
|
|
17934
|
+
], RelTimePicker.prototype, "error", 2);
|
|
17935
|
+
__decorateClass([
|
|
17936
|
+
property48({ type: Boolean })
|
|
17937
|
+
], RelTimePicker.prototype, "invalid", 2);
|
|
17938
|
+
__decorateClass([
|
|
17939
|
+
property48({ type: String, attribute: "error-message" })
|
|
17940
|
+
], RelTimePicker.prototype, "errorMessage", 2);
|
|
17941
|
+
__decorateClass([
|
|
17942
|
+
property48({ type: String, attribute: "helper-text" })
|
|
17943
|
+
], RelTimePicker.prototype, "helperText", 2);
|
|
17944
|
+
__decorateClass([
|
|
17945
|
+
property48({ type: String })
|
|
17946
|
+
], RelTimePicker.prototype, "name", 2);
|
|
17947
|
+
__decorateClass([
|
|
17948
|
+
property48({ type: String })
|
|
17949
|
+
], RelTimePicker.prototype, "size", 2);
|
|
17950
|
+
__decorateClass([
|
|
17951
|
+
property48({ type: String, reflect: true })
|
|
17952
|
+
], RelTimePicker.prototype, "variant", 2);
|
|
17953
|
+
__decorateClass([
|
|
17954
|
+
property48({ type: Boolean, reflect: true })
|
|
17955
|
+
], RelTimePicker.prototype, "fullWidth", 2);
|
|
17956
|
+
__decorateClass([
|
|
17957
|
+
state21()
|
|
17958
|
+
], RelTimePicker.prototype, "draftHour", 2);
|
|
17959
|
+
__decorateClass([
|
|
17960
|
+
state21()
|
|
17961
|
+
], RelTimePicker.prototype, "draftMinute", 2);
|
|
17962
|
+
__decorateClass([
|
|
17963
|
+
query10(".trigger-button")
|
|
17964
|
+
], RelTimePicker.prototype, "triggerButton", 2);
|
|
17965
|
+
__decorateClass([
|
|
17966
|
+
query10(".columns")
|
|
17967
|
+
], RelTimePicker.prototype, "columnsElement", 2);
|
|
17968
|
+
RelTimePicker = __decorateClass([
|
|
17969
|
+
customElement54("rel-time-picker")
|
|
17970
|
+
], RelTimePicker);
|
|
17971
|
+
|
|
17972
|
+
// src/components/rel-toast/rel-toast.ts
|
|
17973
|
+
import { LitElement as LitElement55, html as html55, css as css52 } from "lit";
|
|
17974
|
+
import { customElement as customElement55, property as property49, state as state22 } from "lit/decorators.js";
|
|
17975
|
+
var RelToast = class extends LitElement55 {
|
|
17976
|
+
constructor() {
|
|
17977
|
+
super(...arguments);
|
|
17978
|
+
this.message = "";
|
|
17979
|
+
this.type = "default";
|
|
17980
|
+
this.duration = 4e3;
|
|
17981
|
+
this.show = false;
|
|
17982
|
+
this.visible = false;
|
|
17983
|
+
this._dismissTimer = null;
|
|
17984
|
+
}
|
|
17985
|
+
willUpdate(changed) {
|
|
17986
|
+
if (!changed.has("show")) return;
|
|
17987
|
+
if (this.show) {
|
|
17988
|
+
this.visible = true;
|
|
17989
|
+
if (this._dismissTimer) clearTimeout(this._dismissTimer);
|
|
17990
|
+
if (this.duration > 0) {
|
|
17991
|
+
this._dismissTimer = setTimeout(() => this.close(), this.duration);
|
|
17992
|
+
}
|
|
17993
|
+
} else {
|
|
17994
|
+
this.visible = false;
|
|
17995
|
+
if (this._dismissTimer) {
|
|
17996
|
+
clearTimeout(this._dismissTimer);
|
|
17997
|
+
this._dismissTimer = null;
|
|
17998
|
+
}
|
|
17999
|
+
}
|
|
18000
|
+
}
|
|
18001
|
+
disconnectedCallback() {
|
|
18002
|
+
super.disconnectedCallback();
|
|
18003
|
+
if (this._dismissTimer) clearTimeout(this._dismissTimer);
|
|
18004
|
+
}
|
|
18005
|
+
open() {
|
|
18006
|
+
this.show = true;
|
|
18007
|
+
}
|
|
18008
|
+
close() {
|
|
18009
|
+
this.show = false;
|
|
18010
|
+
this.visible = false;
|
|
18011
|
+
if (this._dismissTimer) {
|
|
18012
|
+
clearTimeout(this._dismissTimer);
|
|
18013
|
+
this._dismissTimer = null;
|
|
18014
|
+
}
|
|
18015
|
+
this.dispatchEvent(
|
|
18016
|
+
new CustomEvent("dismissed", {
|
|
18017
|
+
bubbles: true,
|
|
18018
|
+
composed: true
|
|
18019
|
+
})
|
|
18020
|
+
);
|
|
18021
|
+
}
|
|
18022
|
+
handleActionClick(event) {
|
|
18023
|
+
event.stopPropagation();
|
|
18024
|
+
this.dispatchEvent(
|
|
18025
|
+
new CustomEvent("action", {
|
|
18026
|
+
bubbles: true,
|
|
18027
|
+
composed: true
|
|
18028
|
+
})
|
|
18029
|
+
);
|
|
18030
|
+
this.close();
|
|
18031
|
+
}
|
|
18032
|
+
render() {
|
|
18033
|
+
return html55`
|
|
18034
|
+
<div part="base" class="toast ${this.type} ${this.visible ? "" : "hidden"}" role="status" aria-live="polite">
|
|
18035
|
+
<span part="label" class="toast-message">${this.message}</span>
|
|
18036
|
+
|
|
18037
|
+
${this.actionLabel ? html55`<button
|
|
18038
|
+
part="action" class="toast-action"
|
|
18039
|
+
@click="${this.handleActionClick}"
|
|
18040
|
+
>
|
|
18041
|
+
${this.actionLabel}
|
|
18042
|
+
</button>` : ""}
|
|
18043
|
+
|
|
18044
|
+
<button part="close-button" class="toast-close" @click="${this.close}" aria-label="Dismiss">✕</button>
|
|
18045
|
+
</div>
|
|
18046
|
+
`;
|
|
18047
|
+
}
|
|
18048
|
+
};
|
|
18049
|
+
RelToast.styles = css52`
|
|
18050
|
+
:host {
|
|
18051
|
+
color: var(--rel-text-default);
|
|
18052
|
+
position: fixed;
|
|
18053
|
+
bottom: 24px;
|
|
18054
|
+
left: 50%;
|
|
18055
|
+
transform: translateX(-50%);
|
|
18056
|
+
display: flex;
|
|
18057
|
+
justify-content: center;
|
|
18058
|
+
width: 100%;
|
|
18059
|
+
pointer-events: none;
|
|
18060
|
+
z-index: var(--rel-z-toast, 9000);
|
|
18061
|
+
font-family: var(--rel-fonts-font-family);
|
|
18062
|
+
}
|
|
18063
|
+
|
|
18064
|
+
/* Toast container */
|
|
18065
|
+
.toast {
|
|
18066
|
+
display: inline-flex;
|
|
18067
|
+
align-items: center;
|
|
18068
|
+
gap: var(--rel-spacing-xs);
|
|
18069
|
+
background-color: var(--rel-bg-neutral-inverse-1);
|
|
18070
|
+
color: var(--rel-text-inverse);
|
|
18071
|
+
|
|
18072
|
+
border-radius: var(--rel-border-radius-sm);
|
|
18073
|
+
padding: var(--rel-spacing-sm) var(--rel-spacing-md);
|
|
18074
|
+
box-shadow: var(--rel-shadows-shadow-300);
|
|
18075
|
+
pointer-events: all;
|
|
18076
|
+
animation: fadeInUp 0.25s ease;
|
|
18077
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
18078
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
18079
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
18080
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
18081
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
18082
|
+
}
|
|
18083
|
+
|
|
18084
|
+
.toast.error {
|
|
18085
|
+
background-color: var(--rel-bg-danger);
|
|
18086
|
+
color: var(--rel-text-static-light);
|
|
18087
|
+
}
|
|
18088
|
+
|
|
18089
|
+
.toast.hidden {
|
|
18090
|
+
display: none;
|
|
18091
|
+
}
|
|
18092
|
+
|
|
18093
|
+
/* Message */
|
|
18094
|
+
.toast-message {
|
|
18095
|
+
flex: 1;
|
|
18096
|
+
}
|
|
18097
|
+
|
|
18098
|
+
/* Action button */
|
|
18099
|
+
.toast-action {
|
|
18100
|
+
background: transparent;
|
|
18101
|
+
border: none;
|
|
18102
|
+
color: var(--rel-action-neutral-label-icons-fill-inverse);
|
|
18103
|
+
font-family: var(--rel-action-md-font-family);
|
|
18104
|
+
font-weight: var(--rel-action-md-font-weight);
|
|
18105
|
+
font-size: var(--rel-action-md-font-size);
|
|
18106
|
+
line-height: var(--rel-action-md-line-height);
|
|
18107
|
+
letter-spacing: var(--rel-action-md-letter-spacing);
|
|
18108
|
+
cursor: pointer;
|
|
18109
|
+
transition: opacity 0.2s;
|
|
18110
|
+
}
|
|
18111
|
+
|
|
18112
|
+
.toast-action:hover {
|
|
18113
|
+
background: var(--rel-bg-neutral);
|
|
18114
|
+
color: var(--rel-text-default);
|
|
18115
|
+
opacity: 0.75;
|
|
18116
|
+
}
|
|
18117
|
+
|
|
18118
|
+
.toast-action:focus-visible {
|
|
18119
|
+
outline: 2px solid var(--rel-text-static-light);
|
|
18120
|
+
outline-offset: 2px;
|
|
18121
|
+
border-radius: var(--rel-border-radius-xs);
|
|
18122
|
+
}
|
|
18123
|
+
|
|
18124
|
+
/* Close button */
|
|
18125
|
+
.toast-close {
|
|
18126
|
+
background: transparent;
|
|
18127
|
+
color: var(--rel-action-neutral-label-icons-ghost);
|
|
18128
|
+
border: none;
|
|
18129
|
+
color: inherit;
|
|
18130
|
+
font-size: 1rem;
|
|
18131
|
+
cursor: pointer;
|
|
18132
|
+
line-height: 1;
|
|
18133
|
+
padding: var(--rel-spacing-2xs);
|
|
18134
|
+
margin-left: var(--rel-spacing-2xs);
|
|
18135
|
+
}
|
|
18136
|
+
|
|
18137
|
+
.toast-close:hover {
|
|
18138
|
+
opacity: 0.75;
|
|
18139
|
+
}
|
|
18140
|
+
|
|
18141
|
+
.toast-close:focus-visible {
|
|
18142
|
+
outline: 2px solid var(--rel-text-static-light);
|
|
18143
|
+
outline-offset: 2px;
|
|
18144
|
+
border-radius: var(--rel-border-radius-xs);
|
|
18145
|
+
}
|
|
18146
|
+
|
|
18147
|
+
|
|
18148
|
+
@media (prefers-reduced-motion: reduce) {
|
|
18149
|
+
.toast {
|
|
18150
|
+
animation: none;
|
|
18151
|
+
}
|
|
18152
|
+
}
|
|
18153
|
+
|
|
18154
|
+
/* Animations */
|
|
18155
|
+
@keyframes fadeInUp {
|
|
18156
|
+
from {
|
|
18157
|
+
opacity: 0;
|
|
18158
|
+
transform: translateY(20px);
|
|
18159
|
+
}
|
|
18160
|
+
to {
|
|
18161
|
+
opacity: 1;
|
|
18162
|
+
transform: translateY(0);
|
|
18163
|
+
}
|
|
18164
|
+
}
|
|
18165
|
+
`;
|
|
18166
|
+
__decorateClass([
|
|
18167
|
+
property49({ type: String })
|
|
18168
|
+
], RelToast.prototype, "message", 2);
|
|
18169
|
+
__decorateClass([
|
|
18170
|
+
property49({ type: String })
|
|
18171
|
+
], RelToast.prototype, "type", 2);
|
|
18172
|
+
__decorateClass([
|
|
18173
|
+
property49({ type: String })
|
|
18174
|
+
], RelToast.prototype, "actionLabel", 2);
|
|
18175
|
+
__decorateClass([
|
|
18176
|
+
property49({ type: Number })
|
|
18177
|
+
], RelToast.prototype, "duration", 2);
|
|
18178
|
+
__decorateClass([
|
|
18179
|
+
property49({ type: Boolean, reflect: true })
|
|
18180
|
+
], RelToast.prototype, "show", 2);
|
|
18181
|
+
__decorateClass([
|
|
18182
|
+
state22()
|
|
18183
|
+
], RelToast.prototype, "visible", 2);
|
|
18184
|
+
RelToast = __decorateClass([
|
|
18185
|
+
customElement55("rel-toast")
|
|
18186
|
+
], RelToast);
|
|
18187
|
+
|
|
18188
|
+
// src/components/rel-tooltip/rel-tooltip.ts
|
|
18189
|
+
import { LitElement as LitElement56, html as html56, css as css53 } from "lit";
|
|
18190
|
+
import { customElement as customElement56, property as property50 } from "lit/decorators.js";
|
|
18191
|
+
var RelTooltip = class extends LitElement56 {
|
|
18192
|
+
constructor() {
|
|
18193
|
+
super(...arguments);
|
|
18194
|
+
this.text = "";
|
|
18195
|
+
this.position = "above";
|
|
18196
|
+
this.showTail = false;
|
|
18197
|
+
this.type = "single-line";
|
|
18198
|
+
// Stable id so aria-describedby can reference the bubble across renders.
|
|
18199
|
+
this.uniqueId = Math.random().toString(36).slice(2);
|
|
18200
|
+
}
|
|
18201
|
+
firstUpdated() {
|
|
18202
|
+
this.linkTrigger();
|
|
18203
|
+
}
|
|
18204
|
+
linkTrigger() {
|
|
18205
|
+
const slot = this.shadowRoot?.querySelector("slot");
|
|
18206
|
+
if (!slot) return;
|
|
18207
|
+
const trigger = slot.assignedElements()[0];
|
|
18208
|
+
if (trigger) {
|
|
18209
|
+
trigger.setAttribute("aria-describedby", `tooltip-${this.uniqueId}`);
|
|
18210
|
+
}
|
|
18211
|
+
}
|
|
18212
|
+
render() {
|
|
18213
|
+
return html56`
|
|
18214
|
+
<slot @slotchange="${this.linkTrigger}"></slot>
|
|
18215
|
+
<div
|
|
18216
|
+
part="panel" class="tooltip-bubble ${this.position}"
|
|
18217
|
+
id="tooltip-${this.uniqueId}"
|
|
18218
|
+
role="tooltip"
|
|
18219
|
+
>
|
|
18220
|
+
${this.text}
|
|
18221
|
+
${this.showTail ? html56`<span part="indicator" class="tooltip-tail"></span>` : ""}
|
|
18222
|
+
</div>
|
|
18223
|
+
`;
|
|
18224
|
+
}
|
|
18225
|
+
};
|
|
18226
|
+
RelTooltip.styles = css53`
|
|
18227
|
+
:host {
|
|
18228
|
+
color: var(--rel-text-default);
|
|
18229
|
+
display: inline-block;
|
|
18230
|
+
position: relative;
|
|
18231
|
+
}
|
|
18232
|
+
|
|
18233
|
+
/* Tooltip container */
|
|
18234
|
+
.tooltip-bubble {
|
|
18235
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
18236
|
+
font-weight: var(--rel-body-md-medium-font-weight);
|
|
18237
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
18238
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
18239
|
+
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
18240
|
+
position: absolute;
|
|
18241
|
+
background-color: var(--rel-bg-neutral-inverse-2);
|
|
18242
|
+
color: var(--rel-text-inverse);
|
|
18243
|
+
padding: var(--rel-spacing-xs, 8px)
|
|
18244
|
+
var(--rel-spacing-sm, 12px);
|
|
18245
|
+
border-radius: var(--rel-border-radius-sm, 8px);
|
|
18246
|
+
white-space: nowrap;
|
|
18247
|
+
z-index: 1000;
|
|
18248
|
+
opacity: 0;
|
|
18249
|
+
pointer-events: none;
|
|
18250
|
+
transition: opacity 0.2s ease;
|
|
18251
|
+
overflow: visible;
|
|
18252
|
+
}
|
|
18253
|
+
|
|
18254
|
+
/* Figma Type=Multi Line: wrap inside a bounded bubble */
|
|
18255
|
+
:host([type="multi-line"]) .tooltip-bubble {
|
|
18256
|
+
white-space: normal;
|
|
18257
|
+
max-width: 220px;
|
|
18258
|
+
width: max-content;
|
|
18259
|
+
}
|
|
18260
|
+
|
|
18261
|
+
:host(:hover) .tooltip-bubble,
|
|
18262
|
+
:host(:focus-within) .tooltip-bubble {
|
|
18263
|
+
opacity: 1;
|
|
18264
|
+
}
|
|
18265
|
+
|
|
18266
|
+
/* Positioning */
|
|
18267
|
+
.tooltip-bubble.above {
|
|
18268
|
+
bottom: calc(100% + 6px);
|
|
18269
|
+
left: 50%;
|
|
18270
|
+
transform: translateX(-50%);
|
|
18271
|
+
}
|
|
18272
|
+
|
|
18273
|
+
.tooltip-bubble.below {
|
|
18274
|
+
top: calc(100% + 6px);
|
|
18275
|
+
left: 50%;
|
|
18276
|
+
transform: translateX(-50%);
|
|
18277
|
+
}
|
|
18278
|
+
|
|
18279
|
+
.tooltip-bubble.left {
|
|
18280
|
+
right: calc(100% + 6px);
|
|
18281
|
+
top: 50%;
|
|
18282
|
+
transform: translateY(-50%);
|
|
18283
|
+
}
|
|
18284
|
+
|
|
18285
|
+
.tooltip-bubble.right {
|
|
18286
|
+
left: calc(100% + 6px);
|
|
18287
|
+
top: 50%;
|
|
18288
|
+
transform: translateY(-50%);
|
|
18289
|
+
}
|
|
18290
|
+
|
|
18291
|
+
/* Tail/Arrow */
|
|
18292
|
+
.tooltip-tail {
|
|
18293
|
+
position: absolute;
|
|
18294
|
+
width: 0;
|
|
18295
|
+
height: 0;
|
|
18296
|
+
display: block;
|
|
18297
|
+
}
|
|
18298
|
+
|
|
18299
|
+
.tooltip-bubble.above .tooltip-tail,
|
|
18300
|
+
.tooltip-bubble.below .tooltip-tail {
|
|
18301
|
+
left: 50%;
|
|
18302
|
+
transform: translateX(-50%);
|
|
18303
|
+
border-left: 6px solid transparent;
|
|
18304
|
+
border-right: 6px solid transparent;
|
|
18305
|
+
}
|
|
18306
|
+
|
|
18307
|
+
.tooltip-bubble.above .tooltip-tail {
|
|
18308
|
+
bottom: -6px;
|
|
18309
|
+
border-top: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
18310
|
+
border-bottom: none;
|
|
18311
|
+
}
|
|
18312
|
+
|
|
18313
|
+
.tooltip-bubble.below .tooltip-tail {
|
|
18314
|
+
top: -6px;
|
|
18315
|
+
border-bottom: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
18316
|
+
border-top: none;
|
|
18317
|
+
}
|
|
18318
|
+
|
|
18319
|
+
.tooltip-bubble.left .tooltip-tail,
|
|
18320
|
+
.tooltip-bubble.right .tooltip-tail {
|
|
18321
|
+
top: 50%;
|
|
18322
|
+
transform: translateY(-50%);
|
|
18323
|
+
border-top: 6px solid transparent;
|
|
18324
|
+
border-bottom: 6px solid transparent;
|
|
18325
|
+
}
|
|
18326
|
+
|
|
18327
|
+
.tooltip-bubble.left .tooltip-tail {
|
|
18328
|
+
right: -6px;
|
|
18329
|
+
border-left: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
18330
|
+
border-right: none;
|
|
18331
|
+
}
|
|
18332
|
+
|
|
18333
|
+
.tooltip-bubble.right .tooltip-tail {
|
|
18334
|
+
left: -6px;
|
|
18335
|
+
border-right: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
18336
|
+
border-left: none;
|
|
18337
|
+
}
|
|
18338
|
+
`;
|
|
18339
|
+
__decorateClass([
|
|
18340
|
+
property50({ type: String })
|
|
18341
|
+
], RelTooltip.prototype, "text", 2);
|
|
18342
|
+
__decorateClass([
|
|
18343
|
+
property50({ type: String })
|
|
18344
|
+
], RelTooltip.prototype, "position", 2);
|
|
18345
|
+
__decorateClass([
|
|
18346
|
+
property50({ type: Boolean, attribute: "show-tail" })
|
|
18347
|
+
], RelTooltip.prototype, "showTail", 2);
|
|
18348
|
+
__decorateClass([
|
|
18349
|
+
property50({ type: String, reflect: true })
|
|
18350
|
+
], RelTooltip.prototype, "type", 2);
|
|
18351
|
+
RelTooltip = __decorateClass([
|
|
18352
|
+
customElement56("rel-tooltip")
|
|
18353
|
+
], RelTooltip);
|
|
18354
|
+
|
|
18355
|
+
// src/components/rel-upload/rel-upload.ts
|
|
18356
|
+
import { LitElement as LitElement57, html as html57, css as css54, nothing as nothing12 } from "lit";
|
|
18357
|
+
import { customElement as customElement57, property as property51, state as state23, query as query11 } from "lit/decorators.js";
|
|
18358
|
+
import "@reliability-design/icons/rel-icon";
|
|
18359
|
+
var uid = 0;
|
|
18360
|
+
var RelUpload = class extends LitElement57 {
|
|
18361
|
+
constructor() {
|
|
18362
|
+
super(...arguments);
|
|
18363
|
+
this.accept = "";
|
|
18364
|
+
this.maxSize = "";
|
|
18365
|
+
this.multiple = false;
|
|
18366
|
+
this.disabled = false;
|
|
18367
|
+
this.hint = "";
|
|
18368
|
+
this.dropzoneTitle = "Drag and drop your files here";
|
|
18369
|
+
this.browseLabel = "Browse files";
|
|
18370
|
+
this.files = [];
|
|
18371
|
+
this.concurrency = 3;
|
|
18372
|
+
this.delayThreshold = 15e3;
|
|
18373
|
+
this.dragOver = false;
|
|
18374
|
+
this.delayed = false;
|
|
18375
|
+
this.lastSignature = "";
|
|
18376
|
+
this.openPicker = () => {
|
|
18377
|
+
if (this.disabled) return;
|
|
18378
|
+
this.inputEl?.click();
|
|
18379
|
+
};
|
|
18380
|
+
this.onInputChange = (e) => {
|
|
18381
|
+
const input = e.target;
|
|
18382
|
+
this.ingest(input.files);
|
|
18383
|
+
input.value = "";
|
|
18384
|
+
};
|
|
18385
|
+
this.onDragOver = (e) => {
|
|
18386
|
+
if (this.disabled) return;
|
|
18387
|
+
e.preventDefault();
|
|
18388
|
+
this.dragOver = true;
|
|
18389
|
+
};
|
|
18390
|
+
this.onDragLeave = (e) => {
|
|
18391
|
+
e.preventDefault();
|
|
18392
|
+
this.dragOver = false;
|
|
18393
|
+
};
|
|
18394
|
+
this.onDrop = (e) => {
|
|
18395
|
+
e.preventDefault();
|
|
18396
|
+
this.dragOver = false;
|
|
18397
|
+
if (this.disabled) return;
|
|
18398
|
+
this.ingest(e.dataTransfer?.files ?? null);
|
|
18399
|
+
};
|
|
18400
|
+
}
|
|
18401
|
+
// ── derived ───────────────────────────────────────────────────────────────
|
|
18402
|
+
/**
|
|
18403
|
+
* Aggregate state. Read-only; corresponds to the Figma modal variants.
|
|
18404
|
+
*
|
|
18405
|
+
* `failed` means there is nothing left to send AND something errored — while
|
|
18406
|
+
* any row is still `ready` the primary action is Upload, not Retry.
|
|
18407
|
+
*/
|
|
18408
|
+
get phase() {
|
|
18409
|
+
if (this.files.length === 0) return "empty";
|
|
18410
|
+
if (this.files.some((f) => f.status === "uploading")) {
|
|
18411
|
+
return this.delayed ? "delayed" : "uploading";
|
|
18412
|
+
}
|
|
18413
|
+
if (this.pending.length > 0) return "selected";
|
|
18414
|
+
if (this.files.some((f) => f.status === "error")) return "failed";
|
|
18415
|
+
return "selected";
|
|
18416
|
+
}
|
|
18417
|
+
/** Files still eligible to be sent. */
|
|
18418
|
+
get pending() {
|
|
18419
|
+
return this.files.filter((f) => f.status === "ready");
|
|
18420
|
+
}
|
|
18421
|
+
get failedFiles() {
|
|
18422
|
+
return this.files.filter((f) => f.status === "error");
|
|
18423
|
+
}
|
|
18424
|
+
/**
|
|
18425
|
+
* Failed rows worth sending again. A file rejected by `accept`/`max-size`
|
|
18426
|
+
* will fail validation every time, so retrying it is pointless — re-check
|
|
18427
|
+
* rather than re-queueing it blindly.
|
|
18428
|
+
*/
|
|
18429
|
+
get retryable() {
|
|
18430
|
+
const maxBytes = this.parseMaxBytes();
|
|
18431
|
+
return this.failedFiles.filter(
|
|
18432
|
+
(f) => !!f.file && this.matchesAccept(f.file) && f.file.size <= maxBytes
|
|
18433
|
+
);
|
|
18434
|
+
}
|
|
18435
|
+
get canUpload() {
|
|
18436
|
+
if (this.disabled) return false;
|
|
18437
|
+
const phase = this.phase;
|
|
18438
|
+
if (phase === "uploading" || phase === "delayed") return false;
|
|
18439
|
+
if (phase === "failed") return this.retryable.length > 0;
|
|
18440
|
+
return this.pending.length > 0;
|
|
18441
|
+
}
|
|
18442
|
+
/** Footer button label — "Upload", "Upload 3 files" or "Retry upload". */
|
|
18443
|
+
get primaryLabel() {
|
|
18444
|
+
if (this.phase === "failed") return "Retry upload";
|
|
18445
|
+
const n = this.files.length;
|
|
18446
|
+
return n > 1 ? `Upload ${n} files` : "Upload";
|
|
18447
|
+
}
|
|
18448
|
+
/** The snapshot `<rel-upload-actions>` renders from. */
|
|
18449
|
+
get state() {
|
|
18450
|
+
return {
|
|
18451
|
+
phase: this.phase,
|
|
18452
|
+
total: this.files.length,
|
|
18453
|
+
failed: this.failedFiles.length,
|
|
18454
|
+
canUpload: this.canUpload,
|
|
18455
|
+
primaryLabel: this.primaryLabel
|
|
18456
|
+
};
|
|
18457
|
+
}
|
|
18458
|
+
get listHeading() {
|
|
18459
|
+
const n = this.files.length;
|
|
18460
|
+
switch (this.phase) {
|
|
18461
|
+
case "uploading":
|
|
18462
|
+
case "delayed":
|
|
18463
|
+
return `Uploading ${n} ${n === 1 ? "file" : "files"}`;
|
|
18464
|
+
case "failed":
|
|
18465
|
+
return `${n} ${n === 1 ? "file" : "files"} - ${this.failedFiles.length} failed`;
|
|
18466
|
+
default:
|
|
18467
|
+
return n === 1 ? "Selected file (1)" : `Selected files (${n})`;
|
|
18468
|
+
}
|
|
18469
|
+
}
|
|
18470
|
+
get hintText() {
|
|
18471
|
+
if (this.hint) return this.hint;
|
|
18472
|
+
const formats = this.acceptDisplay;
|
|
18473
|
+
const size = this.maxSize ? ` - up to ${this.maxSize} per file` : "";
|
|
18474
|
+
return `${formats}${size}`;
|
|
18475
|
+
}
|
|
18476
|
+
get acceptDisplay() {
|
|
18477
|
+
const tokens = this.accept.split(",").map((s) => s.trim()).filter(Boolean).map((tok) => {
|
|
18478
|
+
if (tok.startsWith(".")) return tok.slice(1).toUpperCase();
|
|
18479
|
+
if (tok.endsWith("/*")) return tok.slice(0, -2).toUpperCase();
|
|
18480
|
+
return (tok.split("/").pop() ?? tok).toUpperCase();
|
|
18481
|
+
});
|
|
18482
|
+
if (tokens.length === 0) return "Any file type";
|
|
18483
|
+
if (tokens.length === 1) return tokens[0];
|
|
18484
|
+
return `${tokens.slice(0, -1).join(", ")} or ${tokens[tokens.length - 1]}`;
|
|
18485
|
+
}
|
|
18486
|
+
get alertContent() {
|
|
18487
|
+
if (this.phase === "delayed") {
|
|
18488
|
+
return {
|
|
18489
|
+
type: "warning",
|
|
18490
|
+
message1: "This is taking longer than usual",
|
|
18491
|
+
message2: "Large files can take a few minutes on a slow connection. You can leave this open or come back later."
|
|
18492
|
+
};
|
|
18493
|
+
}
|
|
18494
|
+
const failed = this.failedFiles.length;
|
|
18495
|
+
if (failed > 0 && this.phase !== "uploading") {
|
|
18496
|
+
const limit = this.maxSize ? `under ${this.maxSize} and ` : "";
|
|
18497
|
+
return {
|
|
18498
|
+
type: "error",
|
|
18499
|
+
message1: failed === 1 && this.files.length === 1 ? "We couldn't upload your file" : `We couldn't upload ${failed} of your files`,
|
|
18500
|
+
message2: `Check that each file is ${limit}in a supported format, then try again.`
|
|
18501
|
+
};
|
|
18502
|
+
}
|
|
18503
|
+
return null;
|
|
18504
|
+
}
|
|
18505
|
+
// ── validation ────────────────────────────────────────────────────────────
|
|
18506
|
+
parseMaxBytes() {
|
|
18507
|
+
const s = this.maxSize.trim().toLowerCase();
|
|
18508
|
+
if (!s) return Infinity;
|
|
18509
|
+
const m = s.match(/^([\d.]+)\s*(b|kb|mb|gb)?$/);
|
|
18510
|
+
if (!m) return Infinity;
|
|
18511
|
+
const n = parseFloat(m[1]);
|
|
18512
|
+
const unit = m[2] || "b";
|
|
18513
|
+
const mult = unit === "gb" ? 1e9 : unit === "mb" ? 1e6 : unit === "kb" ? 1e3 : 1;
|
|
18514
|
+
return n * mult;
|
|
18515
|
+
}
|
|
18516
|
+
matchesAccept(file) {
|
|
18517
|
+
const raw = this.accept.trim();
|
|
18518
|
+
if (!raw) return true;
|
|
18519
|
+
const tokens = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
18520
|
+
const name = file.name.toLowerCase();
|
|
18521
|
+
const type = (file.type || "").toLowerCase();
|
|
18522
|
+
return tokens.some((tok) => {
|
|
18523
|
+
if (tok.startsWith(".")) return name.endsWith(tok);
|
|
18524
|
+
if (tok.endsWith("/*")) return type.startsWith(tok.slice(0, -1));
|
|
18525
|
+
return type === tok;
|
|
18526
|
+
});
|
|
18527
|
+
}
|
|
18528
|
+
static fmtSize(bytes) {
|
|
18529
|
+
if (bytes < 1e3) return `${bytes} B`;
|
|
18530
|
+
if (bytes < 1e6) return `${(bytes / 1e3).toFixed(0)} KB`;
|
|
18531
|
+
return `${(bytes / 1e6).toFixed(1)} MB`;
|
|
18532
|
+
}
|
|
18533
|
+
static ext(name) {
|
|
18534
|
+
const i = name.lastIndexOf(".");
|
|
18535
|
+
return i > -1 ? name.slice(i + 1).toUpperCase() : "FILE";
|
|
18536
|
+
}
|
|
18537
|
+
/**
|
|
18538
|
+
* Validate and append. Invalid files are kept as `error` rows rather than
|
|
18539
|
+
* dropped — that is how the Figma Failure state surfaces them.
|
|
18540
|
+
*/
|
|
18541
|
+
ingest(list) {
|
|
18542
|
+
if (!list || list.length === 0) return;
|
|
18543
|
+
const maxBytes = this.parseMaxBytes();
|
|
18544
|
+
const incoming = this.multiple ? Array.from(list) : Array.from(list).slice(0, 1);
|
|
18545
|
+
const added = [];
|
|
18546
|
+
const rejected = [];
|
|
18547
|
+
for (const f of incoming) {
|
|
18548
|
+
const entry = {
|
|
18549
|
+
id: `rel-upload-${++uid}`,
|
|
18550
|
+
name: f.name,
|
|
18551
|
+
size: f.size,
|
|
18552
|
+
status: "ready",
|
|
18553
|
+
file: f
|
|
18554
|
+
};
|
|
18555
|
+
if (!this.matchesAccept(f)) {
|
|
18556
|
+
entry.status = "error";
|
|
18557
|
+
entry.error = "unsupported file type";
|
|
18558
|
+
rejected.push({ name: f.name, reason: "format", message: `${f.name}: unsupported file type` });
|
|
18559
|
+
} else if (f.size > maxBytes) {
|
|
18560
|
+
entry.status = "error";
|
|
18561
|
+
entry.error = `file exceeds the ${this.maxSize} limit`;
|
|
18562
|
+
rejected.push({
|
|
18563
|
+
name: f.name,
|
|
18564
|
+
reason: "size",
|
|
18565
|
+
message: `${f.name}: exceeds ${this.maxSize}`
|
|
18566
|
+
});
|
|
18567
|
+
}
|
|
18568
|
+
added.push(entry);
|
|
18569
|
+
}
|
|
18570
|
+
const names = new Set(added.map((f) => f.name));
|
|
18571
|
+
const kept = this.multiple ? this.files.filter((f) => !names.has(f.name)) : [];
|
|
18572
|
+
this.files = [...kept, ...added];
|
|
18573
|
+
if (rejected.length) this.emit("rel-invalid", { rejected });
|
|
18574
|
+
this.emit("rel-files-added", { added, rejected });
|
|
18575
|
+
this.emit("rel-change", { files: this.files });
|
|
18576
|
+
this.notify();
|
|
18577
|
+
}
|
|
18578
|
+
// ── upload orchestration (self-driving mode) ──────────────────────────────
|
|
18579
|
+
patch(id, partial) {
|
|
18580
|
+
this.files = this.files.map((f) => f.id === id ? { ...f, ...partial } : f);
|
|
18581
|
+
this.notify();
|
|
18582
|
+
}
|
|
18583
|
+
startDelayTimer() {
|
|
18584
|
+
this.stopDelayTimer();
|
|
18585
|
+
if (this.delayThreshold <= 0) return;
|
|
18586
|
+
this.delayTimer = setTimeout(() => {
|
|
18587
|
+
if (this.files.some((f) => f.status === "uploading")) {
|
|
18588
|
+
this.delayed = true;
|
|
18589
|
+
this.notify();
|
|
18590
|
+
}
|
|
18591
|
+
}, this.delayThreshold);
|
|
18592
|
+
}
|
|
18593
|
+
stopDelayTimer() {
|
|
18594
|
+
if (this.delayTimer) clearTimeout(this.delayTimer);
|
|
18595
|
+
this.delayTimer = void 0;
|
|
18596
|
+
}
|
|
18597
|
+
async runBatch(targets) {
|
|
18598
|
+
const uploader = this.uploader;
|
|
18599
|
+
if (!uploader || targets.length === 0) return;
|
|
18600
|
+
this.aborter = new AbortController();
|
|
18601
|
+
const signal = this.aborter.signal;
|
|
18602
|
+
this.startDelayTimer();
|
|
18603
|
+
const queue = [...targets];
|
|
18604
|
+
const workers = Array.from({ length: Math.max(1, this.concurrency) }, async () => {
|
|
18605
|
+
while (queue.length) {
|
|
18606
|
+
if (signal.aborted) return;
|
|
18607
|
+
const item = queue.shift();
|
|
18608
|
+
if (!item || !item.file) continue;
|
|
18609
|
+
this.patch(item.id, { status: "uploading", progress: 0, loaded: 0, error: void 0 });
|
|
18610
|
+
try {
|
|
18611
|
+
await uploader(item.file, {
|
|
18612
|
+
file: item,
|
|
18613
|
+
signal,
|
|
18614
|
+
onProgress: (loaded, total) => {
|
|
18615
|
+
const t = total ?? item.size;
|
|
18616
|
+
const progress = t > 0 ? Math.min(100, Math.round(loaded / t * 100)) : 0;
|
|
18617
|
+
this.patch(item.id, { loaded, progress });
|
|
18618
|
+
this.emit("rel-progress", { id: item.id, progress, loaded, total: t });
|
|
18619
|
+
}
|
|
18620
|
+
});
|
|
18621
|
+
if (signal.aborted) {
|
|
18622
|
+
this.patch(item.id, { status: "ready", progress: void 0, loaded: void 0 });
|
|
18623
|
+
return;
|
|
18624
|
+
}
|
|
18625
|
+
this.patch(item.id, { status: "success", progress: 100, loaded: item.size });
|
|
18626
|
+
this.emit("rel-file-complete", { id: item.id, file: item.file });
|
|
18627
|
+
} catch (err) {
|
|
18628
|
+
if (signal.aborted) {
|
|
18629
|
+
this.patch(item.id, { status: "ready", progress: void 0, loaded: void 0 });
|
|
18630
|
+
return;
|
|
18631
|
+
}
|
|
18632
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
18633
|
+
this.patch(item.id, { status: "error", error: message, progress: void 0 });
|
|
18634
|
+
this.emit("rel-file-error", { id: item.id, file: item.file, error: message });
|
|
18635
|
+
}
|
|
18636
|
+
}
|
|
18637
|
+
});
|
|
18638
|
+
await Promise.all(workers);
|
|
18639
|
+
this.stopDelayTimer();
|
|
18640
|
+
this.delayed = false;
|
|
18641
|
+
this.aborter = void 0;
|
|
18642
|
+
if (!signal.aborted) {
|
|
18643
|
+
this.emit("rel-complete", {
|
|
18644
|
+
succeeded: this.files.filter((f) => f.status === "success"),
|
|
18645
|
+
failed: this.failedFiles
|
|
18646
|
+
});
|
|
18647
|
+
}
|
|
18648
|
+
this.notify();
|
|
18649
|
+
}
|
|
18650
|
+
// ── public API ────────────────────────────────────────────────────────────
|
|
18651
|
+
/** Start (or resume) the batch. Emits `rel-upload`; runs it when `uploader` is set. */
|
|
18652
|
+
upload() {
|
|
18653
|
+
if (!this.canUpload) return;
|
|
18654
|
+
const targets = this.pending;
|
|
18655
|
+
this.emit("rel-upload", { files: targets });
|
|
18656
|
+
if (this.uploader) void this.runBatch(targets);
|
|
18657
|
+
else this.notify();
|
|
18658
|
+
}
|
|
18659
|
+
/** Re-queue every retryable failed row and send again. */
|
|
18660
|
+
retry() {
|
|
18661
|
+
const targets = this.retryable;
|
|
18662
|
+
if (targets.length === 0) return;
|
|
18663
|
+
const ids = new Set(targets.map((f) => f.id));
|
|
18664
|
+
this.files = this.files.map(
|
|
18665
|
+
(f) => ids.has(f.id) ? { ...f, status: "ready", error: void 0, progress: void 0 } : f
|
|
18666
|
+
);
|
|
18667
|
+
const requeued = this.files.filter((f) => ids.has(f.id));
|
|
18668
|
+
this.emit("rel-retry", { files: requeued });
|
|
18669
|
+
if (this.uploader) void this.runBatch(requeued);
|
|
18670
|
+
else this.notify();
|
|
18671
|
+
}
|
|
18672
|
+
/** Abort anything in flight and emit `rel-cancel`. Does not clear the list. */
|
|
18673
|
+
cancel() {
|
|
18674
|
+
this.aborter?.abort();
|
|
18675
|
+
this.stopDelayTimer();
|
|
18676
|
+
this.delayed = false;
|
|
18677
|
+
this.emit("rel-cancel");
|
|
18678
|
+
this.notify();
|
|
18679
|
+
}
|
|
18680
|
+
/** Remove one row by id. */
|
|
18681
|
+
removeFile(id) {
|
|
18682
|
+
const target = this.files.find((f) => f.id === id);
|
|
18683
|
+
this.files = this.files.filter((f) => f.id !== id);
|
|
18684
|
+
this.emit("rel-remove", { id, file: target?.file });
|
|
18685
|
+
this.emit("rel-change", { files: this.files });
|
|
18686
|
+
this.notify();
|
|
18687
|
+
}
|
|
18688
|
+
/** Clear everything and abort in-flight transfers. */
|
|
18689
|
+
reset() {
|
|
18690
|
+
this.aborter?.abort();
|
|
18691
|
+
this.stopDelayTimer();
|
|
18692
|
+
this.files = [];
|
|
18693
|
+
this.delayed = false;
|
|
18694
|
+
if (this.inputEl) this.inputEl.value = "";
|
|
18695
|
+
this.emit("rel-change", { files: this.files });
|
|
18696
|
+
this.notify();
|
|
18697
|
+
}
|
|
18698
|
+
// ── events ────────────────────────────────────────────────────────────────
|
|
18699
|
+
emit(type, detail) {
|
|
18700
|
+
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
18701
|
+
}
|
|
18702
|
+
/**
|
|
18703
|
+
* Emit `rel-state` only when the derived snapshot actually changes, so
|
|
18704
|
+
* progress ticks don't spam `<rel-upload-actions>`.
|
|
18705
|
+
*/
|
|
18706
|
+
notify() {
|
|
18707
|
+
const s = this.state;
|
|
18708
|
+
const signature = `${s.phase}|${s.total}|${s.failed}|${s.canUpload}|${s.primaryLabel}`;
|
|
18709
|
+
if (signature === this.lastSignature) return;
|
|
18710
|
+
this.lastSignature = signature;
|
|
18711
|
+
this.emit("rel-state", s);
|
|
18712
|
+
}
|
|
18713
|
+
connectedCallback() {
|
|
18714
|
+
super.connectedCallback();
|
|
18715
|
+
queueMicrotask(() => this.emit("rel-state", this.state));
|
|
18716
|
+
}
|
|
18717
|
+
disconnectedCallback() {
|
|
18718
|
+
super.disconnectedCallback();
|
|
18719
|
+
this.stopDelayTimer();
|
|
18720
|
+
this.aborter?.abort();
|
|
18721
|
+
}
|
|
18722
|
+
updated() {
|
|
18723
|
+
this.notify();
|
|
18724
|
+
}
|
|
18725
|
+
// ── render ────────────────────────────────────────────────────────────────
|
|
18726
|
+
rowMeta(f) {
|
|
18727
|
+
switch (f.status) {
|
|
18728
|
+
case "uploading": {
|
|
18729
|
+
const pct = f.progress ?? 0;
|
|
18730
|
+
const loaded = RelUpload.fmtSize(f.loaded ?? 0);
|
|
18731
|
+
return `${pct}% - ${loaded} of ${RelUpload.fmtSize(f.size)}`;
|
|
18732
|
+
}
|
|
18733
|
+
case "success":
|
|
18734
|
+
return `${RelUpload.ext(f.name)} - ${RelUpload.fmtSize(f.size)} - Uploaded`;
|
|
18735
|
+
case "error":
|
|
18736
|
+
return `Upload failed - ${f.error ?? "please try again"}`;
|
|
18737
|
+
default: {
|
|
18738
|
+
const base = `${RelUpload.ext(f.name)} - ${RelUpload.fmtSize(f.size)}`;
|
|
18739
|
+
const busy = this.phase === "uploading" || this.phase === "delayed";
|
|
18740
|
+
return busy ? `${base} - Waiting` : base;
|
|
18741
|
+
}
|
|
18742
|
+
}
|
|
18743
|
+
}
|
|
18744
|
+
renderRow(f) {
|
|
18745
|
+
return html57`
|
|
18746
|
+
<div class="row ${f.status}">
|
|
18747
|
+
<rel-icon name="document" size="md"></rel-icon>
|
|
18748
|
+
|
|
18749
|
+
<div class="row-content">
|
|
18750
|
+
<span class="row-name" title=${f.name}>${f.name}</span>
|
|
18751
|
+
${f.status === "uploading" ? html57`<rel-progress
|
|
18752
|
+
mode="linear-determinate"
|
|
18753
|
+
.value=${f.progress ?? 0}
|
|
18754
|
+
></rel-progress>` : nothing12}
|
|
18755
|
+
<span class="row-meta">${this.rowMeta(f)}</span>
|
|
18756
|
+
</div>
|
|
18757
|
+
|
|
18758
|
+
<div class="row-trailing">
|
|
18759
|
+
${f.status === "success" ? html57`<rel-icon
|
|
18760
|
+
class="status-success"
|
|
18761
|
+
name="check-circle"
|
|
18762
|
+
size="md"
|
|
18763
|
+
role="img"
|
|
18764
|
+
aria-label="Uploaded"
|
|
18765
|
+
></rel-icon>` : nothing12}
|
|
18766
|
+
${f.status === "error" ? html57`<rel-icon
|
|
18767
|
+
class="status-error"
|
|
18768
|
+
name="error"
|
|
18769
|
+
size="md"
|
|
18770
|
+
role="img"
|
|
18771
|
+
aria-label="Failed"
|
|
18772
|
+
></rel-icon>` : nothing12}
|
|
18773
|
+
<button
|
|
18774
|
+
class="remove"
|
|
18775
|
+
aria-label="Remove ${f.name}"
|
|
18776
|
+
?disabled=${this.disabled}
|
|
18777
|
+
@click=${() => this.removeFile(f.id)}
|
|
18778
|
+
>
|
|
18779
|
+
<rel-icon name="delete" size="md"></rel-icon>
|
|
18780
|
+
</button>
|
|
18781
|
+
</div>
|
|
18782
|
+
</div>
|
|
18783
|
+
`;
|
|
18784
|
+
}
|
|
18785
|
+
render() {
|
|
18786
|
+
const alert = this.alertContent;
|
|
18787
|
+
return html57`
|
|
18788
|
+
<div class="body">
|
|
18789
|
+
${alert ? html57`<rel-alert
|
|
18790
|
+
type=${alert.type}
|
|
18791
|
+
appearance="subtle"
|
|
18792
|
+
message1=${alert.message1}
|
|
18793
|
+
message2=${alert.message2}
|
|
18794
|
+
></rel-alert>` : nothing12}
|
|
18795
|
+
|
|
18796
|
+
<div
|
|
18797
|
+
class="dropzone ${this.dragOver ? "dragover" : ""}"
|
|
18798
|
+
@click=${this.openPicker}
|
|
18799
|
+
@dragover=${this.onDragOver}
|
|
18800
|
+
@dragleave=${this.onDragLeave}
|
|
18801
|
+
@drop=${this.onDrop}
|
|
18802
|
+
>
|
|
18803
|
+
<span class="icon-badge">
|
|
18804
|
+
<rel-icon name="upload" size="md"></rel-icon>
|
|
18805
|
+
</span>
|
|
18806
|
+
|
|
18807
|
+
<div class="dz-copy">
|
|
18808
|
+
<p class="dz-title">${this.dropzoneTitle}</p>
|
|
18809
|
+
<p class="dz-hint">${this.hintText}</p>
|
|
18810
|
+
</div>
|
|
18811
|
+
|
|
18812
|
+
<button
|
|
18813
|
+
class="browse"
|
|
18814
|
+
type="button"
|
|
18815
|
+
?disabled=${this.disabled}
|
|
18816
|
+
@click=${(e) => {
|
|
18817
|
+
e.stopPropagation();
|
|
18818
|
+
this.openPicker();
|
|
18819
|
+
}}
|
|
18820
|
+
>
|
|
18821
|
+
${this.browseLabel}
|
|
18822
|
+
</button>
|
|
18823
|
+
</div>
|
|
18824
|
+
|
|
18825
|
+
${this.files.length ? html57`<div class="file-list">
|
|
18826
|
+
<p class="list-heading">${this.listHeading}</p>
|
|
18827
|
+
<div class="rows" role="list">${this.files.map((f) => this.renderRow(f))}</div>
|
|
18828
|
+
</div>` : nothing12}
|
|
18829
|
+
|
|
18830
|
+
<input
|
|
18831
|
+
class="file-input"
|
|
18832
|
+
type="file"
|
|
18833
|
+
?multiple=${this.multiple}
|
|
18834
|
+
accept=${this.accept || nothing12}
|
|
18835
|
+
@change=${this.onInputChange}
|
|
18836
|
+
/>
|
|
18837
|
+
</div>
|
|
18838
|
+
`;
|
|
18839
|
+
}
|
|
18840
|
+
};
|
|
18841
|
+
RelUpload.styles = css54`
|
|
18842
|
+
:host {
|
|
18843
|
+
display: block;
|
|
18844
|
+
box-sizing: border-box;
|
|
18845
|
+
color: var(--rel-text-default);
|
|
18846
|
+
font-family: var(--rel-fonts-font-family);
|
|
18847
|
+
}
|
|
18848
|
+
|
|
18849
|
+
:host([disabled]) {
|
|
18850
|
+
opacity: 0.6;
|
|
18851
|
+
}
|
|
18852
|
+
|
|
18853
|
+
:host([disabled]) .body {
|
|
18854
|
+
pointer-events: none;
|
|
18855
|
+
}
|
|
18856
|
+
|
|
18857
|
+
.body {
|
|
18858
|
+
display: flex;
|
|
18859
|
+
flex-direction: column;
|
|
18860
|
+
gap: var(--rel-spacing-lg, 20px);
|
|
18861
|
+
}
|
|
18862
|
+
|
|
18863
|
+
/* ── Dropzone ─────────────────────────────────────────────────────────── */
|
|
18864
|
+
|
|
18865
|
+
.dropzone {
|
|
18866
|
+
box-sizing: border-box;
|
|
18867
|
+
display: flex;
|
|
18868
|
+
flex-direction: column;
|
|
18869
|
+
align-items: center;
|
|
18870
|
+
justify-content: center;
|
|
18871
|
+
gap: var(--rel-spacing-md, 16px);
|
|
18872
|
+
padding: var(--rel-spacing-2xl, 32px) var(--rel-spacing-xl, 24px);
|
|
18873
|
+
background: var(--rel-bg-surface);
|
|
18874
|
+
border: 1px dashed var(--rel-border-accent);
|
|
18875
|
+
border-radius: var(--rel-border-radius-md, 16px);
|
|
18876
|
+
text-align: center;
|
|
18877
|
+
cursor: pointer;
|
|
18878
|
+
transition: border-color 0.15s ease, background 0.15s ease;
|
|
18879
|
+
}
|
|
18880
|
+
|
|
18881
|
+
/* Design gap: no drag-over frame exists in Figma. */
|
|
18882
|
+
.dropzone.dragover {
|
|
18883
|
+
border-style: solid;
|
|
18884
|
+
border-color: var(--rel-border-brand);
|
|
18885
|
+
background: color-mix(in srgb, var(--rel-bg-brand) 8%, var(--rel-bg-surface));
|
|
18886
|
+
}
|
|
18887
|
+
|
|
18888
|
+
.icon-badge {
|
|
18889
|
+
display: inline-flex;
|
|
18890
|
+
align-items: center;
|
|
18891
|
+
justify-content: center;
|
|
18892
|
+
padding: var(--rel-spacing-sm, 12px);
|
|
18893
|
+
background: var(--rel-bg-surface);
|
|
18894
|
+
border: 1px solid var(--rel-border-subtle);
|
|
18895
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
18896
|
+
}
|
|
18897
|
+
|
|
18898
|
+
.dz-copy {
|
|
18899
|
+
display: flex;
|
|
18900
|
+
flex-direction: column;
|
|
18901
|
+
align-items: center;
|
|
18902
|
+
gap: var(--rel-spacing-2xs, 4px);
|
|
18903
|
+
}
|
|
18904
|
+
|
|
18905
|
+
.dz-title {
|
|
18906
|
+
margin: 0;
|
|
18907
|
+
color: var(--rel-text-default);
|
|
18908
|
+
font-family: var(--rel-body-lg-medium-font-family);
|
|
18909
|
+
font-weight: var(--rel-body-lg-medium-font-weight);
|
|
18910
|
+
font-size: var(--rel-body-lg-medium-font-size);
|
|
18911
|
+
line-height: var(--rel-body-lg-medium-line-height);
|
|
18912
|
+
}
|
|
18913
|
+
|
|
18914
|
+
.dz-hint {
|
|
18915
|
+
margin: 0;
|
|
18916
|
+
color: var(--rel-text-caption);
|
|
18917
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
18918
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
18919
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
18920
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
18921
|
+
}
|
|
18922
|
+
|
|
18923
|
+
/* The dropzone is a mouse affordance only — the Browse button is the sole
|
|
18924
|
+
focusable control, so there are no nested interactive elements. */
|
|
18925
|
+
.browse {
|
|
18926
|
+
display: inline-flex;
|
|
18927
|
+
align-items: center;
|
|
18928
|
+
gap: var(--rel-spacing-xs, 8px);
|
|
18929
|
+
padding: var(--rel-spacing-xs, 8px) var(--rel-spacing-md, 16px);
|
|
18930
|
+
background: transparent;
|
|
18931
|
+
color: var(--rel-text-default);
|
|
18932
|
+
border: 1px solid var(--rel-action-neutral-border-outline, var(--rel-border-accent));
|
|
18933
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
18934
|
+
cursor: pointer;
|
|
18935
|
+
font-family: var(--rel-body-md-semibold-font-family);
|
|
18936
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
18937
|
+
font-size: var(--rel-body-md-semibold-font-size);
|
|
18938
|
+
line-height: var(--rel-body-md-semibold-line-height);
|
|
18939
|
+
}
|
|
18940
|
+
|
|
18941
|
+
.browse:hover:not(:disabled) {
|
|
18942
|
+
background: var(--rel-action-neutral-outline-hover, var(--rel-bg-surface-elevated));
|
|
18943
|
+
}
|
|
18944
|
+
|
|
18945
|
+
.browse:focus-visible {
|
|
18946
|
+
outline: 2px solid var(--rel-border-focus);
|
|
18947
|
+
outline-offset: 2px;
|
|
18948
|
+
}
|
|
18949
|
+
|
|
18950
|
+
.browse:disabled {
|
|
18951
|
+
cursor: not-allowed;
|
|
18952
|
+
}
|
|
18953
|
+
|
|
18954
|
+
/* ── File list ────────────────────────────────────────────────────────── */
|
|
18955
|
+
|
|
18956
|
+
.file-list {
|
|
18957
|
+
display: flex;
|
|
18958
|
+
flex-direction: column;
|
|
18959
|
+
gap: var(--rel-spacing-sm, 12px);
|
|
18960
|
+
}
|
|
18961
|
+
|
|
18962
|
+
.list-heading {
|
|
18963
|
+
margin: 0;
|
|
18964
|
+
color: var(--rel-text-default);
|
|
18965
|
+
font-family: var(--rel-body-md-semibold-font-family);
|
|
18966
|
+
font-weight: var(--rel-body-md-semibold-font-weight);
|
|
18967
|
+
font-size: var(--rel-body-md-semibold-font-size);
|
|
18968
|
+
line-height: var(--rel-body-md-semibold-line-height);
|
|
18969
|
+
}
|
|
18970
|
+
|
|
18971
|
+
.rows {
|
|
18972
|
+
display: flex;
|
|
18973
|
+
flex-direction: column;
|
|
18974
|
+
gap: var(--rel-spacing-xs, 8px);
|
|
18975
|
+
}
|
|
18976
|
+
|
|
18977
|
+
.row {
|
|
18978
|
+
display: flex;
|
|
18979
|
+
align-items: center;
|
|
18980
|
+
gap: var(--rel-spacing-sm, 12px);
|
|
18981
|
+
box-sizing: border-box;
|
|
18982
|
+
padding: var(--rel-spacing-sm, 12px) var(--rel-spacing-md, 16px);
|
|
18983
|
+
background: var(--rel-bg-surface);
|
|
18984
|
+
border: 1px solid var(--rel-border-default);
|
|
18985
|
+
border-radius: var(--rel-border-radius-sm, 8px);
|
|
18986
|
+
}
|
|
18987
|
+
|
|
18988
|
+
.row.error {
|
|
18989
|
+
background: var(--rel-bg-danger-light);
|
|
18990
|
+
border-color: var(--rel-border-danger);
|
|
18991
|
+
}
|
|
18992
|
+
|
|
18993
|
+
.row > rel-icon {
|
|
18994
|
+
flex-shrink: 0;
|
|
18995
|
+
}
|
|
18996
|
+
|
|
18997
|
+
.row-content {
|
|
18998
|
+
display: flex;
|
|
18999
|
+
flex-direction: column;
|
|
19000
|
+
gap: var(--rel-spacing-3xs, 2px);
|
|
19001
|
+
flex: 1;
|
|
19002
|
+
min-width: 0;
|
|
19003
|
+
}
|
|
19004
|
+
|
|
19005
|
+
.row.uploading .row-content {
|
|
19006
|
+
gap: var(--rel-spacing-2xs, 4px);
|
|
19007
|
+
}
|
|
19008
|
+
|
|
19009
|
+
.row-name {
|
|
19010
|
+
overflow: hidden;
|
|
19011
|
+
text-overflow: ellipsis;
|
|
19012
|
+
white-space: nowrap;
|
|
19013
|
+
color: var(--rel-text-default);
|
|
19014
|
+
font-family: var(--rel-body-md-regular-font-family);
|
|
19015
|
+
font-weight: var(--rel-body-md-regular-font-weight);
|
|
19016
|
+
font-size: var(--rel-body-md-regular-font-size);
|
|
19017
|
+
line-height: var(--rel-body-md-regular-line-height);
|
|
19018
|
+
}
|
|
19019
|
+
|
|
19020
|
+
.row-meta {
|
|
19021
|
+
color: var(--rel-text-caption);
|
|
19022
|
+
font-family: var(--rel-body-sm-regular-font-family);
|
|
19023
|
+
font-weight: var(--rel-body-sm-regular-font-weight);
|
|
19024
|
+
font-size: var(--rel-body-sm-regular-font-size);
|
|
19025
|
+
line-height: var(--rel-body-sm-regular-line-height);
|
|
19026
|
+
}
|
|
19027
|
+
|
|
19028
|
+
.row.success .row-meta {
|
|
19029
|
+
color: var(--rel-text-success);
|
|
19030
|
+
}
|
|
19031
|
+
|
|
19032
|
+
.row.error .row-meta {
|
|
19033
|
+
color: var(--rel-text-danger);
|
|
16676
19034
|
}
|
|
16677
19035
|
|
|
16678
|
-
.
|
|
16679
|
-
|
|
19036
|
+
/* Figma's 4px rounded track; rel-progress ships a 6px square one. */
|
|
19037
|
+
rel-progress::part(track) {
|
|
19038
|
+
height: 4px;
|
|
19039
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
16680
19040
|
}
|
|
16681
19041
|
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
|
|
19042
|
+
rel-progress::part(indicator) {
|
|
19043
|
+
background: var(--rel-bg-success);
|
|
19044
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
16685
19045
|
}
|
|
16686
19046
|
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
font-family: var(--rel-action-md-font-family);
|
|
16693
|
-
font-weight: var(--rel-action-md-font-weight);
|
|
16694
|
-
font-size: var(--rel-action-md-font-size);
|
|
16695
|
-
line-height: var(--rel-action-md-line-height);
|
|
16696
|
-
letter-spacing: var(--rel-action-md-letter-spacing);
|
|
16697
|
-
cursor: pointer;
|
|
16698
|
-
transition: opacity 0.2s;
|
|
19047
|
+
.row-trailing {
|
|
19048
|
+
display: flex;
|
|
19049
|
+
align-items: center;
|
|
19050
|
+
gap: var(--rel-spacing-sm, 12px);
|
|
19051
|
+
flex-shrink: 0;
|
|
16699
19052
|
}
|
|
16700
19053
|
|
|
16701
|
-
.
|
|
16702
|
-
|
|
16703
|
-
color: var(--rel-text-default);
|
|
16704
|
-
opacity: 0.75;
|
|
19054
|
+
.status-success {
|
|
19055
|
+
color: var(--rel-text-success);
|
|
16705
19056
|
}
|
|
16706
19057
|
|
|
16707
|
-
.
|
|
16708
|
-
|
|
16709
|
-
outline-offset: 2px;
|
|
16710
|
-
border-radius: var(--rel-border-radius-xs);
|
|
19058
|
+
.status-error {
|
|
19059
|
+
color: var(--rel-text-danger);
|
|
16711
19060
|
}
|
|
16712
19061
|
|
|
16713
|
-
|
|
16714
|
-
|
|
16715
|
-
|
|
16716
|
-
|
|
19062
|
+
.remove {
|
|
19063
|
+
display: inline-flex;
|
|
19064
|
+
align-items: center;
|
|
19065
|
+
justify-content: center;
|
|
19066
|
+
padding: 0;
|
|
19067
|
+
background: none;
|
|
16717
19068
|
border: none;
|
|
16718
|
-
color: inherit;
|
|
16719
|
-
font-size: 1rem;
|
|
16720
19069
|
cursor: pointer;
|
|
16721
|
-
|
|
16722
|
-
padding: var(--rel-spacing-2xs);
|
|
16723
|
-
margin-left: var(--rel-spacing-2xs);
|
|
16724
|
-
}
|
|
16725
|
-
|
|
16726
|
-
.toast-close:hover {
|
|
16727
|
-
opacity: 0.75;
|
|
19070
|
+
color: var(--rel-text-default);
|
|
16728
19071
|
}
|
|
16729
19072
|
|
|
16730
|
-
.
|
|
16731
|
-
outline: 2px solid var(--rel-
|
|
19073
|
+
.remove:focus-visible {
|
|
19074
|
+
outline: 2px solid var(--rel-border-focus);
|
|
16732
19075
|
outline-offset: 2px;
|
|
16733
|
-
border-radius: var(--rel-border-radius-xs);
|
|
19076
|
+
border-radius: var(--rel-border-radius-xs, 4px);
|
|
16734
19077
|
}
|
|
16735
19078
|
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
.toast {
|
|
16739
|
-
animation: none;
|
|
16740
|
-
}
|
|
19079
|
+
.file-input {
|
|
19080
|
+
display: none;
|
|
16741
19081
|
}
|
|
16742
19082
|
|
|
16743
|
-
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
opacity: 0;
|
|
16747
|
-
transform: translateY(20px);
|
|
16748
|
-
}
|
|
16749
|
-
to {
|
|
16750
|
-
opacity: 1;
|
|
16751
|
-
transform: translateY(0);
|
|
19083
|
+
@media (prefers-reduced-motion: reduce) {
|
|
19084
|
+
.dropzone {
|
|
19085
|
+
transition: none;
|
|
16752
19086
|
}
|
|
16753
19087
|
}
|
|
16754
19088
|
`;
|
|
16755
19089
|
__decorateClass([
|
|
16756
|
-
|
|
16757
|
-
],
|
|
19090
|
+
property51({ type: String })
|
|
19091
|
+
], RelUpload.prototype, "accept", 2);
|
|
16758
19092
|
__decorateClass([
|
|
16759
|
-
|
|
16760
|
-
],
|
|
19093
|
+
property51({ type: String, attribute: "max-size" })
|
|
19094
|
+
], RelUpload.prototype, "maxSize", 2);
|
|
16761
19095
|
__decorateClass([
|
|
16762
|
-
|
|
16763
|
-
],
|
|
19096
|
+
property51({ type: Boolean })
|
|
19097
|
+
], RelUpload.prototype, "multiple", 2);
|
|
16764
19098
|
__decorateClass([
|
|
16765
|
-
|
|
16766
|
-
],
|
|
19099
|
+
property51({ type: Boolean, reflect: true })
|
|
19100
|
+
], RelUpload.prototype, "disabled", 2);
|
|
16767
19101
|
__decorateClass([
|
|
16768
|
-
|
|
16769
|
-
],
|
|
19102
|
+
property51({ type: String })
|
|
19103
|
+
], RelUpload.prototype, "hint", 2);
|
|
16770
19104
|
__decorateClass([
|
|
16771
|
-
|
|
16772
|
-
],
|
|
16773
|
-
|
|
16774
|
-
|
|
16775
|
-
],
|
|
19105
|
+
property51({ type: String, attribute: "dropzone-title" })
|
|
19106
|
+
], RelUpload.prototype, "dropzoneTitle", 2);
|
|
19107
|
+
__decorateClass([
|
|
19108
|
+
property51({ type: String, attribute: "browse-label" })
|
|
19109
|
+
], RelUpload.prototype, "browseLabel", 2);
|
|
19110
|
+
__decorateClass([
|
|
19111
|
+
property51({ attribute: false })
|
|
19112
|
+
], RelUpload.prototype, "files", 2);
|
|
19113
|
+
__decorateClass([
|
|
19114
|
+
property51({ attribute: false })
|
|
19115
|
+
], RelUpload.prototype, "uploader", 2);
|
|
19116
|
+
__decorateClass([
|
|
19117
|
+
property51({ type: Number })
|
|
19118
|
+
], RelUpload.prototype, "concurrency", 2);
|
|
19119
|
+
__decorateClass([
|
|
19120
|
+
property51({ type: Number, attribute: "delay-threshold" })
|
|
19121
|
+
], RelUpload.prototype, "delayThreshold", 2);
|
|
19122
|
+
__decorateClass([
|
|
19123
|
+
state23()
|
|
19124
|
+
], RelUpload.prototype, "dragOver", 2);
|
|
19125
|
+
__decorateClass([
|
|
19126
|
+
state23()
|
|
19127
|
+
], RelUpload.prototype, "delayed", 2);
|
|
19128
|
+
__decorateClass([
|
|
19129
|
+
query11(".file-input")
|
|
19130
|
+
], RelUpload.prototype, "inputEl", 2);
|
|
19131
|
+
RelUpload = __decorateClass([
|
|
19132
|
+
customElement57("rel-upload")
|
|
19133
|
+
], RelUpload);
|
|
16776
19134
|
|
|
16777
|
-
// src/components/rel-
|
|
16778
|
-
import { LitElement as
|
|
16779
|
-
import { customElement as
|
|
16780
|
-
|
|
19135
|
+
// src/components/rel-upload/rel-upload-actions.ts
|
|
19136
|
+
import { LitElement as LitElement58, html as html58, css as css55, nothing as nothing13 } from "lit";
|
|
19137
|
+
import { customElement as customElement58, property as property52, state as state24 } from "lit/decorators.js";
|
|
19138
|
+
import "@reliability-design/icons/rel-icon";
|
|
19139
|
+
var EMPTY_STATE = {
|
|
19140
|
+
phase: "empty",
|
|
19141
|
+
total: 0,
|
|
19142
|
+
failed: 0,
|
|
19143
|
+
canUpload: false,
|
|
19144
|
+
primaryLabel: "Upload"
|
|
19145
|
+
};
|
|
19146
|
+
var RelUploadActions = class extends LitElement58 {
|
|
16781
19147
|
constructor() {
|
|
16782
19148
|
super(...arguments);
|
|
16783
|
-
this.
|
|
16784
|
-
this.
|
|
16785
|
-
this.
|
|
16786
|
-
this.
|
|
16787
|
-
|
|
16788
|
-
this.
|
|
19149
|
+
this.for = "";
|
|
19150
|
+
this.cancelLabel = "Cancel";
|
|
19151
|
+
this.noCancel = false;
|
|
19152
|
+
this.snapshot = EMPTY_STATE;
|
|
19153
|
+
this.target = null;
|
|
19154
|
+
this.onState = (e) => {
|
|
19155
|
+
if (!this.target) this.resolveTarget();
|
|
19156
|
+
if (e.target !== this.target) return;
|
|
19157
|
+
this.snapshot = e.detail;
|
|
19158
|
+
};
|
|
19159
|
+
this.onCancel = () => {
|
|
19160
|
+
this.target?.cancel();
|
|
19161
|
+
this.dispatchEvent(new CustomEvent("rel-cancel", { bubbles: true, composed: true }));
|
|
19162
|
+
};
|
|
19163
|
+
this.onPrimary = () => {
|
|
19164
|
+
if (!this.target) return;
|
|
19165
|
+
if (this.snapshot.phase === "failed") this.target.retry();
|
|
19166
|
+
else this.target.upload();
|
|
19167
|
+
};
|
|
16789
19168
|
}
|
|
16790
|
-
|
|
16791
|
-
|
|
19169
|
+
connectedCallback() {
|
|
19170
|
+
super.connectedCallback();
|
|
19171
|
+
queueMicrotask(() => this.resolveTarget());
|
|
19172
|
+
this.root.addEventListener("rel-state", this.onState);
|
|
16792
19173
|
}
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
|
|
16798
|
-
|
|
19174
|
+
disconnectedCallback() {
|
|
19175
|
+
super.disconnectedCallback();
|
|
19176
|
+
this.root.removeEventListener("rel-state", this.onState);
|
|
19177
|
+
this.target = null;
|
|
19178
|
+
}
|
|
19179
|
+
updated(changed) {
|
|
19180
|
+
if (changed.has("for")) this.resolveTarget();
|
|
19181
|
+
}
|
|
19182
|
+
/**
|
|
19183
|
+
* Listen on our own root rather than `document`: a shared root means no
|
|
19184
|
+
* shadow boundary sits between us and the sibling `<rel-upload>`, so
|
|
19185
|
+
* `event.target` is never retargeted away from it.
|
|
19186
|
+
*/
|
|
19187
|
+
get root() {
|
|
19188
|
+
const node = this.getRootNode();
|
|
19189
|
+
return node instanceof ShadowRoot ? node : this.ownerDocument;
|
|
19190
|
+
}
|
|
19191
|
+
resolveTarget() {
|
|
19192
|
+
const root = this.getRootNode();
|
|
19193
|
+
let found = null;
|
|
19194
|
+
if (this.for) {
|
|
19195
|
+
found = root.querySelector(`#${CSS.escape(this.for)}`);
|
|
19196
|
+
} else {
|
|
19197
|
+
found = this.parentElement?.querySelector("rel-upload") ?? root.querySelector("rel-upload");
|
|
16799
19198
|
}
|
|
19199
|
+
this.target = found;
|
|
19200
|
+
if (found) this.snapshot = found.state;
|
|
16800
19201
|
}
|
|
16801
19202
|
render() {
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
|
|
16807
|
-
|
|
16808
|
-
|
|
16809
|
-
|
|
16810
|
-
|
|
19203
|
+
const { phase, canUpload, primaryLabel } = this.snapshot;
|
|
19204
|
+
const busy = phase === "uploading" || phase === "delayed";
|
|
19205
|
+
return html58`
|
|
19206
|
+
<div class="actions" part="base">
|
|
19207
|
+
${this.noCancel ? nothing13 : html58`<rel-button part="cancel" variant="secondary" @click=${this.onCancel}>
|
|
19208
|
+
${this.cancelLabel}
|
|
19209
|
+
</rel-button>`}
|
|
19210
|
+
|
|
19211
|
+
<rel-button
|
|
19212
|
+
part="primary"
|
|
19213
|
+
variant="primary"
|
|
19214
|
+
?loading=${busy}
|
|
19215
|
+
?disabled=${!canUpload}
|
|
19216
|
+
aria-label=${busy ? "Uploading" : primaryLabel}
|
|
19217
|
+
@click=${this.onPrimary}
|
|
19218
|
+
>
|
|
19219
|
+
${busy ? nothing13 : html58`${phase === "failed" ? html58`<rel-icon name="refresh" size="sm"></rel-icon>` : nothing13}${primaryLabel}`}
|
|
19220
|
+
</rel-button>
|
|
16811
19221
|
</div>
|
|
16812
19222
|
`;
|
|
16813
19223
|
}
|
|
16814
19224
|
};
|
|
16815
|
-
|
|
19225
|
+
RelUploadActions.styles = css55`
|
|
16816
19226
|
:host {
|
|
16817
|
-
color: var(--rel-text-default);
|
|
16818
|
-
display: inline-block;
|
|
16819
|
-
position: relative;
|
|
16820
|
-
}
|
|
16821
|
-
|
|
16822
|
-
/* Tooltip container */
|
|
16823
|
-
.tooltip-bubble {
|
|
16824
|
-
font-family: var(--rel-body-md-regular-font-family);
|
|
16825
|
-
font-weight: var(--rel-body-md-medium-font-weight);
|
|
16826
|
-
font-size: var(--rel-body-md-regular-font-size);
|
|
16827
|
-
line-height: var(--rel-body-md-regular-line-height);
|
|
16828
|
-
letter-spacing: var(--rel-body-md-regular-letter-spacing);
|
|
16829
|
-
position: absolute;
|
|
16830
|
-
background-color: var(--rel-bg-neutral-inverse-2);
|
|
16831
|
-
color: var(--rel-text-inverse);
|
|
16832
|
-
padding: var(--rel-spacing-xs, 8px)
|
|
16833
|
-
var(--rel-spacing-sm, 12px);
|
|
16834
|
-
border-radius: var(--rel-border-radius-sm, 8px);
|
|
16835
|
-
white-space: nowrap;
|
|
16836
|
-
z-index: 1000;
|
|
16837
|
-
opacity: 0;
|
|
16838
|
-
pointer-events: none;
|
|
16839
|
-
transition: opacity 0.2s ease;
|
|
16840
|
-
overflow: visible;
|
|
16841
|
-
}
|
|
16842
|
-
|
|
16843
|
-
/* Figma Type=Multi Line: wrap inside a bounded bubble */
|
|
16844
|
-
:host([type="multi-line"]) .tooltip-bubble {
|
|
16845
|
-
white-space: normal;
|
|
16846
|
-
max-width: 220px;
|
|
16847
|
-
width: max-content;
|
|
16848
|
-
}
|
|
16849
|
-
|
|
16850
|
-
:host(:hover) .tooltip-bubble,
|
|
16851
|
-
:host(:focus-within) .tooltip-bubble {
|
|
16852
|
-
opacity: 1;
|
|
16853
|
-
}
|
|
16854
|
-
|
|
16855
|
-
/* Positioning */
|
|
16856
|
-
.tooltip-bubble.above {
|
|
16857
|
-
bottom: calc(100% + 6px);
|
|
16858
|
-
left: 50%;
|
|
16859
|
-
transform: translateX(-50%);
|
|
16860
|
-
}
|
|
16861
|
-
|
|
16862
|
-
.tooltip-bubble.below {
|
|
16863
|
-
top: calc(100% + 6px);
|
|
16864
|
-
left: 50%;
|
|
16865
|
-
transform: translateX(-50%);
|
|
16866
|
-
}
|
|
16867
|
-
|
|
16868
|
-
.tooltip-bubble.left {
|
|
16869
|
-
right: calc(100% + 6px);
|
|
16870
|
-
top: 50%;
|
|
16871
|
-
transform: translateY(-50%);
|
|
16872
|
-
}
|
|
16873
|
-
|
|
16874
|
-
.tooltip-bubble.right {
|
|
16875
|
-
left: calc(100% + 6px);
|
|
16876
|
-
top: 50%;
|
|
16877
|
-
transform: translateY(-50%);
|
|
16878
|
-
}
|
|
16879
|
-
|
|
16880
|
-
/* Tail/Arrow */
|
|
16881
|
-
.tooltip-tail {
|
|
16882
|
-
position: absolute;
|
|
16883
|
-
width: 0;
|
|
16884
|
-
height: 0;
|
|
16885
19227
|
display: block;
|
|
19228
|
+
font-family: var(--rel-fonts-font-family);
|
|
16886
19229
|
}
|
|
16887
19230
|
|
|
16888
|
-
.
|
|
16889
|
-
|
|
16890
|
-
|
|
16891
|
-
|
|
16892
|
-
|
|
16893
|
-
border-right: 6px solid transparent;
|
|
16894
|
-
}
|
|
16895
|
-
|
|
16896
|
-
.tooltip-bubble.above .tooltip-tail {
|
|
16897
|
-
bottom: -6px;
|
|
16898
|
-
border-top: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
16899
|
-
border-bottom: none;
|
|
16900
|
-
}
|
|
16901
|
-
|
|
16902
|
-
.tooltip-bubble.below .tooltip-tail {
|
|
16903
|
-
top: -6px;
|
|
16904
|
-
border-bottom: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
16905
|
-
border-top: none;
|
|
16906
|
-
}
|
|
16907
|
-
|
|
16908
|
-
.tooltip-bubble.left .tooltip-tail,
|
|
16909
|
-
.tooltip-bubble.right .tooltip-tail {
|
|
16910
|
-
top: 50%;
|
|
16911
|
-
transform: translateY(-50%);
|
|
16912
|
-
border-top: 6px solid transparent;
|
|
16913
|
-
border-bottom: 6px solid transparent;
|
|
16914
|
-
}
|
|
16915
|
-
|
|
16916
|
-
.tooltip-bubble.left .tooltip-tail {
|
|
16917
|
-
right: -6px;
|
|
16918
|
-
border-left: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
16919
|
-
border-right: none;
|
|
19231
|
+
.actions {
|
|
19232
|
+
display: flex;
|
|
19233
|
+
align-items: center;
|
|
19234
|
+
justify-content: flex-end;
|
|
19235
|
+
gap: var(--rel-spacing-xs, 8px);
|
|
16920
19236
|
}
|
|
16921
19237
|
|
|
16922
|
-
|
|
16923
|
-
|
|
16924
|
-
border-right: 6px solid var(--rel-bg-neutral-inverse-2);
|
|
16925
|
-
border-left: none;
|
|
19238
|
+
rel-button rel-icon {
|
|
19239
|
+
margin-inline-end: var(--rel-spacing-xs, 8px);
|
|
16926
19240
|
}
|
|
16927
19241
|
`;
|
|
16928
19242
|
__decorateClass([
|
|
16929
|
-
|
|
16930
|
-
],
|
|
19243
|
+
property52({ type: String })
|
|
19244
|
+
], RelUploadActions.prototype, "for", 2);
|
|
16931
19245
|
__decorateClass([
|
|
16932
|
-
|
|
16933
|
-
],
|
|
19246
|
+
property52({ type: String, attribute: "cancel-label" })
|
|
19247
|
+
], RelUploadActions.prototype, "cancelLabel", 2);
|
|
16934
19248
|
__decorateClass([
|
|
16935
|
-
|
|
16936
|
-
],
|
|
19249
|
+
property52({ type: Boolean, attribute: "no-cancel" })
|
|
19250
|
+
], RelUploadActions.prototype, "noCancel", 2);
|
|
16937
19251
|
__decorateClass([
|
|
16938
|
-
|
|
16939
|
-
],
|
|
16940
|
-
|
|
16941
|
-
|
|
16942
|
-
],
|
|
19252
|
+
state24()
|
|
19253
|
+
], RelUploadActions.prototype, "snapshot", 2);
|
|
19254
|
+
RelUploadActions = __decorateClass([
|
|
19255
|
+
customElement58("rel-upload-actions")
|
|
19256
|
+
], RelUploadActions);
|
|
16943
19257
|
|
|
16944
19258
|
// src/index.ts
|
|
16945
19259
|
export * from "@reliability-design/icons";
|
|
@@ -16991,6 +19305,7 @@ export {
|
|
|
16991
19305
|
RelTab,
|
|
16992
19306
|
RelTabItem,
|
|
16993
19307
|
RelTextfield,
|
|
19308
|
+
RelTimePicker,
|
|
16994
19309
|
RelToast,
|
|
16995
19310
|
RelTooltip,
|
|
16996
19311
|
RelTopbar,
|
|
@@ -17001,6 +19316,8 @@ export {
|
|
|
17001
19316
|
RelTopbarItem,
|
|
17002
19317
|
RelTopbarMobilePanel,
|
|
17003
19318
|
RelTopbarProfile,
|
|
17004
|
-
RelTopbarUtilities
|
|
19319
|
+
RelTopbarUtilities,
|
|
19320
|
+
RelUpload,
|
|
19321
|
+
RelUploadActions
|
|
17005
19322
|
};
|
|
17006
19323
|
//# sourceMappingURL=index.mjs.map
|