@rogieking/figui3 2.21.0 → 2.22.0

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.
Files changed (3) hide show
  1. package/fig.js +82 -63
  2. package/index.html +11 -0
  3. package/package.json +1 -1
package/fig.js CHANGED
@@ -1177,8 +1177,6 @@ class FigPopup extends HTMLDialogElement {
1177
1177
  this.#setupDragListeners();
1178
1178
  } else {
1179
1179
  this.#removeDragListeners();
1180
- const header = this.querySelector("fig-header, header");
1181
- if (header) header.style.cursor = "";
1182
1180
  }
1183
1181
  return;
1184
1182
  }
@@ -1298,19 +1296,32 @@ class FigPopup extends HTMLDialogElement {
1298
1296
  const anchor = this.#resolveAnchor();
1299
1297
  if (anchor && anchor.contains(target)) return;
1300
1298
 
1299
+ if (this.#isInsideDescendantPopup(target)) return;
1300
+
1301
1301
  this.open = false;
1302
1302
  }
1303
1303
 
1304
+ #isInsideDescendantPopup(target) {
1305
+ const targetDialog = target.closest?.('dialog[is="fig-popup"]');
1306
+ if (!targetDialog || targetDialog === this) return false;
1307
+
1308
+ let current = targetDialog;
1309
+ const visited = new Set();
1310
+ while (current && !visited.has(current)) {
1311
+ visited.add(current);
1312
+ const popupAnchor = current.anchor;
1313
+ if (!(popupAnchor instanceof Element)) break;
1314
+ if (this.contains(popupAnchor)) return true;
1315
+ current = popupAnchor.closest?.('dialog[is="fig-popup"]');
1316
+ }
1317
+ return false;
1318
+ }
1319
+
1304
1320
  // ---- Drag support ----
1305
1321
 
1306
1322
  #setupDragListeners() {
1307
1323
  if (this.drag) {
1308
1324
  this.addEventListener("pointerdown", this.#boundPointerDown);
1309
- const handleSelector = this.getAttribute("handle");
1310
- const handleEl = handleSelector
1311
- ? this.querySelector(handleSelector)
1312
- : this.querySelector("fig-header, header");
1313
- if (handleEl) handleEl.style.cursor = "grab";
1314
1325
  }
1315
1326
  }
1316
1327
 
@@ -1527,21 +1538,49 @@ class FigPopup extends HTMLDialogElement {
1527
1538
  };
1528
1539
  }
1529
1540
 
1530
- #getOrder(preferred, axis) {
1531
- const verticalMap = {
1532
- top: ["top", "bottom", "center"],
1533
- center: ["center", "top", "bottom"],
1534
- bottom: ["bottom", "top", "center"],
1535
- };
1536
- const horizontalMap = {
1537
- left: ["left", "right", "center"],
1538
- center: ["center", "left", "right"],
1539
- right: ["right", "left", "center"],
1540
- };
1541
+ #getPlacementCandidates(vertical, horizontal, shorthand) {
1542
+ const opp = { top: "bottom", bottom: "top", left: "right", right: "left", center: "center" };
1541
1543
 
1542
- return axis === "vertical"
1543
- ? verticalMap[preferred] || verticalMap.top
1544
- : horizontalMap[preferred] || horizontalMap.center;
1544
+ if (shorthand) {
1545
+ const perp = (shorthand === "left" || shorthand === "right")
1546
+ ? ["top", "bottom"]
1547
+ : ["left", "right"];
1548
+ return [
1549
+ { v: vertical, h: horizontal, s: shorthand },
1550
+ { v: vertical, h: horizontal, s: opp[shorthand] },
1551
+ { v: vertical, h: horizontal, s: perp[0] },
1552
+ { v: vertical, h: horizontal, s: perp[1] },
1553
+ ];
1554
+ }
1555
+
1556
+ if (vertical === "center") {
1557
+ return [
1558
+ { v: "center", h: horizontal, s: null },
1559
+ { v: "center", h: opp[horizontal], s: null },
1560
+ { v: "top", h: horizontal, s: null },
1561
+ { v: "bottom", h: horizontal, s: null },
1562
+ { v: "top", h: opp[horizontal], s: null },
1563
+ { v: "bottom", h: opp[horizontal], s: null },
1564
+ ];
1565
+ }
1566
+
1567
+ if (horizontal === "center") {
1568
+ return [
1569
+ { v: vertical, h: "center", s: null },
1570
+ { v: opp[vertical], h: "center", s: null },
1571
+ { v: vertical, h: "left", s: null },
1572
+ { v: vertical, h: "right", s: null },
1573
+ { v: opp[vertical], h: "left", s: null },
1574
+ { v: opp[vertical], h: "right", s: null },
1575
+ ];
1576
+ }
1577
+
1578
+ return [
1579
+ { v: vertical, h: horizontal, s: null },
1580
+ { v: opp[vertical], h: horizontal, s: null },
1581
+ { v: vertical, h: opp[horizontal], s: null },
1582
+ { v: opp[vertical], h: opp[horizontal], s: null },
1583
+ ];
1545
1584
  }
1546
1585
 
1547
1586
  #computeCoords(anchorRect, popupRect, vertical, horizontal, offset, shorthand) {
@@ -1695,8 +1734,6 @@ class FigPopup extends HTMLDialogElement {
1695
1734
  const popupRect = this.getBoundingClientRect();
1696
1735
  const offset = this.#parseOffset();
1697
1736
  const { vertical, horizontal, shorthand } = this.#parsePosition();
1698
- const verticalOrder = this.#getOrder(vertical, "vertical");
1699
- const horizontalOrder = this.#getOrder(horizontal, "horizontal");
1700
1737
  const anchor = this.#resolveAnchor();
1701
1738
 
1702
1739
  if (!anchor) {
@@ -1712,52 +1749,32 @@ class FigPopup extends HTMLDialogElement {
1712
1749
  }
1713
1750
 
1714
1751
  const anchorRect = anchor.getBoundingClientRect();
1752
+ const candidates = this.#getPlacementCandidates(vertical, horizontal, shorthand);
1715
1753
  let best = null;
1716
1754
  let bestSide = "top";
1717
1755
  let bestScore = Number.POSITIVE_INFINITY;
1718
1756
 
1719
- for (const v of verticalOrder) {
1720
- for (const h of horizontalOrder) {
1721
- const coords = this.#computeCoords(
1722
- anchorRect,
1723
- popupRect,
1724
- v,
1725
- h,
1726
- offset,
1727
- shorthand
1728
- );
1729
- const placementSide = this.#getPlacementSide(v, h, shorthand);
1730
- if (this.#fits(coords, popupRect)) {
1731
- this.style.left = `${coords.left}px`;
1732
- this.style.top = `${coords.top}px`;
1733
- this.#updatePopoverBeak(
1734
- anchorRect,
1735
- popupRect,
1736
- coords.left,
1737
- coords.top,
1738
- placementSide
1739
- );
1740
- return;
1741
- }
1742
- const score = this.#overflowScore(coords, popupRect);
1743
- if (score < bestScore) {
1744
- bestScore = score;
1745
- best = coords;
1746
- bestSide = placementSide;
1747
- }
1757
+ for (const { v, h, s } of candidates) {
1758
+ const coords = this.#computeCoords(anchorRect, popupRect, v, h, offset, s);
1759
+ const placementSide = this.#getPlacementSide(v, h, s);
1760
+ if (this.#fits(coords, popupRect)) {
1761
+ this.style.left = `${coords.left}px`;
1762
+ this.style.top = `${coords.top}px`;
1763
+ this.#updatePopoverBeak(anchorRect, popupRect, coords.left, coords.top, placementSide);
1764
+ return;
1765
+ }
1766
+ const score = this.#overflowScore(coords, popupRect);
1767
+ if (score < bestScore) {
1768
+ bestScore = score;
1769
+ best = coords;
1770
+ bestSide = placementSide;
1748
1771
  }
1749
1772
  }
1750
1773
 
1751
1774
  const clamped = this.#clamp(best || { left: 0, top: 0 }, popupRect);
1752
1775
  this.style.left = `${clamped.left}px`;
1753
1776
  this.style.top = `${clamped.top}px`;
1754
- this.#updatePopoverBeak(
1755
- anchorRect,
1756
- popupRect,
1757
- clamped.left,
1758
- clamped.top,
1759
- bestSide
1760
- );
1777
+ this.#updatePopoverBeak(anchorRect, popupRect, clamped.left, clamped.top, bestSide);
1761
1778
  }
1762
1779
 
1763
1780
  #queueReposition() {
@@ -3352,6 +3369,8 @@ class FigInputColor extends HTMLElement {
3352
3369
  const showAlpha = this.getAttribute("alpha") === "true";
3353
3370
  const experimental = this.getAttribute("experimental");
3354
3371
  const expAttr = experimental ? `experimental="${experimental}"` : "";
3372
+ const dialogPos = this.getAttribute("dialog-position") || "left";
3373
+ const dialogPosAttr = `dialog-position="${dialogPos}"`;
3355
3374
 
3356
3375
  let html = ``;
3357
3376
  if (this.getAttribute("text")) {
@@ -3376,7 +3395,7 @@ class FigInputColor extends HTMLElement {
3376
3395
  let swatchElement = "";
3377
3396
  if (!hidePicker) {
3378
3397
  swatchElement = useFigmaPicker
3379
- ? `<fig-fill-picker mode="solid" dialog-position="left" ${expAttr} ${
3398
+ ? `<fig-fill-picker mode="solid" ${dialogPosAttr} ${expAttr} ${
3380
3399
  showAlpha ? "" : 'alpha="false"'
3381
3400
  } value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
3382
3401
  this.alpha
@@ -3394,7 +3413,7 @@ class FigInputColor extends HTMLElement {
3394
3413
  html = ``;
3395
3414
  } else {
3396
3415
  html = useFigmaPicker
3397
- ? `<fig-fill-picker mode="solid" dialog-position="left" ${expAttr} ${
3416
+ ? `<fig-fill-picker mode="solid" ${dialogPosAttr} ${expAttr} ${
3398
3417
  showAlpha ? "" : 'alpha="false"'
3399
3418
  } value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
3400
3419
  this.alpha
@@ -3586,7 +3605,7 @@ class FigInputColor extends HTMLElement {
3586
3605
  }
3587
3606
 
3588
3607
  static get observedAttributes() {
3589
- return ["value", "style", "mode", "picker", "experimental"];
3608
+ return ["value", "style", "mode", "picker", "experimental", "dialog-position"];
3590
3609
  }
3591
3610
 
3592
3611
  get mode() {
@@ -6957,7 +6976,7 @@ class FigFillPicker extends HTMLElement {
6957
6976
  <fig-input-number class="fig-fill-picker-stop-position" min="0" max="100" value="${
6958
6977
  stop.position
6959
6978
  }" units="%"></fig-input-number>
6960
- <fig-input-color class="fig-fill-picker-stop-color" text="true" alpha="true" picker="figma" value="${
6979
+ <fig-input-color class="fig-fill-picker-stop-color" text="true" alpha="true" picker="figma" dialog-position="right" value="${
6961
6980
  stop.color
6962
6981
  }"></fig-input-color>
6963
6982
  <fig-button icon variant="ghost" class="fig-fill-picker-stop-remove" ${
package/index.html CHANGED
@@ -1806,6 +1806,13 @@
1806
1806
  alpha="true"
1807
1807
  picker="figma"></fig-input-color>
1808
1808
 
1809
+ <h4>Figma Picker (Experimental Modern)</h4>
1810
+ <fig-input-color value="#E84393"
1811
+ text="true"
1812
+ alpha="true"
1813
+ picker="figma"
1814
+ experimental="modern"></fig-input-color>
1815
+
1809
1816
  <h4>No Picker (text input only)</h4>
1810
1817
  <fig-input-color value="#9747FF"
1811
1818
  text="true"
@@ -1995,6 +2002,10 @@
1995
2002
  <h3>Webcam Fill</h3>
1996
2003
  <fig-input-fill value='{"type":"webcam","webcam":{"opacity":1}}'></fig-input-fill>
1997
2004
 
2005
+ <h3>Experimental Modern</h3>
2006
+ <fig-input-fill experimental="modern"
2007
+ value='{"type":"solid","color":"#E84393","alpha":1}'></fig-input-fill>
2008
+
1998
2009
  <h3>Disabled</h3>
1999
2010
  <fig-input-fill disabled
2000
2011
  value='{"type":"solid","color":"#AA96DA","alpha":1}'></fig-input-fill>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "2.21.0",
3
+ "version": "2.22.0",
4
4
  "description": "A lightweight web components library for building Figma plugin and widget UIs with native look and feel",
5
5
  "author": "Rogie King",
6
6
  "license": "MIT",