@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/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(() => this.updatePosition());
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(query11) {
1719
+ async runSearch(query12) {
1684
1720
  const seq = ++this.searchSeq;
1685
1721
  try {
1686
- const results = await this.searchFn(query11);
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: query11, error: err }, bubbles: true, composed: true })
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
- ${this.icon ? html13`<rel-icon name="${this.icon}" size="md" class="icon-prefix"></rel-icon>` : null}
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
- <span part="label" class="label">${this.label}</span>
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) => p === -1 ? html38`<span class="pag-ellipsis">...</span>` : html38`<button
11458
- type="button"
11459
- class="pag-page-btn ${Math.abs(p - current) <= 1 ? "near" : ""} ${p === 1 || p === total ? "edge" : ""}"
11460
- aria-label="Page ${p}"
11461
- aria-current="${p === current ? "page" : "false"}"
11462
- @click="${() => this.handlePageChange(p)}"
11463
- >${p}</button>`
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: 40px;
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 nothing9 } from "lit";
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>` : nothing9}
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 ? nothing9 : accessibleName || this.placeholder}"
13542
- aria-labelledby="${this.fieldAriaLabelledby ?? nothing9}"
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() : nothing9}
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-search">
13584
- <div class="search-input-wrap">
13585
- <rel-icon name="search" size="${this.searchIconSize}" class="icon"></rel-icon>
13586
- <input
13587
- part="input" class="search-input"
13588
- type="text"
13589
- placeholder="${this.searchPlaceholder}"
13590
- .value="${this.searchValue}"
13591
- @input="${this.onSearchInput}"
13592
- @keydown="${(e) => this.onSearchKeyDown(e)}"
13593
- />
13594
- </div>
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>` : nothing9}
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
- .select-search {
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 nothing10 } from "lit";
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>` : nothing10}</label
17088
- >` : nothing10}
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 ?? ""} />` : nothing10}
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 ?? nothing10}
17112
- aria-labelledby=${this.label ? "time-picker-label" : nothing10}
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
- ` : nothing10}
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
- ` : nothing10}
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>` : nothing10}
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