@rogieking/figui3 4.15.4 → 4.15.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/base.css +11 -0
- package/components.css +119 -60
- package/dist/base.css +1 -1
- package/dist/components.css +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +23 -37
- package/fig.js +141 -142
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -7308,6 +7308,10 @@ class FigInputGradient extends HTMLElement {
|
|
|
7308
7308
|
});
|
|
7309
7309
|
}
|
|
7310
7310
|
|
|
7311
|
+
refreshLayout() {
|
|
7312
|
+
this.#repositionHandles();
|
|
7313
|
+
}
|
|
7314
|
+
|
|
7311
7315
|
#syncHandles() {
|
|
7312
7316
|
if (!this.#track) return;
|
|
7313
7317
|
const handles = this.#track.querySelectorAll(
|
|
@@ -11811,6 +11815,13 @@ customElements.define("fig-footer", FigFooter);
|
|
|
11811
11815
|
class FigSpinner extends HTMLElement {}
|
|
11812
11816
|
customElements.define("fig-spinner", FigSpinner);
|
|
11813
11817
|
|
|
11818
|
+
/**
|
|
11819
|
+
* A styled visual preview layer for arbitrary content such as images, canvas,
|
|
11820
|
+
* video, SVG, or custom rendered surfaces.
|
|
11821
|
+
*/
|
|
11822
|
+
class FigPreview extends HTMLElement {}
|
|
11823
|
+
customElements.define("fig-preview", FigPreview);
|
|
11824
|
+
|
|
11814
11825
|
/** @type {Record<string, string>} */
|
|
11815
11826
|
const FIG_ICON_TOKENS = {
|
|
11816
11827
|
chevron: "--icon-16-chevron",
|
|
@@ -12410,6 +12421,11 @@ class FigFillPicker extends HTMLElement {
|
|
|
12410
12421
|
// Use RAF to ensure layout is complete before updating angle input
|
|
12411
12422
|
requestAnimationFrame(() => {
|
|
12412
12423
|
this.#updateGradientUI();
|
|
12424
|
+
const barInput = tab.querySelector(".fig-fill-picker-gradient-bar-input");
|
|
12425
|
+
barInput?.refreshLayout?.();
|
|
12426
|
+
requestAnimationFrame(() => {
|
|
12427
|
+
barInput?.refreshLayout?.();
|
|
12428
|
+
});
|
|
12413
12429
|
});
|
|
12414
12430
|
}
|
|
12415
12431
|
|
|
@@ -13347,16 +13363,11 @@ class FigFillPicker extends HTMLElement {
|
|
|
13347
13363
|
</fig-dropdown>
|
|
13348
13364
|
<fig-input-number class="fig-fill-picker-scale" min="1" max="200" value="${
|
|
13349
13365
|
this.#image.scale
|
|
13350
|
-
}" units="%"
|
|
13366
|
+
}" units="%" ${
|
|
13367
|
+
this.#image.scaleMode === "tile" ? "" : 'style="display: none;"'
|
|
13368
|
+
}></fig-input-number>
|
|
13351
13369
|
</fig-field>
|
|
13352
|
-
<
|
|
13353
|
-
<div class="fig-fill-picker-checkerboard"></div>
|
|
13354
|
-
<div class="fig-fill-picker-image-preview"></div>
|
|
13355
|
-
<fig-button variant="overlay" class="fig-fill-picker-upload">
|
|
13356
|
-
Upload from computer
|
|
13357
|
-
<input type="file" accept="image/*" style="display: none;" />
|
|
13358
|
-
</fig-button>
|
|
13359
|
-
</div>
|
|
13370
|
+
<fig-image class="fig-fill-picker-media-preview fig-fill-picker-image-preview" upload="true" label="Upload from computer" size="auto" aspect-ratio="1/1" fit="cover" checkerboard="true"></fig-image>
|
|
13360
13371
|
`;
|
|
13361
13372
|
|
|
13362
13373
|
this.#setupImageEvents(container);
|
|
@@ -13367,8 +13378,6 @@ class FigFillPicker extends HTMLElement {
|
|
|
13367
13378
|
".fig-fill-picker-scale-mode",
|
|
13368
13379
|
);
|
|
13369
13380
|
const scaleInput = container.querySelector(".fig-fill-picker-scale");
|
|
13370
|
-
const uploadBtn = container.querySelector(".fig-fill-picker-upload");
|
|
13371
|
-
const fileInput = container.querySelector('input[type="file"]');
|
|
13372
13381
|
const preview = container.querySelector(".fig-fill-picker-image-preview");
|
|
13373
13382
|
|
|
13374
13383
|
scaleModeDropdown.addEventListener("change", (e) => {
|
|
@@ -13386,88 +13395,106 @@ class FigFillPicker extends HTMLElement {
|
|
|
13386
13395
|
this.#emitInput();
|
|
13387
13396
|
});
|
|
13388
13397
|
|
|
13389
|
-
|
|
13390
|
-
|
|
13398
|
+
preview.addEventListener("loaded", (e) => {
|
|
13399
|
+
const src = e.detail?.src || preview.src;
|
|
13400
|
+
if (!src) return;
|
|
13401
|
+
this.#image.url = src;
|
|
13402
|
+
this.#updateImagePreview(preview);
|
|
13403
|
+
this.#updateChit();
|
|
13404
|
+
this.#emitInput();
|
|
13391
13405
|
});
|
|
13392
13406
|
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
this.#updateImagePreview(preview);
|
|
13400
|
-
this.#updateChit();
|
|
13401
|
-
this.#emitInput();
|
|
13402
|
-
};
|
|
13403
|
-
reader.readAsDataURL(file);
|
|
13404
|
-
}
|
|
13407
|
+
preview.addEventListener("change", () => {
|
|
13408
|
+
if (preview.src) return;
|
|
13409
|
+
this.#image.url = null;
|
|
13410
|
+
this.#updateImagePreview(preview);
|
|
13411
|
+
this.#updateChit();
|
|
13412
|
+
this.#emitInput();
|
|
13405
13413
|
});
|
|
13406
13414
|
|
|
13407
|
-
|
|
13408
|
-
const previewArea = container.querySelector(
|
|
13409
|
-
".fig-fill-picker-media-preview",
|
|
13410
|
-
);
|
|
13411
|
-
previewArea.addEventListener("dragover", (e) => {
|
|
13412
|
-
e.preventDefault();
|
|
13413
|
-
previewArea.classList.add("dragover");
|
|
13414
|
-
});
|
|
13415
|
-
previewArea.addEventListener("dragleave", () => {
|
|
13416
|
-
previewArea.classList.remove("dragover");
|
|
13417
|
-
});
|
|
13418
|
-
previewArea.addEventListener("drop", (e) => {
|
|
13419
|
-
e.preventDefault();
|
|
13420
|
-
previewArea.classList.remove("dragover");
|
|
13421
|
-
const file = e.dataTransfer.files[0];
|
|
13422
|
-
if (file && file.type.startsWith("image/")) {
|
|
13423
|
-
const reader = new FileReader();
|
|
13424
|
-
reader.onload = (e) => {
|
|
13425
|
-
this.#image.url = e.target.result;
|
|
13426
|
-
this.#updateImagePreview(preview);
|
|
13427
|
-
this.#updateChit();
|
|
13428
|
-
this.#emitInput();
|
|
13429
|
-
};
|
|
13430
|
-
reader.readAsDataURL(file);
|
|
13431
|
-
}
|
|
13432
|
-
});
|
|
13415
|
+
this.#updateImagePreview(preview);
|
|
13433
13416
|
}
|
|
13434
13417
|
|
|
13435
13418
|
#updateImagePreview(element) {
|
|
13436
|
-
const container = element.closest(".fig-fill-picker-media-preview");
|
|
13437
13419
|
if (!this.#image.url) {
|
|
13438
|
-
element.
|
|
13439
|
-
|
|
13420
|
+
element.removeAttribute("src");
|
|
13421
|
+
element.classList.remove("has-media", "is-tiled");
|
|
13422
|
+
element.style.backgroundImage = "";
|
|
13423
|
+
element.style.backgroundPosition = "";
|
|
13424
|
+
element.style.backgroundRepeat = "";
|
|
13425
|
+
element.style.backgroundSize = "";
|
|
13440
13426
|
return;
|
|
13441
13427
|
}
|
|
13442
13428
|
|
|
13443
|
-
element.
|
|
13444
|
-
|
|
13445
|
-
element.style.backgroundImage =
|
|
13446
|
-
element.style.backgroundPosition = "
|
|
13429
|
+
element.setAttribute("src", this.#image.url);
|
|
13430
|
+
element.classList.add("has-media");
|
|
13431
|
+
element.style.backgroundImage = "";
|
|
13432
|
+
element.style.backgroundPosition = "";
|
|
13433
|
+
element.style.backgroundRepeat = "";
|
|
13434
|
+
element.style.backgroundSize = "";
|
|
13435
|
+
element.mediaEl?.style.removeProperty("opacity");
|
|
13436
|
+
|
|
13437
|
+
const fileInput = element.querySelector("fig-input-file[data-generated]");
|
|
13438
|
+
if (fileInput) {
|
|
13439
|
+
fileInput.setAttribute("label", "Replace");
|
|
13440
|
+
fileInput.removeAttribute("url");
|
|
13441
|
+
}
|
|
13447
13442
|
|
|
13448
13443
|
switch (this.#image.scaleMode) {
|
|
13449
13444
|
case "fill":
|
|
13450
|
-
element.
|
|
13451
|
-
element.
|
|
13452
|
-
break;
|
|
13453
|
-
case "fit":
|
|
13454
|
-
element.style.backgroundSize = "contain";
|
|
13455
|
-
element.style.backgroundRepeat = "no-repeat";
|
|
13445
|
+
element.classList.remove("is-tiled");
|
|
13446
|
+
element.setAttribute("fit", "cover");
|
|
13456
13447
|
break;
|
|
13457
13448
|
case "crop":
|
|
13458
|
-
element.
|
|
13459
|
-
element.
|
|
13449
|
+
element.classList.remove("is-tiled");
|
|
13450
|
+
element.setAttribute("fit", "cover");
|
|
13451
|
+
break;
|
|
13452
|
+
case "fit":
|
|
13453
|
+
element.classList.remove("is-tiled");
|
|
13454
|
+
element.setAttribute("fit", "contain");
|
|
13460
13455
|
break;
|
|
13461
13456
|
case "tile":
|
|
13457
|
+
element.classList.add("is-tiled");
|
|
13458
|
+
element.setAttribute("fit", "none");
|
|
13459
|
+
element.style.backgroundImage = `url(${this.#image.url})`;
|
|
13460
|
+
element.style.backgroundPosition = "top left";
|
|
13462
13461
|
element.style.backgroundSize = `${this.#image.scale}%`;
|
|
13463
13462
|
element.style.backgroundRepeat = "repeat";
|
|
13464
|
-
element.style.
|
|
13463
|
+
if (element.mediaEl) element.mediaEl.style.opacity = "0";
|
|
13465
13464
|
break;
|
|
13466
13465
|
}
|
|
13467
13466
|
}
|
|
13468
13467
|
|
|
13469
13468
|
// For video elements (still uses object-fit)
|
|
13470
13469
|
#updateVideoPreviewStyle(element) {
|
|
13470
|
+
if (element.tagName === "FIG-MEDIA") {
|
|
13471
|
+
if (!this.#video.url) {
|
|
13472
|
+
element.removeAttribute("src");
|
|
13473
|
+
element.classList.remove("has-media");
|
|
13474
|
+
return;
|
|
13475
|
+
}
|
|
13476
|
+
|
|
13477
|
+
element.setAttribute("src", this.#video.url);
|
|
13478
|
+
element.classList.add("has-media");
|
|
13479
|
+
|
|
13480
|
+
const fileInput = element.querySelector("fig-input-file[data-generated]");
|
|
13481
|
+
if (fileInput) {
|
|
13482
|
+
fileInput.setAttribute("label", "Replace");
|
|
13483
|
+
fileInput.removeAttribute("url");
|
|
13484
|
+
}
|
|
13485
|
+
|
|
13486
|
+
switch (this.#video.scaleMode) {
|
|
13487
|
+
case "fill":
|
|
13488
|
+
case "crop":
|
|
13489
|
+
element.setAttribute("fit", "cover");
|
|
13490
|
+
break;
|
|
13491
|
+
case "fit":
|
|
13492
|
+
element.setAttribute("fit", "contain");
|
|
13493
|
+
break;
|
|
13494
|
+
}
|
|
13495
|
+
return;
|
|
13496
|
+
}
|
|
13497
|
+
|
|
13471
13498
|
element.style.objectPosition = "center";
|
|
13472
13499
|
element.style.width = "100%";
|
|
13473
13500
|
element.style.height = "100%";
|
|
@@ -13499,14 +13526,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
13499
13526
|
<option value="crop">Crop</option>
|
|
13500
13527
|
</fig-dropdown>
|
|
13501
13528
|
</fig-field>
|
|
13502
|
-
<
|
|
13503
|
-
<div class="fig-fill-picker-checkerboard"></div>
|
|
13504
|
-
<video class="fig-fill-picker-video-preview" style="display: none;" muted loop></video>
|
|
13505
|
-
<fig-button variant="overlay" class="fig-fill-picker-upload">
|
|
13506
|
-
Upload from computer
|
|
13507
|
-
<input type="file" accept="video/*" style="display: none;" />
|
|
13508
|
-
</fig-button>
|
|
13509
|
-
</div>
|
|
13529
|
+
<fig-media class="fig-fill-picker-media-preview fig-fill-picker-video-preview" type="video" upload="true" label="Upload from computer" size="auto" aspect-ratio="1/1" fit="cover" checkerboard="true" autoplay="true" muted="true" loop="true"></fig-media>
|
|
13510
13530
|
`;
|
|
13511
13531
|
|
|
13512
13532
|
this.#setupVideoEvents(container);
|
|
@@ -13516,8 +13536,6 @@ class FigFillPicker extends HTMLElement {
|
|
|
13516
13536
|
const scaleModeDropdown = container.querySelector(
|
|
13517
13537
|
".fig-fill-picker-scale-mode",
|
|
13518
13538
|
);
|
|
13519
|
-
const uploadBtn = container.querySelector(".fig-fill-picker-upload");
|
|
13520
|
-
const fileInput = container.querySelector('input[type="file"]');
|
|
13521
13539
|
const preview = container.querySelector(".fig-fill-picker-video-preview");
|
|
13522
13540
|
|
|
13523
13541
|
scaleModeDropdown.addEventListener("change", (e) => {
|
|
@@ -13527,51 +13545,25 @@ class FigFillPicker extends HTMLElement {
|
|
|
13527
13545
|
this.#emitInput();
|
|
13528
13546
|
});
|
|
13529
13547
|
|
|
13530
|
-
|
|
13531
|
-
|
|
13548
|
+
preview.addEventListener("loaded", (e) => {
|
|
13549
|
+
const src = e.detail?.src || preview.src;
|
|
13550
|
+
if (!src) return;
|
|
13551
|
+
this.#video.url = src;
|
|
13552
|
+
this.#updateVideoPreviewStyle(preview);
|
|
13553
|
+
preview.play?.();
|
|
13554
|
+
this.#updateChit();
|
|
13555
|
+
this.#emitInput();
|
|
13532
13556
|
});
|
|
13533
13557
|
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
const file = e.target.files[0];
|
|
13541
|
-
if (file) {
|
|
13542
|
-
this.#video.url = URL.createObjectURL(file);
|
|
13543
|
-
preview.src = this.#video.url;
|
|
13544
|
-
preview.style.display = "block";
|
|
13545
|
-
preview.play();
|
|
13546
|
-
previewArea.classList.add("has-media");
|
|
13547
|
-
this.#updateVideoPreviewStyle(preview);
|
|
13548
|
-
this.#updateChit();
|
|
13549
|
-
this.#emitInput();
|
|
13550
|
-
}
|
|
13558
|
+
preview.addEventListener("change", () => {
|
|
13559
|
+
if (preview.src) return;
|
|
13560
|
+
this.#video.url = null;
|
|
13561
|
+
this.#updateVideoPreviewStyle(preview);
|
|
13562
|
+
this.#updateChit();
|
|
13563
|
+
this.#emitInput();
|
|
13551
13564
|
});
|
|
13552
13565
|
|
|
13553
|
-
|
|
13554
|
-
e.preventDefault();
|
|
13555
|
-
previewArea.classList.add("dragover");
|
|
13556
|
-
});
|
|
13557
|
-
previewArea.addEventListener("dragleave", () => {
|
|
13558
|
-
previewArea.classList.remove("dragover");
|
|
13559
|
-
});
|
|
13560
|
-
previewArea.addEventListener("drop", (e) => {
|
|
13561
|
-
e.preventDefault();
|
|
13562
|
-
previewArea.classList.remove("dragover");
|
|
13563
|
-
const file = e.dataTransfer.files[0];
|
|
13564
|
-
if (file && file.type.startsWith("video/")) {
|
|
13565
|
-
this.#video.url = URL.createObjectURL(file);
|
|
13566
|
-
preview.src = this.#video.url;
|
|
13567
|
-
preview.style.display = "block";
|
|
13568
|
-
preview.play();
|
|
13569
|
-
previewArea.classList.add("has-media");
|
|
13570
|
-
this.#updateVideoPreviewStyle(preview);
|
|
13571
|
-
this.#updateChit();
|
|
13572
|
-
this.#emitInput();
|
|
13573
|
-
}
|
|
13574
|
-
});
|
|
13566
|
+
this.#updateVideoPreviewStyle(preview);
|
|
13575
13567
|
}
|
|
13576
13568
|
|
|
13577
13569
|
// ============ WEBCAM TAB ============
|
|
@@ -15078,14 +15070,8 @@ class FigHandle extends HTMLElement {
|
|
|
15078
15070
|
get value() {
|
|
15079
15071
|
const container = this.#getContainer();
|
|
15080
15072
|
if (!container) return "0% 0%";
|
|
15081
|
-
const
|
|
15082
|
-
|
|
15083
|
-
const hh = this.offsetHeight / 2;
|
|
15084
|
-
const x = parseFloat(this.style.left) || 0;
|
|
15085
|
-
const y = parseFloat(this.style.top) || 0;
|
|
15086
|
-
const px = rect.width > 0 ? ((x + hw) / rect.width) * 100 : 0;
|
|
15087
|
-
const py = rect.height > 0 ? ((y + hh) / rect.height) * 100 : 0;
|
|
15088
|
-
return `${Math.round(px)}% ${Math.round(py)}%`;
|
|
15073
|
+
const { px, py } = this.#positionDetail(container.getBoundingClientRect());
|
|
15074
|
+
return `${Math.round(px * 100)}% ${Math.round(py * 100)}%`;
|
|
15089
15075
|
}
|
|
15090
15076
|
|
|
15091
15077
|
set value(v) {
|
|
@@ -15124,26 +15110,32 @@ class FigHandle extends HTMLElement {
|
|
|
15124
15110
|
const hw = this.offsetWidth / 2;
|
|
15125
15111
|
const hh = this.offsetHeight / 2;
|
|
15126
15112
|
|
|
15127
|
-
const
|
|
15113
|
+
const resolvePx = (token, containerDim, halfHandle) => {
|
|
15128
15114
|
if (token && typeof token === "object" && "px" in token) {
|
|
15129
15115
|
return Math.max(
|
|
15130
15116
|
-halfHandle,
|
|
15131
15117
|
Math.min(containerDim - halfHandle, token.px - halfHandle),
|
|
15132
15118
|
);
|
|
15133
15119
|
}
|
|
15120
|
+
return null;
|
|
15121
|
+
};
|
|
15122
|
+
|
|
15123
|
+
const resolveResponsive = (token, halfHandle) => {
|
|
15134
15124
|
const pct = typeof token === "number" ? token : 0;
|
|
15135
|
-
|
|
15136
|
-
return Math.max(
|
|
15137
|
-
-halfHandle,
|
|
15138
|
-
Math.min(containerDim - halfHandle, center - halfHandle),
|
|
15139
|
-
);
|
|
15125
|
+
return `calc(${pct}% - ${halfHandle}px)`;
|
|
15140
15126
|
};
|
|
15141
15127
|
|
|
15142
15128
|
const axes = this.#axes;
|
|
15143
|
-
if (axes.x)
|
|
15144
|
-
|
|
15145
|
-
|
|
15146
|
-
|
|
15129
|
+
if (axes.x) {
|
|
15130
|
+
const xPx = resolvePx(xToken, rect.width, hw);
|
|
15131
|
+
this.style.left =
|
|
15132
|
+
xPx === null ? resolveResponsive(xToken, hw) : `${Math.round(xPx)}px`;
|
|
15133
|
+
}
|
|
15134
|
+
if (axes.y) {
|
|
15135
|
+
const yPx = resolvePx(yToken, rect.height, hh);
|
|
15136
|
+
this.style.top =
|
|
15137
|
+
yPx === null ? resolveResponsive(yToken, hh) : `${Math.round(yPx)}px`;
|
|
15138
|
+
}
|
|
15147
15139
|
}
|
|
15148
15140
|
|
|
15149
15141
|
#syncValueAttribute() {
|
|
@@ -15372,8 +15364,9 @@ class FigHandle extends HTMLElement {
|
|
|
15372
15364
|
const clampAndApply = (clientX, clientY, shiftKey = false) => {
|
|
15373
15365
|
const rect = container.getBoundingClientRect();
|
|
15374
15366
|
lastRect = rect;
|
|
15375
|
-
const
|
|
15376
|
-
const
|
|
15367
|
+
const currentPosition = this.#positionDetail(rect);
|
|
15368
|
+
const currentLeft = currentPosition.x;
|
|
15369
|
+
const currentTop = currentPosition.y;
|
|
15377
15370
|
const rawX = clientX - offsetX - rect.left - handleW / 2;
|
|
15378
15371
|
const rawY = clientY - offsetY - rect.top - handleH / 2;
|
|
15379
15372
|
|
|
@@ -15447,6 +15440,7 @@ class FigHandle extends HTMLElement {
|
|
|
15447
15440
|
if (this.#didDrag) {
|
|
15448
15441
|
clampAndApply(e.clientX, e.clientY, e.shiftKey);
|
|
15449
15442
|
this.#syncValueAttribute();
|
|
15443
|
+
this.#applyValue(this.getAttribute("value"));
|
|
15450
15444
|
this.dispatchEvent(
|
|
15451
15445
|
new CustomEvent("change", {
|
|
15452
15446
|
bubbles: true,
|
|
@@ -15710,12 +15704,17 @@ class FigHandle extends HTMLElement {
|
|
|
15710
15704
|
};
|
|
15711
15705
|
|
|
15712
15706
|
#positionDetail(containerRect) {
|
|
15707
|
+
const rect = containerRect || this.#getContainer()?.getBoundingClientRect();
|
|
15708
|
+
if (!rect) return { x: 0, y: 0, px: 0, py: 0 };
|
|
15709
|
+
const handleRect = this.getBoundingClientRect();
|
|
15713
15710
|
const hw = this.offsetWidth / 2;
|
|
15714
15711
|
const hh = this.offsetHeight / 2;
|
|
15715
|
-
const x =
|
|
15716
|
-
const y =
|
|
15717
|
-
const
|
|
15718
|
-
const
|
|
15712
|
+
const x = handleRect.left - rect.left;
|
|
15713
|
+
const y = handleRect.top - rect.top;
|
|
15714
|
+
const centerX = x + hw;
|
|
15715
|
+
const centerY = y + hh;
|
|
15716
|
+
const px = rect.width > 0 ? centerX / rect.width : 0;
|
|
15717
|
+
const py = rect.height > 0 ? centerY / rect.height : 0;
|
|
15719
15718
|
return { x, y, px, py };
|
|
15720
15719
|
}
|
|
15721
15720
|
}
|
package/package.json
CHANGED