@masters-union/union-stack 0.3.10 → 0.3.11

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/picker.d.cts CHANGED
@@ -25,6 +25,7 @@ declare class Picker {
25
25
  private editor;
26
26
  private abortCtrl;
27
27
  private uploadStarted;
28
+ private validateSeqCounter;
28
29
  private resolved;
29
30
  private resolvePromise;
30
31
  private donePromise;
package/dist/picker.d.ts CHANGED
@@ -25,6 +25,7 @@ declare class Picker {
25
25
  private editor;
26
26
  private abortCtrl;
27
27
  private uploadStarted;
28
+ private validateSeqCounter;
28
29
  private resolved;
29
30
  private resolvePromise;
30
31
  private donePromise;
package/dist/picker.js CHANGED
@@ -105,6 +105,10 @@ var BASE_CSS = `
105
105
  .us-picker-backdrop {
106
106
  --us-font: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
107
107
  --us-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
108
+ /* Default to a light color-scheme so native controls never inherit the
109
+ visitor's OS dark mode; picker.ts overrides this to match the resolved
110
+ theme mode on mount. */
111
+ color-scheme: light;
108
112
  position: fixed; inset: 0; z-index: 2147483000;
109
113
  background: rgba(2, 6, 23, 0.4);
110
114
  -webkit-backdrop-filter: blur(8px);
@@ -277,6 +281,14 @@ var BASE_CSS = `
277
281
  color: var(--us-muted); font-size: 11px; flex-shrink: 0;
278
282
  font-family: var(--us-mono); letter-spacing: 0;
279
283
  }
284
+ /* Failure reason \u2014 full-width, danger-colored, wraps. Collapses entirely when
285
+ empty so non-failed rows keep their original height. */
286
+ .us-file-error {
287
+ color: var(--us-danger); font-size: 12px; line-height: 1.35;
288
+ margin-top: 4px; letter-spacing: -0.006em;
289
+ overflow-wrap: anywhere;
290
+ }
291
+ .us-file-error:empty { display: none; }
280
292
 
281
293
  .us-file-progress {
282
294
  height: 3px; background: var(--us-border); border-radius: 999px; overflow: hidden;
@@ -854,21 +866,30 @@ var ImageEditor = class {
854
866
  const dw = this.displayCanvas.width;
855
867
  const dh = this.displayCanvas.height;
856
868
  if (this.aspect) {
869
+ const a = this.aspect;
857
870
  let w = dw;
858
- let h = w / this.aspect;
871
+ let h = w / a;
859
872
  if (h > dh) {
860
873
  h = dh;
861
- w = h * this.aspect;
874
+ w = h * a;
875
+ }
876
+ w = w * 0.9;
877
+ h = w / a;
878
+ if (w < MIN_CROP) {
879
+ w = MIN_CROP;
880
+ h = w / a;
881
+ }
882
+ if (h < MIN_CROP) {
883
+ h = MIN_CROP;
884
+ w = h * a;
862
885
  }
863
- w = Math.max(MIN_CROP, Math.round(w * 0.9));
864
- h = Math.max(MIN_CROP, Math.round(w / this.aspect));
865
886
  if (w > dw) {
866
887
  w = dw;
867
- h = w / this.aspect;
888
+ h = w / a;
868
889
  }
869
890
  if (h > dh) {
870
891
  h = dh;
871
- w = h * this.aspect;
892
+ w = h * a;
872
893
  }
873
894
  this.cropRect = {
874
895
  x: Math.round((dw - w) / 2),
@@ -989,6 +1010,7 @@ var ImageEditor = class {
989
1010
  this.working = next;
990
1011
  this.exitCropMode();
991
1012
  this.markEdited(true);
1013
+ this.opts.onAspectReset?.();
992
1014
  this.draw();
993
1015
  }
994
1016
  applyCircle() {
@@ -1012,11 +1034,13 @@ var ImageEditor = class {
1012
1034
  this.hasAlpha = true;
1013
1035
  this.exitCropMode();
1014
1036
  this.markEdited(true);
1037
+ this.opts.onAspectReset?.();
1015
1038
  this.draw();
1016
1039
  }
1017
1040
  async revert() {
1018
1041
  await this.loadTrueOriginalIntoWorking();
1019
1042
  this.exitCropMode();
1043
+ this.opts.onAspectReset?.();
1020
1044
  this.draw();
1021
1045
  }
1022
1046
  markEdited(edited) {
@@ -1180,6 +1204,10 @@ var Picker = class {
1180
1204
  this.editor = null;
1181
1205
  this.abortCtrl = new AbortController();
1182
1206
  this.uploadStarted = false;
1207
+ // Monotonic counter so a stale image-dimension decode (e.g. one started before
1208
+ // the user edited the file) can't clobber the row's state — only the latest
1209
+ // validation per item is allowed to mutate it.
1210
+ this.validateSeqCounter = 0;
1183
1211
  this.resolved = false;
1184
1212
  // Element focused before the modal opened, restored on close (a11y).
1185
1213
  this.previouslyFocused = null;
@@ -1227,6 +1255,7 @@ var Picker = class {
1227
1255
  Object.entries(themeToCssVars(this.opts.theme)).forEach(([k, v]) => {
1228
1256
  root.style.setProperty(k, v);
1229
1257
  });
1258
+ root.style.colorScheme = this.opts.theme?.mode === "dark" ? "dark" : "light";
1230
1259
  root.addEventListener("click", (e) => {
1231
1260
  if (e.target === root && !this.uploadStarted) this.cancel();
1232
1261
  });
@@ -1637,6 +1666,9 @@ var Picker = class {
1637
1666
  const bar = el2("div", "us-file-progress-bar");
1638
1667
  progress.appendChild(bar);
1639
1668
  main.appendChild(progress);
1669
+ const error = el2("div", "us-file-error");
1670
+ if (item.error) error.textContent = item.error;
1671
+ main.appendChild(error);
1640
1672
  row.appendChild(main);
1641
1673
  const isImage = item.file.type.startsWith("image/");
1642
1674
  const editingEnabled = this.opts.imageEditing !== false;
@@ -1660,6 +1692,7 @@ var Picker = class {
1660
1692
  item.$bar = bar;
1661
1693
  item.$status = status;
1662
1694
  item.$meta = meta;
1695
+ item.$error = error;
1663
1696
  item.$thumb = thumb;
1664
1697
  this.$list.appendChild(row);
1665
1698
  this.updateSummary();
@@ -1711,6 +1744,11 @@ var Picker = class {
1711
1744
  onCropApplied: () => {
1712
1745
  didRatioCrop = true;
1713
1746
  },
1747
+ // Rotating/circling/reverting after a crop changes the working aspect, so
1748
+ // the image is no longer guaranteed to match the locked ratio.
1749
+ onAspectReset: () => {
1750
+ didRatioCrop = false;
1751
+ },
1714
1752
  onApply: (edited) => {
1715
1753
  const wasEdited = edited !== item.originalFile;
1716
1754
  this.replaceItemFile(item, edited, wasEdited);
@@ -1775,16 +1813,13 @@ var Picker = class {
1775
1813
  case "uploading":
1776
1814
  item.$meta.textContent = `${size} \xB7 ${Math.round(item.progress)}%`;
1777
1815
  break;
1778
- case "done":
1779
- item.$meta.textContent = size;
1780
- break;
1781
- case "failed":
1782
- item.$meta.textContent = item.error || "Failed";
1783
- break;
1784
1816
  default:
1785
1817
  item.$meta.textContent = size;
1786
1818
  }
1787
1819
  }
1820
+ if (item.$error) {
1821
+ item.$error.textContent = state === "failed" ? item.error || "Upload failed" : "";
1822
+ }
1788
1823
  this.updateSummary();
1789
1824
  }
1790
1825
  updateSummary() {
@@ -1844,25 +1879,27 @@ var Picker = class {
1844
1879
  // Decode an image and fail the row if it's below the configured minimum.
1845
1880
  // Non-decodable inputs are left queued — we can't prove they're too small.
1846
1881
  async validateDimensions(item) {
1882
+ const seq = ++this.validateSeqCounter;
1883
+ item.validateSeq = seq;
1847
1884
  const minW = this.opts.minImageWidth ?? 0;
1848
1885
  const minH = this.opts.minImageHeight ?? 0;
1886
+ const file = item.file;
1849
1887
  let dim = null;
1850
1888
  try {
1851
- dim = await readImageSize(item.file);
1889
+ dim = await readImageSize(file);
1852
1890
  } catch {
1853
1891
  dim = null;
1854
1892
  }
1855
- if (item.state !== "queued") {
1856
- item.validating = false;
1857
- return;
1858
- }
1893
+ if (item.validateSeq !== seq) return;
1859
1894
  item.validating = false;
1860
- if (dim && dim.w > 0 && dim.h > 0 && (dim.w < minW || dim.h < minH)) {
1861
- const need = `${minW || "?"}\xD7${minH || "?"}`;
1862
- item.error = `Image too small \u2014 needs at least ${need}px (got ${dim.w}\xD7${dim.h})`;
1863
- this.setItemState(item, "failed");
1864
- } else {
1865
- this.updateItemBadge(item);
1895
+ if (item.state === "queued") {
1896
+ if (dim && dim.w > 0 && dim.h > 0 && (dim.w < minW || dim.h < minH)) {
1897
+ const need = `${minW || "?"}\xD7${minH || "?"}`;
1898
+ item.error = `Image too small \u2014 needs at least ${need}px (got ${dim.w}\xD7${dim.h})`;
1899
+ this.setItemState(item, "failed");
1900
+ } else {
1901
+ this.updateItemBadge(item);
1902
+ }
1866
1903
  }
1867
1904
  this.refreshConfirm();
1868
1905
  this.maybeAutoUpload();