@rogieking/figui3 2.28.0 → 2.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -23
- package/components.css +250 -25
- package/dist/fig.js +367 -0
- package/fig.js +919 -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.
|
|
@@ -1086,7 +1121,11 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1086
1121
|
super();
|
|
1087
1122
|
this.#boundReposition = this.#queueReposition.bind(this);
|
|
1088
1123
|
this.#boundScroll = (e) => {
|
|
1089
|
-
if (
|
|
1124
|
+
if (
|
|
1125
|
+
this.open &&
|
|
1126
|
+
!this.contains(e.target) &&
|
|
1127
|
+
this.#shouldAutoReposition()
|
|
1128
|
+
) {
|
|
1090
1129
|
this.#positionPopup();
|
|
1091
1130
|
}
|
|
1092
1131
|
};
|
|
@@ -1097,7 +1136,18 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1097
1136
|
}
|
|
1098
1137
|
|
|
1099
1138
|
static get observedAttributes() {
|
|
1100
|
-
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
|
+
];
|
|
1101
1151
|
}
|
|
1102
1152
|
|
|
1103
1153
|
get open() {
|
|
@@ -1168,7 +1218,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1168
1218
|
document.removeEventListener(
|
|
1169
1219
|
"pointerdown",
|
|
1170
1220
|
this.#boundOutsidePointerDown,
|
|
1171
|
-
true
|
|
1221
|
+
true,
|
|
1172
1222
|
);
|
|
1173
1223
|
if (this.#rafId !== null) {
|
|
1174
1224
|
cancelAnimationFrame(this.#rafId);
|
|
@@ -1224,7 +1274,11 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1224
1274
|
}
|
|
1225
1275
|
|
|
1226
1276
|
this.#setupObservers();
|
|
1227
|
-
document.addEventListener(
|
|
1277
|
+
document.addEventListener(
|
|
1278
|
+
"pointerdown",
|
|
1279
|
+
this.#boundOutsidePointerDown,
|
|
1280
|
+
true,
|
|
1281
|
+
);
|
|
1228
1282
|
this.#wasDragged = false;
|
|
1229
1283
|
this.#queueReposition();
|
|
1230
1284
|
this.#isPopupActive = true;
|
|
@@ -1243,7 +1297,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1243
1297
|
document.removeEventListener(
|
|
1244
1298
|
"pointerdown",
|
|
1245
1299
|
this.#boundOutsidePointerDown,
|
|
1246
|
-
true
|
|
1300
|
+
true,
|
|
1247
1301
|
);
|
|
1248
1302
|
|
|
1249
1303
|
if (super.open) {
|
|
@@ -1284,7 +1338,10 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1284
1338
|
}
|
|
1285
1339
|
|
|
1286
1340
|
window.addEventListener("resize", this.#boundReposition);
|
|
1287
|
-
window.addEventListener("scroll", this.#boundScroll, {
|
|
1341
|
+
window.addEventListener("scroll", this.#boundScroll, {
|
|
1342
|
+
capture: true,
|
|
1343
|
+
passive: true,
|
|
1344
|
+
});
|
|
1288
1345
|
}
|
|
1289
1346
|
|
|
1290
1347
|
#teardownObservers() {
|
|
@@ -1301,7 +1358,10 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1301
1358
|
this.#mutationObserver = null;
|
|
1302
1359
|
}
|
|
1303
1360
|
window.removeEventListener("resize", this.#boundReposition);
|
|
1304
|
-
window.removeEventListener("scroll", this.#boundScroll, {
|
|
1361
|
+
window.removeEventListener("scroll", this.#boundScroll, {
|
|
1362
|
+
capture: true,
|
|
1363
|
+
passive: true,
|
|
1364
|
+
});
|
|
1305
1365
|
}
|
|
1306
1366
|
|
|
1307
1367
|
#handleOutsidePointerDown(event) {
|
|
@@ -1352,15 +1412,31 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1352
1412
|
|
|
1353
1413
|
#isInteractiveElement(element) {
|
|
1354
1414
|
const interactiveSelectors = [
|
|
1355
|
-
"input",
|
|
1356
|
-
"
|
|
1357
|
-
|
|
1415
|
+
"input",
|
|
1416
|
+
"button",
|
|
1417
|
+
"select",
|
|
1418
|
+
"textarea",
|
|
1419
|
+
"a",
|
|
1420
|
+
"label",
|
|
1421
|
+
"details",
|
|
1422
|
+
"summary",
|
|
1423
|
+
'[contenteditable="true"]',
|
|
1424
|
+
"[tabindex]",
|
|
1358
1425
|
];
|
|
1359
1426
|
|
|
1360
1427
|
const nonInteractiveFigElements = [
|
|
1361
|
-
"FIG-HEADER",
|
|
1362
|
-
"FIG-
|
|
1363
|
-
"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",
|
|
1364
1440
|
];
|
|
1365
1441
|
|
|
1366
1442
|
const isInteractive = (el) =>
|
|
@@ -1560,17 +1636,49 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1560
1636
|
|
|
1561
1637
|
#parseViewportMargins() {
|
|
1562
1638
|
const raw = (this.getAttribute("viewport-margin") || "8").trim();
|
|
1563
|
-
const tokens = raw
|
|
1639
|
+
const tokens = raw
|
|
1640
|
+
.split(/\s+/)
|
|
1641
|
+
.map(Number)
|
|
1642
|
+
.filter((n) => !Number.isNaN(n));
|
|
1564
1643
|
const d = 8;
|
|
1565
1644
|
if (tokens.length === 0) return { top: d, right: d, bottom: d, left: d };
|
|
1566
|
-
if (tokens.length === 1)
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
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
|
+
};
|
|
1570
1672
|
}
|
|
1571
1673
|
|
|
1572
1674
|
#getPlacementCandidates(vertical, horizontal, shorthand) {
|
|
1573
|
-
const opp = {
|
|
1675
|
+
const opp = {
|
|
1676
|
+
top: "bottom",
|
|
1677
|
+
bottom: "top",
|
|
1678
|
+
left: "right",
|
|
1679
|
+
right: "left",
|
|
1680
|
+
center: "center",
|
|
1681
|
+
};
|
|
1574
1682
|
|
|
1575
1683
|
if (shorthand) {
|
|
1576
1684
|
const isHorizontal = shorthand === "left" || shorthand === "right";
|
|
@@ -1613,21 +1721,30 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1613
1721
|
];
|
|
1614
1722
|
}
|
|
1615
1723
|
|
|
1616
|
-
#computeCoords(
|
|
1724
|
+
#computeCoords(
|
|
1725
|
+
anchorRect,
|
|
1726
|
+
popupRect,
|
|
1727
|
+
vertical,
|
|
1728
|
+
horizontal,
|
|
1729
|
+
offset,
|
|
1730
|
+
shorthand,
|
|
1731
|
+
) {
|
|
1617
1732
|
let top;
|
|
1618
1733
|
let left;
|
|
1619
1734
|
|
|
1620
1735
|
if (shorthand === "left" || shorthand === "right") {
|
|
1621
|
-
left =
|
|
1622
|
-
|
|
1623
|
-
|
|
1736
|
+
left =
|
|
1737
|
+
shorthand === "left"
|
|
1738
|
+
? anchorRect.left - popupRect.width - offset.xPx
|
|
1739
|
+
: anchorRect.right + offset.xPx;
|
|
1624
1740
|
top = anchorRect.top;
|
|
1625
1741
|
return { top, left };
|
|
1626
1742
|
}
|
|
1627
1743
|
if (shorthand === "top" || shorthand === "bottom") {
|
|
1628
|
-
top =
|
|
1629
|
-
|
|
1630
|
-
|
|
1744
|
+
top =
|
|
1745
|
+
shorthand === "top"
|
|
1746
|
+
? anchorRect.top - popupRect.height - offset.yPx
|
|
1747
|
+
: anchorRect.bottom + offset.yPx;
|
|
1631
1748
|
left = anchorRect.left;
|
|
1632
1749
|
return { top, left };
|
|
1633
1750
|
}
|
|
@@ -1734,8 +1851,14 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1734
1851
|
#clamp(coords, popupRect, m) {
|
|
1735
1852
|
const minLeft = m.left;
|
|
1736
1853
|
const minTop = m.top;
|
|
1737
|
-
const maxLeft = Math.max(
|
|
1738
|
-
|
|
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
|
+
);
|
|
1739
1862
|
|
|
1740
1863
|
return {
|
|
1741
1864
|
left: Math.min(maxLeft, Math.max(minLeft, coords.left)),
|
|
@@ -1755,8 +1878,11 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1755
1878
|
if (!anchor) {
|
|
1756
1879
|
this.#updatePopoverBeak(null, popupRect, 0, 0, "top");
|
|
1757
1880
|
const centered = {
|
|
1758
|
-
left:
|
|
1759
|
-
|
|
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,
|
|
1760
1886
|
};
|
|
1761
1887
|
const clamped = this.#clamp(centered, popupRect, m);
|
|
1762
1888
|
this.style.left = `${clamped.left}px`;
|
|
@@ -1765,24 +1891,44 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1765
1891
|
}
|
|
1766
1892
|
|
|
1767
1893
|
const anchorRect = anchor.getBoundingClientRect();
|
|
1768
|
-
const candidates = this.#getPlacementCandidates(
|
|
1894
|
+
const candidates = this.#getPlacementCandidates(
|
|
1895
|
+
vertical,
|
|
1896
|
+
horizontal,
|
|
1897
|
+
shorthand,
|
|
1898
|
+
);
|
|
1769
1899
|
let best = null;
|
|
1770
1900
|
let bestSide = "top";
|
|
1771
1901
|
let bestScore = Number.POSITIVE_INFINITY;
|
|
1772
1902
|
|
|
1773
1903
|
for (const { v, h, s } of candidates) {
|
|
1774
|
-
const coords = this.#computeCoords(
|
|
1904
|
+
const coords = this.#computeCoords(
|
|
1905
|
+
anchorRect,
|
|
1906
|
+
popupRect,
|
|
1907
|
+
v,
|
|
1908
|
+
h,
|
|
1909
|
+
offset,
|
|
1910
|
+
s,
|
|
1911
|
+
);
|
|
1775
1912
|
const placementSide = this.#getPlacementSide(v, h, s);
|
|
1776
1913
|
|
|
1777
1914
|
if (s) {
|
|
1778
1915
|
const clamped = this.#clamp(coords, popupRect, m);
|
|
1779
|
-
const primaryFits =
|
|
1780
|
-
|
|
1781
|
-
|
|
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;
|
|
1782
1922
|
if (primaryFits) {
|
|
1783
1923
|
this.style.left = `${clamped.left}px`;
|
|
1784
1924
|
this.style.top = `${clamped.top}px`;
|
|
1785
|
-
this.#updatePopoverBeak(
|
|
1925
|
+
this.#updatePopoverBeak(
|
|
1926
|
+
anchorRect,
|
|
1927
|
+
popupRect,
|
|
1928
|
+
clamped.left,
|
|
1929
|
+
clamped.top,
|
|
1930
|
+
placementSide,
|
|
1931
|
+
);
|
|
1786
1932
|
return;
|
|
1787
1933
|
}
|
|
1788
1934
|
const score = this.#overflowScore(coords, popupRect, m);
|
|
@@ -1795,7 +1941,13 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1795
1941
|
if (this.#fits(coords, popupRect, m)) {
|
|
1796
1942
|
this.style.left = `${coords.left}px`;
|
|
1797
1943
|
this.style.top = `${coords.top}px`;
|
|
1798
|
-
this.#updatePopoverBeak(
|
|
1944
|
+
this.#updatePopoverBeak(
|
|
1945
|
+
anchorRect,
|
|
1946
|
+
popupRect,
|
|
1947
|
+
coords.left,
|
|
1948
|
+
coords.top,
|
|
1949
|
+
placementSide,
|
|
1950
|
+
);
|
|
1799
1951
|
return;
|
|
1800
1952
|
}
|
|
1801
1953
|
const score = this.#overflowScore(coords, popupRect, m);
|
|
@@ -1810,7 +1962,13 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1810
1962
|
const clamped = this.#clamp(best || { left: 0, top: 0 }, popupRect, m);
|
|
1811
1963
|
this.style.left = `${clamped.left}px`;
|
|
1812
1964
|
this.style.top = `${clamped.top}px`;
|
|
1813
|
-
this.#updatePopoverBeak(
|
|
1965
|
+
this.#updatePopoverBeak(
|
|
1966
|
+
anchorRect,
|
|
1967
|
+
popupRect,
|
|
1968
|
+
clamped.left,
|
|
1969
|
+
clamped.top,
|
|
1970
|
+
bestSide,
|
|
1971
|
+
);
|
|
1814
1972
|
}
|
|
1815
1973
|
|
|
1816
1974
|
#queueReposition() {
|
|
@@ -1830,69 +1988,6 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1830
1988
|
}
|
|
1831
1989
|
customElements.define("fig-popup", FigPopup, { extends: "dialog" });
|
|
1832
1990
|
|
|
1833
|
-
/**
|
|
1834
|
-
* A popover element using the native Popover API.
|
|
1835
|
-
* @attr {string} trigger-action - The trigger action: "click" (default) or "hover"
|
|
1836
|
-
* @attr {number} delay - Delay in ms before showing on hover (default: 0)
|
|
1837
|
-
*/
|
|
1838
|
-
class FigPopover2 extends HTMLElement {
|
|
1839
|
-
#popover;
|
|
1840
|
-
#trigger;
|
|
1841
|
-
#id;
|
|
1842
|
-
#delay;
|
|
1843
|
-
#timeout;
|
|
1844
|
-
#action;
|
|
1845
|
-
|
|
1846
|
-
constructor() {
|
|
1847
|
-
super();
|
|
1848
|
-
}
|
|
1849
|
-
connectedCallback() {
|
|
1850
|
-
this.#popover = this.querySelector("[popover]");
|
|
1851
|
-
this.#trigger = this;
|
|
1852
|
-
this.#delay = Number(this.getAttribute("delay")) || 0;
|
|
1853
|
-
this.#action = this.getAttribute("trigger-action") || "click";
|
|
1854
|
-
this.#id = `tooltip-${figUniqueId()}`;
|
|
1855
|
-
if (this.#popover) {
|
|
1856
|
-
this.#popover.setAttribute("id", this.#id);
|
|
1857
|
-
this.#popover.setAttribute("role", "tooltip");
|
|
1858
|
-
this.#popover.setAttribute("popover", "manual");
|
|
1859
|
-
this.#popover.style["position-anchor"] = `--${this.#id}`;
|
|
1860
|
-
|
|
1861
|
-
this.#trigger.setAttribute("popovertarget", this.#id);
|
|
1862
|
-
this.#trigger.setAttribute("popovertargetaction", "toggle");
|
|
1863
|
-
this.#trigger.style["anchor-name"] = `--${this.#id}`;
|
|
1864
|
-
|
|
1865
|
-
if (this.#action === "hover") {
|
|
1866
|
-
this.#trigger.addEventListener("mouseover", this.handleOpen.bind(this));
|
|
1867
|
-
this.#trigger.addEventListener("mouseout", this.handleClose.bind(this));
|
|
1868
|
-
} else {
|
|
1869
|
-
this.#trigger.addEventListener("click", this.handleToggle.bind(this));
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
document.body.append(this.#popover);
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
|
-
handleClose() {
|
|
1877
|
-
clearTimeout(this.#timeout);
|
|
1878
|
-
this.#popover.hidePopover();
|
|
1879
|
-
}
|
|
1880
|
-
handleToggle() {
|
|
1881
|
-
if (this.#popover.matches(":popover-open")) {
|
|
1882
|
-
this.handleClose();
|
|
1883
|
-
} else {
|
|
1884
|
-
this.handleOpen();
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
handleOpen() {
|
|
1888
|
-
clearTimeout(this.#timeout);
|
|
1889
|
-
this.#timeout = setTimeout(() => {
|
|
1890
|
-
this.#popover.showPopover();
|
|
1891
|
-
}, this.#delay);
|
|
1892
|
-
}
|
|
1893
|
-
}
|
|
1894
|
-
customElements.define("fig-popover-2", FigPopover2);
|
|
1895
|
-
|
|
1896
1991
|
/* Tabs */
|
|
1897
1992
|
/**
|
|
1898
1993
|
* A custom tab element for use within FigTabs.
|
|
@@ -2173,14 +2268,15 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
2173
2268
|
this.name = this.getAttribute("name") || "segmented-control";
|
|
2174
2269
|
this.addEventListener("click", this.handleClick.bind(this));
|
|
2175
2270
|
this.#applyDisabled(
|
|
2176
|
-
this.hasAttribute("disabled") &&
|
|
2271
|
+
this.hasAttribute("disabled") &&
|
|
2272
|
+
this.getAttribute("disabled") !== "false",
|
|
2177
2273
|
);
|
|
2178
2274
|
|
|
2179
2275
|
// Ensure at least one segment is selected (default to first)
|
|
2180
2276
|
requestAnimationFrame(() => {
|
|
2181
2277
|
const segments = this.querySelectorAll("fig-segment");
|
|
2182
2278
|
const hasSelected = Array.from(segments).some((s) =>
|
|
2183
|
-
s.hasAttribute("selected")
|
|
2279
|
+
s.hasAttribute("selected"),
|
|
2184
2280
|
);
|
|
2185
2281
|
if (!hasSelected && segments.length > 0) {
|
|
2186
2282
|
this.selectedSegment = segments[0];
|
|
@@ -2364,13 +2460,17 @@ class FigSlider extends HTMLElement {
|
|
|
2364
2460
|
this.input.addEventListener("input", this.#boundHandleInput);
|
|
2365
2461
|
this.input.removeEventListener("change", this.#boundHandleChange);
|
|
2366
2462
|
this.input.addEventListener("change", this.#boundHandleChange);
|
|
2367
|
-
this.input.addEventListener("pointerdown", () => {
|
|
2368
|
-
|
|
2463
|
+
this.input.addEventListener("pointerdown", () => {
|
|
2464
|
+
this.#isInteracting = true;
|
|
2465
|
+
});
|
|
2466
|
+
this.input.addEventListener("pointerup", () => {
|
|
2467
|
+
this.#isInteracting = false;
|
|
2468
|
+
});
|
|
2369
2469
|
|
|
2370
2470
|
if (this.default) {
|
|
2371
2471
|
this.style.setProperty(
|
|
2372
2472
|
"--default",
|
|
2373
|
-
this.#calculateNormal(this.default)
|
|
2473
|
+
this.#calculateNormal(this.default),
|
|
2374
2474
|
);
|
|
2375
2475
|
}
|
|
2376
2476
|
|
|
@@ -2380,7 +2480,7 @@ class FigSlider extends HTMLElement {
|
|
|
2380
2480
|
this.inputContainer.append(this.datalist);
|
|
2381
2481
|
this.datalist.setAttribute(
|
|
2382
2482
|
"id",
|
|
2383
|
-
this.datalist.getAttribute("id") || figUniqueId()
|
|
2483
|
+
this.datalist.getAttribute("id") || figUniqueId(),
|
|
2384
2484
|
);
|
|
2385
2485
|
this.input.setAttribute("list", this.datalist.getAttribute("id"));
|
|
2386
2486
|
} else if (this.type === "stepper") {
|
|
@@ -2405,7 +2505,7 @@ class FigSlider extends HTMLElement {
|
|
|
2405
2505
|
}
|
|
2406
2506
|
if (this.datalist) {
|
|
2407
2507
|
let defaultOption = this.datalist.querySelector(
|
|
2408
|
-
`option[value='${this.default}']
|
|
2508
|
+
`option[value='${this.default}']`,
|
|
2409
2509
|
);
|
|
2410
2510
|
if (defaultOption) {
|
|
2411
2511
|
defaultOption.setAttribute("default", "true");
|
|
@@ -2414,19 +2514,19 @@ class FigSlider extends HTMLElement {
|
|
|
2414
2514
|
if (this.figInputNumber) {
|
|
2415
2515
|
this.figInputNumber.removeEventListener(
|
|
2416
2516
|
"input",
|
|
2417
|
-
this.#boundHandleTextInput
|
|
2517
|
+
this.#boundHandleTextInput,
|
|
2418
2518
|
);
|
|
2419
2519
|
this.figInputNumber.addEventListener(
|
|
2420
2520
|
"input",
|
|
2421
|
-
this.#boundHandleTextInput
|
|
2521
|
+
this.#boundHandleTextInput,
|
|
2422
2522
|
);
|
|
2423
2523
|
this.figInputNumber.removeEventListener(
|
|
2424
2524
|
"change",
|
|
2425
|
-
this.#boundHandleTextChange
|
|
2525
|
+
this.#boundHandleTextChange,
|
|
2426
2526
|
);
|
|
2427
2527
|
this.figInputNumber.addEventListener(
|
|
2428
2528
|
"change",
|
|
2429
|
-
this.#boundHandleTextChange
|
|
2529
|
+
this.#boundHandleTextChange,
|
|
2430
2530
|
);
|
|
2431
2531
|
}
|
|
2432
2532
|
|
|
@@ -2446,11 +2546,11 @@ class FigSlider extends HTMLElement {
|
|
|
2446
2546
|
if (this.figInputNumber) {
|
|
2447
2547
|
this.figInputNumber.removeEventListener(
|
|
2448
2548
|
"input",
|
|
2449
|
-
this.#boundHandleTextInput
|
|
2549
|
+
this.#boundHandleTextInput,
|
|
2450
2550
|
);
|
|
2451
2551
|
this.figInputNumber.removeEventListener(
|
|
2452
2552
|
"change",
|
|
2453
|
-
this.#boundHandleTextChange
|
|
2553
|
+
this.#boundHandleTextChange,
|
|
2454
2554
|
);
|
|
2455
2555
|
}
|
|
2456
2556
|
}
|
|
@@ -2460,7 +2560,7 @@ class FigSlider extends HTMLElement {
|
|
|
2460
2560
|
this.value = this.input.value = this.figInputNumber.value;
|
|
2461
2561
|
this.#syncProperties();
|
|
2462
2562
|
this.dispatchEvent(
|
|
2463
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
2563
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
2464
2564
|
);
|
|
2465
2565
|
}
|
|
2466
2566
|
}
|
|
@@ -2490,7 +2590,7 @@ class FigSlider extends HTMLElement {
|
|
|
2490
2590
|
#handleInput() {
|
|
2491
2591
|
this.#syncValue();
|
|
2492
2592
|
this.dispatchEvent(
|
|
2493
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
2593
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
2494
2594
|
);
|
|
2495
2595
|
}
|
|
2496
2596
|
|
|
@@ -2498,7 +2598,7 @@ class FigSlider extends HTMLElement {
|
|
|
2498
2598
|
this.#isInteracting = false;
|
|
2499
2599
|
this.#syncValue();
|
|
2500
2600
|
this.dispatchEvent(
|
|
2501
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
2601
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
2502
2602
|
);
|
|
2503
2603
|
}
|
|
2504
2604
|
|
|
@@ -2507,7 +2607,7 @@ class FigSlider extends HTMLElement {
|
|
|
2507
2607
|
this.value = this.input.value = this.figInputNumber.value;
|
|
2508
2608
|
this.#syncProperties();
|
|
2509
2609
|
this.dispatchEvent(
|
|
2510
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
2610
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
2511
2611
|
);
|
|
2512
2612
|
}
|
|
2513
2613
|
}
|
|
@@ -2727,10 +2827,10 @@ class FigInputText extends HTMLElement {
|
|
|
2727
2827
|
this.value = value;
|
|
2728
2828
|
this.input.value = valueTransformed;
|
|
2729
2829
|
this.dispatchEvent(
|
|
2730
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
2830
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
2731
2831
|
);
|
|
2732
2832
|
this.dispatchEvent(
|
|
2733
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
2833
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
2734
2834
|
);
|
|
2735
2835
|
}
|
|
2736
2836
|
#handleMouseMove(e) {
|
|
@@ -2778,13 +2878,13 @@ class FigInputText extends HTMLElement {
|
|
|
2778
2878
|
if (typeof this.min === "number") {
|
|
2779
2879
|
sanitized = Math.max(
|
|
2780
2880
|
transform ? this.#transformNumber(this.min) : this.min,
|
|
2781
|
-
sanitized
|
|
2881
|
+
sanitized,
|
|
2782
2882
|
);
|
|
2783
2883
|
}
|
|
2784
2884
|
if (typeof this.max === "number") {
|
|
2785
2885
|
sanitized = Math.min(
|
|
2786
2886
|
transform ? this.#transformNumber(this.max) : this.max,
|
|
2787
|
-
sanitized
|
|
2887
|
+
sanitized,
|
|
2788
2888
|
);
|
|
2789
2889
|
}
|
|
2790
2890
|
|
|
@@ -3104,7 +3204,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
3104
3204
|
e.target.value = "";
|
|
3105
3205
|
}
|
|
3106
3206
|
this.dispatchEvent(
|
|
3107
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
3207
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
3108
3208
|
);
|
|
3109
3209
|
}
|
|
3110
3210
|
|
|
@@ -3130,10 +3230,10 @@ class FigInputNumber extends HTMLElement {
|
|
|
3130
3230
|
this.input.value = this.#formatWithUnit(this.value);
|
|
3131
3231
|
|
|
3132
3232
|
this.dispatchEvent(
|
|
3133
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
3233
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
3134
3234
|
);
|
|
3135
3235
|
this.dispatchEvent(
|
|
3136
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
3236
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
3137
3237
|
);
|
|
3138
3238
|
}
|
|
3139
3239
|
|
|
@@ -3145,7 +3245,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
3145
3245
|
this.value = "";
|
|
3146
3246
|
}
|
|
3147
3247
|
this.dispatchEvent(
|
|
3148
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
3248
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
3149
3249
|
);
|
|
3150
3250
|
}
|
|
3151
3251
|
|
|
@@ -3162,10 +3262,10 @@ class FigInputNumber extends HTMLElement {
|
|
|
3162
3262
|
e.target.value = "";
|
|
3163
3263
|
}
|
|
3164
3264
|
this.dispatchEvent(
|
|
3165
|
-
new CustomEvent("input", { detail: this.value, bubbles: true })
|
|
3265
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
3166
3266
|
);
|
|
3167
3267
|
this.dispatchEvent(
|
|
3168
|
-
new CustomEvent("change", { detail: this.value, bubbles: true })
|
|
3268
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
3169
3269
|
);
|
|
3170
3270
|
}
|
|
3171
3271
|
|
|
@@ -3224,7 +3324,9 @@ class FigInputNumber extends HTMLElement {
|
|
|
3224
3324
|
const factor = Math.pow(10, precision);
|
|
3225
3325
|
const rounded = Math.round(num * factor) / factor;
|
|
3226
3326
|
// Only show decimals if needed and up to precision
|
|
3227
|
-
return Number.isInteger(rounded)
|
|
3327
|
+
return Number.isInteger(rounded)
|
|
3328
|
+
? rounded
|
|
3329
|
+
: parseFloat(rounded.toFixed(precision));
|
|
3228
3330
|
}
|
|
3229
3331
|
|
|
3230
3332
|
static get observedAttributes() {
|
|
@@ -3358,7 +3460,7 @@ class FigField extends HTMLElement {
|
|
|
3358
3460
|
requestAnimationFrame(() => {
|
|
3359
3461
|
this.label = this.querySelector(":scope>label");
|
|
3360
3462
|
this.input = Array.from(this.childNodes).find((node) =>
|
|
3361
|
-
node.nodeName.toLowerCase().startsWith("fig-")
|
|
3463
|
+
node.nodeName.toLowerCase().startsWith("fig-"),
|
|
3362
3464
|
);
|
|
3363
3465
|
if (this.input && this.label) {
|
|
3364
3466
|
this.label.addEventListener("click", this.focus.bind(this));
|
|
@@ -3503,11 +3605,11 @@ class FigInputColor extends HTMLElement {
|
|
|
3503
3605
|
}
|
|
3504
3606
|
this.#fillPicker.addEventListener(
|
|
3505
3607
|
"input",
|
|
3506
|
-
this.#handleFillPickerInput.bind(this)
|
|
3608
|
+
this.#handleFillPickerInput.bind(this),
|
|
3507
3609
|
);
|
|
3508
3610
|
this.#fillPicker.addEventListener(
|
|
3509
3611
|
"change",
|
|
3510
|
-
this.#handleChange.bind(this)
|
|
3612
|
+
this.#handleChange.bind(this),
|
|
3511
3613
|
);
|
|
3512
3614
|
}
|
|
3513
3615
|
|
|
@@ -3520,22 +3622,22 @@ class FigInputColor extends HTMLElement {
|
|
|
3520
3622
|
}
|
|
3521
3623
|
this.#textInput.addEventListener(
|
|
3522
3624
|
"input",
|
|
3523
|
-
this.#handleTextInput.bind(this)
|
|
3625
|
+
this.#handleTextInput.bind(this),
|
|
3524
3626
|
);
|
|
3525
3627
|
this.#textInput.addEventListener(
|
|
3526
3628
|
"change",
|
|
3527
|
-
this.#handleChange.bind(this)
|
|
3629
|
+
this.#handleChange.bind(this),
|
|
3528
3630
|
);
|
|
3529
3631
|
}
|
|
3530
3632
|
|
|
3531
3633
|
if (this.#alphaInput) {
|
|
3532
3634
|
this.#alphaInput.addEventListener(
|
|
3533
3635
|
"input",
|
|
3534
|
-
this.#handleAlphaInput.bind(this)
|
|
3636
|
+
this.#handleAlphaInput.bind(this),
|
|
3535
3637
|
);
|
|
3536
3638
|
this.#alphaInput.addEventListener(
|
|
3537
3639
|
"change",
|
|
3538
|
-
this.#handleChange.bind(this)
|
|
3640
|
+
this.#handleChange.bind(this),
|
|
3539
3641
|
);
|
|
3540
3642
|
}
|
|
3541
3643
|
});
|
|
@@ -3548,7 +3650,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3548
3650
|
g: isNaN(this.rgba.g) ? 0 : this.rgba.g,
|
|
3549
3651
|
b: isNaN(this.rgba.b) ? 0 : this.rgba.b,
|
|
3550
3652
|
},
|
|
3551
|
-
this.rgba.a
|
|
3653
|
+
this.rgba.a,
|
|
3552
3654
|
);
|
|
3553
3655
|
this.hexWithAlpha = this.value.toUpperCase();
|
|
3554
3656
|
this.hexOpaque = this.hexWithAlpha.slice(0, 7);
|
|
@@ -3591,7 +3693,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3591
3693
|
type: "solid",
|
|
3592
3694
|
color: this.hexOpaque,
|
|
3593
3695
|
opacity: this.alpha,
|
|
3594
|
-
})
|
|
3696
|
+
}),
|
|
3595
3697
|
);
|
|
3596
3698
|
}
|
|
3597
3699
|
this.#emitInputEvent();
|
|
@@ -3614,7 +3716,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3614
3716
|
// Display without # prefix
|
|
3615
3717
|
this.#textInput.setAttribute(
|
|
3616
3718
|
"value",
|
|
3617
|
-
this.hexOpaque.slice(1).toUpperCase()
|
|
3719
|
+
this.hexOpaque.slice(1).toUpperCase(),
|
|
3618
3720
|
);
|
|
3619
3721
|
}
|
|
3620
3722
|
this.#emitInputEvent();
|
|
@@ -3636,7 +3738,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3636
3738
|
if (this.#textInput) {
|
|
3637
3739
|
this.#textInput.setAttribute(
|
|
3638
3740
|
"value",
|
|
3639
|
-
this.hexOpaque.slice(1).toUpperCase()
|
|
3741
|
+
this.hexOpaque.slice(1).toUpperCase(),
|
|
3640
3742
|
);
|
|
3641
3743
|
}
|
|
3642
3744
|
if (this.#alphaInput && detail.alpha !== undefined) {
|
|
@@ -3694,7 +3796,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3694
3796
|
type: "solid",
|
|
3695
3797
|
color: this.hexOpaque,
|
|
3696
3798
|
opacity: this.alpha,
|
|
3697
|
-
})
|
|
3799
|
+
}),
|
|
3698
3800
|
);
|
|
3699
3801
|
}
|
|
3700
3802
|
if (this.#alphaInput) {
|
|
@@ -3761,7 +3863,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3761
3863
|
// Handle rgba colors
|
|
3762
3864
|
else if (color.startsWith("rgba") || color.startsWith("rgb")) {
|
|
3763
3865
|
let matches = color.match(
|
|
3764
|
-
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)
|
|
3866
|
+
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/,
|
|
3765
3867
|
);
|
|
3766
3868
|
if (matches) {
|
|
3767
3869
|
r = parseInt(matches[1]);
|
|
@@ -3773,7 +3875,7 @@ class FigInputColor extends HTMLElement {
|
|
|
3773
3875
|
// Handle hsla colors
|
|
3774
3876
|
else if (color.startsWith("hsla") || color.startsWith("hsl")) {
|
|
3775
3877
|
let matches = color.match(
|
|
3776
|
-
/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)
|
|
3878
|
+
/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)/,
|
|
3777
3879
|
);
|
|
3778
3880
|
if (matches) {
|
|
3779
3881
|
let h = parseInt(matches[1]) / 360;
|
|
@@ -3989,8 +4091,8 @@ class FigInputFill extends HTMLElement {
|
|
|
3989
4091
|
this.innerHTML = `
|
|
3990
4092
|
<div class="input-combo">
|
|
3991
4093
|
<fig-fill-picker ${fpAttrs} value='${fillPickerValue}' ${
|
|
3992
|
-
|
|
3993
|
-
|
|
4094
|
+
disabled ? "disabled" : ""
|
|
4095
|
+
}></fig-fill-picker>
|
|
3994
4096
|
${controlsHtml}
|
|
3995
4097
|
</div>`;
|
|
3996
4098
|
|
|
@@ -4127,13 +4229,13 @@ class FigInputFill extends HTMLElement {
|
|
|
4127
4229
|
if (this.#hexInput) {
|
|
4128
4230
|
this.#hexInput.setAttribute(
|
|
4129
4231
|
"value",
|
|
4130
|
-
this.#solid.color.slice(1).toUpperCase()
|
|
4232
|
+
this.#solid.color.slice(1).toUpperCase(),
|
|
4131
4233
|
);
|
|
4132
4234
|
}
|
|
4133
4235
|
if (this.#opacityInput) {
|
|
4134
4236
|
this.#opacityInput.setAttribute(
|
|
4135
4237
|
"value",
|
|
4136
|
-
Math.round(this.#solid.alpha * 100)
|
|
4238
|
+
Math.round(this.#solid.alpha * 100),
|
|
4137
4239
|
);
|
|
4138
4240
|
}
|
|
4139
4241
|
break;
|
|
@@ -4141,7 +4243,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4141
4243
|
if (this.#opacityInput) {
|
|
4142
4244
|
this.#opacityInput.setAttribute(
|
|
4143
4245
|
"value",
|
|
4144
|
-
this.#gradient.stops[0]?.opacity ?? 100
|
|
4246
|
+
this.#gradient.stops[0]?.opacity ?? 100,
|
|
4145
4247
|
);
|
|
4146
4248
|
}
|
|
4147
4249
|
const label = this.querySelector(".fig-input-fill-label");
|
|
@@ -4157,7 +4259,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4157
4259
|
if (this.#opacityInput) {
|
|
4158
4260
|
this.#opacityInput.setAttribute(
|
|
4159
4261
|
"value",
|
|
4160
|
-
Math.round((this.#image.opacity ?? 1) * 100)
|
|
4262
|
+
Math.round((this.#image.opacity ?? 1) * 100),
|
|
4161
4263
|
);
|
|
4162
4264
|
}
|
|
4163
4265
|
break;
|
|
@@ -4165,7 +4267,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4165
4267
|
if (this.#opacityInput) {
|
|
4166
4268
|
this.#opacityInput.setAttribute(
|
|
4167
4269
|
"value",
|
|
4168
|
-
Math.round((this.#video.opacity ?? 1) * 100)
|
|
4270
|
+
Math.round((this.#video.opacity ?? 1) * 100),
|
|
4169
4271
|
);
|
|
4170
4272
|
}
|
|
4171
4273
|
break;
|
|
@@ -4173,7 +4275,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4173
4275
|
if (this.#opacityInput) {
|
|
4174
4276
|
this.#opacityInput.setAttribute(
|
|
4175
4277
|
"value",
|
|
4176
|
-
Math.round((this.#webcam.opacity ?? 1) * 100)
|
|
4278
|
+
Math.round((this.#webcam.opacity ?? 1) * 100),
|
|
4177
4279
|
);
|
|
4178
4280
|
}
|
|
4179
4281
|
break;
|
|
@@ -4377,7 +4479,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4377
4479
|
new CustomEvent("input", {
|
|
4378
4480
|
bubbles: true,
|
|
4379
4481
|
detail: this.value,
|
|
4380
|
-
})
|
|
4482
|
+
}),
|
|
4381
4483
|
);
|
|
4382
4484
|
}
|
|
4383
4485
|
|
|
@@ -4386,7 +4488,7 @@ class FigInputFill extends HTMLElement {
|
|
|
4386
4488
|
new CustomEvent("change", {
|
|
4387
4489
|
bubbles: true,
|
|
4388
4490
|
detail: this.value,
|
|
4389
|
-
})
|
|
4491
|
+
}),
|
|
4390
4492
|
);
|
|
4391
4493
|
}
|
|
4392
4494
|
|
|
@@ -4626,14 +4728,14 @@ class FigCheckbox extends HTMLElement {
|
|
|
4626
4728
|
bubbles: true,
|
|
4627
4729
|
composed: true,
|
|
4628
4730
|
detail: { checked: this.input.checked, value: this.input.value },
|
|
4629
|
-
})
|
|
4731
|
+
}),
|
|
4630
4732
|
);
|
|
4631
4733
|
this.dispatchEvent(
|
|
4632
4734
|
new CustomEvent("change", {
|
|
4633
4735
|
bubbles: true,
|
|
4634
4736
|
composed: true,
|
|
4635
4737
|
detail: { checked: this.input.checked, value: this.input.value },
|
|
4636
|
-
})
|
|
4738
|
+
}),
|
|
4637
4739
|
);
|
|
4638
4740
|
}
|
|
4639
4741
|
}
|
|
@@ -4828,8 +4930,9 @@ class FigComboInput extends HTMLElement {
|
|
|
4828
4930
|
}
|
|
4829
4931
|
connectedCallback() {
|
|
4830
4932
|
const customDropdown =
|
|
4831
|
-
Array.from(this.children).find(
|
|
4832
|
-
|
|
4933
|
+
Array.from(this.children).find(
|
|
4934
|
+
(child) => child.tagName === "FIG-DROPDOWN",
|
|
4935
|
+
) || null;
|
|
4833
4936
|
this.#usesCustomDropdown = customDropdown !== null;
|
|
4834
4937
|
if (customDropdown) {
|
|
4835
4938
|
customDropdown.remove();
|
|
@@ -5151,7 +5254,10 @@ class FigImage extends HTMLElement {
|
|
|
5151
5254
|
}
|
|
5152
5255
|
disconnectedCallback() {
|
|
5153
5256
|
this.fileInput?.removeEventListener("change", this.#boundHandleFileInput);
|
|
5154
|
-
this.downloadButton?.removeEventListener(
|
|
5257
|
+
this.downloadButton?.removeEventListener(
|
|
5258
|
+
"click",
|
|
5259
|
+
this.#boundHandleDownload,
|
|
5260
|
+
);
|
|
5155
5261
|
}
|
|
5156
5262
|
|
|
5157
5263
|
#updateRefs() {
|
|
@@ -5160,13 +5266,22 @@ class FigImage extends HTMLElement {
|
|
|
5160
5266
|
if (this.upload) {
|
|
5161
5267
|
this.uploadButton = this.querySelector("fig-button[type='upload']");
|
|
5162
5268
|
this.fileInput = this.uploadButton?.querySelector("input");
|
|
5163
|
-
this.fileInput?.removeEventListener(
|
|
5269
|
+
this.fileInput?.removeEventListener(
|
|
5270
|
+
"change",
|
|
5271
|
+
this.#boundHandleFileInput,
|
|
5272
|
+
);
|
|
5164
5273
|
this.fileInput?.addEventListener("change", this.#boundHandleFileInput);
|
|
5165
5274
|
}
|
|
5166
5275
|
if (this.download) {
|
|
5167
5276
|
this.downloadButton = this.querySelector("fig-button[type='download']");
|
|
5168
|
-
this.downloadButton?.removeEventListener(
|
|
5169
|
-
|
|
5277
|
+
this.downloadButton?.removeEventListener(
|
|
5278
|
+
"click",
|
|
5279
|
+
this.#boundHandleDownload,
|
|
5280
|
+
);
|
|
5281
|
+
this.downloadButton?.addEventListener(
|
|
5282
|
+
"click",
|
|
5283
|
+
this.#boundHandleDownload,
|
|
5284
|
+
);
|
|
5170
5285
|
}
|
|
5171
5286
|
});
|
|
5172
5287
|
}
|
|
@@ -5188,7 +5303,7 @@ class FigImage extends HTMLElement {
|
|
|
5188
5303
|
if (!ar || ar === "auto") {
|
|
5189
5304
|
this.style.setProperty(
|
|
5190
5305
|
"--aspect-ratio",
|
|
5191
|
-
`${this.image.width}/${this.image.height}
|
|
5306
|
+
`${this.image.width}/${this.image.height}`,
|
|
5192
5307
|
);
|
|
5193
5308
|
}
|
|
5194
5309
|
this.dispatchEvent(
|
|
@@ -5199,7 +5314,7 @@ class FigImage extends HTMLElement {
|
|
|
5199
5314
|
blob: this.blob,
|
|
5200
5315
|
base64: this.base64,
|
|
5201
5316
|
},
|
|
5202
|
-
})
|
|
5317
|
+
}),
|
|
5203
5318
|
);
|
|
5204
5319
|
resolve();
|
|
5205
5320
|
|
|
@@ -5250,14 +5365,14 @@ class FigImage extends HTMLElement {
|
|
|
5250
5365
|
blob: this.blob,
|
|
5251
5366
|
base64: this.base64,
|
|
5252
5367
|
},
|
|
5253
|
-
})
|
|
5368
|
+
}),
|
|
5254
5369
|
);
|
|
5255
5370
|
//emit for change too
|
|
5256
5371
|
this.dispatchEvent(
|
|
5257
5372
|
new CustomEvent("change", {
|
|
5258
5373
|
bubbles: true,
|
|
5259
5374
|
cancelable: true,
|
|
5260
|
-
})
|
|
5375
|
+
}),
|
|
5261
5376
|
);
|
|
5262
5377
|
this.setAttribute("src", this.blob);
|
|
5263
5378
|
}
|
|
@@ -5278,7 +5393,7 @@ class FigImage extends HTMLElement {
|
|
|
5278
5393
|
if (this.chit) {
|
|
5279
5394
|
this.chit.setAttribute(
|
|
5280
5395
|
"background",
|
|
5281
|
-
this.#src ? `url(${this.#src})` : ""
|
|
5396
|
+
this.#src ? `url(${this.#src})` : "",
|
|
5282
5397
|
);
|
|
5283
5398
|
}
|
|
5284
5399
|
if (this.#src) {
|
|
@@ -5331,6 +5446,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5331
5446
|
#line2 = null;
|
|
5332
5447
|
#handle1 = null;
|
|
5333
5448
|
#handle2 = null;
|
|
5449
|
+
#bezierEndpointStart = null;
|
|
5450
|
+
#bezierEndpointEnd = null;
|
|
5334
5451
|
#dropdown = null;
|
|
5335
5452
|
#presetName = null;
|
|
5336
5453
|
#targetLine = null;
|
|
@@ -5340,29 +5457,86 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5340
5457
|
#bounds = null;
|
|
5341
5458
|
#diagonal = null;
|
|
5342
5459
|
#resizeObserver = null;
|
|
5460
|
+
#bezierHandleRadius = 3.625;
|
|
5461
|
+
#bezierEndpointRadius = 2;
|
|
5462
|
+
#springHandleRadius = 3.625;
|
|
5463
|
+
#durationBarWidth = 6;
|
|
5464
|
+
#durationBarHeight = 16;
|
|
5465
|
+
#durationBarRadius = 3;
|
|
5343
5466
|
|
|
5344
5467
|
static PRESETS = [
|
|
5345
5468
|
{ group: null, name: "Linear", type: "bezier", value: [0, 0, 1, 1] },
|
|
5346
|
-
{
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
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
|
+
},
|
|
5352
5505
|
{ group: "Bezier", name: "Custom bezier", type: "bezier", value: null },
|
|
5353
|
-
{
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
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
|
+
},
|
|
5357
5530
|
{ group: "Spring", name: "Custom spring", type: "spring", spring: null },
|
|
5358
5531
|
];
|
|
5359
5532
|
|
|
5360
5533
|
static get observedAttributes() {
|
|
5361
|
-
return ["value", "precision"];
|
|
5534
|
+
return ["value", "precision", "aspect-ratio"];
|
|
5362
5535
|
}
|
|
5363
5536
|
|
|
5364
5537
|
connectedCallback() {
|
|
5365
5538
|
this.#precision = parseInt(this.getAttribute("precision") || "2");
|
|
5539
|
+
this.#syncAspectRatioVar(this.getAttribute("aspect-ratio"));
|
|
5366
5540
|
const val = this.getAttribute("value");
|
|
5367
5541
|
if (val) this.#parseValue(val);
|
|
5368
5542
|
this.#presetName = this.#matchPreset();
|
|
@@ -5378,7 +5552,24 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5378
5552
|
}
|
|
5379
5553
|
}
|
|
5380
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
|
+
|
|
5381
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
|
+
|
|
5382
5573
|
if (!this.#svg) return;
|
|
5383
5574
|
if (name === "value" && newValue) {
|
|
5384
5575
|
const prevMode = this.#mode;
|
|
@@ -5414,7 +5605,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5414
5605
|
for (let i = 0; i < points.length; i += step) {
|
|
5415
5606
|
vals.push(points[i].value.toFixed(3));
|
|
5416
5607
|
}
|
|
5417
|
-
if (points.length > 0)
|
|
5608
|
+
if (points.length > 0)
|
|
5609
|
+
vals.push(points[points.length - 1].value.toFixed(3));
|
|
5418
5610
|
return `linear(${vals.join(", ")})`;
|
|
5419
5611
|
}
|
|
5420
5612
|
const p = this.#precision;
|
|
@@ -5430,7 +5622,9 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5430
5622
|
}
|
|
5431
5623
|
|
|
5432
5624
|
#parseValue(str) {
|
|
5433
|
-
const springMatch = str.match(
|
|
5625
|
+
const springMatch = str.match(
|
|
5626
|
+
/^spring\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/,
|
|
5627
|
+
);
|
|
5434
5628
|
if (springMatch) {
|
|
5435
5629
|
this.#mode = "spring";
|
|
5436
5630
|
this.#spring.stiffness = parseFloat(springMatch[1]);
|
|
@@ -5458,7 +5652,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5458
5652
|
Math.abs(this.#cp1.y - p.value[1]) < ep &&
|
|
5459
5653
|
Math.abs(this.#cp2.x - p.value[2]) < ep &&
|
|
5460
5654
|
Math.abs(this.#cp2.y - p.value[3]) < ep
|
|
5461
|
-
)
|
|
5655
|
+
)
|
|
5656
|
+
return p.name;
|
|
5462
5657
|
}
|
|
5463
5658
|
return "Custom bezier";
|
|
5464
5659
|
}
|
|
@@ -5468,7 +5663,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5468
5663
|
Math.abs(this.#spring.stiffness - p.spring.stiffness) < ep &&
|
|
5469
5664
|
Math.abs(this.#spring.damping - p.spring.damping) < ep &&
|
|
5470
5665
|
Math.abs(this.#spring.mass - p.spring.mass) < ep
|
|
5471
|
-
)
|
|
5666
|
+
)
|
|
5667
|
+
return p.name;
|
|
5472
5668
|
}
|
|
5473
5669
|
return "Custom spring";
|
|
5474
5670
|
}
|
|
@@ -5480,13 +5676,15 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5480
5676
|
const dt = 0.004;
|
|
5481
5677
|
const maxTime = 5;
|
|
5482
5678
|
const points = [];
|
|
5483
|
-
let pos = 0,
|
|
5679
|
+
let pos = 0,
|
|
5680
|
+
vel = 0;
|
|
5484
5681
|
for (let t = 0; t <= maxTime; t += dt) {
|
|
5485
5682
|
const force = -stiffness * (pos - 1) - damping * vel;
|
|
5486
5683
|
vel += (force / mass) * dt;
|
|
5487
5684
|
pos += vel * dt;
|
|
5488
5685
|
points.push({ t, value: pos });
|
|
5489
|
-
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;
|
|
5490
5688
|
}
|
|
5491
5689
|
return points;
|
|
5492
5690
|
}
|
|
@@ -5496,7 +5694,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5496
5694
|
const dt = 0.004;
|
|
5497
5695
|
const maxTime = 5;
|
|
5498
5696
|
const pts = [];
|
|
5499
|
-
let pos = 0,
|
|
5697
|
+
let pos = 0,
|
|
5698
|
+
vel = 0;
|
|
5500
5699
|
for (let t = 0; t <= maxTime; t += dt) {
|
|
5501
5700
|
const force = -stiffness * (pos - 1) - damping * vel;
|
|
5502
5701
|
vel += (force / mass) * dt;
|
|
@@ -5519,21 +5718,64 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5519
5718
|
const y = pad + (1 - (pts[i].value - minVal) / range) * s;
|
|
5520
5719
|
d += (i === 0 ? "M" : "L") + x.toFixed(1) + "," + y.toFixed(1);
|
|
5521
5720
|
}
|
|
5522
|
-
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>`;
|
|
5523
5722
|
}
|
|
5524
5723
|
|
|
5525
5724
|
static curveIcon(cp1x, cp1y, cp2x, cp2y, size = 24) {
|
|
5526
|
-
const
|
|
5527
|
-
const
|
|
5528
|
-
const
|
|
5529
|
-
const
|
|
5530
|
-
|
|
5531
|
-
|
|
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>`;
|
|
5532
5771
|
}
|
|
5533
5772
|
|
|
5534
5773
|
// --- Rendering ---
|
|
5535
5774
|
|
|
5536
5775
|
#render() {
|
|
5776
|
+
this.classList.toggle("spring-mode", this.#mode === "spring");
|
|
5777
|
+
this.classList.toggle("bezier-mode", this.#mode !== "spring");
|
|
5778
|
+
this.#syncMetricsFromCSS();
|
|
5537
5779
|
this.innerHTML = this.#getInnerHTML();
|
|
5538
5780
|
this.#cacheRefs();
|
|
5539
5781
|
this.#syncViewportSize();
|
|
@@ -5556,7 +5798,12 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5556
5798
|
const sp = p.spring || this.#spring;
|
|
5557
5799
|
icon = FigEasingCurve.#springIcon(sp);
|
|
5558
5800
|
} else {
|
|
5559
|
-
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
|
+
];
|
|
5560
5807
|
icon = FigEasingCurve.curveIcon(...v);
|
|
5561
5808
|
}
|
|
5562
5809
|
const selected = p.name === this.#presetName ? " selected" : "";
|
|
@@ -5578,8 +5825,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5578
5825
|
<line class="fig-easing-curve-target" x1="0" y1="${targetY}" x2="${size}" y2="${targetY}"/>
|
|
5579
5826
|
<line class="fig-easing-curve-diagonal" x1="0" y1="${startY}" x2="0" y2="${startY}"/>
|
|
5580
5827
|
<path class="fig-easing-curve-path"/>
|
|
5581
|
-
<circle class="fig-easing-curve-handle" data-handle="bounce" r="
|
|
5582
|
-
<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}"/>
|
|
5583
5830
|
</svg></div>`;
|
|
5584
5831
|
}
|
|
5585
5832
|
|
|
@@ -5589,18 +5836,60 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5589
5836
|
<line class="fig-easing-curve-arm" data-arm="1"/>
|
|
5590
5837
|
<line class="fig-easing-curve-arm" data-arm="2"/>
|
|
5591
5838
|
<path class="fig-easing-curve-path"/>
|
|
5592
|
-
<circle class="fig-easing-curve-
|
|
5593
|
-
<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}"/>
|
|
5594
5843
|
</svg></div>`;
|
|
5595
5844
|
}
|
|
5596
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
|
+
|
|
5597
5880
|
#cacheRefs() {
|
|
5598
5881
|
this.#svg = this.querySelector(".fig-easing-curve-svg");
|
|
5599
5882
|
this.#curve = this.querySelector(".fig-easing-curve-path");
|
|
5600
5883
|
this.#line1 = this.querySelector('[data-arm="1"]');
|
|
5601
5884
|
this.#line2 = this.querySelector('[data-arm="2"]');
|
|
5602
|
-
this.#handle1 =
|
|
5603
|
-
|
|
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"]');
|
|
5604
5893
|
this.#dropdown = this.querySelector(".fig-easing-curve-dropdown");
|
|
5605
5894
|
this.#targetLine = this.querySelector(".fig-easing-curve-target");
|
|
5606
5895
|
this.#bounds = this.querySelector(".fig-easing-curve-bounds");
|
|
@@ -5682,7 +5971,10 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5682
5971
|
const p2 = this.#toSVG(this.#cp2.x, this.#cp2.y);
|
|
5683
5972
|
const p3 = this.#toSVG(1, 1);
|
|
5684
5973
|
|
|
5685
|
-
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
|
+
);
|
|
5686
5978
|
this.#line1.setAttribute("x1", p0.x);
|
|
5687
5979
|
this.#line1.setAttribute("y1", p0.y);
|
|
5688
5980
|
this.#line1.setAttribute("x2", p1.x);
|
|
@@ -5695,6 +5987,14 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5695
5987
|
this.#handle1.setAttribute("cy", p1.y);
|
|
5696
5988
|
this.#handle2.setAttribute("cx", p2.x);
|
|
5697
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
|
+
}
|
|
5698
5998
|
}
|
|
5699
5999
|
|
|
5700
6000
|
#updateSpringPaths() {
|
|
@@ -5709,12 +6009,17 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5709
6009
|
if (!points.length) return;
|
|
5710
6010
|
const totalTime = points[points.length - 1].t || 1;
|
|
5711
6011
|
|
|
5712
|
-
let minVal = 0,
|
|
6012
|
+
let minVal = 0,
|
|
6013
|
+
maxVal = 1;
|
|
5713
6014
|
for (const p of points) {
|
|
5714
6015
|
if (p.value < minVal) minVal = p.value;
|
|
5715
6016
|
if (p.value > maxVal) maxVal = p.value;
|
|
5716
6017
|
}
|
|
5717
|
-
const maxDistFromCenter = Math.max(
|
|
6018
|
+
const maxDistFromCenter = Math.max(
|
|
6019
|
+
Math.abs(minVal - 1),
|
|
6020
|
+
Math.abs(maxVal - 1),
|
|
6021
|
+
0.01,
|
|
6022
|
+
);
|
|
5718
6023
|
const valPad = 0;
|
|
5719
6024
|
this.#springScale = {
|
|
5720
6025
|
minVal: 1 - maxDistFromCenter - valPad,
|
|
@@ -5753,9 +6058,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5753
6058
|
|
|
5754
6059
|
// Duration handle: on the target line
|
|
5755
6060
|
const targetPt = this.#springToSVG(durationNorm, 1);
|
|
5756
|
-
this.#handle2.setAttribute("x", targetPt.x -
|
|
5757
|
-
this.#handle2.setAttribute("y", targetPt.y -
|
|
5758
|
-
|
|
6061
|
+
this.#handle2.setAttribute("x", targetPt.x - this.#durationBarWidth / 2);
|
|
6062
|
+
this.#handle2.setAttribute("y", targetPt.y - this.#durationBarHeight / 2);
|
|
5759
6063
|
}
|
|
5760
6064
|
|
|
5761
6065
|
#findPeakOvershoot(points) {
|
|
@@ -5793,38 +6097,76 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5793
6097
|
this.#cp1.x,
|
|
5794
6098
|
this.#cp1.y,
|
|
5795
6099
|
this.#cp2.x,
|
|
5796
|
-
this.#cp2.y
|
|
6100
|
+
this.#cp2.y,
|
|
5797
6101
|
);
|
|
5798
6102
|
const springIcon = FigEasingCurve.#springIcon(this.#spring);
|
|
5799
6103
|
|
|
5800
6104
|
// Update both slotted options and the cloned native select options.
|
|
5801
6105
|
this.#setOptionIconByValue(this.#dropdown, "Custom bezier", bezierIcon);
|
|
5802
6106
|
this.#setOptionIconByValue(this.#dropdown, "Custom spring", springIcon);
|
|
5803
|
-
this.#setOptionIconByValue(
|
|
5804
|
-
|
|
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
|
+
);
|
|
5805
6117
|
}
|
|
5806
6118
|
|
|
5807
6119
|
// --- Events ---
|
|
5808
6120
|
|
|
5809
6121
|
#emit(type) {
|
|
5810
|
-
this.dispatchEvent(
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
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
|
+
);
|
|
5819
6133
|
}
|
|
5820
6134
|
|
|
5821
6135
|
#setupEvents() {
|
|
5822
6136
|
if (this.#mode === "bezier") {
|
|
5823
|
-
this.#handle1.addEventListener("pointerdown", (e) =>
|
|
5824
|
-
|
|
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
|
+
}
|
|
5825
6152
|
} else {
|
|
5826
|
-
this.#handle1.addEventListener("pointerdown", (e) =>
|
|
5827
|
-
|
|
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
|
+
}
|
|
5828
6170
|
}
|
|
5829
6171
|
|
|
5830
6172
|
if (this.#dropdown) {
|
|
@@ -5875,6 +6217,11 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5875
6217
|
};
|
|
5876
6218
|
}
|
|
5877
6219
|
|
|
6220
|
+
#bezierHandleForClientHalf(e) {
|
|
6221
|
+
const svgPt = this.#clientToSVG(e);
|
|
6222
|
+
return svgPt.x <= this.#drawWidth / 2 ? 1 : 2;
|
|
6223
|
+
}
|
|
6224
|
+
|
|
5878
6225
|
#startBezierDrag(e, handle) {
|
|
5879
6226
|
e.preventDefault();
|
|
5880
6227
|
this.#isDragging = handle;
|
|
@@ -5927,11 +6274,20 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5927
6274
|
|
|
5928
6275
|
if (handleType === "bounce") {
|
|
5929
6276
|
const dy = e.clientY - startY;
|
|
5930
|
-
this.#spring.damping = Math.max(
|
|
6277
|
+
this.#spring.damping = Math.max(
|
|
6278
|
+
1,
|
|
6279
|
+
Math.round(startDamping + dy * 0.15),
|
|
6280
|
+
);
|
|
5931
6281
|
} else {
|
|
5932
6282
|
const dx = e.clientX - startX;
|
|
5933
|
-
this.#springDuration = Math.max(
|
|
5934
|
-
|
|
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
|
+
);
|
|
5935
6291
|
}
|
|
5936
6292
|
|
|
5937
6293
|
this.#updatePaths();
|
|
@@ -5953,6 +6309,250 @@ class FigEasingCurve extends HTMLElement {
|
|
|
5953
6309
|
}
|
|
5954
6310
|
customElements.define("fig-easing-curve", FigEasingCurve);
|
|
5955
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
|
+
|
|
5956
6556
|
/**
|
|
5957
6557
|
* A custom joystick input element.
|
|
5958
6558
|
* @attr {string} value - The current position of the joystick (e.g., "0.5,0.5").
|
|
@@ -6046,7 +6646,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
6046
6646
|
this.plane.addEventListener("mousedown", this.#handleMouseDown.bind(this));
|
|
6047
6647
|
this.plane.addEventListener(
|
|
6048
6648
|
"touchstart",
|
|
6049
|
-
this.#handleTouchStart.bind(this)
|
|
6649
|
+
this.#handleTouchStart.bind(this),
|
|
6050
6650
|
);
|
|
6051
6651
|
window.addEventListener("keydown", this.#handleKeyDown.bind(this));
|
|
6052
6652
|
window.addEventListener("keyup", this.#handleKeyUp.bind(this));
|
|
@@ -6106,7 +6706,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
6106
6706
|
let x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
|
|
6107
6707
|
let screenY = Math.max(
|
|
6108
6708
|
0,
|
|
6109
|
-
Math.min(1, (e.clientY - rect.top) / rect.height)
|
|
6709
|
+
Math.min(1, (e.clientY - rect.top) / rect.height),
|
|
6110
6710
|
);
|
|
6111
6711
|
|
|
6112
6712
|
// Convert screen Y to internal Y (flip for math coordinates)
|
|
@@ -6134,7 +6734,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
6134
6734
|
new CustomEvent("input", {
|
|
6135
6735
|
bubbles: true,
|
|
6136
6736
|
cancelable: true,
|
|
6137
|
-
})
|
|
6737
|
+
}),
|
|
6138
6738
|
);
|
|
6139
6739
|
}
|
|
6140
6740
|
|
|
@@ -6143,7 +6743,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
6143
6743
|
new CustomEvent("change", {
|
|
6144
6744
|
bubbles: true,
|
|
6145
6745
|
cancelable: true,
|
|
6146
|
-
})
|
|
6746
|
+
}),
|
|
6147
6747
|
);
|
|
6148
6748
|
}
|
|
6149
6749
|
|
|
@@ -6318,8 +6918,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
6318
6918
|
this.max = this.hasAttribute("max")
|
|
6319
6919
|
? Number(this.getAttribute("max"))
|
|
6320
6920
|
: null;
|
|
6321
|
-
this.showRotations =
|
|
6322
|
-
this.getAttribute("show-rotations") === "true";
|
|
6921
|
+
this.showRotations = this.getAttribute("show-rotations") === "true";
|
|
6323
6922
|
|
|
6324
6923
|
this.#render();
|
|
6325
6924
|
this.#setupListeners();
|
|
@@ -6328,7 +6927,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
6328
6927
|
if (this.text && this.angleInput) {
|
|
6329
6928
|
this.angleInput.setAttribute(
|
|
6330
6929
|
"value",
|
|
6331
|
-
this.angle.toFixed(this.precision)
|
|
6930
|
+
this.angle.toFixed(this.precision),
|
|
6332
6931
|
);
|
|
6333
6932
|
}
|
|
6334
6933
|
});
|
|
@@ -6453,14 +7052,14 @@ class FigInputAngle extends HTMLElement {
|
|
|
6453
7052
|
this.plane.addEventListener("mousedown", this.#handleMouseDown.bind(this));
|
|
6454
7053
|
this.plane.addEventListener(
|
|
6455
7054
|
"touchstart",
|
|
6456
|
-
this.#handleTouchStart.bind(this)
|
|
7055
|
+
this.#handleTouchStart.bind(this),
|
|
6457
7056
|
);
|
|
6458
7057
|
window.addEventListener("keydown", this.#handleKeyDown.bind(this));
|
|
6459
7058
|
window.addEventListener("keyup", this.#handleKeyUp.bind(this));
|
|
6460
7059
|
if (this.text && this.angleInput) {
|
|
6461
7060
|
this.angleInput.addEventListener(
|
|
6462
7061
|
"input",
|
|
6463
|
-
this.#handleAngleInput.bind(this)
|
|
7062
|
+
this.#handleAngleInput.bind(this),
|
|
6464
7063
|
);
|
|
6465
7064
|
}
|
|
6466
7065
|
// Capture-phase listener for unit suffix parsing
|
|
@@ -6578,7 +7177,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
6578
7177
|
new CustomEvent("input", {
|
|
6579
7178
|
bubbles: true,
|
|
6580
7179
|
cancelable: true,
|
|
6581
|
-
})
|
|
7180
|
+
}),
|
|
6582
7181
|
);
|
|
6583
7182
|
}
|
|
6584
7183
|
|
|
@@ -6587,7 +7186,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
6587
7186
|
new CustomEvent("change", {
|
|
6588
7187
|
bubbles: true,
|
|
6589
7188
|
cancelable: true,
|
|
6590
|
-
})
|
|
7189
|
+
}),
|
|
6591
7190
|
);
|
|
6592
7191
|
}
|
|
6593
7192
|
|
|
@@ -6670,7 +7269,15 @@ class FigInputAngle extends HTMLElement {
|
|
|
6670
7269
|
// --- Attributes ---
|
|
6671
7270
|
|
|
6672
7271
|
static get observedAttributes() {
|
|
6673
|
-
return [
|
|
7272
|
+
return [
|
|
7273
|
+
"value",
|
|
7274
|
+
"precision",
|
|
7275
|
+
"text",
|
|
7276
|
+
"min",
|
|
7277
|
+
"max",
|
|
7278
|
+
"units",
|
|
7279
|
+
"show-rotations",
|
|
7280
|
+
];
|
|
6674
7281
|
}
|
|
6675
7282
|
|
|
6676
7283
|
get value() {
|
|
@@ -6853,7 +7460,7 @@ class FigLayer extends HTMLElement {
|
|
|
6853
7460
|
new CustomEvent("openchange", {
|
|
6854
7461
|
detail: { open: value },
|
|
6855
7462
|
bubbles: true,
|
|
6856
|
-
})
|
|
7463
|
+
}),
|
|
6857
7464
|
);
|
|
6858
7465
|
}
|
|
6859
7466
|
}
|
|
@@ -6875,7 +7482,7 @@ class FigLayer extends HTMLElement {
|
|
|
6875
7482
|
new CustomEvent("visibilitychange", {
|
|
6876
7483
|
detail: { visible: value },
|
|
6877
7484
|
bubbles: true,
|
|
6878
|
-
})
|
|
7485
|
+
}),
|
|
6879
7486
|
);
|
|
6880
7487
|
}
|
|
6881
7488
|
}
|
|
@@ -6889,7 +7496,7 @@ class FigLayer extends HTMLElement {
|
|
|
6889
7496
|
new CustomEvent("openchange", {
|
|
6890
7497
|
detail: { open: isOpen },
|
|
6891
7498
|
bubbles: true,
|
|
6892
|
-
})
|
|
7499
|
+
}),
|
|
6893
7500
|
);
|
|
6894
7501
|
}
|
|
6895
7502
|
|
|
@@ -6899,7 +7506,7 @@ class FigLayer extends HTMLElement {
|
|
|
6899
7506
|
new CustomEvent("visibilitychange", {
|
|
6900
7507
|
detail: { visible: isVisible },
|
|
6901
7508
|
bubbles: true,
|
|
6902
|
-
})
|
|
7509
|
+
}),
|
|
6903
7510
|
);
|
|
6904
7511
|
}
|
|
6905
7512
|
}
|
|
@@ -6979,7 +7586,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
6979
7586
|
|
|
6980
7587
|
#setupTrigger() {
|
|
6981
7588
|
const child = Array.from(this.children).find(
|
|
6982
|
-
(el) => !el.getAttribute("slot")?.startsWith("mode-")
|
|
7589
|
+
(el) => !el.getAttribute("slot")?.startsWith("mode-"),
|
|
6983
7590
|
);
|
|
6984
7591
|
|
|
6985
7592
|
if (!child) {
|
|
@@ -7078,7 +7685,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7078
7685
|
bg = `url(${this.#image.url})`;
|
|
7079
7686
|
const sizing = this.#getBackgroundSizing(
|
|
7080
7687
|
this.#image.scaleMode,
|
|
7081
|
-
this.#image.scale
|
|
7688
|
+
this.#image.scale,
|
|
7082
7689
|
);
|
|
7083
7690
|
bgSize = sizing.size;
|
|
7084
7691
|
bgPosition = sizing.position;
|
|
@@ -7091,7 +7698,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7091
7698
|
bg = `url(${this.#video.url})`;
|
|
7092
7699
|
const sizing = this.#getBackgroundSizing(
|
|
7093
7700
|
this.#video.scaleMode,
|
|
7094
|
-
this.#video.scale
|
|
7701
|
+
this.#video.scale,
|
|
7095
7702
|
);
|
|
7096
7703
|
bgSize = sizing.size;
|
|
7097
7704
|
bgPosition = sizing.position;
|
|
@@ -7186,7 +7793,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7186
7793
|
if (mode) {
|
|
7187
7794
|
const requested = mode.split(",").map((m) => m.trim().toLowerCase());
|
|
7188
7795
|
allowedModes = requested.filter(
|
|
7189
|
-
(m) => builtinModes.includes(m) || this.#customSlots[m]
|
|
7796
|
+
(m) => builtinModes.includes(m) || this.#customSlots[m],
|
|
7190
7797
|
);
|
|
7191
7798
|
if (allowedModes.length === 0) allowedModes = [...builtinModes];
|
|
7192
7799
|
} else {
|
|
@@ -7240,9 +7847,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7240
7847
|
|
|
7241
7848
|
// Populate custom tab containers and emit modeready
|
|
7242
7849
|
for (const [modeName, { element }] of Object.entries(this.#customSlots)) {
|
|
7243
|
-
const container = this.#dialog.querySelector(
|
|
7244
|
-
`[data-tab="${modeName}"]`
|
|
7245
|
-
);
|
|
7850
|
+
const container = this.#dialog.querySelector(`[data-tab="${modeName}"]`);
|
|
7246
7851
|
if (!container) continue;
|
|
7247
7852
|
|
|
7248
7853
|
// Move children (not the element itself) for vanilla HTML usage
|
|
@@ -7255,7 +7860,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7255
7860
|
new CustomEvent("modeready", {
|
|
7256
7861
|
bubbles: true,
|
|
7257
7862
|
detail: { mode: modeName, container },
|
|
7258
|
-
})
|
|
7863
|
+
}),
|
|
7259
7864
|
);
|
|
7260
7865
|
}
|
|
7261
7866
|
|
|
@@ -7292,9 +7897,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7292
7897
|
// Listen for input/change from custom tab content
|
|
7293
7898
|
for (const modeName of Object.keys(this.#customSlots)) {
|
|
7294
7899
|
if (builtinModes.includes(modeName)) continue;
|
|
7295
|
-
const container = this.#dialog.querySelector(
|
|
7296
|
-
`[data-tab="${modeName}"]`
|
|
7297
|
-
);
|
|
7900
|
+
const container = this.#dialog.querySelector(`[data-tab="${modeName}"]`);
|
|
7298
7901
|
if (!container) continue;
|
|
7299
7902
|
container.addEventListener("input", (e) => {
|
|
7300
7903
|
if (e.target === this) return;
|
|
@@ -7314,7 +7917,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7314
7917
|
#switchTab(tabName) {
|
|
7315
7918
|
// Only allow switching to modes that have a tab container in the dialog
|
|
7316
7919
|
const tab = this.#dialog?.querySelector(
|
|
7317
|
-
`.fig-fill-picker-tab[data-tab="${tabName}"]
|
|
7920
|
+
`.fig-fill-picker-tab[data-tab="${tabName}"]`,
|
|
7318
7921
|
);
|
|
7319
7922
|
if (!tab) return;
|
|
7320
7923
|
|
|
@@ -7380,7 +7983,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7380
7983
|
<div class="fig-fill-picker-inputs">
|
|
7381
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>
|
|
7382
7985
|
<fig-input-color class="fig-fill-picker-color-input" text="true" picker="false" value="${this.#hsvToHex(
|
|
7383
|
-
this.#color
|
|
7986
|
+
this.#color,
|
|
7384
7987
|
)}"></fig-input-color>
|
|
7385
7988
|
</div>
|
|
7386
7989
|
`;
|
|
@@ -7408,7 +8011,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7408
8011
|
// Setup opacity slider
|
|
7409
8012
|
if (showAlpha) {
|
|
7410
8013
|
this.#opacitySlider = container.querySelector(
|
|
7411
|
-
'fig-slider[type="opacity"]'
|
|
8014
|
+
'fig-slider[type="opacity"]',
|
|
7412
8015
|
);
|
|
7413
8016
|
this.#opacitySlider.addEventListener("input", (e) => {
|
|
7414
8017
|
this.#color.a = parseFloat(e.target.value) / 100;
|
|
@@ -7501,13 +8104,13 @@ class FigFillPicker extends HTMLElement {
|
|
|
7501
8104
|
if (!this.#colorAreaHandle || !this.#colorArea) return;
|
|
7502
8105
|
|
|
7503
8106
|
const rect = this.#colorArea.getBoundingClientRect();
|
|
7504
|
-
|
|
8107
|
+
|
|
7505
8108
|
// If the canvas isn't visible yet (0 dimensions), schedule a retry (max 5 attempts)
|
|
7506
8109
|
if ((rect.width === 0 || rect.height === 0) && retryCount < 5) {
|
|
7507
8110
|
requestAnimationFrame(() => this.#updateHandlePosition(retryCount + 1));
|
|
7508
8111
|
return;
|
|
7509
8112
|
}
|
|
7510
|
-
|
|
8113
|
+
|
|
7511
8114
|
const x = (this.#color.s / 100) * rect.width;
|
|
7512
8115
|
const y = ((100 - this.#color.v) / 100) * rect.height;
|
|
7513
8116
|
|
|
@@ -7515,7 +8118,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7515
8118
|
this.#colorAreaHandle.style.top = `${y}px`;
|
|
7516
8119
|
this.#colorAreaHandle.style.setProperty(
|
|
7517
8120
|
"--picker-color",
|
|
7518
|
-
this.#hsvToHex({ ...this.#color, a: 1 })
|
|
8121
|
+
this.#hsvToHex({ ...this.#color, a: 1 }),
|
|
7519
8122
|
);
|
|
7520
8123
|
}
|
|
7521
8124
|
|
|
@@ -7578,7 +8181,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7578
8181
|
const hex = this.#hsvToHex(this.#color);
|
|
7579
8182
|
|
|
7580
8183
|
const colorInput = this.#dialog.querySelector(
|
|
7581
|
-
".fig-fill-picker-color-input"
|
|
8184
|
+
".fig-fill-picker-color-input",
|
|
7582
8185
|
);
|
|
7583
8186
|
if (colorInput) {
|
|
7584
8187
|
colorInput.setAttribute("value", hex);
|
|
@@ -7647,7 +8250,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7647
8250
|
#setupGradientEvents(container) {
|
|
7648
8251
|
// Type dropdown
|
|
7649
8252
|
const typeDropdown = container.querySelector(
|
|
7650
|
-
".fig-fill-picker-gradient-type"
|
|
8253
|
+
".fig-fill-picker-gradient-type",
|
|
7651
8254
|
);
|
|
7652
8255
|
typeDropdown.addEventListener("change", (e) => {
|
|
7653
8256
|
this.#gradient.type = e.target.value;
|
|
@@ -7658,7 +8261,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7658
8261
|
// Angle input
|
|
7659
8262
|
// Convert from fig-input-angle coordinates (0° = right) to CSS coordinates (0° = up)
|
|
7660
8263
|
const angleInput = container.querySelector(
|
|
7661
|
-
".fig-fill-picker-gradient-angle"
|
|
8264
|
+
".fig-fill-picker-gradient-angle",
|
|
7662
8265
|
);
|
|
7663
8266
|
angleInput.addEventListener("input", (e) => {
|
|
7664
8267
|
const pickerAngle = parseFloat(e.target.value) || 0;
|
|
@@ -7717,10 +8320,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
7717
8320
|
|
|
7718
8321
|
// Show/hide angle vs center inputs
|
|
7719
8322
|
const angleInput = container.querySelector(
|
|
7720
|
-
".fig-fill-picker-gradient-angle"
|
|
8323
|
+
".fig-fill-picker-gradient-angle",
|
|
7721
8324
|
);
|
|
7722
8325
|
const centerInputs = container.querySelector(
|
|
7723
|
-
".fig-fill-picker-gradient-center"
|
|
8326
|
+
".fig-fill-picker-gradient-center",
|
|
7724
8327
|
);
|
|
7725
8328
|
|
|
7726
8329
|
if (this.#gradient.type === "radial") {
|
|
@@ -7753,7 +8356,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7753
8356
|
if (!this.#dialog) return;
|
|
7754
8357
|
|
|
7755
8358
|
const list = this.#dialog.querySelector(
|
|
7756
|
-
".fig-fill-picker-gradient-stops-list"
|
|
8359
|
+
".fig-fill-picker-gradient-stops-list",
|
|
7757
8360
|
);
|
|
7758
8361
|
if (!list) return;
|
|
7759
8362
|
|
|
@@ -7773,7 +8376,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7773
8376
|
<span class="fig-mask-icon" style="--icon: var(--icon-minus)"></span>
|
|
7774
8377
|
</fig-button>
|
|
7775
8378
|
</div>
|
|
7776
|
-
|
|
8379
|
+
`,
|
|
7777
8380
|
)
|
|
7778
8381
|
.join("");
|
|
7779
8382
|
|
|
@@ -7804,14 +8407,15 @@ class FigFillPicker extends HTMLElement {
|
|
|
7804
8407
|
}
|
|
7805
8408
|
|
|
7806
8409
|
stopColor.addEventListener("input", (e) => {
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
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
|
+
});
|
|
7815
8419
|
|
|
7816
8420
|
row
|
|
7817
8421
|
.querySelector(".fig-fill-picker-stop-remove")
|
|
@@ -7884,7 +8488,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7884
8488
|
|
|
7885
8489
|
#setupImageEvents(container) {
|
|
7886
8490
|
const scaleModeDropdown = container.querySelector(
|
|
7887
|
-
".fig-fill-picker-scale-mode"
|
|
8491
|
+
".fig-fill-picker-scale-mode",
|
|
7888
8492
|
);
|
|
7889
8493
|
const scaleInput = container.querySelector(".fig-fill-picker-scale");
|
|
7890
8494
|
const uploadBtn = container.querySelector(".fig-fill-picker-upload");
|
|
@@ -7926,7 +8530,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
7926
8530
|
|
|
7927
8531
|
// Drag and drop
|
|
7928
8532
|
const previewArea = container.querySelector(
|
|
7929
|
-
".fig-fill-picker-media-preview"
|
|
8533
|
+
".fig-fill-picker-media-preview",
|
|
7930
8534
|
);
|
|
7931
8535
|
previewArea.addEventListener("dragover", (e) => {
|
|
7932
8536
|
e.preventDefault();
|
|
@@ -8034,7 +8638,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8034
8638
|
|
|
8035
8639
|
#setupVideoEvents(container) {
|
|
8036
8640
|
const scaleModeDropdown = container.querySelector(
|
|
8037
|
-
".fig-fill-picker-scale-mode"
|
|
8641
|
+
".fig-fill-picker-scale-mode",
|
|
8038
8642
|
);
|
|
8039
8643
|
const uploadBtn = container.querySelector(".fig-fill-picker-upload");
|
|
8040
8644
|
const fileInput = container.querySelector('input[type="file"]');
|
|
@@ -8053,7 +8657,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8053
8657
|
|
|
8054
8658
|
// Drag and drop
|
|
8055
8659
|
const previewArea = container.querySelector(
|
|
8056
|
-
".fig-fill-picker-media-preview"
|
|
8660
|
+
".fig-fill-picker-media-preview",
|
|
8057
8661
|
);
|
|
8058
8662
|
|
|
8059
8663
|
fileInput.addEventListener("change", (e) => {
|
|
@@ -8124,10 +8728,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
8124
8728
|
const video = container.querySelector(".fig-fill-picker-webcam-video");
|
|
8125
8729
|
const status = container.querySelector(".fig-fill-picker-webcam-status");
|
|
8126
8730
|
const captureBtn = container.querySelector(
|
|
8127
|
-
".fig-fill-picker-webcam-capture"
|
|
8731
|
+
".fig-fill-picker-webcam-capture",
|
|
8128
8732
|
);
|
|
8129
8733
|
const cameraSelect = container.querySelector(
|
|
8130
|
-
".fig-fill-picker-camera-select"
|
|
8734
|
+
".fig-fill-picker-camera-select",
|
|
8131
8735
|
);
|
|
8132
8736
|
|
|
8133
8737
|
const startWebcam = async (deviceId = null) => {
|
|
@@ -8140,9 +8744,8 @@ class FigFillPicker extends HTMLElement {
|
|
|
8140
8744
|
this.#webcam.stream.getTracks().forEach((track) => track.stop());
|
|
8141
8745
|
}
|
|
8142
8746
|
|
|
8143
|
-
this.#webcam.stream =
|
|
8144
|
-
constraints
|
|
8145
|
-
);
|
|
8747
|
+
this.#webcam.stream =
|
|
8748
|
+
await navigator.mediaDevices.getUserMedia(constraints);
|
|
8146
8749
|
video.srcObject = this.#webcam.stream;
|
|
8147
8750
|
video.style.display = "block";
|
|
8148
8751
|
status.style.display = "none";
|
|
@@ -8158,7 +8761,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8158
8761
|
(cam, i) =>
|
|
8159
8762
|
`<option value="${cam.deviceId}">${
|
|
8160
8763
|
cam.label || `Camera ${i + 1}`
|
|
8161
|
-
}</option
|
|
8764
|
+
}</option>`,
|
|
8162
8765
|
)
|
|
8163
8766
|
.join("");
|
|
8164
8767
|
}
|
|
@@ -8450,7 +9053,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8450
9053
|
new CustomEvent("input", {
|
|
8451
9054
|
bubbles: true,
|
|
8452
9055
|
detail: this.value,
|
|
8453
|
-
})
|
|
9056
|
+
}),
|
|
8454
9057
|
);
|
|
8455
9058
|
}
|
|
8456
9059
|
|
|
@@ -8459,7 +9062,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8459
9062
|
new CustomEvent("change", {
|
|
8460
9063
|
bubbles: true,
|
|
8461
9064
|
detail: this.value,
|
|
8462
|
-
})
|
|
9065
|
+
}),
|
|
8463
9066
|
);
|
|
8464
9067
|
}
|
|
8465
9068
|
|
|
@@ -8531,7 +9134,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
8531
9134
|
this.#opacitySlider.setAttribute("value", this.#color.a * 100);
|
|
8532
9135
|
this.#opacitySlider.setAttribute(
|
|
8533
9136
|
"color",
|
|
8534
|
-
this.#hsvToHex(this.#color)
|
|
9137
|
+
this.#hsvToHex(this.#color),
|
|
8535
9138
|
);
|
|
8536
9139
|
}
|
|
8537
9140
|
}
|