@reliability-design/web 1.4.3 → 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/dist/components/rel-autocomplete/README.md +1 -0
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +56 -1
- 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-range-picker/rel-range-picker.ts +117 -2
- package/dist/components/rel-select/README.md +1 -0
- package/dist/components/rel-select/rel-select.ts +1237 -1170
- 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 +267 -2
- package/dist/index.d.ts +267 -2
- package/dist/index.js +1259 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1273 -61
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1568,6 +1568,14 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1568
1568
|
if (left + t.width > window.innerWidth - 8) {
|
|
1569
1569
|
left = Math.max(8, window.innerWidth - t.width - 8);
|
|
1570
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`;
|
|
1571
1579
|
this.panelStyle = `top:${top}px;left:${left}px;width:${t.width}px;`;
|
|
1572
1580
|
};
|
|
1573
1581
|
}
|
|
@@ -1579,6 +1587,7 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1579
1587
|
disconnectedCallback() {
|
|
1580
1588
|
super.disconnectedCallback();
|
|
1581
1589
|
this.removeGlobalListeners();
|
|
1590
|
+
this.hideNativePopover();
|
|
1582
1591
|
clearTimeout(this.debounceTimer);
|
|
1583
1592
|
}
|
|
1584
1593
|
willUpdate(changed) {
|
|
@@ -1638,13 +1647,40 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1638
1647
|
this.open = true;
|
|
1639
1648
|
this.highlightedIndex = -1;
|
|
1640
1649
|
this.addGlobalListeners();
|
|
1641
|
-
requestAnimationFrame(() =>
|
|
1650
|
+
requestAnimationFrame(() => {
|
|
1651
|
+
this.showNativePopover();
|
|
1652
|
+
this.updatePosition();
|
|
1653
|
+
});
|
|
1642
1654
|
}
|
|
1643
1655
|
closePanel() {
|
|
1644
1656
|
if (!this.open) return;
|
|
1645
1657
|
this.open = false;
|
|
1646
1658
|
this.highlightedIndex = -1;
|
|
1647
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
|
+
}
|
|
1648
1684
|
}
|
|
1649
1685
|
/** Re-evaluates whether the panel should be showing for the current state. */
|
|
1650
1686
|
syncPanel() {
|
|
@@ -1680,17 +1716,17 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1680
1716
|
this.syncPanel();
|
|
1681
1717
|
}
|
|
1682
1718
|
}
|
|
1683
|
-
async runSearch(
|
|
1719
|
+
async runSearch(query12) {
|
|
1684
1720
|
const seq = ++this.searchSeq;
|
|
1685
1721
|
try {
|
|
1686
|
-
const results = await this.searchFn(
|
|
1722
|
+
const results = await this.searchFn(query12);
|
|
1687
1723
|
if (seq !== this.searchSeq) return;
|
|
1688
1724
|
this.remoteItems = results ?? [];
|
|
1689
1725
|
} catch (err) {
|
|
1690
1726
|
if (seq !== this.searchSeq) return;
|
|
1691
1727
|
this.remoteItems = [];
|
|
1692
1728
|
this.dispatchEvent(
|
|
1693
|
-
new CustomEvent("rel-search-error", { detail: { query:
|
|
1729
|
+
new CustomEvent("rel-search-error", { detail: { query: query12, error: err }, bubbles: true, composed: true })
|
|
1694
1730
|
);
|
|
1695
1731
|
} finally {
|
|
1696
1732
|
if (seq === this.searchSeq) {
|
|
@@ -1860,6 +1896,7 @@ var RelAutocomplete = class extends LitElement4 {
|
|
|
1860
1896
|
id="${panelId}"
|
|
1861
1897
|
part="panel list"
|
|
1862
1898
|
class="ac-panel"
|
|
1899
|
+
popover="manual"
|
|
1863
1900
|
role="listbox"
|
|
1864
1901
|
aria-label="${this.accessibleName || "Suggestions"}"
|
|
1865
1902
|
style="${this.panelStyle}"
|
|
@@ -1914,6 +1951,15 @@ RelAutocomplete.styles = css4`
|
|
|
1914
1951
|
position: fixed;
|
|
1915
1952
|
z-index: 1001;
|
|
1916
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;
|
|
1917
1963
|
box-sizing: border-box;
|
|
1918
1964
|
background: var(--rel-autocomplete-bg, var(--rel-bg-neutral));
|
|
1919
1965
|
border: 1px solid var(--rel-autocomplete-menu-border, var(--rel-border-default));
|
|
@@ -4387,6 +4433,9 @@ var RelCheckbox = class extends LitElement13 {
|
|
|
4387
4433
|
this.state = "default";
|
|
4388
4434
|
this.status = "unchecked";
|
|
4389
4435
|
this.value = "";
|
|
4436
|
+
this.size = "medium";
|
|
4437
|
+
this.density = "compact";
|
|
4438
|
+
this.alignment = "leading";
|
|
4390
4439
|
this._onChange = () => {
|
|
4391
4440
|
};
|
|
4392
4441
|
this._onTouched = () => {
|
|
@@ -4398,6 +4447,17 @@ var RelCheckbox = class extends LitElement13 {
|
|
|
4398
4447
|
set checked(val) {
|
|
4399
4448
|
this.status = val ? "checked" : "unchecked";
|
|
4400
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
|
+
}
|
|
4401
4461
|
toggle() {
|
|
4402
4462
|
if (this.state === "disabled") return;
|
|
4403
4463
|
this.status = this.status === "checked" ? "unchecked" : "checked";
|
|
@@ -4445,12 +4505,22 @@ var RelCheckbox = class extends LitElement13 {
|
|
|
4445
4505
|
</svg>
|
|
4446
4506
|
</span>
|
|
4447
4507
|
|
|
4448
|
-
|
|
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}
|
|
4515
|
+
|
|
4516
|
+
<span part="label" class="label">${this.label}</span>
|
|
4517
|
+
</span>
|
|
4449
4518
|
|
|
4450
|
-
|
|
4519
|
+
${this.effectiveDensity === "comfortable" && this.supportingtext ? html13`<span part="helper" class="text">${this.supportingtext}</span>` : null}
|
|
4520
|
+
</span>
|
|
4451
4521
|
</label>
|
|
4452
4522
|
|
|
4453
|
-
${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}
|
|
4454
4524
|
</div>
|
|
4455
4525
|
`;
|
|
4456
4526
|
}
|
|
@@ -4483,6 +4553,29 @@ RelCheckbox.styles = css13`
|
|
|
4483
4553
|
cursor: pointer;
|
|
4484
4554
|
}
|
|
4485
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
|
+
|
|
4486
4579
|
input {
|
|
4487
4580
|
position: absolute;
|
|
4488
4581
|
inset: 0;
|
|
@@ -4625,7 +4718,6 @@ RelCheckbox.styles = css13`
|
|
|
4625
4718
|
|
|
4626
4719
|
.text {
|
|
4627
4720
|
color: var(--rel-text-caption);
|
|
4628
|
-
padding-left: 28px;
|
|
4629
4721
|
|
|
4630
4722
|
font-family: var(--rel-body-sm-regular-font-family);
|
|
4631
4723
|
|
|
@@ -4638,6 +4730,44 @@ RelCheckbox.styles = css13`
|
|
|
4638
4730
|
letter-spacing: var(--rel-body-sm-regular-letter-spacing);
|
|
4639
4731
|
|
|
4640
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
|
+
}
|
|
4641
4771
|
`;
|
|
4642
4772
|
__decorateClass([
|
|
4643
4773
|
property13({ type: String })
|
|
@@ -4657,6 +4787,15 @@ __decorateClass([
|
|
|
4657
4787
|
__decorateClass([
|
|
4658
4788
|
property13({ type: String })
|
|
4659
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);
|
|
4660
4799
|
RelCheckbox = __decorateClass([
|
|
4661
4800
|
customElement13("rel-checkbox")
|
|
4662
4801
|
], RelCheckbox);
|
|
@@ -5788,6 +5927,31 @@ var RelRangePicker = class extends LitElement15 {
|
|
|
5788
5927
|
this.viewMonth = (/* @__PURE__ */ new Date()).getMonth();
|
|
5789
5928
|
this.isSelectingRange = false;
|
|
5790
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
|
+
};
|
|
5791
5955
|
// ── Open / close ──
|
|
5792
5956
|
this.handleClickOutside = (e) => {
|
|
5793
5957
|
if (!this.open) return;
|
|
@@ -5830,14 +5994,64 @@ var RelRangePicker = class extends LitElement15 {
|
|
|
5830
5994
|
super.disconnectedCallback();
|
|
5831
5995
|
document.removeEventListener("click", this.handleClickOutside);
|
|
5832
5996
|
document.removeEventListener("keydown", this.handleGlobalKeydown);
|
|
5997
|
+
window.removeEventListener("resize", this.onReposition);
|
|
5998
|
+
window.removeEventListener("scroll", this.onReposition, true);
|
|
5999
|
+
this.hideTopLayer();
|
|
5833
6000
|
}
|
|
5834
|
-
updated() {
|
|
6001
|
+
updated(changed) {
|
|
5835
6002
|
this.style.width = this.width;
|
|
5836
6003
|
this.style.minWidth = this.minWidth;
|
|
5837
6004
|
this.style.maxWidth = this.maxWidth;
|
|
5838
6005
|
this.style.height = this.height;
|
|
5839
6006
|
this.style.minHeight = this.minHeight;
|
|
5840
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
|
+
}
|
|
5841
6055
|
}
|
|
5842
6056
|
toggleOpen() {
|
|
5843
6057
|
if (this.disabled) return;
|
|
@@ -6193,10 +6407,11 @@ var RelRangePicker = class extends LitElement15 {
|
|
|
6193
6407
|
</button>
|
|
6194
6408
|
`}
|
|
6195
6409
|
|
|
6196
|
-
${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}
|
|
6197
6411
|
|
|
6198
6412
|
<div
|
|
6199
6413
|
part="panel" class="popover ${this.open ? "open" : ""} ${isPopup ? "popup" : ""}"
|
|
6414
|
+
popover="manual"
|
|
6200
6415
|
role="dialog"
|
|
6201
6416
|
aria-label="Select date range"
|
|
6202
6417
|
>
|
|
@@ -6407,6 +6622,17 @@ RelRangePicker.styles = css15`
|
|
|
6407
6622
|
|
|
6408
6623
|
/* ── Popover shell ── */
|
|
6409
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;
|
|
6410
6636
|
position: absolute;
|
|
6411
6637
|
top: calc(100% + 4px);
|
|
6412
6638
|
left: 0;
|
|
@@ -6431,6 +6657,13 @@ RelRangePicker.styles = css15`
|
|
|
6431
6657
|
.backdrop {
|
|
6432
6658
|
position: fixed;
|
|
6433
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;
|
|
6434
6667
|
z-index: 2147483646;
|
|
6435
6668
|
background: rgba(0,0,0,0.4);
|
|
6436
6669
|
}
|
|
@@ -6804,6 +7037,12 @@ __decorateClass([
|
|
|
6804
7037
|
__decorateClass([
|
|
6805
7038
|
query4(".trigger-button")
|
|
6806
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);
|
|
6807
7046
|
RelRangePicker = __decorateClass([
|
|
6808
7047
|
customElement15("rel-range-picker")
|
|
6809
7048
|
], RelRangePicker);
|
|
@@ -11337,7 +11576,7 @@ RelOtp = __decorateClass([
|
|
|
11337
11576
|
], RelOtp);
|
|
11338
11577
|
|
|
11339
11578
|
// src/components/rel-pagination/rel-pagination.ts
|
|
11340
|
-
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";
|
|
11341
11580
|
import { customElement as customElement38, property as property32, state as state14 } from "lit/decorators.js";
|
|
11342
11581
|
var RelPagination = class extends LitElement38 {
|
|
11343
11582
|
constructor() {
|
|
@@ -11408,6 +11647,14 @@ var RelPagination = class extends LitElement38 {
|
|
|
11408
11647
|
}
|
|
11409
11648
|
return pages;
|
|
11410
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
|
+
}
|
|
11411
11658
|
handlePageChange(newPage) {
|
|
11412
11659
|
const total = this.computedTotalPages;
|
|
11413
11660
|
if (this.disabled || newPage < 1 || newPage > total || newPage === this.activePage) return;
|
|
@@ -11453,15 +11700,20 @@ var RelPagination = class extends LitElement38 {
|
|
|
11453
11700
|
<div class="pag-stack">
|
|
11454
11701
|
${this.renderNavBtn("prev")}
|
|
11455
11702
|
<div class="pag-numbers">
|
|
11456
|
-
${pages.map(
|
|
11457
|
-
(p
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
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
|
+
})}
|
|
11465
11717
|
</div>
|
|
11466
11718
|
${this.renderNavBtn("next")}
|
|
11467
11719
|
</div>
|
|
@@ -11614,9 +11866,10 @@ RelPagination.styles = css35`
|
|
|
11614
11866
|
display: inline-flex;
|
|
11615
11867
|
align-items: center;
|
|
11616
11868
|
justify-content: center;
|
|
11617
|
-
width:
|
|
11869
|
+
width: auto;
|
|
11870
|
+
min-width: 40px;
|
|
11618
11871
|
height: 40px;
|
|
11619
|
-
padding: var(--rel-spacing-xs);
|
|
11872
|
+
padding: 0 var(--rel-spacing-xs);
|
|
11620
11873
|
border-radius: var(--rel-border-radius-sm);
|
|
11621
11874
|
border: 1px solid var(--rel-border-default);
|
|
11622
11875
|
background: var(--rel-bg-neutral);
|
|
@@ -13149,7 +13402,7 @@ RelSegmentedButton = __decorateClass([
|
|
|
13149
13402
|
], RelSegmentedButton);
|
|
13150
13403
|
|
|
13151
13404
|
// src/components/rel-select/rel-select.ts
|
|
13152
|
-
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";
|
|
13153
13406
|
import { customElement as customElement46, property as property40, state as state16, query as query8 } from "lit/decorators.js";
|
|
13154
13407
|
import { repeat as repeat2 } from "lit/directives/repeat.js";
|
|
13155
13408
|
import "@reliability-design/icons/rel-icon";
|
|
@@ -13219,6 +13472,14 @@ var RelSelect = class extends LitElement46 {
|
|
|
13219
13472
|
if (left + t.width > window.innerWidth - 8) {
|
|
13220
13473
|
left = Math.max(8, window.innerWidth - t.width - 8);
|
|
13221
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`;
|
|
13222
13483
|
this.panelStyle = `top:${top}px;left:${left}px;width:${t.width}px;`;
|
|
13223
13484
|
};
|
|
13224
13485
|
this.onSearchInput = (e) => {
|
|
@@ -13236,6 +13497,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13236
13497
|
disconnectedCallback() {
|
|
13237
13498
|
super.disconnectedCallback();
|
|
13238
13499
|
this.removeGlobalListeners();
|
|
13500
|
+
this.hideNativePopover();
|
|
13239
13501
|
this.chipResizeObserver?.disconnect();
|
|
13240
13502
|
}
|
|
13241
13503
|
firstUpdated() {
|
|
@@ -13352,6 +13614,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13352
13614
|
this.highlightedIndex = -1;
|
|
13353
13615
|
this.addGlobalListeners();
|
|
13354
13616
|
requestAnimationFrame(() => {
|
|
13617
|
+
this.showNativePopover();
|
|
13355
13618
|
this.updatePosition();
|
|
13356
13619
|
if (!this.noSearch) {
|
|
13357
13620
|
this.searchInputEl?.focus({ preventScroll: true });
|
|
@@ -13362,8 +13625,32 @@ var RelSelect = class extends LitElement46 {
|
|
|
13362
13625
|
if (!this.open) return;
|
|
13363
13626
|
this.open = false;
|
|
13364
13627
|
this.removeGlobalListeners();
|
|
13628
|
+
this.hideNativePopover();
|
|
13365
13629
|
this.triggerEl?.focus();
|
|
13366
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
|
+
}
|
|
13367
13654
|
selectSingleOption(option) {
|
|
13368
13655
|
this.selected = option.value;
|
|
13369
13656
|
if (this.state === "error") this.state = "default";
|
|
@@ -13468,7 +13755,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13468
13755
|
`;
|
|
13469
13756
|
}
|
|
13470
13757
|
)}
|
|
13471
|
-
${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}
|
|
13472
13759
|
</span>
|
|
13473
13760
|
`;
|
|
13474
13761
|
}
|
|
@@ -13538,8 +13825,8 @@ var RelSelect = class extends LitElement46 {
|
|
|
13538
13825
|
aria-controls="${listboxId}"
|
|
13539
13826
|
aria-activedescendant="${activeOptionId}"
|
|
13540
13827
|
aria-invalid="${this.state === "error"}"
|
|
13541
|
-
aria-label="${this.fieldAriaLabelledby ?
|
|
13542
|
-
aria-labelledby="${this.fieldAriaLabelledby ??
|
|
13828
|
+
aria-label="${this.fieldAriaLabelledby ? nothing10 : accessibleName || this.placeholder}"
|
|
13829
|
+
aria-labelledby="${this.fieldAriaLabelledby ?? nothing10}"
|
|
13543
13830
|
?disabled="${isDisabled}"
|
|
13544
13831
|
@click="${this.onTriggerClick}"
|
|
13545
13832
|
@keydown="${this.onTriggerKeyDown}"
|
|
@@ -13563,7 +13850,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13563
13850
|
></rel-icon>
|
|
13564
13851
|
</button>
|
|
13565
13852
|
|
|
13566
|
-
${this.multiple && this.selectedValues.length > 0 ? this.renderChipsMeasure() :
|
|
13853
|
+
${this.multiple && this.selectedValues.length > 0 ? this.renderChipsMeasure() : nothing10}
|
|
13567
13854
|
</div>
|
|
13568
13855
|
|
|
13569
13856
|
${this.supportingText ? html46`<span part="helper" class="select-support ${this.state === "error" ? "error" : ""}">
|
|
@@ -13574,39 +13861,42 @@ var RelSelect = class extends LitElement46 {
|
|
|
13574
13861
|
<div
|
|
13575
13862
|
id="${listboxId}"
|
|
13576
13863
|
part="panel list" class="select-listbox"
|
|
13864
|
+
popover="manual"
|
|
13577
13865
|
role="listbox"
|
|
13578
13866
|
aria-multiselectable="${this.multiple}"
|
|
13579
13867
|
aria-label="${accessibleName || "Options"}"
|
|
13580
13868
|
style="${this.panelStyle}"
|
|
13581
13869
|
?data-open="${this.open}"
|
|
13582
13870
|
>
|
|
13583
|
-
${!this.noSearch ? html46`<div class="select-
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
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>` : ""}
|
|
13595
13895
|
</div>` : ""}
|
|
13596
13896
|
|
|
13597
13897
|
${filtered.length === 0 ? html46`<div class="select-empty">
|
|
13598
13898
|
${opts.length === 0 ? "No options available" : "No matches found"}
|
|
13599
13899
|
</div>` : this.multiple ? html46`
|
|
13600
|
-
<div class="select-all-row">
|
|
13601
|
-
<rel-checkbox
|
|
13602
|
-
label="${this.selectAllLabel}"
|
|
13603
|
-
status="${this.selectAllStatus}"
|
|
13604
|
-
@rel-change=${(e) => {
|
|
13605
|
-
e.stopPropagation();
|
|
13606
|
-
this.toggleSelectAll();
|
|
13607
|
-
}}
|
|
13608
|
-
></rel-checkbox>
|
|
13609
|
-
</div>
|
|
13610
13900
|
<div class="select-options">
|
|
13611
13901
|
${repeat2(
|
|
13612
13902
|
filtered,
|
|
@@ -13653,7 +13943,7 @@ var RelSelect = class extends LitElement46 {
|
|
|
13653
13943
|
name="${option.icon}"
|
|
13654
13944
|
size="sm"
|
|
13655
13945
|
class="select-option-icon"
|
|
13656
|
-
></rel-icon>` :
|
|
13946
|
+
></rel-icon>` : nothing10}
|
|
13657
13947
|
<span class="select-option-label">${option.label}</span>
|
|
13658
13948
|
</span>
|
|
13659
13949
|
${option.value === this.selected ? html46`<rel-icon name="check" size="sm" class="select-option-check"></rel-icon>` : ""}
|
|
@@ -13949,6 +14239,15 @@ RelSelect.styles = css43`
|
|
|
13949
14239
|
position: fixed;
|
|
13950
14240
|
z-index: 1001;
|
|
13951
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;
|
|
13952
14251
|
box-sizing: border-box;
|
|
13953
14252
|
background: var(--rel-select-bg, var(--rel-bg-neutral));
|
|
13954
14253
|
border: 1px solid var(--rel-select-menu-border, var(--rel-border-default));
|
|
@@ -13965,10 +14264,18 @@ RelSelect.styles = css43`
|
|
|
13965
14264
|
display: block;
|
|
13966
14265
|
}
|
|
13967
14266
|
|
|
13968
|
-
.
|
|
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 {
|
|
13969
14272
|
position: sticky;
|
|
13970
14273
|
top: 0;
|
|
14274
|
+
z-index: 1;
|
|
13971
14275
|
background: var(--rel-select-bg, var(--rel-bg-neutral));
|
|
14276
|
+
}
|
|
14277
|
+
|
|
14278
|
+
.select-search {
|
|
13972
14279
|
padding: var(--rel-spacing-xs);
|
|
13973
14280
|
border-bottom: 1px solid var(--rel-select-divider, var(--rel-border-subtle));
|
|
13974
14281
|
}
|
|
@@ -16721,7 +17028,7 @@ RelTab = __decorateClass([
|
|
|
16721
17028
|
], RelTab);
|
|
16722
17029
|
|
|
16723
17030
|
// src/components/rel-time-picker/rel-time-picker.ts
|
|
16724
|
-
import { LitElement as LitElement54, html as html54, css as css51, nothing as
|
|
17031
|
+
import { LitElement as LitElement54, html as html54, css as css51, nothing as nothing11 } from "lit";
|
|
16725
17032
|
import { customElement as customElement54, property as property48, state as state21, query as query10 } from "lit/decorators.js";
|
|
16726
17033
|
import "@reliability-design/icons/rel-icon";
|
|
16727
17034
|
var RelTimePicker = class extends LitElement54 {
|
|
@@ -17084,10 +17391,10 @@ var RelTimePicker = class extends LitElement54 {
|
|
|
17084
17391
|
return html54`
|
|
17085
17392
|
<div part="base" class="${wrapperClasses}">
|
|
17086
17393
|
${this.label ? html54`<label part="label" class="label" id="time-picker-label"
|
|
17087
|
-
>${this.label}${this.required ? html54`<span class="required-mark" aria-hidden="true">*</span>` :
|
|
17088
|
-
>` :
|
|
17394
|
+
>${this.label}${this.required ? html54`<span class="required-mark" aria-hidden="true">*</span>` : nothing11}</label
|
|
17395
|
+
>` : nothing11}
|
|
17089
17396
|
|
|
17090
|
-
${this.name ? html54`<input type="hidden" name=${this.name} .value=${this.value ?? ""} />` :
|
|
17397
|
+
${this.name ? html54`<input type="hidden" name=${this.name} .value=${this.value ?? ""} />` : nothing11}
|
|
17091
17398
|
|
|
17092
17399
|
<button
|
|
17093
17400
|
part="trigger"
|
|
@@ -17108,8 +17415,8 @@ var RelTimePicker = class extends LitElement54 {
|
|
|
17108
17415
|
aria-required=${this.required}
|
|
17109
17416
|
aria-invalid=${isError}
|
|
17110
17417
|
aria-readonly=${this.readonly}
|
|
17111
|
-
aria-describedby=${describedById ??
|
|
17112
|
-
aria-labelledby=${this.label ? "time-picker-label" :
|
|
17418
|
+
aria-describedby=${describedById ?? nothing11}
|
|
17419
|
+
aria-labelledby=${this.label ? "time-picker-label" : nothing11}
|
|
17113
17420
|
>
|
|
17114
17421
|
<span part="value" class="trigger-content ${displayText ? "" : "placeholder"}">
|
|
17115
17422
|
${displayText || this.placeholder}
|
|
@@ -17131,7 +17438,7 @@ var RelTimePicker = class extends LitElement54 {
|
|
|
17131
17438
|
/>
|
|
17132
17439
|
</svg>
|
|
17133
17440
|
</button>
|
|
17134
|
-
` :
|
|
17441
|
+
` : nothing11}
|
|
17135
17442
|
<rel-icon part="icon" name="time-filled" size="sm"></rel-icon>
|
|
17136
17443
|
</span>
|
|
17137
17444
|
</button>
|
|
@@ -17219,7 +17526,7 @@ var RelTimePicker = class extends LitElement54 {
|
|
|
17219
17526
|
)}
|
|
17220
17527
|
</div>
|
|
17221
17528
|
</div>
|
|
17222
|
-
` :
|
|
17529
|
+
` : nothing11}
|
|
17223
17530
|
</div>
|
|
17224
17531
|
|
|
17225
17532
|
<div part="footer" class="time-footer">
|
|
@@ -17260,7 +17567,7 @@ var RelTimePicker = class extends LitElement54 {
|
|
|
17260
17567
|
id=${describedById}
|
|
17261
17568
|
>
|
|
17262
17569
|
${belowText}
|
|
17263
|
-
</div>` :
|
|
17570
|
+
</div>` : nothing11}
|
|
17264
17571
|
</div>
|
|
17265
17572
|
`;
|
|
17266
17573
|
}
|
|
@@ -18045,6 +18352,909 @@ RelTooltip = __decorateClass([
|
|
|
18045
18352
|
customElement56("rel-tooltip")
|
|
18046
18353
|
], RelTooltip);
|
|
18047
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);
|
|
19034
|
+
}
|
|
19035
|
+
|
|
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);
|
|
19040
|
+
}
|
|
19041
|
+
|
|
19042
|
+
rel-progress::part(indicator) {
|
|
19043
|
+
background: var(--rel-bg-success);
|
|
19044
|
+
border-radius: var(--rel-border-radius-full-rounded, 1000px);
|
|
19045
|
+
}
|
|
19046
|
+
|
|
19047
|
+
.row-trailing {
|
|
19048
|
+
display: flex;
|
|
19049
|
+
align-items: center;
|
|
19050
|
+
gap: var(--rel-spacing-sm, 12px);
|
|
19051
|
+
flex-shrink: 0;
|
|
19052
|
+
}
|
|
19053
|
+
|
|
19054
|
+
.status-success {
|
|
19055
|
+
color: var(--rel-text-success);
|
|
19056
|
+
}
|
|
19057
|
+
|
|
19058
|
+
.status-error {
|
|
19059
|
+
color: var(--rel-text-danger);
|
|
19060
|
+
}
|
|
19061
|
+
|
|
19062
|
+
.remove {
|
|
19063
|
+
display: inline-flex;
|
|
19064
|
+
align-items: center;
|
|
19065
|
+
justify-content: center;
|
|
19066
|
+
padding: 0;
|
|
19067
|
+
background: none;
|
|
19068
|
+
border: none;
|
|
19069
|
+
cursor: pointer;
|
|
19070
|
+
color: var(--rel-text-default);
|
|
19071
|
+
}
|
|
19072
|
+
|
|
19073
|
+
.remove:focus-visible {
|
|
19074
|
+
outline: 2px solid var(--rel-border-focus);
|
|
19075
|
+
outline-offset: 2px;
|
|
19076
|
+
border-radius: var(--rel-border-radius-xs, 4px);
|
|
19077
|
+
}
|
|
19078
|
+
|
|
19079
|
+
.file-input {
|
|
19080
|
+
display: none;
|
|
19081
|
+
}
|
|
19082
|
+
|
|
19083
|
+
@media (prefers-reduced-motion: reduce) {
|
|
19084
|
+
.dropzone {
|
|
19085
|
+
transition: none;
|
|
19086
|
+
}
|
|
19087
|
+
}
|
|
19088
|
+
`;
|
|
19089
|
+
__decorateClass([
|
|
19090
|
+
property51({ type: String })
|
|
19091
|
+
], RelUpload.prototype, "accept", 2);
|
|
19092
|
+
__decorateClass([
|
|
19093
|
+
property51({ type: String, attribute: "max-size" })
|
|
19094
|
+
], RelUpload.prototype, "maxSize", 2);
|
|
19095
|
+
__decorateClass([
|
|
19096
|
+
property51({ type: Boolean })
|
|
19097
|
+
], RelUpload.prototype, "multiple", 2);
|
|
19098
|
+
__decorateClass([
|
|
19099
|
+
property51({ type: Boolean, reflect: true })
|
|
19100
|
+
], RelUpload.prototype, "disabled", 2);
|
|
19101
|
+
__decorateClass([
|
|
19102
|
+
property51({ type: String })
|
|
19103
|
+
], RelUpload.prototype, "hint", 2);
|
|
19104
|
+
__decorateClass([
|
|
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);
|
|
19134
|
+
|
|
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 {
|
|
19147
|
+
constructor() {
|
|
19148
|
+
super(...arguments);
|
|
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
|
+
};
|
|
19168
|
+
}
|
|
19169
|
+
connectedCallback() {
|
|
19170
|
+
super.connectedCallback();
|
|
19171
|
+
queueMicrotask(() => this.resolveTarget());
|
|
19172
|
+
this.root.addEventListener("rel-state", this.onState);
|
|
19173
|
+
}
|
|
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");
|
|
19198
|
+
}
|
|
19199
|
+
this.target = found;
|
|
19200
|
+
if (found) this.snapshot = found.state;
|
|
19201
|
+
}
|
|
19202
|
+
render() {
|
|
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>
|
|
19221
|
+
</div>
|
|
19222
|
+
`;
|
|
19223
|
+
}
|
|
19224
|
+
};
|
|
19225
|
+
RelUploadActions.styles = css55`
|
|
19226
|
+
:host {
|
|
19227
|
+
display: block;
|
|
19228
|
+
font-family: var(--rel-fonts-font-family);
|
|
19229
|
+
}
|
|
19230
|
+
|
|
19231
|
+
.actions {
|
|
19232
|
+
display: flex;
|
|
19233
|
+
align-items: center;
|
|
19234
|
+
justify-content: flex-end;
|
|
19235
|
+
gap: var(--rel-spacing-xs, 8px);
|
|
19236
|
+
}
|
|
19237
|
+
|
|
19238
|
+
rel-button rel-icon {
|
|
19239
|
+
margin-inline-end: var(--rel-spacing-xs, 8px);
|
|
19240
|
+
}
|
|
19241
|
+
`;
|
|
19242
|
+
__decorateClass([
|
|
19243
|
+
property52({ type: String })
|
|
19244
|
+
], RelUploadActions.prototype, "for", 2);
|
|
19245
|
+
__decorateClass([
|
|
19246
|
+
property52({ type: String, attribute: "cancel-label" })
|
|
19247
|
+
], RelUploadActions.prototype, "cancelLabel", 2);
|
|
19248
|
+
__decorateClass([
|
|
19249
|
+
property52({ type: Boolean, attribute: "no-cancel" })
|
|
19250
|
+
], RelUploadActions.prototype, "noCancel", 2);
|
|
19251
|
+
__decorateClass([
|
|
19252
|
+
state24()
|
|
19253
|
+
], RelUploadActions.prototype, "snapshot", 2);
|
|
19254
|
+
RelUploadActions = __decorateClass([
|
|
19255
|
+
customElement58("rel-upload-actions")
|
|
19256
|
+
], RelUploadActions);
|
|
19257
|
+
|
|
18048
19258
|
// src/index.ts
|
|
18049
19259
|
export * from "@reliability-design/icons";
|
|
18050
19260
|
export {
|
|
@@ -18106,6 +19316,8 @@ export {
|
|
|
18106
19316
|
RelTopbarItem,
|
|
18107
19317
|
RelTopbarMobilePanel,
|
|
18108
19318
|
RelTopbarProfile,
|
|
18109
|
-
RelTopbarUtilities
|
|
19319
|
+
RelTopbarUtilities,
|
|
19320
|
+
RelUpload,
|
|
19321
|
+
RelUploadActions
|
|
18110
19322
|
};
|
|
18111
19323
|
//# sourceMappingURL=index.mjs.map
|