@rogieking/figui3 2.19.1 → 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 +34 -66
- package/fig.js +230 -117
- package/index.html +213 -6
- package/package.json +1 -1
package/components.css
CHANGED
|
@@ -305,73 +305,37 @@
|
|
|
305
305
|
--icon-rotate: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.2325 6.47442C11.2088 5.49811 12.7917 5.49811 13.768 6.47442L15.2931 7.99955H14.0002C13.7241 7.99955 13.5002 8.2234 13.5002 8.49955C13.5002 8.77569 13.7241 8.99955 14.0002 8.99955H16.5002C16.7764 8.99955 17.0002 8.77569 17.0002 8.49955V5.99955C17.0002 5.7234 16.7764 5.49955 16.5002 5.49955C16.2241 5.49955 16.0002 5.7234 16.0002 5.99955V7.29244L14.4751 5.76731C13.1083 4.40048 10.8922 4.40048 9.52537 5.76731L7.14669 8.14599C6.95143 8.34126 6.95143 8.65784 7.14669 8.8531C7.34195 9.04836 7.65854 9.04836 7.8538 8.8531L10.2325 6.47442ZM13.0609 9.64599C12.4751 9.06021 11.5254 9.06021 10.9396 9.64599L7.64669 12.9389C7.06091 13.5247 7.0609 14.4744 7.64669 15.0602L10.9396 18.3531C11.5254 18.9389 12.4751 18.9389 13.0609 18.3531L16.3538 15.0602C16.9396 14.4744 16.9396 13.5247 16.3538 12.9389L13.0609 9.64599ZM11.6467 10.3531C11.842 10.1578 12.1585 10.1578 12.3538 10.3531L15.6467 13.646C15.842 13.8413 15.842 14.1578 15.6467 14.3531L12.3538 17.646C12.1585 17.8413 11.842 17.8413 11.6467 17.646L8.3538 14.3531C8.15854 14.1578 8.15854 13.8413 8.3538 13.646L11.6467 10.3531Z' fill='currentColor'/%3E%3C/svg%3E");
|
|
306
306
|
--icon-swap: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.35355 6.35355C8.54882 6.15829 8.54882 5.84171 8.35355 5.64645C8.15829 5.45118 7.84171 5.45118 7.64645 5.64645L5.14645 8.14645C4.95118 8.34171 4.95118 8.65829 5.14645 8.85355L7.64645 11.3536C7.84171 11.5488 8.15829 11.5488 8.35355 11.3536C8.54882 11.1583 8.54882 10.8417 8.35355 10.6464L6.70711 9H18.5C18.7761 9 19 8.77614 19 8.5C19 8.22386 18.7761 8 18.5 8H6.70711L8.35355 6.35355ZM15.6464 13.3536C15.4512 13.1583 15.4512 12.8417 15.6464 12.6464C15.8417 12.4512 16.1583 12.4512 16.3536 12.6464L18.8536 15.1464C19.0488 15.3417 19.0488 15.6583 18.8536 15.8536L16.3536 18.3536C16.1583 18.5488 15.8417 18.5488 15.6464 18.3536C15.4512 18.1583 15.4512 17.8417 15.6464 17.6464L17.2929 16H5.5C5.22386 16 5 15.7761 5 15.5C5 15.2239 5.22386 15 5.5 15H17.2929L15.6464 13.3536Z' fill='currentColor'/%3E%3C/svg%3E");
|
|
307
307
|
|
|
308
|
-
/* Elevations
|
|
309
|
-
--figma-elevation-500-modal-window:
|
|
310
|
-
0 0 0.5px 0 rgba(0, 0, 0, 0.08), 0 10px 24px 0 rgba(0, 0, 0, 0.18),
|
|
311
|
-
0 2px 5px 0 rgba(0, 0, 0, 0.15);
|
|
312
|
-
|
|
308
|
+
/* Elevations — light-dark() handles theme switching inline */
|
|
313
309
|
--figma-elevation-100:
|
|
314
|
-
0px 0px 0.5px 0px rgba(0, 0, 0, 0.3),
|
|
310
|
+
0px 0px 0.5px 0px light-dark(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.5)),
|
|
311
|
+
inset 0px 0.75px 0px 0px light-dark(transparent, rgba(255, 255, 255, 0.1)),
|
|
312
|
+
0px 1px 3px 0px light-dark(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.4));
|
|
313
|
+
|
|
315
314
|
--figma-elevation-200:
|
|
316
|
-
|
|
317
|
-
0
|
|
315
|
+
0px 0px 0.5px 0px light-dark(rgba(0, 0, 0, 0.18), transparent),
|
|
316
|
+
0px 3px 8px 0px light-dark(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.35)),
|
|
317
|
+
0px 1px 3px 0px light-dark(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.5)),
|
|
318
|
+
inset 0px 0.5px 0px 0px light-dark(transparent, rgba(255, 255, 255, 0.08)),
|
|
319
|
+
inset 0px 0px 0.5px 0px light-dark(transparent, rgba(255, 255, 255, 0.3));
|
|
320
|
+
|
|
318
321
|
--figma-elevation-400-menu-panel:
|
|
319
|
-
0px 0px 0.5px 0px rgba(0, 0, 0, 0.12),
|
|
320
|
-
0px 10px 16px 0px rgba(0, 0, 0, 0.12),
|
|
322
|
+
0px 0px 0.5px 0px light-dark(rgba(0, 0, 0, 0.12), transparent),
|
|
323
|
+
0px 10px 16px 0px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.35)),
|
|
324
|
+
0px 2px 5px 0px light-dark(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.35)),
|
|
325
|
+
inset 0px 0.5px 0px 0px light-dark(transparent, rgba(255, 255, 255, 0.08)),
|
|
326
|
+
inset 0px 0.75px 0px 0px light-dark(transparent, rgba(255, 255, 255, 0.075));
|
|
327
|
+
|
|
321
328
|
--figma-elevation-500-modal-window:
|
|
322
|
-
0px 0px 0.5px 0px rgba(0, 0, 0, 0.08),
|
|
323
|
-
0px 10px 24px 0px rgba(0, 0, 0, 0.18),
|
|
324
|
-
|
|
325
|
-
|
|
329
|
+
0px 0px 0.5px 0px light-dark(rgba(0, 0, 0, 0.08), transparent),
|
|
330
|
+
0px 10px 24px 0px light-dark(rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0.45)),
|
|
331
|
+
0px 2px 5px 0px light-dark(rgba(0, 0, 0, 0.15), transparent),
|
|
332
|
+
0px 3px 5px 0px light-dark(transparent, rgba(0, 0, 0, 0.35)),
|
|
333
|
+
inset 0px 0.75px 0px 0px light-dark(transparent, rgba(255, 255, 255, 0.1));
|
|
326
334
|
|
|
327
|
-
/* Dark theme overrides for non-color values (shadows, elevations) */
|
|
328
|
-
/* light-dark() only works for colors, so these use class-based switching */
|
|
329
|
-
/* The @media block is a no-JS fallback — ignored when setTheme() sets classes */
|
|
330
|
-
@media (prefers-color-scheme: dark) {
|
|
331
|
-
:root:not(.figma-dark):not(.figma-light) {
|
|
332
|
-
--handle-shadow:
|
|
333
|
-
0px 0 0 0.75px rgba(0, 0, 0, 0.1),
|
|
334
|
-
0px 0px 0.5px 0px rgba(255, 255, 255, 0.1);
|
|
335
|
-
--figma-elevation-100:
|
|
336
|
-
0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
|
|
337
|
-
0px 0.75px 0px 0px rgba(255, 255, 255, 0.1) inset,
|
|
338
|
-
0px 1px 3px 0px rgba(0, 0, 0, 0.4);
|
|
339
|
-
--figma-elevation-200:
|
|
340
|
-
0px 3px 8px rgba(0, 0, 0, 0.35), 0px 1px 3px rgba(0, 0, 0, 0.5),
|
|
341
|
-
inset 0px 0.5px 0px rgba(255, 255, 255, 0.08),
|
|
342
|
-
inset 0px 0px 0.5px rgba(255, 255, 255, 0.3);
|
|
343
|
-
--figma-elevation-400-menu-panel:
|
|
344
|
-
0px 0.5px 0px 0px rgba(255, 255, 255, 0.08) inset,
|
|
345
|
-
0px 10px 16px 0px rgba(0, 0, 0, 0.35),
|
|
346
|
-
inset 0px 0.75px 0px rgba(255, 255, 255, 0.075),
|
|
347
|
-
0px 2px 5px 0px rgba(0, 0, 0, 0.35);
|
|
348
|
-
--figma-elevation-500-modal-window:
|
|
349
|
-
0px 10px 24px rgba(0, 0, 0, 0.45), 0px 3px 5px rgba(0, 0, 0, 0.35),
|
|
350
|
-
inset 0px 0.75px 0px rgba(255, 255, 255, 0.1);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/* Class-based dark theme override (primary mechanism via setTheme()) */
|
|
355
|
-
:root.figma-dark {
|
|
356
335
|
--handle-shadow:
|
|
357
|
-
0px
|
|
358
|
-
0px 0px 0.5px 0px rgba(255, 255, 255, 0.1)
|
|
359
|
-
|
|
360
|
-
0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
|
|
361
|
-
0px 0.75px 0px 0px rgba(255, 255, 255, 0.1) inset,
|
|
362
|
-
0px 1px 3px 0px rgba(0, 0, 0, 0.4);
|
|
363
|
-
--figma-elevation-200:
|
|
364
|
-
0px 3px 8px rgba(0, 0, 0, 0.35), 0px 1px 3px rgba(0, 0, 0, 0.5),
|
|
365
|
-
inset 0px 0.5px 0px rgba(255, 255, 255, 0.08),
|
|
366
|
-
inset 0px 0px 0.5px rgba(255, 255, 255, 0.3);
|
|
367
|
-
--figma-elevation-400-menu-panel:
|
|
368
|
-
0px 0.5px 0px 0px rgba(255, 255, 255, 0.08) inset,
|
|
369
|
-
0px 10px 16px 0px rgba(0, 0, 0, 0.35),
|
|
370
|
-
inset 0px 0.75px 0px rgba(255, 255, 255, 0.075),
|
|
371
|
-
0px 2px 5px 0px rgba(0, 0, 0, 0.35);
|
|
372
|
-
--figma-elevation-500-modal-window:
|
|
373
|
-
0px 10px 24px rgba(0, 0, 0, 0.45), 0px 3px 5px rgba(0, 0, 0, 0.35),
|
|
374
|
-
inset 0px 0.75px 0px rgba(255, 255, 255, 0.1);
|
|
336
|
+
0px 0px 0px 0.5px rgba(0, 0, 0, 0.1),
|
|
337
|
+
0px 0px 0.5px 0px light-dark(rgba(0, 0, 0, 0.3), rgba(255, 255, 255, 0.1)),
|
|
338
|
+
0px 1px 3px 0px light-dark(rgba(0, 0, 0, 0.15), transparent);
|
|
375
339
|
}
|
|
376
340
|
|
|
377
341
|
button,
|
|
@@ -1448,7 +1412,8 @@ input[type="checkbox"]:not(.switch) {
|
|
|
1448
1412
|
/* Light theme checkbox hover (black checkmark preview) */
|
|
1449
1413
|
/* @media is a no-JS fallback — ignored when setTheme() sets classes */
|
|
1450
1414
|
@media (prefers-color-scheme: light) {
|
|
1451
|
-
:root:not(.figma-dark):not(.figma-light)
|
|
1415
|
+
:root:not(.figma-dark):not(.figma-light)
|
|
1416
|
+
input[type="checkbox"]:not(.switch):not(:disabled):hover {
|
|
1452
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");
|
|
1453
1418
|
}
|
|
1454
1419
|
}
|
|
@@ -2066,12 +2031,15 @@ dialog[is="fig-popup"] {
|
|
|
2066
2031
|
position: fixed;
|
|
2067
2032
|
margin: 0;
|
|
2068
2033
|
min-width: 0;
|
|
2069
|
-
|
|
2070
|
-
max-width: calc(100vw - var(--spacer-4));
|
|
2071
|
-
max-height: calc(100vh - var(--spacer-4));
|
|
2072
|
-
padding: var(--spacer-2);
|
|
2034
|
+
padding: 0;
|
|
2073
2035
|
overflow: auto;
|
|
2074
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
|
+
|
|
2075
2043
|
&[open] {
|
|
2076
2044
|
display: block;
|
|
2077
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);
|
|
@@ -1240,15 +1289,134 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1240
1289
|
|
|
1241
1290
|
#handleOutsidePointerDown(event) {
|
|
1242
1291
|
if (!this.open || !super.open) return;
|
|
1292
|
+
const closedby = this.getAttribute("closedby");
|
|
1293
|
+
if (closedby === "none" || closedby === "closerequest") return;
|
|
1243
1294
|
const target = event.target;
|
|
1244
1295
|
if (!(target instanceof Node)) return;
|
|
1245
1296
|
if (this.contains(target)) return;
|
|
1246
1297
|
|
|
1247
|
-
|
|
1298
|
+
const anchor = this.#resolveAnchor();
|
|
1299
|
+
if (anchor && anchor.contains(target)) return;
|
|
1300
|
+
|
|
1248
1301
|
this.open = false;
|
|
1249
1302
|
}
|
|
1250
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
|
+
|
|
1251
1417
|
#resolveAnchor() {
|
|
1418
|
+
if (this.#anchorRef) return this.#anchorRef;
|
|
1419
|
+
|
|
1252
1420
|
const selector = this.getAttribute("anchor");
|
|
1253
1421
|
if (!selector) return null;
|
|
1254
1422
|
|
|
@@ -1416,12 +1584,24 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1416
1584
|
top = anchorRect.top + (anchorRect.height - popupRect.height) / 2;
|
|
1417
1585
|
}
|
|
1418
1586
|
|
|
1419
|
-
if (
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
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
|
+
}
|
|
1423
1596
|
} else {
|
|
1424
|
-
|
|
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
|
+
}
|
|
1425
1605
|
}
|
|
1426
1606
|
|
|
1427
1607
|
return { top, left };
|
|
@@ -3196,7 +3376,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3196
3376
|
let swatchElement = "";
|
|
3197
3377
|
if (!hidePicker) {
|
|
3198
3378
|
swatchElement = useFigmaPicker
|
|
3199
|
-
? `<fig-fill-picker mode="solid" ${expAttr} ${
|
|
3379
|
+
? `<fig-fill-picker mode="solid" dialog-position="left" ${expAttr} ${
|
|
3200
3380
|
showAlpha ? "" : 'alpha="false"'
|
|
3201
3381
|
} value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
|
|
3202
3382
|
this.alpha
|
|
@@ -3214,7 +3394,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3214
3394
|
html = ``;
|
|
3215
3395
|
} else {
|
|
3216
3396
|
html = useFigmaPicker
|
|
3217
|
-
? `<fig-fill-picker mode="solid" ${expAttr} ${
|
|
3397
|
+
? `<fig-fill-picker mode="solid" dialog-position="left" ${expAttr} ${
|
|
3218
3398
|
showAlpha ? "" : 'alpha="false"'
|
|
3219
3399
|
} value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
|
|
3220
3400
|
this.alpha
|
|
@@ -3742,7 +3922,7 @@ class FigInputFill extends HTMLElement {
|
|
|
3742
3922
|
const experimentalAttr = this.getAttribute("experimental");
|
|
3743
3923
|
this.innerHTML = `
|
|
3744
3924
|
<div class="input-combo">
|
|
3745
|
-
<fig-fill-picker value='${fillPickerValue}' ${
|
|
3925
|
+
<fig-fill-picker dialog-position="left" value='${fillPickerValue}' ${
|
|
3746
3926
|
disabled ? "disabled" : ""
|
|
3747
3927
|
} ${modeAttr ? `mode="${modeAttr}"` : ""} ${experimentalAttr ? `experimental="${experimentalAttr}"` : ""}></fig-fill-picker>
|
|
3748
3928
|
${controlsHtml}
|
|
@@ -6006,7 +6186,7 @@ customElements.define("fig-layer", FigLayer);
|
|
|
6006
6186
|
* @attr {string} value - JSON-encoded fill value
|
|
6007
6187
|
* @attr {boolean} disabled - Whether the picker is disabled
|
|
6008
6188
|
* @attr {boolean} alpha - Whether to show alpha/opacity controls (default: true)
|
|
6009
|
-
* @attr {string} dialog-position - Position of the
|
|
6189
|
+
* @attr {string} dialog-position - Position of the popup (default: "left")
|
|
6010
6190
|
*/
|
|
6011
6191
|
class FigFillPicker extends HTMLElement {
|
|
6012
6192
|
#trigger = null;
|
|
@@ -6212,21 +6392,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
6212
6392
|
this.#createDialog();
|
|
6213
6393
|
}
|
|
6214
6394
|
|
|
6215
|
-
// Position off-screen first to prevent scroll jump
|
|
6216
|
-
this.#dialog.style.position = "fixed";
|
|
6217
|
-
this.#dialog.style.top = "-9999px";
|
|
6218
|
-
this.#dialog.style.left = "-9999px";
|
|
6219
|
-
|
|
6220
|
-
this.#dialog.show();
|
|
6221
6395
|
this.#switchTab(this.#fillType);
|
|
6396
|
+
this.#dialog.open = true;
|
|
6222
6397
|
|
|
6223
|
-
// Position after dialog has rendered and has dimensions
|
|
6224
|
-
// Use nested RAF to ensure canvas is fully ready for drawing
|
|
6225
6398
|
requestAnimationFrame(() => {
|
|
6226
|
-
this.#positionDialog();
|
|
6227
|
-
this.#dialog.setAttribute("closedby", "any");
|
|
6228
|
-
|
|
6229
|
-
// Second RAF ensures the dialog is visible and canvas is ready
|
|
6230
6399
|
requestAnimationFrame(() => {
|
|
6231
6400
|
this.#drawColorArea();
|
|
6232
6401
|
this.#updateHandlePosition();
|
|
@@ -6234,72 +6403,17 @@ class FigFillPicker extends HTMLElement {
|
|
|
6234
6403
|
});
|
|
6235
6404
|
}
|
|
6236
6405
|
|
|
6237
|
-
#positionDialog() {
|
|
6238
|
-
const triggerRect = this.#trigger.getBoundingClientRect();
|
|
6239
|
-
const dialogRect = this.#dialog.getBoundingClientRect();
|
|
6240
|
-
const padding = 8; // Gap between trigger and dialog
|
|
6241
|
-
const viewportPadding = 16; // Min distance from viewport edges
|
|
6242
|
-
|
|
6243
|
-
// Calculate available space in each direction
|
|
6244
|
-
const spaceBelow =
|
|
6245
|
-
window.innerHeight - triggerRect.bottom - viewportPadding;
|
|
6246
|
-
const spaceAbove = triggerRect.top - viewportPadding;
|
|
6247
|
-
const spaceRight = window.innerWidth - triggerRect.left - viewportPadding;
|
|
6248
|
-
const spaceLeft = triggerRect.right - viewportPadding;
|
|
6249
|
-
|
|
6250
|
-
let top, left;
|
|
6251
|
-
|
|
6252
|
-
// Vertical positioning: prefer below, fallback to above
|
|
6253
|
-
if (spaceBelow >= dialogRect.height || spaceBelow >= spaceAbove) {
|
|
6254
|
-
// Position below trigger
|
|
6255
|
-
top = triggerRect.bottom + padding;
|
|
6256
|
-
} else {
|
|
6257
|
-
// Position above trigger
|
|
6258
|
-
top = triggerRect.top - dialogRect.height - padding;
|
|
6259
|
-
}
|
|
6260
|
-
|
|
6261
|
-
// Horizontal positioning: align left edge with trigger, adjust if needed
|
|
6262
|
-
left = triggerRect.left;
|
|
6263
|
-
|
|
6264
|
-
// Adjust if dialog would go off right edge
|
|
6265
|
-
if (left + dialogRect.width > window.innerWidth - viewportPadding) {
|
|
6266
|
-
left = window.innerWidth - dialogRect.width - viewportPadding;
|
|
6267
|
-
}
|
|
6268
|
-
|
|
6269
|
-
// Adjust if dialog would go off left edge
|
|
6270
|
-
if (left < viewportPadding) {
|
|
6271
|
-
left = viewportPadding;
|
|
6272
|
-
}
|
|
6273
|
-
|
|
6274
|
-
// Clamp vertical position to viewport
|
|
6275
|
-
if (top < viewportPadding) {
|
|
6276
|
-
top = viewportPadding;
|
|
6277
|
-
}
|
|
6278
|
-
if (top + dialogRect.height > window.innerHeight - viewportPadding) {
|
|
6279
|
-
top = window.innerHeight - dialogRect.height - viewportPadding;
|
|
6280
|
-
}
|
|
6281
|
-
|
|
6282
|
-
// Apply position (override fig-dialog's default positioning)
|
|
6283
|
-
this.#dialog.style.position = "fixed";
|
|
6284
|
-
this.#dialog.style.top = `${top}px`;
|
|
6285
|
-
this.#dialog.style.left = `${left}px`;
|
|
6286
|
-
this.#dialog.style.bottom = "auto";
|
|
6287
|
-
this.#dialog.style.right = "auto";
|
|
6288
|
-
this.#dialog.style.margin = "0";
|
|
6289
|
-
}
|
|
6290
|
-
|
|
6291
6406
|
#createDialog() {
|
|
6292
|
-
this.#dialog = document.createElement("dialog", { is: "fig-
|
|
6293
|
-
this.#dialog.setAttribute("is", "fig-
|
|
6407
|
+
this.#dialog = document.createElement("dialog", { is: "fig-popup" });
|
|
6408
|
+
this.#dialog.setAttribute("is", "fig-popup");
|
|
6294
6409
|
this.#dialog.setAttribute("drag", "true");
|
|
6295
6410
|
this.#dialog.setAttribute("handle", "fig-header");
|
|
6411
|
+
this.#dialog.setAttribute("autoresize", "false");
|
|
6296
6412
|
this.#dialog.classList.add("fig-fill-picker-dialog");
|
|
6297
6413
|
|
|
6298
|
-
|
|
6299
|
-
const dialogPosition = this.getAttribute("dialog-position");
|
|
6300
|
-
|
|
6301
|
-
this.#dialog.setAttribute("position", dialogPosition);
|
|
6302
|
-
}
|
|
6414
|
+
this.#dialog.anchor = this.#trigger;
|
|
6415
|
+
const dialogPosition = this.getAttribute("dialog-position") || "left";
|
|
6416
|
+
this.#dialog.setAttribute("position", dialogPosition);
|
|
6303
6417
|
|
|
6304
6418
|
// Check for allowed modes (supports comma-separated values like "solid,gradient")
|
|
6305
6419
|
const mode = this.getAttribute("mode");
|
|
@@ -6332,7 +6446,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
6332
6446
|
|
|
6333
6447
|
let headerContent;
|
|
6334
6448
|
if (allowedModes.length === 1) {
|
|
6335
|
-
headerContent = `<
|
|
6449
|
+
headerContent = `<h3 class="fig-fill-picker-type-label">${modeLabels[allowedModes[0]]}</h3>`;
|
|
6336
6450
|
} else {
|
|
6337
6451
|
const options = allowedModes
|
|
6338
6452
|
.map((m) => `<option value="${m}">${modeLabels[m]}</option>`)
|
|
@@ -6345,7 +6459,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
6345
6459
|
this.#dialog.innerHTML = `
|
|
6346
6460
|
<fig-header>
|
|
6347
6461
|
${headerContent}
|
|
6348
|
-
<fig-button icon variant="ghost" close
|
|
6462
|
+
<fig-button icon variant="ghost" class="fig-fill-picker-close">
|
|
6349
6463
|
<span class="fig-mask-icon" style="--icon: var(--icon-close)"></span>
|
|
6350
6464
|
</fig-button>
|
|
6351
6465
|
</fig-header>
|
|
@@ -6368,11 +6482,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
6368
6482
|
});
|
|
6369
6483
|
}
|
|
6370
6484
|
|
|
6371
|
-
// Close button
|
|
6372
6485
|
this.#dialog
|
|
6373
|
-
.querySelector("fig-
|
|
6486
|
+
.querySelector(".fig-fill-picker-close")
|
|
6374
6487
|
.addEventListener("click", () => {
|
|
6375
|
-
this.#dialog.
|
|
6488
|
+
this.#dialog.open = false;
|
|
6376
6489
|
});
|
|
6377
6490
|
|
|
6378
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;
|
|
@@ -158,6 +158,36 @@
|
|
|
158
158
|
border-radius: 4px;
|
|
159
159
|
color: var(--figma-color-text);
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
.toolbelt {
|
|
163
|
+
position: fixed;
|
|
164
|
+
bottom: 0.75rem;
|
|
165
|
+
left: 0;
|
|
166
|
+
right: 0;
|
|
167
|
+
width: fit-content;
|
|
168
|
+
margin: 0 auto;
|
|
169
|
+
display: flex;
|
|
170
|
+
align-items: center;
|
|
171
|
+
gap: var(--spacer-2);
|
|
172
|
+
padding: var(--spacer-2);
|
|
173
|
+
background-color: var(--figma-color-bg);
|
|
174
|
+
border-radius: var(--radius-large);
|
|
175
|
+
box-shadow: var(--figma-elevation-200);
|
|
176
|
+
z-index: 100;
|
|
177
|
+
|
|
178
|
+
&>section {
|
|
179
|
+
display: flex;
|
|
180
|
+
padding: 0;
|
|
181
|
+
align-items: center;
|
|
182
|
+
margin: 0;
|
|
183
|
+
gap: var(--spacer-2);
|
|
184
|
+
|
|
185
|
+
&:not(:first-child):not(:only-child) {
|
|
186
|
+
border-left: 1px solid var(--figma-color-border);
|
|
187
|
+
padding-left: var(--spacer-2);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
161
191
|
</style>
|
|
162
192
|
</head>
|
|
163
193
|
|
|
@@ -296,7 +326,8 @@
|
|
|
296
326
|
<label>Input (with icon)</label>
|
|
297
327
|
<fig-button variant="input">
|
|
298
328
|
Options
|
|
299
|
-
<span class="fig-mask-icon"
|
|
329
|
+
<span class="fig-mask-icon"
|
|
330
|
+
style="--icon: var(--icon-chevron); --size: 1rem"></span>
|
|
300
331
|
</fig-button>
|
|
301
332
|
</fig-field>
|
|
302
333
|
|
|
@@ -392,7 +423,8 @@
|
|
|
392
423
|
<label>Icon (input)</label>
|
|
393
424
|
<fig-button variant="input"
|
|
394
425
|
icon="true">
|
|
395
|
-
<span class="fig-mask-icon"
|
|
426
|
+
<span class="fig-mask-icon"
|
|
427
|
+
style="--icon: var(--icon-chevron); --size: 1rem"></span>
|
|
396
428
|
</fig-button>
|
|
397
429
|
</fig-field>
|
|
398
430
|
<fig-field direction="horizontal">
|
|
@@ -863,6 +895,115 @@
|
|
|
863
895
|
<fig-button close-dialog>Close</fig-button>
|
|
864
896
|
</footer>
|
|
865
897
|
</dialog>
|
|
898
|
+
|
|
899
|
+
<h3>Non-Modal Dialog</h3>
|
|
900
|
+
<p style="font-size: 12px; color: var(--figma-color-text-secondary); margin-bottom: 12px;">
|
|
901
|
+
Use <code>.show()</code> instead of <code>.showModal()</code> to open a non-modal dialog.
|
|
902
|
+
It won't create a backdrop or trap focus, and the rest of the page remains interactive.
|
|
903
|
+
</p>
|
|
904
|
+
<pre><code><dialog id="my-dialog" is="fig-dialog" drag="true">
|
|
905
|
+
...
|
|
906
|
+
</dialog>
|
|
907
|
+
|
|
908
|
+
<script>
|
|
909
|
+
// Non-modal — no backdrop, page stays interactive
|
|
910
|
+
document.getElementById('my-dialog').show();
|
|
911
|
+
|
|
912
|
+
// Modal — backdrop + focus trap
|
|
913
|
+
document.getElementById('my-dialog').showModal();
|
|
914
|
+
</script></code></pre>
|
|
915
|
+
<fig-button onclick="document.getElementById('non-modal-dialog').show()">Open Non-Modal Dialog</fig-button>
|
|
916
|
+
|
|
917
|
+
<dialog id="non-modal-dialog"
|
|
918
|
+
is="fig-dialog"
|
|
919
|
+
drag="true"
|
|
920
|
+
position="center center">
|
|
921
|
+
<fig-header>
|
|
922
|
+
<h3>Non-Modal Dialog</h3>
|
|
923
|
+
<fig-tooltip text="Close">
|
|
924
|
+
<fig-button variant="ghost"
|
|
925
|
+
icon="true"
|
|
926
|
+
close-dialog>
|
|
927
|
+
<span class="fig-mask-icon"
|
|
928
|
+
style="--icon: var(--icon-close)"></span>
|
|
929
|
+
</fig-button>
|
|
930
|
+
</fig-tooltip>
|
|
931
|
+
</fig-header>
|
|
932
|
+
<fig-content>
|
|
933
|
+
<p>This dialog is non-modal — you can still interact with the page behind it.</p>
|
|
934
|
+
<fig-field direction="horizontal">
|
|
935
|
+
<label>Opacity</label>
|
|
936
|
+
<fig-slider value="75"
|
|
937
|
+
min="0"
|
|
938
|
+
max="100"></fig-slider>
|
|
939
|
+
</fig-field>
|
|
940
|
+
</fig-content>
|
|
941
|
+
<footer>
|
|
942
|
+
<fig-button close-dialog>Done</fig-button>
|
|
943
|
+
</footer>
|
|
944
|
+
</dialog>
|
|
945
|
+
|
|
946
|
+
<h3>closedby Attribute</h3>
|
|
947
|
+
<p style="font-size: 12px; color: var(--figma-color-text-secondary); margin-bottom: 12px;">
|
|
948
|
+
The <code>closedby</code> attribute controls how the dialog can be dismissed.
|
|
949
|
+
<code>fig-dialog</code> defaults to <code>closedby="any"</code> (light dismiss).
|
|
950
|
+
</p>
|
|
951
|
+
<pre><code><!-- Click outside, Esc key, or close button all dismiss -->
|
|
952
|
+
<dialog is="fig-dialog" closedby="any">...</dialog>
|
|
953
|
+
|
|
954
|
+
<!-- Only Esc key or close button dismiss (no click-outside) -->
|
|
955
|
+
<dialog is="fig-dialog" closedby="closerequest">...</dialog>
|
|
956
|
+
|
|
957
|
+
<!-- Only explicit close button dismisses -->
|
|
958
|
+
<dialog is="fig-dialog" closedby="none">...</dialog></code></pre>
|
|
959
|
+
<hstack>
|
|
960
|
+
<fig-button variant="secondary"
|
|
961
|
+
onclick="document.getElementById('closedby-any-dialog').show()">closedby="any"</fig-button>
|
|
962
|
+
<fig-button variant="secondary"
|
|
963
|
+
onclick="document.getElementById('closedby-none-dialog').show()">closedby="none"</fig-button>
|
|
964
|
+
</hstack>
|
|
965
|
+
|
|
966
|
+
<dialog id="closedby-any-dialog"
|
|
967
|
+
is="fig-dialog"
|
|
968
|
+
drag="true"
|
|
969
|
+
closedby="any"
|
|
970
|
+
position="center center">
|
|
971
|
+
<fig-header>
|
|
972
|
+
<h3>Light Dismiss</h3>
|
|
973
|
+
<fig-tooltip text="Close">
|
|
974
|
+
<fig-button variant="ghost"
|
|
975
|
+
icon="true"
|
|
976
|
+
close-dialog>
|
|
977
|
+
<span class="fig-mask-icon"
|
|
978
|
+
style="--icon: var(--icon-close)"></span>
|
|
979
|
+
</fig-button>
|
|
980
|
+
</fig-tooltip>
|
|
981
|
+
</fig-header>
|
|
982
|
+
<fig-content>
|
|
983
|
+
<p>Click anywhere outside this dialog to close it.</p>
|
|
984
|
+
</fig-content>
|
|
985
|
+
</dialog>
|
|
986
|
+
|
|
987
|
+
<dialog id="closedby-none-dialog"
|
|
988
|
+
is="fig-dialog"
|
|
989
|
+
drag="true"
|
|
990
|
+
closedby="none"
|
|
991
|
+
position="center center">
|
|
992
|
+
<fig-header>
|
|
993
|
+
<h3>No Light Dismiss</h3>
|
|
994
|
+
<fig-tooltip text="Close">
|
|
995
|
+
<fig-button variant="ghost"
|
|
996
|
+
icon="true"
|
|
997
|
+
close-dialog>
|
|
998
|
+
<span class="fig-mask-icon"
|
|
999
|
+
style="--icon: var(--icon-close)"></span>
|
|
1000
|
+
</fig-button>
|
|
1001
|
+
</fig-tooltip>
|
|
1002
|
+
</fig-header>
|
|
1003
|
+
<fig-content>
|
|
1004
|
+
<p>This dialog can only be closed with the close button.</p>
|
|
1005
|
+
</fig-content>
|
|
1006
|
+
</dialog>
|
|
866
1007
|
</section>
|
|
867
1008
|
<hr>
|
|
868
1009
|
|
|
@@ -2968,6 +3109,28 @@
|
|
|
2968
3109
|
<dialog is="fig-popup" anchor="#popup-open-right-single" position="right" offset="12 8">
|
|
2969
3110
|
...
|
|
2970
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
|
+
|
|
2971
3134
|
</section>
|
|
2972
3135
|
<hr>
|
|
2973
3136
|
|
|
@@ -4113,6 +4276,50 @@ button.addEventListener('click', () => {
|
|
|
4113
4276
|
</fig-footer>
|
|
4114
4277
|
</main>
|
|
4115
4278
|
|
|
4279
|
+
<div class="toolbelt">
|
|
4280
|
+
<section>
|
|
4281
|
+
<fig-button variant="ghost"
|
|
4282
|
+
icon="true">
|
|
4283
|
+
<span class="fig-mask-icon"
|
|
4284
|
+
style="--icon: var(--icon-back)"></span>
|
|
4285
|
+
</fig-button>
|
|
4286
|
+
<fig-button variant="ghost"
|
|
4287
|
+
icon="true">
|
|
4288
|
+
<span class="fig-mask-icon"
|
|
4289
|
+
style="--icon: var(--icon-forward)"></span>
|
|
4290
|
+
</fig-button>
|
|
4291
|
+
</section>
|
|
4292
|
+
<section>
|
|
4293
|
+
<fig-button variant="ghost"
|
|
4294
|
+
icon="true">
|
|
4295
|
+
<span class="fig-mask-icon"
|
|
4296
|
+
style="--icon: var(--icon-add)"></span>
|
|
4297
|
+
</fig-button>
|
|
4298
|
+
<fig-button variant="ghost"
|
|
4299
|
+
icon="true">
|
|
4300
|
+
<span class="fig-mask-icon"
|
|
4301
|
+
style="--icon: var(--icon-minus)"></span>
|
|
4302
|
+
</fig-button>
|
|
4303
|
+
</section>
|
|
4304
|
+
<section>
|
|
4305
|
+
<fig-button variant="ghost"
|
|
4306
|
+
icon="true">
|
|
4307
|
+
<span class="fig-mask-icon"
|
|
4308
|
+
style="--icon: var(--icon-swap)"></span>
|
|
4309
|
+
</fig-button>
|
|
4310
|
+
<fig-button variant="ghost"
|
|
4311
|
+
icon="true">
|
|
4312
|
+
<span class="fig-mask-icon"
|
|
4313
|
+
style="--icon: var(--icon-rotate)"></span>
|
|
4314
|
+
</fig-button>
|
|
4315
|
+
<fig-button variant="ghost"
|
|
4316
|
+
icon="true">
|
|
4317
|
+
<span class="fig-mask-icon"
|
|
4318
|
+
style="--icon: var(--icon-eyedropper)"></span>
|
|
4319
|
+
</fig-button>
|
|
4320
|
+
</section>
|
|
4321
|
+
</div>
|
|
4322
|
+
|
|
4116
4323
|
<script>
|
|
4117
4324
|
// Highlight nav item based on hash
|
|
4118
4325
|
function updateActiveNav() {
|
|
@@ -4180,8 +4387,7 @@ button.addEventListener('click', () => {
|
|
|
4180
4387
|
|
|
4181
4388
|
function setTheme(isDark) {
|
|
4182
4389
|
document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';
|
|
4183
|
-
|
|
4184
|
-
document.documentElement.classList.toggle('figma-light', !isDark);
|
|
4390
|
+
|
|
4185
4391
|
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
4186
4392
|
// Sync switch state
|
|
4187
4393
|
if (isDark) {
|
|
@@ -4268,6 +4474,7 @@ button.addEventListener('click', () => {
|
|
|
4268
4474
|
['popup-open-center-left', 'popup-close-center-left', 'popup-center-left'],
|
|
4269
4475
|
['popup-open-top-single', 'popup-close-top-single', 'popup-top-single'],
|
|
4270
4476
|
['popup-open-right-single', 'popup-close-right-single', 'popup-right-single'],
|
|
4477
|
+
['popup-open-left-single', 'popup-close-left-single', 'popup-left-single'],
|
|
4271
4478
|
];
|
|
4272
4479
|
|
|
4273
4480
|
popupExamples.forEach(([openId, closeId, popupId]) => {
|
package/package.json
CHANGED