@rogieking/figui3 2.10.5 → 2.11.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 +209 -26
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -4554,14 +4554,16 @@ customElements.define("fig-input-joystick", FigInputJoystick);
|
|
|
4554
4554
|
* @attr {number} opposite - The opposite value of the angle.
|
|
4555
4555
|
*/
|
|
4556
4556
|
class FigInputAngle extends HTMLElement {
|
|
4557
|
-
//
|
|
4557
|
+
// Private fields
|
|
4558
4558
|
#adjacent;
|
|
4559
4559
|
#opposite;
|
|
4560
|
+
#prevRawAngle = null;
|
|
4561
|
+
#boundHandleRawChange;
|
|
4560
4562
|
|
|
4561
4563
|
constructor() {
|
|
4562
4564
|
super();
|
|
4563
4565
|
|
|
4564
|
-
this.angle = 0;
|
|
4566
|
+
this.angle = 0;
|
|
4565
4567
|
this.#adjacent = 1;
|
|
4566
4568
|
this.#opposite = 0;
|
|
4567
4569
|
this.isDragging = false;
|
|
@@ -4569,6 +4571,11 @@ class FigInputAngle extends HTMLElement {
|
|
|
4569
4571
|
this.handle = null;
|
|
4570
4572
|
this.angleInput = null;
|
|
4571
4573
|
this.plane = null;
|
|
4574
|
+
this.units = "°";
|
|
4575
|
+
this.min = null;
|
|
4576
|
+
this.max = null;
|
|
4577
|
+
|
|
4578
|
+
this.#boundHandleRawChange = this.#handleRawChange.bind(this);
|
|
4572
4579
|
}
|
|
4573
4580
|
|
|
4574
4581
|
connectedCallback() {
|
|
@@ -4577,8 +4584,18 @@ class FigInputAngle extends HTMLElement {
|
|
|
4577
4584
|
this.precision = parseInt(this.precision);
|
|
4578
4585
|
this.text = this.getAttribute("text") === "true";
|
|
4579
4586
|
|
|
4580
|
-
this
|
|
4587
|
+
let rawUnits = this.getAttribute("units") || "°";
|
|
4588
|
+
if (rawUnits === "deg") rawUnits = "°";
|
|
4589
|
+
this.units = rawUnits;
|
|
4590
|
+
|
|
4591
|
+
this.min = this.hasAttribute("min")
|
|
4592
|
+
? Number(this.getAttribute("min"))
|
|
4593
|
+
: null;
|
|
4594
|
+
this.max = this.hasAttribute("max")
|
|
4595
|
+
? Number(this.getAttribute("max"))
|
|
4596
|
+
: null;
|
|
4581
4597
|
|
|
4598
|
+
this.#render();
|
|
4582
4599
|
this.#setupListeners();
|
|
4583
4600
|
|
|
4584
4601
|
this.#syncHandlePosition();
|
|
@@ -4600,25 +4617,89 @@ class FigInputAngle extends HTMLElement {
|
|
|
4600
4617
|
}
|
|
4601
4618
|
|
|
4602
4619
|
#getInnerHTML() {
|
|
4620
|
+
const step = this.#getStepForUnit();
|
|
4621
|
+
const minAttr = this.min !== null ? `min="${this.min}"` : "";
|
|
4622
|
+
const maxAttr = this.max !== null ? `max="${this.max}"` : "";
|
|
4603
4623
|
return `
|
|
4604
4624
|
<div class="fig-input-angle-plane" tabindex="0">
|
|
4605
4625
|
<div class="fig-input-angle-handle"></div>
|
|
4606
4626
|
</div>
|
|
4607
4627
|
${
|
|
4608
4628
|
this.text
|
|
4609
|
-
? `<fig-input-number
|
|
4629
|
+
? `<fig-input-number
|
|
4610
4630
|
name="angle"
|
|
4611
|
-
step="
|
|
4631
|
+
step="${step}"
|
|
4612
4632
|
value="${this.angle}"
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
units="
|
|
4633
|
+
${minAttr}
|
|
4634
|
+
${maxAttr}
|
|
4635
|
+
units="${this.units}">
|
|
4616
4636
|
</fig-input-number>`
|
|
4617
4637
|
: ""
|
|
4618
4638
|
}
|
|
4619
4639
|
`;
|
|
4620
4640
|
}
|
|
4621
4641
|
|
|
4642
|
+
#getStepForUnit() {
|
|
4643
|
+
switch (this.units) {
|
|
4644
|
+
case "rad":
|
|
4645
|
+
return 0.01;
|
|
4646
|
+
case "turn":
|
|
4647
|
+
return 0.001;
|
|
4648
|
+
default:
|
|
4649
|
+
return 0.1;
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
|
|
4653
|
+
// --- Unit conversion helpers ---
|
|
4654
|
+
|
|
4655
|
+
#toDegrees(value) {
|
|
4656
|
+
switch (this.units) {
|
|
4657
|
+
case "rad":
|
|
4658
|
+
return (value * 180) / Math.PI;
|
|
4659
|
+
case "turn":
|
|
4660
|
+
return value * 360;
|
|
4661
|
+
default:
|
|
4662
|
+
return value;
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
|
|
4666
|
+
#fromDegrees(degrees) {
|
|
4667
|
+
switch (this.units) {
|
|
4668
|
+
case "rad":
|
|
4669
|
+
return (degrees * Math.PI) / 180;
|
|
4670
|
+
case "turn":
|
|
4671
|
+
return degrees / 360;
|
|
4672
|
+
default:
|
|
4673
|
+
return degrees;
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4677
|
+
#convertAngle(value, fromUnit, toUnit) {
|
|
4678
|
+
// Convert to degrees first
|
|
4679
|
+
let degrees;
|
|
4680
|
+
switch (fromUnit) {
|
|
4681
|
+
case "rad":
|
|
4682
|
+
degrees = (value * 180) / Math.PI;
|
|
4683
|
+
break;
|
|
4684
|
+
case "turn":
|
|
4685
|
+
degrees = value * 360;
|
|
4686
|
+
break;
|
|
4687
|
+
default:
|
|
4688
|
+
degrees = value;
|
|
4689
|
+
}
|
|
4690
|
+
// Convert from degrees to target
|
|
4691
|
+
switch (toUnit) {
|
|
4692
|
+
case "rad":
|
|
4693
|
+
return (degrees * Math.PI) / 180;
|
|
4694
|
+
case "turn":
|
|
4695
|
+
return degrees / 360;
|
|
4696
|
+
default:
|
|
4697
|
+
return degrees;
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
|
|
4701
|
+
// --- Event listeners ---
|
|
4702
|
+
|
|
4622
4703
|
#setupListeners() {
|
|
4623
4704
|
this.handle = this.querySelector(".fig-input-angle-handle");
|
|
4624
4705
|
this.plane = this.querySelector(".fig-input-angle-plane");
|
|
@@ -4636,16 +4717,35 @@ class FigInputAngle extends HTMLElement {
|
|
|
4636
4717
|
this.#handleAngleInput.bind(this),
|
|
4637
4718
|
);
|
|
4638
4719
|
}
|
|
4720
|
+
// Capture-phase listener for unit suffix parsing
|
|
4721
|
+
this.addEventListener("change", this.#boundHandleRawChange, true);
|
|
4639
4722
|
}
|
|
4640
4723
|
|
|
4641
4724
|
#cleanupListeners() {
|
|
4642
|
-
this.plane
|
|
4643
|
-
this.plane
|
|
4725
|
+
this.plane?.removeEventListener("mousedown", this.#handleMouseDown);
|
|
4726
|
+
this.plane?.removeEventListener("touchstart", this.#handleTouchStart);
|
|
4644
4727
|
window.removeEventListener("keydown", this.#handleKeyDown);
|
|
4645
4728
|
window.removeEventListener("keyup", this.#handleKeyUp);
|
|
4646
4729
|
if (this.text && this.angleInput) {
|
|
4647
4730
|
this.angleInput.removeEventListener("input", this.#handleAngleInput);
|
|
4648
4731
|
}
|
|
4732
|
+
this.removeEventListener("change", this.#boundHandleRawChange, true);
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4735
|
+
#handleRawChange(e) {
|
|
4736
|
+
// Only intercept native change events from the raw <input> element
|
|
4737
|
+
if (!e.target?.matches?.("input")) return;
|
|
4738
|
+
const raw = e.target.value;
|
|
4739
|
+
const match = raw.match(/^(-?\d*\.?\d+)\s*(turn|rad|deg|°)$/i);
|
|
4740
|
+
if (match) {
|
|
4741
|
+
const num = parseFloat(match[1]);
|
|
4742
|
+
let fromUnit = match[2].toLowerCase();
|
|
4743
|
+
if (fromUnit === "deg") fromUnit = "°";
|
|
4744
|
+
if (fromUnit !== this.units) {
|
|
4745
|
+
const converted = this.#convertAngle(num, fromUnit, this.units);
|
|
4746
|
+
e.target.value = String(converted);
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4649
4749
|
}
|
|
4650
4750
|
|
|
4651
4751
|
#handleAngleInput(e) {
|
|
@@ -4657,8 +4757,11 @@ class FigInputAngle extends HTMLElement {
|
|
|
4657
4757
|
this.#emitChangeEvent();
|
|
4658
4758
|
}
|
|
4659
4759
|
|
|
4760
|
+
// --- Angle calculation ---
|
|
4761
|
+
|
|
4660
4762
|
#calculateAdjacentAndOpposite() {
|
|
4661
|
-
const
|
|
4763
|
+
const degrees = this.#toDegrees(this.angle);
|
|
4764
|
+
const radians = (degrees * Math.PI) / 180;
|
|
4662
4765
|
this.#adjacent = Math.cos(radians);
|
|
4663
4766
|
this.#opposite = Math.sin(radians);
|
|
4664
4767
|
}
|
|
@@ -4669,16 +4772,46 @@ class FigInputAngle extends HTMLElement {
|
|
|
4669
4772
|
return Math.round(angle / increment) * increment;
|
|
4670
4773
|
}
|
|
4671
4774
|
|
|
4672
|
-
#
|
|
4775
|
+
#getRawAngle(e) {
|
|
4673
4776
|
const rect = this.plane.getBoundingClientRect();
|
|
4674
4777
|
const centerX = rect.left + rect.width / 2;
|
|
4675
4778
|
const centerY = rect.top + rect.height / 2;
|
|
4676
4779
|
const deltaX = e.clientX - centerX;
|
|
4677
4780
|
const deltaY = e.clientY - centerY;
|
|
4678
|
-
|
|
4781
|
+
return (Math.atan2(deltaY, deltaX) * 180) / Math.PI;
|
|
4782
|
+
}
|
|
4679
4783
|
|
|
4680
|
-
|
|
4681
|
-
|
|
4784
|
+
#updateAngle(e) {
|
|
4785
|
+
let rawAngle = this.#getRawAngle(e);
|
|
4786
|
+
// Normalize to 0-360 for snap and positioning
|
|
4787
|
+
let normalizedAngle = ((rawAngle % 360) + 360) % 360;
|
|
4788
|
+
normalizedAngle = this.#snapToIncrement(normalizedAngle);
|
|
4789
|
+
|
|
4790
|
+
const isBounded = this.min !== null || this.max !== null;
|
|
4791
|
+
|
|
4792
|
+
if (isBounded) {
|
|
4793
|
+
// Bounded: absolute position
|
|
4794
|
+
this.angle = this.#fromDegrees(normalizedAngle);
|
|
4795
|
+
} else {
|
|
4796
|
+
// Unbounded: cumulative winding
|
|
4797
|
+
if (this.#prevRawAngle === null) {
|
|
4798
|
+
// First event of this drag — snap to clicked position, preserving revolution
|
|
4799
|
+
this.#prevRawAngle = normalizedAngle;
|
|
4800
|
+
const currentDeg = this.#toDegrees(this.angle);
|
|
4801
|
+
const currentMod = ((currentDeg % 360) + 360) % 360;
|
|
4802
|
+
let delta = normalizedAngle - currentMod;
|
|
4803
|
+
if (delta > 180) delta -= 360;
|
|
4804
|
+
if (delta < -180) delta += 360;
|
|
4805
|
+
this.angle += this.#fromDegrees(delta);
|
|
4806
|
+
} else {
|
|
4807
|
+
// Subsequent events — accumulate delta
|
|
4808
|
+
let delta = normalizedAngle - this.#prevRawAngle;
|
|
4809
|
+
if (delta > 180) delta -= 360;
|
|
4810
|
+
if (delta < -180) delta += 360;
|
|
4811
|
+
this.angle += this.#fromDegrees(delta);
|
|
4812
|
+
this.#prevRawAngle = normalizedAngle;
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4682
4815
|
|
|
4683
4816
|
this.#calculateAdjacentAndOpposite();
|
|
4684
4817
|
|
|
@@ -4690,6 +4823,8 @@ class FigInputAngle extends HTMLElement {
|
|
|
4690
4823
|
this.#emitInputEvent();
|
|
4691
4824
|
}
|
|
4692
4825
|
|
|
4826
|
+
// --- Event dispatching ---
|
|
4827
|
+
|
|
4693
4828
|
#emitInputEvent() {
|
|
4694
4829
|
this.dispatchEvent(
|
|
4695
4830
|
new CustomEvent("input", {
|
|
@@ -4708,9 +4843,12 @@ class FigInputAngle extends HTMLElement {
|
|
|
4708
4843
|
);
|
|
4709
4844
|
}
|
|
4710
4845
|
|
|
4846
|
+
// --- Handle position ---
|
|
4847
|
+
|
|
4711
4848
|
#syncHandlePosition() {
|
|
4712
4849
|
if (this.handle) {
|
|
4713
|
-
const
|
|
4850
|
+
const degrees = this.#toDegrees(this.angle);
|
|
4851
|
+
const radians = (degrees * Math.PI) / 180;
|
|
4714
4852
|
const radius = this.plane.offsetWidth / 2 - this.handle.offsetWidth / 2;
|
|
4715
4853
|
const x = Math.cos(radians) * radius;
|
|
4716
4854
|
const y = Math.sin(radians) * radius;
|
|
@@ -4718,8 +4856,11 @@ class FigInputAngle extends HTMLElement {
|
|
|
4718
4856
|
}
|
|
4719
4857
|
}
|
|
4720
4858
|
|
|
4859
|
+
// --- Mouse/Touch handlers ---
|
|
4860
|
+
|
|
4721
4861
|
#handleMouseDown(e) {
|
|
4722
4862
|
this.isDragging = true;
|
|
4863
|
+
this.#prevRawAngle = null;
|
|
4723
4864
|
this.#updateAngle(e);
|
|
4724
4865
|
|
|
4725
4866
|
const handleMouseMove = (e) => {
|
|
@@ -4729,6 +4870,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
4729
4870
|
|
|
4730
4871
|
const handleMouseUp = () => {
|
|
4731
4872
|
this.isDragging = false;
|
|
4873
|
+
this.#prevRawAngle = null;
|
|
4732
4874
|
this.plane.classList.remove("dragging");
|
|
4733
4875
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
4734
4876
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
@@ -4742,6 +4884,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
4742
4884
|
#handleTouchStart(e) {
|
|
4743
4885
|
e.preventDefault();
|
|
4744
4886
|
this.isDragging = true;
|
|
4887
|
+
this.#prevRawAngle = null;
|
|
4745
4888
|
this.#updateAngle(e.touches[0]);
|
|
4746
4889
|
|
|
4747
4890
|
const handleTouchMove = (e) => {
|
|
@@ -4751,6 +4894,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
4751
4894
|
|
|
4752
4895
|
const handleTouchEnd = () => {
|
|
4753
4896
|
this.isDragging = false;
|
|
4897
|
+
this.#prevRawAngle = null;
|
|
4754
4898
|
this.plane.classList.remove("dragging");
|
|
4755
4899
|
window.removeEventListener("touchmove", handleTouchMove);
|
|
4756
4900
|
window.removeEventListener("touchend", handleTouchEnd);
|
|
@@ -4761,6 +4905,8 @@ class FigInputAngle extends HTMLElement {
|
|
|
4761
4905
|
window.addEventListener("touchend", handleTouchEnd);
|
|
4762
4906
|
}
|
|
4763
4907
|
|
|
4908
|
+
// --- Keyboard handlers ---
|
|
4909
|
+
|
|
4764
4910
|
#handleKeyDown(e) {
|
|
4765
4911
|
if (e.key === "Shift") this.isShiftHeld = true;
|
|
4766
4912
|
}
|
|
@@ -4773,8 +4919,10 @@ class FigInputAngle extends HTMLElement {
|
|
|
4773
4919
|
this.plane?.focus();
|
|
4774
4920
|
}
|
|
4775
4921
|
|
|
4922
|
+
// --- Attributes ---
|
|
4923
|
+
|
|
4776
4924
|
static get observedAttributes() {
|
|
4777
|
-
return ["value", "precision", "text"];
|
|
4925
|
+
return ["value", "precision", "text", "min", "max", "units"];
|
|
4778
4926
|
}
|
|
4779
4927
|
|
|
4780
4928
|
get value() {
|
|
@@ -4800,15 +4948,50 @@ class FigInputAngle extends HTMLElement {
|
|
|
4800
4948
|
}
|
|
4801
4949
|
|
|
4802
4950
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4951
|
+
switch (name) {
|
|
4952
|
+
case "value":
|
|
4953
|
+
this.value = Number(newValue);
|
|
4954
|
+
break;
|
|
4955
|
+
case "precision":
|
|
4956
|
+
this.precision = parseInt(newValue);
|
|
4957
|
+
break;
|
|
4958
|
+
case "text":
|
|
4959
|
+
if (newValue !== oldValue) {
|
|
4960
|
+
this.text = newValue?.toLowerCase() === "true";
|
|
4961
|
+
if (this.plane) {
|
|
4962
|
+
this.#render();
|
|
4963
|
+
this.#setupListeners();
|
|
4964
|
+
this.#syncHandlePosition();
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
break;
|
|
4968
|
+
case "units": {
|
|
4969
|
+
let units = newValue || "°";
|
|
4970
|
+
if (units === "deg") units = "°";
|
|
4971
|
+
this.units = units;
|
|
4972
|
+
if (this.plane) {
|
|
4973
|
+
this.#render();
|
|
4974
|
+
this.#setupListeners();
|
|
4975
|
+
this.#syncHandlePosition();
|
|
4976
|
+
}
|
|
4977
|
+
break;
|
|
4978
|
+
}
|
|
4979
|
+
case "min":
|
|
4980
|
+
this.min = newValue !== null ? Number(newValue) : null;
|
|
4981
|
+
if (this.plane) {
|
|
4982
|
+
this.#render();
|
|
4983
|
+
this.#setupListeners();
|
|
4984
|
+
this.#syncHandlePosition();
|
|
4985
|
+
}
|
|
4986
|
+
break;
|
|
4987
|
+
case "max":
|
|
4988
|
+
this.max = newValue !== null ? Number(newValue) : null;
|
|
4989
|
+
if (this.plane) {
|
|
4990
|
+
this.#render();
|
|
4991
|
+
this.#setupListeners();
|
|
4992
|
+
this.#syncHandlePosition();
|
|
4993
|
+
}
|
|
4994
|
+
break;
|
|
4812
4995
|
}
|
|
4813
4996
|
}
|
|
4814
4997
|
}
|
package/package.json
CHANGED