@rogieking/figui3 8.9.47 → 8.9.49

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.css CHANGED
@@ -249,7 +249,6 @@ propskit-group {
249
249
 
250
250
  & > fig-header {
251
251
  cursor: default;
252
- padding-left: var(--spacer-3);
253
252
  gap: 0;
254
253
 
255
254
  &:focus-visible {
@@ -2093,14 +2092,21 @@ fig-canvas-control {
2093
2092
  rgba(255, 255, 255, 0.5)
2094
2093
  );
2095
2094
  --fig-canvas-control-radius-stroke: #0D99FF;
2096
- --fig-canvas-control-radius-stroke-halo: #FFFFFF;
2095
+ /* Deprecated compatibility alias. */
2096
+ --fig-canvas-control-radius-stroke-halo: rgba(255,255,255,0);
2097
+ --fig-canvas-control-stroke-halo: var(--fig-canvas-control-radius-stroke-halo);
2098
+ --fig-canvas-control-stroke-halo-hover: rgba(255,255,255,1);
2097
2099
  --fig-canvas-control-line-stroke-hover: #0D99FF;
2098
2100
  --fig-canvas-control-line-stroke-active: #0D99FF;
2099
2101
  --fig-canvas-control-line-stroke-width: 1px;
2100
2102
  --fig-canvas-control-line-stroke-width-hover: 2px;
2101
2103
 
2102
- &:has(.fig-canvas-control-radius-hit:hover) {
2104
+ &:has(
2105
+ .fig-canvas-control-radius-hit:hover,
2106
+ .fig-canvas-control-angle-line-hit:hover
2107
+ ) {
2103
2108
  --fig-canvas-control-line-stroke-width: var(--fig-canvas-control-line-stroke-width-hover);
2109
+ --fig-canvas-control-stroke-halo: var(--fig-canvas-control-stroke-halo-hover);
2104
2110
  }
2105
2111
 
2106
2112
  & > fig-handle,
@@ -2164,7 +2170,7 @@ fig-canvas-control {
2164
2170
  }
2165
2171
 
2166
2172
  .fig-canvas-control-radius-halo {
2167
- stroke: var(--fig-canvas-control-radius-stroke-halo);
2173
+ stroke: var(--fig-canvas-control-stroke-halo);
2168
2174
  stroke-width: calc(var(--fig-canvas-control-line-stroke-width) + 2px);
2169
2175
  filter: none;
2170
2176
  }
@@ -2195,7 +2201,7 @@ fig-canvas-control {
2195
2201
  }
2196
2202
 
2197
2203
  .fig-canvas-control-angle-line-halo {
2198
- stroke: var(--fig-canvas-control-radius-stroke-halo);
2204
+ stroke: var(--fig-canvas-control-stroke-halo);
2199
2205
  stroke-width: calc(var(--fig-canvas-control-line-stroke-width) + 2px);
2200
2206
  stroke-linecap: butt;
2201
2207
  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 `${Math.round(this.#x)}%, ${Math.round(this.#y)}%`;
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 `${Math.round(this.#x2)}%, ${Math.round(this.#y2)}%`;
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
- if (this.#radiusIsPercent) return `Radius ${Math.round(this.#radius)}%`;
7486
- return `Radius ${Math.round(this.#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") this.#angle = v.angle;
7564
- if (typeof v.x2 === "number") this.#x2 = v.x2;
7565
- if (typeof v.y2 === "number") this.#y2 = v.y2;
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 = { x: this.#x, y: this.#y };
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
- v.radius = this.#radiusIsPercent ? `${this.#radius}%` : this.#radius;
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 ${Math.round(this.#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", `${Math.round(this.#angle)}°`);
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 = Math.round(Math.max(0, Math.min(100, px * 100)));
8309
- this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
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 = Math.round(Math.max(0, Math.min(100, px * 100)));
8331
- this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
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 = Math.max(0, (dist / rect.width) * 100);
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 ${Math.round(this.#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 ${Math.round(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 = Math.round(Math.max(0, Math.min(100, px * 100)));
8468
- this.#y2 = Math.round(Math.max(0, Math.min(100, py * 100)));
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 = Math.max(0, (dist / rect.width) * 100);
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
@@ -17142,6 +17142,7 @@ const FIG_ICON_TOKENS = {
17142
17142
  globe: { medium: "--icon-24-globe", small: "--icon-16-globe" },
17143
17143
  warning: { medium: "--icon-24-warning", small: "--icon-16-warning" },
17144
17144
  copy: { medium: "--icon-24-copy", small: "--icon-16-copy" },
17145
+ trash: { medium: "--icon-24-trash", small: "--icon-16-trash" },
17145
17146
  };
17146
17147
 
17147
17148
  function figIconCssVar(name, size = "medium") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "8.9.47",
3
+ "version": "8.9.49",
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": "SEE LICENSE IN LICENSE",