@rogieking/figui3 8.9.46 → 8.9.48
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/components.md +1 -1
- package/.cursor/skills/figui3/components.md +1 -1
- package/README.md +2 -0
- package/dist/fig-editor.js +2 -2
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +2 -2
- package/dist/fig.js +5 -5
- package/fig-editor.js +21 -2
- package/fig-lab.css +11 -4
- package/fig-lab.js +113 -40
- package/fig.js +85 -13
- package/package.json +1 -1
package/fig-editor.js
CHANGED
|
@@ -391,6 +391,7 @@ class FigSelectOptions extends HTMLElement {
|
|
|
391
391
|
#navStart = null;
|
|
392
392
|
#navEnd = null;
|
|
393
393
|
#resizeObserver = null;
|
|
394
|
+
#mutationObserver = null;
|
|
394
395
|
#boundSyncOverflow = this.syncOverflow.bind(this);
|
|
395
396
|
|
|
396
397
|
connectedCallback() {
|
|
@@ -402,6 +403,13 @@ class FigSelectOptions extends HTMLElement {
|
|
|
402
403
|
this.#resizeObserver?.disconnect();
|
|
403
404
|
this.#resizeObserver = new ResizeObserver(() => this.syncOverflow());
|
|
404
405
|
this.#resizeObserver.observe(this);
|
|
406
|
+
this.#mutationObserver?.disconnect();
|
|
407
|
+
this.#mutationObserver = new MutationObserver(() => {
|
|
408
|
+
if (!this.isConnected) return;
|
|
409
|
+
this.#ensureNavButtons();
|
|
410
|
+
this.syncOverflow();
|
|
411
|
+
});
|
|
412
|
+
this.#mutationObserver.observe(this, { childList: true });
|
|
405
413
|
requestAnimationFrame(() => this.syncOverflow());
|
|
406
414
|
}
|
|
407
415
|
|
|
@@ -409,6 +417,8 @@ class FigSelectOptions extends HTMLElement {
|
|
|
409
417
|
this.removeEventListener("scroll", this.#boundSyncOverflow);
|
|
410
418
|
this.#resizeObserver?.disconnect();
|
|
411
419
|
this.#resizeObserver = null;
|
|
420
|
+
this.#mutationObserver?.disconnect();
|
|
421
|
+
this.#mutationObserver = null;
|
|
412
422
|
this.#removeNavButtons();
|
|
413
423
|
}
|
|
414
424
|
|
|
@@ -446,6 +456,7 @@ class FigSelectOptions extends HTMLElement {
|
|
|
446
456
|
this.contains(this.#navStart) &&
|
|
447
457
|
this.contains(this.#navEnd)
|
|
448
458
|
) {
|
|
459
|
+
this.#positionNavButtons();
|
|
449
460
|
return;
|
|
450
461
|
}
|
|
451
462
|
this.#removeNavButtons();
|
|
@@ -458,8 +469,16 @@ class FigSelectOptions extends HTMLElement {
|
|
|
458
469
|
});
|
|
459
470
|
this.#navStart = buttons.start;
|
|
460
471
|
this.#navEnd = buttons.end;
|
|
461
|
-
this
|
|
462
|
-
|
|
472
|
+
this.#positionNavButtons();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
#positionNavButtons() {
|
|
476
|
+
if (this.firstElementChild !== this.#navStart) {
|
|
477
|
+
this.prepend(this.#navStart);
|
|
478
|
+
}
|
|
479
|
+
if (this.lastElementChild !== this.#navEnd) {
|
|
480
|
+
this.append(this.#navEnd);
|
|
481
|
+
}
|
|
463
482
|
}
|
|
464
483
|
|
|
465
484
|
#removeNavButtons() {
|
package/fig-lab.css
CHANGED
|
@@ -2093,14 +2093,21 @@ fig-canvas-control {
|
|
|
2093
2093
|
rgba(255, 255, 255, 0.5)
|
|
2094
2094
|
);
|
|
2095
2095
|
--fig-canvas-control-radius-stroke: #0D99FF;
|
|
2096
|
-
|
|
2096
|
+
/* Deprecated compatibility alias. */
|
|
2097
|
+
--fig-canvas-control-radius-stroke-halo: rgba(255,255,255,0);
|
|
2098
|
+
--fig-canvas-control-stroke-halo: var(--fig-canvas-control-radius-stroke-halo);
|
|
2099
|
+
--fig-canvas-control-stroke-halo-hover: rgba(255,255,255,1);
|
|
2097
2100
|
--fig-canvas-control-line-stroke-hover: #0D99FF;
|
|
2098
2101
|
--fig-canvas-control-line-stroke-active: #0D99FF;
|
|
2099
2102
|
--fig-canvas-control-line-stroke-width: 1px;
|
|
2100
2103
|
--fig-canvas-control-line-stroke-width-hover: 2px;
|
|
2101
2104
|
|
|
2102
|
-
&:has(
|
|
2105
|
+
&:has(
|
|
2106
|
+
.fig-canvas-control-radius-hit:hover,
|
|
2107
|
+
.fig-canvas-control-angle-line-hit:hover
|
|
2108
|
+
) {
|
|
2103
2109
|
--fig-canvas-control-line-stroke-width: var(--fig-canvas-control-line-stroke-width-hover);
|
|
2110
|
+
--fig-canvas-control-stroke-halo: var(--fig-canvas-control-stroke-halo-hover);
|
|
2104
2111
|
}
|
|
2105
2112
|
|
|
2106
2113
|
& > fig-handle,
|
|
@@ -2164,7 +2171,7 @@ fig-canvas-control {
|
|
|
2164
2171
|
}
|
|
2165
2172
|
|
|
2166
2173
|
.fig-canvas-control-radius-halo {
|
|
2167
|
-
stroke: var(--fig-canvas-control-
|
|
2174
|
+
stroke: var(--fig-canvas-control-stroke-halo);
|
|
2168
2175
|
stroke-width: calc(var(--fig-canvas-control-line-stroke-width) + 2px);
|
|
2169
2176
|
filter: none;
|
|
2170
2177
|
}
|
|
@@ -2195,7 +2202,7 @@ fig-canvas-control {
|
|
|
2195
2202
|
}
|
|
2196
2203
|
|
|
2197
2204
|
.fig-canvas-control-angle-line-halo {
|
|
2198
|
-
stroke: var(--fig-canvas-control-
|
|
2205
|
+
stroke: var(--fig-canvas-control-stroke-halo);
|
|
2199
2206
|
stroke-width: calc(var(--fig-canvas-control-line-stroke-width) + 2px);
|
|
2200
2207
|
stroke-linecap: butt;
|
|
2201
2208
|
filter: none;
|
package/fig-lab.js
CHANGED
|
@@ -7351,6 +7351,10 @@ class PropskitWheel extends HTMLElement {
|
|
|
7351
7351
|
figLabDefineElement("propskit-wheel", PropskitWheel);
|
|
7352
7352
|
|
|
7353
7353
|
/* Canvas Control */
|
|
7354
|
+
/**
|
|
7355
|
+
* A composite spatial control built from one or more fig-handle elements.
|
|
7356
|
+
* @attr {number} precision - Decimal places for positions, radius, angle, and internal handle dragging (default 2).
|
|
7357
|
+
*/
|
|
7354
7358
|
class FigCanvasControl extends HTMLElement {
|
|
7355
7359
|
static observedAttributes = [
|
|
7356
7360
|
"type",
|
|
@@ -7361,6 +7365,7 @@ class FigCanvasControl extends HTMLElement {
|
|
|
7361
7365
|
"disabled",
|
|
7362
7366
|
"drag-surface",
|
|
7363
7367
|
"snapping",
|
|
7368
|
+
"precision",
|
|
7364
7369
|
];
|
|
7365
7370
|
|
|
7366
7371
|
#x = 50;
|
|
@@ -7429,6 +7434,31 @@ class FigCanvasControl extends HTMLElement {
|
|
|
7429
7434
|
return "false";
|
|
7430
7435
|
}
|
|
7431
7436
|
|
|
7437
|
+
get #precision() {
|
|
7438
|
+
const parsed = Number(this.getAttribute("precision") ?? 2);
|
|
7439
|
+
if (!Number.isFinite(parsed)) return 2;
|
|
7440
|
+
return Math.max(0, Math.min(8, Math.trunc(parsed)));
|
|
7441
|
+
}
|
|
7442
|
+
|
|
7443
|
+
#roundPosition(value) {
|
|
7444
|
+
const factor = 10 ** this.#precision;
|
|
7445
|
+
const rounded = Math.round(value * factor) / factor;
|
|
7446
|
+
return Object.is(rounded, -0) ? 0 : rounded;
|
|
7447
|
+
}
|
|
7448
|
+
|
|
7449
|
+
#syncHandlePrecision() {
|
|
7450
|
+
const precision = this.getAttribute("precision");
|
|
7451
|
+
for (const handle of [
|
|
7452
|
+
this.#pointHandle,
|
|
7453
|
+
this.#angleHandle,
|
|
7454
|
+
this.#secondHandle,
|
|
7455
|
+
]) {
|
|
7456
|
+
if (!handle) continue;
|
|
7457
|
+
if (precision === null) handle.removeAttribute("precision");
|
|
7458
|
+
else handle.setAttribute("precision", String(this.#precision));
|
|
7459
|
+
}
|
|
7460
|
+
}
|
|
7461
|
+
|
|
7432
7462
|
#shouldSnap(shiftKey) {
|
|
7433
7463
|
const mode = this.#snappingMode;
|
|
7434
7464
|
if (mode === "true") return true;
|
|
@@ -7442,7 +7472,7 @@ class FigCanvasControl extends HTMLElement {
|
|
|
7442
7472
|
const parts = name.split(",");
|
|
7443
7473
|
return parts[0].trim();
|
|
7444
7474
|
}
|
|
7445
|
-
return `${
|
|
7475
|
+
return `${this.#roundPosition(this.#x)}%, ${this.#roundPosition(this.#y)}%`;
|
|
7446
7476
|
}
|
|
7447
7477
|
|
|
7448
7478
|
get #secondTipText() {
|
|
@@ -7451,7 +7481,7 @@ class FigCanvasControl extends HTMLElement {
|
|
|
7451
7481
|
const parts = name.split(",");
|
|
7452
7482
|
if (parts.length > 1) return parts[1].trim();
|
|
7453
7483
|
}
|
|
7454
|
-
return `${
|
|
7484
|
+
return `${this.#roundPosition(this.#x2)}%, ${this.#roundPosition(this.#y2)}%`;
|
|
7455
7485
|
}
|
|
7456
7486
|
|
|
7457
7487
|
get #dragSurface() {
|
|
@@ -7482,8 +7512,9 @@ class FigCanvasControl extends HTMLElement {
|
|
|
7482
7512
|
}
|
|
7483
7513
|
|
|
7484
7514
|
#formatRadius() {
|
|
7485
|
-
|
|
7486
|
-
return `Radius ${
|
|
7515
|
+
const radius = this.#roundPosition(this.#radius);
|
|
7516
|
+
if (this.#radiusIsPercent) return `Radius ${radius}%`;
|
|
7517
|
+
return `Radius ${radius}`;
|
|
7487
7518
|
}
|
|
7488
7519
|
|
|
7489
7520
|
connectedCallback() {
|
|
@@ -7534,6 +7565,18 @@ class FigCanvasControl extends HTMLElement {
|
|
|
7534
7565
|
if (this.#secondHandle)
|
|
7535
7566
|
this.#secondHandle.setAttribute("drag-snapping", newVal || "false");
|
|
7536
7567
|
}
|
|
7568
|
+
if (name === "precision") {
|
|
7569
|
+
if (!this.isConnected) return;
|
|
7570
|
+
this.#syncHandlePrecision();
|
|
7571
|
+
this.#x = this.#roundPosition(this.#x);
|
|
7572
|
+
this.#y = this.#roundPosition(this.#y);
|
|
7573
|
+
this.#x2 = this.#roundPosition(this.#x2);
|
|
7574
|
+
this.#y2 = this.#roundPosition(this.#y2);
|
|
7575
|
+
this.#radius = this.#roundPosition(this.#radius);
|
|
7576
|
+
this.#angle = this.#roundPosition(this.#angle);
|
|
7577
|
+
this.#syncPositions();
|
|
7578
|
+
this.#syncValueAttribute();
|
|
7579
|
+
}
|
|
7537
7580
|
if (name === "name") {
|
|
7538
7581
|
if (this.#pointTooltip)
|
|
7539
7582
|
this.#pointTooltip.setAttribute("text", this.#pointTipText);
|
|
@@ -7547,22 +7590,23 @@ class FigCanvasControl extends HTMLElement {
|
|
|
7547
7590
|
if (!raw) return;
|
|
7548
7591
|
try {
|
|
7549
7592
|
const v = JSON.parse(raw);
|
|
7550
|
-
if (typeof v.x === "number") this.#x = v.x;
|
|
7551
|
-
if (typeof v.y === "number") this.#y = v.y;
|
|
7593
|
+
if (typeof v.x === "number") this.#x = this.#roundPosition(v.x);
|
|
7594
|
+
if (typeof v.y === "number") this.#y = this.#roundPosition(v.y);
|
|
7552
7595
|
if (v.radius !== undefined) {
|
|
7553
7596
|
const rs = String(v.radius);
|
|
7554
7597
|
if (rs.endsWith("%")) {
|
|
7555
7598
|
this.#radiusIsPercent = true;
|
|
7556
|
-
this.#radius = parseFloat(rs);
|
|
7599
|
+
this.#radius = this.#roundPosition(parseFloat(rs));
|
|
7557
7600
|
} else {
|
|
7558
7601
|
this.#radiusIsPercent = false;
|
|
7559
|
-
this.#radius = parseFloat(rs);
|
|
7602
|
+
this.#radius = this.#roundPosition(parseFloat(rs));
|
|
7560
7603
|
}
|
|
7561
7604
|
if (!Number.isFinite(this.#radius)) this.#radius = 0;
|
|
7562
7605
|
}
|
|
7563
|
-
if (typeof v.angle === "number")
|
|
7564
|
-
|
|
7565
|
-
if (typeof v.
|
|
7606
|
+
if (typeof v.angle === "number")
|
|
7607
|
+
this.#angle = this.#roundPosition(v.angle);
|
|
7608
|
+
if (typeof v.x2 === "number") this.#x2 = this.#roundPosition(v.x2);
|
|
7609
|
+
if (typeof v.y2 === "number") this.#y2 = this.#roundPosition(v.y2);
|
|
7566
7610
|
if (
|
|
7567
7611
|
this.#type === "color" &&
|
|
7568
7612
|
typeof v.color === "string" &&
|
|
@@ -7577,19 +7621,23 @@ class FigCanvasControl extends HTMLElement {
|
|
|
7577
7621
|
}
|
|
7578
7622
|
|
|
7579
7623
|
get value() {
|
|
7580
|
-
const v = {
|
|
7624
|
+
const v = {
|
|
7625
|
+
x: this.#roundPosition(this.#x),
|
|
7626
|
+
y: this.#roundPosition(this.#y),
|
|
7627
|
+
};
|
|
7581
7628
|
if (this.#type === "color") {
|
|
7582
7629
|
const color =
|
|
7583
7630
|
this.getAttribute("color") || this.#pointHandle?.getAttribute("color");
|
|
7584
7631
|
if (color) v.color = color;
|
|
7585
7632
|
}
|
|
7586
7633
|
if (this.#hasRadius) {
|
|
7587
|
-
|
|
7634
|
+
const radius = this.#roundPosition(this.#radius);
|
|
7635
|
+
v.radius = this.#radiusIsPercent ? `${radius}%` : radius;
|
|
7588
7636
|
}
|
|
7589
|
-
if (this.#hasAngle) v.angle = this.#angle;
|
|
7637
|
+
if (this.#hasAngle) v.angle = this.#roundPosition(this.#angle);
|
|
7590
7638
|
if (this.#hasSecondPoint) {
|
|
7591
|
-
v.x2 = this.#x2;
|
|
7592
|
-
v.y2 = this.#y2;
|
|
7639
|
+
v.x2 = this.#roundPosition(this.#x2);
|
|
7640
|
+
v.y2 = this.#roundPosition(this.#y2);
|
|
7593
7641
|
}
|
|
7594
7642
|
return v;
|
|
7595
7643
|
}
|
|
@@ -7627,6 +7675,9 @@ class FigCanvasControl extends HTMLElement {
|
|
|
7627
7675
|
handle.setAttribute("drag-axes", "x,y");
|
|
7628
7676
|
handle.setAttribute("drag-snapping", this.#snappingMode);
|
|
7629
7677
|
handle.setAttribute("value", `${this.#x}% ${this.#y}%`);
|
|
7678
|
+
if (this.hasAttribute("precision")) {
|
|
7679
|
+
handle.setAttribute("precision", String(this.#precision));
|
|
7680
|
+
}
|
|
7630
7681
|
if (disabled) handle.setAttribute("disabled", "");
|
|
7631
7682
|
if (type === "color") {
|
|
7632
7683
|
handle.setAttribute("type", "color");
|
|
@@ -7890,7 +7941,7 @@ class FigCanvasControl extends HTMLElement {
|
|
|
7890
7941
|
this.#wireHoverTooltip(
|
|
7891
7942
|
this.#angleHandle,
|
|
7892
7943
|
() => this.#angleTooltip,
|
|
7893
|
-
() => `Angle ${
|
|
7944
|
+
() => `Angle ${this.#roundPosition(this.#angle)}°`,
|
|
7894
7945
|
() => this.#hasActiveInteraction(),
|
|
7895
7946
|
);
|
|
7896
7947
|
}
|
|
@@ -8049,10 +8100,10 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8049
8100
|
const maxDy = 100 - Math.max(y0, y20);
|
|
8050
8101
|
const dxPct = Math.max(minDx, Math.min(maxDx, dxPctRaw));
|
|
8051
8102
|
const dyPct = Math.max(minDy, Math.min(maxDy, dyPctRaw));
|
|
8052
|
-
this.#x = x0 + dxPct;
|
|
8053
|
-
this.#y = y0 + dyPct;
|
|
8054
|
-
this.#x2 = x20 + dxPct;
|
|
8055
|
-
this.#y2 = y20 + dyPct;
|
|
8103
|
+
this.#x = this.#roundPosition(x0 + dxPct);
|
|
8104
|
+
this.#y = this.#roundPosition(y0 + dyPct);
|
|
8105
|
+
this.#x2 = this.#roundPosition(x20 + dxPct);
|
|
8106
|
+
this.#y2 = this.#roundPosition(y20 + dyPct);
|
|
8056
8107
|
this.#syncPositions();
|
|
8057
8108
|
this.#emitInput();
|
|
8058
8109
|
};
|
|
@@ -8087,6 +8138,9 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8087
8138
|
handle.setAttribute("size", "small");
|
|
8088
8139
|
handle.setAttribute("hit-area", "12 circle");
|
|
8089
8140
|
handle.setAttribute("hit-area-mode", "delegate");
|
|
8141
|
+
if (this.hasAttribute("precision")) {
|
|
8142
|
+
handle.setAttribute("precision", String(this.#precision));
|
|
8143
|
+
}
|
|
8090
8144
|
if (disabled) handle.setAttribute("disabled", "");
|
|
8091
8145
|
this.#angleHandle = handle;
|
|
8092
8146
|
|
|
@@ -8095,7 +8149,7 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8095
8149
|
tip.setAttribute("action", "manual");
|
|
8096
8150
|
tip.setAttribute("theme", "canvas");
|
|
8097
8151
|
tip.setAttribute("pointer", "false");
|
|
8098
|
-
tip.setAttribute("text", `${
|
|
8152
|
+
tip.setAttribute("text", `${this.#roundPosition(this.#angle)}°`);
|
|
8099
8153
|
tip.appendChild(handle);
|
|
8100
8154
|
this.appendChild(tip);
|
|
8101
8155
|
this.#angleTooltip = tip;
|
|
@@ -8114,6 +8168,9 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8114
8168
|
handle.setAttribute("hit-area", "12 circle");
|
|
8115
8169
|
handle.setAttribute("hit-area-mode", "delegate");
|
|
8116
8170
|
handle.setAttribute("value", `${this.#x2}% ${this.#y2}%`);
|
|
8171
|
+
if (this.hasAttribute("precision")) {
|
|
8172
|
+
handle.setAttribute("precision", String(this.#precision));
|
|
8173
|
+
}
|
|
8117
8174
|
if (disabled) handle.setAttribute("disabled", "");
|
|
8118
8175
|
this.#secondHandle = handle;
|
|
8119
8176
|
|
|
@@ -8305,8 +8362,12 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8305
8362
|
this.#isDragging = true;
|
|
8306
8363
|
const px = e.detail?.px ?? this.#x / 100;
|
|
8307
8364
|
const py = e.detail?.py ?? this.#y / 100;
|
|
8308
|
-
this.#x =
|
|
8309
|
-
|
|
8365
|
+
this.#x = this.#roundPosition(
|
|
8366
|
+
Math.max(0, Math.min(100, px * 100)),
|
|
8367
|
+
);
|
|
8368
|
+
this.#y = this.#roundPosition(
|
|
8369
|
+
Math.max(0, Math.min(100, py * 100)),
|
|
8370
|
+
);
|
|
8310
8371
|
if (this.#pointTooltip) {
|
|
8311
8372
|
this.#pointTooltip.removeAttribute("show");
|
|
8312
8373
|
this.#pointTooltip.hidePopup?.();
|
|
@@ -8327,8 +8388,12 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8327
8388
|
}
|
|
8328
8389
|
const px = e.detail?.px ?? this.#x / 100;
|
|
8329
8390
|
const py = e.detail?.py ?? this.#y / 100;
|
|
8330
|
-
this.#x =
|
|
8331
|
-
|
|
8391
|
+
this.#x = this.#roundPosition(
|
|
8392
|
+
Math.max(0, Math.min(100, px * 100)),
|
|
8393
|
+
);
|
|
8394
|
+
this.#y = this.#roundPosition(
|
|
8395
|
+
Math.max(0, Math.min(100, py * 100)),
|
|
8396
|
+
);
|
|
8332
8397
|
if (this.#pointTooltip) this.#pointTooltip.removeAttribute("show");
|
|
8333
8398
|
this.#syncPositions();
|
|
8334
8399
|
this.#syncValueAttribute();
|
|
@@ -8358,7 +8423,7 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8358
8423
|
if (this.#shouldSnap(e.detail?.shiftKey)) {
|
|
8359
8424
|
angle = Math.round(angle / 15) * 15;
|
|
8360
8425
|
}
|
|
8361
|
-
this.#angle = angle;
|
|
8426
|
+
this.#angle = this.#roundPosition(angle);
|
|
8362
8427
|
|
|
8363
8428
|
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
8364
8429
|
if (this.#shouldSnap(e.detail?.shiftKey)) {
|
|
@@ -8372,15 +8437,17 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8372
8437
|
}
|
|
8373
8438
|
}
|
|
8374
8439
|
if (this.#radiusIsPercent) {
|
|
8375
|
-
this.#radius =
|
|
8440
|
+
this.#radius = this.#roundPosition(
|
|
8441
|
+
Math.max(0, (dist / rect.width) * 100),
|
|
8442
|
+
);
|
|
8376
8443
|
} else {
|
|
8377
|
-
this.#radius = Math.max(0, dist);
|
|
8444
|
+
this.#radius = this.#roundPosition(Math.max(0, dist));
|
|
8378
8445
|
}
|
|
8379
8446
|
|
|
8380
8447
|
if (this.#angleTooltip) {
|
|
8381
8448
|
this.#angleTooltip.setAttribute(
|
|
8382
8449
|
"text",
|
|
8383
|
-
`Angle ${
|
|
8450
|
+
`Angle ${this.#roundPosition(this.#angle)}°`,
|
|
8384
8451
|
);
|
|
8385
8452
|
this.#angleTooltip.setAttribute("show", "true");
|
|
8386
8453
|
this.#angleTooltip.showPopup?.();
|
|
@@ -8427,11 +8494,11 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8427
8494
|
if (this.#shouldSnap(ev.shiftKey)) {
|
|
8428
8495
|
angle = Math.round(angle / 15) * 15;
|
|
8429
8496
|
}
|
|
8430
|
-
this.#angle = angle;
|
|
8497
|
+
this.#angle = this.#roundPosition(angle);
|
|
8431
8498
|
if (this.#angleTooltip)
|
|
8432
8499
|
this.#angleTooltip.setAttribute(
|
|
8433
8500
|
"text",
|
|
8434
|
-
`Angle ${
|
|
8501
|
+
`Angle ${this.#roundPosition(angle)}°`,
|
|
8435
8502
|
);
|
|
8436
8503
|
this.#syncPositions();
|
|
8437
8504
|
this.#emitInput();
|
|
@@ -8464,8 +8531,12 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8464
8531
|
this.#isSecondDragging = true;
|
|
8465
8532
|
const px = e.detail?.px ?? this.#x2 / 100;
|
|
8466
8533
|
const py = e.detail?.py ?? this.#y2 / 100;
|
|
8467
|
-
this.#x2 =
|
|
8468
|
-
|
|
8534
|
+
this.#x2 = this.#roundPosition(
|
|
8535
|
+
Math.max(0, Math.min(100, px * 100)),
|
|
8536
|
+
);
|
|
8537
|
+
this.#y2 = this.#roundPosition(
|
|
8538
|
+
Math.max(0, Math.min(100, py * 100)),
|
|
8539
|
+
);
|
|
8469
8540
|
if (this.#secondTooltip) {
|
|
8470
8541
|
this.#secondTooltip.removeAttribute("show");
|
|
8471
8542
|
this.#secondTooltip.hidePopup?.();
|
|
@@ -8534,11 +8605,11 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8534
8605
|
const newPctX = Math.max(0, Math.min(100, (nx / r.width) * 100));
|
|
8535
8606
|
const newPctY = Math.max(0, Math.min(100, (ny / r.height) * 100));
|
|
8536
8607
|
if (isFirst) {
|
|
8537
|
-
this.#x = newPctX;
|
|
8538
|
-
this.#y = newPctY;
|
|
8608
|
+
this.#x = this.#roundPosition(newPctX);
|
|
8609
|
+
this.#y = this.#roundPosition(newPctY);
|
|
8539
8610
|
} else {
|
|
8540
|
-
this.#x2 = newPctX;
|
|
8541
|
-
this.#y2 = newPctY;
|
|
8611
|
+
this.#x2 = this.#roundPosition(newPctX);
|
|
8612
|
+
this.#y2 = this.#roundPosition(newPctY);
|
|
8542
8613
|
}
|
|
8543
8614
|
this.#syncPositions();
|
|
8544
8615
|
this.#emitInput();
|
|
@@ -8643,9 +8714,11 @@ class FigCanvasControl extends HTMLElement {
|
|
|
8643
8714
|
}
|
|
8644
8715
|
}
|
|
8645
8716
|
if (this.#radiusIsPercent) {
|
|
8646
|
-
this.#radius =
|
|
8717
|
+
this.#radius = this.#roundPosition(
|
|
8718
|
+
Math.max(0, (dist / rect.width) * 100),
|
|
8719
|
+
);
|
|
8647
8720
|
} else {
|
|
8648
|
-
this.#radius = Math.max(0, dist);
|
|
8721
|
+
this.#radius = this.#roundPosition(Math.max(0, dist));
|
|
8649
8722
|
}
|
|
8650
8723
|
if (this.#radiusTooltip) {
|
|
8651
8724
|
this.#radiusTooltip.setAttribute("text", this.#formatRadius());
|
package/fig.js
CHANGED
|
@@ -4522,6 +4522,7 @@ class FigTabs extends HTMLElement {
|
|
|
4522
4522
|
this.contains(this.#navStart) &&
|
|
4523
4523
|
this.contains(this.#navEnd)
|
|
4524
4524
|
) {
|
|
4525
|
+
this.#positionNavButtons();
|
|
4525
4526
|
return;
|
|
4526
4527
|
}
|
|
4527
4528
|
|
|
@@ -4535,8 +4536,16 @@ class FigTabs extends HTMLElement {
|
|
|
4535
4536
|
});
|
|
4536
4537
|
this.#navStart = buttons.start;
|
|
4537
4538
|
this.#navEnd = buttons.end;
|
|
4538
|
-
this
|
|
4539
|
-
|
|
4539
|
+
this.#positionNavButtons();
|
|
4540
|
+
}
|
|
4541
|
+
|
|
4542
|
+
#positionNavButtons() {
|
|
4543
|
+
if (this.firstElementChild !== this.#navStart) {
|
|
4544
|
+
this.prepend(this.#navStart);
|
|
4545
|
+
}
|
|
4546
|
+
if (this.lastElementChild !== this.#navEnd) {
|
|
4547
|
+
this.append(this.#navEnd);
|
|
4548
|
+
}
|
|
4540
4549
|
}
|
|
4541
4550
|
|
|
4542
4551
|
#handleKeyDown(event) {
|
|
@@ -18261,6 +18270,7 @@ class FigChooser extends HTMLElement {
|
|
|
18261
18270
|
this.contains(this.#navStart) &&
|
|
18262
18271
|
this.contains(this.#navEnd)
|
|
18263
18272
|
) {
|
|
18273
|
+
this.#positionNavButtons();
|
|
18264
18274
|
return;
|
|
18265
18275
|
}
|
|
18266
18276
|
|
|
@@ -18279,9 +18289,16 @@ class FigChooser extends HTMLElement {
|
|
|
18279
18289
|
});
|
|
18280
18290
|
this.#navStart = buttons.start;
|
|
18281
18291
|
this.#navEnd = buttons.end;
|
|
18292
|
+
this.#positionNavButtons();
|
|
18293
|
+
}
|
|
18282
18294
|
|
|
18283
|
-
|
|
18284
|
-
this.
|
|
18295
|
+
#positionNavButtons() {
|
|
18296
|
+
if (this.firstElementChild !== this.#navStart) {
|
|
18297
|
+
this.prepend(this.#navStart);
|
|
18298
|
+
}
|
|
18299
|
+
if (this.lastElementChild !== this.#navEnd) {
|
|
18300
|
+
this.append(this.#navEnd);
|
|
18301
|
+
}
|
|
18285
18302
|
}
|
|
18286
18303
|
|
|
18287
18304
|
#scrollByPage(direction) {
|
|
@@ -18403,6 +18420,10 @@ class FigChooser extends HTMLElement {
|
|
|
18403
18420
|
figDefineElement("fig-chooser", FigChooser);
|
|
18404
18421
|
|
|
18405
18422
|
/* Handle */
|
|
18423
|
+
/**
|
|
18424
|
+
* A draggable handle positioned within a drag surface.
|
|
18425
|
+
* @attr {number} precision - Decimal places for position values and pixel dragging (default 2).
|
|
18426
|
+
*/
|
|
18406
18427
|
class FigHandle extends HTMLElement {
|
|
18407
18428
|
static observedAttributes = [
|
|
18408
18429
|
"color",
|
|
@@ -18413,6 +18434,7 @@ class FigHandle extends HTMLElement {
|
|
|
18413
18434
|
"drag-axes",
|
|
18414
18435
|
"drag-snapping",
|
|
18415
18436
|
"value",
|
|
18437
|
+
"precision",
|
|
18416
18438
|
"type",
|
|
18417
18439
|
"tip",
|
|
18418
18440
|
"hit-area",
|
|
@@ -18495,13 +18517,37 @@ class FigHandle extends HTMLElement {
|
|
|
18495
18517
|
const container = this.#getContainer();
|
|
18496
18518
|
if (!container) return "0% 0%";
|
|
18497
18519
|
const { px, py } = this.#positionDetail(container.getBoundingClientRect());
|
|
18498
|
-
return `${
|
|
18520
|
+
return `${(px * 100).toFixed(this.precision)}% ${(py * 100).toFixed(this.precision)}%`;
|
|
18499
18521
|
}
|
|
18500
18522
|
|
|
18501
18523
|
set value(v) {
|
|
18502
18524
|
this.setAttribute("value", v ?? "0% 0%");
|
|
18503
18525
|
}
|
|
18504
18526
|
|
|
18527
|
+
get precision() {
|
|
18528
|
+
const parsed = Number(this.getAttribute("precision") ?? 2);
|
|
18529
|
+
if (!Number.isFinite(parsed)) return 2;
|
|
18530
|
+
return Math.max(0, Math.min(10, Math.trunc(parsed)));
|
|
18531
|
+
}
|
|
18532
|
+
|
|
18533
|
+
set precision(value) {
|
|
18534
|
+
if (value === null || value === undefined || value === "") {
|
|
18535
|
+
this.removeAttribute("precision");
|
|
18536
|
+
} else {
|
|
18537
|
+
const parsed = Number(value);
|
|
18538
|
+
const precision = Number.isFinite(parsed)
|
|
18539
|
+
? Math.max(0, Math.min(10, Math.trunc(parsed)))
|
|
18540
|
+
: 2;
|
|
18541
|
+
this.setAttribute("precision", String(precision));
|
|
18542
|
+
}
|
|
18543
|
+
}
|
|
18544
|
+
|
|
18545
|
+
#roundCoordinate(value) {
|
|
18546
|
+
const factor = 10 ** this.precision;
|
|
18547
|
+
const rounded = Math.round(value * factor) / factor;
|
|
18548
|
+
return Object.is(rounded, -0) ? 0 : rounded;
|
|
18549
|
+
}
|
|
18550
|
+
|
|
18505
18551
|
#parseValue(str) {
|
|
18506
18552
|
const normalized = str == null ? "" : String(str).trim();
|
|
18507
18553
|
if (!normalized) return { xPct: 0, yPct: 0 };
|
|
@@ -18541,7 +18587,7 @@ class FigHandle extends HTMLElement {
|
|
|
18541
18587
|
|
|
18542
18588
|
const resolveResponsive = (token) => {
|
|
18543
18589
|
const pct = typeof token === "number" ? token : 0;
|
|
18544
|
-
return `${pct}%`;
|
|
18590
|
+
return `${this.#roundCoordinate(pct)}%`;
|
|
18545
18591
|
};
|
|
18546
18592
|
|
|
18547
18593
|
const axes = this.#axes;
|
|
@@ -18549,12 +18595,16 @@ class FigHandle extends HTMLElement {
|
|
|
18549
18595
|
if (axes.x) {
|
|
18550
18596
|
const xPx = resolvePx(xToken, rect.width);
|
|
18551
18597
|
this.style.left =
|
|
18552
|
-
xPx === null
|
|
18598
|
+
xPx === null
|
|
18599
|
+
? resolveResponsive(xToken)
|
|
18600
|
+
: `${this.#roundCoordinate(xPx)}px`;
|
|
18553
18601
|
}
|
|
18554
18602
|
if (axes.y) {
|
|
18555
18603
|
const yPx = resolvePx(yToken, rect.height);
|
|
18556
18604
|
this.style.top =
|
|
18557
|
-
yPx === null
|
|
18605
|
+
yPx === null
|
|
18606
|
+
? resolveResponsive(yToken)
|
|
18607
|
+
: `${this.#roundCoordinate(yPx)}px`;
|
|
18558
18608
|
}
|
|
18559
18609
|
}
|
|
18560
18610
|
|
|
@@ -18783,8 +18833,12 @@ class FigHandle extends HTMLElement {
|
|
|
18783
18833
|
px = Math.max(0, Math.min(1, px));
|
|
18784
18834
|
py = Math.max(0, Math.min(1, py));
|
|
18785
18835
|
this.#syncPositionTranslate(axes);
|
|
18786
|
-
if (axes.x)
|
|
18787
|
-
|
|
18836
|
+
if (axes.x) {
|
|
18837
|
+
this.style.left = `${this.#roundCoordinate(px * rect.width)}px`;
|
|
18838
|
+
}
|
|
18839
|
+
if (axes.y) {
|
|
18840
|
+
this.style.top = `${this.#roundCoordinate(py * rect.height)}px`;
|
|
18841
|
+
}
|
|
18788
18842
|
this.#syncValueAttribute();
|
|
18789
18843
|
const detail = {
|
|
18790
18844
|
...this.#positionDetail(rect),
|
|
@@ -18824,6 +18878,15 @@ class FigHandle extends HTMLElement {
|
|
|
18824
18878
|
if (name === "value" && !this.#applyingValue && !this.#isDragging) {
|
|
18825
18879
|
this.#applyValue(value);
|
|
18826
18880
|
}
|
|
18881
|
+
if (
|
|
18882
|
+
name === "precision" &&
|
|
18883
|
+
this.isConnected &&
|
|
18884
|
+
!this.#isDragging &&
|
|
18885
|
+
this.hasAttribute("value")
|
|
18886
|
+
) {
|
|
18887
|
+
this.#applyValue(this.getAttribute("value"));
|
|
18888
|
+
this.#syncValueAttribute();
|
|
18889
|
+
}
|
|
18827
18890
|
if (name === "tip") {
|
|
18828
18891
|
this.#hideColorTip();
|
|
18829
18892
|
if (this.#tipMode) {
|
|
@@ -18926,10 +18989,14 @@ class FigHandle extends HTMLElement {
|
|
|
18926
18989
|
|
|
18927
18990
|
this.#syncPositionTranslate(axes);
|
|
18928
18991
|
if (axes.x) {
|
|
18929
|
-
this.style.left = `${
|
|
18992
|
+
this.style.left = `${this.#roundCoordinate(
|
|
18993
|
+
Math.max(0, Math.min(rect.width, centerX * rect.width)),
|
|
18994
|
+
)}px`;
|
|
18930
18995
|
}
|
|
18931
18996
|
if (axes.y) {
|
|
18932
|
-
this.style.top = `${
|
|
18997
|
+
this.style.top = `${this.#roundCoordinate(
|
|
18998
|
+
Math.max(0, Math.min(rect.height, centerY * rect.height)),
|
|
18999
|
+
)}px`;
|
|
18933
19000
|
}
|
|
18934
19001
|
};
|
|
18935
19002
|
|
|
@@ -19360,7 +19427,12 @@ class FigHandle extends HTMLElement {
|
|
|
19360
19427
|
const centerY = y + hh;
|
|
19361
19428
|
const px = rect.width > 0 ? centerX / rect.width : 0;
|
|
19362
19429
|
const py = rect.height > 0 ? centerY / rect.height : 0;
|
|
19363
|
-
return {
|
|
19430
|
+
return {
|
|
19431
|
+
x: this.#roundCoordinate(x),
|
|
19432
|
+
y: this.#roundCoordinate(y),
|
|
19433
|
+
px,
|
|
19434
|
+
py,
|
|
19435
|
+
};
|
|
19364
19436
|
}
|
|
19365
19437
|
}
|
|
19366
19438
|
figDefineElement("fig-handle", FigHandle);
|
package/package.json
CHANGED