@rogieking/figui3 2.27.0 → 2.29.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/README.md +0 -23
- package/components.css +250 -25
- package/dist/fig.js +367 -0
- package/fig.js +920 -316
- package/index.html +8 -78
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -175,6 +175,8 @@ class FigDropdown extends HTMLElement {
|
|
|
175
175
|
#selectedValue = null; // Stores last selected value for dropdown type
|
|
176
176
|
#boundHandleSelectInput;
|
|
177
177
|
#boundHandleSelectChange;
|
|
178
|
+
#selectedContentEnabled = false;
|
|
179
|
+
#selectedContentEl = null;
|
|
178
180
|
|
|
179
181
|
get label() {
|
|
180
182
|
return this.#label;
|
|
@@ -191,6 +193,52 @@ class FigDropdown extends HTMLElement {
|
|
|
191
193
|
this.#boundHandleSelectChange = this.#handleSelectChange.bind(this);
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
#supportsSelectedContent() {
|
|
197
|
+
if (typeof CSS === "undefined" || typeof CSS.supports !== "function")
|
|
198
|
+
return false;
|
|
199
|
+
try {
|
|
200
|
+
return (
|
|
201
|
+
CSS.supports("appearance: base-select") &&
|
|
202
|
+
CSS.supports("selector(::picker(select))")
|
|
203
|
+
);
|
|
204
|
+
} catch {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#enableSelectedContentIfNeeded() {
|
|
210
|
+
const experimental = this.getAttribute("experimental") || "";
|
|
211
|
+
const wantsModern = experimental
|
|
212
|
+
.split(/\s+/)
|
|
213
|
+
.filter(Boolean)
|
|
214
|
+
.includes("modern");
|
|
215
|
+
|
|
216
|
+
if (!wantsModern || !this.#supportsSelectedContent()) {
|
|
217
|
+
this.#selectedContentEnabled = false;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const button = document.createElement("button");
|
|
222
|
+
button.setAttribute("type", "button");
|
|
223
|
+
button.setAttribute("aria-hidden", "true");
|
|
224
|
+
const selected = document.createElement("selectedcontent");
|
|
225
|
+
button.appendChild(selected);
|
|
226
|
+
this.select.appendChild(button);
|
|
227
|
+
this.#selectedContentEnabled = true;
|
|
228
|
+
this.#selectedContentEl = selected;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#syncSelectedContent() {
|
|
232
|
+
if (!this.#selectedContentEl) return;
|
|
233
|
+
const selectedOption = this.select.selectedOptions?.[0];
|
|
234
|
+
if (!selectedOption) {
|
|
235
|
+
this.#selectedContentEl.textContent = "";
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
// Fallback mirror for browsers that don't auto-project selectedcontent reliably.
|
|
239
|
+
this.#selectedContentEl.innerHTML = selectedOption.innerHTML;
|
|
240
|
+
}
|
|
241
|
+
|
|
194
242
|
#addEventListeners() {
|
|
195
243
|
this.select.addEventListener("input", this.#boundHandleSelectInput);
|
|
196
244
|
this.select.addEventListener("change", this.#boundHandleSelectChange);
|
|
@@ -199,7 +247,7 @@ class FigDropdown extends HTMLElement {
|
|
|
199
247
|
#hasPersistentControl(optionEl) {
|
|
200
248
|
if (!optionEl || !(optionEl instanceof Element)) return false;
|
|
201
249
|
return !!optionEl.querySelector(
|
|
202
|
-
'fig-checkbox, fig-switch, input[type="checkbox"]'
|
|
250
|
+
'fig-checkbox, fig-switch, input[type="checkbox"]',
|
|
203
251
|
);
|
|
204
252
|
}
|
|
205
253
|
|
|
@@ -235,6 +283,8 @@ class FigDropdown extends HTMLElement {
|
|
|
235
283
|
this.select.firstChild.remove();
|
|
236
284
|
}
|
|
237
285
|
|
|
286
|
+
this.#enableSelectedContentIfNeeded();
|
|
287
|
+
|
|
238
288
|
if (this.type === "dropdown") {
|
|
239
289
|
const hiddenOption = document.createElement("option");
|
|
240
290
|
hiddenOption.setAttribute("hidden", "true");
|
|
@@ -248,6 +298,7 @@ class FigDropdown extends HTMLElement {
|
|
|
248
298
|
}
|
|
249
299
|
});
|
|
250
300
|
this.#syncSelectedValue(this.value);
|
|
301
|
+
this.#syncSelectedContent();
|
|
251
302
|
if (this.type === "dropdown") {
|
|
252
303
|
this.select.selectedIndex = -1;
|
|
253
304
|
}
|
|
@@ -269,12 +320,13 @@ class FigDropdown extends HTMLElement {
|
|
|
269
320
|
this.#selectedValue = selectedValue;
|
|
270
321
|
}
|
|
271
322
|
this.setAttribute("value", selectedValue);
|
|
323
|
+
this.#syncSelectedContent();
|
|
272
324
|
this.dispatchEvent(
|
|
273
325
|
new CustomEvent("input", {
|
|
274
326
|
detail: selectedValue,
|
|
275
327
|
bubbles: true,
|
|
276
328
|
composed: true,
|
|
277
|
-
})
|
|
329
|
+
}),
|
|
278
330
|
);
|
|
279
331
|
}
|
|
280
332
|
|
|
@@ -295,12 +347,13 @@ class FigDropdown extends HTMLElement {
|
|
|
295
347
|
if (this.type === "dropdown") {
|
|
296
348
|
this.select.selectedIndex = -1;
|
|
297
349
|
}
|
|
350
|
+
this.#syncSelectedContent();
|
|
298
351
|
this.dispatchEvent(
|
|
299
352
|
new CustomEvent("change", {
|
|
300
353
|
detail: selectedValue,
|
|
301
354
|
bubbles: true,
|
|
302
355
|
composed: true,
|
|
303
|
-
})
|
|
356
|
+
}),
|
|
304
357
|
);
|
|
305
358
|
}
|
|
306
359
|
|
|
@@ -325,7 +378,7 @@ class FigDropdown extends HTMLElement {
|
|
|
325
378
|
this.setAttribute("value", value);
|
|
326
379
|
}
|
|
327
380
|
static get observedAttributes() {
|
|
328
|
-
return ["value", "type"];
|
|
381
|
+
return ["value", "type", "experimental"];
|
|
329
382
|
}
|
|
330
383
|
#syncSelectedValue(value) {
|
|
331
384
|
// For dropdown type, don't sync the visual selection - it should always show the hidden placeholder
|
|
@@ -339,6 +392,7 @@ class FigDropdown extends HTMLElement {
|
|
|
339
392
|
}
|
|
340
393
|
});
|
|
341
394
|
}
|
|
395
|
+
this.#syncSelectedContent();
|
|
342
396
|
}
|
|
343
397
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
344
398
|
if (name === "value") {
|
|
@@ -347,6 +401,9 @@ class FigDropdown extends HTMLElement {
|
|
|
347
401
|
if (name === "type") {
|
|
348
402
|
this.type = newValue;
|
|
349
403
|
}
|
|
404
|
+
if (name === "experimental") {
|
|
405
|
+
this.slotChange();
|
|
406
|
+
}
|
|
350
407
|
if (name === "label") {
|
|
351
408
|
this.#label = newValue;
|
|
352
409
|
this.select.setAttribute("aria-label", this.#label);
|
|
@@ -395,7 +452,7 @@ class FigTooltip extends HTMLElement {
|
|
|
395
452
|
document.removeEventListener(
|
|
396
453
|
"mousedown",
|
|
397
454
|
this.#boundHideOnChromeOpen,
|
|
398
|
-
true
|
|
455
|
+
true,
|
|
399
456
|
);
|
|
400
457
|
// Disconnect mutation observer
|
|
401
458
|
this.#stopObserving();
|
|
@@ -404,7 +461,7 @@ class FigTooltip extends HTMLElement {
|
|
|
404
461
|
if (this.action === "click") {
|
|
405
462
|
document.body.removeEventListener(
|
|
406
463
|
"click",
|
|
407
|
-
this.#boundHidePopupOutsideClick
|
|
464
|
+
this.#boundHidePopupOutsideClick,
|
|
408
465
|
);
|
|
409
466
|
}
|
|
410
467
|
|
|
@@ -470,7 +527,7 @@ class FigTooltip extends HTMLElement {
|
|
|
470
527
|
if (this.action === "click") {
|
|
471
528
|
document.body.removeEventListener(
|
|
472
529
|
"click",
|
|
473
|
-
this.#boundHidePopupOutsideClick
|
|
530
|
+
this.#boundHidePopupOutsideClick,
|
|
474
531
|
);
|
|
475
532
|
}
|
|
476
533
|
}
|
|
@@ -488,7 +545,7 @@ class FigTooltip extends HTMLElement {
|
|
|
488
545
|
this.addEventListener("pointerenter", this.showDelayedPopup.bind(this));
|
|
489
546
|
this.addEventListener(
|
|
490
547
|
"pointerleave",
|
|
491
|
-
this.#handlePointerLeave.bind(this)
|
|
548
|
+
this.#handlePointerLeave.bind(this),
|
|
492
549
|
);
|
|
493
550
|
}
|
|
494
551
|
// Touch support for mobile hover simulation
|
|
@@ -535,7 +592,8 @@ class FigTooltip extends HTMLElement {
|
|
|
535
592
|
showDelayedPopup() {
|
|
536
593
|
this.render();
|
|
537
594
|
clearTimeout(this.timeout);
|
|
538
|
-
const warm =
|
|
595
|
+
const warm =
|
|
596
|
+
Date.now() - FigTooltip.#lastShownAt < FigTooltip.#warmupWindow;
|
|
539
597
|
const effectiveDelay = warm ? 0 : this.delay;
|
|
540
598
|
this.timeout = setTimeout(this.showPopup.bind(this), effectiveDelay);
|
|
541
599
|
}
|
|
@@ -740,29 +798,6 @@ class FigTooltip extends HTMLElement {
|
|
|
740
798
|
|
|
741
799
|
customElements.define("fig-tooltip", FigTooltip);
|
|
742
800
|
|
|
743
|
-
/* Popover */
|
|
744
|
-
/**
|
|
745
|
-
* A custom popover element extending FigTooltip.
|
|
746
|
-
* @attr {string} action - The trigger action: "click" (default) or "hover"
|
|
747
|
-
* @attr {string} size - The size of the popover
|
|
748
|
-
*/
|
|
749
|
-
class FigPopover extends FigTooltip {
|
|
750
|
-
constructor() {
|
|
751
|
-
super();
|
|
752
|
-
this.action = this.getAttribute("action") || "click";
|
|
753
|
-
this.delay = parseInt(this.getAttribute("delay")) || 0;
|
|
754
|
-
}
|
|
755
|
-
render() {
|
|
756
|
-
this.popup = this.popup || this.querySelector("[popover]");
|
|
757
|
-
this.popup.setAttribute("class", "fig-popover");
|
|
758
|
-
this.popup.style.position = "fixed";
|
|
759
|
-
this.popup.style.visibility = "hidden";
|
|
760
|
-
this.popup.style.display = "inline-flex";
|
|
761
|
-
document.body.append(this.popup);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
customElements.define("fig-popover", FigPopover);
|
|
765
|
-
|
|
766
801
|
/* Dialog */
|
|
767
802
|
/**
|
|
768
803
|
* A custom dialog element for modal and non-modal dialogs.
|
|
@@ -1080,12 +1115,17 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1080
1115
|
#boundPointerDown;
|
|
1081
1116
|
#boundPointerMove;
|
|
1082
1117
|
#boundPointerUp;
|
|
1118
|
+
#wasDragged = false;
|
|
1083
1119
|
|
|
1084
1120
|
constructor() {
|
|
1085
1121
|
super();
|
|
1086
1122
|
this.#boundReposition = this.#queueReposition.bind(this);
|
|
1087
1123
|
this.#boundScroll = (e) => {
|
|
1088
|
-
if (
|
|
1124
|
+
if (
|
|
1125
|
+
this.open &&
|
|
1126
|
+
!this.contains(e.target) &&
|
|
1127
|
+
this.#shouldAutoReposition()
|
|
1128
|
+
) {
|
|
1089
1129
|
this.#positionPopup();
|
|
1090
1130
|
}
|
|
1091
1131
|
};
|
|
@@ -1096,7 +1136,18 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1096
1136
|
}
|
|
1097
1137
|
|
|
1098
1138
|
static get observedAttributes() {
|
|
1099
|
-
return [
|
|
1139
|
+
return [
|
|
1140
|
+
"open",
|
|
1141
|
+
"anchor",
|
|
1142
|
+
"position",
|
|
1143
|
+
"offset",
|
|
1144
|
+
"variant",
|
|
1145
|
+
"theme",
|
|
1146
|
+
"drag",
|
|
1147
|
+
"handle",
|
|
1148
|
+
"autoresize",
|
|
1149
|
+
"viewport-margin",
|
|
1150
|
+
];
|
|
1100
1151
|
}
|
|
1101
1152
|
|
|
1102
1153
|
get open() {
|
|
@@ -1167,7 +1218,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1167
1218
|
document.removeEventListener(
|
|
1168
1219
|
"pointerdown",
|
|
1169
1220
|
this.#boundOutsidePointerDown,
|
|
1170
|
-
true
|
|
1221
|
+
true,
|
|
1171
1222
|
);
|
|
1172
1223
|
if (this.#rafId !== null) {
|
|
1173
1224
|
cancelAnimationFrame(this.#rafId);
|
|
@@ -1223,7 +1274,11 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1223
1274
|
}
|
|
1224
1275
|
|
|
1225
1276
|
this.#setupObservers();
|
|
1226
|
-
document.addEventListener(
|
|
1277
|
+
document.addEventListener(
|
|
1278
|
+
"pointerdown",
|
|
1279
|
+
this.#boundOutsidePointerDown,
|
|
1280
|
+
true,
|
|
1281
|
+
);
|
|
1227
1282
|
this.#wasDragged = false;
|
|
1228
1283
|
this.#queueReposition();
|
|
1229
1284
|
this.#isPopupActive = true;
|
|
@@ -1242,7 +1297,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1242
1297
|
document.removeEventListener(
|
|
1243
1298
|
"pointerdown",
|
|
1244
1299
|
this.#boundOutsidePointerDown,
|
|
1245
|
-
true
|
|
1300
|
+
true,
|
|
1246
1301
|
);
|
|
1247
1302
|
|
|
1248
1303
|
if (super.open) {
|
|
@@ -1283,7 +1338,10 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1283
1338
|
}
|
|
1284
1339
|
|
|
1285
1340
|
window.addEventListener("resize", this.#boundReposition);
|
|
1286
|
-
window.addEventListener("scroll", this.#boundScroll, {
|
|
1341
|
+
window.addEventListener("scroll", this.#boundScroll, {
|
|
1342
|
+
capture: true,
|
|
1343
|
+
passive: true,
|
|
1344
|
+
});
|
|
1287
1345
|
}
|
|
1288
1346
|
|
|
1289
1347
|
#teardownObservers() {
|
|
@@ -1300,7 +1358,10 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1300
1358
|
this.#mutationObserver = null;
|
|
1301
1359
|
}
|
|
1302
1360
|
window.removeEventListener("resize", this.#boundReposition);
|
|
1303
|
-
window.removeEventListener("scroll", this.#boundScroll, {
|
|
1361
|
+
window.removeEventListener("scroll", this.#boundScroll, {
|
|
1362
|
+
capture: true,
|
|
1363
|
+
passive: true,
|
|
1364
|
+
});
|
|
1304
1365
|
}
|
|
1305
1366
|
|
|
1306
1367
|
#handleOutsidePointerDown(event) {
|
|
@@ -1351,15 +1412,31 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1351
1412
|
|
|
1352
1413
|
#isInteractiveElement(element) {
|
|
1353
1414
|
const interactiveSelectors = [
|
|
1354
|
-
"input",
|
|
1355
|
-
"
|
|
1356
|
-
|
|
1415
|
+
"input",
|
|
1416
|
+
"button",
|
|
1417
|
+
"select",
|
|
1418
|
+
"textarea",
|
|
1419
|
+
"a",
|
|
1420
|
+
"label",
|
|
1421
|
+
"details",
|
|
1422
|
+
"summary",
|
|
1423
|
+
'[contenteditable="true"]',
|
|
1424
|
+
"[tabindex]",
|
|
1357
1425
|
];
|
|
1358
1426
|
|
|
1359
1427
|
const nonInteractiveFigElements = [
|
|
1360
|
-
"FIG-HEADER",
|
|
1361
|
-
"FIG-
|
|
1362
|
-
"FIG-
|
|
1428
|
+
"FIG-HEADER",
|
|
1429
|
+
"FIG-DIALOG",
|
|
1430
|
+
"FIG-POPUP",
|
|
1431
|
+
"FIG-FIELD",
|
|
1432
|
+
"FIG-TOOLTIP",
|
|
1433
|
+
"FIG-CONTENT",
|
|
1434
|
+
"FIG-TABS",
|
|
1435
|
+
"FIG-TAB",
|
|
1436
|
+
"FIG-POPOVER",
|
|
1437
|
+
"FIG-SHIMMER",
|
|
1438
|
+
"FIG-LAYER",
|
|
1439
|
+
"FIG-FILL-PICKER",
|
|
1363
1440
|
];
|
|
1364
1441
|
|
|
1365
1442
|
const isInteractive = (el) =>
|
|
@@ -1559,17 +1636,49 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1559
1636
|
|
|
1560
1637
|
#parseViewportMargins() {
|
|
1561
1638
|
const raw = (this.getAttribute("viewport-margin") || "8").trim();
|
|
1562
|
-
const tokens = raw
|
|
1639
|
+
const tokens = raw
|
|
1640
|
+
.split(/\s+/)
|
|
1641
|
+
.map(Number)
|
|
1642
|
+
.filter((n) => !Number.isNaN(n));
|
|
1563
1643
|
const d = 8;
|
|
1564
1644
|
if (tokens.length === 0) return { top: d, right: d, bottom: d, left: d };
|
|
1565
|
-
if (tokens.length === 1)
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1645
|
+
if (tokens.length === 1)
|
|
1646
|
+
return {
|
|
1647
|
+
top: tokens[0],
|
|
1648
|
+
right: tokens[0],
|
|
1649
|
+
bottom: tokens[0],
|
|
1650
|
+
left: tokens[0],
|
|
1651
|
+
};
|
|
1652
|
+
if (tokens.length === 2)
|
|
1653
|
+
return {
|
|
1654
|
+
top: tokens[0],
|
|
1655
|
+
right: tokens[1],
|
|
1656
|
+
bottom: tokens[0],
|
|
1657
|
+
left: tokens[1],
|
|
1658
|
+
};
|
|
1659
|
+
if (tokens.length === 3)
|
|
1660
|
+
return {
|
|
1661
|
+
top: tokens[0],
|
|
1662
|
+
right: tokens[1],
|
|
1663
|
+
bottom: tokens[2],
|
|
1664
|
+
left: tokens[1],
|
|
1665
|
+
};
|
|
1666
|
+
return {
|
|
1667
|
+
top: tokens[0],
|
|
1668
|
+
right: tokens[1],
|
|
1669
|
+
bottom: tokens[2],
|
|
1670
|
+
left: tokens[3],
|
|
1671
|
+
};
|
|
1569
1672
|
}
|
|
1570
1673
|
|
|
1571
1674
|
#getPlacementCandidates(vertical, horizontal, shorthand) {
|
|
1572
|
-
const opp = {
|
|
1675
|
+
const opp = {
|
|
1676
|
+
top: "bottom",
|
|
1677
|
+
bottom: "top",
|
|
1678
|
+
left: "right",
|
|
1679
|
+
right: "left",
|
|
1680
|
+
center: "center",
|
|
1681
|
+
};
|
|
1573
1682
|
|
|
1574
1683
|
if (shorthand) {
|
|
1575
1684
|
const isHorizontal = shorthand === "left" || shorthand === "right";
|
|
@@ -1612,21 +1721,30 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1612
1721
|
];
|
|
1613
1722
|
}
|
|
1614
1723
|
|
|
1615
|
-
#computeCoords(
|
|
1724
|
+
#computeCoords(
|
|
1725
|
+
anchorRect,
|
|
1726
|
+
popupRect,
|
|
1727
|
+
vertical,
|
|
1728
|
+
horizontal,
|
|
1729
|
+
offset,
|
|
1730
|
+
shorthand,
|
|
1731
|
+
) {
|
|
1616
1732
|
let top;
|
|
1617
1733
|
let left;
|
|
1618
1734
|
|
|
1619
1735
|
if (shorthand === "left" || shorthand === "right") {
|
|
1620
|
-
left =
|
|
1621
|
-
|
|
1622
|
-
|
|
1736
|
+
left =
|
|
1737
|
+
shorthand === "left"
|
|
1738
|
+
? anchorRect.left - popupRect.width - offset.xPx
|
|
1739
|
+
: anchorRect.right + offset.xPx;
|
|
1623
1740
|
top = anchorRect.top;
|
|
1624
1741
|
return { top, left };
|
|
1625
1742
|
}
|
|
1626
1743
|
if (shorthand === "top" || shorthand === "bottom") {
|
|
1627
|
-
top =
|
|
1628
|
-
|
|
1629
|
-
|
|
1744
|
+
top =
|
|
1745
|
+
shorthand === "top"
|
|
1746
|
+
? anchorRect.top - popupRect.height - offset.yPx
|
|
1747
|
+
: anchorRect.bottom + offset.yPx;
|
|
1630
1748
|
left = anchorRect.left;
|
|
1631
1749
|
return { top, left };
|
|
1632
1750
|
}
|
|
@@ -1733,8 +1851,14 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1733
1851
|
#clamp(coords, popupRect, m) {
|
|
1734
1852
|
const minLeft = m.left;
|
|
1735
1853
|
const minTop = m.top;
|
|
1736
|
-
const maxLeft = Math.max(
|
|
1737
|
-
|
|
1854
|
+
const maxLeft = Math.max(
|
|
1855
|
+
m.left,
|
|
1856
|
+
window.innerWidth - popupRect.width - m.right,
|
|
1857
|
+
);
|
|
1858
|
+
const maxTop = Math.max(
|
|
1859
|
+
m.top,
|
|
1860
|
+
window.innerHeight - popupRect.height - m.bottom,
|
|
1861
|
+
);
|
|
1738
1862
|
|
|
1739
1863
|
return {
|
|
1740
1864
|
left: Math.min(maxLeft, Math.max(minLeft, coords.left)),
|
|
@@ -1754,8 +1878,11 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1754
1878
|
if (!anchor) {
|
|
1755
1879
|
this.#updatePopoverBeak(null, popupRect, 0, 0, "top");
|
|
1756
1880
|
const centered = {
|
|
1757
|
-
left:
|
|
1758
|
-
|
|
1881
|
+
left:
|
|
1882
|
+
m.left + (window.innerWidth - m.right - m.left - popupRect.width) / 2,
|
|
1883
|
+
top:
|
|
1884
|
+
m.top +
|
|
1885
|
+
(window.innerHeight - m.bottom - m.top - popupRect.height) / 2,
|
|
1759
1886
|
};
|
|
1760
1887
|
const clamped = this.#clamp(centered, popupRect, m);
|
|
1761
1888
|
this.style.left = `${clamped.left}px`;
|
|
@@ -1764,24 +1891,44 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1764
1891
|
}
|
|
1765
1892
|
|
|
1766
1893
|
const anchorRect = anchor.getBoundingClientRect();
|
|
1767
|
-
const candidates = this.#getPlacementCandidates(
|
|
1894
|
+
const candidates = this.#getPlacementCandidates(
|
|
1895
|
+
vertical,
|
|
1896
|
+
horizontal,
|
|
1897
|
+
shorthand,
|
|
1898
|
+
);
|
|
1768
1899
|
let best = null;
|
|
1769
1900
|
let bestSide = "top";
|
|
1770
1901
|
let bestScore = Number.POSITIVE_INFINITY;
|
|
1771
1902
|
|
|
1772
1903
|
for (const { v, h, s } of candidates) {
|
|
1773
|
-
const coords = this.#computeCoords(
|
|
1904
|
+
const coords = this.#computeCoords(
|
|
1905
|
+
anchorRect,
|
|
1906
|
+
popupRect,
|
|
1907
|
+
v,
|
|
1908
|
+
h,
|
|
1909
|
+
offset,
|
|
1910
|
+
s,
|
|
1911
|
+
);
|
|
1774
1912
|
const placementSide = this.#getPlacementSide(v, h, s);
|
|
1775
1913
|
|
|
1776
1914
|
if (s) {
|
|
1777
1915
|
const clamped = this.#clamp(coords, popupRect, m);
|
|
1778
|
-
const primaryFits =
|
|
1779
|
-
|
|
1780
|
-
|
|
1916
|
+
const primaryFits =
|
|
1917
|
+
s === "left" || s === "right"
|
|
1918
|
+
? coords.left >= m.left &&
|
|
1919
|
+
coords.left + popupRect.width <= window.innerWidth - m.right
|
|
1920
|
+
: coords.top >= m.top &&
|
|
1921
|
+
coords.top + popupRect.height <= window.innerHeight - m.bottom;
|
|
1781
1922
|
if (primaryFits) {
|
|
1782
1923
|
this.style.left = `${clamped.left}px`;
|
|
1783
1924
|
this.style.top = `${clamped.top}px`;
|
|
1784
|
-
this.#updatePopoverBeak(
|
|
1925
|
+
this.#updatePopoverBeak(
|
|
1926
|
+
anchorRect,
|
|
1927
|
+
popupRect,
|
|
1928
|
+
clamped.left,
|
|
1929
|
+
clamped.top,
|
|
1930
|
+
placementSide,
|
|
1931
|
+
);
|
|
1785
1932
|
return;
|
|
1786
1933
|
}
|
|
1787
1934
|
const score = this.#overflowScore(coords, popupRect, m);
|
|
@@ -1794,7 +1941,13 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1794
1941
|
if (this.#fits(coords, popupRect, m)) {
|
|
1795
1942
|
this.style.left = `${coords.left}px`;
|
|
1796
1943
|
this.style.top = `${coords.top}px`;
|
|
1797
|
-
this.#updatePopoverBeak(
|
|
1944
|
+
this.#updatePopoverBeak(
|
|
1945
|
+
anchorRect,
|
|
1946
|
+
popupRect,
|
|
1947
|
+
coords.left,
|
|
1948
|
+
coords.top,
|
|
1949
|
+
placementSide,
|
|
1950
|
+
);
|
|
1798
1951
|
return;
|
|
1799
1952
|
}
|
|
1800
1953
|
const score = this.#overflowScore(coords, popupRect, m);
|
|
@@ -1809,7 +1962,13 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1809
1962
|
const clamped = this.#clamp(best || { left: 0, top: 0 }, popupRect, m);
|
|
1810
1963
|
this.style.left = `${clamped.left}px`;
|
|
1811
1964
|
this.style.top = `${clamped.top}px`;
|
|
1812
|
-
this.#updatePopoverBeak(
|
|
1965
|
+
this.#updatePopoverBeak(
|
|
1966
|
+
anchorRect,
|
|
1967
|
+
popupRect,
|
|
1968
|
+
clamped.left,
|
|
1969
|
+
clamped.top,
|
|
1970
|
+
bestSide,
|
|
1971
|
+
);
|
|
1813
1972
|
}
|
|
1814
1973
|
|
|
1815
1974
|
#queueReposition() {
|
|
@@ -1829,69 +1988,6 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1829
1988
|
}
|
|
1830
1989
|
customElements.define("fig-popup", FigPopup, { extends: "dialog" });
|
|
1831
1990
|
|
|
1832
|
-
/**
|
|
1833
|
-
* A popover element using the native Popover API.
|
|
1834
|
-
* @attr {string} trigger-action - The trigger action: "click" (default) or "hover"
|
|
1835
|
-
* @attr {number} delay - Delay in ms before showing on hover (default: 0)
|
|
1836
|
-
*/
|
|
1837
|
-
class FigPopover2 extends HTMLElement {
|
|
1838
|
-
#popover;
|
|
1839
|
-
#trigger;
|
|
1840
|
-
#id;
|
|
1841
|
-
#delay;
|
|
1842
|
-
#timeout;
|
|
1843
|
-
#action;
|
|
1844
|
-
|
|
1845
|
-
constructor() {
|
|
1846
|
-
super();
|
|
1847
|
-
}
|
|
1848
|
-
connectedCallback() {
|
|
1849
|
-
this.#popover = this.querySelector("[popover]");
|
|
1850
|
-
this.#trigger = this;
|
|
1851
|
-
this.#delay = Number(this.getAttribute("delay")) || 0;
|
|
1852
|
-
this.#action = this.getAttribute("trigger-action") || "click";
|
|
1853
|
-
this.#id = `tooltip-${figUniqueId()}`;
|
|
1854
|
-
if (this.#popover) {
|
|
1855
|
-
this.#popover.setAttribute("id", this.#id);
|
|
1856
|
-
this.#popover.setAttribute("role", "tooltip");
|
|
1857
|
-
this.#popover.setAttribute("popover", "manual");
|
|
1858
|
-
this.#popover.style["position-anchor"] = `--${this.#id}`;
|
|
1859
|
-
|
|
1860
|
-
this.#trigger.setAttribute("popovertarget", this.#id);
|
|
1861
|
-
this.#trigger.setAttribute("popovertargetaction", "toggle");
|
|
1862
|
-
this.#trigger.style["anchor-name"] = `--${this.#id}`;
|
|
1863
|
-
|
|
1864
|
-
if (this.#action === "hover") {
|
|
1865
|
-
this.#trigger.addEventListener("mouseover", this.handleOpen.bind(this));
|
|
1866
|
-
this.#trigger.addEventListener("mouseout", this.handleClose.bind(this));
|
|
1867
|
-
} else {
|
|
1868
|
-
this.#trigger.addEventListener("click", this.handleToggle.bind(this));
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
document.body.append(this.#popover);
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
handleClose() {
|
|
1876
|
-
clearTimeout(this.#timeout);
|
|
1877
|
-
this.#popover.hidePopover();
|
|
1878
|
-
}
|
|
1879
|
-
handleToggle() {
|
|
1880
|
-
if (this.#popover.matches(":popover-open")) {
|
|
1881
|
-
this.handleClose();
|
|
1882
|
-
} else {
|
|
1883
|
-
this.handleOpen();
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
handleOpen() {
|
|
1887
|
-
clearTimeout(this.#timeout);
|
|
1888
|
-
this.#timeout = setTimeout(() => {
|
|
1889
|
-
this.#popover.showPopover();
|
|
1890
|
-
}, this.#delay);
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
customElements.define("fig-popover-2", FigPopover2);
|
|
1894
|
-
|
|
1895
1991
|
/* Tabs */
|
|
1896
1992
|
/**
|
|
1897
1993
|
* A custom tab element for use within FigTabs.
|
|
@@ -2172,14 +2268,15 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
2172
2268
|
this.name = this.getAttribute("name") || "segmented-control";
|
|
2173
2269
|
this.addEventListener("click", this.handleClick.bind(this));
|
|
2174
2270
|
this.#applyDisabled(
|
|
2175
|
-
this.hasAttribute("disabled") &&
|
|
2271
|
+
this.hasAttribute("disabled") &&
|
|
2272
|
+
this.getAttribute("disabled") !== "false",
|
|
2176
2273
|
);
|
|
2177
2274
|
|
|
2178
2275
|
// Ensure at least one segment is selected (default to first)
|
|
2179
2276
|
requestAnimationFrame(() => {
|
|
2180
2277
|
const segments = this.querySelectorAll("fig-segment");
|
|
2181
2278
|
const hasSelected = Array.from(segments).some((s) =>
|
|
2182
|
-
s.hasAttribute("selected")
|
|
2279
|
+
s.hasAttribute("selected"),
|
|
2183
2280
|
);
|
|
2184
2281
|
if (!hasSelected && segments.length > 0) {
|
|
2185
2282
|
this.selectedSegment = segments[0];
|
|
@@ -2363,13 +2460,17 @@ class FigSlider extends HTMLElement {
|
|
|
2363
2460
|
this.input.addEventListener("input", this.#boundHandleInput);
|
|
2364
2461
|
this.input.removeEventListener("change", this.#boundHandleChange);
|
|
2365
2462
|
this.input.addEventListener("change", this.#boundHandleChange);
|
|
2366
|
-
this.input.addEventListener("pointerdown", () => {
|
|
2367
|
-
|
|
2463
|
+
this.input.addEventListener("pointerdown", () => {
|
|
2464
|
+
this.#isInteracting = true;
|
|
2465
|
+
});
|
|
2466
|
+
this.input.addEventListener("pointerup", () => {
|
|
2467
|
+
this.#isInteracting = false;
|
|
2468
|
+
});
|
|
2368
2469
|
|
|
2369
2470
|
if (this.default) {
|
|
2370
2471
|
this.style.setProperty(
|
|
2371
2472
|
"--default",
|
|
2372
|
-
this.#calculateNormal(this.default)
|
|
2473
|
+
this.#calculateNormal(this.default),
|
|
2373
2474
|
);
|
|
2374
2475
|
}
|
|
2375
2476
|
|
|
@@ -2379,7 +2480,7 @@ class FigSlider extends HTMLElement {
|
|
|
2379
2480
|
this.inputContainer.append(this.datalist);
|
|
2380
2481
|
this.datalist.setAttribute(
|
|
2381
2482
|
"id",
|
|
2382
|
-
this.datalist.getAttribute("id") || figUniqueId()
|
|
2483
|
+
this.datalist.getAttribute("id") || figUniqueId(),
|
|
2383
2484
|
);
|
|
2384
2485
|
this.input.setAttribute("list", this.datalist.getAttribute("id"));
|
|
2385
2486
|
} else if (this.type === "stepper") {
|
|
@@ -2404,7 +2505,7 @@ class FigSlider extends HTMLElement {
|
|
|
2404
2505
|
}
|
|
2405
2506
|
if (this.datalist) {
|
|
2406
2507
|
let defaultOption = this.datalist.querySelector(
|
|
2407
|
-
`option[value='${this.default}']
|
|
2508
|
+
`option[value='${this.default}']`,
|
|
2408
2509
|
);
|
|
2409
2510
|
if (defaultOption) {
|
|
2410
2511
|
defaultOption.setAttribute("default", "true");
|
|
@@ -2413,19 +2514,19 @@ class FigSlider extends HTMLElement {
|
|
|
2413
2514
|
if (this.figInputNumber) {
|
|
2414
2515
|
this.figInputNumber.removeEventListener(
|
|
2415
2516
|
"input",
|
|
2416
|
-
this.#boundHandleTextInput
|
|
2517
|
+
this.#boundHandleTextInput,
|
|
2417
2518
|
);
|
|
2418
2519
|
this.figInputNumber.addEventListener(
|
|
2419
2520
|
"input",
|
|
2420
|
-
this.#boundHandleTextInput
|
|
2521
|
+
this.#boundHandleTextInput,
|
|
2421
2522
|
);
|
|
2422
2523
|
this.figInputNumber.removeEventListener(
|
|
2423
2524
|
"change",
|
|
2424
|
-
this.#boundHandleTextChange
|
|
2525
|
+
this.#boundHandleTextChange,
|
|
2425
2526
|
);
|
|
2426
2527
|
this.figInputNumber.addEventListener(
|
|
2427
2528
|
"change",
|
|
2428
|
-
this.#boundHandleTextChange
|
|
2529
|
+
this.#boundHandleTextChange,
|
|
2429
2530
|
);
|
|
2430
2531
|
}
|
|
2431
2532
|
|
|
@@ -2445,11 +2546,11 @@ class FigSlider extends HTMLElement {
|
|
|
2445
2546
|
if (this.figInputNumber) {
|
|
2446
2547
|
this.figInputNumber.removeEventListener(
|
|
2447
2548
|
"input",
|
|
2448
|
-
this.#boundHandleTextInput
|
|
2549
|
+
this.#boundHandleTextInput,
|
|
2449
2550
|
);
|
|
2450
2551
|
this.figInputNumber.removeEventListener(
|
|
2451
2552
|
"change",
|
|
2452
|
-
this.#boundHandleTextChange
|
|
2553
|
+
this.#boundHandleTextChange,
|
|
2453
2554
|
);
|
|
2454
2555
|
}
|
|
2455
2556
|
}
|
|
@@ -2459,7 +2560,7 @@ class FigSlider extends HTMLElement {
|
|
|
2459
2560
|
this.value = this.input.value = this.figInputNumber.value;
|
|
2460
2561
|
this.#syncProperties();
|
|
2461
2562
|
this.dispatchEvent(
|
|
2462
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
2563
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
2463
2564
|
);
|
|
2464
2565
|
}
|
|
2465
2566
|
}
|
|
@@ -2489,7 +2590,7 @@ class FigSlider extends HTMLElement {
|
|
|
2489
2590
|
#handleInput() {
|
|
2490
2591
|
this.#syncValue();
|
|
2491
2592
|
this.dispatchEvent(
|
|
2492
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
2593
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
2493
2594
|
);
|
|
2494
2595
|
}
|
|
2495
2596
|
|
|
@@ -2497,7 +2598,7 @@ class FigSlider extends HTMLElement {
|
|
|
2497
2598
|
this.#isInteracting = false;
|
|
2498
2599
|
this.#syncValue();
|
|
2499
2600
|
this.dispatchEvent(
|
|
2500
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
2601
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
2501
2602
|
);
|
|
2502
2603
|
}
|
|
2503
2604
|
|
|
@@ -2506,7 +2607,7 @@ class FigSlider extends HTMLElement {
|
|
|
2506
2607
|
this.value = this.input.value = this.figInputNumber.value;
|
|
2507
2608
|
this.#syncProperties();
|
|
2508
2609
|
this.dispatchEvent(
|
|
2509
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
2610
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
2510
2611
|
);
|
|
2511
2612
|
}
|
|
2512
2613
|
}
|
|
@@ -2726,10 +2827,10 @@ class FigInputText extends HTMLElement {
|
|
|
2726
2827
|
this.value = value;
|
|
2727
2828
|
this.input.value = valueTransformed;
|
|
2728
2829
|
this.dispatchEvent(
|
|
2729
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
2830
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
2730
2831
|
);
|
|
2731
2832
|
this.dispatchEvent(
|
|
2732
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
2833
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
2733
2834
|
);
|
|
2734
2835
|
}
|
|
2735
2836
|
#handleMouseMove(e) {
|
|
@@ -2777,13 +2878,13 @@ class FigInputText extends HTMLElement {
|
|
|
2777
2878
|
if (typeof this.min === "number") {
|
|
2778
2879
|
sanitized = Math.max(
|
|
2779
2880
|
transform ? this.#transformNumber(this.min) : this.min,
|
|
2780
|
-
sanitized
|
|
2881
|
+
sanitized,
|
|
2781
2882
|
);
|
|
2782
2883
|
}
|
|
2783
2884
|
if (typeof this.max === "number") {
|
|
2784
2885
|
sanitized = Math.min(
|
|
2785
2886
|
transform ? this.#transformNumber(this.max) : this.max,
|
|
2786
|
-
sanitized
|
|
2887
|
+
sanitized,
|
|
2787
2888
|
);
|
|
2788
2889
|
}
|
|
2789
2890
|
|
|
@@ -3103,7 +3204,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
3103
3204
|
e.target.value = "";
|
|
3104
3205
|
}
|
|
3105
3206
|
this.dispatchEvent(
|
|
3106
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
3207
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
3107
3208
|
);
|
|
3108
3209
|
}
|
|
3109
3210
|
|
|
@@ -3129,10 +3230,10 @@ class FigInputNumber extends HTMLElement {
|
|
|
3129
3230
|
this.input.value = this.#formatWithUnit(this.value);
|
|
3130
3231
|
|
|
3131
3232
|
this.dispatchEvent(
|
|
3132
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
3233
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
3133
3234
|
);
|
|
3134
3235
|
this.dispatchEvent(
|
|
3135
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
3236
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
3136
3237
|
);
|
|
3137
3238
|
}
|
|
3138
3239
|
|
|
@@ -3144,7 +3245,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
3144
3245
|
this.value = "";
|
|
3145
3246
|
}
|
|
3146
3247
|
this.dispatchEvent(
|
|
3147
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
3248
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
3148
3249
|
);
|
|
3149
3250
|
}
|
|
3150
3251
|
|
|
@@ -3161,10 +3262,10 @@ class FigInputNumber extends HTMLElement {
|
|
|
3161
3262
|
e.target.value = "";
|
|
3162
3263
|
}
|
|
3163
3264
|
this.dispatchEvent(
|
|
3164
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
3265
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
3165
3266
|
);
|
|
3166
3267
|
this.dispatchEvent(
|
|
3167
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
3268
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
3168
3269
|
);
|
|
3169
3270
|
}
|
|
3170
3271
|
|
|
@@ -3223,7 +3324,9 @@ class FigInputNumber extends HTMLElement {
|
|
|
3223
3324
|
const factor = Math.pow(10, precision);
|
|
3224
3325
|
const rounded = Math.round(num * factor) / factor;
|
|
3225
3326
|
// Only show decimals if needed and up to precision
|
|
3226
|
-
return Number.isInteger(rounded)
|
|
3327
|
+
return Number.isInteger(rounded)
|
|
3328
|
+
? rounded
|
|
3329
|
+
: parseFloat(rounded.toFixed(precision));
|
|
3227
3330
|
}
|
|
3228
3331
|
|
|
3229
3332
|
static get observedAttributes() {
|
|
@@ -3357,7 +3460,7 @@ class FigField extends HTMLElement {
|
|
|
3357
3460
|
requestAnimationFrame(() => {
|
|
3358
3461
|
this.label = this.querySelector(":scope>label");
|
|
3359
3462
|
this.input = Array.from(this.childNodes).find((node) =>
|
|
3360
|
-
node.nodeName.toLowerCase().startsWith("fig-")
|
|
3463
|
+
node.nodeName.toLowerCase().startsWith("fig-"),
|
|
3361
3464
|
);
|
|
3362
3465
|
if (this.input && this.label) {
|
|
3363
3466
|
this.label.addEventListener("click", this.focus.bind(this));
|
|
@@ -3502,11 +3605,11 @@ class FigInputColor extends HTMLElement {
|
|
|
3502
3605
|
}
|
|
3503
3606
|
this.#fillPicker.addEventListener(
|
|
3504
3607
|
"input",
|
|
3505
|
-
this.#handleFillPickerInput.bind(this)
|
|
3608
|
+
this.#handleFillPickerInput.bind(this),
|
|
3506
3609
|
);
|
|
3507
3610
|
this.#fillPicker.addEventListener(
|
|
3508
3611
|
"change",
|
|
3509
|
-
this.#handleChange.bind(this)
|
|
3612
|
+
this.#handleChange.bind(this),
|
|
3510
3613
|
);
|
|
3511
3614
|
}
|
|
3512
3615
|
|
|
@@ -3519,22 +3622,22 @@ class FigInputColor extends HTMLElement {
|
|
|
3519
3622
|
}
|
|
3520
3623
|
this.#textInput.addEventListener(
|
|
3521
3624
|
"input",
|
|
3522
|
-
this.#handleTextInput.bind(this)
|
|
3625
|
+
this.#handleTextInput.bind(this),
|
|
3523
3626
|
);
|
|
3524
3627
|
this.#textInput.addEventListener(
|
|
3525
3628
|
"change",
|
|
3526
|
-
this.#handleChange.bind(this)
|
|
3629
|
+
this.#handleChange.bind(this),
|
|
3527
3630
|
);
|
|
3528
3631
|
}
|
|
3529
3632
|
|
|
3530
3633
|
if (this.#alphaInput) {
|
|
3531
3634
|
this.#alphaInput.addEventListener(
|
|
3532
3635
|
"input",
|
|
3533
|
-
this.#handleAlphaInput.bind(this)
|
|
3636
|
+
this.#handleAlphaInput.bind(this),
|
|
3534
3637
|
);
|
|
3535
3638
|
this.#alphaInput.addEventListener(
|
|
3536
3639
|
"change",
|
|
3537
|
-
this.#handleChange.bind(this)
|
|
3640
|
+
this.#handleChange.bind(this),
|
|
3538
3641
|
);
|
|
3539
3642
|
}
|
|
3540
3643
|
});
|
|
@@ -3547,7 +3650,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3547
3650
|
g: isNaN(this.rgba.g) ? 0 : this.rgba.g,
|
|
3548
3651
|
b: isNaN(this.rgba.b) ? 0 : this.rgba.b,
|
|
3549
3652
|
},
|
|
3550
|
-
this.rgba.a
|
|
3653
|
+
this.rgba.a,
|
|
3551
3654
|
);
|
|
3552
3655
|
this.hexWithAlpha = this.value.toUpperCase();
|
|
3553
3656
|
this.hexOpaque = this.hexWithAlpha.slice(0, 7);
|
|
@@ -3590,7 +3693,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3590
3693
|
type: "solid",
|
|
3591
3694
|
color: this.hexOpaque,
|
|
3592
3695
|
opacity: this.alpha,
|
|
3593
|
-
})
|
|
3696
|
+
}),
|
|
3594
3697
|
);
|
|
3595
3698
|
}
|
|
3596
3699
|
this.#emitInputEvent();
|
|
@@ -3613,7 +3716,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3613
3716
|
// Display without # prefix
|
|
3614
3717
|
this.#textInput.setAttribute(
|
|
3615
3718
|
"value",
|
|
3616
|
-
this.hexOpaque.slice(1).toUpperCase()
|
|
3719
|
+
this.hexOpaque.slice(1).toUpperCase(),
|
|
3617
3720
|
);
|
|
3618
3721
|
}
|
|
3619
3722
|
this.#emitInputEvent();
|
|
@@ -3635,7 +3738,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3635
3738
|
if (this.#textInput) {
|
|
3636
3739
|
this.#textInput.setAttribute(
|
|
3637
3740
|
"value",
|
|
3638
|
-
this.hexOpaque.slice(1).toUpperCase()
|
|
3741
|
+
this.hexOpaque.slice(1).toUpperCase(),
|
|
3639
3742
|
);
|
|
3640
3743
|
}
|
|
3641
3744
|
if (this.#alphaInput && detail.alpha !== undefined) {
|
|
@@ -3693,7 +3796,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3693
3796
|
type: "solid",
|
|
3694
3797
|
color: this.hexOpaque,
|
|
3695
3798
|
opacity: this.alpha,
|
|
3696
|
-
})
|
|
3799
|
+
}),
|
|
3697
3800
|
);
|
|
3698
3801
|
}
|
|
3699
3802
|
if (this.#alphaInput) {
|
|
@@ -3760,7 +3863,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3760
3863
|
// Handle rgba colors
|
|
3761
3864
|
else if (color.startsWith("rgba") || color.startsWith("rgb")) {
|
|
3762
3865
|
let matches = color.match(
|
|
3763
|
-
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)
|
|
3866
|
+
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/,
|
|
3764
3867
|
);
|
|
3765
3868
|
if (matches) {
|
|
3766
3869
|
r = parseInt(matches[1]);
|
|
@@ -3772,7 +3875,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3772
3875
|
// Handle hsla colors
|
|
3773
3876
|
else if (color.startsWith("hsla") || color.startsWith("hsl")) {
|
|
3774
3877
|
let matches = color.match(
|
|
3775
|
-
/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)
|
|
3878
|
+
/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)/,
|
|
3776
3879
|
);
|
|
3777
3880
|
if (matches) {
|
|
3778
3881
|
let h = parseInt(matches[1]) / 360;
|
|
@@ -3988,8 +4091,8 @@ class FigInputFill extends HTMLElement {
|
|
|
3988
4091
|
this.innerHTML = `
|
|
3989
4092
|
<div class="input-combo">
|
|
3990
4093
|
<fig-fill-picker ${fpAttrs} value='${fillPickerValue}' ${
|
|
3991
|
-
|
|
3992
|
-
|
|
4094
|
+
disabled ? "disabled" : ""
|
|
4095
|
+
}></fig-fill-picker>
|
|
3993
4096
|
${controlsHtml}
|
|
3994
4097
|
</div>`;
|
|
3995
4098
|
|
|
@@ -4126,13 +4229,13 @@ class FigInputFill extends HTMLElement {
|
|
|
4126
4229
|
if (this.#hexInput) {
|
|
4127
4230
|
this.#hexInput.setAttribute(
|
|
4128
4231
|
"value",
|
|
4129
|
-
this.#solid.color.slice(1).toUpperCase()
|
|
4232
|
+
this.#solid.color.slice(1).toUpperCase(),
|
|
4130
4233
|
);
|
|
4131
4234
|
}
|
|
4132
4235
|
if (this.#opacityInput) {
|
|
4133
4236
|
this.#opacityInput.setAttribute(
|
|
4134
4237
|
"value",
|
|
4135
|
-
Math.round(this.#solid.alpha * 100)
|
|
4238
|
+
Math.round(this.#solid.alpha * 100),
|
|
4136
4239
|
);
|
|
4137
4240
|
}
|
|
4138
4241
|
break;
|
|
@@ -4140,7 +4243,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4140
4243
|
if (this.#opacityInput) {
|
|
4141
4244
|
this.#opacityInput.setAttribute(
|
|
4142
4245
|
"value",
|
|
4143
|
-
this.#gradient.stops[0]?.opacity ?? 100
|
|
4246
|
+
this.#gradient.stops[0]?.opacity ?? 100,
|
|
4144
4247
|
);
|
|
4145
4248
|
}
|
|
4146
4249
|
const label = this.querySelector(".fig-input-fill-label");
|
|
@@ -4156,7 +4259,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4156
4259
|
if (this.#opacityInput) {
|
|
4157
4260
|
this.#opacityInput.setAttribute(
|
|
4158
4261
|
"value",
|
|
4159
|
-
Math.round((this.#image.opacity ?? 1) * 100)
|
|
4262
|
+
Math.round((this.#image.opacity ?? 1) * 100),
|
|
4160
4263
|
);
|
|
4161
4264
|
}
|
|
4162
4265
|
break;
|
|
@@ -4164,7 +4267,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4164
4267
|
if (this.#opacityInput) {
|
|
4165
4268
|
this.#opacityInput.setAttribute(
|
|
4166
4269
|
"value",
|
|
4167
|
-
Math.round((this.#video.opacity ?? 1) * 100)
|
|
4270
|
+
Math.round((this.#video.opacity ?? 1) * 100),
|
|
4168
4271
|
);
|
|
4169
4272
|
}
|
|
4170
4273
|
break;
|
|
@@ -4172,7 +4275,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4172
4275
|
if (this.#opacityInput) {
|
|
4173
4276
|
this.#opacityInput.setAttribute(
|
|
4174
4277
|
"value",
|
|
4175
|
-
Math.round((this.#webcam.opacity ?? 1) * 100)
|
|
4278
|
+
Math.round((this.#webcam.opacity ?? 1) * 100),
|
|
4176
4279
|
);
|
|
4177
4280
|
}
|
|
4178
4281
|
break;
|
|
@@ -4376,7 +4479,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4376
4479
|
new CustomEvent("input", {
|
|
4377
4480
|
bubbles: true,
|
|
4378
4481
|
detail: this.value,
|
|
4379
|
-
})
|
|
4482
|
+
}),
|
|
4380
4483
|
);
|
|
4381
4484
|
}
|
|
4382
4485
|
|
|
@@ -4385,7 +4488,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4385
4488
|
new CustomEvent("change", {
|
|
4386
4489
|
bubbles: true,
|
|
4387
4490
|
detail: this.value,
|
|
4388
|
-
})
|
|
4491
|
+
}),
|
|
4389
4492
|
);
|
|
4390
4493
|
}
|
|
4391
4494
|
|
|
@@ -4625,14 +4728,14 @@ class FigCheckbox extends HTMLElement {
|
|
|
4625
4728
|
bubbles: true,
|
|
4626
4729
|
composed: true,
|
|
4627
4730
|
detail: { checked: this.input.checked, value: this.input.value },
|
|
4628
|
-
})
|
|
4731
|
+
}),
|
|
4629
4732
|
);
|
|
4630
4733
|
this.dispatchEvent(
|
|
4631
4734
|
new CustomEvent("change", {
|
|
4632
4735
|
bubbles: true,
|
|
4633
4736
|
composed: true,
|
|
4634
4737
|
detail: { checked: this.input.checked, value: this.input.value },
|
|
4635
|
-
})
|
|
4738
|
+
}),
|
|
4636
4739
|
);
|
|
4637
4740
|
}
|
|
4638
4741
|
}
|
|
@@ -4827,8 +4930,9 @@ class FigComboInput extends HTMLElement {
|
|
|
4827
4930
|
}
|
|
4828
4931
|
connectedCallback() {
|
|
4829
4932
|
const customDropdown =
|
|
4830
|
-
Array.from(this.children).find(
|
|
4831
|
-
|
|
4933
|
+
Array.from(this.children).find(
|
|
4934
|
+
(child) => child.tagName === "FIG-DROPDOWN",
|
|
4935
|
+
) || null;
|
|
4832
4936
|
this.#usesCustomDropdown = customDropdown !== null;
|
|
4833
4937
|
if (customDropdown) {
|
|
4834
4938
|
customDropdown.remove();
|
|
@@ -5150,7 +5254,10 @@ class FigImage extends HTMLElement {
|
|
|
5150
5254
|
}
|
|
5151
5255
|
disconnectedCallback() {
|
|
5152
5256
|
this.fileInput?.removeEventListener("change", this.#boundHandleFileInput);
|
|
5153
|
-
this.downloadButton?.removeEventListener(
|
|
5257
|
+
this.downloadButton?.removeEventListener(
|
|
5258
|
+
"click",
|
|
5259
|
+
this.#boundHandleDownload,
|
|
5260
|
+
);
|
|
5154
5261
|
}
|
|
5155
5262
|
|
|
5156
5263
|
#updateRefs() {
|
|
@@ -5159,13 +5266,22 @@ class FigImage extends HTMLElement {
|
|
|
5159
5266
|
if (this.upload) {
|
|
5160
5267
|
this.uploadButton = this.querySelector("fig-button[type='upload']");
|
|
5161
5268
|
this.fileInput = this.uploadButton?.querySelector("input");
|
|
5162
|
-
this.fileInput?.removeEventListener(
|
|
5269
|
+
this.fileInput?.removeEventListener(
|
|
5270
|
+
"change",
|
|
5271
|
+
this.#boundHandleFileInput,
|
|
5272
|
+
);
|
|
5163
5273
|
this.fileInput?.addEventListener("change", this.#boundHandleFileInput);
|
|
5164
5274
|
}
|
|
5165
5275
|
if (this.download) {
|
|
5166
5276
|
this.downloadButton = this.querySelector("fig-button[type='download']");
|
|
5167
|
-
this.downloadButton?.removeEventListener(
|
|
5168
|
-
|
|
5277
|
+
this.downloadButton?.removeEventListener(
|
|
5278
|
+
"click",
|
|
5279
|
+
this.#boundHandleDownload,
|
|
5280
|
+
);
|
|
5281
|
+
this.downloadButton?.addEventListener(
|
|
5282
|
+
"click",
|
|
5283
|
+
this.#boundHandleDownload,
|
|
5284
|
+
);
|
|
5169
5285
|
}
|
|
5170
5286
|
});
|
|
5171
5287
|
}
|
|
@@ -5187,7 +5303,7 @@ class FigImage extends HTMLElement {
|
|
|
5187
5303
|
if (!ar || ar === "auto") {
|
|
5188
5304
|
this.style.setProperty(
|
|
5189
5305
|
"--aspect-ratio",
|
|
5190
|
-
`${this.image.width}/${this.image.height}
|
|
5306
|
+
`${this.image.width}/${this.image.height}`,
|
|
5191
5307
|
);
|
|
5192
5308
|
}
|
|
5193
5309
|
this.dispatchEvent(
|
|
@@ -5198,7 +5314,7 @@ class FigImage extends HTMLElement {
|
|
|
5198
5314
|
blob: this.blob,
|
|
5199
5315
|
base64: this.base64,
|
|
5200
5316
|
},
|
|
5201
|
-
})
|
|
5317
|
+
}),
|
|
5202
5318
|
);
|
|
5203
5319
|
resolve();
|
|
5204
5320
|
|
|
@@ -5249,14 +5365,14 @@ class FigImage extends HTMLElement {
|
|
|
5249
5365
|
blob: this.blob,
|
|
5250
5366
|
base64: this.base64,
|
|
5251
5367
|
},
|
|
5252
|
-
})
|
|
5368
|
+
}),
|
|
5253
5369
|
);
|
|
5254
5370
|
//emit for change too
|
|
5255
5371
|
this.dispatchEvent(
|
|
5256
5372
|
new CustomEvent("change", {
|
|
5257
5373
|
bubbles: true,
|
|
5258
5374
|
cancelable: true,
|
|
5259
|
-
})
|
|
5375
|
+
}),
|
|
5260
5376
|
);
|
|
5261
5377
|
this.setAttribute("src", this.blob);
|
|
5262
5378
|
}
|
|
@@ -5277,7 +5393,7 @@ class FigImage extends HTMLElement {
|
|
|
5277
5393
|
if (this.chit) {
|
|
5278
5394
|
this.chit.setAttribute(
|
|
5279
5395
|
"background",
|
|
5280
|
-
this.#src ? `url(${this.#src})` : ""
|
|
5396
|
+
this.#src ? `url(${this.#src})` : "",
|
|
5281
5397
|
);
|
|
5282
5398
|
}
|
|
5283
5399
|
if (this.#src) {
|
|
@@ -5330,6 +5446,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5330
5446
|
#line2 = null;
|
|
5331
5447
|
#handle1 = null;
|
|
5332
5448
|
#handle2 = null;
|
|
5449
|
+
#bezierEndpointStart = null;
|
|
5450
|
+
#bezierEndpointEnd = null;
|
|
5333
5451
|
#dropdown = null;
|
|
5334
5452
|
#presetName = null;
|
|
5335
5453
|
#targetLine = null;
|
|
@@ -5339,29 +5457,86 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5339
5457
|
#bounds = null;
|
|
5340
5458
|
#diagonal = null;
|
|
5341
5459
|
#resizeObserver = null;
|
|
5460
|
+
#bezierHandleRadius = 3.625;
|
|
5461
|
+
#bezierEndpointRadius = 2;
|
|
5462
|
+
#springHandleRadius = 3.625;
|
|
5463
|
+
#durationBarWidth = 6;
|
|
5464
|
+
#durationBarHeight = 16;
|
|
5465
|
+
#durationBarRadius = 3;
|
|
5342
5466
|
|
|
5343
5467
|
static PRESETS = [
|
|
5344
5468
|
{ group: null, name: "Linear", type: "bezier", value: [0, 0, 1, 1] },
|
|
5345
|
-
{
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5469
|
+
{
|
|
5470
|
+
group: "Bezier",
|
|
5471
|
+
name: "Ease in",
|
|
5472
|
+
type: "bezier",
|
|
5473
|
+
value: [0.42, 0, 1, 1],
|
|
5474
|
+
},
|
|
5475
|
+
{
|
|
5476
|
+
group: "Bezier",
|
|
5477
|
+
name: "Ease out",
|
|
5478
|
+
type: "bezier",
|
|
5479
|
+
value: [0, 0, 0.58, 1],
|
|
5480
|
+
},
|
|
5481
|
+
{
|
|
5482
|
+
group: "Bezier",
|
|
5483
|
+
name: "Ease in and out",
|
|
5484
|
+
type: "bezier",
|
|
5485
|
+
value: [0.42, 0, 0.58, 1],
|
|
5486
|
+
},
|
|
5487
|
+
{
|
|
5488
|
+
group: "Bezier",
|
|
5489
|
+
name: "Ease in back",
|
|
5490
|
+
type: "bezier",
|
|
5491
|
+
value: [0.6, -0.28, 0.735, 0.045],
|
|
5492
|
+
},
|
|
5493
|
+
{
|
|
5494
|
+
group: "Bezier",
|
|
5495
|
+
name: "Ease out back",
|
|
5496
|
+
type: "bezier",
|
|
5497
|
+
value: [0.175, 0.885, 0.32, 1.275],
|
|
5498
|
+
},
|
|
5499
|
+
{
|
|
5500
|
+
group: "Bezier",
|
|
5501
|
+
name: "Ease in and out back",
|
|
5502
|
+
type: "bezier",
|
|
5503
|
+
value: [0.68, -0.55, 0.265, 1.55],
|
|
5504
|
+
},
|
|
5351
5505
|
{ group: "Bezier", name: "Custom bezier", type: "bezier", value: null },
|
|
5352
|
-
{
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5506
|
+
{
|
|
5507
|
+
group: "Spring",
|
|
5508
|
+
name: "Gentle",
|
|
5509
|
+
type: "spring",
|
|
5510
|
+
spring: { stiffness: 120, damping: 14, mass: 1 },
|
|
5511
|
+
},
|
|
5512
|
+
{
|
|
5513
|
+
group: "Spring",
|
|
5514
|
+
name: "Quick",
|
|
5515
|
+
type: "spring",
|
|
5516
|
+
spring: { stiffness: 380, damping: 20, mass: 1 },
|
|
5517
|
+
},
|
|
5518
|
+
{
|
|
5519
|
+
group: "Spring",
|
|
5520
|
+
name: "Bouncy",
|
|
5521
|
+
type: "spring",
|
|
5522
|
+
spring: { stiffness: 250, damping: 8, mass: 1 },
|
|
5523
|
+
},
|
|
5524
|
+
{
|
|
5525
|
+
group: "Spring",
|
|
5526
|
+
name: "Slow",
|
|
5527
|
+
type: "spring",
|
|
5528
|
+
spring: { stiffness: 60, damping: 11, mass: 1 },
|
|
5529
|
+
},
|
|
5356
5530
|
{ group: "Spring", name: "Custom spring", type: "spring", spring: null },
|
|
5357
5531
|
];
|
|
5358
5532
|
|
|
5359
5533
|
static get observedAttributes() {
|
|
5360
|
-
return ["value", "precision"];
|
|
5534
|
+
return ["value", "precision", "aspect-ratio"];
|
|
5361
5535
|
}
|
|
5362
5536
|
|
|
5363
5537
|
connectedCallback() {
|
|
5364
5538
|
this.#precision = parseInt(this.getAttribute("precision") || "2");
|
|
5539
|
+
this.#syncAspectRatioVar(this.getAttribute("aspect-ratio"));
|
|
5365
5540
|
const val = this.getAttribute("value");
|
|
5366
5541
|
if (val) this.#parseValue(val);
|
|
5367
5542
|
this.#presetName = this.#matchPreset();
|
|
@@ -5377,7 +5552,24 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5377
5552
|
}
|
|
5378
5553
|
}
|
|
5379
5554
|
|
|
5555
|
+
#syncAspectRatioVar(value) {
|
|
5556
|
+
if (value && value.trim()) {
|
|
5557
|
+
this.style.setProperty("--aspect-ratio", value.trim());
|
|
5558
|
+
} else {
|
|
5559
|
+
this.style.removeProperty("--aspect-ratio");
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
|
|
5380
5563
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
5564
|
+
if (name === "aspect-ratio") {
|
|
5565
|
+
this.#syncAspectRatioVar(newValue);
|
|
5566
|
+
if (this.#svg) {
|
|
5567
|
+
this.#syncViewportSize();
|
|
5568
|
+
this.#updatePaths();
|
|
5569
|
+
}
|
|
5570
|
+
return;
|
|
5571
|
+
}
|
|
5572
|
+
|
|
5381
5573
|
if (!this.#svg) return;
|
|
5382
5574
|
if (name === "value" && newValue) {
|
|
5383
5575
|
const prevMode = this.#mode;
|
|
@@ -5413,7 +5605,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5413
5605
|
for (let i = 0; i < points.length; i += step) {
|
|
5414
5606
|
vals.push(points[i].value.toFixed(3));
|
|
5415
5607
|
}
|
|
5416
|
-
if (points.length > 0)
|
|
5608
|
+
if (points.length > 0)
|
|
5609
|
+
vals.push(points[points.length - 1].value.toFixed(3));
|
|
5417
5610
|
return `linear(${vals.join(", ")})`;
|
|
5418
5611
|
}
|
|
5419
5612
|
const p = this.#precision;
|
|
@@ -5429,7 +5622,9 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5429
5622
|
}
|
|
5430
5623
|
|
|
5431
5624
|
#parseValue(str) {
|
|
5432
|
-
const springMatch = str.match(
|
|
5625
|
+
const springMatch = str.match(
|
|
5626
|
+
/^spring\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/,
|
|
5627
|
+
);
|
|
5433
5628
|
if (springMatch) {
|
|
5434
5629
|
this.#mode = "spring";
|
|
5435
5630
|
this.#spring.stiffness = parseFloat(springMatch[1]);
|
|
@@ -5457,7 +5652,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5457
5652
|
Math.abs(this.#cp1.y - p.value[1]) < ep &&
|
|
5458
5653
|
Math.abs(this.#cp2.x - p.value[2]) < ep &&
|
|
5459
5654
|
Math.abs(this.#cp2.y - p.value[3]) < ep
|
|
5460
|
-
)
|
|
5655
|
+
)
|
|
5656
|
+
return p.name;
|
|
5461
5657
|
}
|
|
5462
5658
|
return "Custom bezier";
|
|
5463
5659
|
}
|
|
@@ -5467,7 +5663,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5467
5663
|
Math.abs(this.#spring.stiffness - p.spring.stiffness) < ep &&
|
|
5468
5664
|
Math.abs(this.#spring.damping - p.spring.damping) < ep &&
|
|
5469
5665
|
Math.abs(this.#spring.mass - p.spring.mass) < ep
|
|
5470
|
-
)
|
|
5666
|
+
)
|
|
5667
|
+
return p.name;
|
|
5471
5668
|
}
|
|
5472
5669
|
return "Custom spring";
|
|
5473
5670
|
}
|
|
@@ -5479,13 +5676,15 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5479
5676
|
const dt = 0.004;
|
|
5480
5677
|
const maxTime = 5;
|
|
5481
5678
|
const points = [];
|
|
5482
|
-
let pos = 0,
|
|
5679
|
+
let pos = 0,
|
|
5680
|
+
vel = 0;
|
|
5483
5681
|
for (let t = 0; t <= maxTime; t += dt) {
|
|
5484
5682
|
const force = -stiffness * (pos - 1) - damping * vel;
|
|
5485
5683
|
vel += (force / mass) * dt;
|
|
5486
5684
|
pos += vel * dt;
|
|
5487
5685
|
points.push({ t, value: pos });
|
|
5488
|
-
if (t > 0.1 && Math.abs(pos - 1) < 0.0005 && Math.abs(vel) < 0.0005)
|
|
5686
|
+
if (t > 0.1 && Math.abs(pos - 1) < 0.0005 && Math.abs(vel) < 0.0005)
|
|
5687
|
+
break;
|
|
5489
5688
|
}
|
|
5490
5689
|
return points;
|
|
5491
5690
|
}
|
|
@@ -5495,7 +5694,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5495
5694
|
const dt = 0.004;
|
|
5496
5695
|
const maxTime = 5;
|
|
5497
5696
|
const pts = [];
|
|
5498
|
-
let pos = 0,
|
|
5697
|
+
let pos = 0,
|
|
5698
|
+
vel = 0;
|
|
5499
5699
|
for (let t = 0; t <= maxTime; t += dt) {
|
|
5500
5700
|
const force = -stiffness * (pos - 1) - damping * vel;
|
|
5501
5701
|
vel += (force / mass) * dt;
|
|
@@ -5518,21 +5718,64 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5518
5718
|
const y = pad + (1 - (pts[i].value - minVal) / range) * s;
|
|
5519
5719
|
d += (i === 0 ? "M" : "L") + x.toFixed(1) + "," + y.toFixed(1);
|
|
5520
5720
|
}
|
|
5521
|
-
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" fill="none"><path d="${d}" stroke="currentColor" stroke-width="1
|
|
5721
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" fill="none"><path d="${d}" stroke="currentColor" stroke-width="1" stroke-linecap="round" fill="none"/></svg>`;
|
|
5522
5722
|
}
|
|
5523
5723
|
|
|
5524
5724
|
static curveIcon(cp1x, cp1y, cp2x, cp2y, size = 24) {
|
|
5525
|
-
const
|
|
5526
|
-
const
|
|
5527
|
-
const
|
|
5528
|
-
const
|
|
5529
|
-
|
|
5530
|
-
|
|
5725
|
+
const draw = 12;
|
|
5726
|
+
const pad = (size - draw) / 2;
|
|
5727
|
+
const samples = 48;
|
|
5728
|
+
const points = [];
|
|
5729
|
+
|
|
5730
|
+
const cubic = (p0, p1, p2, p3, t) => {
|
|
5731
|
+
const mt = 1 - t;
|
|
5732
|
+
return (
|
|
5733
|
+
mt * mt * mt * p0 +
|
|
5734
|
+
3 * mt * mt * t * p1 +
|
|
5735
|
+
3 * mt * t * t * p2 +
|
|
5736
|
+
t * t * t * p3
|
|
5737
|
+
);
|
|
5738
|
+
};
|
|
5739
|
+
|
|
5740
|
+
for (let i = 0; i <= samples; i++) {
|
|
5741
|
+
const t = i / samples;
|
|
5742
|
+
points.push({
|
|
5743
|
+
x: cubic(0, cp1x, cp2x, 1, t),
|
|
5744
|
+
y: cubic(0, cp1y, cp2y, 1, t),
|
|
5745
|
+
});
|
|
5746
|
+
}
|
|
5747
|
+
|
|
5748
|
+
let minX = Infinity;
|
|
5749
|
+
let maxX = -Infinity;
|
|
5750
|
+
let minY = Infinity;
|
|
5751
|
+
let maxY = -Infinity;
|
|
5752
|
+
for (const p of points) {
|
|
5753
|
+
if (p.x < minX) minX = p.x;
|
|
5754
|
+
if (p.x > maxX) maxX = p.x;
|
|
5755
|
+
if (p.y < minY) minY = p.y;
|
|
5756
|
+
if (p.y > maxY) maxY = p.y;
|
|
5757
|
+
}
|
|
5758
|
+
|
|
5759
|
+
const rangeX = Math.max(maxX - minX, 1e-6);
|
|
5760
|
+
const rangeY = Math.max(maxY - minY, 1e-6);
|
|
5761
|
+
const toX = (x) => pad + ((x - minX) / rangeX) * draw;
|
|
5762
|
+
const toY = (y) => pad + (1 - (y - minY) / rangeY) * draw;
|
|
5763
|
+
|
|
5764
|
+
let d = "";
|
|
5765
|
+
for (let i = 0; i < points.length; i++) {
|
|
5766
|
+
const px = toX(points[i].x);
|
|
5767
|
+
const py = toY(points[i].y);
|
|
5768
|
+
d += `${i === 0 ? "M" : "L"}${px.toFixed(1)},${py.toFixed(1)}`;
|
|
5769
|
+
}
|
|
5770
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" fill="none"><path d="${d}" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>`;
|
|
5531
5771
|
}
|
|
5532
5772
|
|
|
5533
5773
|
// --- Rendering ---
|
|
5534
5774
|
|
|
5535
5775
|
#render() {
|
|
5776
|
+
this.classList.toggle("spring-mode", this.#mode === "spring");
|
|
5777
|
+
this.classList.toggle("bezier-mode", this.#mode !== "spring");
|
|
5778
|
+
this.#syncMetricsFromCSS();
|
|
5536
5779
|
this.innerHTML = this.#getInnerHTML();
|
|
5537
5780
|
this.#cacheRefs();
|
|
5538
5781
|
this.#syncViewportSize();
|
|
@@ -5555,7 +5798,12 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5555
5798
|
const sp = p.spring || this.#spring;
|
|
5556
5799
|
icon = FigEasingCurve.#springIcon(sp);
|
|
5557
5800
|
} else {
|
|
5558
|
-
const v = p.value || [
|
|
5801
|
+
const v = p.value || [
|
|
5802
|
+
this.#cp1.x,
|
|
5803
|
+
this.#cp1.y,
|
|
5804
|
+
this.#cp2.x,
|
|
5805
|
+
this.#cp2.y,
|
|
5806
|
+
];
|
|
5559
5807
|
icon = FigEasingCurve.curveIcon(...v);
|
|
5560
5808
|
}
|
|
5561
5809
|
const selected = p.name === this.#presetName ? " selected" : "";
|
|
@@ -5577,8 +5825,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5577
5825
|
<line class="fig-easing-curve-target" x1="0" y1="${targetY}" x2="${size}" y2="${targetY}"/>
|
|
5578
5826
|
<line class="fig-easing-curve-diagonal" x1="0" y1="${startY}" x2="0" y2="${startY}"/>
|
|
5579
5827
|
<path class="fig-easing-curve-path"/>
|
|
5580
|
-
<circle class="fig-easing-curve-handle" data-handle="bounce" r="
|
|
5581
|
-
<rect class="fig-easing-curve-duration-bar" data-handle="duration" width="
|
|
5828
|
+
<circle class="fig-easing-curve-handle" data-handle="bounce" r="${this.#springHandleRadius}"/>
|
|
5829
|
+
<rect class="fig-easing-curve-duration-bar" data-handle="duration" width="${this.#durationBarWidth}" height="${this.#durationBarHeight}" rx="${this.#durationBarRadius}" ry="${this.#durationBarRadius}"/>
|
|
5582
5830
|
</svg></div>`;
|
|
5583
5831
|
}
|
|
5584
5832
|
|
|
@@ -5588,18 +5836,60 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5588
5836
|
<line class="fig-easing-curve-arm" data-arm="1"/>
|
|
5589
5837
|
<line class="fig-easing-curve-arm" data-arm="2"/>
|
|
5590
5838
|
<path class="fig-easing-curve-path"/>
|
|
5591
|
-
<circle class="fig-easing-curve-
|
|
5592
|
-
<circle class="fig-easing-curve-
|
|
5839
|
+
<circle class="fig-easing-curve-endpoint" data-endpoint="start" r="${this.#bezierEndpointRadius}"/>
|
|
5840
|
+
<circle class="fig-easing-curve-endpoint" data-endpoint="end" r="${this.#bezierEndpointRadius}"/>
|
|
5841
|
+
<circle class="fig-easing-curve-handle" data-handle="1" r="${this.#bezierHandleRadius}"/>
|
|
5842
|
+
<circle class="fig-easing-curve-handle" data-handle="2" r="${this.#bezierHandleRadius}"/>
|
|
5593
5843
|
</svg></div>`;
|
|
5594
5844
|
}
|
|
5595
5845
|
|
|
5846
|
+
#readCssNumber(name, fallback) {
|
|
5847
|
+
const raw = getComputedStyle(this).getPropertyValue(name).trim();
|
|
5848
|
+
if (!raw) return fallback;
|
|
5849
|
+
const value = Number.parseFloat(raw);
|
|
5850
|
+
return Number.isFinite(value) ? value : fallback;
|
|
5851
|
+
}
|
|
5852
|
+
|
|
5853
|
+
#syncMetricsFromCSS() {
|
|
5854
|
+
this.#bezierHandleRadius = this.#readCssNumber(
|
|
5855
|
+
"--easing-bezier-handle-radius",
|
|
5856
|
+
this.#bezierHandleRadius,
|
|
5857
|
+
);
|
|
5858
|
+
this.#bezierEndpointRadius = this.#readCssNumber(
|
|
5859
|
+
"--easing-bezier-endpoint-radius",
|
|
5860
|
+
this.#bezierEndpointRadius,
|
|
5861
|
+
);
|
|
5862
|
+
this.#springHandleRadius = this.#readCssNumber(
|
|
5863
|
+
"--easing-spring-handle-radius",
|
|
5864
|
+
this.#springHandleRadius,
|
|
5865
|
+
);
|
|
5866
|
+
this.#durationBarWidth = this.#readCssNumber(
|
|
5867
|
+
"--easing-duration-bar-width",
|
|
5868
|
+
this.#durationBarWidth,
|
|
5869
|
+
);
|
|
5870
|
+
this.#durationBarHeight = this.#readCssNumber(
|
|
5871
|
+
"--easing-duration-bar-height",
|
|
5872
|
+
this.#durationBarHeight,
|
|
5873
|
+
);
|
|
5874
|
+
this.#durationBarRadius = this.#readCssNumber(
|
|
5875
|
+
"--easing-duration-bar-radius",
|
|
5876
|
+
this.#durationBarRadius,
|
|
5877
|
+
);
|
|
5878
|
+
}
|
|
5879
|
+
|
|
5596
5880
|
#cacheRefs() {
|
|
5597
5881
|
this.#svg = this.querySelector(".fig-easing-curve-svg");
|
|
5598
5882
|
this.#curve = this.querySelector(".fig-easing-curve-path");
|
|
5599
5883
|
this.#line1 = this.querySelector('[data-arm="1"]');
|
|
5600
5884
|
this.#line2 = this.querySelector('[data-arm="2"]');
|
|
5601
|
-
this.#handle1 =
|
|
5602
|
-
|
|
5885
|
+
this.#handle1 =
|
|
5886
|
+
this.querySelector('[data-handle="1"]') ||
|
|
5887
|
+
this.querySelector('[data-handle="bounce"]');
|
|
5888
|
+
this.#handle2 =
|
|
5889
|
+
this.querySelector('[data-handle="2"]') ||
|
|
5890
|
+
this.querySelector('[data-handle="duration"]');
|
|
5891
|
+
this.#bezierEndpointStart = this.querySelector('[data-endpoint="start"]');
|
|
5892
|
+
this.#bezierEndpointEnd = this.querySelector('[data-endpoint="end"]');
|
|
5603
5893
|
this.#dropdown = this.querySelector(".fig-easing-curve-dropdown");
|
|
5604
5894
|
this.#targetLine = this.querySelector(".fig-easing-curve-target");
|
|
5605
5895
|
this.#bounds = this.querySelector(".fig-easing-curve-bounds");
|
|
@@ -5681,7 +5971,10 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5681
5971
|
const p2 = this.#toSVG(this.#cp2.x, this.#cp2.y);
|
|
5682
5972
|
const p3 = this.#toSVG(1, 1);
|
|
5683
5973
|
|
|
5684
|
-
this.#curve.setAttribute(
|
|
5974
|
+
this.#curve.setAttribute(
|
|
5975
|
+
"d",
|
|
5976
|
+
`M${p0.x},${p0.y} C${p1.x},${p1.y} ${p2.x},${p2.y} ${p3.x},${p3.y}`,
|
|
5977
|
+
);
|
|
5685
5978
|
this.#line1.setAttribute("x1", p0.x);
|
|
5686
5979
|
this.#line1.setAttribute("y1", p0.y);
|
|
5687
5980
|
this.#line1.setAttribute("x2", p1.x);
|
|
@@ -5694,6 +5987,14 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5694
5987
|
this.#handle1.setAttribute("cy", p1.y);
|
|
5695
5988
|
this.#handle2.setAttribute("cx", p2.x);
|
|
5696
5989
|
this.#handle2.setAttribute("cy", p2.y);
|
|
5990
|
+
if (this.#bezierEndpointStart) {
|
|
5991
|
+
this.#bezierEndpointStart.setAttribute("cx", p0.x);
|
|
5992
|
+
this.#bezierEndpointStart.setAttribute("cy", p0.y);
|
|
5993
|
+
}
|
|
5994
|
+
if (this.#bezierEndpointEnd) {
|
|
5995
|
+
this.#bezierEndpointEnd.setAttribute("cx", p3.x);
|
|
5996
|
+
this.#bezierEndpointEnd.setAttribute("cy", p3.y);
|
|
5997
|
+
}
|
|
5697
5998
|
}
|
|
5698
5999
|
|
|
5699
6000
|
#updateSpringPaths() {
|
|
@@ -5708,12 +6009,17 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5708
6009
|
if (!points.length) return;
|
|
5709
6010
|
const totalTime = points[points.length - 1].t || 1;
|
|
5710
6011
|
|
|
5711
|
-
let minVal = 0,
|
|
6012
|
+
let minVal = 0,
|
|
6013
|
+
maxVal = 1;
|
|
5712
6014
|
for (const p of points) {
|
|
5713
6015
|
if (p.value < minVal) minVal = p.value;
|
|
5714
6016
|
if (p.value > maxVal) maxVal = p.value;
|
|
5715
6017
|
}
|
|
5716
|
-
const maxDistFromCenter = Math.max(
|
|
6018
|
+
const maxDistFromCenter = Math.max(
|
|
6019
|
+
Math.abs(minVal - 1),
|
|
6020
|
+
Math.abs(maxVal - 1),
|
|
6021
|
+
0.01,
|
|
6022
|
+
);
|
|
5717
6023
|
const valPad = 0;
|
|
5718
6024
|
this.#springScale = {
|
|
5719
6025
|
minVal: 1 - maxDistFromCenter - valPad,
|
|
@@ -5752,9 +6058,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5752
6058
|
|
|
5753
6059
|
// Duration handle: on the target line
|
|
5754
6060
|
const targetPt = this.#springToSVG(durationNorm, 1);
|
|
5755
|
-
this.#handle2.setAttribute("x", targetPt.x -
|
|
5756
|
-
this.#handle2.setAttribute("y", targetPt.y -
|
|
5757
|
-
|
|
6061
|
+
this.#handle2.setAttribute("x", targetPt.x - this.#durationBarWidth / 2);
|
|
6062
|
+
this.#handle2.setAttribute("y", targetPt.y - this.#durationBarHeight / 2);
|
|
5758
6063
|
}
|
|
5759
6064
|
|
|
5760
6065
|
#findPeakOvershoot(points) {
|
|
@@ -5792,38 +6097,76 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5792
6097
|
this.#cp1.x,
|
|
5793
6098
|
this.#cp1.y,
|
|
5794
6099
|
this.#cp2.x,
|
|
5795
|
-
this.#cp2.y
|
|
6100
|
+
this.#cp2.y,
|
|
5796
6101
|
);
|
|
5797
6102
|
const springIcon = FigEasingCurve.#springIcon(this.#spring);
|
|
5798
6103
|
|
|
5799
6104
|
// Update both slotted options and the cloned native select options.
|
|
5800
6105
|
this.#setOptionIconByValue(this.#dropdown, "Custom bezier", bezierIcon);
|
|
5801
6106
|
this.#setOptionIconByValue(this.#dropdown, "Custom spring", springIcon);
|
|
5802
|
-
this.#setOptionIconByValue(
|
|
5803
|
-
|
|
6107
|
+
this.#setOptionIconByValue(
|
|
6108
|
+
this.#dropdown.select,
|
|
6109
|
+
"Custom bezier",
|
|
6110
|
+
bezierIcon,
|
|
6111
|
+
);
|
|
6112
|
+
this.#setOptionIconByValue(
|
|
6113
|
+
this.#dropdown.select,
|
|
6114
|
+
"Custom spring",
|
|
6115
|
+
springIcon,
|
|
6116
|
+
);
|
|
5804
6117
|
}
|
|
5805
6118
|
|
|
5806
6119
|
// --- Events ---
|
|
5807
6120
|
|
|
5808
6121
|
#emit(type) {
|
|
5809
|
-
this.dispatchEvent(
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
6122
|
+
this.dispatchEvent(
|
|
6123
|
+
new CustomEvent(type, {
|
|
6124
|
+
bubbles: true,
|
|
6125
|
+
detail: {
|
|
6126
|
+
mode: this.#mode,
|
|
6127
|
+
value: this.value,
|
|
6128
|
+
cssValue: this.cssValue,
|
|
6129
|
+
preset: this.#presetName,
|
|
6130
|
+
},
|
|
6131
|
+
}),
|
|
6132
|
+
);
|
|
5818
6133
|
}
|
|
5819
6134
|
|
|
5820
6135
|
#setupEvents() {
|
|
5821
6136
|
if (this.#mode === "bezier") {
|
|
5822
|
-
this.#handle1.addEventListener("pointerdown", (e) =>
|
|
5823
|
-
|
|
6137
|
+
this.#handle1.addEventListener("pointerdown", (e) =>
|
|
6138
|
+
this.#startBezierDrag(e, 1),
|
|
6139
|
+
);
|
|
6140
|
+
this.#handle2.addEventListener("pointerdown", (e) =>
|
|
6141
|
+
this.#startBezierDrag(e, 2),
|
|
6142
|
+
);
|
|
6143
|
+
|
|
6144
|
+
const bezierSurface = this.querySelector(".fig-easing-curve-svg-container");
|
|
6145
|
+
if (bezierSurface) {
|
|
6146
|
+
bezierSurface.addEventListener("pointerdown", (e) => {
|
|
6147
|
+
// Handles keep their own direct drag behavior.
|
|
6148
|
+
if (e.target?.closest?.(".fig-easing-curve-handle")) return;
|
|
6149
|
+
this.#startBezierDrag(e, this.#bezierHandleForClientHalf(e));
|
|
6150
|
+
});
|
|
6151
|
+
}
|
|
5824
6152
|
} else {
|
|
5825
|
-
this.#handle1.addEventListener("pointerdown", (e) =>
|
|
5826
|
-
|
|
6153
|
+
this.#handle1.addEventListener("pointerdown", (e) => {
|
|
6154
|
+
e.stopPropagation();
|
|
6155
|
+
this.#startSpringDrag(e, "bounce");
|
|
6156
|
+
});
|
|
6157
|
+
this.#handle2.addEventListener("pointerdown", (e) => {
|
|
6158
|
+
e.stopPropagation();
|
|
6159
|
+
this.#startSpringDrag(e, "duration");
|
|
6160
|
+
});
|
|
6161
|
+
|
|
6162
|
+
const springSurface = this.querySelector(".fig-easing-curve-svg-container");
|
|
6163
|
+
if (springSurface) {
|
|
6164
|
+
springSurface.addEventListener("pointerdown", (e) => {
|
|
6165
|
+
// Bounce handle keeps its own drag mode/cursor.
|
|
6166
|
+
if (e.target?.closest?.(".fig-easing-curve-handle")) return;
|
|
6167
|
+
this.#startSpringDrag(e, "duration");
|
|
6168
|
+
});
|
|
6169
|
+
}
|
|
5827
6170
|
}
|
|
5828
6171
|
|
|
5829
6172
|
if (this.#dropdown) {
|
|
@@ -5874,6 +6217,11 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5874
6217
|
};
|
|
5875
6218
|
}
|
|
5876
6219
|
|
|
6220
|
+
#bezierHandleForClientHalf(e) {
|
|
6221
|
+
const svgPt = this.#clientToSVG(e);
|
|
6222
|
+
return svgPt.x <= this.#drawWidth / 2 ? 1 : 2;
|
|
6223
|
+
}
|
|
6224
|
+
|
|
5877
6225
|
#startBezierDrag(e, handle) {
|
|
5878
6226
|
e.preventDefault();
|
|
5879
6227
|
this.#isDragging = handle;
|
|
@@ -5926,11 +6274,20 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5926
6274
|
|
|
5927
6275
|
if (handleType === "bounce") {
|
|
5928
6276
|
const dy = e.clientY - startY;
|
|
5929
|
-
this.#spring.damping = Math.max(
|
|
6277
|
+
this.#spring.damping = Math.max(
|
|
6278
|
+
1,
|
|
6279
|
+
Math.round(startDamping + dy * 0.15),
|
|
6280
|
+
);
|
|
5930
6281
|
} else {
|
|
5931
6282
|
const dx = e.clientX - startX;
|
|
5932
|
-
this.#springDuration = Math.max(
|
|
5933
|
-
|
|
6283
|
+
this.#springDuration = Math.max(
|
|
6284
|
+
0.05,
|
|
6285
|
+
Math.min(0.95, startDuration + dx / 200),
|
|
6286
|
+
);
|
|
6287
|
+
this.#spring.stiffness = Math.max(
|
|
6288
|
+
10,
|
|
6289
|
+
Math.round(startStiffness - dx * 1.5),
|
|
6290
|
+
);
|
|
5934
6291
|
}
|
|
5935
6292
|
|
|
5936
6293
|
this.#updatePaths();
|
|
@@ -5952,6 +6309,250 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5952
6309
|
}
|
|
5953
6310
|
customElements.define("fig-easing-curve", FigEasingCurve);
|
|
5954
6311
|
|
|
6312
|
+
/**
|
|
6313
|
+
* A 3D rotation control with an interactive cube preview.
|
|
6314
|
+
* @attr {string} value - CSS transform string, e.g. "rotateX(20deg) rotateY(-35deg) rotateZ(0deg)".
|
|
6315
|
+
* @attr {number} precision - Decimal places for angle output (default 1).
|
|
6316
|
+
*/
|
|
6317
|
+
class Fig3DRotate extends HTMLElement {
|
|
6318
|
+
#rx = 0;
|
|
6319
|
+
#ry = 0;
|
|
6320
|
+
#rz = 0;
|
|
6321
|
+
#precision = 1;
|
|
6322
|
+
#isDragging = false;
|
|
6323
|
+
#isShiftHeld = false;
|
|
6324
|
+
#cube = null;
|
|
6325
|
+
#container = null;
|
|
6326
|
+
#boundKeyDown = null;
|
|
6327
|
+
#boundKeyUp = null;
|
|
6328
|
+
#fields = [];
|
|
6329
|
+
#fieldInputs = {};
|
|
6330
|
+
|
|
6331
|
+
static get observedAttributes() {
|
|
6332
|
+
return ["value", "precision", "aspect-ratio", "fields"];
|
|
6333
|
+
}
|
|
6334
|
+
|
|
6335
|
+
connectedCallback() {
|
|
6336
|
+
this.#precision = parseInt(this.getAttribute("precision") || "1");
|
|
6337
|
+
this.#syncAspectRatioVar(this.getAttribute("aspect-ratio"));
|
|
6338
|
+
this.#parseFields(this.getAttribute("fields"));
|
|
6339
|
+
const val = this.getAttribute("value");
|
|
6340
|
+
if (val) this.#parseValue(val);
|
|
6341
|
+
this.#render();
|
|
6342
|
+
}
|
|
6343
|
+
|
|
6344
|
+
disconnectedCallback() {
|
|
6345
|
+
this.#isDragging = false;
|
|
6346
|
+
if (this.#boundKeyDown) {
|
|
6347
|
+
window.removeEventListener("keydown", this.#boundKeyDown);
|
|
6348
|
+
window.removeEventListener("keyup", this.#boundKeyUp);
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
6351
|
+
|
|
6352
|
+
#syncAspectRatioVar(value) {
|
|
6353
|
+
if (value && value.trim()) {
|
|
6354
|
+
this.style.setProperty("--aspect-ratio", value.trim());
|
|
6355
|
+
} else {
|
|
6356
|
+
this.style.removeProperty("--aspect-ratio");
|
|
6357
|
+
}
|
|
6358
|
+
}
|
|
6359
|
+
|
|
6360
|
+
#parseFields(str) {
|
|
6361
|
+
if (!str || !str.trim()) {
|
|
6362
|
+
this.#fields = [];
|
|
6363
|
+
return;
|
|
6364
|
+
}
|
|
6365
|
+
const valid = ["rotateX", "rotateY", "rotateZ"];
|
|
6366
|
+
this.#fields = str
|
|
6367
|
+
.split(",")
|
|
6368
|
+
.map((s) => s.trim())
|
|
6369
|
+
.filter((s) => valid.includes(s));
|
|
6370
|
+
}
|
|
6371
|
+
|
|
6372
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
6373
|
+
if (name === "aspect-ratio") {
|
|
6374
|
+
this.#syncAspectRatioVar(newValue);
|
|
6375
|
+
return;
|
|
6376
|
+
}
|
|
6377
|
+
if (name === "fields") {
|
|
6378
|
+
this.#parseFields(newValue);
|
|
6379
|
+
if (this.#cube) this.#render();
|
|
6380
|
+
return;
|
|
6381
|
+
}
|
|
6382
|
+
if (!this.#cube) return;
|
|
6383
|
+
if (name === "value" && newValue) {
|
|
6384
|
+
if (this.#isDragging) return;
|
|
6385
|
+
this.#parseValue(newValue);
|
|
6386
|
+
this.#updateCube();
|
|
6387
|
+
this.#syncFieldInputs();
|
|
6388
|
+
}
|
|
6389
|
+
if (name === "precision") {
|
|
6390
|
+
this.#precision = parseInt(newValue || "1");
|
|
6391
|
+
}
|
|
6392
|
+
}
|
|
6393
|
+
|
|
6394
|
+
get value() {
|
|
6395
|
+
const p = this.#precision;
|
|
6396
|
+
return `rotateX(${this.#rx.toFixed(p)}deg) rotateY(${this.#ry.toFixed(p)}deg) rotateZ(${this.#rz.toFixed(p)}deg)`;
|
|
6397
|
+
}
|
|
6398
|
+
|
|
6399
|
+
set value(v) {
|
|
6400
|
+
this.setAttribute("value", v);
|
|
6401
|
+
}
|
|
6402
|
+
|
|
6403
|
+
get rotateX() {
|
|
6404
|
+
return this.#rx;
|
|
6405
|
+
}
|
|
6406
|
+
get rotateY() {
|
|
6407
|
+
return this.#ry;
|
|
6408
|
+
}
|
|
6409
|
+
get rotateZ() {
|
|
6410
|
+
return this.#rz;
|
|
6411
|
+
}
|
|
6412
|
+
|
|
6413
|
+
#parseValue(str) {
|
|
6414
|
+
const rxMatch = str.match(/rotateX\(\s*(-?[\d.]+)\s*deg\s*\)/);
|
|
6415
|
+
const ryMatch = str.match(/rotateY\(\s*(-?[\d.]+)\s*deg\s*\)/);
|
|
6416
|
+
const rzMatch = str.match(/rotateZ\(\s*(-?[\d.]+)\s*deg\s*\)/);
|
|
6417
|
+
if (rxMatch) this.#rx = parseFloat(rxMatch[1]);
|
|
6418
|
+
if (ryMatch) this.#ry = parseFloat(ryMatch[1]);
|
|
6419
|
+
if (rzMatch) this.#rz = parseFloat(rzMatch[1]);
|
|
6420
|
+
}
|
|
6421
|
+
|
|
6422
|
+
#render() {
|
|
6423
|
+
const axisLabels = { rotateX: "X", rotateY: "Y", rotateZ: "Z" };
|
|
6424
|
+
const axisValues = { rotateX: this.#rx, rotateY: this.#ry, rotateZ: this.#rz };
|
|
6425
|
+
const fieldsHTML = this.#fields
|
|
6426
|
+
.map(
|
|
6427
|
+
(axis) =>
|
|
6428
|
+
`<fig-input-number
|
|
6429
|
+
name="${axis}"
|
|
6430
|
+
step="1"
|
|
6431
|
+
precision="1"
|
|
6432
|
+
value="${axisValues[axis]}"
|
|
6433
|
+
units="°">
|
|
6434
|
+
<span slot="prepend">${axisLabels[axis]}</span>
|
|
6435
|
+
</fig-input-number>`,
|
|
6436
|
+
)
|
|
6437
|
+
.join("");
|
|
6438
|
+
|
|
6439
|
+
this.innerHTML = `<div class="fig-3d-rotate-container" tabindex="0">
|
|
6440
|
+
<div class="fig-3d-rotate-scene">
|
|
6441
|
+
<div class="fig-3d-rotate-cube">
|
|
6442
|
+
<div class="fig-3d-rotate-face front"></div>
|
|
6443
|
+
<div class="fig-3d-rotate-face back"></div>
|
|
6444
|
+
<div class="fig-3d-rotate-face right"></div>
|
|
6445
|
+
<div class="fig-3d-rotate-face left"></div>
|
|
6446
|
+
<div class="fig-3d-rotate-face top"></div>
|
|
6447
|
+
<div class="fig-3d-rotate-face bottom"></div>
|
|
6448
|
+
</div>
|
|
6449
|
+
</div>
|
|
6450
|
+
</div>${fieldsHTML}`;
|
|
6451
|
+
this.#container = this.querySelector(".fig-3d-rotate-container");
|
|
6452
|
+
this.#cube = this.querySelector(".fig-3d-rotate-cube");
|
|
6453
|
+
this.#fieldInputs = {};
|
|
6454
|
+
for (const axis of this.#fields) {
|
|
6455
|
+
const input = this.querySelector(`fig-input-number[name="${axis}"]`);
|
|
6456
|
+
if (input) {
|
|
6457
|
+
this.#fieldInputs[axis] = input;
|
|
6458
|
+
const handleFieldValue = (e) => {
|
|
6459
|
+
e.stopPropagation();
|
|
6460
|
+
const val = parseFloat(e.target.value);
|
|
6461
|
+
if (isNaN(val)) return;
|
|
6462
|
+
if (axis === "rotateX") this.#rx = val;
|
|
6463
|
+
else if (axis === "rotateY") this.#ry = val;
|
|
6464
|
+
else if (axis === "rotateZ") this.#rz = val;
|
|
6465
|
+
this.#updateCube();
|
|
6466
|
+
this.#emit(e.type);
|
|
6467
|
+
};
|
|
6468
|
+
input.addEventListener("input", handleFieldValue);
|
|
6469
|
+
input.addEventListener("change", handleFieldValue);
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
this.#updateCube();
|
|
6473
|
+
this.#setupEvents();
|
|
6474
|
+
}
|
|
6475
|
+
|
|
6476
|
+
#syncFieldInputs() {
|
|
6477
|
+
const axisValues = { rotateX: this.#rx, rotateY: this.#ry, rotateZ: this.#rz };
|
|
6478
|
+
for (const axis of this.#fields) {
|
|
6479
|
+
const input = this.#fieldInputs[axis];
|
|
6480
|
+
if (input) input.setAttribute("value", axisValues[axis].toFixed(this.#precision));
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
|
|
6484
|
+
#updateCube() {
|
|
6485
|
+
if (!this.#cube) return;
|
|
6486
|
+
this.#cube.style.transform =
|
|
6487
|
+
`rotateX(${this.#rx}deg) rotateY(${this.#ry}deg) rotateZ(${this.#rz}deg)`;
|
|
6488
|
+
}
|
|
6489
|
+
|
|
6490
|
+
#emit(type) {
|
|
6491
|
+
this.dispatchEvent(
|
|
6492
|
+
new CustomEvent(type, {
|
|
6493
|
+
bubbles: true,
|
|
6494
|
+
detail: {
|
|
6495
|
+
value: this.value,
|
|
6496
|
+
rotateX: this.#rx,
|
|
6497
|
+
rotateY: this.#ry,
|
|
6498
|
+
rotateZ: this.#rz,
|
|
6499
|
+
},
|
|
6500
|
+
}),
|
|
6501
|
+
);
|
|
6502
|
+
}
|
|
6503
|
+
|
|
6504
|
+
#snapToIncrement(angle) {
|
|
6505
|
+
if (!this.#isShiftHeld) return angle;
|
|
6506
|
+
return Math.round(angle / 15) * 15;
|
|
6507
|
+
}
|
|
6508
|
+
|
|
6509
|
+
#setupEvents() {
|
|
6510
|
+
this.#container.addEventListener("pointerdown", (e) => this.#startDrag(e));
|
|
6511
|
+
this.#boundKeyDown = (e) => {
|
|
6512
|
+
if (e.key === "Shift") this.#isShiftHeld = true;
|
|
6513
|
+
};
|
|
6514
|
+
this.#boundKeyUp = (e) => {
|
|
6515
|
+
if (e.key === "Shift") this.#isShiftHeld = false;
|
|
6516
|
+
};
|
|
6517
|
+
window.addEventListener("keydown", this.#boundKeyDown);
|
|
6518
|
+
window.addEventListener("keyup", this.#boundKeyUp);
|
|
6519
|
+
}
|
|
6520
|
+
|
|
6521
|
+
#startDrag(e) {
|
|
6522
|
+
e.preventDefault();
|
|
6523
|
+
this.#isDragging = true;
|
|
6524
|
+
this.#container.classList.add("dragging");
|
|
6525
|
+
|
|
6526
|
+
const startX = e.clientX;
|
|
6527
|
+
const startY = e.clientY;
|
|
6528
|
+
const startRx = this.#rx;
|
|
6529
|
+
const startRy = this.#ry;
|
|
6530
|
+
|
|
6531
|
+
const onMove = (e) => {
|
|
6532
|
+
if (!this.#isDragging) return;
|
|
6533
|
+
const dx = e.clientX - startX;
|
|
6534
|
+
const dy = e.clientY - startY;
|
|
6535
|
+
this.#ry = this.#snapToIncrement(startRy + dx * 0.5);
|
|
6536
|
+
this.#rx = this.#snapToIncrement(startRx - dy * 0.5);
|
|
6537
|
+
this.#updateCube();
|
|
6538
|
+
this.#syncFieldInputs();
|
|
6539
|
+
this.#emit("input");
|
|
6540
|
+
};
|
|
6541
|
+
|
|
6542
|
+
const onUp = () => {
|
|
6543
|
+
this.#isDragging = false;
|
|
6544
|
+
this.#container.classList.remove("dragging");
|
|
6545
|
+
document.removeEventListener("pointermove", onMove);
|
|
6546
|
+
document.removeEventListener("pointerup", onUp);
|
|
6547
|
+
this.#emit("change");
|
|
6548
|
+
};
|
|
6549
|
+
|
|
6550
|
+
document.addEventListener("pointermove", onMove);
|
|
6551
|
+
document.addEventListener("pointerup", onUp);
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6554
|
+
customElements.define("fig-3d-rotate", Fig3DRotate);
|
|
6555
|
+
|
|
5955
6556
|
/**
|
|
5956
6557
|
* A custom joystick input element.
|
|
5957
6558
|
* @attr {string} value - The current position of the joystick (e.g., "0.5,0.5").
|
|
@@ -6045,7 +6646,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
6045
6646
|
this.plane.addEventListener("mousedown", this.#handleMouseDown.bind(this));
|
|
6046
6647
|
this.plane.addEventListener(
|
|
6047
6648
|
"touchstart",
|
|
6048
|
-
this.#handleTouchStart.bind(this)
|
|
6649
|
+
this.#handleTouchStart.bind(this),
|
|
6049
6650
|
);
|
|
6050
6651
|
window.addEventListener("keydown", this.#handleKeyDown.bind(this));
|
|
6051
6652
|
window.addEventListener("keyup", this.#handleKeyUp.bind(this));
|
|
@@ -6105,7 +6706,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
6105
6706
|
let x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
|
|
6106
6707
|
let screenY = Math.max(
|
|
6107
6708
|
0,
|
|
6108
|
-
Math.min(1, (e.clientY - rect.top) / rect.height)
|
|
6709
|
+
Math.min(1, (e.clientY - rect.top) / rect.height),
|
|
6109
6710
|
);
|
|
6110
6711
|
|
|
6111
6712
|
// Convert screen Y to internal Y (flip for math coordinates)
|
|
@@ -6133,7 +6734,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
6133
6734
|
new CustomEvent("input", {
|
|
6134
6735
|
bubbles: true,
|
|
6135
6736
|
cancelable: true,
|
|
6136
|
-
})
|
|
6737
|
+
}),
|
|
6137
6738
|
);
|
|
6138
6739
|
}
|
|
6139
6740
|
|
|
@@ -6142,7 +6743,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
6142
6743
|
new CustomEvent("change", {
|
|
6143
6744
|
bubbles: true,
|
|
6144
6745
|
cancelable: true,
|
|
6145
|
-
})
|
|
6746
|
+
}),
|
|
6146
6747
|
);
|
|
6147
6748
|
}
|
|
6148
6749
|
|
|
@@ -6317,8 +6918,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
6317
6918
|
this.max = this.hasAttribute("max")
|
|
6318
6919
|
? Number(this.getAttribute("max"))
|
|
6319
6920
|
: null;
|
|
6320
|
-
this.showRotations =
|
|
6321
|
-
this.getAttribute("show-rotations") === "true";
|
|
6921
|
+
this.showRotations = this.getAttribute("show-rotations") === "true";
|
|
6322
6922
|
|
|
6323
6923
|
this.#render();
|
|
6324
6924
|
this.#setupListeners();
|
|
@@ -6327,7 +6927,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
6327
6927
|
if (this.text && this.angleInput) {
|
|
6328
6928
|
this.angleInput.setAttribute(
|
|
6329
6929
|
"value",
|
|
6330
|
-
this.angle.toFixed(this.precision)
|
|
6930
|
+
this.angle.toFixed(this.precision),
|
|
6331
6931
|
);
|
|
6332
6932
|
}
|
|
6333
6933
|
});
|
|
@@ -6452,14 +7052,14 @@ class FigInputAngle extends HTMLElement {
|
|
|
6452
7052
|
this.plane.addEventListener("mousedown", this.#handleMouseDown.bind(this));
|
|
6453
7053
|
this.plane.addEventListener(
|
|
6454
7054
|
"touchstart",
|
|
6455
|
-
this.#handleTouchStart.bind(this)
|
|
7055
|
+
this.#handleTouchStart.bind(this),
|
|
6456
7056
|
);
|
|
6457
7057
|
window.addEventListener("keydown", this.#handleKeyDown.bind(this));
|
|
6458
7058
|
window.addEventListener("keyup", this.#handleKeyUp.bind(this));
|
|
6459
7059
|
if (this.text && this.angleInput) {
|
|
6460
7060
|
this.angleInput.addEventListener(
|
|
6461
7061
|
"input",
|
|
6462
|
-
this.#handleAngleInput.bind(this)
|
|
7062
|
+
this.#handleAngleInput.bind(this),
|
|
6463
7063
|
);
|
|
6464
7064
|
}
|
|
6465
7065
|
// Capture-phase listener for unit suffix parsing
|
|
@@ -6577,7 +7177,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
6577
7177
|
new CustomEvent("input", {
|
|
6578
7178
|
bubbles: true,
|
|
6579
7179
|
cancelable: true,
|
|
6580
|
-
})
|
|
7180
|
+
}),
|
|
6581
7181
|
);
|
|
6582
7182
|
}
|
|
6583
7183
|
|
|
@@ -6586,7 +7186,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
6586
7186
|
new CustomEvent("change", {
|
|
6587
7187
|
bubbles: true,
|
|
6588
7188
|
cancelable: true,
|
|
6589
|
-
})
|
|
7189
|
+
}),
|
|
6590
7190
|
);
|
|
6591
7191
|
}
|
|
6592
7192
|
|
|
@@ -6669,7 +7269,15 @@ class FigInputAngle extends HTMLElement {
|
|
|
6669
7269
|
// --- Attributes ---
|
|
6670
7270
|
|
|
6671
7271
|
static get observedAttributes() {
|
|
6672
|
-
return [
|
|
7272
|
+
return [
|
|
7273
|
+
"value",
|
|
7274
|
+
"precision",
|
|
7275
|
+
"text",
|
|
7276
|
+
"min",
|
|
7277
|
+
"max",
|
|
7278
|
+
"units",
|
|
7279
|
+
"show-rotations",
|
|
7280
|
+
];
|
|
6673
7281
|
}
|
|
6674
7282
|
|
|
6675
7283
|
get value() {
|
|
@@ -6852,7 +7460,7 @@ class FigLayer extends HTMLElement {
|
|
|
6852
7460
|
new CustomEvent("openchange", {
|
|
6853
7461
|
detail: { open: value },
|
|
6854
7462
|
bubbles: true,
|
|
6855
|
-
})
|
|
7463
|
+
}),
|
|
6856
7464
|
);
|
|
6857
7465
|
}
|
|
6858
7466
|
}
|
|
@@ -6874,7 +7482,7 @@ class FigLayer extends HTMLElement {
|
|
|
6874
7482
|
new CustomEvent("visibilitychange", {
|
|
6875
7483
|
detail: { visible: value },
|
|
6876
7484
|
bubbles: true,
|
|
6877
|
-
})
|
|
7485
|
+
}),
|
|
6878
7486
|
);
|
|
6879
7487
|
}
|
|
6880
7488
|
}
|
|
@@ -6888,7 +7496,7 @@ class FigLayer extends HTMLElement {
|
|
|
6888
7496
|
new CustomEvent("openchange", {
|
|
6889
7497
|
detail: { open: isOpen },
|
|
6890
7498
|
bubbles: true,
|
|
6891
|
-
})
|
|
7499
|
+
}),
|
|
6892
7500
|
);
|
|
6893
7501
|
}
|
|
6894
7502
|
|
|
@@ -6898,7 +7506,7 @@ class FigLayer extends HTMLElement {
|
|
|
6898
7506
|
new CustomEvent("visibilitychange", {
|
|
6899
7507
|
detail: { visible: isVisible },
|
|
6900
7508
|
bubbles: true,
|
|
6901
|
-
})
|
|
7509
|
+
}),
|
|
6902
7510
|
);
|
|
6903
7511
|
}
|
|
6904
7512
|
}
|
|
@@ -6978,7 +7586,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
6978
7586
|
|
|
6979
7587
|
#setupTrigger() {
|
|
6980
7588
|
const child = Array.from(this.children).find(
|
|
6981
|
-
(el) => !el.getAttribute("slot")?.startsWith("mode-")
|
|
7589
|
+
(el) => !el.getAttribute("slot")?.startsWith("mode-"),
|
|
6982
7590
|
);
|
|
6983
7591
|
|
|
6984
7592
|
if (!child) {
|
|
@@ -7077,7 +7685,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7077
7685
|
bg = `url(${this.#image.url})`;
|
|
7078
7686
|
const sizing = this.#getBackgroundSizing(
|
|
7079
7687
|
this.#image.scaleMode,
|
|
7080
|
-
this.#image.scale
|
|
7688
|
+
this.#image.scale,
|
|
7081
7689
|
);
|
|
7082
7690
|
bgSize = sizing.size;
|
|
7083
7691
|
bgPosition = sizing.position;
|
|
@@ -7090,7 +7698,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7090
7698
|
bg = `url(${this.#video.url})`;
|
|
7091
7699
|
const sizing = this.#getBackgroundSizing(
|
|
7092
7700
|
this.#video.scaleMode,
|
|
7093
|
-
this.#video.scale
|
|
7701
|
+
this.#video.scale,
|
|
7094
7702
|
);
|
|
7095
7703
|
bgSize = sizing.size;
|
|
7096
7704
|
bgPosition = sizing.position;
|
|
@@ -7185,7 +7793,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7185
7793
|
if (mode) {
|
|
7186
7794
|
const requested = mode.split(",").map((m) => m.trim().toLowerCase());
|
|
7187
7795
|
allowedModes = requested.filter(
|
|
7188
|
-
(m) => builtinModes.includes(m) || this.#customSlots[m]
|
|
7796
|
+
(m) => builtinModes.includes(m) || this.#customSlots[m],
|
|
7189
7797
|
);
|
|
7190
7798
|
if (allowedModes.length === 0) allowedModes = [...builtinModes];
|
|
7191
7799
|
} else {
|
|
@@ -7239,9 +7847,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7239
7847
|
|
|
7240
7848
|
// Populate custom tab containers and emit modeready
|
|
7241
7849
|
for (const [modeName, { element }] of Object.entries(this.#customSlots)) {
|
|
7242
|
-
const container = this.#dialog.querySelector(
|
|
7243
|
-
`[data-tab="${modeName}"]`
|
|
7244
|
-
);
|
|
7850
|
+
const container = this.#dialog.querySelector(`[data-tab="${modeName}"]`);
|
|
7245
7851
|
if (!container) continue;
|
|
7246
7852
|
|
|
7247
7853
|
// Move children (not the element itself) for vanilla HTML usage
|
|
@@ -7254,7 +7860,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7254
7860
|
new CustomEvent("modeready", {
|
|
7255
7861
|
bubbles: true,
|
|
7256
7862
|
detail: { mode: modeName, container },
|
|
7257
|
-
})
|
|
7863
|
+
}),
|
|
7258
7864
|
);
|
|
7259
7865
|
}
|
|
7260
7866
|
|
|
@@ -7291,9 +7897,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7291
7897
|
// Listen for input/change from custom tab content
|
|
7292
7898
|
for (const modeName of Object.keys(this.#customSlots)) {
|
|
7293
7899
|
if (builtinModes.includes(modeName)) continue;
|
|
7294
|
-
const container = this.#dialog.querySelector(
|
|
7295
|
-
`[data-tab="${modeName}"]`
|
|
7296
|
-
);
|
|
7900
|
+
const container = this.#dialog.querySelector(`[data-tab="${modeName}"]`);
|
|
7297
7901
|
if (!container) continue;
|
|
7298
7902
|
container.addEventListener("input", (e) => {
|
|
7299
7903
|
if (e.target === this) return;
|
|
@@ -7313,7 +7917,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7313
7917
|
#switchTab(tabName) {
|
|
7314
7918
|
// Only allow switching to modes that have a tab container in the dialog
|
|
7315
7919
|
const tab = this.#dialog?.querySelector(
|
|
7316
|
-
`.fig-fill-picker-tab[data-tab="${tabName}"]
|
|
7920
|
+
`.fig-fill-picker-tab[data-tab="${tabName}"]`,
|
|
7317
7921
|
);
|
|
7318
7922
|
if (!tab) return;
|
|
7319
7923
|
|
|
@@ -7379,7 +7983,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7379
7983
|
<div class="fig-fill-picker-inputs">
|
|
7380
7984
|
<fig-button icon variant="ghost" class="fig-fill-picker-eyedropper" title="Pick color from screen"><span class="fig-mask-icon" style="--icon: var(--icon-eyedropper)"></span></fig-button>
|
|
7381
7985
|
<fig-input-color class="fig-fill-picker-color-input" text="true" picker="false" value="${this.#hsvToHex(
|
|
7382
|
-
this.#color
|
|
7986
|
+
this.#color,
|
|
7383
7987
|
)}"></fig-input-color>
|
|
7384
7988
|
</div>
|
|
7385
7989
|
`;
|
|
@@ -7407,7 +8011,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7407
8011
|
// Setup opacity slider
|
|
7408
8012
|
if (showAlpha) {
|
|
7409
8013
|
this.#opacitySlider = container.querySelector(
|
|
7410
|
-
'fig-slider[type="opacity"]'
|
|
8014
|
+
'fig-slider[type="opacity"]',
|
|
7411
8015
|
);
|
|
7412
8016
|
this.#opacitySlider.addEventListener("input", (e) => {
|
|
7413
8017
|
this.#color.a = parseFloat(e.target.value) / 100;
|
|
@@ -7500,13 +8104,13 @@ class FigFillPicker extends HTMLElement {
|
|
|
7500
8104
|
if (!this.#colorAreaHandle || !this.#colorArea) return;
|
|
7501
8105
|
|
|
7502
8106
|
const rect = this.#colorArea.getBoundingClientRect();
|
|
7503
|
-
|
|
8107
|
+
|
|
7504
8108
|
// If the canvas isn't visible yet (0 dimensions), schedule a retry (max 5 attempts)
|
|
7505
8109
|
if ((rect.width === 0 || rect.height === 0) && retryCount < 5) {
|
|
7506
8110
|
requestAnimationFrame(() => this.#updateHandlePosition(retryCount + 1));
|
|
7507
8111
|
return;
|
|
7508
8112
|
}
|
|
7509
|
-
|
|
8113
|
+
|
|
7510
8114
|
const x = (this.#color.s / 100) * rect.width;
|
|
7511
8115
|
const y = ((100 - this.#color.v) / 100) * rect.height;
|
|
7512
8116
|
|
|
@@ -7514,7 +8118,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7514
8118
|
this.#colorAreaHandle.style.top = `${y}px`;
|
|
7515
8119
|
this.#colorAreaHandle.style.setProperty(
|
|
7516
8120
|
"--picker-color",
|
|
7517
|
-
this.#hsvToHex({ ...this.#color, a: 1 })
|
|
8121
|
+
this.#hsvToHex({ ...this.#color, a: 1 }),
|
|
7518
8122
|
);
|
|
7519
8123
|
}
|
|
7520
8124
|
|
|
@@ -7577,7 +8181,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7577
8181
|
const hex = this.#hsvToHex(this.#color);
|
|
7578
8182
|
|
|
7579
8183
|
const colorInput = this.#dialog.querySelector(
|
|
7580
|
-
".fig-fill-picker-color-input"
|
|
8184
|
+
".fig-fill-picker-color-input",
|
|
7581
8185
|
);
|
|
7582
8186
|
if (colorInput) {
|
|
7583
8187
|
colorInput.setAttribute("value", hex);
|
|
@@ -7646,7 +8250,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7646
8250
|
#setupGradientEvents(container) {
|
|
7647
8251
|
// Type dropdown
|
|
7648
8252
|
const typeDropdown = container.querySelector(
|
|
7649
|
-
".fig-fill-picker-gradient-type"
|
|
8253
|
+
".fig-fill-picker-gradient-type",
|
|
7650
8254
|
);
|
|
7651
8255
|
typeDropdown.addEventListener("change", (e) => {
|
|
7652
8256
|
this.#gradient.type = e.target.value;
|
|
@@ -7657,7 +8261,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7657
8261
|
// Angle input
|
|
7658
8262
|
// Convert from fig-input-angle coordinates (0° = right) to CSS coordinates (0° = up)
|
|
7659
8263
|
const angleInput = container.querySelector(
|
|
7660
|
-
".fig-fill-picker-gradient-angle"
|
|
8264
|
+
".fig-fill-picker-gradient-angle",
|
|
7661
8265
|
);
|
|
7662
8266
|
angleInput.addEventListener("input", (e) => {
|
|
7663
8267
|
const pickerAngle = parseFloat(e.target.value) || 0;
|
|
@@ -7716,10 +8320,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
7716
8320
|
|
|
7717
8321
|
// Show/hide angle vs center inputs
|
|
7718
8322
|
const angleInput = container.querySelector(
|
|
7719
|
-
".fig-fill-picker-gradient-angle"
|
|
8323
|
+
".fig-fill-picker-gradient-angle",
|
|
7720
8324
|
);
|
|
7721
8325
|
const centerInputs = container.querySelector(
|
|
7722
|
-
".fig-fill-picker-gradient-center"
|
|
8326
|
+
".fig-fill-picker-gradient-center",
|
|
7723
8327
|
);
|
|
7724
8328
|
|
|
7725
8329
|
if (this.#gradient.type === "radial") {
|
|
@@ -7752,7 +8356,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7752
8356
|
if (!this.#dialog) return;
|
|
7753
8357
|
|
|
7754
8358
|
const list = this.#dialog.querySelector(
|
|
7755
|
-
".fig-fill-picker-gradient-stops-list"
|
|
8359
|
+
".fig-fill-picker-gradient-stops-list",
|
|
7756
8360
|
);
|
|
7757
8361
|
if (!list) return;
|
|
7758
8362
|
|
|
@@ -7772,7 +8376,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7772
8376
|
<span class="fig-mask-icon" style="--icon: var(--icon-minus)"></span>
|
|
7773
8377
|
</fig-button>
|
|
7774
8378
|
</div>
|
|
7775
|
-
|
|
8379
|
+
`,
|
|
7776
8380
|
)
|
|
7777
8381
|
.join("");
|
|
7778
8382
|
|
|
@@ -7803,14 +8407,15 @@ class FigFillPicker extends HTMLElement {
|
|
|
7803
8407
|
}
|
|
7804
8408
|
|
|
7805
8409
|
stopColor.addEventListener("input", (e) => {
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
8410
|
+
this.#gradient.stops[index].color =
|
|
8411
|
+
e.target.hexOpaque || e.target.value;
|
|
8412
|
+
const parsedAlpha = parseFloat(e.target.alpha);
|
|
8413
|
+
this.#gradient.stops[index].opacity = isNaN(parsedAlpha)
|
|
8414
|
+
? 100
|
|
8415
|
+
: parsedAlpha;
|
|
8416
|
+
this.#updateGradientPreview();
|
|
8417
|
+
this.#emitInput();
|
|
8418
|
+
});
|
|
7814
8419
|
|
|
7815
8420
|
row
|
|
7816
8421
|
.querySelector(".fig-fill-picker-stop-remove")
|
|
@@ -7883,7 +8488,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7883
8488
|
|
|
7884
8489
|
#setupImageEvents(container) {
|
|
7885
8490
|
const scaleModeDropdown = container.querySelector(
|
|
7886
|
-
".fig-fill-picker-scale-mode"
|
|
8491
|
+
".fig-fill-picker-scale-mode",
|
|
7887
8492
|
);
|
|
7888
8493
|
const scaleInput = container.querySelector(".fig-fill-picker-scale");
|
|
7889
8494
|
const uploadBtn = container.querySelector(".fig-fill-picker-upload");
|
|
@@ -7925,7 +8530,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7925
8530
|
|
|
7926
8531
|
// Drag and drop
|
|
7927
8532
|
const previewArea = container.querySelector(
|
|
7928
|
-
".fig-fill-picker-media-preview"
|
|
8533
|
+
".fig-fill-picker-media-preview",
|
|
7929
8534
|
);
|
|
7930
8535
|
previewArea.addEventListener("dragover", (e) => {
|
|
7931
8536
|
e.preventDefault();
|
|
@@ -8033,7 +8638,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8033
8638
|
|
|
8034
8639
|
#setupVideoEvents(container) {
|
|
8035
8640
|
const scaleModeDropdown = container.querySelector(
|
|
8036
|
-
".fig-fill-picker-scale-mode"
|
|
8641
|
+
".fig-fill-picker-scale-mode",
|
|
8037
8642
|
);
|
|
8038
8643
|
const uploadBtn = container.querySelector(".fig-fill-picker-upload");
|
|
8039
8644
|
const fileInput = container.querySelector('input[type="file"]');
|
|
@@ -8052,7 +8657,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8052
8657
|
|
|
8053
8658
|
// Drag and drop
|
|
8054
8659
|
const previewArea = container.querySelector(
|
|
8055
|
-
".fig-fill-picker-media-preview"
|
|
8660
|
+
".fig-fill-picker-media-preview",
|
|
8056
8661
|
);
|
|
8057
8662
|
|
|
8058
8663
|
fileInput.addEventListener("change", (e) => {
|
|
@@ -8123,10 +8728,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
8123
8728
|
const video = container.querySelector(".fig-fill-picker-webcam-video");
|
|
8124
8729
|
const status = container.querySelector(".fig-fill-picker-webcam-status");
|
|
8125
8730
|
const captureBtn = container.querySelector(
|
|
8126
|
-
".fig-fill-picker-webcam-capture"
|
|
8731
|
+
".fig-fill-picker-webcam-capture",
|
|
8127
8732
|
);
|
|
8128
8733
|
const cameraSelect = container.querySelector(
|
|
8129
|
-
".fig-fill-picker-camera-select"
|
|
8734
|
+
".fig-fill-picker-camera-select",
|
|
8130
8735
|
);
|
|
8131
8736
|
|
|
8132
8737
|
const startWebcam = async (deviceId = null) => {
|
|
@@ -8139,9 +8744,8 @@ class FigFillPicker extends HTMLElement {
|
|
|
8139
8744
|
this.#webcam.stream.getTracks().forEach((track) => track.stop());
|
|
8140
8745
|
}
|
|
8141
8746
|
|
|
8142
|
-
this.#webcam.stream =
|
|
8143
|
-
constraints
|
|
8144
|
-
);
|
|
8747
|
+
this.#webcam.stream =
|
|
8748
|
+
await navigator.mediaDevices.getUserMedia(constraints);
|
|
8145
8749
|
video.srcObject = this.#webcam.stream;
|
|
8146
8750
|
video.style.display = "block";
|
|
8147
8751
|
status.style.display = "none";
|
|
@@ -8157,7 +8761,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8157
8761
|
(cam, i) =>
|
|
8158
8762
|
`<option value="${cam.deviceId}">${
|
|
8159
8763
|
cam.label || `Camera ${i + 1}`
|
|
8160
|
-
}</option
|
|
8764
|
+
}</option>`,
|
|
8161
8765
|
)
|
|
8162
8766
|
.join("");
|
|
8163
8767
|
}
|
|
@@ -8449,7 +9053,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8449
9053
|
new CustomEvent("input", {
|
|
8450
9054
|
bubbles: true,
|
|
8451
9055
|
detail: this.value,
|
|
8452
|
-
})
|
|
9056
|
+
}),
|
|
8453
9057
|
);
|
|
8454
9058
|
}
|
|
8455
9059
|
|
|
@@ -8458,7 +9062,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8458
9062
|
new CustomEvent("change", {
|
|
8459
9063
|
bubbles: true,
|
|
8460
9064
|
detail: this.value,
|
|
8461
|
-
})
|
|
9065
|
+
}),
|
|
8462
9066
|
);
|
|
8463
9067
|
}
|
|
8464
9068
|
|
|
@@ -8530,7 +9134,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8530
9134
|
this.#opacitySlider.setAttribute("value", this.#color.a * 100);
|
|
8531
9135
|
this.#opacitySlider.setAttribute(
|
|
8532
9136
|
"color",
|
|
8533
|
-
this.#hsvToHex(this.#color)
|
|
9137
|
+
this.#hsvToHex(this.#color),
|
|
8534
9138
|
);
|
|
8535
9139
|
}
|
|
8536
9140
|
}
|