@rogieking/figui3 8.2.1 → 8.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/components.css +41 -71
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-editor.js +3 -193
- package/dist/fig-lab.js +2 -59
- package/dist/fig.css +1 -1
- package/dist/fig.js +33 -272
- package/fig-editor.css +129 -28
- package/fig-editor.js +1303 -354
- package/fig-lab.js +319 -133
- package/fig.js +883 -831
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -27,6 +27,51 @@ function createFigIcon(name, options = {}) {
|
|
|
27
27
|
return icon;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const FIG_SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
31
|
+
|
|
32
|
+
function figAppendChildren(parent, children) {
|
|
33
|
+
const append = (child) => {
|
|
34
|
+
if (child === null || child === undefined || child === false) return;
|
|
35
|
+
if (Array.isArray(child)) {
|
|
36
|
+
child.forEach(append);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
parent.append(child instanceof Node ? child : String(child));
|
|
40
|
+
};
|
|
41
|
+
append(children);
|
|
42
|
+
return parent;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function figSetAttributes(element, attributes = {}) {
|
|
46
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
47
|
+
if (value === null || value === undefined || value === false) continue;
|
|
48
|
+
if (name === "className") {
|
|
49
|
+
if (element.namespaceURI === FIG_SVG_NAMESPACE) {
|
|
50
|
+
element.setAttribute("class", String(value));
|
|
51
|
+
} else {
|
|
52
|
+
element.className = String(value);
|
|
53
|
+
}
|
|
54
|
+
} else if (value === true) {
|
|
55
|
+
element.setAttribute(name, "");
|
|
56
|
+
} else {
|
|
57
|
+
element.setAttribute(name, String(value));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return element;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function figCreateElement(tagName, attributes, children) {
|
|
64
|
+
const element = document.createElement(tagName);
|
|
65
|
+
figSetAttributes(element, attributes);
|
|
66
|
+
return figAppendChildren(element, children);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function figCreateSvgElement(tagName, attributes, children) {
|
|
70
|
+
const element = document.createElementNS(FIG_SVG_NAMESPACE, tagName);
|
|
71
|
+
figSetAttributes(element, attributes);
|
|
72
|
+
return figAppendChildren(element, children);
|
|
73
|
+
}
|
|
74
|
+
|
|
30
75
|
/** Run callback on the next frame; skip if the host disconnected first. */
|
|
31
76
|
function figNextFrame(host, callback) {
|
|
32
77
|
requestAnimationFrame(() => {
|
|
@@ -387,46 +432,50 @@ class FigButton extends HTMLElement {
|
|
|
387
432
|
// Custom button modes are implemented by the host. Keep the shadow
|
|
388
433
|
// control inert so invalid native types (for example "toggle") cannot
|
|
389
434
|
// normalize to "submit" inside a form.
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
435
|
+
const style = document.createElement("style");
|
|
436
|
+
style.textContent = `
|
|
437
|
+
button, button:hover, button:active, .fig-button-control {
|
|
438
|
+
padding: 0 var(--spacer-2);
|
|
439
|
+
appearance: none;
|
|
440
|
+
display: flex;
|
|
441
|
+
border: 0;
|
|
442
|
+
flex: 1;
|
|
443
|
+
text-align: center;
|
|
444
|
+
align-items: stretch;
|
|
445
|
+
justify-content: center;
|
|
446
|
+
font: inherit;
|
|
447
|
+
color: inherit;
|
|
448
|
+
outline: 0;
|
|
449
|
+
place-items: center;
|
|
450
|
+
background: transparent;
|
|
451
|
+
margin: calc(var(--spacer-2)*-1);
|
|
452
|
+
height: var(--spacer-4);
|
|
453
|
+
white-space: nowrap;
|
|
454
|
+
overflow: hidden;
|
|
455
|
+
text-overflow: ellipsis;
|
|
456
|
+
width: 100%;
|
|
457
|
+
min-width: 0;
|
|
458
|
+
}
|
|
459
|
+
:host([size="large"]) button,
|
|
460
|
+
:host([size="large"]) .fig-button-control {
|
|
461
|
+
height: var(--spacer-5);
|
|
462
|
+
}
|
|
463
|
+
:host([size="large"][icon]) button,
|
|
464
|
+
:host([size="large"][icon]) .fig-button-control {
|
|
465
|
+
padding: 0;
|
|
466
|
+
}
|
|
467
|
+
`;
|
|
468
|
+
const control = figCreateElement(
|
|
469
|
+
controlTag,
|
|
470
|
+
{
|
|
471
|
+
className: "fig-button-control",
|
|
472
|
+
type: isControlWrapper ? null : "button",
|
|
473
|
+
},
|
|
474
|
+
document.createElement("slot"),
|
|
475
|
+
);
|
|
476
|
+
this.shadowRoot.replaceChildren(style, control);
|
|
477
|
+
|
|
478
|
+
this.button = control;
|
|
430
479
|
this.button.addEventListener("click", this.#boundHandleClick);
|
|
431
480
|
this.button.addEventListener("focus", this.#boundHandleFocus);
|
|
432
481
|
this.button.addEventListener("blur", this.#boundHandleBlur);
|
|
@@ -1573,7 +1622,7 @@ class FigTruncate extends HTMLElement {
|
|
|
1573
1622
|
} else {
|
|
1574
1623
|
splitIndex = Math.ceil(text.length / 2);
|
|
1575
1624
|
}
|
|
1576
|
-
this.
|
|
1625
|
+
this.replaceChildren();
|
|
1577
1626
|
const startSpan = document.createElement("span");
|
|
1578
1627
|
startSpan.className = "start";
|
|
1579
1628
|
startSpan.textContent = text.slice(0, splitIndex);
|
|
@@ -4960,7 +5009,7 @@ class FigOptions extends HTMLElement {
|
|
|
4960
5009
|
}
|
|
4961
5010
|
|
|
4962
5011
|
#renderSegments() {
|
|
4963
|
-
this.
|
|
5012
|
+
this.replaceChildren();
|
|
4964
5013
|
if (this.#parsedOptions.length === 0) return;
|
|
4965
5014
|
|
|
4966
5015
|
const sc = document.createElement("fig-segmented-control");
|
|
@@ -5019,7 +5068,7 @@ class FigOptions extends HTMLElement {
|
|
|
5019
5068
|
}
|
|
5020
5069
|
|
|
5021
5070
|
#renderDropdown() {
|
|
5022
|
-
this.
|
|
5071
|
+
this.replaceChildren();
|
|
5023
5072
|
if (this.#parsedOptions.length === 0) return;
|
|
5024
5073
|
|
|
5025
5074
|
const dd = document.createElement("fig-dropdown");
|
|
@@ -5207,7 +5256,7 @@ class FigSlider extends HTMLElement {
|
|
|
5207
5256
|
constructor() {
|
|
5208
5257
|
super();
|
|
5209
5258
|
// Parser-created custom elements do not have their children yet here.
|
|
5210
|
-
this.
|
|
5259
|
+
this.initialChildren = null;
|
|
5211
5260
|
|
|
5212
5261
|
// Bind the event handlers
|
|
5213
5262
|
this.#boundHandleInput = (e) => {
|
|
@@ -5239,7 +5288,7 @@ class FigSlider extends HTMLElement {
|
|
|
5239
5288
|
this.#isInteracting = false;
|
|
5240
5289
|
if (this.#pendingRegeneration && this.isConnected) {
|
|
5241
5290
|
this.#pendingRegeneration = false;
|
|
5242
|
-
this.#
|
|
5291
|
+
this.#regenerateMarkup();
|
|
5243
5292
|
}
|
|
5244
5293
|
};
|
|
5245
5294
|
}
|
|
@@ -5372,48 +5421,57 @@ class FigSlider extends HTMLElement {
|
|
|
5372
5421
|
}
|
|
5373
5422
|
}
|
|
5374
5423
|
|
|
5375
|
-
#
|
|
5424
|
+
#regenerateMarkup() {
|
|
5376
5425
|
this.#readAttributesFromMarkup();
|
|
5377
5426
|
|
|
5378
5427
|
if (this.color) {
|
|
5379
5428
|
this.style.setProperty("--color", this.color);
|
|
5380
5429
|
}
|
|
5381
5430
|
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5431
|
+
const input = figCreateElement("input", {
|
|
5432
|
+
type: "range",
|
|
5433
|
+
tabindex: this.text ? "-1" : null,
|
|
5434
|
+
disabled: this.disabled,
|
|
5435
|
+
min: this.min,
|
|
5436
|
+
max: this.max,
|
|
5437
|
+
step: this.step,
|
|
5438
|
+
className: this.type,
|
|
5439
|
+
value: this.value,
|
|
5440
|
+
"aria-valuemin": this.min,
|
|
5441
|
+
"aria-valuemax": this.max,
|
|
5442
|
+
"aria-valuenow": this.value,
|
|
5443
|
+
});
|
|
5444
|
+
const inputContainer = figCreateElement(
|
|
5445
|
+
"div",
|
|
5446
|
+
{
|
|
5447
|
+
className: "fig-slider-input-container",
|
|
5448
|
+
role: "group",
|
|
5449
|
+
},
|
|
5450
|
+
input,
|
|
5451
|
+
);
|
|
5452
|
+
for (const child of this.initialChildren ?? []) {
|
|
5453
|
+
inputContainer.appendChild(child);
|
|
5454
|
+
}
|
|
5455
|
+
|
|
5456
|
+
const renderedChildren = [inputContainer];
|
|
5398
5457
|
if (this.text) {
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
html = slider;
|
|
5458
|
+
renderedChildren.push(
|
|
5459
|
+
figCreateElement("fig-input-number", {
|
|
5460
|
+
placeholder: this.placeholder,
|
|
5461
|
+
min: this.min,
|
|
5462
|
+
max: this.max,
|
|
5463
|
+
transform: this.transform,
|
|
5464
|
+
step: this.step,
|
|
5465
|
+
value: this.value,
|
|
5466
|
+
units: this.units || null,
|
|
5467
|
+
precision: this.precision,
|
|
5468
|
+
}),
|
|
5469
|
+
);
|
|
5412
5470
|
}
|
|
5413
|
-
this.
|
|
5471
|
+
this.replaceChildren(...renderedChildren);
|
|
5414
5472
|
|
|
5415
|
-
this.input =
|
|
5416
|
-
this.inputContainer =
|
|
5473
|
+
this.input = input;
|
|
5474
|
+
this.inputContainer = inputContainer;
|
|
5417
5475
|
this.figInputNumber = this.querySelector("fig-input-number");
|
|
5418
5476
|
this.#bindControlListeners();
|
|
5419
5477
|
|
|
@@ -5449,8 +5507,8 @@ class FigSlider extends HTMLElement {
|
|
|
5449
5507
|
this.input.setAttribute("list", this.datalist.getAttribute("id"));
|
|
5450
5508
|
}
|
|
5451
5509
|
if (this.datalist) {
|
|
5452
|
-
|
|
5453
|
-
|
|
5510
|
+
const defaultOption = [...this.datalist.querySelectorAll("option")].find(
|
|
5511
|
+
(option) => option.value === String(this.default),
|
|
5454
5512
|
);
|
|
5455
5513
|
if (defaultOption) {
|
|
5456
5514
|
defaultOption.setAttribute("default", "true");
|
|
@@ -5460,14 +5518,16 @@ class FigSlider extends HTMLElement {
|
|
|
5460
5518
|
}
|
|
5461
5519
|
|
|
5462
5520
|
connectedCallback() {
|
|
5463
|
-
if (this.
|
|
5521
|
+
if (this.initialChildren === null) {
|
|
5522
|
+
this.initialChildren = Array.from(this.childNodes);
|
|
5523
|
+
}
|
|
5464
5524
|
if (this.#canReuseRenderedMarkup()) {
|
|
5465
5525
|
this.#updateRenderedMarkup();
|
|
5466
5526
|
this.#bindControlListeners();
|
|
5467
5527
|
this.#syncValue();
|
|
5468
5528
|
return;
|
|
5469
5529
|
}
|
|
5470
|
-
this.#
|
|
5530
|
+
this.#regenerateMarkup();
|
|
5471
5531
|
}
|
|
5472
5532
|
|
|
5473
5533
|
get value() {
|
|
@@ -5752,7 +5812,7 @@ class FigSlider extends HTMLElement {
|
|
|
5752
5812
|
return;
|
|
5753
5813
|
}
|
|
5754
5814
|
this.#pendingRegeneration = false;
|
|
5755
|
-
this.#
|
|
5815
|
+
this.#regenerateMarkup();
|
|
5756
5816
|
}
|
|
5757
5817
|
|
|
5758
5818
|
static get observedAttributes() {
|
|
@@ -6581,9 +6641,18 @@ class FigInputNumber extends HTMLElement {
|
|
|
6581
6641
|
if (hasSteppers && !this.#stepperEl) {
|
|
6582
6642
|
this.#stepperEl = document.createElement("span");
|
|
6583
6643
|
this.#stepperEl.className = "fig-steppers";
|
|
6584
|
-
this.#stepperEl.
|
|
6585
|
-
|
|
6586
|
-
|
|
6644
|
+
this.#stepperEl.append(
|
|
6645
|
+
figCreateElement("button", {
|
|
6646
|
+
className: "fig-stepper-up",
|
|
6647
|
+
tabindex: "-1",
|
|
6648
|
+
"aria-label": "Increase",
|
|
6649
|
+
}),
|
|
6650
|
+
figCreateElement("button", {
|
|
6651
|
+
className: "fig-stepper-down",
|
|
6652
|
+
tabindex: "-1",
|
|
6653
|
+
"aria-label": "Decrease",
|
|
6654
|
+
}),
|
|
6655
|
+
);
|
|
6587
6656
|
this.#stepperEl.addEventListener("pointerdown", (e) => {
|
|
6588
6657
|
e.preventDefault();
|
|
6589
6658
|
e.stopPropagation();
|
|
@@ -7511,7 +7580,9 @@ class FigInputColor extends HTMLElement {
|
|
|
7511
7580
|
name !== "picker-anchor" &&
|
|
7512
7581
|
name !== "picker-experimental"
|
|
7513
7582
|
) {
|
|
7514
|
-
|
|
7583
|
+
const forwardedName = name.slice(7);
|
|
7584
|
+
if (/^on/i.test(forwardedName)) continue;
|
|
7585
|
+
attrs[forwardedName] = value;
|
|
7515
7586
|
}
|
|
7516
7587
|
}
|
|
7517
7588
|
if (!attrs["dialog-position"]) attrs["dialog-position"] = "left";
|
|
@@ -7630,41 +7701,49 @@ class FigInputColor extends HTMLElement {
|
|
|
7630
7701
|
|
|
7631
7702
|
const showAlpha = this.getAttribute("alpha") !== "false";
|
|
7632
7703
|
const disabled = this.#disabled;
|
|
7633
|
-
const disabledAttr = disabled ? " disabled" : "";
|
|
7634
|
-
|
|
7635
|
-
let html = ``;
|
|
7636
7704
|
const showText = this.getAttribute("text") !== "false";
|
|
7705
|
+
const swatch = figCreateElement("fig-swatch", {
|
|
7706
|
+
background: this.hexOpaque,
|
|
7707
|
+
alpha: this.rgba.a,
|
|
7708
|
+
disabled,
|
|
7709
|
+
});
|
|
7710
|
+
|
|
7637
7711
|
if (showText) {
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7712
|
+
const combo = figCreateElement(
|
|
7713
|
+
"div",
|
|
7714
|
+
{ className: "input-combo" },
|
|
7715
|
+
[
|
|
7716
|
+
swatch,
|
|
7717
|
+
figCreateElement("fig-input-text", {
|
|
7718
|
+
type: "text",
|
|
7719
|
+
placeholder: "000000",
|
|
7720
|
+
value: this.hexOpaque.slice(1).toUpperCase(),
|
|
7721
|
+
disabled,
|
|
7722
|
+
}),
|
|
7723
|
+
],
|
|
7724
|
+
);
|
|
7643
7725
|
if (showAlpha) {
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
${label}
|
|
7661
|
-
</div>`;
|
|
7726
|
+
combo.appendChild(
|
|
7727
|
+
figCreateElement(
|
|
7728
|
+
"fig-tooltip",
|
|
7729
|
+
{ text: "Opacity" },
|
|
7730
|
+
figCreateElement("fig-input-number", {
|
|
7731
|
+
placeholder: "##",
|
|
7732
|
+
min: "0",
|
|
7733
|
+
max: "100",
|
|
7734
|
+
value: this.#alphaPercent,
|
|
7735
|
+
units: "%",
|
|
7736
|
+
disabled,
|
|
7737
|
+
}),
|
|
7738
|
+
),
|
|
7739
|
+
);
|
|
7740
|
+
}
|
|
7741
|
+
this.replaceChildren(combo);
|
|
7662
7742
|
} else {
|
|
7663
|
-
|
|
7743
|
+
this.replaceChildren(swatch);
|
|
7664
7744
|
}
|
|
7665
|
-
this.innerHTML = html;
|
|
7666
7745
|
|
|
7667
|
-
this.#swatch =
|
|
7746
|
+
this.#swatch = swatch;
|
|
7668
7747
|
this.#fillPicker = this.querySelector("fig-fill-picker");
|
|
7669
7748
|
this.#textInput = this.querySelector("fig-input-text:not([type=number])");
|
|
7670
7749
|
this.#alphaInput = this.querySelector("fig-input-number");
|
|
@@ -7716,7 +7795,7 @@ class FigInputColor extends HTMLElement {
|
|
|
7716
7795
|
}
|
|
7717
7796
|
|
|
7718
7797
|
const picker = document.createElement("fig-fill-picker");
|
|
7719
|
-
picker.
|
|
7798
|
+
picker.appendChild(figCreateElement("span", { hidden: true }));
|
|
7720
7799
|
picker.addEventListener("input", this.#boundFillPickerInput);
|
|
7721
7800
|
picker.addEventListener("change", this.#boundChange);
|
|
7722
7801
|
this.appendChild(picker);
|
|
@@ -8636,13 +8715,13 @@ class FigInputFill extends HTMLElement {
|
|
|
8636
8715
|
name !== "picker-anchor" &&
|
|
8637
8716
|
name !== "picker-experimental"
|
|
8638
8717
|
) {
|
|
8639
|
-
|
|
8718
|
+
const forwardedName = name.slice(7);
|
|
8719
|
+
if (/^on/i.test(forwardedName)) continue;
|
|
8720
|
+
attrs[forwardedName] = value;
|
|
8640
8721
|
}
|
|
8641
8722
|
}
|
|
8642
8723
|
if (!attrs["dialog-position"]) attrs["dialog-position"] = "left";
|
|
8643
|
-
return
|
|
8644
|
-
.map(([k, v]) => `${k}="${figEscapeAttribute(v)}"`)
|
|
8645
|
-
.join(" ");
|
|
8724
|
+
return attrs;
|
|
8646
8725
|
}
|
|
8647
8726
|
|
|
8648
8727
|
#fillPickerSwatchBackground() {
|
|
@@ -8666,6 +8745,10 @@ class FigInputFill extends HTMLElement {
|
|
|
8666
8745
|
}
|
|
8667
8746
|
case "image":
|
|
8668
8747
|
return this.#image.url ? `url(${this.#image.url})` : "#D9D9D9";
|
|
8748
|
+
case "webcam":
|
|
8749
|
+
return this.#webcam.snapshot
|
|
8750
|
+
? `url(${this.#webcam.snapshot})`
|
|
8751
|
+
: "#D9D9D9";
|
|
8669
8752
|
default:
|
|
8670
8753
|
return "#D9D9D9";
|
|
8671
8754
|
}
|
|
@@ -8733,81 +8816,97 @@ class FigInputFill extends HTMLElement {
|
|
|
8733
8816
|
syncState(this.#opacityInput, `${name} opacity`);
|
|
8734
8817
|
}
|
|
8735
8818
|
|
|
8736
|
-
#
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
${disabled ? "disabled" : ""}>
|
|
8753
|
-
</fig-input-number>
|
|
8754
|
-
</fig-tooltip>`
|
|
8755
|
-
: "";
|
|
8819
|
+
#createOpacityControl(value, disabled) {
|
|
8820
|
+
if (this.getAttribute("alpha") === "false") return null;
|
|
8821
|
+
return figCreateElement(
|
|
8822
|
+
"fig-tooltip",
|
|
8823
|
+
{ text: "Opacity" },
|
|
8824
|
+
figCreateElement("fig-input-number", {
|
|
8825
|
+
className: "fig-input-fill-opacity",
|
|
8826
|
+
placeholder: "##",
|
|
8827
|
+
min: "0",
|
|
8828
|
+
max: "100",
|
|
8829
|
+
value,
|
|
8830
|
+
units: "%",
|
|
8831
|
+
disabled,
|
|
8832
|
+
}),
|
|
8833
|
+
);
|
|
8834
|
+
}
|
|
8756
8835
|
|
|
8757
|
-
|
|
8836
|
+
#createControls(disabled) {
|
|
8837
|
+
let label = null;
|
|
8838
|
+
let opacity = 1;
|
|
8758
8839
|
|
|
8759
8840
|
switch (this.#fillType) {
|
|
8760
8841
|
case "solid":
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
type
|
|
8764
|
-
|
|
8765
|
-
placeholder
|
|
8766
|
-
value
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8842
|
+
return [
|
|
8843
|
+
figCreateElement("fig-input-text", {
|
|
8844
|
+
type: "text",
|
|
8845
|
+
className: "fig-input-fill-hex",
|
|
8846
|
+
placeholder: "000000",
|
|
8847
|
+
value: this.#solid.color.slice(1).toUpperCase(),
|
|
8848
|
+
disabled,
|
|
8849
|
+
}),
|
|
8850
|
+
this.#createOpacityControl(
|
|
8851
|
+
Math.round(this.#solid.alpha * 100),
|
|
8852
|
+
disabled,
|
|
8853
|
+
),
|
|
8854
|
+
].filter(Boolean);
|
|
8855
|
+
case "gradient":
|
|
8856
|
+
label =
|
|
8774
8857
|
this.#gradient.type.charAt(0).toUpperCase() +
|
|
8775
8858
|
this.#gradient.type.slice(1);
|
|
8776
|
-
|
|
8777
|
-
<label class="fig-input-fill-label">${figEscapeAttribute(gradientLabel)}</label>
|
|
8778
|
-
${opacityHtml(Math.round((this.#gradient.opacity ?? 1) * 100))}`;
|
|
8859
|
+
opacity = this.#gradient.opacity ?? 1;
|
|
8779
8860
|
break;
|
|
8780
|
-
}
|
|
8781
|
-
|
|
8782
8861
|
case "image":
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
${opacityHtml(Math.round((this.#image.opacity ?? 1) * 100))}`;
|
|
8862
|
+
label = "Image";
|
|
8863
|
+
opacity = this.#image.opacity ?? 1;
|
|
8786
8864
|
break;
|
|
8787
|
-
|
|
8788
8865
|
case "video":
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
${opacityHtml(Math.round((this.#video.opacity ?? 1) * 100))}`;
|
|
8866
|
+
label = "Video";
|
|
8867
|
+
opacity = this.#video.opacity ?? 1;
|
|
8792
8868
|
break;
|
|
8793
|
-
|
|
8794
8869
|
case "webcam":
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
${opacityHtml(Math.round((this.#webcam.opacity ?? 1) * 100))}`;
|
|
8870
|
+
label = "Webcam";
|
|
8871
|
+
opacity = this.#webcam.opacity ?? 1;
|
|
8798
8872
|
break;
|
|
8799
8873
|
}
|
|
8800
8874
|
|
|
8875
|
+
return [
|
|
8876
|
+
figCreateElement(
|
|
8877
|
+
"label",
|
|
8878
|
+
{ className: "fig-input-fill-label" },
|
|
8879
|
+
label,
|
|
8880
|
+
),
|
|
8881
|
+
this.#createOpacityControl(Math.round(opacity * 100), disabled),
|
|
8882
|
+
].filter(Boolean);
|
|
8883
|
+
}
|
|
8884
|
+
|
|
8885
|
+
#render() {
|
|
8886
|
+
const disabled =
|
|
8887
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
8888
|
+
const fillPickerValue = JSON.stringify(this.value);
|
|
8801
8889
|
const fpAttrs = this.#buildFillPickerAttrs();
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8890
|
+
const swatch = figCreateElement("fig-swatch", {
|
|
8891
|
+
background: this.#fillPickerSwatchBackground(),
|
|
8892
|
+
alpha: this.#fillPickerSwatchAlpha(),
|
|
8893
|
+
disabled,
|
|
8894
|
+
});
|
|
8895
|
+
const picker = figCreateElement(
|
|
8896
|
+
"fig-fill-picker",
|
|
8897
|
+
{
|
|
8898
|
+
value: fillPickerValue,
|
|
8899
|
+
disabled,
|
|
8900
|
+
...fpAttrs,
|
|
8901
|
+
},
|
|
8902
|
+
swatch,
|
|
8903
|
+
);
|
|
8904
|
+
const combo = figCreateElement(
|
|
8905
|
+
"div",
|
|
8906
|
+
{ className: "input-combo" },
|
|
8907
|
+
[picker, this.#createControls(disabled)],
|
|
8908
|
+
);
|
|
8909
|
+
this.replaceChildren(combo);
|
|
8811
8910
|
|
|
8812
8911
|
this.#setupEventListeners();
|
|
8813
8912
|
}
|
|
@@ -8870,6 +8969,9 @@ class FigInputFill extends HTMLElement {
|
|
|
8870
8969
|
case "video":
|
|
8871
8970
|
if (detail.video) this.#video = detail.video;
|
|
8872
8971
|
break;
|
|
8972
|
+
case "webcam":
|
|
8973
|
+
if (detail.image?.url) this.#webcam.snapshot = detail.image.url;
|
|
8974
|
+
break;
|
|
8873
8975
|
}
|
|
8874
8976
|
// Update controls (don't re-render to keep dialog open)
|
|
8875
8977
|
if (typeChanged) {
|
|
@@ -9002,7 +9104,6 @@ class FigInputFill extends HTMLElement {
|
|
|
9002
9104
|
// Update only the controls (not the fill picker) when type changes
|
|
9003
9105
|
const disabled =
|
|
9004
9106
|
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
9005
|
-
const showAlpha = this.getAttribute("alpha") !== "false";
|
|
9006
9107
|
const combo = this.querySelector(".input-combo");
|
|
9007
9108
|
if (!combo) return;
|
|
9008
9109
|
|
|
@@ -9016,98 +9117,8 @@ class FigInputFill extends HTMLElement {
|
|
|
9016
9117
|
oldHex?.remove();
|
|
9017
9118
|
oldTooltips.forEach((t) => t.remove());
|
|
9018
9119
|
|
|
9019
|
-
// Generate new controls HTML
|
|
9020
|
-
let controlsHtml = "";
|
|
9021
|
-
switch (this.#fillType) {
|
|
9022
|
-
case "solid":
|
|
9023
|
-
controlsHtml = `
|
|
9024
|
-
<fig-input-text
|
|
9025
|
-
type="text"
|
|
9026
|
-
class="fig-input-fill-hex"
|
|
9027
|
-
placeholder="000000"
|
|
9028
|
-
value="${figEscapeAttribute(this.#solid.color.slice(1).toUpperCase())}"
|
|
9029
|
-
${disabled ? "disabled" : ""}>
|
|
9030
|
-
</fig-input-text>
|
|
9031
|
-
${showAlpha ? `<fig-tooltip text="Opacity">
|
|
9032
|
-
<fig-input-number
|
|
9033
|
-
class="fig-input-fill-opacity"
|
|
9034
|
-
placeholder="##"
|
|
9035
|
-
min="0"
|
|
9036
|
-
max="100"
|
|
9037
|
-
value="${Math.round(this.#solid.alpha * 100)}"
|
|
9038
|
-
units="%"
|
|
9039
|
-
${disabled ? "disabled" : ""}>
|
|
9040
|
-
</fig-input-number>
|
|
9041
|
-
</fig-tooltip>` : ""}`;
|
|
9042
|
-
break;
|
|
9043
|
-
case "gradient": {
|
|
9044
|
-
const gradientLabel =
|
|
9045
|
-
this.#gradient.type.charAt(0).toUpperCase() +
|
|
9046
|
-
this.#gradient.type.slice(1);
|
|
9047
|
-
controlsHtml = `
|
|
9048
|
-
<label class="fig-input-fill-label">${figEscapeAttribute(gradientLabel)}</label>
|
|
9049
|
-
${showAlpha ? `<fig-tooltip text="Opacity">
|
|
9050
|
-
<fig-input-number
|
|
9051
|
-
class="fig-input-fill-opacity"
|
|
9052
|
-
placeholder="##"
|
|
9053
|
-
min="0"
|
|
9054
|
-
max="100"
|
|
9055
|
-
value="${Math.round((this.#gradient.opacity ?? 1) * 100)}"
|
|
9056
|
-
units="%"
|
|
9057
|
-
${disabled ? "disabled" : ""}>
|
|
9058
|
-
</fig-input-number>
|
|
9059
|
-
</fig-tooltip>` : ""}`;
|
|
9060
|
-
break;
|
|
9061
|
-
}
|
|
9062
|
-
case "image":
|
|
9063
|
-
controlsHtml = `
|
|
9064
|
-
<label class="fig-input-fill-label">Image</label>
|
|
9065
|
-
${showAlpha ? `<fig-tooltip text="Opacity">
|
|
9066
|
-
<fig-input-number
|
|
9067
|
-
class="fig-input-fill-opacity"
|
|
9068
|
-
placeholder="##"
|
|
9069
|
-
min="0"
|
|
9070
|
-
max="100"
|
|
9071
|
-
value="${Math.round((this.#image.opacity ?? 1) * 100)}"
|
|
9072
|
-
units="%"
|
|
9073
|
-
${disabled ? "disabled" : ""}>
|
|
9074
|
-
</fig-input-number>
|
|
9075
|
-
</fig-tooltip>` : ""}`;
|
|
9076
|
-
break;
|
|
9077
|
-
case "video":
|
|
9078
|
-
controlsHtml = `
|
|
9079
|
-
<label class="fig-input-fill-label">Video</label>
|
|
9080
|
-
${showAlpha ? `<fig-tooltip text="Opacity">
|
|
9081
|
-
<fig-input-number
|
|
9082
|
-
class="fig-input-fill-opacity"
|
|
9083
|
-
placeholder="##"
|
|
9084
|
-
min="0"
|
|
9085
|
-
max="100"
|
|
9086
|
-
value="${Math.round((this.#video.opacity ?? 1) * 100)}"
|
|
9087
|
-
units="%"
|
|
9088
|
-
${disabled ? "disabled" : ""}>
|
|
9089
|
-
</fig-input-number>
|
|
9090
|
-
</fig-tooltip>` : ""}`;
|
|
9091
|
-
break;
|
|
9092
|
-
case "webcam":
|
|
9093
|
-
controlsHtml = `
|
|
9094
|
-
<label class="fig-input-fill-label">Webcam</label>
|
|
9095
|
-
${showAlpha ? `<fig-tooltip text="Opacity">
|
|
9096
|
-
<fig-input-number
|
|
9097
|
-
class="fig-input-fill-opacity"
|
|
9098
|
-
placeholder="##"
|
|
9099
|
-
min="0"
|
|
9100
|
-
max="100"
|
|
9101
|
-
value="${Math.round((this.#webcam.opacity ?? 1) * 100)}"
|
|
9102
|
-
units="%"
|
|
9103
|
-
${disabled ? "disabled" : ""}>
|
|
9104
|
-
</fig-input-number>
|
|
9105
|
-
</fig-tooltip>` : ""}`;
|
|
9106
|
-
break;
|
|
9107
|
-
}
|
|
9108
|
-
|
|
9109
9120
|
// Append new controls after the fill picker
|
|
9110
|
-
combo.
|
|
9121
|
+
combo.append(...this.#createControls(disabled));
|
|
9111
9122
|
|
|
9112
9123
|
// Re-setup event listeners for the new controls
|
|
9113
9124
|
this.#opacityInput = this.querySelector(".fig-input-fill-opacity");
|
|
@@ -9496,7 +9507,7 @@ class FigInputPalette extends HTMLElement {
|
|
|
9496
9507
|
this.hasAttribute("disabled") &&
|
|
9497
9508
|
this.getAttribute("disabled") !== "false";
|
|
9498
9509
|
|
|
9499
|
-
this.
|
|
9510
|
+
this.replaceChildren();
|
|
9500
9511
|
this.#inlinePickers = [];
|
|
9501
9512
|
this.#expandedPickers = [];
|
|
9502
9513
|
|
|
@@ -9841,6 +9852,30 @@ class FigInputGradient extends HTMLElement {
|
|
|
9841
9852
|
);
|
|
9842
9853
|
}
|
|
9843
9854
|
|
|
9855
|
+
#activeStopHandle() {
|
|
9856
|
+
if (!this.#track) return null;
|
|
9857
|
+
const selected = this.#track.querySelector(
|
|
9858
|
+
"fig-handle[selected]:not(.fig-input-gradient-ghost)",
|
|
9859
|
+
);
|
|
9860
|
+
if (selected) return selected;
|
|
9861
|
+
const active = document.activeElement;
|
|
9862
|
+
if (!(active instanceof Element) || !this.#track.contains(active)) {
|
|
9863
|
+
return null;
|
|
9864
|
+
}
|
|
9865
|
+
return active.closest("fig-handle:not(.fig-input-gradient-ghost)");
|
|
9866
|
+
}
|
|
9867
|
+
|
|
9868
|
+
#activateStopHandle(handle) {
|
|
9869
|
+
if (!handle || !this.#track) return;
|
|
9870
|
+
this.#track
|
|
9871
|
+
.querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)")
|
|
9872
|
+
.forEach((other) => {
|
|
9873
|
+
if (other !== handle) other.deselect();
|
|
9874
|
+
});
|
|
9875
|
+
handle.select();
|
|
9876
|
+
handle.focus();
|
|
9877
|
+
}
|
|
9878
|
+
|
|
9844
9879
|
#syncFocusTarget() {
|
|
9845
9880
|
const disabled =
|
|
9846
9881
|
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
@@ -9910,7 +9945,15 @@ class FigInputGradient extends HTMLElement {
|
|
|
9910
9945
|
|
|
9911
9946
|
#onKeyDown = (e) => {
|
|
9912
9947
|
const active = document.activeElement;
|
|
9913
|
-
|
|
9948
|
+
const activeIsInside = active instanceof Node && this.contains(active);
|
|
9949
|
+
const selectedHandle = this.#track?.querySelector(
|
|
9950
|
+
"fig-handle[selected]:not(.fig-input-gradient-ghost)",
|
|
9951
|
+
);
|
|
9952
|
+
const focusLostToBody =
|
|
9953
|
+
!active ||
|
|
9954
|
+
active === document.body ||
|
|
9955
|
+
active === document.documentElement;
|
|
9956
|
+
if (!activeIsInside && !(selectedHandle && focusLostToBody)) return;
|
|
9914
9957
|
const isTyping =
|
|
9915
9958
|
active &&
|
|
9916
9959
|
(active.tagName === "INPUT" ||
|
|
@@ -9919,9 +9962,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
9919
9962
|
if (!this.#track) return;
|
|
9920
9963
|
|
|
9921
9964
|
if (e.key === "Tab" && !isTyping) {
|
|
9922
|
-
const selected = this.#
|
|
9923
|
-
"fig-handle[selected]:not(.fig-input-gradient-ghost)",
|
|
9924
|
-
);
|
|
9965
|
+
const selected = this.#activeStopHandle();
|
|
9925
9966
|
if (!selected) return;
|
|
9926
9967
|
e.preventDefault();
|
|
9927
9968
|
const handles = [
|
|
@@ -9934,14 +9975,12 @@ class FigInputGradient extends HTMLElement {
|
|
|
9934
9975
|
? (curIdx - 1 + handles.length) % handles.length
|
|
9935
9976
|
: (curIdx + 1) % handles.length;
|
|
9936
9977
|
selected.deselect();
|
|
9937
|
-
handles[next]
|
|
9978
|
+
this.#activateStopHandle(handles[next]);
|
|
9938
9979
|
return;
|
|
9939
9980
|
}
|
|
9940
9981
|
|
|
9941
9982
|
if ((e.key === "ArrowLeft" || e.key === "ArrowRight") && !isTyping) {
|
|
9942
|
-
const selected = this.#
|
|
9943
|
-
"fig-handle[selected]:not(.fig-input-gradient-ghost)",
|
|
9944
|
-
);
|
|
9983
|
+
const selected = this.#activeStopHandle();
|
|
9945
9984
|
if (!selected) return;
|
|
9946
9985
|
const idx = parseInt(selected.dataset.stopIndex, 10);
|
|
9947
9986
|
if (isNaN(idx) || !this.#gradient.stops[idx]) return;
|
|
@@ -9973,19 +10012,21 @@ class FigInputGradient extends HTMLElement {
|
|
|
9973
10012
|
if (e.key !== "Delete" && e.key !== "Backspace") return;
|
|
9974
10013
|
if (isTyping) return;
|
|
9975
10014
|
if (this.#gradient.stops.length <= 2) return;
|
|
9976
|
-
const selected = this.#
|
|
9977
|
-
"fig-handle[selected]:not(.fig-input-gradient-ghost)",
|
|
9978
|
-
);
|
|
10015
|
+
const selected = this.#activeStopHandle();
|
|
9979
10016
|
if (!selected) return;
|
|
9980
10017
|
const idx = parseInt(selected.dataset.stopIndex, 10);
|
|
9981
10018
|
if (isNaN(idx) || !this.#gradient.stops[idx]) return;
|
|
9982
10019
|
e.preventDefault();
|
|
9983
|
-
selected.removeAttribute("selected");
|
|
9984
10020
|
this.#gradient.stops.splice(idx, 1);
|
|
9985
10021
|
this.#syncHandles();
|
|
9986
10022
|
this.#syncSwatch();
|
|
9987
10023
|
this.#emitInput();
|
|
9988
10024
|
this.#emitChange();
|
|
10025
|
+
const nextIdx = Math.min(idx, this.#gradient.stops.length - 1);
|
|
10026
|
+
const next = this.#track.querySelectorAll(
|
|
10027
|
+
"fig-handle:not(.fig-input-gradient-ghost)",
|
|
10028
|
+
)[nextIdx];
|
|
10029
|
+
this.#activateStopHandle(next);
|
|
9989
10030
|
};
|
|
9990
10031
|
|
|
9991
10032
|
#onPickerKeyDown = (e) => {
|
|
@@ -10049,11 +10090,6 @@ class FigInputGradient extends HTMLElement {
|
|
|
10049
10090
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
10050
10091
|
}
|
|
10051
10092
|
|
|
10052
|
-
#swatchSizeAttr() {
|
|
10053
|
-
const size = this.getAttribute("size");
|
|
10054
|
-
return size ? ` size="${size}"` : "";
|
|
10055
|
-
}
|
|
10056
|
-
|
|
10057
10093
|
#syncSwatchSize() {
|
|
10058
10094
|
if (!this.#swatch) return;
|
|
10059
10095
|
const size = this.getAttribute("size");
|
|
@@ -10061,16 +10097,32 @@ class FigInputGradient extends HTMLElement {
|
|
|
10061
10097
|
else this.#swatch.removeAttribute("size");
|
|
10062
10098
|
}
|
|
10063
10099
|
|
|
10064
|
-
#
|
|
10100
|
+
#createStopHandles() {
|
|
10065
10101
|
const disabled =
|
|
10066
10102
|
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
10067
|
-
const tipAttr = this.#stopHandleMode === "tip" ? ' tip="color"' : "";
|
|
10068
10103
|
return this.#gradient.stops
|
|
10069
10104
|
.map(
|
|
10070
10105
|
(stop, i) =>
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10106
|
+
figCreateElement(
|
|
10107
|
+
"fig-tooltip",
|
|
10108
|
+
{
|
|
10109
|
+
action: "manual",
|
|
10110
|
+
text: `${Math.round(stop.position)}%`,
|
|
10111
|
+
},
|
|
10112
|
+
figCreateElement("fig-handle", {
|
|
10113
|
+
drag: true,
|
|
10114
|
+
"drag-axes": "x",
|
|
10115
|
+
"drag-surface": ".fig-input-gradient-track",
|
|
10116
|
+
type: "color",
|
|
10117
|
+
tip: this.#stopHandleMode === "tip" ? "color" : null,
|
|
10118
|
+
color: this.#stopColorCSS(stop),
|
|
10119
|
+
value: `${stop.position}% 50%`,
|
|
10120
|
+
"hit-area": "4",
|
|
10121
|
+
"data-stop-index": i,
|
|
10122
|
+
disabled,
|
|
10123
|
+
}),
|
|
10124
|
+
),
|
|
10125
|
+
);
|
|
10074
10126
|
}
|
|
10075
10127
|
|
|
10076
10128
|
#ghostHandle = null;
|
|
@@ -10081,27 +10133,45 @@ class FigInputGradient extends HTMLElement {
|
|
|
10081
10133
|
this.#colorObserver = null;
|
|
10082
10134
|
const disabled = figBooleanAttribute(this, "disabled");
|
|
10083
10135
|
const mode = this.#editMode;
|
|
10136
|
+
const size = this.getAttribute("size");
|
|
10137
|
+
const swatch = figCreateElement("fig-swatch", {
|
|
10138
|
+
background: this.#buildGradientCSS(),
|
|
10139
|
+
size: size || null,
|
|
10140
|
+
disabled,
|
|
10141
|
+
});
|
|
10084
10142
|
|
|
10085
10143
|
if (mode === "picker" && hasFigFillPicker()) {
|
|
10086
10144
|
const gradientValue = JSON.stringify(this.value);
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10145
|
+
const picker = figCreateElement(
|
|
10146
|
+
"fig-fill-picker",
|
|
10147
|
+
{
|
|
10148
|
+
mode: "gradient",
|
|
10149
|
+
value: gradientValue,
|
|
10150
|
+
disabled,
|
|
10151
|
+
},
|
|
10152
|
+
swatch,
|
|
10153
|
+
);
|
|
10154
|
+
this.replaceChildren(picker);
|
|
10155
|
+
this.#swatch = swatch;
|
|
10092
10156
|
this.#track = null;
|
|
10093
10157
|
this.#setupPickerEvents();
|
|
10094
10158
|
this.#syncFocusTarget();
|
|
10095
10159
|
return;
|
|
10096
10160
|
}
|
|
10097
10161
|
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10162
|
+
const editable = mode === "true" || mode === "picker";
|
|
10163
|
+
const track = editable
|
|
10164
|
+
? figCreateElement(
|
|
10165
|
+
"div",
|
|
10166
|
+
{ className: "fig-input-gradient-track" },
|
|
10167
|
+
this.#createStopHandles(),
|
|
10168
|
+
)
|
|
10169
|
+
: null;
|
|
10170
|
+
this.replaceChildren(...(track ? [swatch, track] : [swatch]));
|
|
10171
|
+
this.#swatch = swatch;
|
|
10172
|
+
this.#track = track;
|
|
10103
10173
|
|
|
10104
|
-
if (
|
|
10174
|
+
if (editable) {
|
|
10105
10175
|
this.#setupGhostHandle();
|
|
10106
10176
|
this.#setupEventListeners();
|
|
10107
10177
|
}
|
|
@@ -10279,7 +10349,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
10279
10349
|
"fig-handle:not(.fig-input-gradient-ghost)",
|
|
10280
10350
|
);
|
|
10281
10351
|
const newHandle = handles[newIndex];
|
|
10282
|
-
if (newHandle) newHandle
|
|
10352
|
+
if (newHandle) this.#activateStopHandle(newHandle);
|
|
10283
10353
|
});
|
|
10284
10354
|
};
|
|
10285
10355
|
|
|
@@ -10315,7 +10385,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
10315
10385
|
|
|
10316
10386
|
if (handles.length !== stops.length) {
|
|
10317
10387
|
const ghost = this.#ghostHandle;
|
|
10318
|
-
this.#track.
|
|
10388
|
+
this.#track.replaceChildren(...this.#createStopHandles());
|
|
10319
10389
|
if (ghost) this.#track.appendChild(ghost);
|
|
10320
10390
|
this.#syncHandleMode();
|
|
10321
10391
|
this.#reobserveHandleColors();
|
|
@@ -10446,12 +10516,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
10446
10516
|
);
|
|
10447
10517
|
const newHandle = handles[newIndex];
|
|
10448
10518
|
if (newHandle) {
|
|
10449
|
-
this.#
|
|
10450
|
-
.querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)")
|
|
10451
|
-
.forEach((h) => {
|
|
10452
|
-
if (h !== newHandle) h.deselect();
|
|
10453
|
-
});
|
|
10454
|
-
newHandle.select();
|
|
10519
|
+
this.#activateStopHandle(newHandle);
|
|
10455
10520
|
newHandle.dispatchEvent(
|
|
10456
10521
|
new PointerEvent("pointerdown", {
|
|
10457
10522
|
bubbles: true,
|
|
@@ -11071,22 +11136,54 @@ class FigComboInput extends HTMLElement {
|
|
|
11071
11136
|
const currentValue = this.value;
|
|
11072
11137
|
const dropdownLabel = this.#dropdownLabel();
|
|
11073
11138
|
|
|
11074
|
-
const
|
|
11075
|
-
|
|
11076
|
-
:
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11139
|
+
const input = figCreateElement("fig-input-text", {
|
|
11140
|
+
placeholder,
|
|
11141
|
+
value: currentValue,
|
|
11142
|
+
});
|
|
11143
|
+
const icon = figCreateSvgElement(
|
|
11144
|
+
"svg",
|
|
11145
|
+
{
|
|
11146
|
+
width: "16",
|
|
11147
|
+
height: "16",
|
|
11148
|
+
viewBox: "0 0 16 16",
|
|
11149
|
+
fill: "none",
|
|
11150
|
+
},
|
|
11151
|
+
figCreateSvgElement("path", {
|
|
11152
|
+
d: "M5.87868 7.12132L8 9.24264L10.1213 7.12132",
|
|
11153
|
+
stroke: "currentColor",
|
|
11154
|
+
"stroke-opacity": "0.9",
|
|
11155
|
+
"stroke-linecap": "round",
|
|
11156
|
+
}),
|
|
11157
|
+
);
|
|
11158
|
+
const button = figCreateElement(
|
|
11159
|
+
"fig-button",
|
|
11160
|
+
{
|
|
11161
|
+
type: "select",
|
|
11162
|
+
variant: "input",
|
|
11163
|
+
icon: true,
|
|
11164
|
+
},
|
|
11165
|
+
icon,
|
|
11166
|
+
);
|
|
11167
|
+
if (!this.#usesCustomDropdown) {
|
|
11168
|
+
button.appendChild(
|
|
11169
|
+
figCreateElement(
|
|
11170
|
+
"fig-dropdown",
|
|
11171
|
+
{
|
|
11172
|
+
type: "dropdown",
|
|
11173
|
+
label: dropdownLabel,
|
|
11174
|
+
},
|
|
11175
|
+
options.map((option) =>
|
|
11176
|
+
figCreateElement("option", {}, option.trim()),
|
|
11177
|
+
),
|
|
11178
|
+
),
|
|
11179
|
+
);
|
|
11180
|
+
}
|
|
11181
|
+
this.replaceChildren(
|
|
11182
|
+
figCreateElement("div", { className: "input-combo" }, [input, button]),
|
|
11183
|
+
);
|
|
11087
11184
|
|
|
11088
|
-
this.#input =
|
|
11089
|
-
this.#button =
|
|
11185
|
+
this.#input = input;
|
|
11186
|
+
this.#button = button;
|
|
11090
11187
|
|
|
11091
11188
|
if (this.#usesCustomDropdown && this.#customDropdown && this.#button) {
|
|
11092
11189
|
if (!this.#customDropdown.hasAttribute("type")) {
|
|
@@ -11351,14 +11448,18 @@ class FigSwatch extends HTMLElement {
|
|
|
11351
11448
|
|
|
11352
11449
|
if (this.#type === "color") {
|
|
11353
11450
|
const hex = this.#toHex(bg);
|
|
11354
|
-
|
|
11355
|
-
|
|
11451
|
+
const input = figCreateElement("input", {
|
|
11452
|
+
type: "color",
|
|
11453
|
+
value: hex,
|
|
11454
|
+
});
|
|
11455
|
+
this.replaceChildren(document.createElement("div"), input);
|
|
11456
|
+
this.input = input;
|
|
11356
11457
|
if (!isVar) {
|
|
11357
11458
|
this.input.addEventListener("input", this.#boundHandleInput);
|
|
11358
11459
|
}
|
|
11359
11460
|
this.#syncA11y();
|
|
11360
11461
|
} else {
|
|
11361
|
-
this.
|
|
11462
|
+
this.replaceChildren(document.createElement("div"));
|
|
11362
11463
|
this.input = null;
|
|
11363
11464
|
this.#syncA11y();
|
|
11364
11465
|
}
|
|
@@ -13137,7 +13238,7 @@ class FigInputFile extends HTMLElement {
|
|
|
13137
13238
|
!!this.getAttribute("url") ||
|
|
13138
13239
|
!!this.getAttribute("filename");
|
|
13139
13240
|
|
|
13140
|
-
this.
|
|
13241
|
+
this.replaceChildren();
|
|
13141
13242
|
|
|
13142
13243
|
if (hasFile) {
|
|
13143
13244
|
const tooltipText = accepts
|
|
@@ -13520,10 +13621,10 @@ class FigEasingCurve extends HTMLElement {
|
|
|
13520
13621
|
const y = pad + (1 - (pts[i].value - minVal) / range) * s;
|
|
13521
13622
|
d += (i === 0 ? "M" : "L") + x.toFixed(1) + "," + y.toFixed(1);
|
|
13522
13623
|
}
|
|
13523
|
-
return
|
|
13624
|
+
return FigEasingCurve.#createSvgIcon(d, size);
|
|
13524
13625
|
}
|
|
13525
13626
|
|
|
13526
|
-
static
|
|
13627
|
+
static #curveIconPath(cp1x, cp1y, cp2x, cp2y, size = 24) {
|
|
13527
13628
|
const draw = 12;
|
|
13528
13629
|
const pad = (size - draw) / 2;
|
|
13529
13630
|
const samples = 48;
|
|
@@ -13569,6 +13670,43 @@ class FigEasingCurve extends HTMLElement {
|
|
|
13569
13670
|
const py = toY(points[i].y);
|
|
13570
13671
|
d += `${i === 0 ? "M" : "L"}${px.toFixed(1)},${py.toFixed(1)}`;
|
|
13571
13672
|
}
|
|
13673
|
+
return d;
|
|
13674
|
+
}
|
|
13675
|
+
|
|
13676
|
+
static #createSvgIcon(d, size = 24) {
|
|
13677
|
+
return figCreateSvgElement(
|
|
13678
|
+
"svg",
|
|
13679
|
+
{
|
|
13680
|
+
width: size,
|
|
13681
|
+
height: size,
|
|
13682
|
+
viewBox: `0 0 ${size} ${size}`,
|
|
13683
|
+
fill: "none",
|
|
13684
|
+
},
|
|
13685
|
+
figCreateSvgElement("path", {
|
|
13686
|
+
d,
|
|
13687
|
+
stroke: "currentColor",
|
|
13688
|
+
"stroke-width": "1",
|
|
13689
|
+
"stroke-linecap": "round",
|
|
13690
|
+
fill: "none",
|
|
13691
|
+
}),
|
|
13692
|
+
);
|
|
13693
|
+
}
|
|
13694
|
+
|
|
13695
|
+
static #createCurveIcon(cp1x, cp1y, cp2x, cp2y, size = 24) {
|
|
13696
|
+
return FigEasingCurve.#createSvgIcon(
|
|
13697
|
+
FigEasingCurve.#curveIconPath(cp1x, cp1y, cp2x, cp2y, size),
|
|
13698
|
+
size,
|
|
13699
|
+
);
|
|
13700
|
+
}
|
|
13701
|
+
|
|
13702
|
+
static curveIcon(cp1x, cp1y, cp2x, cp2y, size = 24) {
|
|
13703
|
+
const d = FigEasingCurve.#curveIconPath(
|
|
13704
|
+
cp1x,
|
|
13705
|
+
cp1y,
|
|
13706
|
+
cp2x,
|
|
13707
|
+
cp2y,
|
|
13708
|
+
size,
|
|
13709
|
+
);
|
|
13572
13710
|
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>`;
|
|
13573
13711
|
}
|
|
13574
13712
|
|
|
@@ -13582,7 +13720,7 @@ class FigEasingCurve extends HTMLElement {
|
|
|
13582
13720
|
this.classList.toggle("spring-mode", this.#mode === "spring");
|
|
13583
13721
|
this.classList.toggle("bezier-mode", this.#mode !== "spring");
|
|
13584
13722
|
this.#syncMetricsFromCSS();
|
|
13585
|
-
this.
|
|
13723
|
+
this.replaceChildren(...this.#createContent());
|
|
13586
13724
|
this.#cacheRefs();
|
|
13587
13725
|
if (this.#svg) {
|
|
13588
13726
|
this.#syncHandleSizes();
|
|
@@ -13593,14 +13731,6 @@ class FigEasingCurve extends HTMLElement {
|
|
|
13593
13731
|
this.#setupEvents();
|
|
13594
13732
|
}
|
|
13595
13733
|
|
|
13596
|
-
static #escapeAttribute(value) {
|
|
13597
|
-
return String(value)
|
|
13598
|
-
.replace(/&/g, "&")
|
|
13599
|
-
.replace(/"/g, """)
|
|
13600
|
-
.replace(/</g, "<")
|
|
13601
|
-
.replace(/>/g, ">");
|
|
13602
|
-
}
|
|
13603
|
-
|
|
13604
13734
|
#canUseFigSelect() {
|
|
13605
13735
|
return ["fig-select", "fig-select-options", "fig-select-option"].every(
|
|
13606
13736
|
(tag) => {
|
|
@@ -13613,38 +13743,43 @@ class FigEasingCurve extends HTMLElement {
|
|
|
13613
13743
|
);
|
|
13614
13744
|
}
|
|
13615
13745
|
|
|
13616
|
-
#
|
|
13617
|
-
|
|
13746
|
+
#createDropdown() {
|
|
13747
|
+
const dropdown = figCreateElement("fig-dropdown", {
|
|
13748
|
+
className: "fig-easing-curve-select",
|
|
13749
|
+
label: "Easing preset",
|
|
13750
|
+
value: this.#presetName,
|
|
13751
|
+
full: true,
|
|
13752
|
+
});
|
|
13618
13753
|
let currentGroup = undefined;
|
|
13619
|
-
let
|
|
13754
|
+
let parent = dropdown;
|
|
13620
13755
|
for (const preset of FigEasingCurve.PRESETS) {
|
|
13621
13756
|
if (!this.#isEditEnabled() && !preset.value && !preset.spring) continue;
|
|
13622
13757
|
if (preset.group !== currentGroup) {
|
|
13623
|
-
if (groupOpen) optionsHTML += "</optgroup>";
|
|
13624
|
-
groupOpen = Boolean(preset.group);
|
|
13625
|
-
if (groupOpen) {
|
|
13626
|
-
optionsHTML += `<optgroup label="${FigEasingCurve.#escapeAttribute(preset.group)}">`;
|
|
13627
|
-
}
|
|
13628
13758
|
currentGroup = preset.group;
|
|
13759
|
+
parent = currentGroup
|
|
13760
|
+
? figCreateElement("optgroup", { label: currentGroup })
|
|
13761
|
+
: dropdown;
|
|
13762
|
+
if (parent !== dropdown) dropdown.appendChild(parent);
|
|
13629
13763
|
}
|
|
13630
|
-
|
|
13631
|
-
|
|
13764
|
+
parent.appendChild(
|
|
13765
|
+
figCreateElement("option", { value: preset.name }, preset.name),
|
|
13766
|
+
);
|
|
13632
13767
|
}
|
|
13633
|
-
|
|
13634
|
-
const value = FigEasingCurve.#escapeAttribute(this.#presetName);
|
|
13635
|
-
return `<fig-dropdown class="fig-easing-curve-select" label="Easing preset" value="${value}" full>${optionsHTML}</fig-dropdown>`;
|
|
13768
|
+
return dropdown;
|
|
13636
13769
|
}
|
|
13637
13770
|
|
|
13638
|
-
#
|
|
13639
|
-
if (!this.#canUseFigSelect()) return this.#
|
|
13771
|
+
#createSelect() {
|
|
13772
|
+
if (!this.#canUseFigSelect()) return this.#createDropdown();
|
|
13640
13773
|
|
|
13641
|
-
|
|
13774
|
+
const options = document.createElement("fig-select-options");
|
|
13642
13775
|
let currentGroup = undefined;
|
|
13643
13776
|
for (const p of FigEasingCurve.PRESETS) {
|
|
13644
13777
|
if (!this.#isEditEnabled() && !p.value && !p.spring) continue;
|
|
13645
13778
|
if (p.group !== currentGroup) {
|
|
13646
13779
|
if (p.group) {
|
|
13647
|
-
|
|
13780
|
+
options.appendChild(
|
|
13781
|
+
figCreateElement("fig-separator", { label: p.group }),
|
|
13782
|
+
);
|
|
13648
13783
|
}
|
|
13649
13784
|
currentGroup = p.group;
|
|
13650
13785
|
}
|
|
@@ -13659,42 +13794,152 @@ class FigEasingCurve extends HTMLElement {
|
|
|
13659
13794
|
this.#cp2.x,
|
|
13660
13795
|
this.#cp2.y,
|
|
13661
13796
|
];
|
|
13662
|
-
icon = FigEasingCurve
|
|
13663
|
-
}
|
|
13664
|
-
|
|
13665
|
-
|
|
13797
|
+
icon = FigEasingCurve.#createCurveIcon(...v);
|
|
13798
|
+
}
|
|
13799
|
+
options.appendChild(
|
|
13800
|
+
figCreateElement(
|
|
13801
|
+
"fig-select-option",
|
|
13802
|
+
{
|
|
13803
|
+
value: p.name,
|
|
13804
|
+
label: p.name,
|
|
13805
|
+
},
|
|
13806
|
+
[
|
|
13807
|
+
figCreateElement("span", { slot: "prepend" }, icon),
|
|
13808
|
+
figCreateElement("span", {}, p.name),
|
|
13809
|
+
],
|
|
13810
|
+
),
|
|
13811
|
+
);
|
|
13666
13812
|
}
|
|
13667
|
-
|
|
13668
|
-
|
|
13813
|
+
return figCreateElement(
|
|
13814
|
+
"fig-select",
|
|
13815
|
+
{
|
|
13816
|
+
className: "fig-easing-curve-select",
|
|
13817
|
+
label: "Easing preset",
|
|
13818
|
+
value: this.#presetName,
|
|
13819
|
+
full: true,
|
|
13820
|
+
},
|
|
13821
|
+
options,
|
|
13822
|
+
);
|
|
13823
|
+
}
|
|
13824
|
+
|
|
13825
|
+
#createHandle(className, dataHandle, label) {
|
|
13826
|
+
return figCreateSvgElement(
|
|
13827
|
+
"foreignObject",
|
|
13828
|
+
{
|
|
13829
|
+
className,
|
|
13830
|
+
"data-handle": dataHandle,
|
|
13831
|
+
width: "20",
|
|
13832
|
+
height: "20",
|
|
13833
|
+
},
|
|
13834
|
+
figCreateElement("fig-handle", {
|
|
13835
|
+
size: "small",
|
|
13836
|
+
"aria-label": label,
|
|
13837
|
+
}),
|
|
13838
|
+
);
|
|
13669
13839
|
}
|
|
13670
13840
|
|
|
13671
|
-
#
|
|
13841
|
+
#createContent() {
|
|
13672
13842
|
const size = 200;
|
|
13673
|
-
const select = this.#
|
|
13674
|
-
if (!this.#isEditEnabled()) return select;
|
|
13675
|
-
const valueInput =
|
|
13843
|
+
const select = this.#createSelect();
|
|
13844
|
+
if (!this.#isEditEnabled()) return [select];
|
|
13845
|
+
const valueInput = figCreateElement("fig-input-text", {
|
|
13846
|
+
className: "fig-easing-curve-value-input",
|
|
13847
|
+
value: this.value,
|
|
13848
|
+
full: true,
|
|
13849
|
+
});
|
|
13850
|
+
const svgChildren = [
|
|
13851
|
+
figCreateSvgElement("rect", {
|
|
13852
|
+
className: "fig-easing-curve-bounds",
|
|
13853
|
+
x: "0",
|
|
13854
|
+
y: "0",
|
|
13855
|
+
width: size,
|
|
13856
|
+
height: size,
|
|
13857
|
+
}),
|
|
13858
|
+
];
|
|
13676
13859
|
|
|
13677
13860
|
if (this.#mode === "spring") {
|
|
13678
13861
|
const targetY = 40;
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13862
|
+
svgChildren.push(
|
|
13863
|
+
figCreateSvgElement("line", {
|
|
13864
|
+
className: "fig-easing-curve-target",
|
|
13865
|
+
x1: "0",
|
|
13866
|
+
y1: targetY,
|
|
13867
|
+
x2: size,
|
|
13868
|
+
y2: targetY,
|
|
13869
|
+
}),
|
|
13870
|
+
figCreateSvgElement("path", {
|
|
13871
|
+
className: "fig-easing-curve-path",
|
|
13872
|
+
}),
|
|
13873
|
+
this.#createHandle(
|
|
13874
|
+
"fig-easing-curve-handle",
|
|
13875
|
+
"bounce",
|
|
13876
|
+
"Spring bounce handle",
|
|
13877
|
+
),
|
|
13878
|
+
this.#createHandle(
|
|
13879
|
+
"fig-easing-curve-handle fig-easing-curve-duration-bar",
|
|
13880
|
+
"duration",
|
|
13881
|
+
"Spring duration handle",
|
|
13882
|
+
),
|
|
13883
|
+
);
|
|
13884
|
+
} else {
|
|
13885
|
+
svgChildren.push(
|
|
13886
|
+
figCreateSvgElement("line", {
|
|
13887
|
+
className: "fig-easing-curve-boundary",
|
|
13888
|
+
"data-boundary": "top",
|
|
13889
|
+
x1: "0",
|
|
13890
|
+
y1: "0",
|
|
13891
|
+
x2: size,
|
|
13892
|
+
y2: "0",
|
|
13893
|
+
}),
|
|
13894
|
+
figCreateSvgElement("line", {
|
|
13895
|
+
className: "fig-easing-curve-boundary",
|
|
13896
|
+
"data-boundary": "bottom",
|
|
13897
|
+
x1: "0",
|
|
13898
|
+
y1: size,
|
|
13899
|
+
x2: size,
|
|
13900
|
+
y2: size,
|
|
13901
|
+
}),
|
|
13902
|
+
figCreateSvgElement("path", {
|
|
13903
|
+
className: "fig-easing-curve-path",
|
|
13904
|
+
}),
|
|
13905
|
+
figCreateSvgElement("line", {
|
|
13906
|
+
className: "fig-easing-curve-arm",
|
|
13907
|
+
"data-arm": "1",
|
|
13908
|
+
}),
|
|
13909
|
+
figCreateSvgElement("line", {
|
|
13910
|
+
className: "fig-easing-curve-arm",
|
|
13911
|
+
"data-arm": "2",
|
|
13912
|
+
}),
|
|
13913
|
+
this.#createHandle(
|
|
13914
|
+
"fig-easing-curve-handle",
|
|
13915
|
+
"1",
|
|
13916
|
+
"First easing control point",
|
|
13917
|
+
),
|
|
13918
|
+
this.#createHandle(
|
|
13919
|
+
"fig-easing-curve-handle",
|
|
13920
|
+
"2",
|
|
13921
|
+
"Second easing control point",
|
|
13922
|
+
),
|
|
13923
|
+
);
|
|
13924
|
+
}
|
|
13925
|
+
|
|
13926
|
+
const svg = figCreateSvgElement(
|
|
13927
|
+
"svg",
|
|
13928
|
+
{
|
|
13929
|
+
viewBox: `0 0 ${size} ${size}`,
|
|
13930
|
+
className: "fig-easing-curve-svg",
|
|
13931
|
+
},
|
|
13932
|
+
svgChildren,
|
|
13933
|
+
);
|
|
13934
|
+
return [
|
|
13935
|
+
select,
|
|
13936
|
+
figCreateElement(
|
|
13937
|
+
"div",
|
|
13938
|
+
{ className: "fig-easing-curve-svg-container" },
|
|
13939
|
+
svg,
|
|
13940
|
+
),
|
|
13941
|
+
valueInput,
|
|
13942
|
+
];
|
|
13698
13943
|
}
|
|
13699
13944
|
|
|
13700
13945
|
#readCssNumber(name, fallback) {
|
|
@@ -14029,7 +14274,7 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14029
14274
|
for (const option of this.#select.querySelectorAll("fig-select-option")) {
|
|
14030
14275
|
if (option.value === optionValue) {
|
|
14031
14276
|
const prepend = option.querySelector(':scope > [slot="prepend"]');
|
|
14032
|
-
if (prepend) prepend.
|
|
14277
|
+
if (prepend) prepend.replaceChildren(icon.cloneNode(true));
|
|
14033
14278
|
}
|
|
14034
14279
|
}
|
|
14035
14280
|
}
|
|
@@ -14037,7 +14282,7 @@ class FigEasingCurve extends HTMLElement {
|
|
|
14037
14282
|
#refreshCustomPresetIcons() {
|
|
14038
14283
|
if (!this.#select) return;
|
|
14039
14284
|
if (!this.#isEditEnabled()) return;
|
|
14040
|
-
const bezierIcon = FigEasingCurve
|
|
14285
|
+
const bezierIcon = FigEasingCurve.#createCurveIcon(
|
|
14041
14286
|
this.#cp1.x,
|
|
14042
14287
|
this.#cp1.y,
|
|
14043
14288
|
this.#cp2.x,
|
|
@@ -14627,34 +14872,44 @@ class Fig3DRotate extends HTMLElement {
|
|
|
14627
14872
|
rotateY: this.#ry,
|
|
14628
14873
|
rotateZ: this.#rz,
|
|
14629
14874
|
};
|
|
14630
|
-
const
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
|
|
14875
|
+
const cube = figCreateElement(
|
|
14876
|
+
"div",
|
|
14877
|
+
{ className: "fig-3d-rotate-cube" },
|
|
14878
|
+
["front", "back", "right", "left", "top", "bottom"].map((face) =>
|
|
14879
|
+
figCreateElement("div", {
|
|
14880
|
+
className: `fig-3d-rotate-face ${face}`,
|
|
14881
|
+
}),
|
|
14882
|
+
),
|
|
14883
|
+
);
|
|
14884
|
+
const container = figCreateElement(
|
|
14885
|
+
"div",
|
|
14886
|
+
{
|
|
14887
|
+
className: "fig-3d-rotate-container",
|
|
14888
|
+
tabindex: "0",
|
|
14889
|
+
},
|
|
14890
|
+
figCreateElement(
|
|
14891
|
+
"div",
|
|
14892
|
+
{ className: "fig-3d-rotate-scene" },
|
|
14893
|
+
cube,
|
|
14894
|
+
),
|
|
14895
|
+
);
|
|
14896
|
+
const fields = this.#fields.map((axis) =>
|
|
14897
|
+
figCreateElement(
|
|
14898
|
+
"fig-input-number",
|
|
14899
|
+
{
|
|
14900
|
+
name: axis,
|
|
14901
|
+
step: "1",
|
|
14902
|
+
precision: "1",
|
|
14903
|
+
value: axisValues[axis],
|
|
14904
|
+
units: "°",
|
|
14905
|
+
},
|
|
14906
|
+
figCreateElement("span", { slot: "prepend" }, axisLabels[axis]),
|
|
14907
|
+
),
|
|
14908
|
+
);
|
|
14909
|
+
|
|
14910
|
+
this.replaceChildren(container, ...fields);
|
|
14911
|
+
this.#container = container;
|
|
14912
|
+
this.#cube = cube;
|
|
14658
14913
|
this.#wireFieldInputs();
|
|
14659
14914
|
this.#updateCube();
|
|
14660
14915
|
this.#setupEvents();
|
|
@@ -14957,31 +15212,65 @@ class FigOriginGrid extends HTMLElement {
|
|
|
14957
15212
|
const cells = Array.from({ length: 9 }, (_, index) => {
|
|
14958
15213
|
const col = index % 3;
|
|
14959
15214
|
const row = Math.floor(index / 3);
|
|
14960
|
-
return
|
|
14961
|
-
|
|
14962
|
-
|
|
14963
|
-
|
|
15215
|
+
return figCreateElement(
|
|
15216
|
+
"span",
|
|
15217
|
+
{
|
|
15218
|
+
className: "origin-grid-cell",
|
|
15219
|
+
"data-col": col,
|
|
15220
|
+
"data-row": row,
|
|
15221
|
+
},
|
|
15222
|
+
figCreateElement("span", { className: "origin-grid-dot" }),
|
|
15223
|
+
);
|
|
15224
|
+
});
|
|
14964
15225
|
|
|
14965
15226
|
const xValue = this.#x.toFixed(this.#precision);
|
|
14966
15227
|
const yValue = this.#y.toFixed(this.#precision);
|
|
14967
|
-
const
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
14974
|
-
|
|
14975
|
-
|
|
14976
|
-
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
|
|
14980
|
-
|
|
15228
|
+
const handle = document.createElement("fig-handle");
|
|
15229
|
+
const grid = figCreateElement(
|
|
15230
|
+
"div",
|
|
15231
|
+
{
|
|
15232
|
+
className: "origin-grid",
|
|
15233
|
+
"aria-label": "Transform origin grid",
|
|
15234
|
+
},
|
|
15235
|
+
[
|
|
15236
|
+
figCreateElement(
|
|
15237
|
+
"div",
|
|
15238
|
+
{ className: "origin-grid-cells" },
|
|
15239
|
+
cells,
|
|
15240
|
+
),
|
|
15241
|
+
handle,
|
|
15242
|
+
],
|
|
15243
|
+
);
|
|
15244
|
+
const surface = figCreateElement(
|
|
15245
|
+
"div",
|
|
15246
|
+
{ className: "fig-origin-grid-surface" },
|
|
15247
|
+
grid,
|
|
15248
|
+
);
|
|
15249
|
+
const rendered = [surface];
|
|
15250
|
+
if (this.#fieldsEnabled) {
|
|
15251
|
+
const createValueInput = (name, value) =>
|
|
15252
|
+
figCreateElement(
|
|
15253
|
+
"fig-input-number",
|
|
15254
|
+
{
|
|
15255
|
+
name,
|
|
15256
|
+
value,
|
|
15257
|
+
step: "1",
|
|
15258
|
+
units: "%",
|
|
15259
|
+
},
|
|
15260
|
+
figCreateElement("span", { slot: "prepend" }, name.toUpperCase()),
|
|
15261
|
+
);
|
|
15262
|
+
rendered.push(
|
|
15263
|
+
figCreateElement("div", { className: "origin-values" }, [
|
|
15264
|
+
createValueInput("x", xValue),
|
|
15265
|
+
createValueInput("y", yValue),
|
|
15266
|
+
]),
|
|
15267
|
+
);
|
|
15268
|
+
}
|
|
14981
15269
|
|
|
14982
|
-
this
|
|
14983
|
-
this.#
|
|
14984
|
-
this.#
|
|
15270
|
+
this.replaceChildren(...rendered);
|
|
15271
|
+
this.#grid = grid;
|
|
15272
|
+
this.#cells = cells;
|
|
15273
|
+
this.#handle = handle;
|
|
14985
15274
|
this.#xInput = this.querySelector('fig-input-number[name="x"]');
|
|
14986
15275
|
this.#yInput = this.querySelector('fig-input-number[name="y"]');
|
|
14987
15276
|
this.#syncHandlePosition();
|
|
@@ -15438,7 +15727,100 @@ class FigInputJoystick extends HTMLElement {
|
|
|
15438
15727
|
}
|
|
15439
15728
|
|
|
15440
15729
|
#render() {
|
|
15441
|
-
|
|
15730
|
+
const axisLabels = this.#getAxisLabels();
|
|
15731
|
+
const planeContainer = figCreateElement("div", {
|
|
15732
|
+
className: "fig-input-joystick-plane-container",
|
|
15733
|
+
});
|
|
15734
|
+
const createAxisLabel = (position, text, noRotate = false) =>
|
|
15735
|
+
text
|
|
15736
|
+
? figCreateElement(
|
|
15737
|
+
"label",
|
|
15738
|
+
{
|
|
15739
|
+
className: [
|
|
15740
|
+
"fig-joystick-axis-label",
|
|
15741
|
+
position,
|
|
15742
|
+
noRotate ? "no-rotate" : "",
|
|
15743
|
+
]
|
|
15744
|
+
.filter(Boolean)
|
|
15745
|
+
.join(" "),
|
|
15746
|
+
"aria-hidden": "true",
|
|
15747
|
+
},
|
|
15748
|
+
text,
|
|
15749
|
+
)
|
|
15750
|
+
: null;
|
|
15751
|
+
planeContainer.append(
|
|
15752
|
+
...[
|
|
15753
|
+
createAxisLabel(
|
|
15754
|
+
"left",
|
|
15755
|
+
axisLabels.left,
|
|
15756
|
+
axisLabels.leftNoRotate,
|
|
15757
|
+
),
|
|
15758
|
+
createAxisLabel("right", axisLabels.right),
|
|
15759
|
+
createAxisLabel("top", axisLabels.top),
|
|
15760
|
+
createAxisLabel("bottom", axisLabels.bottom),
|
|
15761
|
+
].filter(Boolean),
|
|
15762
|
+
);
|
|
15763
|
+
|
|
15764
|
+
const plane = figCreateElement(
|
|
15765
|
+
"div",
|
|
15766
|
+
{ className: "fig-input-joystick-plane" },
|
|
15767
|
+
[
|
|
15768
|
+
figCreateElement("div", {
|
|
15769
|
+
className: "fig-input-joystick-guides",
|
|
15770
|
+
}),
|
|
15771
|
+
figCreateElement("fig-handle", {
|
|
15772
|
+
drag: true,
|
|
15773
|
+
"drag-surface": ".fig-input-joystick-plane",
|
|
15774
|
+
"drag-axes": "x,y",
|
|
15775
|
+
"drag-snapping": "modifier",
|
|
15776
|
+
}),
|
|
15777
|
+
],
|
|
15778
|
+
);
|
|
15779
|
+
const reset = figCreateElement(
|
|
15780
|
+
"fig-tooltip",
|
|
15781
|
+
{ text: "Reset" },
|
|
15782
|
+
figCreateElement(
|
|
15783
|
+
"fig-button",
|
|
15784
|
+
{
|
|
15785
|
+
variant: "ghost",
|
|
15786
|
+
icon: "true",
|
|
15787
|
+
className: "fig-joystick-reset",
|
|
15788
|
+
"aria-label": "Reset to default",
|
|
15789
|
+
},
|
|
15790
|
+
createFigIcon("reset", { size: "small" }),
|
|
15791
|
+
),
|
|
15792
|
+
);
|
|
15793
|
+
planeContainer.append(plane, reset);
|
|
15794
|
+
|
|
15795
|
+
const children = [planeContainer];
|
|
15796
|
+
if (this.#fieldsEnabled) {
|
|
15797
|
+
const createValueInput = (name, value) =>
|
|
15798
|
+
figCreateElement(
|
|
15799
|
+
"fig-input-number",
|
|
15800
|
+
{
|
|
15801
|
+
name,
|
|
15802
|
+
step: "1",
|
|
15803
|
+
value,
|
|
15804
|
+
min: "0",
|
|
15805
|
+
max: "100",
|
|
15806
|
+
units: "%",
|
|
15807
|
+
},
|
|
15808
|
+
figCreateElement("span", { slot: "prepend" }, name.toUpperCase()),
|
|
15809
|
+
);
|
|
15810
|
+
children.push(
|
|
15811
|
+
figCreateElement("div", { className: "joystick-values" }, [
|
|
15812
|
+
createValueInput(
|
|
15813
|
+
"x",
|
|
15814
|
+
(this.position.x * 100).toFixed(this.precision),
|
|
15815
|
+
),
|
|
15816
|
+
createValueInput(
|
|
15817
|
+
"y",
|
|
15818
|
+
(this.position.y * 100).toFixed(this.precision),
|
|
15819
|
+
),
|
|
15820
|
+
]),
|
|
15821
|
+
);
|
|
15822
|
+
}
|
|
15823
|
+
this.replaceChildren(...children);
|
|
15442
15824
|
}
|
|
15443
15825
|
|
|
15444
15826
|
#getAxisLabels() {
|
|
@@ -15467,63 +15849,6 @@ class FigInputJoystick extends HTMLElement {
|
|
|
15467
15849
|
return { left: "", right: "", top: "", bottom: "", leftNoRotate: false };
|
|
15468
15850
|
}
|
|
15469
15851
|
|
|
15470
|
-
#getInnerHTML() {
|
|
15471
|
-
const axisLabels = this.#getAxisLabels();
|
|
15472
|
-
const labelsMarkup = [
|
|
15473
|
-
axisLabels.left
|
|
15474
|
-
? `<label class="fig-joystick-axis-label left${axisLabels.leftNoRotate ? " no-rotate" : ""}" aria-hidden="true">${axisLabels.left}</label>`
|
|
15475
|
-
: "",
|
|
15476
|
-
axisLabels.right
|
|
15477
|
-
? `<label class="fig-joystick-axis-label right" aria-hidden="true">${axisLabels.right}</label>`
|
|
15478
|
-
: "",
|
|
15479
|
-
axisLabels.top
|
|
15480
|
-
? `<label class="fig-joystick-axis-label top" aria-hidden="true">${axisLabels.top}</label>`
|
|
15481
|
-
: "",
|
|
15482
|
-
axisLabels.bottom
|
|
15483
|
-
? `<label class="fig-joystick-axis-label bottom" aria-hidden="true">${axisLabels.bottom}</label>`
|
|
15484
|
-
: "",
|
|
15485
|
-
].join("");
|
|
15486
|
-
|
|
15487
|
-
return `
|
|
15488
|
-
<div class="fig-input-joystick-plane-container">
|
|
15489
|
-
${labelsMarkup}
|
|
15490
|
-
<div class="fig-input-joystick-plane">
|
|
15491
|
-
<div class="fig-input-joystick-guides"></div>
|
|
15492
|
-
<fig-handle drag drag-surface=".fig-input-joystick-plane" drag-axes="x,y" drag-snapping="modifier"></fig-handle>
|
|
15493
|
-
</div>
|
|
15494
|
-
<fig-tooltip text="Reset">
|
|
15495
|
-
<fig-button variant="ghost" icon="true" class="fig-joystick-reset" aria-label="Reset to default">
|
|
15496
|
-
<fig-icon name="reset" size="small"></fig-icon>
|
|
15497
|
-
</fig-button>
|
|
15498
|
-
</fig-tooltip>
|
|
15499
|
-
</div>
|
|
15500
|
-
${
|
|
15501
|
-
this.#fieldsEnabled
|
|
15502
|
-
? `<div class="joystick-values">
|
|
15503
|
-
<fig-input-number
|
|
15504
|
-
name="x"
|
|
15505
|
-
step="1"
|
|
15506
|
-
value="${(this.position.x * 100).toFixed(this.precision)}"
|
|
15507
|
-
min="0"
|
|
15508
|
-
max="100"
|
|
15509
|
-
units="%">
|
|
15510
|
-
<span slot="prepend">X</span>
|
|
15511
|
-
</fig-input-number>
|
|
15512
|
-
<fig-input-number
|
|
15513
|
-
name="y"
|
|
15514
|
-
step="1"
|
|
15515
|
-
min="0"
|
|
15516
|
-
max="100"
|
|
15517
|
-
value="${(this.position.y * 100).toFixed(this.precision)}"
|
|
15518
|
-
units="%">
|
|
15519
|
-
<span slot="prepend">Y</span>
|
|
15520
|
-
</fig-input-number>
|
|
15521
|
-
</div>`
|
|
15522
|
-
: ""
|
|
15523
|
-
}
|
|
15524
|
-
`;
|
|
15525
|
-
}
|
|
15526
|
-
|
|
15527
15852
|
#setupListeners() {
|
|
15528
15853
|
this.plane = this.querySelector(".fig-input-joystick-plane");
|
|
15529
15854
|
this.cursor = this.querySelector("fig-handle");
|
|
@@ -16053,314 +16378,6 @@ class FigPreview extends HTMLElement {
|
|
|
16053
16378
|
}
|
|
16054
16379
|
figDefineElement("fig-preview", FigPreview);
|
|
16055
16380
|
|
|
16056
|
-
/**
|
|
16057
|
-
* Compact swatch previewing gradient color-space interpolation.
|
|
16058
|
-
* Polar: CSS conic-gradient masked (SVG data-URL, round linecaps) to an arc.
|
|
16059
|
-
* Non-polar: CSS linear-gradient masked to a horizontal round-capped stroke.
|
|
16060
|
-
* Accepts the same `value` shape as fig-input-gradient / fig-fill-picker.
|
|
16061
|
-
*
|
|
16062
|
-
* @element fig-interpolation-swatch
|
|
16063
|
-
* @attr {string} value - JSON `{ type: "gradient", gradient: { … } }` (or a bare gradient object)
|
|
16064
|
-
* @attr {string} size - `small` (default, 24px) or `large` (32px)
|
|
16065
|
-
*/
|
|
16066
|
-
class FigInterpolationSwatch extends HTMLElement {
|
|
16067
|
-
static #HUE_SPACES = new Set(["oklch", "hsl"]);
|
|
16068
|
-
static #CX = 10;
|
|
16069
|
-
static #CY = 10;
|
|
16070
|
-
static #R = 8;
|
|
16071
|
-
static #STROKE = 3;
|
|
16072
|
-
// Polar endpoints ≈ 10 o'clock → 2 o'clock (SVG deg: 0 = east, CW).
|
|
16073
|
-
// CSS conic `from` is 0 = north; convert with +90.
|
|
16074
|
-
static #START_DEG = 210;
|
|
16075
|
-
static #DEFAULT_GRADIENT = {
|
|
16076
|
-
type: "linear",
|
|
16077
|
-
angle: 135,
|
|
16078
|
-
interpolationSpace: "srgb",
|
|
16079
|
-
hueInterpolation: "shorter",
|
|
16080
|
-
stops: [
|
|
16081
|
-
{ color: "#FF0000", position: 0, opacity: 100 },
|
|
16082
|
-
{ color: "#4F9EFF", position: 100, opacity: 100 },
|
|
16083
|
-
],
|
|
16084
|
-
};
|
|
16085
|
-
|
|
16086
|
-
#rendered = false;
|
|
16087
|
-
#svgEl = null;
|
|
16088
|
-
#fillEl = null;
|
|
16089
|
-
#gradient = { ...FigInterpolationSwatch.#DEFAULT_GRADIENT };
|
|
16090
|
-
|
|
16091
|
-
static get observedAttributes() {
|
|
16092
|
-
return ["value"];
|
|
16093
|
-
}
|
|
16094
|
-
|
|
16095
|
-
get value() {
|
|
16096
|
-
return {
|
|
16097
|
-
type: "gradient",
|
|
16098
|
-
gradient: { ...this.#gradient },
|
|
16099
|
-
};
|
|
16100
|
-
}
|
|
16101
|
-
|
|
16102
|
-
set value(val) {
|
|
16103
|
-
if (val == null || val === "") {
|
|
16104
|
-
this.removeAttribute("value");
|
|
16105
|
-
return;
|
|
16106
|
-
}
|
|
16107
|
-
if (typeof val === "string") {
|
|
16108
|
-
this.setAttribute("value", val);
|
|
16109
|
-
return;
|
|
16110
|
-
}
|
|
16111
|
-
this.setAttribute("value", JSON.stringify(val));
|
|
16112
|
-
}
|
|
16113
|
-
|
|
16114
|
-
connectedCallback() {
|
|
16115
|
-
this.#ensureA11y();
|
|
16116
|
-
this.#parseValue();
|
|
16117
|
-
this.#render();
|
|
16118
|
-
this.#updatePreview();
|
|
16119
|
-
}
|
|
16120
|
-
|
|
16121
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
16122
|
-
if (oldValue === newValue) return;
|
|
16123
|
-
if (name !== "value") return;
|
|
16124
|
-
this.#parseValue();
|
|
16125
|
-
if (this.#rendered) this.#updatePreview();
|
|
16126
|
-
}
|
|
16127
|
-
|
|
16128
|
-
#ensureA11y() {
|
|
16129
|
-
const named =
|
|
16130
|
-
this.hasAttribute("aria-label") || this.hasAttribute("aria-labelledby");
|
|
16131
|
-
if (!named && !this.hasAttribute("aria-hidden")) {
|
|
16132
|
-
this.setAttribute("aria-hidden", "true");
|
|
16133
|
-
}
|
|
16134
|
-
}
|
|
16135
|
-
|
|
16136
|
-
#parseValue() {
|
|
16137
|
-
const valueAttr = this.getAttribute("value");
|
|
16138
|
-
if (!valueAttr) {
|
|
16139
|
-
this.#gradient = {
|
|
16140
|
-
...FigInterpolationSwatch.#DEFAULT_GRADIENT,
|
|
16141
|
-
stops: FigInterpolationSwatch.#DEFAULT_GRADIENT.stops.map((s) => ({
|
|
16142
|
-
...s,
|
|
16143
|
-
})),
|
|
16144
|
-
};
|
|
16145
|
-
return;
|
|
16146
|
-
}
|
|
16147
|
-
try {
|
|
16148
|
-
const parsed = JSON.parse(valueAttr);
|
|
16149
|
-
const gradient = parsed?.type === "gradient" && parsed.gradient
|
|
16150
|
-
? parsed.gradient
|
|
16151
|
-
: parsed?.gradient
|
|
16152
|
-
? parsed.gradient
|
|
16153
|
-
: parsed;
|
|
16154
|
-
if (!gradient || typeof gradient !== "object") return;
|
|
16155
|
-
this.#gradient = this.#normalizeGradient({
|
|
16156
|
-
...FigInterpolationSwatch.#DEFAULT_GRADIENT,
|
|
16157
|
-
...gradient,
|
|
16158
|
-
});
|
|
16159
|
-
} catch {
|
|
16160
|
-
// Keep current/default gradient on invalid JSON.
|
|
16161
|
-
}
|
|
16162
|
-
}
|
|
16163
|
-
|
|
16164
|
-
#normalizeGradient(gradient) {
|
|
16165
|
-
const next = { ...(gradient ?? {}) };
|
|
16166
|
-
const interpolationSpace = String(
|
|
16167
|
-
next.interpolationSpace ?? "srgb",
|
|
16168
|
-
).toLowerCase();
|
|
16169
|
-
const hueInterpolation = String(
|
|
16170
|
-
next.hueInterpolation ?? "shorter",
|
|
16171
|
-
).toLowerCase();
|
|
16172
|
-
const stops = Array.isArray(next.stops)
|
|
16173
|
-
? next.stops.map((stop) => ({
|
|
16174
|
-
color: String(stop?.color || "#D9D9D9").replace(
|
|
16175
|
-
/^(#(?:[0-9a-f]{6})).*/i,
|
|
16176
|
-
"$1",
|
|
16177
|
-
),
|
|
16178
|
-
position: stop?.position ?? 0,
|
|
16179
|
-
opacity: stop?.opacity ?? 100,
|
|
16180
|
-
}))
|
|
16181
|
-
: FigInterpolationSwatch.#DEFAULT_GRADIENT.stops.map((s) => ({ ...s }));
|
|
16182
|
-
if (stops.length < 2) {
|
|
16183
|
-
return {
|
|
16184
|
-
...FigInterpolationSwatch.#DEFAULT_GRADIENT,
|
|
16185
|
-
stops: FigInterpolationSwatch.#DEFAULT_GRADIENT.stops.map((s) => ({
|
|
16186
|
-
...s,
|
|
16187
|
-
})),
|
|
16188
|
-
};
|
|
16189
|
-
}
|
|
16190
|
-
return {
|
|
16191
|
-
type: ["linear", "radial", "angular"].includes(next.type)
|
|
16192
|
-
? next.type
|
|
16193
|
-
: "linear",
|
|
16194
|
-
angle: Number.isFinite(Number(next.angle)) ? Number(next.angle) : 135,
|
|
16195
|
-
interpolationSpace,
|
|
16196
|
-
hueInterpolation,
|
|
16197
|
-
stops,
|
|
16198
|
-
};
|
|
16199
|
-
}
|
|
16200
|
-
|
|
16201
|
-
#isPolar() {
|
|
16202
|
-
return FigInterpolationSwatch.#HUE_SPACES.has(
|
|
16203
|
-
this.#gradient.interpolationSpace || "srgb",
|
|
16204
|
-
);
|
|
16205
|
-
}
|
|
16206
|
-
|
|
16207
|
-
#hueForColor(color) {
|
|
16208
|
-
const { r, g, b } = figHexToRGB(color);
|
|
16209
|
-
if (this.#gradient.interpolationSpace === "hsl") {
|
|
16210
|
-
return figRgbToHsl(r, g, b).h;
|
|
16211
|
-
}
|
|
16212
|
-
const lab = figRGBToOklab(r, g, b);
|
|
16213
|
-
const hue = figOklabToOklch(lab.l, lab.a, lab.b).h;
|
|
16214
|
-
return ((hue % 360) + 360) % 360;
|
|
16215
|
-
}
|
|
16216
|
-
|
|
16217
|
-
#polarArcGeometry() {
|
|
16218
|
-
const stops = this.#sortedStops();
|
|
16219
|
-
const startHue = this.#hueForColor(stops[0]?.color || "#FF0000");
|
|
16220
|
-
const endHue = this.#hueForColor(
|
|
16221
|
-
stops[stops.length - 1]?.color || "#4F9EFF",
|
|
16222
|
-
);
|
|
16223
|
-
const startDeg = FigInterpolationSwatch.#START_DEG - startHue;
|
|
16224
|
-
const endDeg = FigInterpolationSwatch.#START_DEG - endHue;
|
|
16225
|
-
const clockwiseSweep = ((endDeg - startDeg) % 360 + 360) % 360;
|
|
16226
|
-
const counterclockwiseSweep =
|
|
16227
|
-
clockwiseSweep === 0 ? 0 : clockwiseSweep - 360;
|
|
16228
|
-
const method = this.#gradient.hueInterpolation || "shorter";
|
|
16229
|
-
|
|
16230
|
-
let sweepDeg;
|
|
16231
|
-
if (method === "increasing") {
|
|
16232
|
-
sweepDeg = counterclockwiseSweep;
|
|
16233
|
-
} else if (method === "decreasing") {
|
|
16234
|
-
sweepDeg = clockwiseSweep;
|
|
16235
|
-
} else if (method === "longer") {
|
|
16236
|
-
sweepDeg =
|
|
16237
|
-
clockwiseSweep < 180 ? counterclockwiseSweep : clockwiseSweep;
|
|
16238
|
-
} else {
|
|
16239
|
-
sweepDeg =
|
|
16240
|
-
clockwiseSweep <= 180 ? clockwiseSweep : counterclockwiseSweep;
|
|
16241
|
-
}
|
|
16242
|
-
|
|
16243
|
-
// A round cap extends beyond the path endpoint. Inset the centerline so
|
|
16244
|
-
// the visible cap edges, rather than their centers, land on the hues.
|
|
16245
|
-
const direction = Math.sign(sweepDeg);
|
|
16246
|
-
const capAngle =
|
|
16247
|
-
(Math.asin(FigInterpolationSwatch.#STROKE / 2 / FigInterpolationSwatch.#R) *
|
|
16248
|
-
180) /
|
|
16249
|
-
Math.PI;
|
|
16250
|
-
const inset = Math.min(
|
|
16251
|
-
capAngle,
|
|
16252
|
-
Math.max(0, (Math.abs(sweepDeg) - 0.01) / 2),
|
|
16253
|
-
);
|
|
16254
|
-
return {
|
|
16255
|
-
startDeg: startDeg + direction * inset,
|
|
16256
|
-
sweepDeg: sweepDeg - direction * inset * 2,
|
|
16257
|
-
};
|
|
16258
|
-
}
|
|
16259
|
-
|
|
16260
|
-
#pointOnCircle(deg) {
|
|
16261
|
-
const rad = (deg * Math.PI) / 180;
|
|
16262
|
-
return {
|
|
16263
|
-
x: FigInterpolationSwatch.#CX + FigInterpolationSwatch.#R * Math.cos(rad),
|
|
16264
|
-
y: FigInterpolationSwatch.#CY + FigInterpolationSwatch.#R * Math.sin(rad),
|
|
16265
|
-
};
|
|
16266
|
-
}
|
|
16267
|
-
|
|
16268
|
-
#arcMaskPath(startDeg, sweepDeg) {
|
|
16269
|
-
const endDeg = startDeg + sweepDeg;
|
|
16270
|
-
const start = this.#pointOnCircle(startDeg);
|
|
16271
|
-
const end = this.#pointOnCircle(endDeg);
|
|
16272
|
-
const largeArc = Math.abs(sweepDeg) > 180 ? 1 : 0;
|
|
16273
|
-
const sweepFlag = sweepDeg >= 0 ? 1 : 0;
|
|
16274
|
-
return `M ${start.x} ${start.y} A ${FigInterpolationSwatch.#R} ${FigInterpolationSwatch.#R} 0 ${largeArc} ${sweepFlag} ${end.x} ${end.y}`;
|
|
16275
|
-
}
|
|
16276
|
-
|
|
16277
|
-
#lineMaskPath() {
|
|
16278
|
-
const start = this.#pointOnCircle(180);
|
|
16279
|
-
const end = this.#pointOnCircle(0);
|
|
16280
|
-
return `M ${start.x} ${start.y} L ${end.x} ${end.y}`;
|
|
16281
|
-
}
|
|
16282
|
-
|
|
16283
|
-
#maskImageForPath(d) {
|
|
16284
|
-
const stroke = FigInterpolationSwatch.#STROKE;
|
|
16285
|
-
const svg =
|
|
16286
|
-
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">` +
|
|
16287
|
-
`<path d="${d}" fill="none" stroke="white" stroke-width="${stroke}" ` +
|
|
16288
|
-
`stroke-linecap="round" stroke-linejoin="round"/>` +
|
|
16289
|
-
`</svg>`;
|
|
16290
|
-
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
|
|
16291
|
-
}
|
|
16292
|
-
|
|
16293
|
-
#sortedStops() {
|
|
16294
|
-
return [...this.#gradient.stops].sort(
|
|
16295
|
-
(a, b) => (a.position ?? 0) - (b.position ?? 0),
|
|
16296
|
-
);
|
|
16297
|
-
}
|
|
16298
|
-
|
|
16299
|
-
#cssInterpolationClause() {
|
|
16300
|
-
const space = this.#gradient.interpolationSpace || "srgb";
|
|
16301
|
-
if (space === "srgb") return "";
|
|
16302
|
-
if (FigInterpolationSwatch.#HUE_SPACES.has(space)) {
|
|
16303
|
-
return ` in ${space} ${this.#gradient.hueInterpolation || "shorter"} hue`;
|
|
16304
|
-
}
|
|
16305
|
-
return ` in ${space}`;
|
|
16306
|
-
}
|
|
16307
|
-
|
|
16308
|
-
#previewBackground() {
|
|
16309
|
-
const stops = this.#sortedStops();
|
|
16310
|
-
const clause = this.#cssInterpolationClause();
|
|
16311
|
-
if (this.#isPolar()) {
|
|
16312
|
-
// Fixed hue wheel. The mask maps gradient endpoint hues onto this wheel.
|
|
16313
|
-
const cssFrom = (FigInterpolationSwatch.#START_DEG + 90) % 360;
|
|
16314
|
-
const space = this.#gradient.interpolationSpace;
|
|
16315
|
-
const wheelColor = (hue) =>
|
|
16316
|
-
space === "oklch"
|
|
16317
|
-
? `oklch(65% 0.25 ${hue})`
|
|
16318
|
-
: `hsl(${hue} 100% 50%)`;
|
|
16319
|
-
const wheelStops = [0, 300, 240, 180, 120, 60, 0]
|
|
16320
|
-
.map(wheelColor)
|
|
16321
|
-
.join(", ");
|
|
16322
|
-
return `conic-gradient(from ${cssFrom}deg in ${space} decreasing hue, ${wheelStops})`;
|
|
16323
|
-
}
|
|
16324
|
-
const stopList = stops
|
|
16325
|
-
.map((s) => `${s.color} ${s.position ?? 0}%`)
|
|
16326
|
-
.join(", ");
|
|
16327
|
-
return `linear-gradient(90deg${clause}, ${stopList})`;
|
|
16328
|
-
}
|
|
16329
|
-
|
|
16330
|
-
#render() {
|
|
16331
|
-
if (this.#rendered) return;
|
|
16332
|
-
const stroke = FigInterpolationSwatch.#STROKE;
|
|
16333
|
-
this.innerHTML = `
|
|
16334
|
-
<svg class="fig-interpolation-swatch-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
16335
|
-
<circle class="fig-interpolation-swatch-rim" cx="10" cy="10" r="8" fill="none" stroke="currentColor" stroke-width="${stroke}"/>
|
|
16336
|
-
</svg>
|
|
16337
|
-
<div class="fig-interpolation-swatch-fill" aria-hidden="true"></div>
|
|
16338
|
-
`;
|
|
16339
|
-
this.#svgEl = this.querySelector(".fig-interpolation-swatch-svg");
|
|
16340
|
-
this.#fillEl = this.querySelector(".fig-interpolation-swatch-fill");
|
|
16341
|
-
this.#rendered = true;
|
|
16342
|
-
}
|
|
16343
|
-
|
|
16344
|
-
#updatePreview() {
|
|
16345
|
-
if (!this.#fillEl) return;
|
|
16346
|
-
|
|
16347
|
-
const polar = this.#isPolar();
|
|
16348
|
-
if (this.#svgEl) this.#svgEl.style.display = polar ? "" : "none";
|
|
16349
|
-
|
|
16350
|
-
const d = polar
|
|
16351
|
-
? (() => {
|
|
16352
|
-
const { startDeg, sweepDeg } = this.#polarArcGeometry();
|
|
16353
|
-
return this.#arcMaskPath(startDeg, sweepDeg);
|
|
16354
|
-
})()
|
|
16355
|
-
: this.#lineMaskPath();
|
|
16356
|
-
const mask = this.#maskImageForPath(d);
|
|
16357
|
-
this.#fillEl.style.setProperty("-webkit-mask-image", mask);
|
|
16358
|
-
this.#fillEl.style.maskImage = mask;
|
|
16359
|
-
this.#fillEl.style.background = this.#previewBackground();
|
|
16360
|
-
}
|
|
16361
|
-
}
|
|
16362
|
-
figDefineElement("fig-interpolation-swatch", FigInterpolationSwatch);
|
|
16363
|
-
|
|
16364
16381
|
/** @type {Record<string, string | { medium: string, small: string }>} */
|
|
16365
16382
|
const FIG_ICON_TOKENS = {
|
|
16366
16383
|
chevron: { medium: "--icon-24-chevron", small: "--icon-16-chevron" },
|
|
@@ -16550,11 +16567,22 @@ class FigColorTip extends HTMLElement {
|
|
|
16550
16567
|
}
|
|
16551
16568
|
|
|
16552
16569
|
#render() {
|
|
16570
|
+
this.#teardownListeners();
|
|
16553
16571
|
const mode = this.#controlMode;
|
|
16554
16572
|
if (mode === "add" || mode === "remove") {
|
|
16555
16573
|
const iconName = mode === "add" ? "add" : "minus";
|
|
16556
16574
|
const label = this.getAttribute("aria-label") || (mode === "add" ? "Add color stop" : "Remove color stop");
|
|
16557
|
-
this.
|
|
16575
|
+
this.replaceChildren(
|
|
16576
|
+
figCreateElement(
|
|
16577
|
+
"fig-button",
|
|
16578
|
+
{
|
|
16579
|
+
icon: true,
|
|
16580
|
+
variant: "ghost",
|
|
16581
|
+
"aria-label": label,
|
|
16582
|
+
},
|
|
16583
|
+
createFigIcon(iconName),
|
|
16584
|
+
),
|
|
16585
|
+
);
|
|
16558
16586
|
this.#fillPicker = null;
|
|
16559
16587
|
this.#swatch = null;
|
|
16560
16588
|
this.addEventListener("click", this.#handleControlClick);
|
|
@@ -16566,7 +16594,6 @@ class FigColorTip extends HTMLElement {
|
|
|
16566
16594
|
const rawValue = (this.getAttribute("value") || "").trim();
|
|
16567
16595
|
const color = this.#normalizeColor(rawValue);
|
|
16568
16596
|
const alpha = this.#extractAlpha(rawValue);
|
|
16569
|
-
const alphaAttr = this.#alphaEnabled ? "" : 'alpha="false"';
|
|
16570
16597
|
const pickerValue =
|
|
16571
16598
|
alpha < 1
|
|
16572
16599
|
? JSON.stringify({
|
|
@@ -16575,16 +16602,28 @@ class FigColorTip extends HTMLElement {
|
|
|
16575
16602
|
opacity: Math.round(alpha * 100),
|
|
16576
16603
|
})
|
|
16577
16604
|
: JSON.stringify({ type: "solid", color });
|
|
16578
|
-
const
|
|
16579
|
-
|
|
16580
|
-
?
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16605
|
+
const swatch = figCreateElement("fig-swatch", {
|
|
16606
|
+
background: color,
|
|
16607
|
+
alpha: alpha < 1 ? alpha : null,
|
|
16608
|
+
});
|
|
16609
|
+
let picker = null;
|
|
16610
|
+
if (hasFigFillPicker()) {
|
|
16611
|
+
picker = figCreateElement(
|
|
16612
|
+
"fig-fill-picker",
|
|
16613
|
+
{
|
|
16614
|
+
mode: "solid",
|
|
16615
|
+
alpha: this.#alphaEnabled ? null : "false",
|
|
16616
|
+
value: pickerValue,
|
|
16617
|
+
},
|
|
16618
|
+
swatch,
|
|
16619
|
+
);
|
|
16620
|
+
this.replaceChildren(picker);
|
|
16621
|
+
} else {
|
|
16622
|
+
this.replaceChildren(swatch);
|
|
16623
|
+
}
|
|
16584
16624
|
|
|
16585
|
-
this.#fillPicker =
|
|
16586
|
-
this.#swatch =
|
|
16587
|
-
this.#teardownListeners();
|
|
16625
|
+
this.#fillPicker = picker;
|
|
16626
|
+
this.#swatch = swatch;
|
|
16588
16627
|
this.#fillPicker?.addEventListener("input", this.#boundHandleInput);
|
|
16589
16628
|
this.#fillPicker?.addEventListener("change", this.#boundHandleChange);
|
|
16590
16629
|
if (!this.#fillPicker) {
|
|
@@ -17795,15 +17834,14 @@ class FigHandle extends HTMLElement {
|
|
|
17795
17834
|
this.#didDrag = false;
|
|
17796
17835
|
return;
|
|
17797
17836
|
}
|
|
17837
|
+
this.select();
|
|
17798
17838
|
if (
|
|
17799
17839
|
this.getAttribute("type") === "color" &&
|
|
17800
17840
|
this.#canOpenColorPicker &&
|
|
17801
17841
|
!this.#tipMode
|
|
17802
17842
|
) {
|
|
17803
17843
|
this.#openDirectColorPicker();
|
|
17804
|
-
return;
|
|
17805
17844
|
}
|
|
17806
|
-
this.select();
|
|
17807
17845
|
};
|
|
17808
17846
|
|
|
17809
17847
|
#handleDeselect = (e) => {
|
|
@@ -17985,6 +18023,8 @@ class FigHandle extends HTMLElement {
|
|
|
17985
18023
|
#onPointerDown(e) {
|
|
17986
18024
|
if (!this.#dragEnabled || figBooleanAttribute(this, "disabled")) return;
|
|
17987
18025
|
e.preventDefault();
|
|
18026
|
+
this.focus();
|
|
18027
|
+
this.select();
|
|
17988
18028
|
const container = this.#getContainer();
|
|
17989
18029
|
if (!container) return;
|
|
17990
18030
|
|
|
@@ -18384,17 +18424,19 @@ class FigHandle extends HTMLElement {
|
|
|
18384
18424
|
e.stopPropagation();
|
|
18385
18425
|
const detail = this.#detailFromNativeColor(e.target.value);
|
|
18386
18426
|
this.setAttribute("color", this.#colorWithOpacity(detail.color, detail.opacity));
|
|
18387
|
-
this.removeAttribute("selected");
|
|
18388
18427
|
this.dispatchEvent(
|
|
18389
18428
|
new CustomEvent("change", {
|
|
18390
18429
|
bubbles: true,
|
|
18391
18430
|
detail,
|
|
18392
18431
|
}),
|
|
18393
18432
|
);
|
|
18433
|
+
if (this.isConnected) this.focus();
|
|
18394
18434
|
};
|
|
18395
18435
|
|
|
18396
18436
|
#handleDirectColorPickerClose = () => {
|
|
18397
|
-
this.
|
|
18437
|
+
if (!this.isConnected) return;
|
|
18438
|
+
this.setAttribute("selected", "");
|
|
18439
|
+
this.focus();
|
|
18398
18440
|
};
|
|
18399
18441
|
|
|
18400
18442
|
#handleColorTipInput = (e) => {
|
|
@@ -18558,6 +18600,19 @@ class FigSeparator extends HTMLElement {
|
|
|
18558
18600
|
}
|
|
18559
18601
|
figDefineElement("fig-separator", FigSeparator);
|
|
18560
18602
|
|
|
18603
|
+
// Backwards-compatible alias. Custom element constructors cannot be registered
|
|
18604
|
+
// under multiple tag names, so the legacy tag uses an otherwise empty subclass.
|
|
18605
|
+
class FigMenuSeparator extends FigSeparator {}
|
|
18606
|
+
figDefineElement("fig-menu-separator", FigMenuSeparator);
|
|
18607
|
+
|
|
18608
|
+
const FIG_MENU_CHILD_SELECTOR =
|
|
18609
|
+
":scope > fig-menu-item, :scope > fig-separator, :scope > fig-menu-separator";
|
|
18610
|
+
const FIG_MENU_CHILD_TAGS = new Set([
|
|
18611
|
+
"FIG-MENU-ITEM",
|
|
18612
|
+
"FIG-SEPARATOR",
|
|
18613
|
+
"FIG-MENU-SEPARATOR",
|
|
18614
|
+
]);
|
|
18615
|
+
|
|
18561
18616
|
class FigMenu extends HTMLElement {
|
|
18562
18617
|
#popup = null;
|
|
18563
18618
|
#panel = null;
|
|
@@ -18634,9 +18689,7 @@ class FigMenu extends HTMLElement {
|
|
|
18634
18689
|
if (this.#popup) {
|
|
18635
18690
|
this.#popup.removeEventListener("close", this.#boundPopupClose);
|
|
18636
18691
|
const items = Array.from(
|
|
18637
|
-
this.#panel?.querySelectorAll(
|
|
18638
|
-
":scope > fig-menu-item, :scope > fig-separator",
|
|
18639
|
-
) ?? [],
|
|
18692
|
+
this.#panel?.querySelectorAll(FIG_MENU_CHILD_SELECTOR) ?? [],
|
|
18640
18693
|
);
|
|
18641
18694
|
for (const item of items) this.insertBefore(item, this.#popup);
|
|
18642
18695
|
this.#popup.remove();
|
|
@@ -18678,7 +18731,9 @@ class FigMenu extends HTMLElement {
|
|
|
18678
18731
|
#detectTrigger() {
|
|
18679
18732
|
this.#trigger =
|
|
18680
18733
|
this.querySelector("[fig-menu-trigger]") ||
|
|
18681
|
-
this.querySelector(
|
|
18734
|
+
this.querySelector(
|
|
18735
|
+
":scope > :not(fig-menu-item):not(fig-separator):not(fig-menu-separator)",
|
|
18736
|
+
);
|
|
18682
18737
|
}
|
|
18683
18738
|
|
|
18684
18739
|
#usesContextMenuTrigger() {
|
|
@@ -18717,9 +18772,7 @@ class FigMenu extends HTMLElement {
|
|
|
18717
18772
|
|
|
18718
18773
|
#moveItemsToPopup() {
|
|
18719
18774
|
if (!this.#panel) return;
|
|
18720
|
-
const items = Array.from(this.querySelectorAll(
|
|
18721
|
-
":scope > fig-menu-item, :scope > fig-separator"
|
|
18722
|
-
));
|
|
18775
|
+
const items = Array.from(this.querySelectorAll(FIG_MENU_CHILD_SELECTOR));
|
|
18723
18776
|
for (const item of items) {
|
|
18724
18777
|
this.#panel.appendChild(item);
|
|
18725
18778
|
}
|
|
@@ -18758,8 +18811,7 @@ class FigMenu extends HTMLElement {
|
|
|
18758
18811
|
for (const node of mutation.addedNodes) {
|
|
18759
18812
|
if (node.nodeType !== 1 || node === this.#popup) continue;
|
|
18760
18813
|
if (
|
|
18761
|
-
(node.tagName
|
|
18762
|
-
node.tagName === "FIG-SEPARATOR") &&
|
|
18814
|
+
FIG_MENU_CHILD_TAGS.has(node.tagName) &&
|
|
18763
18815
|
node.parentElement === this
|
|
18764
18816
|
) {
|
|
18765
18817
|
this.#panel?.appendChild(node);
|