@rogieking/figui3 8.9.19 → 8.9.21

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/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;
@@ -807,6 +922,7 @@ class PropskitColor extends HTMLElement {
807
922
  const label = customLabel || document.createElement("label");
808
923
  const picker = document.createElement("fig-fill-picker");
809
924
  const swatch = document.createElement("fig-swatch");
925
+ picker.anchorElement = this;
810
926
  picker.append(swatch);
811
927
  swatch.setAttribute("tabindex", "0");
812
928
  for (const node of initialChildren) {
@@ -996,13 +1112,17 @@ class PropskitColor extends HTMLElement {
996
1112
 
997
1113
  #handleClick(event) {
998
1114
  if (figLabBooleanAttribute(this, "disabled")) return;
1115
+ if (event.target instanceof Element && event.target.closest("fig-menu")) {
1116
+ return;
1117
+ }
999
1118
  if (
1000
1119
  event.target instanceof Element &&
1001
- event.target.closest("fig-fill-picker, fig-swatch, fig-menu")
1120
+ event.target.closest("fig-fill-picker, fig-swatch")
1002
1121
  ) {
1003
1122
  return;
1004
1123
  }
1005
1124
  this.focus();
1125
+ this.#input?.open?.();
1006
1126
  }
1007
1127
 
1008
1128
  get value() {
@@ -1076,6 +1196,375 @@ class PropskitColor extends HTMLElement {
1076
1196
  }
1077
1197
  figLabDefineElement("propskit-color", PropskitColor);
1078
1198
 
1199
+ /* Field + Fill wrapper — color-style swatch that opens the full fill picker */
1200
+ class PropskitFill extends HTMLElement {
1201
+ #field = null;
1202
+ #label = null;
1203
+ #input = null;
1204
+ #swatch = null;
1205
+ #hasCustomLabel = false;
1206
+ #observer = null;
1207
+ #managedInputAttrs = new Set();
1208
+ #boundHandleInput = null;
1209
+ #boundHandleChange = null;
1210
+ #boundHandleClick = this.#handleClick.bind(this);
1211
+ #initialValue = null;
1212
+
1213
+ static get observedAttributes() {
1214
+ return ["label", "direction", "aria-label"];
1215
+ }
1216
+
1217
+ connectedCallback() {
1218
+ if (!this.#field) this.#initialize();
1219
+ this.#syncField();
1220
+ this.#syncInputAttributes();
1221
+ this.#bindInputEvents();
1222
+ if (this.#initialValue === null) {
1223
+ this.#initialValue =
1224
+ this.getAttribute("default") ?? this.getAttribute("value") ?? "";
1225
+ }
1226
+ this.removeEventListener("click", this.#boundHandleClick);
1227
+ this.addEventListener("click", this.#boundHandleClick);
1228
+ figLabConnectPropskitResetMenu(this);
1229
+
1230
+ if (!this.#observer) {
1231
+ this.#observer = new MutationObserver((mutations) => {
1232
+ let syncField = false;
1233
+ let syncInput = false;
1234
+
1235
+ for (const mutation of mutations) {
1236
+ if (mutation.type !== "attributes") continue;
1237
+ if (
1238
+ mutation.attributeName === "label" ||
1239
+ mutation.attributeName === "direction" ||
1240
+ mutation.attributeName === "aria-label"
1241
+ ) {
1242
+ syncField = true;
1243
+ } else {
1244
+ syncInput = true;
1245
+ }
1246
+ }
1247
+
1248
+ if (syncField) this.#syncField();
1249
+ if (syncInput) this.#syncInputAttributes();
1250
+ });
1251
+ }
1252
+
1253
+ this.#observer.observe(this, { attributes: true });
1254
+ }
1255
+
1256
+ disconnectedCallback() {
1257
+ this.#observer?.disconnect();
1258
+ this.#unbindInputEvents();
1259
+ this.removeEventListener("click", this.#boundHandleClick);
1260
+ figLabDisconnectPropskitResetMenu(this);
1261
+ }
1262
+
1263
+ attributeChangedCallback(name, oldValue, newValue) {
1264
+ if (oldValue === newValue || !this.#field) return;
1265
+ if (name === "label" || name === "direction" || name === "aria-label") {
1266
+ this.#syncField();
1267
+ }
1268
+ }
1269
+
1270
+ #initialize() {
1271
+ const initialChildren = Array.from(this.childNodes).filter(
1272
+ (node) =>
1273
+ node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim()),
1274
+ );
1275
+ const customLabel = initialChildren.find(
1276
+ (node) => node.nodeType === Node.ELEMENT_NODE && node.matches("label"),
1277
+ );
1278
+ const field = document.createElement("fig-field");
1279
+ const label = customLabel || document.createElement("label");
1280
+ const picker = document.createElement("fig-fill-picker");
1281
+ const swatch = document.createElement("fig-swatch");
1282
+ picker.anchorElement = this;
1283
+ picker.append(swatch);
1284
+ swatch.setAttribute("tabindex", "0");
1285
+ for (const node of initialChildren) {
1286
+ if (node !== customLabel) picker.appendChild(node);
1287
+ }
1288
+ field.append(label, picker);
1289
+ this.#field = field;
1290
+ this.#label = label;
1291
+ this.#input = picker;
1292
+ this.#swatch = swatch;
1293
+ this.#hasCustomLabel = Boolean(customLabel);
1294
+ this.replaceChildren(field);
1295
+ }
1296
+
1297
+ #syncField() {
1298
+ if (!this.#field || !this.#label || !this.#input) return;
1299
+ const hasLabelAttr = this.hasAttribute("label");
1300
+ const rawLabel = this.getAttribute("label");
1301
+ const isBlankLabel = hasLabelAttr && (rawLabel ?? "").trim() === "";
1302
+
1303
+ if (isBlankLabel) {
1304
+ this.#label.remove();
1305
+ } else {
1306
+ if (!this.#hasCustomLabel) {
1307
+ this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
1308
+ }
1309
+ if (this.#label.parentElement !== this.#field) {
1310
+ this.#field.prepend(this.#label);
1311
+ }
1312
+ }
1313
+
1314
+ this.#field.setAttribute(
1315
+ "direction",
1316
+ this.getAttribute("direction") || "horizontal",
1317
+ );
1318
+ this.#input.setAttribute(
1319
+ "aria-label",
1320
+ this.getAttribute("aria-label") ||
1321
+ this.#label.textContent?.trim() ||
1322
+ "Fill",
1323
+ );
1324
+ }
1325
+
1326
+ #parsedHostFill() {
1327
+ return figLabParseFillValue(this.getAttribute("value"));
1328
+ }
1329
+
1330
+ #pickerValueFromHost() {
1331
+ return figLabSerializeFillValue(this.#parsedHostFill());
1332
+ }
1333
+
1334
+ #customModeSlot() {
1335
+ const type = this.#parsedHostFill()?.type;
1336
+ if (!type) return null;
1337
+ return this.querySelector(`[slot="mode-${type}"]`);
1338
+ }
1339
+
1340
+ #livePickerFill() {
1341
+ const live = this.#input?.value;
1342
+ if (live && typeof live === "object") return live;
1343
+ if (typeof live === "string" && live) return figLabParseFillValue(live);
1344
+ return null;
1345
+ }
1346
+
1347
+ #captureFill(fill) {
1348
+ const live = this.#livePickerFill();
1349
+ if (!fill || typeof fill !== "object") return fill;
1350
+ if (fill.type !== "webcam") return fill;
1351
+ const snapshot = fill.webcam?.snapshot || live?.webcam?.snapshot;
1352
+ if (!snapshot || fill.webcam?.snapshot) return fill;
1353
+ return {
1354
+ ...fill,
1355
+ webcam: {
1356
+ ...(live?.webcam && typeof live.webcam === "object" ? live.webcam : {}),
1357
+ ...fill.webcam,
1358
+ snapshot,
1359
+ },
1360
+ };
1361
+ }
1362
+
1363
+ #syncSwatch(fill = this.#parsedHostFill()) {
1364
+ if (!this.#swatch) return;
1365
+ const next = this.#captureFill(fill);
1366
+ let background = figLabFillSwatchBackground(next, this.#customModeSlot());
1367
+ if (!background) {
1368
+ background = figLabFillSwatchBackground(
1369
+ this.#livePickerFill(),
1370
+ this.#customModeSlot(),
1371
+ );
1372
+ }
1373
+ if (!background) {
1374
+ const painted = this.#swatch.getAttribute("background") ?? "";
1375
+ if (painted.startsWith("url(")) background = painted;
1376
+ }
1377
+ this.#swatch.setAttribute("background", background);
1378
+ this.#swatch.setAttribute("alpha", String(figLabFillSwatchAlpha(next)));
1379
+ const sizing = figLabFillSwatchSizing(next || fill);
1380
+ if (sizing) {
1381
+ this.#swatch.style.setProperty("--swatch-bg-size", sizing.size);
1382
+ this.#swatch.style.setProperty("--swatch-bg-position", sizing.position);
1383
+ }
1384
+ }
1385
+
1386
+ #getForwardedInputAttrNames() {
1387
+ const reserved = new Set([
1388
+ "label",
1389
+ "direction",
1390
+ "oninput",
1391
+ "onchange",
1392
+ "class",
1393
+ "style",
1394
+ "id",
1395
+ "size",
1396
+ "aria-label",
1397
+ "text",
1398
+ "default",
1399
+ "value",
1400
+ ]);
1401
+ return this.getAttributeNames().filter(
1402
+ (name) => !reserved.has(name) && !name.startsWith("data-"),
1403
+ );
1404
+ }
1405
+
1406
+ #syncInputAttributes() {
1407
+ if (!this.#input) return;
1408
+ const inputAttrs = this.#getForwardedInputAttrNames();
1409
+ const nextManaged = new Set(inputAttrs);
1410
+
1411
+ for (const attrName of this.#managedInputAttrs) {
1412
+ if (!nextManaged.has(attrName)) this.#input.removeAttribute(attrName);
1413
+ }
1414
+ for (const attrName of inputAttrs) {
1415
+ this.#input.setAttribute(attrName, this.getAttribute(attrName) ?? "");
1416
+ }
1417
+
1418
+ const nextJson = this.#pickerValueFromHost();
1419
+ if (this.#input.getAttribute("value") !== nextJson) {
1420
+ this.#input.setAttribute("value", nextJson);
1421
+ }
1422
+ this.#syncSwatch();
1423
+ this.#managedInputAttrs = nextManaged;
1424
+ }
1425
+
1426
+ #bindInputEvents() {
1427
+ if (!this.#input) return;
1428
+ this.#boundHandleInput ??= this.#forwardInputEvent.bind(this, "input");
1429
+ this.#boundHandleChange ??= this.#forwardInputEvent.bind(this, "change");
1430
+ this.#input.addEventListener("input", this.#boundHandleInput);
1431
+ this.#input.addEventListener("change", this.#boundHandleChange);
1432
+ }
1433
+
1434
+ #unbindInputEvents() {
1435
+ if (!this.#input) return;
1436
+ if (this.#boundHandleInput) {
1437
+ this.#input.removeEventListener("input", this.#boundHandleInput);
1438
+ }
1439
+ if (this.#boundHandleChange) {
1440
+ this.#input.removeEventListener("change", this.#boundHandleChange);
1441
+ }
1442
+ }
1443
+
1444
+ #fillFromEvent(event) {
1445
+ const detail =
1446
+ event instanceof CustomEvent && event.detail !== undefined
1447
+ ? event.detail
1448
+ : undefined;
1449
+ if (detail && typeof detail === "object") return detail;
1450
+ if (typeof detail === "string" && detail) {
1451
+ return figLabParseFillValue(detail);
1452
+ }
1453
+ const live = this.#input?.value;
1454
+ if (live && typeof live === "object") return live;
1455
+ if (typeof live === "string" && live) return figLabParseFillValue(live);
1456
+ return this.#parsedHostFill();
1457
+ }
1458
+
1459
+ #forwardInputEvent(type, event) {
1460
+ event.stopImmediatePropagation();
1461
+ if (figLabBooleanAttribute(this, "disabled")) return;
1462
+ const fill = this.#captureFill(this.#fillFromEvent(event));
1463
+ const serialized = figLabSerializeFillValue(fill);
1464
+ this.setAttribute("value", serialized);
1465
+ if (this.#input && this.#input.getAttribute("value") !== serialized) {
1466
+ this.#input.setAttribute("value", serialized);
1467
+ }
1468
+ this.#syncSwatch(fill);
1469
+ this.dispatchEvent(
1470
+ new CustomEvent(type, {
1471
+ detail: fill,
1472
+ bubbles: true,
1473
+ cancelable: true,
1474
+ composed: true,
1475
+ }),
1476
+ );
1477
+ }
1478
+
1479
+ #handleClick(event) {
1480
+ if (figLabBooleanAttribute(this, "disabled")) return;
1481
+ if (event.target instanceof Element && event.target.closest("fig-menu")) {
1482
+ return;
1483
+ }
1484
+ if (
1485
+ event.target instanceof Element &&
1486
+ event.target.closest("fig-fill-picker, fig-swatch")
1487
+ ) {
1488
+ return;
1489
+ }
1490
+ this.focus();
1491
+ this.#input?.open?.();
1492
+ }
1493
+
1494
+ get value() {
1495
+ return (
1496
+ this.getAttribute("value") ??
1497
+ figLabSerializeFillValue(this.#input?.value) ??
1498
+ ""
1499
+ );
1500
+ }
1501
+
1502
+ set value(nextValue) {
1503
+ if (nextValue === null || nextValue === undefined || nextValue === "") {
1504
+ this.removeAttribute("value");
1505
+ this.#input?.removeAttribute("value");
1506
+ this.#syncSwatch({ type: "solid", color: "", alpha: 1 });
1507
+ return;
1508
+ }
1509
+ const serialized = figLabSerializeFillValue(
1510
+ typeof nextValue === "string" ? figLabParseFillValue(nextValue) : nextValue,
1511
+ );
1512
+ this.setAttribute("value", serialized);
1513
+ this.#forceInputValue(serialized);
1514
+ }
1515
+
1516
+ #forceInputValue(next) {
1517
+ if (!this.#input) return;
1518
+ const json = figLabSerializeFillValue(figLabParseFillValue(next));
1519
+ if (this.#input.getAttribute("value") === json) {
1520
+ this.#input.removeAttribute("value");
1521
+ }
1522
+ this.#input.setAttribute("value", json);
1523
+ this.#syncSwatch(figLabParseFillValue(json));
1524
+ }
1525
+
1526
+ #defaultValue() {
1527
+ return this.getAttribute("default") ?? this.#initialValue ?? "";
1528
+ }
1529
+
1530
+ get defaultValue() {
1531
+ return String(this.#defaultValue());
1532
+ }
1533
+
1534
+ get isDefault() {
1535
+ return figLabPropskitJsonValuesEqual(this.value, this.defaultValue);
1536
+ }
1537
+
1538
+ resetToDefault() {
1539
+ const next = this.defaultValue;
1540
+ this.setAttribute("value", next);
1541
+ this.#forceInputValue(next);
1542
+ const fill = figLabParseFillValue(next);
1543
+ this.dispatchEvent(
1544
+ new CustomEvent("input", {
1545
+ detail: fill,
1546
+ bubbles: true,
1547
+ cancelable: true,
1548
+ composed: true,
1549
+ }),
1550
+ );
1551
+ this.dispatchEvent(
1552
+ new CustomEvent("change", {
1553
+ detail: fill,
1554
+ bubbles: true,
1555
+ cancelable: true,
1556
+ composed: true,
1557
+ }),
1558
+ );
1559
+ }
1560
+
1561
+ focus(options) {
1562
+ const swatch = this.querySelector("fig-swatch");
1563
+ if (swatch instanceof HTMLElement) swatch.focus(options);
1564
+ }
1565
+ }
1566
+ figLabDefineElement("propskit-fill", PropskitFill);
1567
+
1079
1568
  /* Field + Gradient wrapper */
1080
1569
  class PropskitGradient extends HTMLElement {
1081
1570
  #field = null;
@@ -1159,6 +1648,7 @@ class PropskitGradient extends HTMLElement {
1159
1648
  const field = document.createElement("fig-field");
1160
1649
  const label = customLabel || document.createElement("label");
1161
1650
  const input = document.createElement("fig-input-gradient");
1651
+ input.anchorElement = this;
1162
1652
 
1163
1653
  field.append(label, input);
1164
1654
  this.#field = field;
@@ -1286,13 +1776,17 @@ class PropskitGradient extends HTMLElement {
1286
1776
 
1287
1777
  #handleClick(event) {
1288
1778
  if (figLabBooleanAttribute(this, "disabled")) return;
1779
+ if (event.target instanceof Element && event.target.closest("fig-menu")) {
1780
+ return;
1781
+ }
1289
1782
  if (
1290
1783
  event.target instanceof Element &&
1291
- event.target.closest("fig-input-gradient, fig-menu")
1784
+ event.target.closest("fig-input-gradient, fig-fill-picker, fig-swatch")
1292
1785
  ) {
1293
1786
  return;
1294
1787
  }
1295
1788
  this.focus();
1789
+ this.#input?.querySelector?.("fig-fill-picker")?.open?.();
1296
1790
  }
1297
1791
 
1298
1792
  get value() {
@@ -3718,6 +4212,7 @@ class PropskitGroup extends HTMLElement {
3718
4212
 
3719
4213
  static #CONTROL_SELECTOR = [
3720
4214
  "propskit-color",
4215
+ "propskit-fill",
3721
4216
  "propskit-gradient",
3722
4217
  "propskit-number",
3723
4218
  "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
- return this.#webcam.snapshot
9150
- ? figCssUrl(this.#webcam.snapshot)
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
- swatch.setAttribute("background", this.#fillPickerSwatchBackground());
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
 
@@ -10343,6 +10363,7 @@ class FigInputGradient extends HTMLElement {
10343
10363
  #colorObserver = null;
10344
10364
  #repositionRAF = null;
10345
10365
  #pickerDefinitionPromise = null;
10366
+ anchorElement = null;
10346
10367
  #gradient = {
10347
10368
  type: "linear",
10348
10369
  angle: 90,
@@ -10713,7 +10734,7 @@ class FigInputGradient extends HTMLElement {
10713
10734
  #setupPickerEvents() {
10714
10735
  const picker = this.querySelector("fig-fill-picker");
10715
10736
  if (!picker) return;
10716
- picker.anchorElement = this;
10737
+ picker.anchorElement = this.anchorElement || this;
10717
10738
 
10718
10739
  const syncFromPicker = (e) => {
10719
10740
  e.stopPropagation();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "8.9.19",
3
+ "version": "8.9.21",
4
4
  "description": "A lightweight web components library for building Figma plugin and widget UIs with native look and feel",
5
5
  "author": "Rogie King",
6
6
  "license": "MIT",