@rogieking/figui3 3.21.2 → 3.23.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/.cursor/skills/propkit/SKILL.md +2 -3
- package/README.md +1 -1
- package/components.css +189 -69
- package/fig.js +940 -147
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -947,10 +947,161 @@ class FigTooltip extends HTMLElement {
|
|
|
947
947
|
this.hidePopup();
|
|
948
948
|
}
|
|
949
949
|
}
|
|
950
|
+
|
|
951
|
+
static #programmatic = new WeakMap();
|
|
952
|
+
|
|
953
|
+
static show(anchor, text, options = {}) {
|
|
954
|
+
FigTooltip.hide(anchor);
|
|
955
|
+
const delay = options.delay ?? 500;
|
|
956
|
+
const warm =
|
|
957
|
+
Date.now() - FigTooltip.#lastShownAt < FigTooltip.#warmupWindow;
|
|
958
|
+
const effectiveDelay = warm ? 0 : delay;
|
|
959
|
+
|
|
960
|
+
const state = { timeout: null, popup: null };
|
|
961
|
+
FigTooltip.#programmatic.set(anchor, state);
|
|
962
|
+
|
|
963
|
+
state.timeout = setTimeout(() => {
|
|
964
|
+
const popup = document.createElement("span");
|
|
965
|
+
popup.setAttribute("class", "fig-tooltip");
|
|
966
|
+
popup.setAttribute("role", "tooltip");
|
|
967
|
+
popup.style.position = "fixed";
|
|
968
|
+
popup.style.pointerEvents = "none";
|
|
969
|
+
const content = document.createElement("span");
|
|
970
|
+
content.innerText = text;
|
|
971
|
+
popup.append(content);
|
|
972
|
+
|
|
973
|
+
const parentDialog = anchor.closest("dialog");
|
|
974
|
+
if (parentDialog && parentDialog.open) {
|
|
975
|
+
parentDialog.append(popup);
|
|
976
|
+
} else {
|
|
977
|
+
document.body.append(popup);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
const rect = anchor.getBoundingClientRect();
|
|
981
|
+
const popupRect = popup.getBoundingClientRect();
|
|
982
|
+
const container = popup.parentElement;
|
|
983
|
+
const containerRect =
|
|
984
|
+
container && container !== document.body
|
|
985
|
+
? container.getBoundingClientRect()
|
|
986
|
+
: { left: 0, top: 0 };
|
|
987
|
+
|
|
988
|
+
let top = rect.top - popupRect.height - 4 - containerRect.top;
|
|
989
|
+
let left =
|
|
990
|
+
rect.left + (rect.width - popupRect.width) / 2 - containerRect.left;
|
|
991
|
+
popup.setAttribute("position", "top");
|
|
992
|
+
|
|
993
|
+
if (top + containerRect.top < 0) {
|
|
994
|
+
popup.setAttribute("position", "bottom");
|
|
995
|
+
top = rect.bottom + 4 - containerRect.top;
|
|
996
|
+
}
|
|
997
|
+
if (left + containerRect.left < 8) {
|
|
998
|
+
left = 8 - containerRect.left;
|
|
999
|
+
}
|
|
1000
|
+
if (left + popupRect.width + containerRect.left > window.innerWidth - 8) {
|
|
1001
|
+
left = window.innerWidth - popupRect.width - 8 - containerRect.left;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const targetCenter = rect.left - containerRect.left + rect.width / 2;
|
|
1005
|
+
popup.style.setProperty("--beak-offset", `${targetCenter - left}px`);
|
|
1006
|
+
popup.style.top = `${top}px`;
|
|
1007
|
+
popup.style.left = `${left}px`;
|
|
1008
|
+
popup.style.zIndex = figGetHighestZIndex() + 1;
|
|
1009
|
+
|
|
1010
|
+
state.popup = popup;
|
|
1011
|
+
FigTooltip.#lastShownAt = Date.now();
|
|
1012
|
+
}, effectiveDelay);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
static hide(anchor) {
|
|
1016
|
+
const state = FigTooltip.#programmatic.get(anchor);
|
|
1017
|
+
if (!state) return;
|
|
1018
|
+
clearTimeout(state.timeout);
|
|
1019
|
+
if (state.popup) state.popup.remove();
|
|
1020
|
+
FigTooltip.#programmatic.delete(anchor);
|
|
1021
|
+
}
|
|
950
1022
|
}
|
|
951
1023
|
|
|
952
1024
|
customElements.define("fig-tooltip", FigTooltip);
|
|
953
1025
|
|
|
1026
|
+
/* Text Truncation */
|
|
1027
|
+
class FigTruncate extends HTMLElement {
|
|
1028
|
+
static observedAttributes = ["position", "tail"];
|
|
1029
|
+
|
|
1030
|
+
#originalText = null;
|
|
1031
|
+
#boundEnter = null;
|
|
1032
|
+
#boundLeave = null;
|
|
1033
|
+
|
|
1034
|
+
connectedCallback() {
|
|
1035
|
+
this.#originalText = this.textContent;
|
|
1036
|
+
requestAnimationFrame(() => {
|
|
1037
|
+
this.#render();
|
|
1038
|
+
this.#setupTooltip();
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
disconnectedCallback() {
|
|
1043
|
+
this.#teardownTooltip();
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1047
|
+
if (oldValue === newValue) return;
|
|
1048
|
+
if (this.#originalText === null) return;
|
|
1049
|
+
this.#render();
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
#render() {
|
|
1053
|
+
const position = this.getAttribute("position") || "right";
|
|
1054
|
+
const text = this.#originalText || "";
|
|
1055
|
+
if (position === "middle") {
|
|
1056
|
+
const tail = this.getAttribute("tail");
|
|
1057
|
+
let splitIndex;
|
|
1058
|
+
if (tail) {
|
|
1059
|
+
const idx = text.lastIndexOf(tail);
|
|
1060
|
+
splitIndex = idx > 0 ? idx : Math.ceil(text.length / 2);
|
|
1061
|
+
} else {
|
|
1062
|
+
splitIndex = Math.ceil(text.length / 2);
|
|
1063
|
+
}
|
|
1064
|
+
this.innerHTML = "";
|
|
1065
|
+
const startSpan = document.createElement("span");
|
|
1066
|
+
startSpan.className = "start";
|
|
1067
|
+
startSpan.textContent = text.slice(0, splitIndex);
|
|
1068
|
+
const endSpan = document.createElement("span");
|
|
1069
|
+
endSpan.className = "end";
|
|
1070
|
+
endSpan.textContent = text.slice(splitIndex);
|
|
1071
|
+
this.appendChild(startSpan);
|
|
1072
|
+
this.appendChild(endSpan);
|
|
1073
|
+
} else {
|
|
1074
|
+
this.textContent = text;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
#setupTooltip() {
|
|
1079
|
+
if (
|
|
1080
|
+
!this.hasAttribute("tooltip") ||
|
|
1081
|
+
this.getAttribute("tooltip") === "false"
|
|
1082
|
+
)
|
|
1083
|
+
return;
|
|
1084
|
+
this.#boundEnter = () => {
|
|
1085
|
+
if (this.scrollWidth <= this.clientWidth) return;
|
|
1086
|
+
FigTooltip.show(this, this.#originalText);
|
|
1087
|
+
};
|
|
1088
|
+
this.#boundLeave = () => {
|
|
1089
|
+
FigTooltip.hide(this);
|
|
1090
|
+
};
|
|
1091
|
+
this.addEventListener("pointerenter", this.#boundEnter);
|
|
1092
|
+
this.addEventListener("pointerleave", this.#boundLeave);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
#teardownTooltip() {
|
|
1096
|
+
if (this.#boundEnter)
|
|
1097
|
+
this.removeEventListener("pointerenter", this.#boundEnter);
|
|
1098
|
+
if (this.#boundLeave)
|
|
1099
|
+
this.removeEventListener("pointerleave", this.#boundLeave);
|
|
1100
|
+
FigTooltip.hide(this);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
customElements.define("fig-truncate", FigTruncate);
|
|
1104
|
+
|
|
954
1105
|
/* Dialog */
|
|
955
1106
|
/**
|
|
956
1107
|
* A custom dialog element for modal and non-modal dialogs.
|
|
@@ -3533,8 +3684,12 @@ class FigInputText extends HTMLElement {
|
|
|
3533
3684
|
valueTransformed = this.#formatNumber(valueTransformed);
|
|
3534
3685
|
this.value = value;
|
|
3535
3686
|
this.input.value = valueTransformed;
|
|
3536
|
-
this.dispatchEvent(
|
|
3537
|
-
|
|
3687
|
+
this.dispatchEvent(
|
|
3688
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
3689
|
+
);
|
|
3690
|
+
this.dispatchEvent(
|
|
3691
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
3692
|
+
);
|
|
3538
3693
|
}
|
|
3539
3694
|
#handleMouseDown(e) {
|
|
3540
3695
|
if (this.type !== "number") return;
|
|
@@ -4023,8 +4178,12 @@ class FigInputNumber extends HTMLElement {
|
|
|
4023
4178
|
value = this.#sanitizeInput(value, false);
|
|
4024
4179
|
this.value = value;
|
|
4025
4180
|
this.input.value = this.#formatWithUnit(this.value);
|
|
4026
|
-
this.dispatchEvent(
|
|
4027
|
-
|
|
4181
|
+
this.dispatchEvent(
|
|
4182
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
4183
|
+
);
|
|
4184
|
+
this.dispatchEvent(
|
|
4185
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
4186
|
+
);
|
|
4028
4187
|
}
|
|
4029
4188
|
|
|
4030
4189
|
#handleMouseDown(e) {
|
|
@@ -4209,6 +4368,10 @@ customElements.define("fig-avatar", FigAvatar);
|
|
|
4209
4368
|
|
|
4210
4369
|
/* Form Field */
|
|
4211
4370
|
class FigField extends HTMLElement {
|
|
4371
|
+
#toggleable = false;
|
|
4372
|
+
#chevron = null;
|
|
4373
|
+
#boundToggle = null;
|
|
4374
|
+
|
|
4212
4375
|
constructor() {
|
|
4213
4376
|
super();
|
|
4214
4377
|
}
|
|
@@ -4223,15 +4386,65 @@ class FigField extends HTMLElement {
|
|
|
4223
4386
|
this.input = Array.from(this.childNodes).find((node) =>
|
|
4224
4387
|
node.nodeName.toLowerCase().startsWith("fig-"),
|
|
4225
4388
|
);
|
|
4226
|
-
|
|
4389
|
+
|
|
4390
|
+
this.#toggleable = !!(this.input && "open" in this.input);
|
|
4391
|
+
|
|
4392
|
+
if (this.#toggleable && this.label) {
|
|
4393
|
+
this.#chevron = document.createElement("span");
|
|
4394
|
+
this.#chevron.className = "fig-mask-icon fig-field-chevron";
|
|
4395
|
+
this.insertBefore(this.#chevron, this.label);
|
|
4396
|
+
|
|
4397
|
+
this.#boundToggle = (e) => {
|
|
4398
|
+
e.preventDefault();
|
|
4399
|
+
e.stopPropagation();
|
|
4400
|
+
if (this.input && typeof this.input.open !== "undefined") {
|
|
4401
|
+
this.input.open = !this.input.open;
|
|
4402
|
+
}
|
|
4403
|
+
};
|
|
4404
|
+
this.#chevron.addEventListener("click", this.#boundToggle);
|
|
4405
|
+
this.label.addEventListener("click", this.#boundToggle);
|
|
4406
|
+
} else if (this.input && this.label) {
|
|
4227
4407
|
this.label.addEventListener("click", this.focus.bind(this));
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4410
|
+
if (this.input && this.label && !this.#toggleable) {
|
|
4228
4411
|
let inputId = this.input.getAttribute("id") || figUniqueId();
|
|
4229
4412
|
this.input.setAttribute("id", inputId);
|
|
4230
4413
|
this.label.setAttribute("for", inputId);
|
|
4231
4414
|
}
|
|
4415
|
+
|
|
4416
|
+
if (this.label) {
|
|
4417
|
+
this.label.addEventListener(
|
|
4418
|
+
"pointerenter",
|
|
4419
|
+
this.#onLabelEnter.bind(this),
|
|
4420
|
+
);
|
|
4421
|
+
this.label.addEventListener(
|
|
4422
|
+
"pointerleave",
|
|
4423
|
+
this.#onLabelLeave.bind(this),
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4232
4426
|
});
|
|
4233
4427
|
}
|
|
4234
4428
|
|
|
4429
|
+
disconnectedCallback() {
|
|
4430
|
+
if (this.label) FigTooltip.hide(this.label);
|
|
4431
|
+
if (this.label && this.#boundToggle) {
|
|
4432
|
+
this.label.removeEventListener("click", this.#boundToggle);
|
|
4433
|
+
}
|
|
4434
|
+
if (this.#chevron && this.#boundToggle) {
|
|
4435
|
+
this.#chevron.removeEventListener("click", this.#boundToggle);
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
|
|
4439
|
+
#onLabelEnter() {
|
|
4440
|
+
if (!this.label || this.label.scrollWidth <= this.label.clientWidth) return;
|
|
4441
|
+
FigTooltip.show(this.label, this.label.textContent.trim());
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
#onLabelLeave() {
|
|
4445
|
+
if (this.label) FigTooltip.hide(this.label);
|
|
4446
|
+
}
|
|
4447
|
+
|
|
4235
4448
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
4236
4449
|
switch (name) {
|
|
4237
4450
|
case "label":
|
|
@@ -4243,7 +4456,18 @@ class FigField extends HTMLElement {
|
|
|
4243
4456
|
}
|
|
4244
4457
|
|
|
4245
4458
|
focus() {
|
|
4246
|
-
this.input
|
|
4459
|
+
if (!this.input) return;
|
|
4460
|
+
if (this.input.contains(document.activeElement)) return;
|
|
4461
|
+
const nativeInputs = this.input.querySelectorAll("input, select, textarea");
|
|
4462
|
+
if (nativeInputs.length === 1) {
|
|
4463
|
+
nativeInputs[0].focus();
|
|
4464
|
+
nativeInputs[0].click();
|
|
4465
|
+
} else {
|
|
4466
|
+
this.input.focus();
|
|
4467
|
+
if (nativeInputs.length === 0) {
|
|
4468
|
+
this.input.click();
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4247
4471
|
}
|
|
4248
4472
|
}
|
|
4249
4473
|
customElements.define("fig-field", FigField);
|
|
@@ -4578,7 +4802,7 @@ class FigInputColor extends HTMLElement {
|
|
|
4578
4802
|
const disabledAttr = disabled ? " disabled" : "";
|
|
4579
4803
|
|
|
4580
4804
|
let html = ``;
|
|
4581
|
-
const showText = this.getAttribute("text")
|
|
4805
|
+
const showText = this.getAttribute("text") !== "false";
|
|
4582
4806
|
if (showText) {
|
|
4583
4807
|
let label = `<fig-input-text
|
|
4584
4808
|
type="text"
|
|
@@ -4801,22 +5025,35 @@ class FigInputColor extends HTMLElement {
|
|
|
4801
5025
|
}
|
|
4802
5026
|
|
|
4803
5027
|
#emitInputEvent() {
|
|
4804
|
-
this.dispatchEvent(
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
5028
|
+
this.dispatchEvent(
|
|
5029
|
+
new CustomEvent("input", {
|
|
5030
|
+
bubbles: true,
|
|
5031
|
+
cancelable: true,
|
|
5032
|
+
detail: { value: this.value, hex: this.hex, rgba: this.rgba },
|
|
5033
|
+
}),
|
|
5034
|
+
);
|
|
4809
5035
|
}
|
|
4810
5036
|
#emitChangeEvent() {
|
|
4811
|
-
this.dispatchEvent(
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
5037
|
+
this.dispatchEvent(
|
|
5038
|
+
new CustomEvent("change", {
|
|
5039
|
+
bubbles: true,
|
|
5040
|
+
cancelable: true,
|
|
5041
|
+
detail: { value: this.value, hex: this.hex, rgba: this.rgba },
|
|
5042
|
+
}),
|
|
5043
|
+
);
|
|
4816
5044
|
}
|
|
4817
5045
|
|
|
4818
5046
|
static get observedAttributes() {
|
|
4819
|
-
return [
|
|
5047
|
+
return [
|
|
5048
|
+
"value",
|
|
5049
|
+
"style",
|
|
5050
|
+
"mode",
|
|
5051
|
+
"picker",
|
|
5052
|
+
"experimental",
|
|
5053
|
+
"alpha",
|
|
5054
|
+
"text",
|
|
5055
|
+
"disabled",
|
|
5056
|
+
];
|
|
4820
5057
|
}
|
|
4821
5058
|
|
|
4822
5059
|
get mode() {
|
|
@@ -4877,7 +5114,9 @@ class FigInputColor extends HTMLElement {
|
|
|
4877
5114
|
}
|
|
4878
5115
|
|
|
4879
5116
|
get #disabled() {
|
|
4880
|
-
return
|
|
5117
|
+
return (
|
|
5118
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false"
|
|
5119
|
+
);
|
|
4881
5120
|
}
|
|
4882
5121
|
|
|
4883
5122
|
#syncDisabled() {
|
|
@@ -5076,9 +5315,15 @@ function figRGBToOklab(r, g, b) {
|
|
|
5076
5315
|
const lr = figRGBToLinear(r);
|
|
5077
5316
|
const lg = figRGBToLinear(g);
|
|
5078
5317
|
const lb = figRGBToLinear(b);
|
|
5079
|
-
const l_ = Math.cbrt(
|
|
5080
|
-
|
|
5081
|
-
|
|
5318
|
+
const l_ = Math.cbrt(
|
|
5319
|
+
0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb,
|
|
5320
|
+
);
|
|
5321
|
+
const m_ = Math.cbrt(
|
|
5322
|
+
0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb,
|
|
5323
|
+
);
|
|
5324
|
+
const s_ = Math.cbrt(
|
|
5325
|
+
0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb,
|
|
5326
|
+
);
|
|
5082
5327
|
return {
|
|
5083
5328
|
l: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
|
|
5084
5329
|
a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
|
|
@@ -5101,7 +5346,11 @@ function figOklabToRGB(L, a, b) {
|
|
|
5101
5346
|
}
|
|
5102
5347
|
|
|
5103
5348
|
function figOklabToOklch(L, a, b) {
|
|
5104
|
-
return {
|
|
5349
|
+
return {
|
|
5350
|
+
l: L,
|
|
5351
|
+
c: Math.sqrt(a * a + b * b),
|
|
5352
|
+
h: (Math.atan2(b, a) * 180) / Math.PI,
|
|
5353
|
+
};
|
|
5105
5354
|
}
|
|
5106
5355
|
|
|
5107
5356
|
function figOklchToOklab(l, c, h) {
|
|
@@ -5130,15 +5379,21 @@ function figInterpolateHue(h1, h2, t, mode) {
|
|
|
5130
5379
|
else if (diff < -180) diff += 360;
|
|
5131
5380
|
break;
|
|
5132
5381
|
}
|
|
5133
|
-
return ((a + diff * t) % 360 + 360) % 360;
|
|
5382
|
+
return (((a + diff * t) % 360) + 360) % 360;
|
|
5134
5383
|
}
|
|
5135
5384
|
|
|
5136
|
-
function figSampleGradientAt(
|
|
5385
|
+
function figSampleGradientAt(
|
|
5386
|
+
stops,
|
|
5387
|
+
position,
|
|
5388
|
+
interpolationSpace,
|
|
5389
|
+
hueInterpolation,
|
|
5390
|
+
) {
|
|
5137
5391
|
const sorted = [...stops].sort((a, b) => a.position - b.position);
|
|
5138
5392
|
const pos = position * 100;
|
|
5139
5393
|
if (sorted.length === 0) return "#888888";
|
|
5140
5394
|
if (pos <= sorted[0].position) return sorted[0].color;
|
|
5141
|
-
if (pos >= sorted[sorted.length - 1].position)
|
|
5395
|
+
if (pos >= sorted[sorted.length - 1].position)
|
|
5396
|
+
return sorted[sorted.length - 1].color;
|
|
5142
5397
|
|
|
5143
5398
|
let i = 0;
|
|
5144
5399
|
while (i < sorted.length - 1 && sorted[i + 1].position < pos) i++;
|
|
@@ -5154,8 +5409,12 @@ function figSampleGradientAt(stops, position, interpolationSpace, hueInterpolati
|
|
|
5154
5409
|
const space = interpolationSpace || "oklab";
|
|
5155
5410
|
|
|
5156
5411
|
if (space === "srgb-linear") {
|
|
5157
|
-
const lr1 = figRGBToLinear(c1.r),
|
|
5158
|
-
|
|
5412
|
+
const lr1 = figRGBToLinear(c1.r),
|
|
5413
|
+
lg1 = figRGBToLinear(c1.g),
|
|
5414
|
+
lb1 = figRGBToLinear(c1.b);
|
|
5415
|
+
const lr2 = figRGBToLinear(c2.r),
|
|
5416
|
+
lg2 = figRGBToLinear(c2.g),
|
|
5417
|
+
lb2 = figRGBToLinear(c2.b);
|
|
5159
5418
|
r = figLinearToSRGB(lr1 + (lr2 - lr1) * t);
|
|
5160
5419
|
g = figLinearToSRGB(lg1 + (lg2 - lg1) * t);
|
|
5161
5420
|
b = figLinearToSRGB(lb1 + (lb2 - lb1) * t);
|
|
@@ -5166,10 +5425,17 @@ function figSampleGradientAt(stops, position, interpolationSpace, hueInterpolati
|
|
|
5166
5425
|
const lch2 = figOklabToOklch(lab2.l, lab2.a, lab2.b);
|
|
5167
5426
|
const L = lch1.l + (lch2.l - lch1.l) * t;
|
|
5168
5427
|
const C = lch1.c + (lch2.c - lch1.c) * t;
|
|
5169
|
-
const H = figInterpolateHue(
|
|
5428
|
+
const H = figInterpolateHue(
|
|
5429
|
+
lch1.h,
|
|
5430
|
+
lch2.h,
|
|
5431
|
+
t,
|
|
5432
|
+
hueInterpolation || "shorter",
|
|
5433
|
+
);
|
|
5170
5434
|
const lab = figOklchToOklab(L, C, H);
|
|
5171
5435
|
const rgb = figOklabToRGB(lab.l, lab.a, lab.b);
|
|
5172
|
-
r = rgb.r;
|
|
5436
|
+
r = rgb.r;
|
|
5437
|
+
g = rgb.g;
|
|
5438
|
+
b = rgb.b;
|
|
5173
5439
|
} else {
|
|
5174
5440
|
const lab1 = figRGBToOklab(c1.r, c1.g, c1.b);
|
|
5175
5441
|
const lab2 = figRGBToOklab(c2.r, c2.g, c2.b);
|
|
@@ -5177,7 +5443,9 @@ function figSampleGradientAt(stops, position, interpolationSpace, hueInterpolati
|
|
|
5177
5443
|
const a = lab1.a + (lab2.a - lab1.a) * t;
|
|
5178
5444
|
const bv = lab1.b + (lab2.b - lab1.b) * t;
|
|
5179
5445
|
const rgb = figOklabToRGB(L, a, bv);
|
|
5180
|
-
r = rgb.r;
|
|
5446
|
+
r = rgb.r;
|
|
5447
|
+
g = rgb.g;
|
|
5448
|
+
b = rgb.b;
|
|
5181
5449
|
}
|
|
5182
5450
|
|
|
5183
5451
|
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`.toUpperCase();
|
|
@@ -5347,7 +5615,11 @@ class FigInputFill extends HTMLElement {
|
|
|
5347
5615
|
|
|
5348
5616
|
#syncDisabled() {
|
|
5349
5617
|
const disabled = this.hasAttribute("disabled");
|
|
5350
|
-
for (const child of [
|
|
5618
|
+
for (const child of [
|
|
5619
|
+
this.#fillPicker,
|
|
5620
|
+
this.#opacityInput,
|
|
5621
|
+
this.#hexInput,
|
|
5622
|
+
]) {
|
|
5351
5623
|
if (!child) continue;
|
|
5352
5624
|
if (disabled) child.setAttribute("disabled", "");
|
|
5353
5625
|
else child.removeAttribute("disabled");
|
|
@@ -5911,11 +6183,28 @@ class FigInputPalette extends HTMLElement {
|
|
|
5911
6183
|
#renderRAF = null;
|
|
5912
6184
|
|
|
5913
6185
|
static get observedAttributes() {
|
|
5914
|
-
return ["value", "disabled", "min", "max", "
|
|
6186
|
+
return ["value", "disabled", "min", "max", "open", "add"];
|
|
6187
|
+
}
|
|
6188
|
+
|
|
6189
|
+
get open() {
|
|
6190
|
+
return this.hasAttribute("open") && this.getAttribute("open") !== "false";
|
|
5915
6191
|
}
|
|
5916
6192
|
|
|
5917
|
-
|
|
5918
|
-
|
|
6193
|
+
set open(value) {
|
|
6194
|
+
const was = this.open;
|
|
6195
|
+
if (value) {
|
|
6196
|
+
this.setAttribute("open", "");
|
|
6197
|
+
} else {
|
|
6198
|
+
this.removeAttribute("open");
|
|
6199
|
+
}
|
|
6200
|
+
if (was !== !!value) {
|
|
6201
|
+
this.dispatchEvent(
|
|
6202
|
+
new CustomEvent("openchange", {
|
|
6203
|
+
detail: { open: !!value },
|
|
6204
|
+
bubbles: true,
|
|
6205
|
+
}),
|
|
6206
|
+
);
|
|
6207
|
+
}
|
|
5919
6208
|
}
|
|
5920
6209
|
|
|
5921
6210
|
get #showAdd() {
|
|
@@ -5933,6 +6222,7 @@ class FigInputPalette extends HTMLElement {
|
|
|
5933
6222
|
}
|
|
5934
6223
|
|
|
5935
6224
|
connectedCallback() {
|
|
6225
|
+
if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0");
|
|
5936
6226
|
if (this.#renderRAF) cancelAnimationFrame(this.#renderRAF);
|
|
5937
6227
|
this.#renderRAF = requestAnimationFrame(() => {
|
|
5938
6228
|
this.#renderRAF = null;
|
|
@@ -5963,7 +6253,7 @@ class FigInputPalette extends HTMLElement {
|
|
|
5963
6253
|
break;
|
|
5964
6254
|
case "min":
|
|
5965
6255
|
case "max":
|
|
5966
|
-
case "
|
|
6256
|
+
case "open":
|
|
5967
6257
|
case "add":
|
|
5968
6258
|
this.#render();
|
|
5969
6259
|
break;
|
|
@@ -5985,12 +6275,21 @@ class FigInputPalette extends HTMLElement {
|
|
|
5985
6275
|
if (Array.isArray(parsed)) {
|
|
5986
6276
|
this.#colors = parsed.map((entry) => {
|
|
5987
6277
|
if (typeof entry === "string") {
|
|
5988
|
-
return {
|
|
6278
|
+
return {
|
|
6279
|
+
color: entry.slice(0, 7),
|
|
6280
|
+
alpha:
|
|
6281
|
+
entry.length > 7 ? parseInt(entry.slice(7, 9), 16) / 255 : 1,
|
|
6282
|
+
};
|
|
5989
6283
|
}
|
|
5990
6284
|
if (entry && typeof entry === "object") {
|
|
5991
6285
|
return {
|
|
5992
6286
|
color: entry.color || "#D9D9D9",
|
|
5993
|
-
alpha:
|
|
6287
|
+
alpha:
|
|
6288
|
+
entry.alpha !== undefined
|
|
6289
|
+
? entry.alpha
|
|
6290
|
+
: entry.opacity !== undefined
|
|
6291
|
+
? entry.opacity / 100
|
|
6292
|
+
: 1,
|
|
5994
6293
|
};
|
|
5995
6294
|
}
|
|
5996
6295
|
return { color: "#D9D9D9", alpha: 1 };
|
|
@@ -6015,10 +6314,13 @@ class FigInputPalette extends HTMLElement {
|
|
|
6015
6314
|
|
|
6016
6315
|
// Single hex
|
|
6017
6316
|
if (trimmed.startsWith("#")) {
|
|
6018
|
-
this.#colors = [
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6317
|
+
this.#colors = [
|
|
6318
|
+
{
|
|
6319
|
+
color: trimmed.slice(0, 7),
|
|
6320
|
+
alpha:
|
|
6321
|
+
trimmed.length > 7 ? parseInt(trimmed.slice(7, 9), 16) / 255 : 1,
|
|
6322
|
+
},
|
|
6323
|
+
];
|
|
6022
6324
|
return;
|
|
6023
6325
|
}
|
|
6024
6326
|
|
|
@@ -6038,7 +6340,9 @@ class FigInputPalette extends HTMLElement {
|
|
|
6038
6340
|
}
|
|
6039
6341
|
|
|
6040
6342
|
#render() {
|
|
6041
|
-
const disabled =
|
|
6343
|
+
const disabled =
|
|
6344
|
+
this.hasAttribute("disabled") &&
|
|
6345
|
+
this.getAttribute("disabled") !== "false";
|
|
6042
6346
|
|
|
6043
6347
|
this.innerHTML = "";
|
|
6044
6348
|
this.#inlinePickers = [];
|
|
@@ -6050,7 +6354,9 @@ class FigInputPalette extends HTMLElement {
|
|
|
6050
6354
|
const wrap = document.createElement("div");
|
|
6051
6355
|
wrap.className = "palette-colors";
|
|
6052
6356
|
this.#colors.forEach((entry, i) => {
|
|
6053
|
-
wrap.appendChild(
|
|
6357
|
+
wrap.appendChild(
|
|
6358
|
+
this.#createPicker(entry, i, disabled, { inline: true }),
|
|
6359
|
+
);
|
|
6054
6360
|
});
|
|
6055
6361
|
inlineWrap.appendChild(wrap);
|
|
6056
6362
|
|
|
@@ -6066,9 +6372,13 @@ class FigInputPalette extends HTMLElement {
|
|
|
6066
6372
|
}
|
|
6067
6373
|
|
|
6068
6374
|
#createPicker(entry, index, disabled, { inline = false } = {}) {
|
|
6069
|
-
const hexAlpha =
|
|
6070
|
-
|
|
6071
|
-
|
|
6375
|
+
const hexAlpha =
|
|
6376
|
+
entry.alpha < 1
|
|
6377
|
+
? entry.color +
|
|
6378
|
+
Math.round(entry.alpha * 255)
|
|
6379
|
+
.toString(16)
|
|
6380
|
+
.padStart(2, "0")
|
|
6381
|
+
: entry.color;
|
|
6072
6382
|
const ic = document.createElement("fig-input-color");
|
|
6073
6383
|
ic.setAttribute("value", hexAlpha);
|
|
6074
6384
|
ic.setAttribute("picker", "figma");
|
|
@@ -6095,9 +6405,13 @@ class FigInputPalette extends HTMLElement {
|
|
|
6095
6405
|
const sibling = siblingList[index];
|
|
6096
6406
|
if (sibling) {
|
|
6097
6407
|
const entry = this.#colors[index];
|
|
6098
|
-
const hex =
|
|
6099
|
-
|
|
6100
|
-
|
|
6408
|
+
const hex =
|
|
6409
|
+
entry.alpha < 1
|
|
6410
|
+
? entry.color +
|
|
6411
|
+
Math.round(entry.alpha * 255)
|
|
6412
|
+
.toString(16)
|
|
6413
|
+
.padStart(2, "0")
|
|
6414
|
+
: entry.color;
|
|
6101
6415
|
sibling.setAttribute("value", hex);
|
|
6102
6416
|
}
|
|
6103
6417
|
};
|
|
@@ -6120,14 +6434,18 @@ class FigInputPalette extends HTMLElement {
|
|
|
6120
6434
|
#createAddButton(disabled, parent = this) {
|
|
6121
6435
|
const atMax = this.#colors.length >= this.#max;
|
|
6122
6436
|
const addBtn = document.createElement("fig-button");
|
|
6123
|
-
addBtn.setAttribute("variant", "
|
|
6437
|
+
addBtn.setAttribute("variant", "input");
|
|
6124
6438
|
addBtn.setAttribute("icon", "true");
|
|
6125
6439
|
addBtn.setAttribute("aria-label", "Add color");
|
|
6126
6440
|
addBtn.className = "palette-add-btn";
|
|
6127
6441
|
if (disabled || atMax) addBtn.setAttribute("disabled", "");
|
|
6128
6442
|
addBtn.innerHTML = `<span class="fig-mask-icon" style="--icon: var(--icon-add)"></span>`;
|
|
6129
6443
|
addBtn.addEventListener("click", () => {
|
|
6130
|
-
if (
|
|
6444
|
+
if (
|
|
6445
|
+
this.hasAttribute("disabled") &&
|
|
6446
|
+
this.getAttribute("disabled") !== "false"
|
|
6447
|
+
)
|
|
6448
|
+
return;
|
|
6131
6449
|
if (this.#colors.length >= this.#max) return;
|
|
6132
6450
|
this.#addColor({ color: "#D9D9D9", alpha: 1 });
|
|
6133
6451
|
});
|
|
@@ -6139,10 +6457,14 @@ class FigInputPalette extends HTMLElement {
|
|
|
6139
6457
|
|
|
6140
6458
|
#addColor(entry) {
|
|
6141
6459
|
this.#colors.push(entry);
|
|
6142
|
-
const disabled =
|
|
6460
|
+
const disabled =
|
|
6461
|
+
this.hasAttribute("disabled") &&
|
|
6462
|
+
this.getAttribute("disabled") !== "false";
|
|
6143
6463
|
const index = this.#colors.length - 1;
|
|
6144
6464
|
|
|
6145
|
-
const inlineIc = this.#createPicker(entry, index, disabled, {
|
|
6465
|
+
const inlineIc = this.#createPicker(entry, index, disabled, {
|
|
6466
|
+
inline: true,
|
|
6467
|
+
});
|
|
6146
6468
|
const wrap = this.querySelector(".palette-colors");
|
|
6147
6469
|
if (wrap) wrap.appendChild(inlineIc);
|
|
6148
6470
|
|
|
@@ -6160,9 +6482,13 @@ class FigInputPalette extends HTMLElement {
|
|
|
6160
6482
|
#updateChit(index) {
|
|
6161
6483
|
const entry = this.#colors[index];
|
|
6162
6484
|
if (!entry) return;
|
|
6163
|
-
const hexAlpha =
|
|
6164
|
-
|
|
6165
|
-
|
|
6485
|
+
const hexAlpha =
|
|
6486
|
+
entry.alpha < 1
|
|
6487
|
+
? entry.color +
|
|
6488
|
+
Math.round(entry.alpha * 255)
|
|
6489
|
+
.toString(16)
|
|
6490
|
+
.padStart(2, "0")
|
|
6491
|
+
: entry.color;
|
|
6166
6492
|
const inl = this.#inlinePickers[index];
|
|
6167
6493
|
if (inl) inl.setAttribute("value", hexAlpha);
|
|
6168
6494
|
const exp = this.#expandedPickers[index];
|
|
@@ -6180,7 +6506,9 @@ class FigInputPalette extends HTMLElement {
|
|
|
6180
6506
|
}
|
|
6181
6507
|
|
|
6182
6508
|
#syncDisabled() {
|
|
6183
|
-
const disabled =
|
|
6509
|
+
const disabled =
|
|
6510
|
+
this.hasAttribute("disabled") &&
|
|
6511
|
+
this.getAttribute("disabled") !== "false";
|
|
6184
6512
|
[...this.#inlinePickers, ...this.#expandedPickers].forEach((fp) => {
|
|
6185
6513
|
if (disabled) fp.setAttribute("disabled", "");
|
|
6186
6514
|
else fp.removeAttribute("disabled");
|
|
@@ -6314,7 +6642,9 @@ class FigInputGradient extends HTMLElement {
|
|
|
6314
6642
|
const idx = parseInt(selected.dataset.stopIndex, 10);
|
|
6315
6643
|
if (isNaN(idx) || !this.#gradient.stops[idx]) return;
|
|
6316
6644
|
e.preventDefault();
|
|
6317
|
-
const delta =
|
|
6645
|
+
const delta =
|
|
6646
|
+
(e.key === "ArrowRight" ? 1 : -1) *
|
|
6647
|
+
(e.shiftKey ? FigInputGradient.SHIFT_SNAP : 1);
|
|
6318
6648
|
const stop = this.#gradient.stops[idx];
|
|
6319
6649
|
stop.position = Math.max(0, Math.min(100, stop.position + delta));
|
|
6320
6650
|
selected.setAttribute("value", `${stop.position}% 50%`);
|
|
@@ -6582,10 +6912,12 @@ class FigInputGradient extends HTMLElement {
|
|
|
6582
6912
|
const stopIdx = parseInt(clickedHandle?.dataset.stopIndex, 10);
|
|
6583
6913
|
this.#distributeStops();
|
|
6584
6914
|
if (!isNaN(stopIdx)) {
|
|
6585
|
-
this.#track
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6915
|
+
this.#track
|
|
6916
|
+
.querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)")
|
|
6917
|
+
.forEach((h) => {
|
|
6918
|
+
if (parseInt(h.dataset.stopIndex, 10) === stopIdx) h.select();
|
|
6919
|
+
else h.deselect();
|
|
6920
|
+
});
|
|
6589
6921
|
}
|
|
6590
6922
|
e.stopPropagation();
|
|
6591
6923
|
}
|
|
@@ -6724,7 +7056,10 @@ class FigInputGradient extends HTMLElement {
|
|
|
6724
7056
|
e.stopPropagation();
|
|
6725
7057
|
|
|
6726
7058
|
const trackRect = this.#track.getBoundingClientRect();
|
|
6727
|
-
const pct = Math.max(
|
|
7059
|
+
const pct = Math.max(
|
|
7060
|
+
0,
|
|
7061
|
+
Math.min(1, (e.clientX - trackRect.left) / trackRect.width),
|
|
7062
|
+
);
|
|
6728
7063
|
const position = Math.round(pct * 100);
|
|
6729
7064
|
const color = this.#sampleGradientColor(pct);
|
|
6730
7065
|
this.#gradient.stops.push({ position, color, opacity: 100 });
|
|
@@ -6743,19 +7078,23 @@ class FigInputGradient extends HTMLElement {
|
|
|
6743
7078
|
);
|
|
6744
7079
|
const newHandle = handles[newIndex];
|
|
6745
7080
|
if (newHandle) {
|
|
6746
|
-
this.#track
|
|
6747
|
-
|
|
6748
|
-
|
|
7081
|
+
this.#track
|
|
7082
|
+
.querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)")
|
|
7083
|
+
.forEach((h) => {
|
|
7084
|
+
if (h !== newHandle) h.deselect();
|
|
7085
|
+
});
|
|
6749
7086
|
newHandle.select();
|
|
6750
|
-
newHandle.dispatchEvent(
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
7087
|
+
newHandle.dispatchEvent(
|
|
7088
|
+
new PointerEvent("pointerdown", {
|
|
7089
|
+
bubbles: true,
|
|
7090
|
+
clientX: e.clientX,
|
|
7091
|
+
clientY: e.clientY,
|
|
7092
|
+
pointerId: e.pointerId,
|
|
7093
|
+
pointerType: e.pointerType,
|
|
7094
|
+
button: e.button,
|
|
7095
|
+
buttons: e.buttons,
|
|
7096
|
+
}),
|
|
7097
|
+
);
|
|
6759
7098
|
}
|
|
6760
7099
|
});
|
|
6761
7100
|
|
|
@@ -6770,7 +7109,10 @@ class FigInputGradient extends HTMLElement {
|
|
|
6770
7109
|
if (e.detail.opacity !== undefined) {
|
|
6771
7110
|
this.#gradient.stops[idx].opacity = e.detail.opacity;
|
|
6772
7111
|
}
|
|
6773
|
-
handle.setAttribute(
|
|
7112
|
+
handle.setAttribute(
|
|
7113
|
+
"color",
|
|
7114
|
+
this.#stopColorCSS(this.#gradient.stops[idx]),
|
|
7115
|
+
);
|
|
6774
7116
|
this.#syncChit();
|
|
6775
7117
|
this.#emitInput();
|
|
6776
7118
|
}
|
|
@@ -6785,7 +7127,9 @@ class FigInputGradient extends HTMLElement {
|
|
|
6785
7127
|
let position = rawPosition;
|
|
6786
7128
|
const trackW = this.#track.getBoundingClientRect().width;
|
|
6787
7129
|
if (e.detail?.shiftKey) {
|
|
6788
|
-
position =
|
|
7130
|
+
position =
|
|
7131
|
+
Math.round(position / FigInputGradient.SHIFT_SNAP) *
|
|
7132
|
+
FigInputGradient.SHIFT_SNAP;
|
|
6789
7133
|
} else {
|
|
6790
7134
|
const snapPct = trackW > 0 ? (5 / trackW) * 100 : 0;
|
|
6791
7135
|
for (let i = 0; i < this.#gradient.stops.length; i++) {
|
|
@@ -6825,7 +7169,10 @@ class FigInputGradient extends HTMLElement {
|
|
|
6825
7169
|
if (e.detail.opacity !== undefined) {
|
|
6826
7170
|
this.#gradient.stops[idx].opacity = e.detail.opacity;
|
|
6827
7171
|
}
|
|
6828
|
-
handle.setAttribute(
|
|
7172
|
+
handle.setAttribute(
|
|
7173
|
+
"color",
|
|
7174
|
+
this.#stopColorCSS(this.#gradient.stops[idx]),
|
|
7175
|
+
);
|
|
6829
7176
|
this.#syncChit();
|
|
6830
7177
|
this.#emitChange();
|
|
6831
7178
|
}
|
|
@@ -7578,13 +7925,13 @@ class FigChit extends HTMLElement {
|
|
|
7578
7925
|
|
|
7579
7926
|
if (this.#type === "color") {
|
|
7580
7927
|
const hex = this.#toHex(bg);
|
|
7581
|
-
this.innerHTML = `<input type="color" value="${hex}" />`;
|
|
7928
|
+
this.innerHTML = `<div></div><input type="color" value="${hex}" />`;
|
|
7582
7929
|
this.input = this.querySelector("input");
|
|
7583
7930
|
if (!isVar) {
|
|
7584
7931
|
this.input.addEventListener("input", this.#boundHandleInput);
|
|
7585
7932
|
}
|
|
7586
7933
|
} else {
|
|
7587
|
-
this.innerHTML = "";
|
|
7934
|
+
this.innerHTML = "<div></div>";
|
|
7588
7935
|
this.input = null;
|
|
7589
7936
|
}
|
|
7590
7937
|
} else if (this.#type === "color" && this.input) {
|
|
@@ -7594,8 +7941,14 @@ class FigChit extends HTMLElement {
|
|
|
7594
7941
|
}
|
|
7595
7942
|
}
|
|
7596
7943
|
|
|
7597
|
-
const isImage =
|
|
7598
|
-
|
|
7944
|
+
const isImage =
|
|
7945
|
+
/^(linear-gradient|radial-gradient|conic-gradient|repeating-|url)\s*\(/i.test(
|
|
7946
|
+
rawBg,
|
|
7947
|
+
);
|
|
7948
|
+
this.style.setProperty(
|
|
7949
|
+
"--chit-background",
|
|
7950
|
+
isImage ? rawBg : `linear-gradient(${rawBg}, ${rawBg})`,
|
|
7951
|
+
);
|
|
7599
7952
|
}
|
|
7600
7953
|
|
|
7601
7954
|
#handleInput(e) {
|
|
@@ -7622,8 +7975,14 @@ class FigChit extends HTMLElement {
|
|
|
7622
7975
|
if (oldValue === newValue) return;
|
|
7623
7976
|
if (name === "background") {
|
|
7624
7977
|
if (this.#internalUpdate) {
|
|
7625
|
-
const isImg =
|
|
7626
|
-
|
|
7978
|
+
const isImg =
|
|
7979
|
+
/^(linear-gradient|radial-gradient|conic-gradient|repeating-|url)\s*\(/i.test(
|
|
7980
|
+
newValue,
|
|
7981
|
+
);
|
|
7982
|
+
this.style.setProperty(
|
|
7983
|
+
"--chit-background",
|
|
7984
|
+
isImg ? newValue : `linear-gradient(${newValue}, ${newValue})`,
|
|
7985
|
+
);
|
|
7627
7986
|
return;
|
|
7628
7987
|
}
|
|
7629
7988
|
this.#render();
|
|
@@ -7645,9 +8004,7 @@ class FigChit extends HTMLElement {
|
|
|
7645
8004
|
}
|
|
7646
8005
|
}
|
|
7647
8006
|
customElements.define("fig-chit", FigChit);
|
|
7648
|
-
class FigSwatch extends FigChit{
|
|
7649
|
-
|
|
7650
|
-
}
|
|
8007
|
+
class FigSwatch extends FigChit {}
|
|
7651
8008
|
customElements.define("fig-swatch", FigSwatch);
|
|
7652
8009
|
|
|
7653
8010
|
/* Upload */
|
|
@@ -7905,6 +8262,220 @@ class FigImage extends HTMLElement {
|
|
|
7905
8262
|
}
|
|
7906
8263
|
customElements.define("fig-image", FigImage);
|
|
7907
8264
|
|
|
8265
|
+
/* File Upload Input */
|
|
8266
|
+
class FigInputFile extends HTMLElement {
|
|
8267
|
+
static observedAttributes = ["accepts", "label", "disabled", "multiple"];
|
|
8268
|
+
|
|
8269
|
+
#fileInput = null;
|
|
8270
|
+
#filenameEl = null;
|
|
8271
|
+
#clearBtn = null;
|
|
8272
|
+
#tooltipEl = null;
|
|
8273
|
+
#uploadBtn = null;
|
|
8274
|
+
#files = null;
|
|
8275
|
+
|
|
8276
|
+
get files() {
|
|
8277
|
+
return this.#files;
|
|
8278
|
+
}
|
|
8279
|
+
|
|
8280
|
+
get value() {
|
|
8281
|
+
if (!this.#files || this.#files.length === 0) return "";
|
|
8282
|
+
if (this.#files.length === 1) return this.#files[0].name;
|
|
8283
|
+
return `${this.#files.length} files`;
|
|
8284
|
+
}
|
|
8285
|
+
|
|
8286
|
+
connectedCallback() {
|
|
8287
|
+
this.#render();
|
|
8288
|
+
this.addEventListener("dragover", this.#onDragOver);
|
|
8289
|
+
this.addEventListener("dragleave", this.#onDragLeave);
|
|
8290
|
+
this.addEventListener("drop", this.#onDrop);
|
|
8291
|
+
}
|
|
8292
|
+
|
|
8293
|
+
disconnectedCallback() {
|
|
8294
|
+
this.#fileInput?.removeEventListener("change", this.#onFileChange);
|
|
8295
|
+
this.#clearBtn?.removeEventListener("click", this.#onClear);
|
|
8296
|
+
this.removeEventListener("dragover", this.#onDragOver);
|
|
8297
|
+
this.removeEventListener("dragleave", this.#onDragLeave);
|
|
8298
|
+
this.removeEventListener("drop", this.#onDrop);
|
|
8299
|
+
}
|
|
8300
|
+
|
|
8301
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
8302
|
+
if (oldValue === newValue) return;
|
|
8303
|
+
this.#render();
|
|
8304
|
+
}
|
|
8305
|
+
|
|
8306
|
+
clear() {
|
|
8307
|
+
this.#files = null;
|
|
8308
|
+
if (this.#fileInput) this.#fileInput.value = "";
|
|
8309
|
+
this.#render();
|
|
8310
|
+
this.#emitEvents();
|
|
8311
|
+
}
|
|
8312
|
+
|
|
8313
|
+
#emitEvents() {
|
|
8314
|
+
const detail = { files: this.#files };
|
|
8315
|
+
this.dispatchEvent(new CustomEvent("input", { detail, bubbles: true }));
|
|
8316
|
+
this.dispatchEvent(new CustomEvent("change", { detail, bubbles: true }));
|
|
8317
|
+
}
|
|
8318
|
+
|
|
8319
|
+
#onFileChange = () => {
|
|
8320
|
+
if (this.#fileInput.files.length > 0) {
|
|
8321
|
+
this.#files = this.#fileInput.files;
|
|
8322
|
+
this.#render();
|
|
8323
|
+
this.#emitEvents();
|
|
8324
|
+
}
|
|
8325
|
+
};
|
|
8326
|
+
|
|
8327
|
+
#onClear = (e) => {
|
|
8328
|
+
e.stopPropagation();
|
|
8329
|
+
this.clear();
|
|
8330
|
+
};
|
|
8331
|
+
|
|
8332
|
+
#onDragOver = (e) => {
|
|
8333
|
+
e.preventDefault();
|
|
8334
|
+
this.setAttribute("dragover", "");
|
|
8335
|
+
};
|
|
8336
|
+
|
|
8337
|
+
#onDragLeave = () => {
|
|
8338
|
+
this.removeAttribute("dragover");
|
|
8339
|
+
};
|
|
8340
|
+
|
|
8341
|
+
#onDrop = (e) => {
|
|
8342
|
+
e.preventDefault();
|
|
8343
|
+
this.removeAttribute("dragover");
|
|
8344
|
+
if (
|
|
8345
|
+
this.hasAttribute("disabled") &&
|
|
8346
|
+
this.getAttribute("disabled") !== "false"
|
|
8347
|
+
)
|
|
8348
|
+
return;
|
|
8349
|
+
|
|
8350
|
+
const accepts = this.getAttribute("accepts");
|
|
8351
|
+
let dropped = Array.from(e.dataTransfer.files);
|
|
8352
|
+
if (accepts) {
|
|
8353
|
+
const allowed = accepts.split(",").map((s) => s.trim().toLowerCase());
|
|
8354
|
+
dropped = dropped.filter((file) => {
|
|
8355
|
+
const ext = "." + file.name.split(".").pop().toLowerCase();
|
|
8356
|
+
const mime = file.type.toLowerCase();
|
|
8357
|
+
return allowed.some(
|
|
8358
|
+
(a) =>
|
|
8359
|
+
a === ext ||
|
|
8360
|
+
a === mime ||
|
|
8361
|
+
(a.endsWith("/*") && mime.startsWith(a.slice(0, -1))),
|
|
8362
|
+
);
|
|
8363
|
+
});
|
|
8364
|
+
}
|
|
8365
|
+
if (!this.hasAttribute("multiple")) {
|
|
8366
|
+
dropped = dropped.slice(0, 1);
|
|
8367
|
+
}
|
|
8368
|
+
if (dropped.length === 0) return;
|
|
8369
|
+
|
|
8370
|
+
const dt = new DataTransfer();
|
|
8371
|
+
dropped.forEach((f) => dt.items.add(f));
|
|
8372
|
+
this.#files = dt.files;
|
|
8373
|
+
if (this.#fileInput) {
|
|
8374
|
+
this.#fileInput.files = dt.files;
|
|
8375
|
+
}
|
|
8376
|
+
this.#render();
|
|
8377
|
+
this.#emitEvents();
|
|
8378
|
+
};
|
|
8379
|
+
|
|
8380
|
+
#render() {
|
|
8381
|
+
const accepts = this.getAttribute("accepts") || "";
|
|
8382
|
+
const label = this.getAttribute("label") || "Upload";
|
|
8383
|
+
const disabled =
|
|
8384
|
+
this.hasAttribute("disabled") &&
|
|
8385
|
+
this.getAttribute("disabled") !== "false";
|
|
8386
|
+
const multiple = this.hasAttribute("multiple");
|
|
8387
|
+
const hasFile = this.#files && this.#files.length > 0;
|
|
8388
|
+
|
|
8389
|
+
this.innerHTML = "";
|
|
8390
|
+
|
|
8391
|
+
if (hasFile) {
|
|
8392
|
+
const tooltipText = accepts
|
|
8393
|
+
? `Accepts ${accepts
|
|
8394
|
+
.split(",")
|
|
8395
|
+
.map((s) => s.trim())
|
|
8396
|
+
.join(", ")}`
|
|
8397
|
+
: "";
|
|
8398
|
+
|
|
8399
|
+
this.#uploadBtn = document.createElement("fig-button");
|
|
8400
|
+
this.#uploadBtn.setAttribute("variant", "input");
|
|
8401
|
+
this.#uploadBtn.setAttribute("type", "upload");
|
|
8402
|
+
this.#uploadBtn.className = "fig-input-file-filename";
|
|
8403
|
+
if (disabled) this.#uploadBtn.setAttribute("disabled", "");
|
|
8404
|
+
const truncEl = document.createElement("fig-truncate");
|
|
8405
|
+
truncEl.setAttribute("position", "middle");
|
|
8406
|
+
truncEl.setAttribute("tooltip", "");
|
|
8407
|
+
const filename = this.value;
|
|
8408
|
+
const dotIdx = filename.lastIndexOf(".");
|
|
8409
|
+
if (dotIdx > 0) truncEl.setAttribute("tail", filename.slice(dotIdx));
|
|
8410
|
+
truncEl.textContent = filename;
|
|
8411
|
+
this.#uploadBtn.appendChild(truncEl);
|
|
8412
|
+
|
|
8413
|
+
this.#fileInput = document.createElement("input");
|
|
8414
|
+
this.#fileInput.type = "file";
|
|
8415
|
+
this.#fileInput.title = "";
|
|
8416
|
+
if (accepts) this.#fileInput.setAttribute("accept", accepts);
|
|
8417
|
+
if (multiple) this.#fileInput.setAttribute("multiple", "");
|
|
8418
|
+
this.#fileInput.addEventListener("change", this.#onFileChange);
|
|
8419
|
+
this.#uploadBtn.appendChild(this.#fileInput);
|
|
8420
|
+
|
|
8421
|
+
if (tooltipText) {
|
|
8422
|
+
this.#tooltipEl = document.createElement("fig-tooltip");
|
|
8423
|
+
this.#tooltipEl.setAttribute("text", tooltipText);
|
|
8424
|
+
this.#tooltipEl.appendChild(this.#uploadBtn);
|
|
8425
|
+
this.appendChild(this.#tooltipEl);
|
|
8426
|
+
} else {
|
|
8427
|
+
this.appendChild(this.#uploadBtn);
|
|
8428
|
+
}
|
|
8429
|
+
|
|
8430
|
+
const clearTooltip = document.createElement("fig-tooltip");
|
|
8431
|
+
clearTooltip.setAttribute("text", "Remove");
|
|
8432
|
+
this.#clearBtn = document.createElement("fig-button");
|
|
8433
|
+
this.#clearBtn.setAttribute("variant", "ghost");
|
|
8434
|
+
this.#clearBtn.setAttribute("icon", "true");
|
|
8435
|
+
this.#clearBtn.className = "fig-input-file-clear";
|
|
8436
|
+
if (disabled) this.#clearBtn.setAttribute("disabled", "");
|
|
8437
|
+
this.#clearBtn.innerHTML = `<span class="fig-mask-icon" style="--icon: var(--icon-minus);"></span>`;
|
|
8438
|
+
this.#clearBtn.addEventListener("click", this.#onClear);
|
|
8439
|
+
clearTooltip.appendChild(this.#clearBtn);
|
|
8440
|
+
this.appendChild(clearTooltip);
|
|
8441
|
+
} else {
|
|
8442
|
+
const tooltipText = accepts
|
|
8443
|
+
? `Accepts ${accepts
|
|
8444
|
+
.split(",")
|
|
8445
|
+
.map((s) => s.trim())
|
|
8446
|
+
.join(", ")}`
|
|
8447
|
+
: "";
|
|
8448
|
+
|
|
8449
|
+
if (tooltipText) {
|
|
8450
|
+
this.#tooltipEl = document.createElement("fig-tooltip");
|
|
8451
|
+
this.#tooltipEl.setAttribute("text", tooltipText);
|
|
8452
|
+
}
|
|
8453
|
+
|
|
8454
|
+
this.#uploadBtn = document.createElement("fig-button");
|
|
8455
|
+
this.#uploadBtn.setAttribute("variant", "input");
|
|
8456
|
+
this.#uploadBtn.setAttribute("type", "upload");
|
|
8457
|
+
this.#uploadBtn.textContent = label;
|
|
8458
|
+
if (disabled) this.#uploadBtn.setAttribute("disabled", "");
|
|
8459
|
+
|
|
8460
|
+
this.#fileInput = document.createElement("input");
|
|
8461
|
+
this.#fileInput.type = "file";
|
|
8462
|
+
this.#fileInput.title = "";
|
|
8463
|
+
if (accepts) this.#fileInput.setAttribute("accept", accepts);
|
|
8464
|
+
if (multiple) this.#fileInput.setAttribute("multiple", "");
|
|
8465
|
+
this.#fileInput.addEventListener("change", this.#onFileChange);
|
|
8466
|
+
this.#uploadBtn.appendChild(this.#fileInput);
|
|
8467
|
+
|
|
8468
|
+
if (this.#tooltipEl) {
|
|
8469
|
+
this.#tooltipEl.appendChild(this.#uploadBtn);
|
|
8470
|
+
this.appendChild(this.#tooltipEl);
|
|
8471
|
+
} else {
|
|
8472
|
+
this.appendChild(this.#uploadBtn);
|
|
8473
|
+
}
|
|
8474
|
+
}
|
|
8475
|
+
}
|
|
8476
|
+
}
|
|
8477
|
+
customElements.define("fig-input-file", FigInputFile);
|
|
8478
|
+
|
|
7908
8479
|
/**
|
|
7909
8480
|
* A bezier / spring easing curve editor with draggable control points.
|
|
7910
8481
|
* @attr {string} value - Bezier: "0.42, 0, 0.58, 1" or Spring: "spring(200, 15, 1)"
|
|
@@ -8828,7 +9399,11 @@ class Fig3DRotate extends HTMLElement {
|
|
|
8828
9399
|
this.#precision = parseInt(this.getAttribute("precision") || "1");
|
|
8829
9400
|
figSyncCssVar(this, "--aspect-ratio", this.getAttribute("aspect-ratio"));
|
|
8830
9401
|
figSyncCssVar(this, "--perspective", this.getAttribute("perspective"));
|
|
8831
|
-
figSyncCssVar(
|
|
9402
|
+
figSyncCssVar(
|
|
9403
|
+
this,
|
|
9404
|
+
"--perspective-origin",
|
|
9405
|
+
this.getAttribute("perspective-origin"),
|
|
9406
|
+
);
|
|
8832
9407
|
this.#syncTransformOrigin(this.getAttribute("transform-origin"));
|
|
8833
9408
|
this.#parseFields(this.getAttribute("fields"));
|
|
8834
9409
|
const val = this.getAttribute("value");
|
|
@@ -8846,8 +9421,6 @@ class Fig3DRotate extends HTMLElement {
|
|
|
8846
9421
|
}
|
|
8847
9422
|
}
|
|
8848
9423
|
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
9424
|
#syncTransformOrigin(value) {
|
|
8852
9425
|
if (!value || !value.trim()) {
|
|
8853
9426
|
this.style.removeProperty("--transform-origin");
|
|
@@ -10682,6 +11255,117 @@ class FigLayer extends HTMLElement {
|
|
|
10682
11255
|
}
|
|
10683
11256
|
customElements.define("fig-layer", FigLayer);
|
|
10684
11257
|
|
|
11258
|
+
// FigGroup
|
|
11259
|
+
class FigGroup extends HTMLElement {
|
|
11260
|
+
static observedAttributes = ["name", "collapsible"];
|
|
11261
|
+
|
|
11262
|
+
#header = null;
|
|
11263
|
+
#chevron = null;
|
|
11264
|
+
|
|
11265
|
+
connectedCallback() {
|
|
11266
|
+
requestAnimationFrame(() => this.#render());
|
|
11267
|
+
}
|
|
11268
|
+
|
|
11269
|
+
disconnectedCallback() {
|
|
11270
|
+
if (this.#chevron) {
|
|
11271
|
+
this.#chevron.removeEventListener("click", this.#handleToggle);
|
|
11272
|
+
}
|
|
11273
|
+
if (this.#header) {
|
|
11274
|
+
this.#header.removeEventListener("click", this.#handleToggle);
|
|
11275
|
+
}
|
|
11276
|
+
}
|
|
11277
|
+
|
|
11278
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
11279
|
+
if (oldValue === newValue) return;
|
|
11280
|
+
this.#render();
|
|
11281
|
+
}
|
|
11282
|
+
|
|
11283
|
+
get open() {
|
|
11284
|
+
const attr = this.getAttribute("open");
|
|
11285
|
+
return attr !== null && attr !== "false";
|
|
11286
|
+
}
|
|
11287
|
+
|
|
11288
|
+
set open(value) {
|
|
11289
|
+
const was = this.open;
|
|
11290
|
+
if (value) {
|
|
11291
|
+
this.setAttribute("open", "true");
|
|
11292
|
+
} else {
|
|
11293
|
+
this.setAttribute("open", "false");
|
|
11294
|
+
}
|
|
11295
|
+
if (was !== !!value) {
|
|
11296
|
+
this.dispatchEvent(
|
|
11297
|
+
new CustomEvent("openchange", {
|
|
11298
|
+
detail: { open: !!value },
|
|
11299
|
+
bubbles: true,
|
|
11300
|
+
}),
|
|
11301
|
+
);
|
|
11302
|
+
}
|
|
11303
|
+
}
|
|
11304
|
+
|
|
11305
|
+
#handleToggle = (e) => {
|
|
11306
|
+
e.stopPropagation();
|
|
11307
|
+
this.open = !this.open;
|
|
11308
|
+
};
|
|
11309
|
+
|
|
11310
|
+
#render() {
|
|
11311
|
+
const isCollapsible = this.hasAttribute("collapsible");
|
|
11312
|
+
const nameAttr = this.getAttribute("name");
|
|
11313
|
+
const label = nameAttr || (isCollapsible ? "Group" : null);
|
|
11314
|
+
|
|
11315
|
+
// Check if user supplied their own fig-header
|
|
11316
|
+
const userHeader = this.querySelector(":scope > fig-header");
|
|
11317
|
+
|
|
11318
|
+
if (!label && !isCollapsible && !userHeader) {
|
|
11319
|
+
if (this.#header && this.#header.dataset.generated) {
|
|
11320
|
+
this.#header.remove();
|
|
11321
|
+
this.#header = null;
|
|
11322
|
+
this.#chevron = null;
|
|
11323
|
+
}
|
|
11324
|
+
return;
|
|
11325
|
+
}
|
|
11326
|
+
|
|
11327
|
+
if (userHeader) {
|
|
11328
|
+
this.#header = userHeader;
|
|
11329
|
+
} else if (!this.#header || !this.#header.dataset.generated) {
|
|
11330
|
+
this.#header = document.createElement("fig-header");
|
|
11331
|
+
this.#header.setAttribute("borderless", "");
|
|
11332
|
+
this.#header.dataset.generated = "true";
|
|
11333
|
+
this.prepend(this.#header);
|
|
11334
|
+
}
|
|
11335
|
+
|
|
11336
|
+
// Ensure h3 exists inside header
|
|
11337
|
+
let h3 = this.#header.querySelector("h3");
|
|
11338
|
+
if (!h3) {
|
|
11339
|
+
h3 = document.createElement("h3");
|
|
11340
|
+
this.#header.prepend(h3);
|
|
11341
|
+
}
|
|
11342
|
+
if (this.#header.dataset.generated) {
|
|
11343
|
+
h3.textContent = label;
|
|
11344
|
+
}
|
|
11345
|
+
|
|
11346
|
+
if (isCollapsible) {
|
|
11347
|
+
if (!h3.querySelector(".fig-group-chevron")) {
|
|
11348
|
+
const chevron = document.createElement("span");
|
|
11349
|
+
chevron.className = "fig-mask-icon fig-group-chevron";
|
|
11350
|
+
h3.prepend(chevron);
|
|
11351
|
+
}
|
|
11352
|
+
this.#chevron = h3.querySelector(".fig-group-chevron");
|
|
11353
|
+
h3.addEventListener("click", this.#handleToggle);
|
|
11354
|
+
|
|
11355
|
+
if (!this.hasAttribute("open")) {
|
|
11356
|
+
this.setAttribute("open", "false");
|
|
11357
|
+
}
|
|
11358
|
+
} else {
|
|
11359
|
+
if (this.#chevron) {
|
|
11360
|
+
this.#chevron.remove();
|
|
11361
|
+
this.#chevron = null;
|
|
11362
|
+
}
|
|
11363
|
+
this.removeAttribute("open");
|
|
11364
|
+
}
|
|
11365
|
+
}
|
|
11366
|
+
}
|
|
11367
|
+
customElements.define("fig-group", FigGroup);
|
|
11368
|
+
|
|
10685
11369
|
// FigFillPicker
|
|
10686
11370
|
/**
|
|
10687
11371
|
* A comprehensive fill picker component supporting solid colors, gradients, images, video, and webcam.
|
|
@@ -11114,10 +11798,15 @@ class FigFillPicker extends HTMLElement {
|
|
|
11114
11798
|
this.#dialog.addEventListener("close", onDialogClose);
|
|
11115
11799
|
|
|
11116
11800
|
this.#dialogOpenObserver = new MutationObserver(() => {
|
|
11117
|
-
const isOpen =
|
|
11801
|
+
const isOpen =
|
|
11802
|
+
this.#dialog.hasAttribute("open") &&
|
|
11803
|
+
this.#dialog.getAttribute("open") !== "false";
|
|
11118
11804
|
if (!isOpen) onDialogClose();
|
|
11119
11805
|
});
|
|
11120
|
-
this.#dialogOpenObserver.observe(this.#dialog, {
|
|
11806
|
+
this.#dialogOpenObserver.observe(this.#dialog, {
|
|
11807
|
+
attributes: true,
|
|
11808
|
+
attributeFilter: ["open"],
|
|
11809
|
+
});
|
|
11121
11810
|
|
|
11122
11811
|
// Initialize built-in tabs (skip any overridden by custom slots)
|
|
11123
11812
|
const builtinInits = {
|
|
@@ -11213,6 +11902,8 @@ class FigFillPicker extends HTMLElement {
|
|
|
11213
11902
|
<fig-preview class="fig-fill-picker-color-area">
|
|
11214
11903
|
<canvas width="200" height="200"></canvas>
|
|
11215
11904
|
<fig-handle
|
|
11905
|
+
type="color"
|
|
11906
|
+
color="${this.#hsvToHex({ ...this.#color, a: 1 })}"
|
|
11216
11907
|
drag
|
|
11217
11908
|
drag-surface=".fig-fill-picker-color-area"
|
|
11218
11909
|
drag-axes="x,y"
|
|
@@ -11875,7 +12566,9 @@ class FigFillPicker extends HTMLElement {
|
|
|
11875
12566
|
});
|
|
11876
12567
|
|
|
11877
12568
|
// Embedded gradient bar input
|
|
11878
|
-
const gradientBarInput = container.querySelector(
|
|
12569
|
+
const gradientBarInput = container.querySelector(
|
|
12570
|
+
".fig-fill-picker-gradient-bar-input",
|
|
12571
|
+
);
|
|
11879
12572
|
if (gradientBarInput) {
|
|
11880
12573
|
const syncFromBarInput = (e) => {
|
|
11881
12574
|
e.stopPropagation();
|
|
@@ -11948,7 +12641,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
11948
12641
|
if (barInput) {
|
|
11949
12642
|
barInput.setAttribute(
|
|
11950
12643
|
"value",
|
|
11951
|
-
JSON.stringify({
|
|
12644
|
+
JSON.stringify({
|
|
12645
|
+
type: "gradient",
|
|
12646
|
+
gradient: gradientToValueShape(this.#gradient),
|
|
12647
|
+
}),
|
|
11952
12648
|
);
|
|
11953
12649
|
}
|
|
11954
12650
|
|
|
@@ -11977,7 +12673,8 @@ class FigFillPicker extends HTMLElement {
|
|
|
11977
12673
|
if (colorInput) colorInput.setAttribute("value", stop.color);
|
|
11978
12674
|
const removeBtn = row.querySelector(".fig-fill-picker-stop-remove");
|
|
11979
12675
|
if (removeBtn) {
|
|
11980
|
-
if (this.#gradient.stops.length <= 2)
|
|
12676
|
+
if (this.#gradient.stops.length <= 2)
|
|
12677
|
+
removeBtn.setAttribute("disabled", "");
|
|
11981
12678
|
else removeBtn.removeAttribute("disabled");
|
|
11982
12679
|
}
|
|
11983
12680
|
});
|
|
@@ -12910,9 +13607,14 @@ class FigColorTip extends HTMLElement {
|
|
|
12910
13607
|
const color = this.#normalizeColor(rawValue);
|
|
12911
13608
|
const alpha = this.#extractAlpha(rawValue);
|
|
12912
13609
|
const alphaAttr = this.#alphaEnabled ? "" : 'alpha="false"';
|
|
12913
|
-
const pickerValue =
|
|
12914
|
-
|
|
12915
|
-
|
|
13610
|
+
const pickerValue =
|
|
13611
|
+
alpha < 1
|
|
13612
|
+
? JSON.stringify({
|
|
13613
|
+
type: "solid",
|
|
13614
|
+
color,
|
|
13615
|
+
opacity: Math.round(alpha * 100),
|
|
13616
|
+
})
|
|
13617
|
+
: JSON.stringify({ type: "solid", color });
|
|
12916
13618
|
this.innerHTML = `
|
|
12917
13619
|
<fig-fill-picker mode="solid" ${alphaAttr} value='${pickerValue}'>
|
|
12918
13620
|
<fig-chit background="${color}"></fig-chit>
|
|
@@ -12955,7 +13657,9 @@ class FigColorTip extends HTMLElement {
|
|
|
12955
13657
|
if (v.startsWith("#") && v.length === 9) {
|
|
12956
13658
|
return parseInt(v.slice(7, 9), 16) / 255;
|
|
12957
13659
|
}
|
|
12958
|
-
const rgbaMatch = v.match(
|
|
13660
|
+
const rgbaMatch = v.match(
|
|
13661
|
+
/rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)/i,
|
|
13662
|
+
);
|
|
12959
13663
|
if (rgbaMatch) return parseFloat(rgbaMatch[1]);
|
|
12960
13664
|
return 1;
|
|
12961
13665
|
}
|
|
@@ -13012,9 +13716,10 @@ class FigColorTip extends HTMLElement {
|
|
|
13012
13716
|
}
|
|
13013
13717
|
|
|
13014
13718
|
if (this.#fillPicker) {
|
|
13015
|
-
const pickerVal =
|
|
13016
|
-
|
|
13017
|
-
|
|
13719
|
+
const pickerVal =
|
|
13720
|
+
alpha < 1
|
|
13721
|
+
? { type: "solid", color, opacity: Math.round(alpha * 100) }
|
|
13722
|
+
: { type: "solid", color };
|
|
13018
13723
|
this.#fillPicker.setAttribute("value", JSON.stringify(pickerVal));
|
|
13019
13724
|
if (this.#alphaEnabled) {
|
|
13020
13725
|
this.#fillPicker.removeAttribute("alpha");
|
|
@@ -13192,7 +13897,15 @@ class FigChooser extends HTMLElement {
|
|
|
13192
13897
|
}
|
|
13193
13898
|
|
|
13194
13899
|
static get observedAttributes() {
|
|
13195
|
-
return [
|
|
13900
|
+
return [
|
|
13901
|
+
"value",
|
|
13902
|
+
"disabled",
|
|
13903
|
+
"choice-element",
|
|
13904
|
+
"drag",
|
|
13905
|
+
"overflow",
|
|
13906
|
+
"loop",
|
|
13907
|
+
"padding",
|
|
13908
|
+
];
|
|
13196
13909
|
}
|
|
13197
13910
|
|
|
13198
13911
|
get #overflowMode() {
|
|
@@ -13818,7 +14531,13 @@ class FigCanvasControl extends HTMLElement {
|
|
|
13818
14531
|
|
|
13819
14532
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
13820
14533
|
if (oldVal === newVal) return;
|
|
13821
|
-
if (
|
|
14534
|
+
if (
|
|
14535
|
+
name === "value" &&
|
|
14536
|
+
!this.#isDragging &&
|
|
14537
|
+
!this.#isSecondDragging &&
|
|
14538
|
+
!this.#isRadiusDragging &&
|
|
14539
|
+
!this.#isAngleDragging
|
|
14540
|
+
) {
|
|
13822
14541
|
this.#parseValue();
|
|
13823
14542
|
if (this.#pointHandle) this.#syncPositions();
|
|
13824
14543
|
else this.#render();
|
|
@@ -13839,11 +14558,14 @@ class FigCanvasControl extends HTMLElement {
|
|
|
13839
14558
|
}
|
|
13840
14559
|
if (name === "snapping" && this.#pointHandle) {
|
|
13841
14560
|
this.#pointHandle.setAttribute("drag-snapping", newVal || "false");
|
|
13842
|
-
if (this.#secondHandle)
|
|
14561
|
+
if (this.#secondHandle)
|
|
14562
|
+
this.#secondHandle.setAttribute("drag-snapping", newVal || "false");
|
|
13843
14563
|
}
|
|
13844
14564
|
if (name === "name") {
|
|
13845
|
-
if (this.#pointTooltip)
|
|
13846
|
-
|
|
14565
|
+
if (this.#pointTooltip)
|
|
14566
|
+
this.#pointTooltip.setAttribute("text", this.#pointTipText);
|
|
14567
|
+
if (this.#secondTooltip)
|
|
14568
|
+
this.#secondTooltip.setAttribute("text", this.#secondTipText);
|
|
13847
14569
|
}
|
|
13848
14570
|
}
|
|
13849
14571
|
|
|
@@ -13868,13 +14590,16 @@ class FigCanvasControl extends HTMLElement {
|
|
|
13868
14590
|
if (typeof v.angle === "number") this.#angle = v.angle;
|
|
13869
14591
|
if (typeof v.x2 === "number") this.#x2 = v.x2;
|
|
13870
14592
|
if (typeof v.y2 === "number") this.#y2 = v.y2;
|
|
13871
|
-
} catch {
|
|
14593
|
+
} catch {
|
|
14594
|
+
/* ignore */
|
|
14595
|
+
}
|
|
13872
14596
|
}
|
|
13873
14597
|
|
|
13874
14598
|
get value() {
|
|
13875
14599
|
const v = { x: this.#x, y: this.#y };
|
|
13876
14600
|
if (this.#type === "color") {
|
|
13877
|
-
const color =
|
|
14601
|
+
const color =
|
|
14602
|
+
this.getAttribute("color") || this.#pointHandle?.getAttribute("color");
|
|
13878
14603
|
if (color) v.color = color;
|
|
13879
14604
|
}
|
|
13880
14605
|
if (this.#hasRadius) {
|
|
@@ -14175,11 +14900,15 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14175
14900
|
}
|
|
14176
14901
|
|
|
14177
14902
|
#emitInput() {
|
|
14178
|
-
this.dispatchEvent(
|
|
14903
|
+
this.dispatchEvent(
|
|
14904
|
+
new CustomEvent("input", { bubbles: true, detail: this.value }),
|
|
14905
|
+
);
|
|
14179
14906
|
}
|
|
14180
14907
|
|
|
14181
14908
|
#emitChange() {
|
|
14182
|
-
this.dispatchEvent(
|
|
14909
|
+
this.dispatchEvent(
|
|
14910
|
+
new CustomEvent("change", { bubbles: true, detail: this.value }),
|
|
14911
|
+
);
|
|
14183
14912
|
}
|
|
14184
14913
|
|
|
14185
14914
|
#syncValueAttribute() {
|
|
@@ -14211,7 +14940,9 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14211
14940
|
}
|
|
14212
14941
|
this.#syncPositions();
|
|
14213
14942
|
if (this.#hasSecondPoint) {
|
|
14214
|
-
document.body.style.cursor = this.#resizeCursorSvg(
|
|
14943
|
+
document.body.style.cursor = this.#resizeCursorSvg(
|
|
14944
|
+
this.#pointPointLineDeg(),
|
|
14945
|
+
);
|
|
14215
14946
|
}
|
|
14216
14947
|
this.#emitInput();
|
|
14217
14948
|
});
|
|
@@ -14227,14 +14958,17 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14227
14958
|
const py = e.detail?.py ?? this.#y / 100;
|
|
14228
14959
|
this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
|
|
14229
14960
|
this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
|
|
14230
|
-
if (this.#pointTooltip && this.#type !== "color")
|
|
14961
|
+
if (this.#pointTooltip && this.#type !== "color")
|
|
14962
|
+
this.#pointTooltip.removeAttribute("show");
|
|
14231
14963
|
if (this.#hasSecondPoint) {
|
|
14232
14964
|
document.body.style.cursor = this.#prevBodyCursor ?? "";
|
|
14233
14965
|
}
|
|
14234
14966
|
this.#syncPositions();
|
|
14235
14967
|
this.#syncValueAttribute();
|
|
14236
14968
|
this.#emitChange();
|
|
14237
|
-
requestAnimationFrame(() => {
|
|
14969
|
+
requestAnimationFrame(() => {
|
|
14970
|
+
this.#isDragging = false;
|
|
14971
|
+
});
|
|
14238
14972
|
});
|
|
14239
14973
|
|
|
14240
14974
|
if (this.#angleHandle) {
|
|
@@ -14251,8 +14985,8 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14251
14985
|
const hy = e.detail?.y ?? 0;
|
|
14252
14986
|
const hw = this.#angleHandle.offsetWidth / 2;
|
|
14253
14987
|
const hh = this.#angleHandle.offsetHeight / 2;
|
|
14254
|
-
const dx =
|
|
14255
|
-
const dy =
|
|
14988
|
+
const dx = hx + hw - cx;
|
|
14989
|
+
const dy = hy + hh - cy;
|
|
14256
14990
|
let angle = (Math.atan2(dy, dx) * 180) / Math.PI;
|
|
14257
14991
|
if (this.#shouldSnap(e.detail?.shiftKey)) {
|
|
14258
14992
|
angle = Math.round(angle / 15) * 15;
|
|
@@ -14277,7 +15011,10 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14277
15011
|
}
|
|
14278
15012
|
|
|
14279
15013
|
if (this.#angleTooltip) {
|
|
14280
|
-
this.#angleTooltip.setAttribute(
|
|
15014
|
+
this.#angleTooltip.setAttribute(
|
|
15015
|
+
"text",
|
|
15016
|
+
`Angle ${Math.round(this.#angle)}°`,
|
|
15017
|
+
);
|
|
14281
15018
|
this.#angleTooltip.setAttribute("show", "true");
|
|
14282
15019
|
this.#angleTooltip.showPopup?.();
|
|
14283
15020
|
}
|
|
@@ -14292,7 +15029,9 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14292
15029
|
this.#syncPositions();
|
|
14293
15030
|
this.#syncValueAttribute();
|
|
14294
15031
|
this.#emitChange();
|
|
14295
|
-
requestAnimationFrame(() => {
|
|
15032
|
+
requestAnimationFrame(() => {
|
|
15033
|
+
this.#isAngleDragging = false;
|
|
15034
|
+
});
|
|
14296
15035
|
});
|
|
14297
15036
|
|
|
14298
15037
|
this.#angleHandle.addEventListener("hitareadown", (e) => {
|
|
@@ -14325,7 +15064,11 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14325
15064
|
angle = Math.round(angle / 15) * 15;
|
|
14326
15065
|
}
|
|
14327
15066
|
this.#angle = angle;
|
|
14328
|
-
if (this.#angleTooltip)
|
|
15067
|
+
if (this.#angleTooltip)
|
|
15068
|
+
this.#angleTooltip.setAttribute(
|
|
15069
|
+
"text",
|
|
15070
|
+
`Angle ${Math.round(angle)}°`,
|
|
15071
|
+
);
|
|
14329
15072
|
this.#syncPositions();
|
|
14330
15073
|
const curDeg = Math.round(angle);
|
|
14331
15074
|
if (curDeg !== lastCursorDeg) {
|
|
@@ -14368,7 +15111,9 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14368
15111
|
this.#secondTooltip.showPopup?.();
|
|
14369
15112
|
}
|
|
14370
15113
|
this.#syncPositions();
|
|
14371
|
-
document.body.style.cursor = this.#resizeCursorSvg(
|
|
15114
|
+
document.body.style.cursor = this.#resizeCursorSvg(
|
|
15115
|
+
this.#pointPointLineDeg(),
|
|
15116
|
+
);
|
|
14372
15117
|
this.#emitInput();
|
|
14373
15118
|
});
|
|
14374
15119
|
|
|
@@ -14379,7 +15124,9 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14379
15124
|
this.#syncPositions();
|
|
14380
15125
|
this.#syncValueAttribute();
|
|
14381
15126
|
this.#emitChange();
|
|
14382
|
-
requestAnimationFrame(() => {
|
|
15127
|
+
requestAnimationFrame(() => {
|
|
15128
|
+
this.#isSecondDragging = false;
|
|
15129
|
+
});
|
|
14383
15130
|
});
|
|
14384
15131
|
|
|
14385
15132
|
this.#setupPointPointHitArea(this.#pointHandle, true);
|
|
@@ -14443,7 +15190,9 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14443
15190
|
this.#y2 = newPctY;
|
|
14444
15191
|
}
|
|
14445
15192
|
this.#syncPositions();
|
|
14446
|
-
const curDeg = Math.round(
|
|
15193
|
+
const curDeg = Math.round(
|
|
15194
|
+
isFirst ? this.#pointPointLineDeg() + 180 : this.#pointPointLineDeg(),
|
|
15195
|
+
);
|
|
14447
15196
|
if (curDeg !== lastCursorDeg) {
|
|
14448
15197
|
lastCursorDeg = curDeg;
|
|
14449
15198
|
document.body.style.cursor = this.#rotateCursorSvg(curDeg);
|
|
@@ -14475,7 +15224,10 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14475
15224
|
const rect = container.getBoundingClientRect();
|
|
14476
15225
|
const cx = (this.#x / 100) * rect.width;
|
|
14477
15226
|
const cy = (this.#y / 100) * rect.height;
|
|
14478
|
-
const deg =
|
|
15227
|
+
const deg =
|
|
15228
|
+
(Math.atan2(e.clientY - rect.top - cy, e.clientX - rect.left - cx) *
|
|
15229
|
+
180) /
|
|
15230
|
+
Math.PI;
|
|
14479
15231
|
circle.style.cursor = this.#resizeCursorSvg(deg);
|
|
14480
15232
|
});
|
|
14481
15233
|
const onDown = (e) => {
|
|
@@ -14497,7 +15249,10 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14497
15249
|
const rect0 = container.getBoundingClientRect();
|
|
14498
15250
|
const cx0 = (this.#x / 100) * rect0.width;
|
|
14499
15251
|
const cy0 = (this.#y / 100) * rect0.height;
|
|
14500
|
-
const initDeg =
|
|
15252
|
+
const initDeg =
|
|
15253
|
+
(Math.atan2(e.clientY - rect0.top - cy0, e.clientX - rect0.left - cx0) *
|
|
15254
|
+
180) /
|
|
15255
|
+
Math.PI;
|
|
14501
15256
|
let lastCursorDeg = Math.round(initDeg);
|
|
14502
15257
|
document.body.style.cursor = this.#resizeCursorSvg(lastCursorDeg);
|
|
14503
15258
|
|
|
@@ -14528,7 +15283,8 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14528
15283
|
} else {
|
|
14529
15284
|
this.#radius = Math.max(0, dist);
|
|
14530
15285
|
}
|
|
14531
|
-
if (this.#radiusTooltip)
|
|
15286
|
+
if (this.#radiusTooltip)
|
|
15287
|
+
this.#radiusTooltip.setAttribute("text", this.#formatRadius());
|
|
14532
15288
|
this.#syncPositions();
|
|
14533
15289
|
this.#emitInput();
|
|
14534
15290
|
};
|
|
@@ -14549,7 +15305,8 @@ class FigCanvasControl extends HTMLElement {
|
|
|
14549
15305
|
window.addEventListener("pointerup", onUp);
|
|
14550
15306
|
};
|
|
14551
15307
|
circle.addEventListener("pointerdown", onDown);
|
|
14552
|
-
this._radiusDragCleanup = () =>
|
|
15308
|
+
this._radiusDragCleanup = () =>
|
|
15309
|
+
circle.removeEventListener("pointerdown", onDown);
|
|
14553
15310
|
}
|
|
14554
15311
|
|
|
14555
15312
|
#teardownRadiusDrag() {
|
|
@@ -14711,8 +15468,10 @@ class FigHandle extends HTMLElement {
|
|
|
14711
15468
|
};
|
|
14712
15469
|
|
|
14713
15470
|
const axes = this.#axes;
|
|
14714
|
-
if (axes.x)
|
|
14715
|
-
|
|
15471
|
+
if (axes.x)
|
|
15472
|
+
this.style.left = `${Math.round(resolve(xToken, rect.width, hw))}px`;
|
|
15473
|
+
if (axes.y)
|
|
15474
|
+
this.style.top = `${Math.round(resolve(yToken, rect.height, hh))}px`;
|
|
14716
15475
|
}
|
|
14717
15476
|
|
|
14718
15477
|
#syncValueAttribute() {
|
|
@@ -14729,16 +15488,25 @@ class FigHandle extends HTMLElement {
|
|
|
14729
15488
|
const raw = this.getAttribute("hit-area");
|
|
14730
15489
|
if (!raw) return null;
|
|
14731
15490
|
const tokens = raw.trim().split(/\s+/);
|
|
14732
|
-
let vPad = 0,
|
|
15491
|
+
let vPad = 0,
|
|
15492
|
+
hPad = 0,
|
|
15493
|
+
circle = false;
|
|
14733
15494
|
const nums = [];
|
|
14734
15495
|
for (const t of tokens) {
|
|
14735
|
-
if (t === "circle") {
|
|
15496
|
+
if (t === "circle") {
|
|
15497
|
+
circle = true;
|
|
15498
|
+
continue;
|
|
15499
|
+
}
|
|
14736
15500
|
const n = parseFloat(t);
|
|
14737
15501
|
if (Number.isFinite(n)) nums.push(n);
|
|
14738
15502
|
}
|
|
14739
|
-
if (nums.length >= 2) {
|
|
14740
|
-
|
|
14741
|
-
|
|
15503
|
+
if (nums.length >= 2) {
|
|
15504
|
+
vPad = nums[0];
|
|
15505
|
+
hPad = nums[1];
|
|
15506
|
+
} else if (nums.length === 1) {
|
|
15507
|
+
vPad = nums[0];
|
|
15508
|
+
hPad = nums[0];
|
|
15509
|
+
} else return null;
|
|
14742
15510
|
return { vPad, hPad, circle };
|
|
14743
15511
|
}
|
|
14744
15512
|
|
|
@@ -14759,7 +15527,10 @@ class FigHandle extends HTMLElement {
|
|
|
14759
15527
|
this.prepend(el);
|
|
14760
15528
|
this.#hitAreaEl = el;
|
|
14761
15529
|
}
|
|
14762
|
-
this.style.setProperty(
|
|
15530
|
+
this.style.setProperty(
|
|
15531
|
+
"--fig-handle-hit-area-size",
|
|
15532
|
+
String(parsed.hPad * 2),
|
|
15533
|
+
);
|
|
14763
15534
|
if (parsed.circle) {
|
|
14764
15535
|
this.#hitAreaEl.style.borderRadius = "50%";
|
|
14765
15536
|
} else {
|
|
@@ -14773,10 +15544,12 @@ class FigHandle extends HTMLElement {
|
|
|
14773
15544
|
if (this.#hitAreaMode === "delegate") {
|
|
14774
15545
|
e.preventDefault();
|
|
14775
15546
|
e.stopPropagation();
|
|
14776
|
-
this.dispatchEvent(
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
15547
|
+
this.dispatchEvent(
|
|
15548
|
+
new CustomEvent("hitareadown", {
|
|
15549
|
+
bubbles: true,
|
|
15550
|
+
detail: { originalEvent: e },
|
|
15551
|
+
}),
|
|
15552
|
+
);
|
|
14780
15553
|
} else {
|
|
14781
15554
|
this.#onPointerDown(e);
|
|
14782
15555
|
}
|
|
@@ -14796,7 +15569,10 @@ class FigHandle extends HTMLElement {
|
|
|
14796
15569
|
disconnectedCallback() {
|
|
14797
15570
|
this.#teardownDrag();
|
|
14798
15571
|
this.#hideColorTip();
|
|
14799
|
-
if (this.#hitAreaEl) {
|
|
15572
|
+
if (this.#hitAreaEl) {
|
|
15573
|
+
this.#hitAreaEl.remove();
|
|
15574
|
+
this.#hitAreaEl = null;
|
|
15575
|
+
}
|
|
14800
15576
|
this.removeEventListener("click", this.#handleSelect);
|
|
14801
15577
|
document.removeEventListener("pointerdown", this.#handleDeselect);
|
|
14802
15578
|
document.removeEventListener("keydown", this.#handleKeyDown);
|
|
@@ -14805,7 +15581,8 @@ class FigHandle extends HTMLElement {
|
|
|
14805
15581
|
select() {
|
|
14806
15582
|
if (this.hasAttribute("disabled")) return;
|
|
14807
15583
|
this.setAttribute("selected", "");
|
|
14808
|
-
if (this.getAttribute("type") === "color" && !this.#isDragging)
|
|
15584
|
+
if (this.getAttribute("type") === "color" && !this.#isDragging)
|
|
15585
|
+
this.#showColorTip();
|
|
14809
15586
|
}
|
|
14810
15587
|
|
|
14811
15588
|
deselect() {
|
|
@@ -14908,8 +15685,8 @@ class FigHandle extends HTMLElement {
|
|
|
14908
15685
|
lastRect = rect;
|
|
14909
15686
|
const currentLeft = parseFloat(this.style.left) || 0;
|
|
14910
15687
|
const currentTop = parseFloat(this.style.top) || 0;
|
|
14911
|
-
const rawX =
|
|
14912
|
-
const rawY =
|
|
15688
|
+
const rawX = clientX - offsetX - rect.left - handleW / 2;
|
|
15689
|
+
const rawY = clientY - offsetY - rect.top - handleH / 2;
|
|
14913
15690
|
|
|
14914
15691
|
const clampedX = Math.max(
|
|
14915
15692
|
-handleW / 2,
|
|
@@ -15055,16 +15832,32 @@ class FigHandle extends HTMLElement {
|
|
|
15055
15832
|
#handleColorTipInput = (e) => {
|
|
15056
15833
|
e.stopPropagation();
|
|
15057
15834
|
if (e.detail?.color) {
|
|
15058
|
-
this.setAttribute(
|
|
15059
|
-
|
|
15835
|
+
this.setAttribute(
|
|
15836
|
+
"color",
|
|
15837
|
+
this.#colorWithOpacity(e.detail.color, e.detail.opacity),
|
|
15838
|
+
);
|
|
15839
|
+
this.dispatchEvent(
|
|
15840
|
+
new CustomEvent("input", {
|
|
15841
|
+
bubbles: true,
|
|
15842
|
+
detail: { color: e.detail.color, opacity: e.detail.opacity },
|
|
15843
|
+
}),
|
|
15844
|
+
);
|
|
15060
15845
|
}
|
|
15061
15846
|
};
|
|
15062
15847
|
|
|
15063
15848
|
#handleColorTipChange = (e) => {
|
|
15064
15849
|
e.stopPropagation();
|
|
15065
15850
|
if (e.detail?.color) {
|
|
15066
|
-
this.setAttribute(
|
|
15067
|
-
|
|
15851
|
+
this.setAttribute(
|
|
15852
|
+
"color",
|
|
15853
|
+
this.#colorWithOpacity(e.detail.color, e.detail.opacity),
|
|
15854
|
+
);
|
|
15855
|
+
this.dispatchEvent(
|
|
15856
|
+
new CustomEvent("change", {
|
|
15857
|
+
bubbles: true,
|
|
15858
|
+
detail: { color: e.detail.color, opacity: e.detail.opacity },
|
|
15859
|
+
}),
|
|
15860
|
+
);
|
|
15068
15861
|
}
|
|
15069
15862
|
};
|
|
15070
15863
|
|