@rogieking/figui3 2.10.4 → 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/components.css +1 -0
- package/fig.js +217 -26
- package/package.json +1 -1
package/components.css
CHANGED
package/fig.js
CHANGED
|
@@ -1587,6 +1587,8 @@ class FigSlider extends HTMLElement {
|
|
|
1587
1587
|
"color",
|
|
1588
1588
|
"units",
|
|
1589
1589
|
"transform",
|
|
1590
|
+
"text",
|
|
1591
|
+
"default",
|
|
1590
1592
|
];
|
|
1591
1593
|
}
|
|
1592
1594
|
|
|
@@ -1611,6 +1613,8 @@ class FigSlider extends HTMLElement {
|
|
|
1611
1613
|
break;
|
|
1612
1614
|
case "value":
|
|
1613
1615
|
this.value = newValue;
|
|
1616
|
+
this.input.value = newValue;
|
|
1617
|
+
this.#syncProperties();
|
|
1614
1618
|
if (this.figInputNumber) {
|
|
1615
1619
|
this.figInputNumber.setAttribute("value", newValue);
|
|
1616
1620
|
}
|
|
@@ -1621,6 +1625,10 @@ class FigSlider extends HTMLElement {
|
|
|
1621
1625
|
this.figInputNumber.setAttribute("transform", this.transform);
|
|
1622
1626
|
}
|
|
1623
1627
|
break;
|
|
1628
|
+
case "default":
|
|
1629
|
+
this.default = newValue;
|
|
1630
|
+
this.#syncProperties();
|
|
1631
|
+
break;
|
|
1624
1632
|
case "min":
|
|
1625
1633
|
case "max":
|
|
1626
1634
|
case "step":
|
|
@@ -4546,14 +4554,16 @@ customElements.define("fig-input-joystick", FigInputJoystick);
|
|
|
4546
4554
|
* @attr {number} opposite - The opposite value of the angle.
|
|
4547
4555
|
*/
|
|
4548
4556
|
class FigInputAngle extends HTMLElement {
|
|
4549
|
-
//
|
|
4557
|
+
// Private fields
|
|
4550
4558
|
#adjacent;
|
|
4551
4559
|
#opposite;
|
|
4560
|
+
#prevRawAngle = null;
|
|
4561
|
+
#boundHandleRawChange;
|
|
4552
4562
|
|
|
4553
4563
|
constructor() {
|
|
4554
4564
|
super();
|
|
4555
4565
|
|
|
4556
|
-
this.angle = 0;
|
|
4566
|
+
this.angle = 0;
|
|
4557
4567
|
this.#adjacent = 1;
|
|
4558
4568
|
this.#opposite = 0;
|
|
4559
4569
|
this.isDragging = false;
|
|
@@ -4561,6 +4571,11 @@ class FigInputAngle extends HTMLElement {
|
|
|
4561
4571
|
this.handle = null;
|
|
4562
4572
|
this.angleInput = null;
|
|
4563
4573
|
this.plane = null;
|
|
4574
|
+
this.units = "°";
|
|
4575
|
+
this.min = null;
|
|
4576
|
+
this.max = null;
|
|
4577
|
+
|
|
4578
|
+
this.#boundHandleRawChange = this.#handleRawChange.bind(this);
|
|
4564
4579
|
}
|
|
4565
4580
|
|
|
4566
4581
|
connectedCallback() {
|
|
@@ -4569,8 +4584,18 @@ class FigInputAngle extends HTMLElement {
|
|
|
4569
4584
|
this.precision = parseInt(this.precision);
|
|
4570
4585
|
this.text = this.getAttribute("text") === "true";
|
|
4571
4586
|
|
|
4572
|
-
this
|
|
4587
|
+
let rawUnits = this.getAttribute("units") || "°";
|
|
4588
|
+
if (rawUnits === "deg") rawUnits = "°";
|
|
4589
|
+
this.units = rawUnits;
|
|
4573
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;
|
|
4597
|
+
|
|
4598
|
+
this.#render();
|
|
4574
4599
|
this.#setupListeners();
|
|
4575
4600
|
|
|
4576
4601
|
this.#syncHandlePosition();
|
|
@@ -4592,25 +4617,89 @@ class FigInputAngle extends HTMLElement {
|
|
|
4592
4617
|
}
|
|
4593
4618
|
|
|
4594
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}"` : "";
|
|
4595
4623
|
return `
|
|
4596
4624
|
<div class="fig-input-angle-plane" tabindex="0">
|
|
4597
4625
|
<div class="fig-input-angle-handle"></div>
|
|
4598
4626
|
</div>
|
|
4599
4627
|
${
|
|
4600
4628
|
this.text
|
|
4601
|
-
? `<fig-input-number
|
|
4629
|
+
? `<fig-input-number
|
|
4602
4630
|
name="angle"
|
|
4603
|
-
step="
|
|
4631
|
+
step="${step}"
|
|
4604
4632
|
value="${this.angle}"
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
units="
|
|
4633
|
+
${minAttr}
|
|
4634
|
+
${maxAttr}
|
|
4635
|
+
units="${this.units}">
|
|
4608
4636
|
</fig-input-number>`
|
|
4609
4637
|
: ""
|
|
4610
4638
|
}
|
|
4611
4639
|
`;
|
|
4612
4640
|
}
|
|
4613
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
|
+
|
|
4614
4703
|
#setupListeners() {
|
|
4615
4704
|
this.handle = this.querySelector(".fig-input-angle-handle");
|
|
4616
4705
|
this.plane = this.querySelector(".fig-input-angle-plane");
|
|
@@ -4628,16 +4717,35 @@ class FigInputAngle extends HTMLElement {
|
|
|
4628
4717
|
this.#handleAngleInput.bind(this),
|
|
4629
4718
|
);
|
|
4630
4719
|
}
|
|
4720
|
+
// Capture-phase listener for unit suffix parsing
|
|
4721
|
+
this.addEventListener("change", this.#boundHandleRawChange, true);
|
|
4631
4722
|
}
|
|
4632
4723
|
|
|
4633
4724
|
#cleanupListeners() {
|
|
4634
|
-
this.plane
|
|
4635
|
-
this.plane
|
|
4725
|
+
this.plane?.removeEventListener("mousedown", this.#handleMouseDown);
|
|
4726
|
+
this.plane?.removeEventListener("touchstart", this.#handleTouchStart);
|
|
4636
4727
|
window.removeEventListener("keydown", this.#handleKeyDown);
|
|
4637
4728
|
window.removeEventListener("keyup", this.#handleKeyUp);
|
|
4638
4729
|
if (this.text && this.angleInput) {
|
|
4639
4730
|
this.angleInput.removeEventListener("input", this.#handleAngleInput);
|
|
4640
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
|
+
}
|
|
4641
4749
|
}
|
|
4642
4750
|
|
|
4643
4751
|
#handleAngleInput(e) {
|
|
@@ -4649,8 +4757,11 @@ class FigInputAngle extends HTMLElement {
|
|
|
4649
4757
|
this.#emitChangeEvent();
|
|
4650
4758
|
}
|
|
4651
4759
|
|
|
4760
|
+
// --- Angle calculation ---
|
|
4761
|
+
|
|
4652
4762
|
#calculateAdjacentAndOpposite() {
|
|
4653
|
-
const
|
|
4763
|
+
const degrees = this.#toDegrees(this.angle);
|
|
4764
|
+
const radians = (degrees * Math.PI) / 180;
|
|
4654
4765
|
this.#adjacent = Math.cos(radians);
|
|
4655
4766
|
this.#opposite = Math.sin(radians);
|
|
4656
4767
|
}
|
|
@@ -4661,16 +4772,46 @@ class FigInputAngle extends HTMLElement {
|
|
|
4661
4772
|
return Math.round(angle / increment) * increment;
|
|
4662
4773
|
}
|
|
4663
4774
|
|
|
4664
|
-
#
|
|
4775
|
+
#getRawAngle(e) {
|
|
4665
4776
|
const rect = this.plane.getBoundingClientRect();
|
|
4666
4777
|
const centerX = rect.left + rect.width / 2;
|
|
4667
4778
|
const centerY = rect.top + rect.height / 2;
|
|
4668
4779
|
const deltaX = e.clientX - centerX;
|
|
4669
4780
|
const deltaY = e.clientY - centerY;
|
|
4670
|
-
|
|
4781
|
+
return (Math.atan2(deltaY, deltaX) * 180) / Math.PI;
|
|
4782
|
+
}
|
|
4671
4783
|
|
|
4672
|
-
|
|
4673
|
-
|
|
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
|
+
}
|
|
4674
4815
|
|
|
4675
4816
|
this.#calculateAdjacentAndOpposite();
|
|
4676
4817
|
|
|
@@ -4682,6 +4823,8 @@ class FigInputAngle extends HTMLElement {
|
|
|
4682
4823
|
this.#emitInputEvent();
|
|
4683
4824
|
}
|
|
4684
4825
|
|
|
4826
|
+
// --- Event dispatching ---
|
|
4827
|
+
|
|
4685
4828
|
#emitInputEvent() {
|
|
4686
4829
|
this.dispatchEvent(
|
|
4687
4830
|
new CustomEvent("input", {
|
|
@@ -4700,9 +4843,12 @@ class FigInputAngle extends HTMLElement {
|
|
|
4700
4843
|
);
|
|
4701
4844
|
}
|
|
4702
4845
|
|
|
4846
|
+
// --- Handle position ---
|
|
4847
|
+
|
|
4703
4848
|
#syncHandlePosition() {
|
|
4704
4849
|
if (this.handle) {
|
|
4705
|
-
const
|
|
4850
|
+
const degrees = this.#toDegrees(this.angle);
|
|
4851
|
+
const radians = (degrees * Math.PI) / 180;
|
|
4706
4852
|
const radius = this.plane.offsetWidth / 2 - this.handle.offsetWidth / 2;
|
|
4707
4853
|
const x = Math.cos(radians) * radius;
|
|
4708
4854
|
const y = Math.sin(radians) * radius;
|
|
@@ -4710,8 +4856,11 @@ class FigInputAngle extends HTMLElement {
|
|
|
4710
4856
|
}
|
|
4711
4857
|
}
|
|
4712
4858
|
|
|
4859
|
+
// --- Mouse/Touch handlers ---
|
|
4860
|
+
|
|
4713
4861
|
#handleMouseDown(e) {
|
|
4714
4862
|
this.isDragging = true;
|
|
4863
|
+
this.#prevRawAngle = null;
|
|
4715
4864
|
this.#updateAngle(e);
|
|
4716
4865
|
|
|
4717
4866
|
const handleMouseMove = (e) => {
|
|
@@ -4721,6 +4870,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
4721
4870
|
|
|
4722
4871
|
const handleMouseUp = () => {
|
|
4723
4872
|
this.isDragging = false;
|
|
4873
|
+
this.#prevRawAngle = null;
|
|
4724
4874
|
this.plane.classList.remove("dragging");
|
|
4725
4875
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
4726
4876
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
@@ -4734,6 +4884,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
4734
4884
|
#handleTouchStart(e) {
|
|
4735
4885
|
e.preventDefault();
|
|
4736
4886
|
this.isDragging = true;
|
|
4887
|
+
this.#prevRawAngle = null;
|
|
4737
4888
|
this.#updateAngle(e.touches[0]);
|
|
4738
4889
|
|
|
4739
4890
|
const handleTouchMove = (e) => {
|
|
@@ -4743,6 +4894,7 @@ class FigInputAngle extends HTMLElement {
|
|
|
4743
4894
|
|
|
4744
4895
|
const handleTouchEnd = () => {
|
|
4745
4896
|
this.isDragging = false;
|
|
4897
|
+
this.#prevRawAngle = null;
|
|
4746
4898
|
this.plane.classList.remove("dragging");
|
|
4747
4899
|
window.removeEventListener("touchmove", handleTouchMove);
|
|
4748
4900
|
window.removeEventListener("touchend", handleTouchEnd);
|
|
@@ -4753,6 +4905,8 @@ class FigInputAngle extends HTMLElement {
|
|
|
4753
4905
|
window.addEventListener("touchend", handleTouchEnd);
|
|
4754
4906
|
}
|
|
4755
4907
|
|
|
4908
|
+
// --- Keyboard handlers ---
|
|
4909
|
+
|
|
4756
4910
|
#handleKeyDown(e) {
|
|
4757
4911
|
if (e.key === "Shift") this.isShiftHeld = true;
|
|
4758
4912
|
}
|
|
@@ -4765,8 +4919,10 @@ class FigInputAngle extends HTMLElement {
|
|
|
4765
4919
|
this.plane?.focus();
|
|
4766
4920
|
}
|
|
4767
4921
|
|
|
4922
|
+
// --- Attributes ---
|
|
4923
|
+
|
|
4768
4924
|
static get observedAttributes() {
|
|
4769
|
-
return ["value", "precision", "text"];
|
|
4925
|
+
return ["value", "precision", "text", "min", "max", "units"];
|
|
4770
4926
|
}
|
|
4771
4927
|
|
|
4772
4928
|
get value() {
|
|
@@ -4792,15 +4948,50 @@ class FigInputAngle extends HTMLElement {
|
|
|
4792
4948
|
}
|
|
4793
4949
|
|
|
4794
4950
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
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;
|
|
4804
4995
|
}
|
|
4805
4996
|
}
|
|
4806
4997
|
}
|
package/package.json
CHANGED