@rogieking/figui3 2.21.0 → 2.22.1
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/fig.js +106 -80
- package/index.html +15 -2
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -1072,7 +1072,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1072
1072
|
constructor() {
|
|
1073
1073
|
super();
|
|
1074
1074
|
this.#boundReposition = this.#queueReposition.bind(this);
|
|
1075
|
-
this.#boundScroll = this.#
|
|
1075
|
+
this.#boundScroll = () => { if (this.open) this.#positionPopup(); };
|
|
1076
1076
|
this.#boundOutsidePointerDown = this.#handleOutsidePointerDown.bind(this);
|
|
1077
1077
|
this.#boundPointerDown = this.#handlePointerDown.bind(this);
|
|
1078
1078
|
this.#boundPointerMove = this.#handlePointerMove.bind(this);
|
|
@@ -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
|
}
|
|
@@ -1267,7 +1265,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1267
1265
|
}
|
|
1268
1266
|
|
|
1269
1267
|
window.addEventListener("resize", this.#boundReposition);
|
|
1270
|
-
window.addEventListener("scroll", this.#boundScroll, true);
|
|
1268
|
+
window.addEventListener("scroll", this.#boundScroll, { capture: true, passive: true });
|
|
1271
1269
|
}
|
|
1272
1270
|
|
|
1273
1271
|
#teardownObservers() {
|
|
@@ -1284,7 +1282,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1284
1282
|
this.#mutationObserver = null;
|
|
1285
1283
|
}
|
|
1286
1284
|
window.removeEventListener("resize", this.#boundReposition);
|
|
1287
|
-
window.removeEventListener("scroll", this.#boundScroll, true);
|
|
1285
|
+
window.removeEventListener("scroll", this.#boundScroll, { capture: true, passive: true });
|
|
1288
1286
|
}
|
|
1289
1287
|
|
|
1290
1288
|
#handleOutsidePointerDown(event) {
|
|
@@ -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,53 +1538,67 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1527
1538
|
};
|
|
1528
1539
|
}
|
|
1529
1540
|
|
|
1530
|
-
#
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
+
#getPlacementCandidates(vertical, horizontal, shorthand) {
|
|
1542
|
+
const opp = { top: "bottom", bottom: "top", left: "right", right: "left", center: "center" };
|
|
1543
|
+
|
|
1544
|
+
if (shorthand) {
|
|
1545
|
+
const isHorizontal = shorthand === "left" || shorthand === "right";
|
|
1546
|
+
const perp = isHorizontal ? ["top", "bottom"] : ["left", "right"];
|
|
1547
|
+
return [
|
|
1548
|
+
{ v: vertical, h: horizontal, s: shorthand },
|
|
1549
|
+
{ v: vertical, h: horizontal, s: opp[shorthand] },
|
|
1550
|
+
{ v: vertical, h: horizontal, s: perp[0] },
|
|
1551
|
+
{ v: vertical, h: horizontal, s: perp[1] },
|
|
1552
|
+
];
|
|
1553
|
+
}
|
|
1541
1554
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1555
|
+
if (vertical === "center") {
|
|
1556
|
+
return [
|
|
1557
|
+
{ v: "center", h: horizontal, s: null },
|
|
1558
|
+
{ v: "center", h: opp[horizontal], s: null },
|
|
1559
|
+
{ v: "top", h: horizontal, s: null },
|
|
1560
|
+
{ v: "bottom", h: horizontal, s: null },
|
|
1561
|
+
{ v: "top", h: opp[horizontal], s: null },
|
|
1562
|
+
{ v: "bottom", h: opp[horizontal], s: null },
|
|
1563
|
+
];
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
if (horizontal === "center") {
|
|
1567
|
+
return [
|
|
1568
|
+
{ v: vertical, h: "center", s: null },
|
|
1569
|
+
{ v: opp[vertical], h: "center", s: null },
|
|
1570
|
+
{ v: vertical, h: "left", s: null },
|
|
1571
|
+
{ v: vertical, h: "right", s: null },
|
|
1572
|
+
{ v: opp[vertical], h: "left", s: null },
|
|
1573
|
+
{ v: opp[vertical], h: "right", s: null },
|
|
1574
|
+
];
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
return [
|
|
1578
|
+
{ v: vertical, h: horizontal, s: null },
|
|
1579
|
+
{ v: opp[vertical], h: horizontal, s: null },
|
|
1580
|
+
{ v: vertical, h: opp[horizontal], s: null },
|
|
1581
|
+
{ v: opp[vertical], h: opp[horizontal], s: null },
|
|
1582
|
+
];
|
|
1545
1583
|
}
|
|
1546
1584
|
|
|
1547
1585
|
#computeCoords(anchorRect, popupRect, vertical, horizontal, offset, shorthand) {
|
|
1548
1586
|
let top;
|
|
1549
1587
|
let left;
|
|
1550
1588
|
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
left: anchorRect.right + offset.xPx,
|
|
1558
|
-
};
|
|
1589
|
+
if (shorthand === "left" || shorthand === "right") {
|
|
1590
|
+
left = shorthand === "left"
|
|
1591
|
+
? anchorRect.left - popupRect.width - offset.xPx
|
|
1592
|
+
: anchorRect.right + offset.xPx;
|
|
1593
|
+
top = anchorRect.top;
|
|
1594
|
+
return { top, left };
|
|
1559
1595
|
}
|
|
1560
|
-
if (shorthand === "
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
if (shorthand === "top") {
|
|
1567
|
-
return {
|
|
1568
|
-
top: anchorRect.top - popupRect.height - offset.yPx,
|
|
1569
|
-
left: anchorRect.left,
|
|
1570
|
-
};
|
|
1571
|
-
}
|
|
1572
|
-
if (shorthand === "bottom") {
|
|
1573
|
-
return {
|
|
1574
|
-
top: anchorRect.bottom + offset.yPx,
|
|
1575
|
-
left: anchorRect.left,
|
|
1576
|
-
};
|
|
1596
|
+
if (shorthand === "top" || shorthand === "bottom") {
|
|
1597
|
+
top = shorthand === "top"
|
|
1598
|
+
? anchorRect.top - popupRect.height - offset.yPx
|
|
1599
|
+
: anchorRect.bottom + offset.yPx;
|
|
1600
|
+
left = anchorRect.left;
|
|
1601
|
+
return { top, left };
|
|
1577
1602
|
}
|
|
1578
1603
|
|
|
1579
1604
|
if (vertical === "top") {
|
|
@@ -1695,8 +1720,6 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1695
1720
|
const popupRect = this.getBoundingClientRect();
|
|
1696
1721
|
const offset = this.#parseOffset();
|
|
1697
1722
|
const { vertical, horizontal, shorthand } = this.#parsePosition();
|
|
1698
|
-
const verticalOrder = this.#getOrder(vertical, "vertical");
|
|
1699
|
-
const horizontalOrder = this.#getOrder(horizontal, "horizontal");
|
|
1700
1723
|
const anchor = this.#resolveAnchor();
|
|
1701
1724
|
|
|
1702
1725
|
if (!anchor) {
|
|
@@ -1712,31 +1735,38 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1712
1735
|
}
|
|
1713
1736
|
|
|
1714
1737
|
const anchorRect = anchor.getBoundingClientRect();
|
|
1738
|
+
const candidates = this.#getPlacementCandidates(vertical, horizontal, shorthand);
|
|
1715
1739
|
let best = null;
|
|
1716
1740
|
let bestSide = "top";
|
|
1717
1741
|
let bestScore = Number.POSITIVE_INFINITY;
|
|
1718
1742
|
|
|
1719
|
-
for (const v of
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1743
|
+
for (const { v, h, s } of candidates) {
|
|
1744
|
+
const coords = this.#computeCoords(anchorRect, popupRect, v, h, offset, s);
|
|
1745
|
+
const placementSide = this.#getPlacementSide(v, h, s);
|
|
1746
|
+
|
|
1747
|
+
if (s) {
|
|
1748
|
+
// Shorthand: clamp cross-axis to viewport, check primary axis fit
|
|
1749
|
+
const clamped = this.#clamp(coords, popupRect);
|
|
1750
|
+
const primaryFits = (s === "left" || s === "right")
|
|
1751
|
+
? (coords.left >= this.#viewportPadding && coords.left + popupRect.width <= window.innerWidth - this.#viewportPadding)
|
|
1752
|
+
: (coords.top >= this.#viewportPadding && coords.top + popupRect.height <= window.innerHeight - this.#viewportPadding);
|
|
1753
|
+
if (primaryFits) {
|
|
1754
|
+
this.style.left = `${clamped.left}px`;
|
|
1755
|
+
this.style.top = `${clamped.top}px`;
|
|
1756
|
+
this.#updatePopoverBeak(anchorRect, popupRect, clamped.left, clamped.top, placementSide);
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
const score = this.#overflowScore(coords, popupRect);
|
|
1760
|
+
if (score < bestScore) {
|
|
1761
|
+
bestScore = score;
|
|
1762
|
+
best = clamped;
|
|
1763
|
+
bestSide = placementSide;
|
|
1764
|
+
}
|
|
1765
|
+
} else {
|
|
1730
1766
|
if (this.#fits(coords, popupRect)) {
|
|
1731
1767
|
this.style.left = `${coords.left}px`;
|
|
1732
1768
|
this.style.top = `${coords.top}px`;
|
|
1733
|
-
this.#updatePopoverBeak(
|
|
1734
|
-
anchorRect,
|
|
1735
|
-
popupRect,
|
|
1736
|
-
coords.left,
|
|
1737
|
-
coords.top,
|
|
1738
|
-
placementSide
|
|
1739
|
-
);
|
|
1769
|
+
this.#updatePopoverBeak(anchorRect, popupRect, coords.left, coords.top, placementSide);
|
|
1740
1770
|
return;
|
|
1741
1771
|
}
|
|
1742
1772
|
const score = this.#overflowScore(coords, popupRect);
|
|
@@ -1751,13 +1781,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1751
1781
|
const clamped = this.#clamp(best || { left: 0, top: 0 }, popupRect);
|
|
1752
1782
|
this.style.left = `${clamped.left}px`;
|
|
1753
1783
|
this.style.top = `${clamped.top}px`;
|
|
1754
|
-
this.#updatePopoverBeak(
|
|
1755
|
-
anchorRect,
|
|
1756
|
-
popupRect,
|
|
1757
|
-
clamped.left,
|
|
1758
|
-
clamped.top,
|
|
1759
|
-
bestSide
|
|
1760
|
-
);
|
|
1784
|
+
this.#updatePopoverBeak(anchorRect, popupRect, clamped.left, clamped.top, bestSide);
|
|
1761
1785
|
}
|
|
1762
1786
|
|
|
1763
1787
|
#queueReposition() {
|
|
@@ -3352,6 +3376,8 @@ class FigInputColor extends HTMLElement {
|
|
|
3352
3376
|
const showAlpha = this.getAttribute("alpha") === "true";
|
|
3353
3377
|
const experimental = this.getAttribute("experimental");
|
|
3354
3378
|
const expAttr = experimental ? `experimental="${experimental}"` : "";
|
|
3379
|
+
const dialogPos = this.getAttribute("dialog-position") || "left";
|
|
3380
|
+
const dialogPosAttr = `dialog-position="${dialogPos}"`;
|
|
3355
3381
|
|
|
3356
3382
|
let html = ``;
|
|
3357
3383
|
if (this.getAttribute("text")) {
|
|
@@ -3376,7 +3402,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3376
3402
|
let swatchElement = "";
|
|
3377
3403
|
if (!hidePicker) {
|
|
3378
3404
|
swatchElement = useFigmaPicker
|
|
3379
|
-
? `<fig-fill-picker mode="solid"
|
|
3405
|
+
? `<fig-fill-picker mode="solid" ${dialogPosAttr} ${expAttr} ${
|
|
3380
3406
|
showAlpha ? "" : 'alpha="false"'
|
|
3381
3407
|
} value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
|
|
3382
3408
|
this.alpha
|
|
@@ -3394,7 +3420,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3394
3420
|
html = ``;
|
|
3395
3421
|
} else {
|
|
3396
3422
|
html = useFigmaPicker
|
|
3397
|
-
? `<fig-fill-picker mode="solid"
|
|
3423
|
+
? `<fig-fill-picker mode="solid" ${dialogPosAttr} ${expAttr} ${
|
|
3398
3424
|
showAlpha ? "" : 'alpha="false"'
|
|
3399
3425
|
} value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
|
|
3400
3426
|
this.alpha
|
|
@@ -3586,7 +3612,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3586
3612
|
}
|
|
3587
3613
|
|
|
3588
3614
|
static get observedAttributes() {
|
|
3589
|
-
return ["value", "style", "mode", "picker", "experimental"];
|
|
3615
|
+
return ["value", "style", "mode", "picker", "experimental", "dialog-position"];
|
|
3590
3616
|
}
|
|
3591
3617
|
|
|
3592
3618
|
get mode() {
|
|
@@ -6957,7 +6983,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
6957
6983
|
<fig-input-number class="fig-fill-picker-stop-position" min="0" max="100" value="${
|
|
6958
6984
|
stop.position
|
|
6959
6985
|
}" units="%"></fig-input-number>
|
|
6960
|
-
<fig-input-color class="fig-fill-picker-stop-color" text="true" alpha="true" picker="figma" value="${
|
|
6986
|
+
<fig-input-color class="fig-fill-picker-stop-color" text="true" alpha="true" picker="figma" dialog-position="right" value="${
|
|
6961
6987
|
stop.color
|
|
6962
6988
|
}"></fig-input-color>
|
|
6963
6989
|
<fig-button icon variant="ghost" class="fig-fill-picker-stop-remove" ${
|
package/index.html
CHANGED
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
|
|
25
25
|
nav {
|
|
26
26
|
position: fixed;
|
|
27
|
-
width:
|
|
27
|
+
width: 20vw;
|
|
28
|
+
min-width: 128px;
|
|
29
|
+
max-width: 320px;
|
|
28
30
|
height: 100vh;
|
|
29
31
|
overflow-y: auto;
|
|
30
32
|
background: var(--figma-color-bg-secondary);
|
|
@@ -105,7 +107,7 @@
|
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
main {
|
|
108
|
-
margin-left:
|
|
110
|
+
margin-left: clamp(128px, 20vw, 320px);
|
|
109
111
|
padding: 24px 32px;
|
|
110
112
|
max-width: 800px;
|
|
111
113
|
min-height: 100vh;
|
|
@@ -1806,6 +1808,13 @@
|
|
|
1806
1808
|
alpha="true"
|
|
1807
1809
|
picker="figma"></fig-input-color>
|
|
1808
1810
|
|
|
1811
|
+
<h4>Figma Picker (Experimental Modern)</h4>
|
|
1812
|
+
<fig-input-color value="#E84393"
|
|
1813
|
+
text="true"
|
|
1814
|
+
alpha="true"
|
|
1815
|
+
picker="figma"
|
|
1816
|
+
experimental="modern"></fig-input-color>
|
|
1817
|
+
|
|
1809
1818
|
<h4>No Picker (text input only)</h4>
|
|
1810
1819
|
<fig-input-color value="#9747FF"
|
|
1811
1820
|
text="true"
|
|
@@ -1995,6 +2004,10 @@
|
|
|
1995
2004
|
<h3>Webcam Fill</h3>
|
|
1996
2005
|
<fig-input-fill value='{"type":"webcam","webcam":{"opacity":1}}'></fig-input-fill>
|
|
1997
2006
|
|
|
2007
|
+
<h3>Experimental Modern</h3>
|
|
2008
|
+
<fig-input-fill experimental="modern"
|
|
2009
|
+
value='{"type":"solid","color":"#E84393","alpha":1}'></fig-input-fill>
|
|
2010
|
+
|
|
1998
2011
|
<h3>Disabled</h3>
|
|
1999
2012
|
<fig-input-fill disabled
|
|
2000
2013
|
value='{"type":"solid","color":"#AA96DA","alpha":1}'></fig-input-fill>
|
package/package.json
CHANGED