@rogieking/figui3 2.20.0 → 2.21.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.
- package/components.css +9 -5
- package/fig.js +228 -116
- package/index.html +25 -2
- package/package.json +1 -1
package/components.css
CHANGED
|
@@ -1412,7 +1412,8 @@ input[type="checkbox"]:not(.switch) {
|
|
|
1412
1412
|
/* Light theme checkbox hover (black checkmark preview) */
|
|
1413
1413
|
/* @media is a no-JS fallback — ignored when setTheme() sets classes */
|
|
1414
1414
|
@media (prefers-color-scheme: light) {
|
|
1415
|
-
:root:not(.figma-dark):not(.figma-light)
|
|
1415
|
+
:root:not(.figma-dark):not(.figma-light)
|
|
1416
|
+
input[type="checkbox"]:not(.switch):not(:disabled):hover {
|
|
1416
1417
|
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.50012 7.5L7.50012 10.5L11.5001 4.5' stroke='black' opacity='0.25' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.125' /%3E%3C/svg%3E%0A");
|
|
1417
1418
|
}
|
|
1418
1419
|
}
|
|
@@ -2030,12 +2031,15 @@ dialog[is="fig-popup"] {
|
|
|
2030
2031
|
position: fixed;
|
|
2031
2032
|
margin: 0;
|
|
2032
2033
|
min-width: 0;
|
|
2033
|
-
|
|
2034
|
-
max-width: calc(100vw - var(--spacer-4));
|
|
2035
|
-
max-height: calc(100vh - var(--spacer-4));
|
|
2036
|
-
padding: var(--spacer-2);
|
|
2034
|
+
padding: 0;
|
|
2037
2035
|
overflow: auto;
|
|
2038
2036
|
|
|
2037
|
+
&[autoresize]:not([autoresize="false"]) {
|
|
2038
|
+
width: max-content;
|
|
2039
|
+
max-width: calc(100vw - var(--spacer-4));
|
|
2040
|
+
max-height: calc(100vh - var(--spacer-4));
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2039
2043
|
&[open] {
|
|
2040
2044
|
display: block;
|
|
2041
2045
|
}
|
package/fig.js
CHANGED
|
@@ -863,13 +863,12 @@ class FigDialog extends HTMLDialogElement {
|
|
|
863
863
|
#setupDragListeners() {
|
|
864
864
|
if (this.drag) {
|
|
865
865
|
this.addEventListener("pointerdown", this.#boundPointerDown);
|
|
866
|
-
// Set move cursor on handle element (or fig-header by default)
|
|
867
866
|
const handleSelector = this.getAttribute("handle");
|
|
868
867
|
const handleEl = handleSelector
|
|
869
868
|
? this.querySelector(handleSelector)
|
|
870
869
|
: this.querySelector("fig-header, header");
|
|
871
870
|
if (handleEl) {
|
|
872
|
-
handleEl.style.cursor = "
|
|
871
|
+
handleEl.style.cursor = "grab";
|
|
873
872
|
}
|
|
874
873
|
}
|
|
875
874
|
}
|
|
@@ -977,16 +976,12 @@ class FigDialog extends HTMLDialogElement {
|
|
|
977
976
|
const dy = Math.abs(e.clientY - this.#dragStartPos.y);
|
|
978
977
|
|
|
979
978
|
if (dx > this.#dragThreshold || dy > this.#dragThreshold) {
|
|
980
|
-
// Start actual drag
|
|
981
979
|
this.#isDragging = true;
|
|
982
980
|
this.#dragPending = false;
|
|
983
981
|
this.setPointerCapture(e.pointerId);
|
|
982
|
+
this.style.cursor = "grabbing";
|
|
984
983
|
|
|
985
|
-
// Get current position from computed style
|
|
986
984
|
const rect = this.getBoundingClientRect();
|
|
987
|
-
|
|
988
|
-
// Convert to pixel-based top/left positioning for dragging
|
|
989
|
-
// (clears margin: auto centering)
|
|
990
985
|
this.style.top = `${rect.top}px`;
|
|
991
986
|
this.style.left = `${rect.left}px`;
|
|
992
987
|
this.style.bottom = "auto";
|
|
@@ -997,21 +992,15 @@ class FigDialog extends HTMLDialogElement {
|
|
|
997
992
|
|
|
998
993
|
if (!this.#isDragging) return;
|
|
999
994
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
const newTop = e.clientY - this.#dragOffset.y;
|
|
1003
|
-
|
|
1004
|
-
// Apply position directly with pixels
|
|
1005
|
-
this.style.left = `${newLeft}px`;
|
|
1006
|
-
this.style.top = `${newTop}px`;
|
|
1007
|
-
|
|
995
|
+
this.style.left = `${e.clientX - this.#dragOffset.x}px`;
|
|
996
|
+
this.style.top = `${e.clientY - this.#dragOffset.y}px`;
|
|
1008
997
|
e.preventDefault();
|
|
1009
998
|
}
|
|
1010
999
|
|
|
1011
1000
|
#handlePointerUp(e) {
|
|
1012
|
-
// Clean up pending or active drag
|
|
1013
1001
|
if (this.#isDragging) {
|
|
1014
1002
|
this.releasePointerCapture(e.pointerId);
|
|
1003
|
+
this.style.cursor = "";
|
|
1015
1004
|
}
|
|
1016
1005
|
|
|
1017
1006
|
this.#isDragging = false;
|
|
@@ -1035,7 +1024,6 @@ class FigDialog extends HTMLDialogElement {
|
|
|
1035
1024
|
this.#setupDragListeners();
|
|
1036
1025
|
} else {
|
|
1037
1026
|
this.#removeDragListeners();
|
|
1038
|
-
// Remove move cursor from header
|
|
1039
1027
|
const header = this.querySelector("fig-header, header");
|
|
1040
1028
|
if (header) {
|
|
1041
1029
|
header.style.cursor = "";
|
|
@@ -1070,16 +1058,29 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1070
1058
|
#boundScroll;
|
|
1071
1059
|
#boundOutsidePointerDown;
|
|
1072
1060
|
#rafId = null;
|
|
1061
|
+
#anchorRef = null;
|
|
1062
|
+
|
|
1063
|
+
#isDragging = false;
|
|
1064
|
+
#dragPending = false;
|
|
1065
|
+
#dragStartPos = { x: 0, y: 0 };
|
|
1066
|
+
#dragOffset = { x: 0, y: 0 };
|
|
1067
|
+
#dragThreshold = 3;
|
|
1068
|
+
#boundPointerDown;
|
|
1069
|
+
#boundPointerMove;
|
|
1070
|
+
#boundPointerUp;
|
|
1073
1071
|
|
|
1074
1072
|
constructor() {
|
|
1075
1073
|
super();
|
|
1076
1074
|
this.#boundReposition = this.#queueReposition.bind(this);
|
|
1077
1075
|
this.#boundScroll = this.#queueReposition.bind(this);
|
|
1078
1076
|
this.#boundOutsidePointerDown = this.#handleOutsidePointerDown.bind(this);
|
|
1077
|
+
this.#boundPointerDown = this.#handlePointerDown.bind(this);
|
|
1078
|
+
this.#boundPointerMove = this.#handlePointerMove.bind(this);
|
|
1079
|
+
this.#boundPointerUp = this.#handlePointerUp.bind(this);
|
|
1079
1080
|
}
|
|
1080
1081
|
|
|
1081
1082
|
static get observedAttributes() {
|
|
1082
|
-
return ["open", "anchor", "position", "offset", "variant", "theme"];
|
|
1083
|
+
return ["open", "anchor", "position", "offset", "variant", "theme", "drag", "handle", "autoresize"];
|
|
1083
1084
|
}
|
|
1084
1085
|
|
|
1085
1086
|
get open() {
|
|
@@ -1096,6 +1097,22 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1096
1097
|
this.setAttribute("open", "true");
|
|
1097
1098
|
}
|
|
1098
1099
|
|
|
1100
|
+
get anchor() {
|
|
1101
|
+
return this.#anchorRef ?? this.getAttribute("anchor");
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
set anchor(value) {
|
|
1105
|
+
if (value instanceof Element) {
|
|
1106
|
+
this.#anchorRef = value;
|
|
1107
|
+
} else if (typeof value === "string") {
|
|
1108
|
+
this.#anchorRef = null;
|
|
1109
|
+
this.setAttribute("anchor", value);
|
|
1110
|
+
} else {
|
|
1111
|
+
this.#anchorRef = null;
|
|
1112
|
+
}
|
|
1113
|
+
if (this.open) this.#queueReposition();
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1099
1116
|
connectedCallback() {
|
|
1100
1117
|
if (!this.hasAttribute("position")) {
|
|
1101
1118
|
this.setAttribute("position", "top center");
|
|
@@ -1103,11 +1120,13 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1103
1120
|
if (!this.hasAttribute("role")) {
|
|
1104
1121
|
this.setAttribute("role", "dialog");
|
|
1105
1122
|
}
|
|
1106
|
-
// Default dialog outside-close behavior.
|
|
1107
1123
|
if (!this.hasAttribute("closedby")) {
|
|
1108
1124
|
this.setAttribute("closedby", "any");
|
|
1109
1125
|
}
|
|
1110
1126
|
|
|
1127
|
+
this.drag =
|
|
1128
|
+
this.hasAttribute("drag") && this.getAttribute("drag") !== "false";
|
|
1129
|
+
|
|
1111
1130
|
this.addEventListener("close", () => {
|
|
1112
1131
|
this.#teardownObservers();
|
|
1113
1132
|
if (this.hasAttribute("open")) {
|
|
@@ -1115,6 +1134,10 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1115
1134
|
}
|
|
1116
1135
|
});
|
|
1117
1136
|
|
|
1137
|
+
requestAnimationFrame(() => {
|
|
1138
|
+
this.#setupDragListeners();
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1118
1141
|
if (this.open) {
|
|
1119
1142
|
this.#showPopup();
|
|
1120
1143
|
} else {
|
|
@@ -1124,6 +1147,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1124
1147
|
|
|
1125
1148
|
disconnectedCallback() {
|
|
1126
1149
|
this.#teardownObservers();
|
|
1150
|
+
this.#removeDragListeners();
|
|
1127
1151
|
document.removeEventListener(
|
|
1128
1152
|
"pointerdown",
|
|
1129
1153
|
this.#boundOutsidePointerDown,
|
|
@@ -1147,6 +1171,18 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1147
1171
|
return;
|
|
1148
1172
|
}
|
|
1149
1173
|
|
|
1174
|
+
if (name === "drag") {
|
|
1175
|
+
this.drag = newValue !== null && newValue !== "false";
|
|
1176
|
+
if (this.drag) {
|
|
1177
|
+
this.#setupDragListeners();
|
|
1178
|
+
} else {
|
|
1179
|
+
this.#removeDragListeners();
|
|
1180
|
+
const header = this.querySelector("fig-header, header");
|
|
1181
|
+
if (header) header.style.cursor = "";
|
|
1182
|
+
}
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1150
1186
|
if (this.open) {
|
|
1151
1187
|
this.#queueReposition();
|
|
1152
1188
|
this.#setupObservers();
|
|
@@ -1176,9 +1212,15 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1176
1212
|
document.addEventListener("pointerdown", this.#boundOutsidePointerDown, true);
|
|
1177
1213
|
this.#queueReposition();
|
|
1178
1214
|
this.#isPopupActive = true;
|
|
1215
|
+
|
|
1216
|
+
const anchor = this.#resolveAnchor();
|
|
1217
|
+
if (anchor) anchor.classList.add("has-popup-open");
|
|
1179
1218
|
}
|
|
1180
1219
|
|
|
1181
1220
|
#hidePopup() {
|
|
1221
|
+
const anchor = this.#resolveAnchor();
|
|
1222
|
+
if (anchor) anchor.classList.remove("has-popup-open");
|
|
1223
|
+
|
|
1182
1224
|
this.#isPopupActive = false;
|
|
1183
1225
|
this.#teardownObservers();
|
|
1184
1226
|
document.removeEventListener(
|
|
@@ -1196,6 +1238,11 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1196
1238
|
}
|
|
1197
1239
|
}
|
|
1198
1240
|
|
|
1241
|
+
get autoresize() {
|
|
1242
|
+
const val = this.getAttribute("autoresize");
|
|
1243
|
+
return val === null || val !== "false";
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1199
1246
|
#setupObservers() {
|
|
1200
1247
|
this.#teardownObservers();
|
|
1201
1248
|
|
|
@@ -1205,17 +1252,19 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1205
1252
|
this.#anchorObserver.observe(anchor);
|
|
1206
1253
|
}
|
|
1207
1254
|
|
|
1208
|
-
if (
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1255
|
+
if (this.autoresize) {
|
|
1256
|
+
if ("ResizeObserver" in window) {
|
|
1257
|
+
this.#contentObserver = new ResizeObserver(this.#boundReposition);
|
|
1258
|
+
this.#contentObserver.observe(this);
|
|
1259
|
+
}
|
|
1212
1260
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1261
|
+
this.#mutationObserver = new MutationObserver(this.#boundReposition);
|
|
1262
|
+
this.#mutationObserver.observe(this, {
|
|
1263
|
+
childList: true,
|
|
1264
|
+
subtree: true,
|
|
1265
|
+
characterData: true,
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1219
1268
|
|
|
1220
1269
|
window.addEventListener("resize", this.#boundReposition);
|
|
1221
1270
|
window.addEventListener("scroll", this.#boundScroll, true);
|
|
@@ -1246,10 +1295,128 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1246
1295
|
if (!(target instanceof Node)) return;
|
|
1247
1296
|
if (this.contains(target)) return;
|
|
1248
1297
|
|
|
1298
|
+
const anchor = this.#resolveAnchor();
|
|
1299
|
+
if (anchor && anchor.contains(target)) return;
|
|
1300
|
+
|
|
1249
1301
|
this.open = false;
|
|
1250
1302
|
}
|
|
1251
1303
|
|
|
1304
|
+
// ---- Drag support ----
|
|
1305
|
+
|
|
1306
|
+
#setupDragListeners() {
|
|
1307
|
+
if (this.drag) {
|
|
1308
|
+
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
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
#removeDragListeners() {
|
|
1318
|
+
this.removeEventListener("pointerdown", this.#boundPointerDown);
|
|
1319
|
+
document.removeEventListener("pointermove", this.#boundPointerMove);
|
|
1320
|
+
document.removeEventListener("pointerup", this.#boundPointerUp);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
#isInteractiveElement(element) {
|
|
1324
|
+
const interactiveSelectors = [
|
|
1325
|
+
"input", "button", "select", "textarea", "a",
|
|
1326
|
+
"label", "details", "summary",
|
|
1327
|
+
'[contenteditable="true"]', "[tabindex]",
|
|
1328
|
+
];
|
|
1329
|
+
|
|
1330
|
+
const nonInteractiveFigElements = [
|
|
1331
|
+
"FIG-HEADER", "FIG-DIALOG", "FIG-POPUP", "FIG-FIELD",
|
|
1332
|
+
"FIG-TOOLTIP", "FIG-CONTENT", "FIG-TABS", "FIG-TAB",
|
|
1333
|
+
"FIG-POPOVER", "FIG-SHIMMER", "FIG-LAYER", "FIG-FILL-PICKER",
|
|
1334
|
+
];
|
|
1335
|
+
|
|
1336
|
+
const isInteractive = (el) =>
|
|
1337
|
+
interactiveSelectors.some((s) => el.matches?.(s)) ||
|
|
1338
|
+
(el.tagName?.startsWith("FIG-") &&
|
|
1339
|
+
!nonInteractiveFigElements.includes(el.tagName));
|
|
1340
|
+
|
|
1341
|
+
if (isInteractive(element)) return true;
|
|
1342
|
+
|
|
1343
|
+
let parent = element.parentElement;
|
|
1344
|
+
while (parent && parent !== this) {
|
|
1345
|
+
if (isInteractive(parent)) return true;
|
|
1346
|
+
parent = parent.parentElement;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
return false;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
#handlePointerDown(e) {
|
|
1353
|
+
if (!this.drag) return;
|
|
1354
|
+
if (this.#isInteractiveElement(e.target)) return;
|
|
1355
|
+
|
|
1356
|
+
const handleSelector = this.getAttribute("handle");
|
|
1357
|
+
if (handleSelector && handleSelector.trim()) {
|
|
1358
|
+
const handleEl = this.querySelector(handleSelector);
|
|
1359
|
+
if (!handleEl || !handleEl.contains(e.target)) return;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
this.#dragPending = true;
|
|
1363
|
+
this.#dragStartPos.x = e.clientX;
|
|
1364
|
+
this.#dragStartPos.y = e.clientY;
|
|
1365
|
+
|
|
1366
|
+
const rect = this.getBoundingClientRect();
|
|
1367
|
+
this.#dragOffset.x = e.clientX - rect.left;
|
|
1368
|
+
this.#dragOffset.y = e.clientY - rect.top;
|
|
1369
|
+
|
|
1370
|
+
document.addEventListener("pointermove", this.#boundPointerMove);
|
|
1371
|
+
document.addEventListener("pointerup", this.#boundPointerUp);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
#handlePointerMove(e) {
|
|
1375
|
+
if (this.#dragPending && !this.#isDragging) {
|
|
1376
|
+
const dx = Math.abs(e.clientX - this.#dragStartPos.x);
|
|
1377
|
+
const dy = Math.abs(e.clientY - this.#dragStartPos.y);
|
|
1378
|
+
|
|
1379
|
+
if (dx > this.#dragThreshold || dy > this.#dragThreshold) {
|
|
1380
|
+
this.#isDragging = true;
|
|
1381
|
+
this.#dragPending = false;
|
|
1382
|
+
this.setPointerCapture(e.pointerId);
|
|
1383
|
+
this.style.cursor = "grabbing";
|
|
1384
|
+
|
|
1385
|
+
const rect = this.getBoundingClientRect();
|
|
1386
|
+
this.style.top = `${rect.top}px`;
|
|
1387
|
+
this.style.left = `${rect.left}px`;
|
|
1388
|
+
this.style.bottom = "auto";
|
|
1389
|
+
this.style.right = "auto";
|
|
1390
|
+
this.style.margin = "0";
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
if (!this.#isDragging) return;
|
|
1395
|
+
|
|
1396
|
+
this.style.left = `${e.clientX - this.#dragOffset.x}px`;
|
|
1397
|
+
this.style.top = `${e.clientY - this.#dragOffset.y}px`;
|
|
1398
|
+
e.preventDefault();
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
#handlePointerUp(e) {
|
|
1402
|
+
if (this.#isDragging) {
|
|
1403
|
+
this.releasePointerCapture(e.pointerId);
|
|
1404
|
+
this.style.cursor = "";
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
this.#isDragging = false;
|
|
1408
|
+
this.#dragPending = false;
|
|
1409
|
+
|
|
1410
|
+
document.removeEventListener("pointermove", this.#boundPointerMove);
|
|
1411
|
+
document.removeEventListener("pointerup", this.#boundPointerUp);
|
|
1412
|
+
e.preventDefault();
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// ---- Anchor resolution ----
|
|
1416
|
+
|
|
1252
1417
|
#resolveAnchor() {
|
|
1418
|
+
if (this.#anchorRef) return this.#anchorRef;
|
|
1419
|
+
|
|
1253
1420
|
const selector = this.getAttribute("anchor");
|
|
1254
1421
|
if (!selector) return null;
|
|
1255
1422
|
|
|
@@ -1417,12 +1584,24 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1417
1584
|
top = anchorRect.top + (anchorRect.height - popupRect.height) / 2;
|
|
1418
1585
|
}
|
|
1419
1586
|
|
|
1420
|
-
if (
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1587
|
+
if (vertical === "center") {
|
|
1588
|
+
// Side placement: popup beside the anchor
|
|
1589
|
+
if (horizontal === "left") {
|
|
1590
|
+
left = anchorRect.left - popupRect.width - offset.xPx;
|
|
1591
|
+
} else if (horizontal === "right") {
|
|
1592
|
+
left = anchorRect.right + offset.xPx;
|
|
1593
|
+
} else {
|
|
1594
|
+
left = anchorRect.left + (anchorRect.width - popupRect.width) / 2;
|
|
1595
|
+
}
|
|
1424
1596
|
} else {
|
|
1425
|
-
|
|
1597
|
+
// Edge alignment: popup above/below, aligned to anchor edge
|
|
1598
|
+
if (horizontal === "left") {
|
|
1599
|
+
left = anchorRect.left + offset.xPx;
|
|
1600
|
+
} else if (horizontal === "right") {
|
|
1601
|
+
left = anchorRect.right - popupRect.width - offset.xPx;
|
|
1602
|
+
} else {
|
|
1603
|
+
left = anchorRect.left + (anchorRect.width - popupRect.width) / 2;
|
|
1604
|
+
}
|
|
1426
1605
|
}
|
|
1427
1606
|
|
|
1428
1607
|
return { top, left };
|
|
@@ -3197,7 +3376,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3197
3376
|
let swatchElement = "";
|
|
3198
3377
|
if (!hidePicker) {
|
|
3199
3378
|
swatchElement = useFigmaPicker
|
|
3200
|
-
? `<fig-fill-picker mode="solid" ${expAttr} ${
|
|
3379
|
+
? `<fig-fill-picker mode="solid" dialog-position="left" ${expAttr} ${
|
|
3201
3380
|
showAlpha ? "" : 'alpha="false"'
|
|
3202
3381
|
} value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
|
|
3203
3382
|
this.alpha
|
|
@@ -3215,7 +3394,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3215
3394
|
html = ``;
|
|
3216
3395
|
} else {
|
|
3217
3396
|
html = useFigmaPicker
|
|
3218
|
-
? `<fig-fill-picker mode="solid" ${expAttr} ${
|
|
3397
|
+
? `<fig-fill-picker mode="solid" dialog-position="left" ${expAttr} ${
|
|
3219
3398
|
showAlpha ? "" : 'alpha="false"'
|
|
3220
3399
|
} value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
|
|
3221
3400
|
this.alpha
|
|
@@ -3743,7 +3922,7 @@ class FigInputFill extends HTMLElement {
|
|
|
3743
3922
|
const experimentalAttr = this.getAttribute("experimental");
|
|
3744
3923
|
this.innerHTML = `
|
|
3745
3924
|
<div class="input-combo">
|
|
3746
|
-
<fig-fill-picker value='${fillPickerValue}' ${
|
|
3925
|
+
<fig-fill-picker dialog-position="left" value='${fillPickerValue}' ${
|
|
3747
3926
|
disabled ? "disabled" : ""
|
|
3748
3927
|
} ${modeAttr ? `mode="${modeAttr}"` : ""} ${experimentalAttr ? `experimental="${experimentalAttr}"` : ""}></fig-fill-picker>
|
|
3749
3928
|
${controlsHtml}
|
|
@@ -6007,7 +6186,7 @@ customElements.define("fig-layer", FigLayer);
|
|
|
6007
6186
|
* @attr {string} value - JSON-encoded fill value
|
|
6008
6187
|
* @attr {boolean} disabled - Whether the picker is disabled
|
|
6009
6188
|
* @attr {boolean} alpha - Whether to show alpha/opacity controls (default: true)
|
|
6010
|
-
* @attr {string} dialog-position - Position of the
|
|
6189
|
+
* @attr {string} dialog-position - Position of the popup (default: "left")
|
|
6011
6190
|
*/
|
|
6012
6191
|
class FigFillPicker extends HTMLElement {
|
|
6013
6192
|
#trigger = null;
|
|
@@ -6213,21 +6392,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
6213
6392
|
this.#createDialog();
|
|
6214
6393
|
}
|
|
6215
6394
|
|
|
6216
|
-
// Position off-screen first to prevent scroll jump
|
|
6217
|
-
this.#dialog.style.position = "fixed";
|
|
6218
|
-
this.#dialog.style.top = "-9999px";
|
|
6219
|
-
this.#dialog.style.left = "-9999px";
|
|
6220
|
-
|
|
6221
|
-
this.#dialog.show();
|
|
6222
6395
|
this.#switchTab(this.#fillType);
|
|
6396
|
+
this.#dialog.open = true;
|
|
6223
6397
|
|
|
6224
|
-
// Position after dialog has rendered and has dimensions
|
|
6225
|
-
// Use nested RAF to ensure canvas is fully ready for drawing
|
|
6226
6398
|
requestAnimationFrame(() => {
|
|
6227
|
-
this.#positionDialog();
|
|
6228
|
-
this.#dialog.setAttribute("closedby", "any");
|
|
6229
|
-
|
|
6230
|
-
// Second RAF ensures the dialog is visible and canvas is ready
|
|
6231
6399
|
requestAnimationFrame(() => {
|
|
6232
6400
|
this.#drawColorArea();
|
|
6233
6401
|
this.#updateHandlePosition();
|
|
@@ -6235,72 +6403,17 @@ class FigFillPicker extends HTMLElement {
|
|
|
6235
6403
|
});
|
|
6236
6404
|
}
|
|
6237
6405
|
|
|
6238
|
-
#positionDialog() {
|
|
6239
|
-
const triggerRect = this.#trigger.getBoundingClientRect();
|
|
6240
|
-
const dialogRect = this.#dialog.getBoundingClientRect();
|
|
6241
|
-
const padding = 8; // Gap between trigger and dialog
|
|
6242
|
-
const viewportPadding = 16; // Min distance from viewport edges
|
|
6243
|
-
|
|
6244
|
-
// Calculate available space in each direction
|
|
6245
|
-
const spaceBelow =
|
|
6246
|
-
window.innerHeight - triggerRect.bottom - viewportPadding;
|
|
6247
|
-
const spaceAbove = triggerRect.top - viewportPadding;
|
|
6248
|
-
const spaceRight = window.innerWidth - triggerRect.left - viewportPadding;
|
|
6249
|
-
const spaceLeft = triggerRect.right - viewportPadding;
|
|
6250
|
-
|
|
6251
|
-
let top, left;
|
|
6252
|
-
|
|
6253
|
-
// Vertical positioning: prefer below, fallback to above
|
|
6254
|
-
if (spaceBelow >= dialogRect.height || spaceBelow >= spaceAbove) {
|
|
6255
|
-
// Position below trigger
|
|
6256
|
-
top = triggerRect.bottom + padding;
|
|
6257
|
-
} else {
|
|
6258
|
-
// Position above trigger
|
|
6259
|
-
top = triggerRect.top - dialogRect.height - padding;
|
|
6260
|
-
}
|
|
6261
|
-
|
|
6262
|
-
// Horizontal positioning: align left edge with trigger, adjust if needed
|
|
6263
|
-
left = triggerRect.left;
|
|
6264
|
-
|
|
6265
|
-
// Adjust if dialog would go off right edge
|
|
6266
|
-
if (left + dialogRect.width > window.innerWidth - viewportPadding) {
|
|
6267
|
-
left = window.innerWidth - dialogRect.width - viewportPadding;
|
|
6268
|
-
}
|
|
6269
|
-
|
|
6270
|
-
// Adjust if dialog would go off left edge
|
|
6271
|
-
if (left < viewportPadding) {
|
|
6272
|
-
left = viewportPadding;
|
|
6273
|
-
}
|
|
6274
|
-
|
|
6275
|
-
// Clamp vertical position to viewport
|
|
6276
|
-
if (top < viewportPadding) {
|
|
6277
|
-
top = viewportPadding;
|
|
6278
|
-
}
|
|
6279
|
-
if (top + dialogRect.height > window.innerHeight - viewportPadding) {
|
|
6280
|
-
top = window.innerHeight - dialogRect.height - viewportPadding;
|
|
6281
|
-
}
|
|
6282
|
-
|
|
6283
|
-
// Apply position (override fig-dialog's default positioning)
|
|
6284
|
-
this.#dialog.style.position = "fixed";
|
|
6285
|
-
this.#dialog.style.top = `${top}px`;
|
|
6286
|
-
this.#dialog.style.left = `${left}px`;
|
|
6287
|
-
this.#dialog.style.bottom = "auto";
|
|
6288
|
-
this.#dialog.style.right = "auto";
|
|
6289
|
-
this.#dialog.style.margin = "0";
|
|
6290
|
-
}
|
|
6291
|
-
|
|
6292
6406
|
#createDialog() {
|
|
6293
|
-
this.#dialog = document.createElement("dialog", { is: "fig-
|
|
6294
|
-
this.#dialog.setAttribute("is", "fig-
|
|
6407
|
+
this.#dialog = document.createElement("dialog", { is: "fig-popup" });
|
|
6408
|
+
this.#dialog.setAttribute("is", "fig-popup");
|
|
6295
6409
|
this.#dialog.setAttribute("drag", "true");
|
|
6296
6410
|
this.#dialog.setAttribute("handle", "fig-header");
|
|
6411
|
+
this.#dialog.setAttribute("autoresize", "false");
|
|
6297
6412
|
this.#dialog.classList.add("fig-fill-picker-dialog");
|
|
6298
6413
|
|
|
6299
|
-
|
|
6300
|
-
const dialogPosition = this.getAttribute("dialog-position");
|
|
6301
|
-
|
|
6302
|
-
this.#dialog.setAttribute("position", dialogPosition);
|
|
6303
|
-
}
|
|
6414
|
+
this.#dialog.anchor = this.#trigger;
|
|
6415
|
+
const dialogPosition = this.getAttribute("dialog-position") || "left";
|
|
6416
|
+
this.#dialog.setAttribute("position", dialogPosition);
|
|
6304
6417
|
|
|
6305
6418
|
// Check for allowed modes (supports comma-separated values like "solid,gradient")
|
|
6306
6419
|
const mode = this.getAttribute("mode");
|
|
@@ -6333,7 +6446,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
6333
6446
|
|
|
6334
6447
|
let headerContent;
|
|
6335
6448
|
if (allowedModes.length === 1) {
|
|
6336
|
-
headerContent = `<
|
|
6449
|
+
headerContent = `<h3 class="fig-fill-picker-type-label">${modeLabels[allowedModes[0]]}</h3>`;
|
|
6337
6450
|
} else {
|
|
6338
6451
|
const options = allowedModes
|
|
6339
6452
|
.map((m) => `<option value="${m}">${modeLabels[m]}</option>`)
|
|
@@ -6346,7 +6459,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
6346
6459
|
this.#dialog.innerHTML = `
|
|
6347
6460
|
<fig-header>
|
|
6348
6461
|
${headerContent}
|
|
6349
|
-
<fig-button icon variant="ghost" close
|
|
6462
|
+
<fig-button icon variant="ghost" class="fig-fill-picker-close">
|
|
6350
6463
|
<span class="fig-mask-icon" style="--icon: var(--icon-close)"></span>
|
|
6351
6464
|
</fig-button>
|
|
6352
6465
|
</fig-header>
|
|
@@ -6369,11 +6482,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
6369
6482
|
});
|
|
6370
6483
|
}
|
|
6371
6484
|
|
|
6372
|
-
// Close button
|
|
6373
6485
|
this.#dialog
|
|
6374
|
-
.querySelector("fig-
|
|
6486
|
+
.querySelector(".fig-fill-picker-close")
|
|
6375
6487
|
.addEventListener("click", () => {
|
|
6376
|
-
this.#dialog.
|
|
6488
|
+
this.#dialog.open = false;
|
|
6377
6489
|
});
|
|
6378
6490
|
|
|
6379
6491
|
// Emit change on close
|
package/index.html
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
nav {
|
|
26
26
|
position: fixed;
|
|
27
|
-
width:
|
|
27
|
+
width: 240px;
|
|
28
28
|
height: 100vh;
|
|
29
29
|
overflow-y: auto;
|
|
30
30
|
background: var(--figma-color-bg-secondary);
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
main {
|
|
108
|
-
margin-left:
|
|
108
|
+
margin-left: 240px;
|
|
109
109
|
padding: 24px 32px;
|
|
110
110
|
max-width: 800px;
|
|
111
111
|
min-height: 100vh;
|
|
@@ -3109,6 +3109,28 @@
|
|
|
3109
3109
|
<dialog is="fig-popup" anchor="#popup-open-right-single" position="right" offset="12 8">
|
|
3110
3110
|
...
|
|
3111
3111
|
</dialog></code></pre>
|
|
3112
|
+
|
|
3113
|
+
<h4>Left (top edges aligned)</h4>
|
|
3114
|
+
<hstack>
|
|
3115
|
+
<fig-button id="popup-open-left-single">Open Left (single)</fig-button>
|
|
3116
|
+
<fig-button id="popup-close-left-single"
|
|
3117
|
+
variant="secondary">Close</fig-button>
|
|
3118
|
+
</hstack>
|
|
3119
|
+
<dialog id="popup-left-single"
|
|
3120
|
+
is="fig-popup"
|
|
3121
|
+
anchor="#popup-open-left-single"
|
|
3122
|
+
position="left"
|
|
3123
|
+
offset="12 8">
|
|
3124
|
+
<vstack style="min-width: 11rem;">
|
|
3125
|
+
<strong style="padding: 0 var(--spacer-1);">Left Shorthand</strong>
|
|
3126
|
+
<fig-input-text placeholder="position='left'"></fig-input-text>
|
|
3127
|
+
</vstack>
|
|
3128
|
+
</dialog>
|
|
3129
|
+
<pre><code><fig-button id="popup-open-left-single">Open Left (single)</fig-button>
|
|
3130
|
+
<dialog is="fig-popup" anchor="#popup-open-left-single" position="left" offset="12 8">
|
|
3131
|
+
...
|
|
3132
|
+
</dialog></code></pre>
|
|
3133
|
+
|
|
3112
3134
|
</section>
|
|
3113
3135
|
<hr>
|
|
3114
3136
|
|
|
@@ -4452,6 +4474,7 @@ button.addEventListener('click', () => {
|
|
|
4452
4474
|
['popup-open-center-left', 'popup-close-center-left', 'popup-center-left'],
|
|
4453
4475
|
['popup-open-top-single', 'popup-close-top-single', 'popup-top-single'],
|
|
4454
4476
|
['popup-open-right-single', 'popup-close-right-single', 'popup-right-single'],
|
|
4477
|
+
['popup-open-left-single', 'popup-close-left-single', 'popup-left-single'],
|
|
4455
4478
|
];
|
|
4456
4479
|
|
|
4457
4480
|
popupExamples.forEach(([openId, closeId, popupId]) => {
|
package/package.json
CHANGED