@rogieking/figui3 3.6.0 → 3.8.0

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.js CHANGED
@@ -4019,6 +4019,7 @@ class FigFieldSlider extends HTMLElement {
4019
4019
  #slider = null;
4020
4020
  #observer = null;
4021
4021
  #managedSliderAttrs = new Set();
4022
+ #steppersSyncFrame = 0;
4022
4023
  #boundHandleSliderInput = null;
4023
4024
  #boundHandleSliderChange = null;
4024
4025
  #ignoredSliderAttrs = new Set(["variant", "color", "text", "full"]);
@@ -4070,6 +4071,10 @@ class FigFieldSlider extends HTMLElement {
4070
4071
 
4071
4072
  disconnectedCallback() {
4072
4073
  this.#observer?.disconnect();
4074
+ if (this.#steppersSyncFrame) {
4075
+ cancelAnimationFrame(this.#steppersSyncFrame);
4076
+ this.#steppersSyncFrame = 0;
4077
+ }
4073
4078
  this.#unbindSliderEvents();
4074
4079
  }
4075
4080
 
@@ -4180,15 +4185,49 @@ class FigFieldSlider extends HTMLElement {
4180
4185
  }
4181
4186
 
4182
4187
  this.#managedSliderAttrs = nextManaged;
4188
+ this.#queueSteppersSync();
4183
4189
  }
4184
4190
 
4185
4191
  #getForwardedSliderAttrNames() {
4186
- const reserved = new Set(["label", "direction", "oninput", "onchange"]);
4192
+ const reserved = new Set([
4193
+ "label",
4194
+ "direction",
4195
+ "oninput",
4196
+ "onchange",
4197
+ "steppers",
4198
+ ]);
4187
4199
  return this.getAttributeNames().filter(
4188
4200
  (name) => !reserved.has(name) && !this.#ignoredSliderAttrs.has(name),
4189
4201
  );
4190
4202
  }
4191
4203
 
4204
+ #queueSteppersSync() {
4205
+ if (this.#steppersSyncFrame) {
4206
+ cancelAnimationFrame(this.#steppersSyncFrame);
4207
+ }
4208
+ this.#steppersSyncFrame = requestAnimationFrame(() => {
4209
+ this.#steppersSyncFrame = 0;
4210
+ this.#syncSteppersToNumberInput();
4211
+ });
4212
+ }
4213
+
4214
+ #syncSteppersToNumberInput() {
4215
+ if (!this.#slider) return;
4216
+ const numberInput = this.#slider.querySelector("fig-input-number");
4217
+ if (!numberInput) return;
4218
+
4219
+ const hasSteppers =
4220
+ this.hasAttribute("steppers") &&
4221
+ this.getAttribute("steppers") !== "false";
4222
+ if (!hasSteppers) {
4223
+ numberInput.removeAttribute("steppers");
4224
+ return;
4225
+ }
4226
+
4227
+ const steppersValue = this.getAttribute("steppers");
4228
+ numberInput.setAttribute("steppers", steppersValue ?? "");
4229
+ }
4230
+
4192
4231
  #bindSliderEvents() {
4193
4232
  if (!this.#slider) return;
4194
4233
  if (!this.#boundHandleSliderInput) {
@@ -6655,6 +6694,8 @@ class FigEasingCurve extends HTMLElement {
6655
6694
  const size = 200;
6656
6695
  const dropdown = this.#getDropdownHTML();
6657
6696
 
6697
+ const hs = this.#bezierHandleRadius * 2;
6698
+
6658
6699
  if (this.#mode === "spring") {
6659
6700
  const targetY = 40;
6660
6701
  const startY = 180;
@@ -6663,8 +6704,8 @@ class FigEasingCurve extends HTMLElement {
6663
6704
  <line class="fig-easing-curve-target" x1="0" y1="${targetY}" x2="${size}" y2="${targetY}"/>
6664
6705
  <line class="fig-easing-curve-diagonal" x1="0" y1="${startY}" x2="0" y2="${startY}"/>
6665
6706
  <path class="fig-easing-curve-path"/>
6666
- <circle class="fig-easing-curve-handle" data-handle="bounce" r="${this.#springHandleRadius}"/>
6667
- <rect class="fig-easing-curve-duration-bar" data-handle="duration" width="${this.#durationBarWidth}" height="${this.#durationBarHeight}" rx="${this.#durationBarRadius}" ry="${this.#durationBarRadius}"/>
6707
+ <foreignObject class="fig-easing-curve-handle" data-handle="bounce" width="${hs}" height="${hs}"><fig-handle></fig-handle></foreignObject>
6708
+ <foreignObject class="fig-easing-curve-handle fig-easing-curve-duration-bar" data-handle="duration" width="${this.#durationBarWidth}" height="${this.#durationBarHeight}"><fig-handle></fig-handle></foreignObject>
6668
6709
  </svg></div>`;
6669
6710
  }
6670
6711
 
@@ -6676,8 +6717,8 @@ class FigEasingCurve extends HTMLElement {
6676
6717
  <path class="fig-easing-curve-path"/>
6677
6718
  <circle class="fig-easing-curve-endpoint" data-endpoint="start" r="${this.#bezierEndpointRadius}"/>
6678
6719
  <circle class="fig-easing-curve-endpoint" data-endpoint="end" r="${this.#bezierEndpointRadius}"/>
6679
- <circle class="fig-easing-curve-handle" data-handle="1" r="${this.#bezierHandleRadius}"/>
6680
- <circle class="fig-easing-curve-handle" data-handle="2" r="${this.#bezierHandleRadius}"/>
6720
+ <foreignObject class="fig-easing-curve-handle" data-handle="1" width="${hs}" height="${hs}"><fig-handle></fig-handle></foreignObject>
6721
+ <foreignObject class="fig-easing-curve-handle" data-handle="2" width="${hs}" height="${hs}"><fig-handle></fig-handle></foreignObject>
6681
6722
  </svg></div>`;
6682
6723
  }
6683
6724
 
@@ -6821,10 +6862,11 @@ class FigEasingCurve extends HTMLElement {
6821
6862
  this.#line2.setAttribute("y1", p3.y);
6822
6863
  this.#line2.setAttribute("x2", p2.x);
6823
6864
  this.#line2.setAttribute("y2", p2.y);
6824
- this.#handle1.setAttribute("cx", p1.x);
6825
- this.#handle1.setAttribute("cy", p1.y);
6826
- this.#handle2.setAttribute("cx", p2.x);
6827
- this.#handle2.setAttribute("cy", p2.y);
6865
+ const hr = this.#bezierHandleRadius;
6866
+ this.#handle1.setAttribute("x", p1.x - hr);
6867
+ this.#handle1.setAttribute("y", p1.y - hr);
6868
+ this.#handle2.setAttribute("x", p2.x - hr);
6869
+ this.#handle2.setAttribute("y", p2.y - hr);
6828
6870
  if (this.#bezierEndpointStart) {
6829
6871
  this.#bezierEndpointStart.setAttribute("cx", p0.x);
6830
6872
  this.#bezierEndpointStart.setAttribute("cy", p0.y);
@@ -6891,8 +6933,9 @@ class FigEasingCurve extends HTMLElement {
6891
6933
  const peak = this.#findPeakOvershoot(points);
6892
6934
  const peakNorm = (peak.t / totalTime) * durationNorm;
6893
6935
  const peakPt = this.#springToSVG(peakNorm, peak.value);
6894
- this.#handle1.setAttribute("cx", peakPt.x);
6895
- this.#handle1.setAttribute("cy", peakPt.y);
6936
+ const hr = this.#bezierHandleRadius;
6937
+ this.#handle1.setAttribute("x", peakPt.x - hr);
6938
+ this.#handle1.setAttribute("y", peakPt.y - hr);
6896
6939
 
6897
6940
  // Duration handle: on the target line
6898
6941
  const targetPt = this.#springToSVG(durationNorm, 1);
@@ -6984,8 +7027,7 @@ class FigEasingCurve extends HTMLElement {
6984
7027
  );
6985
7028
  if (bezierSurface) {
6986
7029
  bezierSurface.addEventListener("pointerdown", (e) => {
6987
- // Handles keep their own direct drag behavior.
6988
- if (e.target?.closest?.(".fig-easing-curve-handle")) return;
7030
+ if (e.target?.closest?.(".fig-easing-curve-handle, fig-handle")) return;
6989
7031
  this.#startBezierDrag(e, this.#bezierHandleForClientHalf(e));
6990
7032
  });
6991
7033
  }
@@ -7004,8 +7046,7 @@ class FigEasingCurve extends HTMLElement {
7004
7046
  );
7005
7047
  if (springSurface) {
7006
7048
  springSurface.addEventListener("pointerdown", (e) => {
7007
- // Bounce handle keeps its own drag mode/cursor.
7008
- if (e.target?.closest?.(".fig-easing-curve-handle")) return;
7049
+ if (e.target?.closest?.(".fig-easing-curve-handle, fig-handle")) return;
7009
7050
  this.#startSpringDrag(e, "duration");
7010
7051
  });
7011
7052
  }
@@ -7680,14 +7721,14 @@ class FigOriginGrid extends HTMLElement {
7680
7721
  this.innerHTML = `<div class="fig-origin-grid-surface">
7681
7722
  <div class="origin-grid" aria-label="Transform origin grid">
7682
7723
  <div class="origin-grid-cells">${cells}</div>
7683
- <span class="origin-grid-handle"></span>
7724
+ <fig-handle></fig-handle>
7684
7725
  </div>
7685
7726
  </div>
7686
7727
  ${fieldsMarkup}`;
7687
7728
 
7688
7729
  this.#grid = this.querySelector(".origin-grid");
7689
7730
  this.#cells = Array.from(this.querySelectorAll(".origin-grid-cell"));
7690
- this.#handle = this.querySelector(".origin-grid-handle");
7731
+ this.#handle = this.querySelector("fig-handle");
7691
7732
  this.#xInput = this.querySelector('fig-input-number[name="x"]');
7692
7733
  this.#yInput = this.querySelector('fig-input-number[name="y"]');
7693
7734
  this.#syncHandlePosition();
@@ -7991,18 +8032,19 @@ class FigInputJoystick extends HTMLElement {
7991
8032
  #boundXFocusOut = null;
7992
8033
  #boundYFocusOut = null;
7993
8034
  #isSyncingValueAttr = false;
8035
+ #defaultPosition = { x: 0.5, y: 0.5 };
7994
8036
 
7995
8037
  constructor() {
7996
8038
  super();
7997
8039
 
7998
- this.position = { x: 0.5, y: 0.5 }; // Internal position (0-1)
8040
+ this.position = { x: 0.5, y: 0.5 };
7999
8041
  this.isDragging = false;
8000
8042
  this.isShiftHeld = false;
8001
8043
  this.plane = null;
8002
8044
  this.cursor = null;
8003
8045
  this.xInput = null;
8004
8046
  this.yInput = null;
8005
- this.coordinates = "screen"; // "screen" (0,0 top-left) or "math" (0,0 bottom-left)
8047
+ this.coordinates = "screen";
8006
8048
  this.#initialized = false;
8007
8049
  this.#boundMouseDown = (e) => this.#handleMouseDown(e);
8008
8050
  this.#boundTouchStart = (e) => this.#handleTouchStart(e);
@@ -8033,6 +8075,7 @@ class FigInputJoystick extends HTMLElement {
8033
8075
  this.#setupListeners();
8034
8076
  this.#syncHandlePosition();
8035
8077
  this.#syncValueAttribute();
8078
+ this.#syncResetButton();
8036
8079
  this.#initialized = true;
8037
8080
  });
8038
8081
  }
@@ -8112,8 +8155,13 @@ class FigInputJoystick extends HTMLElement {
8112
8155
  ${labelsMarkup}
8113
8156
  <div class="fig-input-joystick-plane">
8114
8157
  <div class="fig-input-joystick-guides"></div>
8115
- <div class="fig-input-joystick-handle"></div>
8158
+ <fig-handle></fig-handle>
8116
8159
  </div>
8160
+ <fig-tooltip text="Reset">
8161
+ <fig-button variant="ghost" icon="true" class="fig-joystick-reset" aria-label="Reset to default">
8162
+ <span class="fig-mask-icon" style="--icon: var(--icon-reset)"></span>
8163
+ </fig-button>
8164
+ </fig-tooltip>
8117
8165
  </div>
8118
8166
  ${
8119
8167
  this.#fieldsEnabled
@@ -8144,13 +8192,17 @@ class FigInputJoystick extends HTMLElement {
8144
8192
 
8145
8193
  #setupListeners() {
8146
8194
  this.plane = this.querySelector(".fig-input-joystick-plane");
8147
- this.cursor = this.querySelector(".fig-input-joystick-handle");
8195
+ this.cursor = this.querySelector("fig-handle");
8148
8196
  this.xInput = this.querySelector("fig-input-number[name='x']");
8149
8197
  this.yInput = this.querySelector("fig-input-number[name='y']");
8150
8198
  this.plane.addEventListener("mousedown", this.#boundMouseDown);
8151
8199
  this.plane.addEventListener("touchstart", this.#boundTouchStart);
8152
8200
  window.addEventListener("keydown", this.#boundKeyDown);
8153
8201
  window.addEventListener("keyup", this.#boundKeyUp);
8202
+ const resetBtn = this.querySelector(".fig-joystick-reset");
8203
+ if (resetBtn) {
8204
+ resetBtn.addEventListener("click", () => this.#resetToDefault());
8205
+ }
8154
8206
  if (this.#fieldsEnabled && this.xInput && this.yInput) {
8155
8207
  this.xInput.addEventListener("input", this.#boundXInput);
8156
8208
  this.xInput.addEventListener("change", this.#boundXInput);
@@ -8279,13 +8331,33 @@ class FigInputJoystick extends HTMLElement {
8279
8331
 
8280
8332
  #syncValueAttribute() {
8281
8333
  const next = this.value;
8282
- if (this.getAttribute("value") === next) return;
8283
- this.#isSyncingValueAttr = true;
8284
- this.setAttribute("value", next);
8285
- this.#isSyncingValueAttr = false;
8334
+ if (this.getAttribute("value") !== next) {
8335
+ this.#isSyncingValueAttr = true;
8336
+ this.setAttribute("value", next);
8337
+ this.#isSyncingValueAttr = false;
8338
+ }
8339
+ this.#syncResetButton();
8340
+ }
8341
+
8342
+ #syncResetButton() {
8343
+ const d = this.#defaultPosition;
8344
+ const isDefault =
8345
+ Math.round(this.position.x * 100) === Math.round(d.x * 100) &&
8346
+ Math.round(this.position.y * 100) === Math.round(d.y * 100);
8347
+ this.toggleAttribute("default", isDefault);
8348
+ this.style.setProperty("--is-not-default", isDefault ? "0" : "1");
8349
+ }
8350
+
8351
+ #resetToDefault() {
8352
+ this.position = { ...this.#defaultPosition };
8353
+ this.#syncHandlePosition();
8354
+ this.#syncValueAttribute();
8355
+ this.#emitInputEvent();
8356
+ this.#emitChangeEvent();
8286
8357
  }
8287
8358
 
8288
8359
  #handleMouseDown(e) {
8360
+ if (e.target.closest(".fig-joystick-reset, fig-tooltip")) return;
8289
8361
  this.isDragging = true;
8290
8362
 
8291
8363
  this.#updatePosition(e);
@@ -8312,6 +8384,7 @@ class FigInputJoystick extends HTMLElement {
8312
8384
  }
8313
8385
 
8314
8386
  #handleTouchStart(e) {
8387
+ if (e.target.closest(".fig-joystick-reset, fig-tooltip")) return;
8315
8388
  e.preventDefault();
8316
8389
  this.isDragging = true;
8317
8390
  this.#updatePosition(e.touches[0]);
@@ -8380,6 +8453,7 @@ class FigInputJoystick extends HTMLElement {
8380
8453
  }
8381
8454
  if (this.#initialized) {
8382
8455
  this.#syncHandlePosition();
8456
+ this.#syncResetButton();
8383
8457
  }
8384
8458
  }
8385
8459
  attributeChangedCallback(name, oldValue, newValue) {
@@ -11271,3 +11345,11 @@ class FigChooser extends HTMLElement {
11271
11345
  }
11272
11346
  }
11273
11347
  customElements.define("fig-chooser", FigChooser);
11348
+
11349
+ /* Handle */
11350
+ class FigHandle extends HTMLElement {
11351
+ constructor() {
11352
+ super();
11353
+ }
11354
+ }
11355
+ customElements.define("fig-handle", FigHandle);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "3.6.0",
3
+ "version": "3.8.0",
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",