@rogieking/figui3 8.9.19 → 8.9.20
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/fig-lab/SKILL.md +4 -3
- package/.cursor/skills/propkit/SKILL.md +1 -1
- package/README.md +26 -4
- package/components.css +21 -10
- package/dist/components.css +1 -1
- package/dist/fig-editor.js +3 -3
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +2 -2
- package/dist/fig.css +1 -1
- package/dist/fig.js +4 -4
- package/fig-editor.js +123 -5
- package/fig-lab.css +23 -6
- package/fig-lab.js +494 -2
- package/fig.js +27 -7
- package/package.json +1 -1
package/fig-lab.js
CHANGED
|
@@ -724,6 +724,121 @@ function figLabSolidFillValue(raw) {
|
|
|
724
724
|
return JSON.stringify({ type: "solid", color, alpha });
|
|
725
725
|
}
|
|
726
726
|
|
|
727
|
+
function figLabCssUrl(url) {
|
|
728
|
+
return `url("${String(url).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}")`;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function figLabParseFillValue(raw) {
|
|
732
|
+
if (raw && typeof raw === "object") return raw;
|
|
733
|
+
const text = String(raw ?? "").trim();
|
|
734
|
+
if (!text) return { type: "solid", color: "#D9D9D9", alpha: 1 };
|
|
735
|
+
if (text.startsWith("{")) {
|
|
736
|
+
try {
|
|
737
|
+
const parsed = JSON.parse(text);
|
|
738
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
739
|
+
} catch {}
|
|
740
|
+
}
|
|
741
|
+
if (text.startsWith("#")) {
|
|
742
|
+
const { color, alpha } = figLabParseSolidColor(text);
|
|
743
|
+
return { type: "solid", color, alpha };
|
|
744
|
+
}
|
|
745
|
+
return { type: "solid", color: "#D9D9D9", alpha: 1 };
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function figLabSerializeFillValue(fill) {
|
|
749
|
+
if (fill == null) return "";
|
|
750
|
+
return typeof fill === "string" ? fill : JSON.stringify(fill);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function figLabGradientCss(gradient) {
|
|
754
|
+
if (!gradient) return "";
|
|
755
|
+
const stops = [...(gradient.stops || [])]
|
|
756
|
+
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0))
|
|
757
|
+
.map((stop) => `${stop.color} ${stop.position ?? 0}%`)
|
|
758
|
+
.join(", ");
|
|
759
|
+
if (!stops) return "";
|
|
760
|
+
switch (gradient.type) {
|
|
761
|
+
case "radial":
|
|
762
|
+
return `radial-gradient(circle, ${stops})`;
|
|
763
|
+
case "angular":
|
|
764
|
+
return `conic-gradient(from ${gradient.angle || 0}deg, ${stops})`;
|
|
765
|
+
default:
|
|
766
|
+
return `linear-gradient(${gradient.angle ?? 180}deg, ${stops})`;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function figLabFillSwatchBackground(fill, slot) {
|
|
771
|
+
switch (fill?.type) {
|
|
772
|
+
case "solid":
|
|
773
|
+
return fill.color || "";
|
|
774
|
+
case "gradient":
|
|
775
|
+
return fill.css || figLabGradientCss(fill.gradient) || "";
|
|
776
|
+
case "image":
|
|
777
|
+
return fill.image?.url ? figLabCssUrl(fill.image.url) : "";
|
|
778
|
+
case "video":
|
|
779
|
+
return fill.video?.poster ? figLabCssUrl(fill.video.poster) : "";
|
|
780
|
+
case "webcam":
|
|
781
|
+
return fill.webcam?.snapshot ? figLabCssUrl(fill.webcam.snapshot) : "";
|
|
782
|
+
default:
|
|
783
|
+
return (
|
|
784
|
+
fill?.swatchBackground ||
|
|
785
|
+
fill?.background ||
|
|
786
|
+
fill?.css ||
|
|
787
|
+
slot?.getAttribute("swatch-background") ||
|
|
788
|
+
""
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function figLabFillSwatchMedia(fill) {
|
|
794
|
+
if (!fill || typeof fill !== "object") return null;
|
|
795
|
+
if (fill.type === "image") return fill.image;
|
|
796
|
+
if (fill.type === "video") return fill.video;
|
|
797
|
+
if (fill.type === "webcam") return fill.webcam;
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function figLabFillSwatchSizing(fill) {
|
|
802
|
+
const media = figLabFillSwatchMedia(fill);
|
|
803
|
+
const scale = Number(media?.scale);
|
|
804
|
+
switch (media?.scaleMode) {
|
|
805
|
+
case "fit":
|
|
806
|
+
return { size: "contain", position: "center" };
|
|
807
|
+
case "tile":
|
|
808
|
+
return {
|
|
809
|
+
size: `${Number.isFinite(scale) ? scale : 50}%`,
|
|
810
|
+
position: "top left",
|
|
811
|
+
};
|
|
812
|
+
case "fill":
|
|
813
|
+
case "crop":
|
|
814
|
+
return { size: "cover", position: "center" };
|
|
815
|
+
default:
|
|
816
|
+
return fill?.type === "webcam" || fill?.type === "image" || fill?.type === "video"
|
|
817
|
+
? { size: "cover", position: "center" }
|
|
818
|
+
: null;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function figLabFillSwatchAlpha(fill) {
|
|
823
|
+
switch (fill?.type) {
|
|
824
|
+
case "solid": {
|
|
825
|
+
if (Number.isFinite(fill.alpha)) return fill.alpha;
|
|
826
|
+
if (Number.isFinite(fill.opacity)) return fill.opacity / 100;
|
|
827
|
+
return 1;
|
|
828
|
+
}
|
|
829
|
+
case "gradient":
|
|
830
|
+
return fill.gradient?.opacity ?? fill.opacity ?? 1;
|
|
831
|
+
case "image":
|
|
832
|
+
return fill.image?.opacity ?? 1;
|
|
833
|
+
case "video":
|
|
834
|
+
return fill.video?.opacity ?? 1;
|
|
835
|
+
case "webcam":
|
|
836
|
+
return fill.webcam?.opacity ?? 1;
|
|
837
|
+
default:
|
|
838
|
+
return fill?.opacity ?? fill?.alpha ?? 1;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
727
842
|
/* Field + Color wrapper */
|
|
728
843
|
class PropskitColor extends HTMLElement {
|
|
729
844
|
#field = null;
|
|
@@ -996,13 +1111,17 @@ class PropskitColor extends HTMLElement {
|
|
|
996
1111
|
|
|
997
1112
|
#handleClick(event) {
|
|
998
1113
|
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
1114
|
+
if (event.target instanceof Element && event.target.closest("fig-menu")) {
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
999
1117
|
if (
|
|
1000
1118
|
event.target instanceof Element &&
|
|
1001
|
-
event.target.closest("fig-fill-picker, fig-swatch
|
|
1119
|
+
event.target.closest("fig-fill-picker, fig-swatch")
|
|
1002
1120
|
) {
|
|
1003
1121
|
return;
|
|
1004
1122
|
}
|
|
1005
1123
|
this.focus();
|
|
1124
|
+
this.#input?.open?.();
|
|
1006
1125
|
}
|
|
1007
1126
|
|
|
1008
1127
|
get value() {
|
|
@@ -1076,6 +1195,374 @@ class PropskitColor extends HTMLElement {
|
|
|
1076
1195
|
}
|
|
1077
1196
|
figLabDefineElement("propskit-color", PropskitColor);
|
|
1078
1197
|
|
|
1198
|
+
/* Field + Fill wrapper — color-style swatch that opens the full fill picker */
|
|
1199
|
+
class PropskitFill extends HTMLElement {
|
|
1200
|
+
#field = null;
|
|
1201
|
+
#label = null;
|
|
1202
|
+
#input = null;
|
|
1203
|
+
#swatch = null;
|
|
1204
|
+
#hasCustomLabel = false;
|
|
1205
|
+
#observer = null;
|
|
1206
|
+
#managedInputAttrs = new Set();
|
|
1207
|
+
#boundHandleInput = null;
|
|
1208
|
+
#boundHandleChange = null;
|
|
1209
|
+
#boundHandleClick = this.#handleClick.bind(this);
|
|
1210
|
+
#initialValue = null;
|
|
1211
|
+
|
|
1212
|
+
static get observedAttributes() {
|
|
1213
|
+
return ["label", "direction", "aria-label"];
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
connectedCallback() {
|
|
1217
|
+
if (!this.#field) this.#initialize();
|
|
1218
|
+
this.#syncField();
|
|
1219
|
+
this.#syncInputAttributes();
|
|
1220
|
+
this.#bindInputEvents();
|
|
1221
|
+
if (this.#initialValue === null) {
|
|
1222
|
+
this.#initialValue =
|
|
1223
|
+
this.getAttribute("default") ?? this.getAttribute("value") ?? "";
|
|
1224
|
+
}
|
|
1225
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
1226
|
+
this.addEventListener("click", this.#boundHandleClick);
|
|
1227
|
+
figLabConnectPropskitResetMenu(this);
|
|
1228
|
+
|
|
1229
|
+
if (!this.#observer) {
|
|
1230
|
+
this.#observer = new MutationObserver((mutations) => {
|
|
1231
|
+
let syncField = false;
|
|
1232
|
+
let syncInput = false;
|
|
1233
|
+
|
|
1234
|
+
for (const mutation of mutations) {
|
|
1235
|
+
if (mutation.type !== "attributes") continue;
|
|
1236
|
+
if (
|
|
1237
|
+
mutation.attributeName === "label" ||
|
|
1238
|
+
mutation.attributeName === "direction" ||
|
|
1239
|
+
mutation.attributeName === "aria-label"
|
|
1240
|
+
) {
|
|
1241
|
+
syncField = true;
|
|
1242
|
+
} else {
|
|
1243
|
+
syncInput = true;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
if (syncField) this.#syncField();
|
|
1248
|
+
if (syncInput) this.#syncInputAttributes();
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
this.#observer.observe(this, { attributes: true });
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
disconnectedCallback() {
|
|
1256
|
+
this.#observer?.disconnect();
|
|
1257
|
+
this.#unbindInputEvents();
|
|
1258
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
1259
|
+
figLabDisconnectPropskitResetMenu(this);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1263
|
+
if (oldValue === newValue || !this.#field) return;
|
|
1264
|
+
if (name === "label" || name === "direction" || name === "aria-label") {
|
|
1265
|
+
this.#syncField();
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
#initialize() {
|
|
1270
|
+
const initialChildren = Array.from(this.childNodes).filter(
|
|
1271
|
+
(node) =>
|
|
1272
|
+
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
|
|
1273
|
+
);
|
|
1274
|
+
const customLabel = initialChildren.find(
|
|
1275
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
|
|
1276
|
+
);
|
|
1277
|
+
const field = document.createElement("fig-field");
|
|
1278
|
+
const label = customLabel || document.createElement("label");
|
|
1279
|
+
const picker = document.createElement("fig-fill-picker");
|
|
1280
|
+
const swatch = document.createElement("fig-swatch");
|
|
1281
|
+
picker.append(swatch);
|
|
1282
|
+
swatch.setAttribute("tabindex", "0");
|
|
1283
|
+
for (const node of initialChildren) {
|
|
1284
|
+
if (node !== customLabel) picker.appendChild(node);
|
|
1285
|
+
}
|
|
1286
|
+
field.append(label, picker);
|
|
1287
|
+
this.#field = field;
|
|
1288
|
+
this.#label = label;
|
|
1289
|
+
this.#input = picker;
|
|
1290
|
+
this.#swatch = swatch;
|
|
1291
|
+
this.#hasCustomLabel = Boolean(customLabel);
|
|
1292
|
+
this.replaceChildren(field);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
#syncField() {
|
|
1296
|
+
if (!this.#field || !this.#label || !this.#input) return;
|
|
1297
|
+
const hasLabelAttr = this.hasAttribute("label");
|
|
1298
|
+
const rawLabel = this.getAttribute("label");
|
|
1299
|
+
const isBlankLabel = hasLabelAttr && (rawLabel ?? "").trim() === "";
|
|
1300
|
+
|
|
1301
|
+
if (isBlankLabel) {
|
|
1302
|
+
this.#label.remove();
|
|
1303
|
+
} else {
|
|
1304
|
+
if (!this.#hasCustomLabel) {
|
|
1305
|
+
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
1306
|
+
}
|
|
1307
|
+
if (this.#label.parentElement !== this.#field) {
|
|
1308
|
+
this.#field.prepend(this.#label);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
this.#field.setAttribute(
|
|
1313
|
+
"direction",
|
|
1314
|
+
this.getAttribute("direction") || "horizontal",
|
|
1315
|
+
);
|
|
1316
|
+
this.#input.setAttribute(
|
|
1317
|
+
"aria-label",
|
|
1318
|
+
this.getAttribute("aria-label") ||
|
|
1319
|
+
this.#label.textContent?.trim() ||
|
|
1320
|
+
"Fill",
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
#parsedHostFill() {
|
|
1325
|
+
return figLabParseFillValue(this.getAttribute("value"));
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
#pickerValueFromHost() {
|
|
1329
|
+
return figLabSerializeFillValue(this.#parsedHostFill());
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
#customModeSlot() {
|
|
1333
|
+
const type = this.#parsedHostFill()?.type;
|
|
1334
|
+
if (!type) return null;
|
|
1335
|
+
return this.querySelector(`[slot="mode-${type}"]`);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
#livePickerFill() {
|
|
1339
|
+
const live = this.#input?.value;
|
|
1340
|
+
if (live && typeof live === "object") return live;
|
|
1341
|
+
if (typeof live === "string" && live) return figLabParseFillValue(live);
|
|
1342
|
+
return null;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
#captureFill(fill) {
|
|
1346
|
+
const live = this.#livePickerFill();
|
|
1347
|
+
if (!fill || typeof fill !== "object") return fill;
|
|
1348
|
+
if (fill.type !== "webcam") return fill;
|
|
1349
|
+
const snapshot = fill.webcam?.snapshot || live?.webcam?.snapshot;
|
|
1350
|
+
if (!snapshot || fill.webcam?.snapshot) return fill;
|
|
1351
|
+
return {
|
|
1352
|
+
...fill,
|
|
1353
|
+
webcam: {
|
|
1354
|
+
...(live?.webcam && typeof live.webcam === "object" ? live.webcam : {}),
|
|
1355
|
+
...fill.webcam,
|
|
1356
|
+
snapshot,
|
|
1357
|
+
},
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
#syncSwatch(fill = this.#parsedHostFill()) {
|
|
1362
|
+
if (!this.#swatch) return;
|
|
1363
|
+
const next = this.#captureFill(fill);
|
|
1364
|
+
let background = figLabFillSwatchBackground(next, this.#customModeSlot());
|
|
1365
|
+
if (!background) {
|
|
1366
|
+
background = figLabFillSwatchBackground(
|
|
1367
|
+
this.#livePickerFill(),
|
|
1368
|
+
this.#customModeSlot(),
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
if (!background) {
|
|
1372
|
+
const painted = this.#swatch.getAttribute("background") ?? "";
|
|
1373
|
+
if (painted.startsWith("url(")) background = painted;
|
|
1374
|
+
}
|
|
1375
|
+
this.#swatch.setAttribute("background", background);
|
|
1376
|
+
this.#swatch.setAttribute("alpha", String(figLabFillSwatchAlpha(next)));
|
|
1377
|
+
const sizing = figLabFillSwatchSizing(next || fill);
|
|
1378
|
+
if (sizing) {
|
|
1379
|
+
this.#swatch.style.setProperty("--swatch-bg-size", sizing.size);
|
|
1380
|
+
this.#swatch.style.setProperty("--swatch-bg-position", sizing.position);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
#getForwardedInputAttrNames() {
|
|
1385
|
+
const reserved = new Set([
|
|
1386
|
+
"label",
|
|
1387
|
+
"direction",
|
|
1388
|
+
"oninput",
|
|
1389
|
+
"onchange",
|
|
1390
|
+
"class",
|
|
1391
|
+
"style",
|
|
1392
|
+
"id",
|
|
1393
|
+
"size",
|
|
1394
|
+
"aria-label",
|
|
1395
|
+
"text",
|
|
1396
|
+
"default",
|
|
1397
|
+
"value",
|
|
1398
|
+
]);
|
|
1399
|
+
return this.getAttributeNames().filter(
|
|
1400
|
+
(name) => !reserved.has(name) && !name.startsWith("data-"),
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
#syncInputAttributes() {
|
|
1405
|
+
if (!this.#input) return;
|
|
1406
|
+
const inputAttrs = this.#getForwardedInputAttrNames();
|
|
1407
|
+
const nextManaged = new Set(inputAttrs);
|
|
1408
|
+
|
|
1409
|
+
for (const attrName of this.#managedInputAttrs) {
|
|
1410
|
+
if (!nextManaged.has(attrName)) this.#input.removeAttribute(attrName);
|
|
1411
|
+
}
|
|
1412
|
+
for (const attrName of inputAttrs) {
|
|
1413
|
+
this.#input.setAttribute(attrName, this.getAttribute(attrName) ?? "");
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
const nextJson = this.#pickerValueFromHost();
|
|
1417
|
+
if (this.#input.getAttribute("value") !== nextJson) {
|
|
1418
|
+
this.#input.setAttribute("value", nextJson);
|
|
1419
|
+
}
|
|
1420
|
+
this.#syncSwatch();
|
|
1421
|
+
this.#managedInputAttrs = nextManaged;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
#bindInputEvents() {
|
|
1425
|
+
if (!this.#input) return;
|
|
1426
|
+
this.#boundHandleInput ??= this.#forwardInputEvent.bind(this, "input");
|
|
1427
|
+
this.#boundHandleChange ??= this.#forwardInputEvent.bind(this, "change");
|
|
1428
|
+
this.#input.addEventListener("input", this.#boundHandleInput);
|
|
1429
|
+
this.#input.addEventListener("change", this.#boundHandleChange);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
#unbindInputEvents() {
|
|
1433
|
+
if (!this.#input) return;
|
|
1434
|
+
if (this.#boundHandleInput) {
|
|
1435
|
+
this.#input.removeEventListener("input", this.#boundHandleInput);
|
|
1436
|
+
}
|
|
1437
|
+
if (this.#boundHandleChange) {
|
|
1438
|
+
this.#input.removeEventListener("change", this.#boundHandleChange);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
#fillFromEvent(event) {
|
|
1443
|
+
const detail =
|
|
1444
|
+
event instanceof CustomEvent && event.detail !== undefined
|
|
1445
|
+
? event.detail
|
|
1446
|
+
: undefined;
|
|
1447
|
+
if (detail && typeof detail === "object") return detail;
|
|
1448
|
+
if (typeof detail === "string" && detail) {
|
|
1449
|
+
return figLabParseFillValue(detail);
|
|
1450
|
+
}
|
|
1451
|
+
const live = this.#input?.value;
|
|
1452
|
+
if (live && typeof live === "object") return live;
|
|
1453
|
+
if (typeof live === "string" && live) return figLabParseFillValue(live);
|
|
1454
|
+
return this.#parsedHostFill();
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
#forwardInputEvent(type, event) {
|
|
1458
|
+
event.stopImmediatePropagation();
|
|
1459
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
1460
|
+
const fill = this.#captureFill(this.#fillFromEvent(event));
|
|
1461
|
+
const serialized = figLabSerializeFillValue(fill);
|
|
1462
|
+
this.setAttribute("value", serialized);
|
|
1463
|
+
if (this.#input && this.#input.getAttribute("value") !== serialized) {
|
|
1464
|
+
this.#input.setAttribute("value", serialized);
|
|
1465
|
+
}
|
|
1466
|
+
this.#syncSwatch(fill);
|
|
1467
|
+
this.dispatchEvent(
|
|
1468
|
+
new CustomEvent(type, {
|
|
1469
|
+
detail: fill,
|
|
1470
|
+
bubbles: true,
|
|
1471
|
+
cancelable: true,
|
|
1472
|
+
composed: true,
|
|
1473
|
+
}),
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
#handleClick(event) {
|
|
1478
|
+
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
1479
|
+
if (event.target instanceof Element && event.target.closest("fig-menu")) {
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
if (
|
|
1483
|
+
event.target instanceof Element &&
|
|
1484
|
+
event.target.closest("fig-fill-picker, fig-swatch")
|
|
1485
|
+
) {
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
this.focus();
|
|
1489
|
+
this.#input?.open?.();
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
get value() {
|
|
1493
|
+
return (
|
|
1494
|
+
this.getAttribute("value") ??
|
|
1495
|
+
figLabSerializeFillValue(this.#input?.value) ??
|
|
1496
|
+
""
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
set value(nextValue) {
|
|
1501
|
+
if (nextValue === null || nextValue === undefined || nextValue === "") {
|
|
1502
|
+
this.removeAttribute("value");
|
|
1503
|
+
this.#input?.removeAttribute("value");
|
|
1504
|
+
this.#syncSwatch({ type: "solid", color: "", alpha: 1 });
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
const serialized = figLabSerializeFillValue(
|
|
1508
|
+
typeof nextValue === "string" ? figLabParseFillValue(nextValue) : nextValue,
|
|
1509
|
+
);
|
|
1510
|
+
this.setAttribute("value", serialized);
|
|
1511
|
+
this.#forceInputValue(serialized);
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
#forceInputValue(next) {
|
|
1515
|
+
if (!this.#input) return;
|
|
1516
|
+
const json = figLabSerializeFillValue(figLabParseFillValue(next));
|
|
1517
|
+
if (this.#input.getAttribute("value") === json) {
|
|
1518
|
+
this.#input.removeAttribute("value");
|
|
1519
|
+
}
|
|
1520
|
+
this.#input.setAttribute("value", json);
|
|
1521
|
+
this.#syncSwatch(figLabParseFillValue(json));
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
#defaultValue() {
|
|
1525
|
+
return this.getAttribute("default") ?? this.#initialValue ?? "";
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
get defaultValue() {
|
|
1529
|
+
return String(this.#defaultValue());
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
get isDefault() {
|
|
1533
|
+
return figLabPropskitJsonValuesEqual(this.value, this.defaultValue);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
resetToDefault() {
|
|
1537
|
+
const next = this.defaultValue;
|
|
1538
|
+
this.setAttribute("value", next);
|
|
1539
|
+
this.#forceInputValue(next);
|
|
1540
|
+
const fill = figLabParseFillValue(next);
|
|
1541
|
+
this.dispatchEvent(
|
|
1542
|
+
new CustomEvent("input", {
|
|
1543
|
+
detail: fill,
|
|
1544
|
+
bubbles: true,
|
|
1545
|
+
cancelable: true,
|
|
1546
|
+
composed: true,
|
|
1547
|
+
}),
|
|
1548
|
+
);
|
|
1549
|
+
this.dispatchEvent(
|
|
1550
|
+
new CustomEvent("change", {
|
|
1551
|
+
detail: fill,
|
|
1552
|
+
bubbles: true,
|
|
1553
|
+
cancelable: true,
|
|
1554
|
+
composed: true,
|
|
1555
|
+
}),
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
focus(options) {
|
|
1560
|
+
const swatch = this.querySelector("fig-swatch");
|
|
1561
|
+
if (swatch instanceof HTMLElement) swatch.focus(options);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
figLabDefineElement("propskit-fill", PropskitFill);
|
|
1565
|
+
|
|
1079
1566
|
/* Field + Gradient wrapper */
|
|
1080
1567
|
class PropskitGradient extends HTMLElement {
|
|
1081
1568
|
#field = null;
|
|
@@ -1286,13 +1773,17 @@ class PropskitGradient extends HTMLElement {
|
|
|
1286
1773
|
|
|
1287
1774
|
#handleClick(event) {
|
|
1288
1775
|
if (figLabBooleanAttribute(this, "disabled")) return;
|
|
1776
|
+
if (event.target instanceof Element && event.target.closest("fig-menu")) {
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1289
1779
|
if (
|
|
1290
1780
|
event.target instanceof Element &&
|
|
1291
|
-
event.target.closest("fig-input-gradient, fig-
|
|
1781
|
+
event.target.closest("fig-input-gradient, fig-fill-picker, fig-swatch")
|
|
1292
1782
|
) {
|
|
1293
1783
|
return;
|
|
1294
1784
|
}
|
|
1295
1785
|
this.focus();
|
|
1786
|
+
this.#input?.querySelector?.("fig-fill-picker")?.open?.();
|
|
1296
1787
|
}
|
|
1297
1788
|
|
|
1298
1789
|
get value() {
|
|
@@ -3718,6 +4209,7 @@ class PropskitGroup extends HTMLElement {
|
|
|
3718
4209
|
|
|
3719
4210
|
static #CONTROL_SELECTOR = [
|
|
3720
4211
|
"propskit-color",
|
|
4212
|
+
"propskit-fill",
|
|
3721
4213
|
"propskit-gradient",
|
|
3722
4214
|
"propskit-number",
|
|
3723
4215
|
"propskit-position",
|
package/fig.js
CHANGED
|
@@ -9055,8 +9055,11 @@ class FigInputFill extends HTMLElement {
|
|
|
9055
9055
|
break;
|
|
9056
9056
|
case "webcam":
|
|
9057
9057
|
if (parsed.webcam && typeof parsed.webcam === "object") {
|
|
9058
|
-
const { stream: _ignored, ...rest } = parsed.webcam;
|
|
9058
|
+
const { stream: _ignored, snapshot, ...rest } = parsed.webcam;
|
|
9059
9059
|
this.#webcam = { ...this.#webcam, ...rest };
|
|
9060
|
+
if (typeof snapshot === "string" && snapshot) {
|
|
9061
|
+
this.#webcam.snapshot = snapshot;
|
|
9062
|
+
}
|
|
9060
9063
|
} else if (parsed.image?.url) {
|
|
9061
9064
|
this.#webcam.snapshot = parsed.image.url;
|
|
9062
9065
|
if (parsed.image.scaleMode)
|
|
@@ -9145,10 +9148,12 @@ class FigInputFill extends HTMLElement {
|
|
|
9145
9148
|
return this.#image.url ? figCssUrl(this.#image.url) : "#D9D9D9";
|
|
9146
9149
|
case "video":
|
|
9147
9150
|
return this.#video.poster ? figCssUrl(this.#video.poster) : "#D9D9D9";
|
|
9148
|
-
case "webcam":
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9151
|
+
case "webcam": {
|
|
9152
|
+
const snapshot =
|
|
9153
|
+
this.#webcam.snapshot ||
|
|
9154
|
+
this.#fillPicker?.value?.webcam?.snapshot;
|
|
9155
|
+
return snapshot ? figCssUrl(snapshot) : "";
|
|
9156
|
+
}
|
|
9152
9157
|
default:
|
|
9153
9158
|
return this.#customSwatchBackground();
|
|
9154
9159
|
}
|
|
@@ -9419,11 +9424,18 @@ class FigInputFill extends HTMLElement {
|
|
|
9419
9424
|
break;
|
|
9420
9425
|
case "webcam":
|
|
9421
9426
|
if (detail.webcam) {
|
|
9422
|
-
const { stream: _ignored, ...rest } = detail.webcam;
|
|
9427
|
+
const { stream: _ignored, snapshot, ...rest } = detail.webcam;
|
|
9423
9428
|
this.#webcam = { ...this.#webcam, ...rest };
|
|
9429
|
+
if (typeof snapshot === "string" && snapshot) {
|
|
9430
|
+
this.#webcam.snapshot = snapshot;
|
|
9431
|
+
}
|
|
9424
9432
|
} else if (detail.image?.url) {
|
|
9425
9433
|
this.#webcam.snapshot = detail.image.url;
|
|
9426
9434
|
}
|
|
9435
|
+
if (!this.#webcam.snapshot) {
|
|
9436
|
+
const fromPicker = this.#fillPicker?.value?.webcam?.snapshot;
|
|
9437
|
+
if (fromPicker) this.#webcam.snapshot = fromPicker;
|
|
9438
|
+
}
|
|
9427
9439
|
break;
|
|
9428
9440
|
default: {
|
|
9429
9441
|
const { type: _type, colorSpace: _colorSpace, ...rest } = detail;
|
|
@@ -9686,7 +9698,15 @@ class FigInputFill extends HTMLElement {
|
|
|
9686
9698
|
#syncSwatch() {
|
|
9687
9699
|
const swatch = this.#fillPicker?.querySelector("fig-swatch");
|
|
9688
9700
|
if (!swatch) return;
|
|
9689
|
-
|
|
9701
|
+
const background = this.#fillPickerSwatchBackground();
|
|
9702
|
+
if (this.#fillType === "webcam" && !background) {
|
|
9703
|
+
const current = swatch.getAttribute("background") || "";
|
|
9704
|
+
if (current.startsWith("url(")) {
|
|
9705
|
+
swatch.setAttribute("alpha", this.#fillPickerSwatchAlpha());
|
|
9706
|
+
return;
|
|
9707
|
+
}
|
|
9708
|
+
}
|
|
9709
|
+
swatch.setAttribute("background", background);
|
|
9690
9710
|
swatch.setAttribute("alpha", this.#fillPickerSwatchAlpha());
|
|
9691
9711
|
}
|
|
9692
9712
|
|
package/package.json
CHANGED