@rogieking/figui3 2.17.1 → 2.17.3
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 +76 -0
- package/fig.js +542 -0
- package/index.html +368 -0
- package/package.json +1 -1
package/components.css
CHANGED
|
@@ -2044,6 +2044,82 @@ dialog[is="fig-dialog"] {
|
|
|
2044
2044
|
z-index: var(--z-index);
|
|
2045
2045
|
}
|
|
2046
2046
|
|
|
2047
|
+
dialog[is="fig-popup"] {
|
|
2048
|
+
--z-index: 999999;
|
|
2049
|
+
z-index: var(--z-index);
|
|
2050
|
+
position: fixed;
|
|
2051
|
+
margin: 0;
|
|
2052
|
+
min-width: 0;
|
|
2053
|
+
width: max-content;
|
|
2054
|
+
max-width: calc(100vw - var(--spacer-4));
|
|
2055
|
+
max-height: calc(100vh - var(--spacer-4));
|
|
2056
|
+
padding: var(--spacer-2);
|
|
2057
|
+
overflow: auto;
|
|
2058
|
+
|
|
2059
|
+
&[open] {
|
|
2060
|
+
display: block;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
&[theme="dark"] {
|
|
2064
|
+
color-scheme: dark;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
&[theme="light"] {
|
|
2068
|
+
color-scheme: light;
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
&[theme="menu"] {
|
|
2072
|
+
background-color: var(--figma-color-bg-menu);
|
|
2073
|
+
color: var(--figma-color-text-menu);
|
|
2074
|
+
color-scheme: dark;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
&[variant="popover"] {
|
|
2078
|
+
overflow: visible;
|
|
2079
|
+
box-shadow: inset 0 0.5px 0 0 rgba(255, 255, 255, 0.1);
|
|
2080
|
+
filter: drop-shadow(0px 1px 1.5px rgba(0, 0, 0, 0.1))
|
|
2081
|
+
drop-shadow(0px 2.5px 6px rgba(0, 0, 0, 0.13))
|
|
2082
|
+
drop-shadow(0px 0px 0.5px rgba(0, 0, 0, 0.15));
|
|
2083
|
+
|
|
2084
|
+
&:after {
|
|
2085
|
+
content: "";
|
|
2086
|
+
background-color: inherit;
|
|
2087
|
+
clip-path: path(
|
|
2088
|
+
"M16 0H0L0 1H0.757359C1.55301 1 2.31607 1.31607 2.87868 1.87868L6.29587 5.29587C7.23704 6.23704 8.76296 6.23704 9.70413 5.29587L13.1213 1.87868C13.6839 1.31607 14.447 1 15.2426 1H16V0Z"
|
|
2089
|
+
);
|
|
2090
|
+
position: absolute;
|
|
2091
|
+
width: 1rem;
|
|
2092
|
+
height: 6px;
|
|
2093
|
+
z-index: 2;
|
|
2094
|
+
pointer-events: none;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
&[data-beak-side="bottom"]:after {
|
|
2098
|
+
top: calc(100% - 1px);
|
|
2099
|
+
left: var(--beak-offset, 50%);
|
|
2100
|
+
transform: translateX(-50%);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
&[data-beak-side="top"]:after {
|
|
2104
|
+
top: 1px;
|
|
2105
|
+
left: var(--beak-offset, 50%);
|
|
2106
|
+
transform: translate(-50%, -100%) scaleY(-1);
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
&[data-beak-side="left"]:after {
|
|
2110
|
+
left: 1px;
|
|
2111
|
+
top: var(--beak-offset, 50%);
|
|
2112
|
+
transform: translate(-100%, -50%) rotate(90deg);
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
&[data-beak-side="right"]:after {
|
|
2116
|
+
left: calc(100% - 1px);
|
|
2117
|
+
top: var(--beak-offset, 50%);
|
|
2118
|
+
transform: translate(0, -50%) rotate(-90deg);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2047
2123
|
dialog[is="fig-toast"] {
|
|
2048
2124
|
--z-index: 999999;
|
|
2049
2125
|
z-index: var(--z-index);
|
package/fig.js
CHANGED
|
@@ -1050,6 +1050,548 @@ class FigDialog extends HTMLDialogElement {
|
|
|
1050
1050
|
}
|
|
1051
1051
|
customElements.define("fig-dialog", FigDialog, { extends: "dialog" });
|
|
1052
1052
|
|
|
1053
|
+
/* Popup */
|
|
1054
|
+
/**
|
|
1055
|
+
* A floating popup foundation component based on <dialog>.
|
|
1056
|
+
* @attr {string} anchor - CSS selector used to resolve the anchor element.
|
|
1057
|
+
* @attr {string} position - Preferred placement as "vertical horizontal" (default: "top center").
|
|
1058
|
+
* @attr {string} offset - Horizontal and vertical offset as "x y" (default: "0 0").
|
|
1059
|
+
* @attr {string} variant - Visual variant. Use variant="popover" to show an anchor beak.
|
|
1060
|
+
* @attr {string} theme - Visual theme: "light", "dark", or "menu".
|
|
1061
|
+
* @attr {boolean|string} open - Open when present and not "false".
|
|
1062
|
+
*/
|
|
1063
|
+
class FigPopup extends HTMLDialogElement {
|
|
1064
|
+
#viewportPadding = 8;
|
|
1065
|
+
#anchorObserver = null;
|
|
1066
|
+
#contentObserver = null;
|
|
1067
|
+
#mutationObserver = null;
|
|
1068
|
+
#isPopupActive = false;
|
|
1069
|
+
#boundReposition;
|
|
1070
|
+
#boundScroll;
|
|
1071
|
+
#boundOutsidePointerDown;
|
|
1072
|
+
#rafId = null;
|
|
1073
|
+
|
|
1074
|
+
constructor() {
|
|
1075
|
+
super();
|
|
1076
|
+
this.#boundReposition = this.#queueReposition.bind(this);
|
|
1077
|
+
this.#boundScroll = this.#queueReposition.bind(this);
|
|
1078
|
+
this.#boundOutsidePointerDown = this.#handleOutsidePointerDown.bind(this);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
static get observedAttributes() {
|
|
1082
|
+
return ["open", "anchor", "position", "offset", "variant", "theme"];
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
get open() {
|
|
1086
|
+
return this.hasAttribute("open") && this.getAttribute("open") !== "false";
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
set open(value) {
|
|
1090
|
+
if (value === false || value === "false" || value === null) {
|
|
1091
|
+
if (!this.open) return;
|
|
1092
|
+
this.removeAttribute("open");
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (this.open) return;
|
|
1096
|
+
this.setAttribute("open", "true");
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
connectedCallback() {
|
|
1100
|
+
if (!this.hasAttribute("position")) {
|
|
1101
|
+
this.setAttribute("position", "top center");
|
|
1102
|
+
}
|
|
1103
|
+
if (!this.hasAttribute("role")) {
|
|
1104
|
+
this.setAttribute("role", "dialog");
|
|
1105
|
+
}
|
|
1106
|
+
// Default dialog outside-close behavior.
|
|
1107
|
+
if (!this.hasAttribute("closedby")) {
|
|
1108
|
+
this.setAttribute("closedby", "any");
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
this.addEventListener("close", () => {
|
|
1112
|
+
this.#teardownObservers();
|
|
1113
|
+
if (this.hasAttribute("open")) {
|
|
1114
|
+
this.removeAttribute("open");
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
if (this.open) {
|
|
1119
|
+
this.#showPopup();
|
|
1120
|
+
} else {
|
|
1121
|
+
this.#hidePopup();
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
disconnectedCallback() {
|
|
1126
|
+
this.#teardownObservers();
|
|
1127
|
+
document.removeEventListener(
|
|
1128
|
+
"pointerdown",
|
|
1129
|
+
this.#boundOutsidePointerDown,
|
|
1130
|
+
true
|
|
1131
|
+
);
|
|
1132
|
+
if (this.#rafId !== null) {
|
|
1133
|
+
cancelAnimationFrame(this.#rafId);
|
|
1134
|
+
this.#rafId = null;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1139
|
+
if (oldValue === newValue) return;
|
|
1140
|
+
|
|
1141
|
+
if (name === "open") {
|
|
1142
|
+
if (newValue === null || newValue === "false") {
|
|
1143
|
+
this.#hidePopup();
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
this.#showPopup();
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
if (this.open) {
|
|
1151
|
+
this.#queueReposition();
|
|
1152
|
+
this.#setupObservers();
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
#showPopup() {
|
|
1157
|
+
if (this.#isPopupActive) {
|
|
1158
|
+
this.#queueReposition();
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
this.style.position = "fixed";
|
|
1163
|
+
this.style.inset = "auto";
|
|
1164
|
+
this.style.margin = "0";
|
|
1165
|
+
this.style.zIndex = String(figGetHighestZIndex() + 1);
|
|
1166
|
+
|
|
1167
|
+
if (!super.open) {
|
|
1168
|
+
try {
|
|
1169
|
+
this.show();
|
|
1170
|
+
} catch (e) {
|
|
1171
|
+
// Ignore when dialog cannot be shown yet.
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
this.#setupObservers();
|
|
1176
|
+
document.addEventListener("pointerdown", this.#boundOutsidePointerDown, true);
|
|
1177
|
+
this.#queueReposition();
|
|
1178
|
+
this.#isPopupActive = true;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
#hidePopup() {
|
|
1182
|
+
this.#isPopupActive = false;
|
|
1183
|
+
this.#teardownObservers();
|
|
1184
|
+
document.removeEventListener(
|
|
1185
|
+
"pointerdown",
|
|
1186
|
+
this.#boundOutsidePointerDown,
|
|
1187
|
+
true
|
|
1188
|
+
);
|
|
1189
|
+
|
|
1190
|
+
if (super.open) {
|
|
1191
|
+
try {
|
|
1192
|
+
this.close();
|
|
1193
|
+
} catch (e) {
|
|
1194
|
+
// Ignore when dialog is not in an open state.
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
#setupObservers() {
|
|
1200
|
+
this.#teardownObservers();
|
|
1201
|
+
|
|
1202
|
+
const anchor = this.#resolveAnchor();
|
|
1203
|
+
if (anchor && "ResizeObserver" in window) {
|
|
1204
|
+
this.#anchorObserver = new ResizeObserver(this.#boundReposition);
|
|
1205
|
+
this.#anchorObserver.observe(anchor);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
if ("ResizeObserver" in window) {
|
|
1209
|
+
this.#contentObserver = new ResizeObserver(this.#boundReposition);
|
|
1210
|
+
this.#contentObserver.observe(this);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
this.#mutationObserver = new MutationObserver(this.#boundReposition);
|
|
1214
|
+
this.#mutationObserver.observe(this, {
|
|
1215
|
+
childList: true,
|
|
1216
|
+
subtree: true,
|
|
1217
|
+
characterData: true,
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
window.addEventListener("resize", this.#boundReposition);
|
|
1221
|
+
window.addEventListener("scroll", this.#boundScroll, true);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
#teardownObservers() {
|
|
1225
|
+
if (this.#anchorObserver) {
|
|
1226
|
+
this.#anchorObserver.disconnect();
|
|
1227
|
+
this.#anchorObserver = null;
|
|
1228
|
+
}
|
|
1229
|
+
if (this.#contentObserver) {
|
|
1230
|
+
this.#contentObserver.disconnect();
|
|
1231
|
+
this.#contentObserver = null;
|
|
1232
|
+
}
|
|
1233
|
+
if (this.#mutationObserver) {
|
|
1234
|
+
this.#mutationObserver.disconnect();
|
|
1235
|
+
this.#mutationObserver = null;
|
|
1236
|
+
}
|
|
1237
|
+
window.removeEventListener("resize", this.#boundReposition);
|
|
1238
|
+
window.removeEventListener("scroll", this.#boundScroll, true);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
#handleOutsidePointerDown(event) {
|
|
1242
|
+
if (!this.open || !super.open) return;
|
|
1243
|
+
const target = event.target;
|
|
1244
|
+
if (!(target instanceof Node)) return;
|
|
1245
|
+
if (this.contains(target)) return;
|
|
1246
|
+
|
|
1247
|
+
// Fallback for browsers that do not honor dialog closedby consistently.
|
|
1248
|
+
this.open = false;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
#resolveAnchor() {
|
|
1252
|
+
const selector = this.getAttribute("anchor");
|
|
1253
|
+
if (!selector) return null;
|
|
1254
|
+
|
|
1255
|
+
// Local-first: nearest parent subtree.
|
|
1256
|
+
const localScope = this.parentElement;
|
|
1257
|
+
if (localScope?.querySelector) {
|
|
1258
|
+
const localMatch = localScope.querySelector(selector);
|
|
1259
|
+
if (localMatch && !this.contains(localMatch)) return localMatch;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// Fallback: global document query.
|
|
1263
|
+
return document.querySelector(selector);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
#parsePosition() {
|
|
1267
|
+
const raw = (this.getAttribute("position") || "top center")
|
|
1268
|
+
.trim()
|
|
1269
|
+
.toLowerCase();
|
|
1270
|
+
const tokens = raw.split(/\s+/).filter(Boolean);
|
|
1271
|
+
const verticalValues = new Set(["top", "center", "bottom"]);
|
|
1272
|
+
const horizontalValues = new Set(["left", "center", "right"]);
|
|
1273
|
+
|
|
1274
|
+
let vertical = "top";
|
|
1275
|
+
let horizontal = "center";
|
|
1276
|
+
let shorthand = null;
|
|
1277
|
+
|
|
1278
|
+
// Treat position as "vertical horizontal" to avoid center ambiguity.
|
|
1279
|
+
if (tokens.length >= 2) {
|
|
1280
|
+
if (verticalValues.has(tokens[0])) {
|
|
1281
|
+
vertical = tokens[0];
|
|
1282
|
+
}
|
|
1283
|
+
if (horizontalValues.has(tokens[1])) {
|
|
1284
|
+
horizontal = tokens[1];
|
|
1285
|
+
}
|
|
1286
|
+
return { vertical, horizontal, shorthand };
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// Single-token fallback: apply only if non-ambiguous.
|
|
1290
|
+
if (tokens.length === 1) {
|
|
1291
|
+
const token = tokens[0];
|
|
1292
|
+
if (token === "top" || token === "bottom") {
|
|
1293
|
+
vertical = token;
|
|
1294
|
+
shorthand = token;
|
|
1295
|
+
} else if (token === "left" || token === "right") {
|
|
1296
|
+
horizontal = token;
|
|
1297
|
+
shorthand = token;
|
|
1298
|
+
} else if (token === "center") {
|
|
1299
|
+
vertical = "center";
|
|
1300
|
+
horizontal = "center";
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
return { vertical, horizontal, shorthand };
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
#normalizeOffsetToken(token, fallback = "0px") {
|
|
1308
|
+
if (!token) return fallback;
|
|
1309
|
+
const trimmed = token.trim();
|
|
1310
|
+
if (!trimmed) return fallback;
|
|
1311
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
1312
|
+
return `${trimmed}px`;
|
|
1313
|
+
}
|
|
1314
|
+
return trimmed;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
#measureLengthPx(value, axis = "x") {
|
|
1318
|
+
if (!value) return 0;
|
|
1319
|
+
const normalized = this.#normalizeOffsetToken(value, "0px");
|
|
1320
|
+
if (normalized.endsWith("px")) {
|
|
1321
|
+
const px = parseFloat(normalized);
|
|
1322
|
+
return Number.isFinite(px) ? px : 0;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
const probe = document.createElement("div");
|
|
1326
|
+
probe.style.position = "fixed";
|
|
1327
|
+
probe.style.visibility = "hidden";
|
|
1328
|
+
probe.style.pointerEvents = "none";
|
|
1329
|
+
probe.style.left = "0";
|
|
1330
|
+
probe.style.top = "0";
|
|
1331
|
+
probe.style.margin = "0";
|
|
1332
|
+
probe.style.padding = "0";
|
|
1333
|
+
probe.style.border = "0";
|
|
1334
|
+
if (axis === "x") {
|
|
1335
|
+
probe.style.width = normalized;
|
|
1336
|
+
probe.style.height = "0";
|
|
1337
|
+
} else {
|
|
1338
|
+
probe.style.height = normalized;
|
|
1339
|
+
probe.style.width = "0";
|
|
1340
|
+
}
|
|
1341
|
+
document.body.appendChild(probe);
|
|
1342
|
+
const rect = probe.getBoundingClientRect();
|
|
1343
|
+
probe.remove();
|
|
1344
|
+
return axis === "x" ? rect.width : rect.height;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
#parseOffset() {
|
|
1348
|
+
const raw = (this.getAttribute("offset") || "0 0").trim();
|
|
1349
|
+
const tokens = raw.split(/\s+/).filter(Boolean);
|
|
1350
|
+
|
|
1351
|
+
const xToken = this.#normalizeOffsetToken(tokens[0], "0px");
|
|
1352
|
+
const yToken = this.#normalizeOffsetToken(tokens[1], "0px");
|
|
1353
|
+
|
|
1354
|
+
return {
|
|
1355
|
+
xToken,
|
|
1356
|
+
yToken,
|
|
1357
|
+
xPx: this.#measureLengthPx(xToken, "x"),
|
|
1358
|
+
yPx: this.#measureLengthPx(yToken, "y"),
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
#getOrder(preferred, axis) {
|
|
1363
|
+
const verticalMap = {
|
|
1364
|
+
top: ["top", "bottom", "center"],
|
|
1365
|
+
center: ["center", "top", "bottom"],
|
|
1366
|
+
bottom: ["bottom", "top", "center"],
|
|
1367
|
+
};
|
|
1368
|
+
const horizontalMap = {
|
|
1369
|
+
left: ["left", "right", "center"],
|
|
1370
|
+
center: ["center", "left", "right"],
|
|
1371
|
+
right: ["right", "left", "center"],
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1374
|
+
return axis === "vertical"
|
|
1375
|
+
? verticalMap[preferred] || verticalMap.top
|
|
1376
|
+
: horizontalMap[preferred] || horizontalMap.center;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
#computeCoords(anchorRect, popupRect, vertical, horizontal, offset, shorthand) {
|
|
1380
|
+
let top;
|
|
1381
|
+
let left;
|
|
1382
|
+
|
|
1383
|
+
// Shorthand support:
|
|
1384
|
+
// position="right" => top edges aligned, popup sits to the right of anchor.
|
|
1385
|
+
// position="left" => top edges aligned, popup sits to the left of anchor.
|
|
1386
|
+
if (shorthand === "right") {
|
|
1387
|
+
return {
|
|
1388
|
+
top: anchorRect.top,
|
|
1389
|
+
left: anchorRect.right + offset.xPx,
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
if (shorthand === "left") {
|
|
1393
|
+
return {
|
|
1394
|
+
top: anchorRect.top,
|
|
1395
|
+
left: anchorRect.left - popupRect.width - offset.xPx,
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
if (shorthand === "top") {
|
|
1399
|
+
return {
|
|
1400
|
+
top: anchorRect.top - popupRect.height - offset.yPx,
|
|
1401
|
+
left: anchorRect.left,
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
if (shorthand === "bottom") {
|
|
1405
|
+
return {
|
|
1406
|
+
top: anchorRect.bottom + offset.yPx,
|
|
1407
|
+
left: anchorRect.left,
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
if (vertical === "top") {
|
|
1412
|
+
top = anchorRect.top - popupRect.height - offset.yPx;
|
|
1413
|
+
} else if (vertical === "bottom") {
|
|
1414
|
+
top = anchorRect.bottom + offset.yPx;
|
|
1415
|
+
} else {
|
|
1416
|
+
top = anchorRect.top + (anchorRect.height - popupRect.height) / 2;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
if (horizontal === "left") {
|
|
1420
|
+
left = anchorRect.left - popupRect.width - offset.xPx;
|
|
1421
|
+
} else if (horizontal === "right") {
|
|
1422
|
+
left = anchorRect.right + offset.xPx;
|
|
1423
|
+
} else {
|
|
1424
|
+
left = anchorRect.left + (anchorRect.width - popupRect.width) / 2;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
return { top, left };
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
#oppositeSide(side) {
|
|
1431
|
+
const map = {
|
|
1432
|
+
top: "bottom",
|
|
1433
|
+
bottom: "top",
|
|
1434
|
+
left: "right",
|
|
1435
|
+
right: "left",
|
|
1436
|
+
};
|
|
1437
|
+
return map[side] || "bottom";
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
#getPlacementSide(vertical, horizontal, shorthand) {
|
|
1441
|
+
if (shorthand === "top") return "top";
|
|
1442
|
+
if (shorthand === "bottom") return "bottom";
|
|
1443
|
+
if (shorthand === "left") return "left";
|
|
1444
|
+
if (shorthand === "right") return "right";
|
|
1445
|
+
|
|
1446
|
+
if (vertical !== "center") return vertical;
|
|
1447
|
+
if (horizontal !== "center") return horizontal;
|
|
1448
|
+
return "top";
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
#updatePopoverBeak(anchorRect, popupRect, left, top, placementSide) {
|
|
1452
|
+
if (this.getAttribute("variant") !== "popover" || !anchorRect) {
|
|
1453
|
+
this.style.removeProperty("--beak-offset");
|
|
1454
|
+
this.removeAttribute("data-beak-side");
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const beakSide = this.#oppositeSide(placementSide);
|
|
1459
|
+
this.setAttribute("data-beak-side", beakSide);
|
|
1460
|
+
|
|
1461
|
+
const anchorCenterX = anchorRect.left + anchorRect.width / 2;
|
|
1462
|
+
const anchorCenterY = anchorRect.top + anchorRect.height / 2;
|
|
1463
|
+
|
|
1464
|
+
let beakOffset;
|
|
1465
|
+
if (beakSide === "top" || beakSide === "bottom") {
|
|
1466
|
+
beakOffset = anchorCenterX - left;
|
|
1467
|
+
const min = 8;
|
|
1468
|
+
const max = Math.max(min, popupRect.width - 8);
|
|
1469
|
+
beakOffset = Math.min(max, Math.max(min, beakOffset));
|
|
1470
|
+
} else {
|
|
1471
|
+
beakOffset = anchorCenterY - top;
|
|
1472
|
+
const min = 8;
|
|
1473
|
+
const max = Math.max(min, popupRect.height - 8);
|
|
1474
|
+
beakOffset = Math.min(max, Math.max(min, beakOffset));
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
this.style.setProperty("--beak-offset", `${beakOffset}px`);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
#overflowScore(coords, popupRect) {
|
|
1481
|
+
const vw = window.innerWidth;
|
|
1482
|
+
const vh = window.innerHeight;
|
|
1483
|
+
const right = coords.left + popupRect.width;
|
|
1484
|
+
const bottom = coords.top + popupRect.height;
|
|
1485
|
+
const pad = this.#viewportPadding;
|
|
1486
|
+
|
|
1487
|
+
const overflowLeft = Math.max(0, pad - coords.left);
|
|
1488
|
+
const overflowTop = Math.max(0, pad - coords.top);
|
|
1489
|
+
const overflowRight = Math.max(0, right - (vw - pad));
|
|
1490
|
+
const overflowBottom = Math.max(0, bottom - (vh - pad));
|
|
1491
|
+
|
|
1492
|
+
return overflowLeft + overflowTop + overflowRight + overflowBottom;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
#fits(coords, popupRect) {
|
|
1496
|
+
return this.#overflowScore(coords, popupRect) === 0;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
#clamp(coords, popupRect) {
|
|
1500
|
+
const pad = this.#viewportPadding;
|
|
1501
|
+
const minLeft = pad;
|
|
1502
|
+
const minTop = pad;
|
|
1503
|
+
const maxLeft = Math.max(pad, window.innerWidth - popupRect.width - pad);
|
|
1504
|
+
const maxTop = Math.max(pad, window.innerHeight - popupRect.height - pad);
|
|
1505
|
+
|
|
1506
|
+
return {
|
|
1507
|
+
left: Math.min(maxLeft, Math.max(minLeft, coords.left)),
|
|
1508
|
+
top: Math.min(maxTop, Math.max(minTop, coords.top)),
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
#positionPopup() {
|
|
1513
|
+
if (!this.open || !super.open) return;
|
|
1514
|
+
|
|
1515
|
+
const popupRect = this.getBoundingClientRect();
|
|
1516
|
+
const offset = this.#parseOffset();
|
|
1517
|
+
const { vertical, horizontal, shorthand } = this.#parsePosition();
|
|
1518
|
+
const verticalOrder = this.#getOrder(vertical, "vertical");
|
|
1519
|
+
const horizontalOrder = this.#getOrder(horizontal, "horizontal");
|
|
1520
|
+
const anchor = this.#resolveAnchor();
|
|
1521
|
+
|
|
1522
|
+
if (!anchor) {
|
|
1523
|
+
this.#updatePopoverBeak(null, popupRect, 0, 0, "top");
|
|
1524
|
+
const centered = {
|
|
1525
|
+
left: (window.innerWidth - popupRect.width) / 2,
|
|
1526
|
+
top: (window.innerHeight - popupRect.height) / 2,
|
|
1527
|
+
};
|
|
1528
|
+
const clamped = this.#clamp(centered, popupRect);
|
|
1529
|
+
this.style.left = `${clamped.left}px`;
|
|
1530
|
+
this.style.top = `${clamped.top}px`;
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
1535
|
+
let best = null;
|
|
1536
|
+
let bestSide = "top";
|
|
1537
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
1538
|
+
|
|
1539
|
+
for (const v of verticalOrder) {
|
|
1540
|
+
for (const h of horizontalOrder) {
|
|
1541
|
+
const coords = this.#computeCoords(
|
|
1542
|
+
anchorRect,
|
|
1543
|
+
popupRect,
|
|
1544
|
+
v,
|
|
1545
|
+
h,
|
|
1546
|
+
offset,
|
|
1547
|
+
shorthand
|
|
1548
|
+
);
|
|
1549
|
+
const placementSide = this.#getPlacementSide(v, h, shorthand);
|
|
1550
|
+
if (this.#fits(coords, popupRect)) {
|
|
1551
|
+
this.style.left = `${coords.left}px`;
|
|
1552
|
+
this.style.top = `${coords.top}px`;
|
|
1553
|
+
this.#updatePopoverBeak(
|
|
1554
|
+
anchorRect,
|
|
1555
|
+
popupRect,
|
|
1556
|
+
coords.left,
|
|
1557
|
+
coords.top,
|
|
1558
|
+
placementSide
|
|
1559
|
+
);
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
const score = this.#overflowScore(coords, popupRect);
|
|
1563
|
+
if (score < bestScore) {
|
|
1564
|
+
bestScore = score;
|
|
1565
|
+
best = coords;
|
|
1566
|
+
bestSide = placementSide;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
const clamped = this.#clamp(best || { left: 0, top: 0 }, popupRect);
|
|
1572
|
+
this.style.left = `${clamped.left}px`;
|
|
1573
|
+
this.style.top = `${clamped.top}px`;
|
|
1574
|
+
this.#updatePopoverBeak(
|
|
1575
|
+
anchorRect,
|
|
1576
|
+
popupRect,
|
|
1577
|
+
clamped.left,
|
|
1578
|
+
clamped.top,
|
|
1579
|
+
bestSide
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
#queueReposition() {
|
|
1584
|
+
if (!this.open) return;
|
|
1585
|
+
if (this.#rafId !== null) return;
|
|
1586
|
+
|
|
1587
|
+
this.#rafId = requestAnimationFrame(() => {
|
|
1588
|
+
this.#rafId = null;
|
|
1589
|
+
this.#positionPopup();
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
customElements.define("fig-popup", FigPopup, { extends: "dialog" });
|
|
1594
|
+
|
|
1053
1595
|
/**
|
|
1054
1596
|
* A popover element using the native Popover API.
|
|
1055
1597
|
* @attr {string} trigger-action - The trigger action: "click" (default) or "hover"
|
package/index.html
CHANGED
|
@@ -184,6 +184,7 @@
|
|
|
184
184
|
<a href="#input-text">Input Text</a>
|
|
185
185
|
<a href="#layer">Layer</a>
|
|
186
186
|
<a href="#popover">Popover</a>
|
|
187
|
+
<a href="#popup">Popup</a>
|
|
187
188
|
<a href="#radio">Radio</a>
|
|
188
189
|
<a href="#segmented-control">Segmented Control</a>
|
|
189
190
|
<a href="#shimmer">Shimmer</a>
|
|
@@ -2635,6 +2636,305 @@
|
|
|
2635
2636
|
</section>
|
|
2636
2637
|
<hr>
|
|
2637
2638
|
|
|
2639
|
+
<!-- Popup -->
|
|
2640
|
+
<section id="popup">
|
|
2641
|
+
<h2>Popup</h2>
|
|
2642
|
+
<p class="description">A floating foundation component built as <code><dialog is="fig-popup"></code>
|
|
2643
|
+
with anchor-based positioning and viewport-aware fallback.</p>
|
|
2644
|
+
|
|
2645
|
+
<h3>Default Position (top center)</h3>
|
|
2646
|
+
<hstack>
|
|
2647
|
+
<fig-button id="popup-open-default">Open Popup</fig-button>
|
|
2648
|
+
<fig-button id="popup-close-default"
|
|
2649
|
+
variant="secondary">Close Popup</fig-button>
|
|
2650
|
+
</hstack>
|
|
2651
|
+
<dialog id="popup-default"
|
|
2652
|
+
is="fig-popup"
|
|
2653
|
+
anchor="#popup-open-default">
|
|
2654
|
+
<vstack style="min-width: 12rem;">
|
|
2655
|
+
<strong style="padding: 0 var(--spacer-1);">Default Popup</strong>
|
|
2656
|
+
<fig-field direction="horizontal">
|
|
2657
|
+
<label>Opacity</label>
|
|
2658
|
+
<fig-slider value="50"
|
|
2659
|
+
text="true"
|
|
2660
|
+
units="%"></fig-slider>
|
|
2661
|
+
</fig-field>
|
|
2662
|
+
</vstack>
|
|
2663
|
+
</dialog>
|
|
2664
|
+
|
|
2665
|
+
<h3>Preferred Position (bottom right)</h3>
|
|
2666
|
+
<hstack>
|
|
2667
|
+
<fig-button id="popup-open-preferred">Open Bottom Right</fig-button>
|
|
2668
|
+
<fig-button id="popup-close-preferred"
|
|
2669
|
+
variant="secondary">Close</fig-button>
|
|
2670
|
+
</hstack>
|
|
2671
|
+
<dialog id="popup-preferred"
|
|
2672
|
+
is="fig-popup"
|
|
2673
|
+
anchor="#popup-open-preferred"
|
|
2674
|
+
position="bottom right">
|
|
2675
|
+
<vstack style="min-width: 10rem;">
|
|
2676
|
+
<strong style="padding: 0 var(--spacer-1);">Preferred Placement</strong>
|
|
2677
|
+
<fig-checkbox label="Apply to existing strokes"
|
|
2678
|
+
checked></fig-checkbox>
|
|
2679
|
+
<fig-checkbox label="Apply to shapes with fills"></fig-checkbox>
|
|
2680
|
+
</vstack>
|
|
2681
|
+
</dialog>
|
|
2682
|
+
|
|
2683
|
+
<h3>Popover Variant (with Arrow)</h3>
|
|
2684
|
+
<p class="description">Use <code>variant="popover"</code> to show the beak and automatically point it at the
|
|
2685
|
+
anchor. Use <code>theme="menu"</code> for menu-style dark surfaces.</p>
|
|
2686
|
+
<fig-segmented-control id="popup-theme-control">
|
|
2687
|
+
<fig-segment id="popup-theme-default">Default</fig-segment>
|
|
2688
|
+
<fig-segment id="popup-theme-dark">Dark</fig-segment>
|
|
2689
|
+
<fig-segment id="popup-theme-light">Light</fig-segment>
|
|
2690
|
+
<fig-segment id="popup-theme-menu"
|
|
2691
|
+
selected>Menu</fig-segment>
|
|
2692
|
+
</fig-segmented-control>
|
|
2693
|
+
<hstack>
|
|
2694
|
+
<fig-button id="popup-open-popover">Open Popover Variant</fig-button>
|
|
2695
|
+
<fig-button id="popup-close-popover"
|
|
2696
|
+
variant="secondary">Close</fig-button>
|
|
2697
|
+
</hstack>
|
|
2698
|
+
<dialog id="popup-popover"
|
|
2699
|
+
is="fig-popup"
|
|
2700
|
+
anchor="#popup-open-popover"
|
|
2701
|
+
position="top center"
|
|
2702
|
+
variant="popover"
|
|
2703
|
+
theme="menu">
|
|
2704
|
+
<vstack style="min-width: 12rem;">
|
|
2705
|
+
<strong style="padding: 0 var(--spacer-1);">Popover Beak</strong>
|
|
2706
|
+
<fig-field direction="horizontal">
|
|
2707
|
+
<label>Apply</label>
|
|
2708
|
+
<fig-switch checked></fig-switch>
|
|
2709
|
+
</fig-field>
|
|
2710
|
+
</vstack>
|
|
2711
|
+
</dialog>
|
|
2712
|
+
<pre><code><fig-button id="popup-open-popover">Open Popover Variant</fig-button>
|
|
2713
|
+
<dialog is="fig-popup"
|
|
2714
|
+
anchor="#popup-open-popover"
|
|
2715
|
+
position="top center"
|
|
2716
|
+
variant="popover"
|
|
2717
|
+
theme="menu">
|
|
2718
|
+
...
|
|
2719
|
+
</dialog></code></pre>
|
|
2720
|
+
|
|
2721
|
+
<h3>Property API</h3>
|
|
2722
|
+
<p class="description">Uses the popup <code>open</code> property setter/getter (attribute wrapper).</p>
|
|
2723
|
+
<hstack>
|
|
2724
|
+
<fig-button id="popup-open-prop">popup.open = true</fig-button>
|
|
2725
|
+
<fig-button id="popup-close-prop"
|
|
2726
|
+
variant="secondary">popup.open = false</fig-button>
|
|
2727
|
+
</hstack>
|
|
2728
|
+
<dialog id="popup-prop"
|
|
2729
|
+
is="fig-popup"
|
|
2730
|
+
anchor="#popup-open-prop"
|
|
2731
|
+
position="top left">
|
|
2732
|
+
<vstack style="min-width: 11rem;">
|
|
2733
|
+
<strong style="padding: 0 var(--spacer-1);">Property Controlled</strong>
|
|
2734
|
+
<fig-input-text placeholder="Type here"></fig-input-text>
|
|
2735
|
+
</vstack>
|
|
2736
|
+
</dialog>
|
|
2737
|
+
|
|
2738
|
+
<h3>Position Examples</h3>
|
|
2739
|
+
<p class="description">Each example uses a different popup position with a dedicated open button and code
|
|
2740
|
+
snippet.</p>
|
|
2741
|
+
|
|
2742
|
+
<h4>Top Left</h4>
|
|
2743
|
+
<hstack>
|
|
2744
|
+
<fig-button id="popup-open-top-left">Open Top Left</fig-button>
|
|
2745
|
+
<fig-button id="popup-close-top-left"
|
|
2746
|
+
variant="secondary">Close</fig-button>
|
|
2747
|
+
</hstack>
|
|
2748
|
+
<dialog id="popup-top-left"
|
|
2749
|
+
is="fig-popup"
|
|
2750
|
+
anchor="#popup-open-top-left"
|
|
2751
|
+
position="top left">
|
|
2752
|
+
<vstack style="min-width: 10rem;">
|
|
2753
|
+
<strong style="padding: 0 var(--spacer-1);">Top Left</strong>
|
|
2754
|
+
<fig-input-number value="12"
|
|
2755
|
+
units="px"></fig-input-number>
|
|
2756
|
+
</vstack>
|
|
2757
|
+
</dialog>
|
|
2758
|
+
<pre><code><fig-button id="popup-open-top-left">Open Top Left</fig-button>
|
|
2759
|
+
<dialog is="fig-popup" anchor="#popup-open-top-left" position="top left">
|
|
2760
|
+
...
|
|
2761
|
+
</dialog></code></pre>
|
|
2762
|
+
|
|
2763
|
+
<h4>Top Center</h4>
|
|
2764
|
+
<hstack>
|
|
2765
|
+
<fig-button id="popup-open-top-center">Open Top Center</fig-button>
|
|
2766
|
+
<fig-button id="popup-close-top-center"
|
|
2767
|
+
variant="secondary">Close</fig-button>
|
|
2768
|
+
</hstack>
|
|
2769
|
+
<dialog id="popup-top-center"
|
|
2770
|
+
is="fig-popup"
|
|
2771
|
+
anchor="#popup-open-top-center"
|
|
2772
|
+
position="top center">
|
|
2773
|
+
<vstack style="min-width: 10rem;">
|
|
2774
|
+
<strong style="padding: 0 var(--spacer-1);">Top Center</strong>
|
|
2775
|
+
<fig-switch label="Enabled"></fig-switch>
|
|
2776
|
+
</vstack>
|
|
2777
|
+
</dialog>
|
|
2778
|
+
<pre><code><fig-button id="popup-open-top-center">Open Top Center</fig-button>
|
|
2779
|
+
<dialog is="fig-popup" anchor="#popup-open-top-center" position="top center">
|
|
2780
|
+
...
|
|
2781
|
+
</dialog></code></pre>
|
|
2782
|
+
|
|
2783
|
+
<h4>Top Right</h4>
|
|
2784
|
+
<hstack>
|
|
2785
|
+
<fig-button id="popup-open-top-right">Open Top Right</fig-button>
|
|
2786
|
+
<fig-button id="popup-close-top-right"
|
|
2787
|
+
variant="secondary">Close</fig-button>
|
|
2788
|
+
</hstack>
|
|
2789
|
+
<dialog id="popup-top-right"
|
|
2790
|
+
is="fig-popup"
|
|
2791
|
+
anchor="#popup-open-top-right"
|
|
2792
|
+
position="top right">
|
|
2793
|
+
<vstack style="min-width: 10rem;">
|
|
2794
|
+
<strong style="padding: 0 var(--spacer-1);">Top Right</strong>
|
|
2795
|
+
<fig-input-number value="24"
|
|
2796
|
+
units="px"></fig-input-number>
|
|
2797
|
+
</vstack>
|
|
2798
|
+
</dialog>
|
|
2799
|
+
<pre><code><fig-button id="popup-open-top-right">Open Top Right</fig-button>
|
|
2800
|
+
<dialog is="fig-popup" anchor="#popup-open-top-right" position="top right">
|
|
2801
|
+
...
|
|
2802
|
+
</dialog></code></pre>
|
|
2803
|
+
|
|
2804
|
+
<h4>Bottom Left</h4>
|
|
2805
|
+
<hstack>
|
|
2806
|
+
<fig-button id="popup-open-bottom-left">Open Bottom Left</fig-button>
|
|
2807
|
+
<fig-button id="popup-close-bottom-left"
|
|
2808
|
+
variant="secondary">Close</fig-button>
|
|
2809
|
+
</hstack>
|
|
2810
|
+
<dialog id="popup-bottom-left"
|
|
2811
|
+
is="fig-popup"
|
|
2812
|
+
anchor="#popup-open-bottom-left"
|
|
2813
|
+
position="bottom left">
|
|
2814
|
+
<vstack style="min-width: 10rem;">
|
|
2815
|
+
<strong style="padding: 0 var(--spacer-1);">Bottom Left</strong>
|
|
2816
|
+
<fig-checkbox label="Snap to pixel grid"></fig-checkbox>
|
|
2817
|
+
</vstack>
|
|
2818
|
+
</dialog>
|
|
2819
|
+
<pre><code><fig-button id="popup-open-bottom-left">Open Bottom Left</fig-button>
|
|
2820
|
+
<dialog is="fig-popup" anchor="#popup-open-bottom-left" position="bottom left">
|
|
2821
|
+
...
|
|
2822
|
+
</dialog></code></pre>
|
|
2823
|
+
|
|
2824
|
+
<h4>Bottom Right</h4>
|
|
2825
|
+
<hstack>
|
|
2826
|
+
<fig-button id="popup-open-bottom-right">Open Bottom Right</fig-button>
|
|
2827
|
+
<fig-button id="popup-close-bottom-right"
|
|
2828
|
+
variant="secondary">Close</fig-button>
|
|
2829
|
+
</hstack>
|
|
2830
|
+
<dialog id="popup-bottom-right"
|
|
2831
|
+
is="fig-popup"
|
|
2832
|
+
anchor="#popup-open-bottom-right"
|
|
2833
|
+
position="bottom right">
|
|
2834
|
+
<vstack style="min-width: 10rem;">
|
|
2835
|
+
<strong style="padding: 0 var(--spacer-1);">Bottom Right</strong>
|
|
2836
|
+
<fig-dropdown>
|
|
2837
|
+
<option>Normal</option>
|
|
2838
|
+
<option>Multiply</option>
|
|
2839
|
+
<option>Screen</option>
|
|
2840
|
+
</fig-dropdown>
|
|
2841
|
+
</vstack>
|
|
2842
|
+
</dialog>
|
|
2843
|
+
<pre><code><fig-button id="popup-open-bottom-right">Open Bottom Right</fig-button>
|
|
2844
|
+
<dialog is="fig-popup" anchor="#popup-open-bottom-right" position="bottom right">
|
|
2845
|
+
...
|
|
2846
|
+
</dialog></code></pre>
|
|
2847
|
+
|
|
2848
|
+
<h4>Center Right (Vertical Centering)</h4>
|
|
2849
|
+
<hstack>
|
|
2850
|
+
<fig-button id="popup-open-center-right">Open Center Right</fig-button>
|
|
2851
|
+
<fig-button id="popup-close-center-right"
|
|
2852
|
+
variant="secondary">Close</fig-button>
|
|
2853
|
+
</hstack>
|
|
2854
|
+
<dialog id="popup-center-right"
|
|
2855
|
+
is="fig-popup"
|
|
2856
|
+
anchor="#popup-open-center-right"
|
|
2857
|
+
position="center right"
|
|
2858
|
+
offset="12 8">
|
|
2859
|
+
<vstack style="min-width: 11rem;">
|
|
2860
|
+
<strong style="padding: 0 var(--spacer-1);">Center Right</strong>
|
|
2861
|
+
<fig-input-text placeholder="Vertically centered"></fig-input-text>
|
|
2862
|
+
</vstack>
|
|
2863
|
+
</dialog>
|
|
2864
|
+
<pre><code><fig-button id="popup-open-center-right">Open Center Right</fig-button>
|
|
2865
|
+
<dialog is="fig-popup" anchor="#popup-open-center-right" position="center right" offset="12 8">
|
|
2866
|
+
...
|
|
2867
|
+
</dialog></code></pre>
|
|
2868
|
+
|
|
2869
|
+
<h4>Center Left (Vertical Centering)</h4>
|
|
2870
|
+
<hstack>
|
|
2871
|
+
<fig-button id="popup-open-center-left">Open Center Left</fig-button>
|
|
2872
|
+
<fig-button id="popup-close-center-left"
|
|
2873
|
+
variant="secondary">Close</fig-button>
|
|
2874
|
+
</hstack>
|
|
2875
|
+
<dialog id="popup-center-left"
|
|
2876
|
+
is="fig-popup"
|
|
2877
|
+
anchor="#popup-open-center-left"
|
|
2878
|
+
position="center left"
|
|
2879
|
+
offset="12 8">
|
|
2880
|
+
<vstack style="min-width: 11rem;">
|
|
2881
|
+
<strong style="padding: 0 var(--spacer-1);">Center Left</strong>
|
|
2882
|
+
<fig-input-text placeholder="Vertically centered"></fig-input-text>
|
|
2883
|
+
</vstack>
|
|
2884
|
+
</dialog>
|
|
2885
|
+
<pre><code><fig-button id="popup-open-center-left">Open Center Left</fig-button>
|
|
2886
|
+
<dialog is="fig-popup" anchor="#popup-open-center-left" position="center left" offset="12 8">
|
|
2887
|
+
...
|
|
2888
|
+
</dialog></code></pre>
|
|
2889
|
+
|
|
2890
|
+
<h3>Single-Value Position Shorthand</h3>
|
|
2891
|
+
<p class="description">Single values are supported for directional shorthand (for example:
|
|
2892
|
+
<code>top</code>, <code>right</code>).</p>
|
|
2893
|
+
|
|
2894
|
+
<h4>Top (left edges aligned)</h4>
|
|
2895
|
+
<hstack>
|
|
2896
|
+
<fig-button id="popup-open-top-single">Open Top (single)</fig-button>
|
|
2897
|
+
<fig-button id="popup-close-top-single"
|
|
2898
|
+
variant="secondary">Close</fig-button>
|
|
2899
|
+
</hstack>
|
|
2900
|
+
<dialog id="popup-top-single"
|
|
2901
|
+
is="fig-popup"
|
|
2902
|
+
anchor="#popup-open-top-single"
|
|
2903
|
+
position="top"
|
|
2904
|
+
offset="12 8">
|
|
2905
|
+
<vstack style="min-width: 11rem;">
|
|
2906
|
+
<strong style="padding: 0 var(--spacer-1);">Top Shorthand</strong>
|
|
2907
|
+
<fig-input-text placeholder="position='top'"></fig-input-text>
|
|
2908
|
+
</vstack>
|
|
2909
|
+
</dialog>
|
|
2910
|
+
<pre><code><fig-button id="popup-open-top-single">Open Top (single)</fig-button>
|
|
2911
|
+
<dialog is="fig-popup" anchor="#popup-open-top-single" position="top" offset="12 8">
|
|
2912
|
+
...
|
|
2913
|
+
</dialog></code></pre>
|
|
2914
|
+
|
|
2915
|
+
<h4>Right (top edges aligned)</h4>
|
|
2916
|
+
<hstack>
|
|
2917
|
+
<fig-button id="popup-open-right-single">Open Right (single)</fig-button>
|
|
2918
|
+
<fig-button id="popup-close-right-single"
|
|
2919
|
+
variant="secondary">Close</fig-button>
|
|
2920
|
+
</hstack>
|
|
2921
|
+
<dialog id="popup-right-single"
|
|
2922
|
+
is="fig-popup"
|
|
2923
|
+
anchor="#popup-open-right-single"
|
|
2924
|
+
position="right"
|
|
2925
|
+
offset="12 8">
|
|
2926
|
+
<vstack style="min-width: 11rem;">
|
|
2927
|
+
<strong style="padding: 0 var(--spacer-1);">Right Shorthand</strong>
|
|
2928
|
+
<fig-input-text placeholder="position='right'"></fig-input-text>
|
|
2929
|
+
</vstack>
|
|
2930
|
+
</dialog>
|
|
2931
|
+
<pre><code><fig-button id="popup-open-right-single">Open Right (single)</fig-button>
|
|
2932
|
+
<dialog is="fig-popup" anchor="#popup-open-right-single" position="right" offset="12 8">
|
|
2933
|
+
...
|
|
2934
|
+
</dialog></code></pre>
|
|
2935
|
+
</section>
|
|
2936
|
+
<hr>
|
|
2937
|
+
|
|
2638
2938
|
<!-- Radio -->
|
|
2639
2939
|
<section id="radio">
|
|
2640
2940
|
<h2>Radio</h2>
|
|
@@ -3871,6 +4171,74 @@ button.addEventListener('click', () => {
|
|
|
3871
4171
|
setTheme(true);
|
|
3872
4172
|
});
|
|
3873
4173
|
|
|
4174
|
+
// FigPopup demos
|
|
4175
|
+
const popupDefault = document.getElementById('popup-default');
|
|
4176
|
+
const popupPreferred = document.getElementById('popup-preferred');
|
|
4177
|
+
const popupProp = document.getElementById('popup-prop');
|
|
4178
|
+
const popupPopover = document.getElementById('popup-popover');
|
|
4179
|
+
const popupThemeControl = document.getElementById('popup-theme-control');
|
|
4180
|
+
|
|
4181
|
+
document.getElementById('popup-open-default')?.addEventListener('click', () => {
|
|
4182
|
+
popupDefault?.setAttribute('open', 'true');
|
|
4183
|
+
});
|
|
4184
|
+
document.getElementById('popup-close-default')?.addEventListener('click', () => {
|
|
4185
|
+
popupDefault?.removeAttribute('open');
|
|
4186
|
+
});
|
|
4187
|
+
|
|
4188
|
+
document.getElementById('popup-open-preferred')?.addEventListener('click', () => {
|
|
4189
|
+
popupPreferred?.setAttribute('open', 'true');
|
|
4190
|
+
});
|
|
4191
|
+
document.getElementById('popup-close-preferred')?.addEventListener('click', () => {
|
|
4192
|
+
popupPreferred?.removeAttribute('open');
|
|
4193
|
+
});
|
|
4194
|
+
|
|
4195
|
+
document.getElementById('popup-open-prop')?.addEventListener('click', () => {
|
|
4196
|
+
if (popupProp) popupProp.open = true;
|
|
4197
|
+
});
|
|
4198
|
+
document.getElementById('popup-close-prop')?.addEventListener('click', () => {
|
|
4199
|
+
if (popupProp) popupProp.open = false;
|
|
4200
|
+
});
|
|
4201
|
+
|
|
4202
|
+
popupThemeControl?.addEventListener('click', () => {
|
|
4203
|
+
if (!popupPopover) return;
|
|
4204
|
+
if (document.getElementById('popup-theme-default')?.hasAttribute('selected')) {
|
|
4205
|
+
popupPopover.removeAttribute('theme');
|
|
4206
|
+
return;
|
|
4207
|
+
}
|
|
4208
|
+
if (document.getElementById('popup-theme-dark')?.hasAttribute('selected')) {
|
|
4209
|
+
popupPopover.setAttribute('theme', 'dark');
|
|
4210
|
+
return;
|
|
4211
|
+
}
|
|
4212
|
+
if (document.getElementById('popup-theme-light')?.hasAttribute('selected')) {
|
|
4213
|
+
popupPopover.setAttribute('theme', 'light');
|
|
4214
|
+
return;
|
|
4215
|
+
}
|
|
4216
|
+
popupPopover.setAttribute('theme', 'menu');
|
|
4217
|
+
});
|
|
4218
|
+
|
|
4219
|
+
const popupExamples = [
|
|
4220
|
+
['popup-open-popover', 'popup-close-popover', 'popup-popover'],
|
|
4221
|
+
['popup-open-top-left', 'popup-close-top-left', 'popup-top-left'],
|
|
4222
|
+
['popup-open-top-center', 'popup-close-top-center', 'popup-top-center'],
|
|
4223
|
+
['popup-open-top-right', 'popup-close-top-right', 'popup-top-right'],
|
|
4224
|
+
['popup-open-bottom-left', 'popup-close-bottom-left', 'popup-bottom-left'],
|
|
4225
|
+
['popup-open-bottom-right', 'popup-close-bottom-right', 'popup-bottom-right'],
|
|
4226
|
+
['popup-open-center-right', 'popup-close-center-right', 'popup-center-right'],
|
|
4227
|
+
['popup-open-center-left', 'popup-close-center-left', 'popup-center-left'],
|
|
4228
|
+
['popup-open-top-single', 'popup-close-top-single', 'popup-top-single'],
|
|
4229
|
+
['popup-open-right-single', 'popup-close-right-single', 'popup-right-single'],
|
|
4230
|
+
];
|
|
4231
|
+
|
|
4232
|
+
popupExamples.forEach(([openId, closeId, popupId]) => {
|
|
4233
|
+
const popup = document.getElementById(popupId);
|
|
4234
|
+
document.getElementById(openId)?.addEventListener('click', () => {
|
|
4235
|
+
popup?.setAttribute('open', 'true');
|
|
4236
|
+
});
|
|
4237
|
+
document.getElementById(closeId)?.addEventListener('click', () => {
|
|
4238
|
+
popup?.removeAttribute('open');
|
|
4239
|
+
});
|
|
4240
|
+
});
|
|
4241
|
+
|
|
3874
4242
|
// Listen for system preference changes
|
|
3875
4243
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
|
3876
4244
|
if (!localStorage.getItem('theme')) {
|
package/package.json
CHANGED