@rogieking/figui3 9.0.13 → 9.1.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/.cursor/skills/fig-lab/SKILL.md +2 -2
- package/.cursor/skills/fig-lab/components.md +0 -13
- package/.cursor/skills/fig-lab/reference.md +0 -6
- package/.cursor/skills/figui3/SKILL.md +1 -1
- package/.cursor/skills/figui3/components.md +20 -1
- package/.cursor/skills/figui3/react.md +1 -1
- package/.cursor/skills/figui3/reference.md +18 -0
- package/README.md +21 -11
- package/components.css +252 -39
- package/dist/components.css +1 -1
- package/dist/fig-editor.js +3 -3
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +6 -6
- package/fig-editor.js +22 -5
- package/fig-lab.css +4 -71
- package/fig-lab.js +1 -671
- package/fig.js +811 -13
- package/package.json +1 -1
package/fig-lab.js
CHANGED
|
@@ -2533,676 +2533,6 @@ class FigCanvasControl extends HTMLElement {
|
|
|
2533
2533
|
}
|
|
2534
2534
|
}
|
|
2535
2535
|
figLabDefineElement("fig-canvas-control", FigCanvasControl);
|
|
2536
|
-
/* Angle Input */
|
|
2537
|
-
/**
|
|
2538
|
-
* A custom angle chooser input element.
|
|
2539
|
-
* @attr {number} value - The current angle of the handle in degrees.
|
|
2540
|
-
* @attr {number} precision - The number of decimal places for the output.
|
|
2541
|
-
* @attr {boolean} text - Whether to display a text input for the angle value.
|
|
2542
|
-
* @attr {boolean} dial - Whether to display the circular dial control. Defaults to true.
|
|
2543
|
-
* @attr {number} adjacent - The adjacent value of the angle.
|
|
2544
|
-
* @attr {number} opposite - The opposite value of the angle.
|
|
2545
|
-
* @attr {boolean} rotations - Whether to display a rotation count (×N) when rotations > 1. Defaults to false.
|
|
2546
|
-
*/
|
|
2547
|
-
class FigInputAngle extends HTMLElement {
|
|
2548
|
-
// Private fields
|
|
2549
|
-
#adjacent;
|
|
2550
|
-
#opposite;
|
|
2551
|
-
#prevRawAngle = null;
|
|
2552
|
-
#boundHandleRawChange;
|
|
2553
|
-
#boundHandleMouseDown;
|
|
2554
|
-
#boundHandleTouchStart;
|
|
2555
|
-
#boundHandleKeyDown;
|
|
2556
|
-
#boundHandleKeyUp;
|
|
2557
|
-
#boundHandleAngleInput;
|
|
2558
|
-
#boundHandleDialKeyDown;
|
|
2559
|
-
#gestureController = null;
|
|
2560
|
-
#gestureCleanup = null;
|
|
2561
|
-
|
|
2562
|
-
constructor() {
|
|
2563
|
-
super();
|
|
2564
|
-
|
|
2565
|
-
this.angle = 0;
|
|
2566
|
-
this.#adjacent = 1;
|
|
2567
|
-
this.#opposite = 0;
|
|
2568
|
-
this.isDragging = false;
|
|
2569
|
-
this.isShiftHeld = false;
|
|
2570
|
-
this.handle = null;
|
|
2571
|
-
this.angleInput = null;
|
|
2572
|
-
this.plane = null;
|
|
2573
|
-
this.units = "°";
|
|
2574
|
-
this.min = null;
|
|
2575
|
-
this.max = null;
|
|
2576
|
-
this.dial = true;
|
|
2577
|
-
this.showRotations = false;
|
|
2578
|
-
this.rotationSpan = null;
|
|
2579
|
-
|
|
2580
|
-
this.#boundHandleRawChange = this.#handleRawChange.bind(this);
|
|
2581
|
-
this.#boundHandleMouseDown = this.#handleMouseDown.bind(this);
|
|
2582
|
-
this.#boundHandleTouchStart = this.#handleTouchStart.bind(this);
|
|
2583
|
-
this.#boundHandleKeyDown = this.#handleKeyDown.bind(this);
|
|
2584
|
-
this.#boundHandleKeyUp = this.#handleKeyUp.bind(this);
|
|
2585
|
-
this.#boundHandleAngleInput = this.#handleAngleInput.bind(this);
|
|
2586
|
-
this.#boundHandleDialKeyDown = this.#handleDialKeyDown.bind(this);
|
|
2587
|
-
}
|
|
2588
|
-
|
|
2589
|
-
connectedCallback() {
|
|
2590
|
-
requestAnimationFrame(() => {
|
|
2591
|
-
if (!this.isConnected) return;
|
|
2592
|
-
this.precision = this.getAttribute("precision") || 1;
|
|
2593
|
-
this.precision = parseInt(this.precision);
|
|
2594
|
-
this.text = this.getAttribute("text") === "true";
|
|
2595
|
-
|
|
2596
|
-
let rawUnits = this.getAttribute("units") || "°";
|
|
2597
|
-
if (rawUnits === "deg") rawUnits = "°";
|
|
2598
|
-
this.units = rawUnits;
|
|
2599
|
-
|
|
2600
|
-
this.min = this.hasAttribute("min")
|
|
2601
|
-
? Number(this.getAttribute("min"))
|
|
2602
|
-
: null;
|
|
2603
|
-
this.max = this.hasAttribute("max")
|
|
2604
|
-
? Number(this.getAttribute("max"))
|
|
2605
|
-
: null;
|
|
2606
|
-
this.dial = this.#readBooleanAttribute("dial", true);
|
|
2607
|
-
this.showRotations = this.#readRotationsEnabled();
|
|
2608
|
-
|
|
2609
|
-
this.#render();
|
|
2610
|
-
this.#setupListeners();
|
|
2611
|
-
|
|
2612
|
-
this.#syncHandlePosition();
|
|
2613
|
-
if (this.text && this.angleInput) {
|
|
2614
|
-
this.angleInput.setAttribute(
|
|
2615
|
-
"value",
|
|
2616
|
-
this.angle.toFixed(this.precision),
|
|
2617
|
-
);
|
|
2618
|
-
}
|
|
2619
|
-
});
|
|
2620
|
-
}
|
|
2621
|
-
|
|
2622
|
-
disconnectedCallback() {
|
|
2623
|
-
this.#cancelGesture();
|
|
2624
|
-
this.#cleanupListeners();
|
|
2625
|
-
}
|
|
2626
|
-
|
|
2627
|
-
#render() {
|
|
2628
|
-
this.#cancelGesture();
|
|
2629
|
-
this.#cleanupListeners();
|
|
2630
|
-
const step = this.#getStepForUnit();
|
|
2631
|
-
const disabled = this.#isDisabled();
|
|
2632
|
-
const name =
|
|
2633
|
-
this.getAttribute("aria-label") || this.getAttribute("name") || "Angle";
|
|
2634
|
-
const ariaMin = this.min ?? this.#fromDegrees(0);
|
|
2635
|
-
const ariaMax = this.max ?? this.#fromDegrees(360);
|
|
2636
|
-
const children = [];
|
|
2637
|
-
|
|
2638
|
-
if (this.dial) {
|
|
2639
|
-
children.push(
|
|
2640
|
-
figLabCreateElement(
|
|
2641
|
-
"div",
|
|
2642
|
-
{
|
|
2643
|
-
className: "fig-input-angle-plane",
|
|
2644
|
-
role: "slider",
|
|
2645
|
-
tabindex: disabled ? "-1" : "0",
|
|
2646
|
-
"aria-label": name,
|
|
2647
|
-
"aria-valuemin": ariaMin,
|
|
2648
|
-
"aria-valuemax": ariaMax,
|
|
2649
|
-
"aria-valuenow": this.angle,
|
|
2650
|
-
"aria-valuetext": `${this.angle.toFixed(this.precision)}${this.units}`,
|
|
2651
|
-
"aria-disabled": disabled ? "true" : null,
|
|
2652
|
-
},
|
|
2653
|
-
figLabCreateElement("div", {
|
|
2654
|
-
className: "fig-input-angle-handle",
|
|
2655
|
-
}),
|
|
2656
|
-
),
|
|
2657
|
-
);
|
|
2658
|
-
}
|
|
2659
|
-
|
|
2660
|
-
if (this.text) {
|
|
2661
|
-
children.push(
|
|
2662
|
-
figLabCreateElement(
|
|
2663
|
-
"fig-input-number",
|
|
2664
|
-
{
|
|
2665
|
-
name: "angle",
|
|
2666
|
-
step,
|
|
2667
|
-
value: this.angle,
|
|
2668
|
-
min: this.min,
|
|
2669
|
-
max: this.max,
|
|
2670
|
-
units: this.units,
|
|
2671
|
-
"aria-label": name,
|
|
2672
|
-
disabled,
|
|
2673
|
-
},
|
|
2674
|
-
this.showRotations
|
|
2675
|
-
? figLabCreateElement("span", {
|
|
2676
|
-
slot: "append",
|
|
2677
|
-
className: "fig-input-angle-rotations",
|
|
2678
|
-
})
|
|
2679
|
-
: null,
|
|
2680
|
-
),
|
|
2681
|
-
);
|
|
2682
|
-
}
|
|
2683
|
-
|
|
2684
|
-
this.replaceChildren(...children);
|
|
2685
|
-
}
|
|
2686
|
-
|
|
2687
|
-
#readBooleanAttribute(name, defaultValue = false) {
|
|
2688
|
-
const value = this.getAttribute(name);
|
|
2689
|
-
if (value === null) return defaultValue;
|
|
2690
|
-
const normalized = value.trim().toLowerCase();
|
|
2691
|
-
if (normalized === "" || normalized === "true") return true;
|
|
2692
|
-
if (normalized === "false") return false;
|
|
2693
|
-
return true;
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
|
-
#readRotationsEnabled() {
|
|
2697
|
-
if (this.hasAttribute("rotations")) {
|
|
2698
|
-
return this.#readBooleanAttribute("rotations", false);
|
|
2699
|
-
}
|
|
2700
|
-
if (this.hasAttribute("show-rotations")) {
|
|
2701
|
-
return this.#readBooleanAttribute("show-rotations", false);
|
|
2702
|
-
}
|
|
2703
|
-
return false;
|
|
2704
|
-
}
|
|
2705
|
-
|
|
2706
|
-
#getRotationCount() {
|
|
2707
|
-
const degrees = Math.abs(this.#toDegrees(this.angle));
|
|
2708
|
-
return Math.floor(degrees / 360);
|
|
2709
|
-
}
|
|
2710
|
-
|
|
2711
|
-
#isDisabled() {
|
|
2712
|
-
return figLabBooleanAttribute(this, "disabled");
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
#clampValue(value) {
|
|
2716
|
-
let next = Number(value);
|
|
2717
|
-
if (!Number.isFinite(next)) return this.angle;
|
|
2718
|
-
if (this.min !== null && Number.isFinite(this.min)) {
|
|
2719
|
-
next = Math.max(this.min, next);
|
|
2720
|
-
}
|
|
2721
|
-
if (this.max !== null && Number.isFinite(this.max)) {
|
|
2722
|
-
next = Math.min(this.max, next);
|
|
2723
|
-
}
|
|
2724
|
-
return next;
|
|
2725
|
-
}
|
|
2726
|
-
|
|
2727
|
-
#setValue(value, { reflect = true } = {}) {
|
|
2728
|
-
const next = this.#clampValue(value);
|
|
2729
|
-
this.angle = next;
|
|
2730
|
-
this.#calculateAdjacentAndOpposite();
|
|
2731
|
-
if (reflect) {
|
|
2732
|
-
const serialized = String(next);
|
|
2733
|
-
if (this.getAttribute("value") !== serialized) {
|
|
2734
|
-
this.setAttribute("value", serialized);
|
|
2735
|
-
}
|
|
2736
|
-
}
|
|
2737
|
-
this.#syncHandlePosition();
|
|
2738
|
-
if (this.angleInput) {
|
|
2739
|
-
this.angleInput.value = next.toFixed(this.precision);
|
|
2740
|
-
}
|
|
2741
|
-
this.#syncDialState();
|
|
2742
|
-
this.#updateRotationDisplay();
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
#syncDialState() {
|
|
2746
|
-
if (!this.plane) return;
|
|
2747
|
-
this.plane.setAttribute("aria-valuenow", String(this.angle));
|
|
2748
|
-
this.plane.setAttribute(
|
|
2749
|
-
"aria-valuetext",
|
|
2750
|
-
`${this.angle.toFixed(this.precision)}${this.units}`,
|
|
2751
|
-
);
|
|
2752
|
-
}
|
|
2753
|
-
|
|
2754
|
-
#updateRotationDisplay() {
|
|
2755
|
-
if (!this.rotationSpan) return;
|
|
2756
|
-
const rotations = this.#getRotationCount();
|
|
2757
|
-
if (rotations > 1) {
|
|
2758
|
-
this.rotationSpan.textContent = `\u00d7${rotations}`;
|
|
2759
|
-
this.rotationSpan.style.display = "";
|
|
2760
|
-
} else {
|
|
2761
|
-
this.rotationSpan.textContent = "";
|
|
2762
|
-
this.rotationSpan.style.display = "none";
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
|
|
2766
|
-
#getStepForUnit() {
|
|
2767
|
-
switch (this.units) {
|
|
2768
|
-
case "rad":
|
|
2769
|
-
return 0.01;
|
|
2770
|
-
case "turn":
|
|
2771
|
-
return 0.001;
|
|
2772
|
-
default:
|
|
2773
|
-
return 0.1;
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
2776
|
-
|
|
2777
|
-
#toDegrees(value) {
|
|
2778
|
-
switch (this.units) {
|
|
2779
|
-
case "rad":
|
|
2780
|
-
return (value * 180) / Math.PI;
|
|
2781
|
-
case "turn":
|
|
2782
|
-
return value * 360;
|
|
2783
|
-
default:
|
|
2784
|
-
return value;
|
|
2785
|
-
}
|
|
2786
|
-
}
|
|
2787
|
-
|
|
2788
|
-
#fromDegrees(degrees) {
|
|
2789
|
-
switch (this.units) {
|
|
2790
|
-
case "rad":
|
|
2791
|
-
return (degrees * Math.PI) / 180;
|
|
2792
|
-
case "turn":
|
|
2793
|
-
return degrees / 360;
|
|
2794
|
-
default:
|
|
2795
|
-
return degrees;
|
|
2796
|
-
}
|
|
2797
|
-
}
|
|
2798
|
-
|
|
2799
|
-
#convertAngle(value, fromUnit, toUnit) {
|
|
2800
|
-
let degrees;
|
|
2801
|
-
switch (fromUnit) {
|
|
2802
|
-
case "rad":
|
|
2803
|
-
degrees = (value * 180) / Math.PI;
|
|
2804
|
-
break;
|
|
2805
|
-
case "turn":
|
|
2806
|
-
degrees = value * 360;
|
|
2807
|
-
break;
|
|
2808
|
-
default:
|
|
2809
|
-
degrees = value;
|
|
2810
|
-
}
|
|
2811
|
-
switch (toUnit) {
|
|
2812
|
-
case "rad":
|
|
2813
|
-
return (degrees * Math.PI) / 180;
|
|
2814
|
-
case "turn":
|
|
2815
|
-
return degrees / 360;
|
|
2816
|
-
default:
|
|
2817
|
-
return degrees;
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
|
|
2821
|
-
#setupListeners() {
|
|
2822
|
-
this.handle = this.querySelector(".fig-input-angle-handle");
|
|
2823
|
-
this.plane = this.querySelector(".fig-input-angle-plane");
|
|
2824
|
-
this.angleInput = this.querySelector("fig-input-number[name='angle']");
|
|
2825
|
-
this.rotationSpan = this.querySelector(".fig-input-angle-rotations");
|
|
2826
|
-
this.#updateRotationDisplay();
|
|
2827
|
-
this.plane?.addEventListener("mousedown", this.#boundHandleMouseDown);
|
|
2828
|
-
this.plane?.addEventListener("touchstart", this.#boundHandleTouchStart);
|
|
2829
|
-
this.plane?.addEventListener("keydown", this.#boundHandleDialKeyDown);
|
|
2830
|
-
window.addEventListener("keydown", this.#boundHandleKeyDown);
|
|
2831
|
-
window.addEventListener("keyup", this.#boundHandleKeyUp);
|
|
2832
|
-
if (this.text && this.angleInput) {
|
|
2833
|
-
this.angleInput.addEventListener("input", this.#boundHandleAngleInput);
|
|
2834
|
-
this.angleInput.addEventListener("change", this.#boundHandleAngleInput);
|
|
2835
|
-
}
|
|
2836
|
-
this.addEventListener("change", this.#boundHandleRawChange, true);
|
|
2837
|
-
}
|
|
2838
|
-
|
|
2839
|
-
#cleanupListeners() {
|
|
2840
|
-
this.plane?.removeEventListener("mousedown", this.#boundHandleMouseDown);
|
|
2841
|
-
this.plane?.removeEventListener("touchstart", this.#boundHandleTouchStart);
|
|
2842
|
-
this.plane?.removeEventListener("keydown", this.#boundHandleDialKeyDown);
|
|
2843
|
-
window.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
2844
|
-
window.removeEventListener("keyup", this.#boundHandleKeyUp);
|
|
2845
|
-
if (this.text && this.angleInput) {
|
|
2846
|
-
this.angleInput.removeEventListener("input", this.#boundHandleAngleInput);
|
|
2847
|
-
this.angleInput.removeEventListener("change", this.#boundHandleAngleInput);
|
|
2848
|
-
}
|
|
2849
|
-
this.removeEventListener("change", this.#boundHandleRawChange, true);
|
|
2850
|
-
}
|
|
2851
|
-
|
|
2852
|
-
#handleRawChange(e) {
|
|
2853
|
-
if (!e.target?.matches?.("input")) return;
|
|
2854
|
-
const raw = e.target.value;
|
|
2855
|
-
const match = raw.match(/^(-?\d*\.?\d+)\s*(turn|rad|deg|°)$/i);
|
|
2856
|
-
if (match) {
|
|
2857
|
-
const num = parseFloat(match[1]);
|
|
2858
|
-
let fromUnit = match[2].toLowerCase();
|
|
2859
|
-
if (fromUnit === "deg") fromUnit = "°";
|
|
2860
|
-
if (fromUnit !== this.units) {
|
|
2861
|
-
const converted = this.#convertAngle(num, fromUnit, this.units);
|
|
2862
|
-
e.target.value = String(converted);
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
#handleAngleInput(e) {
|
|
2868
|
-
e.stopPropagation();
|
|
2869
|
-
if (this.#isDisabled()) return;
|
|
2870
|
-
this.#setValue(Number(e.target.value));
|
|
2871
|
-
if (e.type === "change") this.#emitChangeEvent();
|
|
2872
|
-
else this.#emitInputEvent();
|
|
2873
|
-
}
|
|
2874
|
-
|
|
2875
|
-
#calculateAdjacentAndOpposite() {
|
|
2876
|
-
const degrees = this.#toDegrees(this.angle);
|
|
2877
|
-
const radians = (degrees * Math.PI) / 180;
|
|
2878
|
-
this.#adjacent = Math.cos(radians);
|
|
2879
|
-
this.#opposite = Math.sin(radians);
|
|
2880
|
-
}
|
|
2881
|
-
|
|
2882
|
-
#snapToIncrement(angle) {
|
|
2883
|
-
if (!this.isShiftHeld) return angle;
|
|
2884
|
-
const increment = 45;
|
|
2885
|
-
return Math.round(angle / increment) * increment;
|
|
2886
|
-
}
|
|
2887
|
-
|
|
2888
|
-
#getRawAngle(e) {
|
|
2889
|
-
const rect = this.plane.getBoundingClientRect();
|
|
2890
|
-
const centerX = rect.left + rect.width / 2;
|
|
2891
|
-
const centerY = rect.top + rect.height / 2;
|
|
2892
|
-
const deltaX = e.clientX - centerX;
|
|
2893
|
-
const deltaY = e.clientY - centerY;
|
|
2894
|
-
return (Math.atan2(deltaY, deltaX) * 180) / Math.PI;
|
|
2895
|
-
}
|
|
2896
|
-
|
|
2897
|
-
#updateAngle(e) {
|
|
2898
|
-
let rawAngle = this.#getRawAngle(e);
|
|
2899
|
-
let normalizedAngle = ((rawAngle % 360) + 360) % 360;
|
|
2900
|
-
normalizedAngle = this.#snapToIncrement(normalizedAngle);
|
|
2901
|
-
|
|
2902
|
-
const isBounded = this.min !== null || this.max !== null;
|
|
2903
|
-
|
|
2904
|
-
if (isBounded) {
|
|
2905
|
-
this.angle = this.#clampValue(this.#fromDegrees(normalizedAngle));
|
|
2906
|
-
} else {
|
|
2907
|
-
if (this.#prevRawAngle === null) {
|
|
2908
|
-
this.#prevRawAngle = normalizedAngle;
|
|
2909
|
-
const currentDeg = this.#toDegrees(this.angle);
|
|
2910
|
-
const currentMod = ((currentDeg % 360) + 360) % 360;
|
|
2911
|
-
let delta = normalizedAngle - currentMod;
|
|
2912
|
-
if (delta > 180) delta -= 360;
|
|
2913
|
-
if (delta < -180) delta += 360;
|
|
2914
|
-
this.angle = this.#clampValue(this.angle + this.#fromDegrees(delta));
|
|
2915
|
-
} else {
|
|
2916
|
-
let delta = normalizedAngle - this.#prevRawAngle;
|
|
2917
|
-
if (delta > 180) delta -= 360;
|
|
2918
|
-
if (delta < -180) delta += 360;
|
|
2919
|
-
this.angle = this.#clampValue(this.angle + this.#fromDegrees(delta));
|
|
2920
|
-
this.#prevRawAngle = normalizedAngle;
|
|
2921
|
-
}
|
|
2922
|
-
}
|
|
2923
|
-
|
|
2924
|
-
this.#calculateAdjacentAndOpposite();
|
|
2925
|
-
this.setAttribute("value", String(this.angle));
|
|
2926
|
-
|
|
2927
|
-
this.#syncHandlePosition();
|
|
2928
|
-
if (this.text && this.angleInput) {
|
|
2929
|
-
this.angleInput.setAttribute("value", this.angle.toFixed(this.precision));
|
|
2930
|
-
}
|
|
2931
|
-
this.#updateRotationDisplay();
|
|
2932
|
-
this.#syncDialState();
|
|
2933
|
-
|
|
2934
|
-
this.#emitInputEvent();
|
|
2935
|
-
}
|
|
2936
|
-
|
|
2937
|
-
#emitInputEvent() {
|
|
2938
|
-
this.dispatchEvent(
|
|
2939
|
-
new CustomEvent("input", {
|
|
2940
|
-
bubbles: true,
|
|
2941
|
-
cancelable: true,
|
|
2942
|
-
detail: { value: this.value, angle: this.angle },
|
|
2943
|
-
}),
|
|
2944
|
-
);
|
|
2945
|
-
}
|
|
2946
|
-
|
|
2947
|
-
#emitChangeEvent() {
|
|
2948
|
-
this.dispatchEvent(
|
|
2949
|
-
new CustomEvent("change", {
|
|
2950
|
-
bubbles: true,
|
|
2951
|
-
cancelable: true,
|
|
2952
|
-
detail: { value: this.value, angle: this.angle },
|
|
2953
|
-
}),
|
|
2954
|
-
);
|
|
2955
|
-
}
|
|
2956
|
-
|
|
2957
|
-
#syncHandlePosition() {
|
|
2958
|
-
if (this.handle) {
|
|
2959
|
-
const degrees = this.#toDegrees(this.angle);
|
|
2960
|
-
const radians = (degrees * Math.PI) / 180;
|
|
2961
|
-
const radius = this.plane.offsetWidth / 2 - this.handle.offsetWidth / 2;
|
|
2962
|
-
const x = Math.cos(radians) * radius;
|
|
2963
|
-
const y = Math.sin(radians) * radius;
|
|
2964
|
-
this.handle.style.transform = `translate(${x}px, ${y}px)`;
|
|
2965
|
-
}
|
|
2966
|
-
}
|
|
2967
|
-
|
|
2968
|
-
#handleMouseDown(e) {
|
|
2969
|
-
if (this.#isDisabled() || e.button !== 0) return;
|
|
2970
|
-
this.#cancelGesture();
|
|
2971
|
-
this.isDragging = true;
|
|
2972
|
-
this.#prevRawAngle = null;
|
|
2973
|
-
this.#updateAngle(e);
|
|
2974
|
-
|
|
2975
|
-
const handleMouseMove = (e) => {
|
|
2976
|
-
this.plane.classList.add("dragging");
|
|
2977
|
-
if (this.isDragging) this.#updateAngle(e);
|
|
2978
|
-
};
|
|
2979
|
-
|
|
2980
|
-
const controller = new AbortController();
|
|
2981
|
-
const finish = (commit = false) => {
|
|
2982
|
-
this.isDragging = false;
|
|
2983
|
-
this.#prevRawAngle = null;
|
|
2984
|
-
this.plane.classList.remove("dragging");
|
|
2985
|
-
controller.abort();
|
|
2986
|
-
if (this.#gestureController === controller) {
|
|
2987
|
-
this.#gestureController = null;
|
|
2988
|
-
this.#gestureCleanup = null;
|
|
2989
|
-
}
|
|
2990
|
-
if (commit) this.#emitChangeEvent();
|
|
2991
|
-
};
|
|
2992
|
-
this.#gestureController = controller;
|
|
2993
|
-
this.#gestureCleanup = finish;
|
|
2994
|
-
|
|
2995
|
-
window.addEventListener("mousemove", handleMouseMove, {
|
|
2996
|
-
signal: controller.signal,
|
|
2997
|
-
});
|
|
2998
|
-
window.addEventListener("mouseup", () => finish(true), {
|
|
2999
|
-
signal: controller.signal,
|
|
3000
|
-
});
|
|
3001
|
-
window.addEventListener("blur", () => finish(false), {
|
|
3002
|
-
signal: controller.signal,
|
|
3003
|
-
});
|
|
3004
|
-
}
|
|
3005
|
-
|
|
3006
|
-
#handleTouchStart(e) {
|
|
3007
|
-
if (this.#isDisabled()) return;
|
|
3008
|
-
e.preventDefault();
|
|
3009
|
-
this.#cancelGesture();
|
|
3010
|
-
this.isDragging = true;
|
|
3011
|
-
this.#prevRawAngle = null;
|
|
3012
|
-
this.#updateAngle(e.touches[0]);
|
|
3013
|
-
|
|
3014
|
-
const handleTouchMove = (e) => {
|
|
3015
|
-
this.plane.classList.add("dragging");
|
|
3016
|
-
if (this.isDragging) this.#updateAngle(e.touches[0]);
|
|
3017
|
-
};
|
|
3018
|
-
|
|
3019
|
-
const controller = new AbortController();
|
|
3020
|
-
const finish = (commit = false) => {
|
|
3021
|
-
this.isDragging = false;
|
|
3022
|
-
this.#prevRawAngle = null;
|
|
3023
|
-
this.plane.classList.remove("dragging");
|
|
3024
|
-
controller.abort();
|
|
3025
|
-
if (this.#gestureController === controller) {
|
|
3026
|
-
this.#gestureController = null;
|
|
3027
|
-
this.#gestureCleanup = null;
|
|
3028
|
-
}
|
|
3029
|
-
if (commit) this.#emitChangeEvent();
|
|
3030
|
-
};
|
|
3031
|
-
this.#gestureController = controller;
|
|
3032
|
-
this.#gestureCleanup = finish;
|
|
3033
|
-
|
|
3034
|
-
window.addEventListener("touchmove", handleTouchMove, {
|
|
3035
|
-
signal: controller.signal,
|
|
3036
|
-
});
|
|
3037
|
-
window.addEventListener("touchend", () => finish(true), {
|
|
3038
|
-
signal: controller.signal,
|
|
3039
|
-
});
|
|
3040
|
-
window.addEventListener("touchcancel", () => finish(false), {
|
|
3041
|
-
signal: controller.signal,
|
|
3042
|
-
});
|
|
3043
|
-
}
|
|
3044
|
-
|
|
3045
|
-
#cancelGesture() {
|
|
3046
|
-
this.#gestureCleanup?.(false);
|
|
3047
|
-
this.#gestureController?.abort();
|
|
3048
|
-
this.#gestureController = null;
|
|
3049
|
-
this.#gestureCleanup = null;
|
|
3050
|
-
this.isDragging = false;
|
|
3051
|
-
this.#prevRawAngle = null;
|
|
3052
|
-
this.plane?.classList.remove("dragging");
|
|
3053
|
-
}
|
|
3054
|
-
|
|
3055
|
-
#handleDialKeyDown(e) {
|
|
3056
|
-
if (this.#isDisabled()) return;
|
|
3057
|
-
const step = this.#getStepForUnit() * (e.shiftKey ? 10 : 1);
|
|
3058
|
-
let next = this.angle;
|
|
3059
|
-
if (e.key === "ArrowLeft" || e.key === "ArrowDown") next -= step;
|
|
3060
|
-
else if (e.key === "ArrowRight" || e.key === "ArrowUp") next += step;
|
|
3061
|
-
else if (e.key === "Home") next = this.min ?? this.#fromDegrees(0);
|
|
3062
|
-
else if (e.key === "End") next = this.max ?? this.#fromDegrees(360);
|
|
3063
|
-
else return;
|
|
3064
|
-
e.preventDefault();
|
|
3065
|
-
e.stopPropagation();
|
|
3066
|
-
this.#setValue(next);
|
|
3067
|
-
this.#emitInputEvent();
|
|
3068
|
-
this.#emitChangeEvent();
|
|
3069
|
-
}
|
|
3070
|
-
|
|
3071
|
-
#handleKeyDown(e) {
|
|
3072
|
-
if (e.key === "Shift") this.isShiftHeld = true;
|
|
3073
|
-
}
|
|
3074
|
-
|
|
3075
|
-
#handleKeyUp(e) {
|
|
3076
|
-
if (e.key === "Shift") this.isShiftHeld = false;
|
|
3077
|
-
}
|
|
3078
|
-
|
|
3079
|
-
focus() {
|
|
3080
|
-
this.plane?.focus();
|
|
3081
|
-
}
|
|
3082
|
-
|
|
3083
|
-
static get observedAttributes() {
|
|
3084
|
-
return [
|
|
3085
|
-
"value",
|
|
3086
|
-
"precision",
|
|
3087
|
-
"text",
|
|
3088
|
-
"min",
|
|
3089
|
-
"max",
|
|
3090
|
-
"units",
|
|
3091
|
-
"dial",
|
|
3092
|
-
"rotations",
|
|
3093
|
-
"show-rotations",
|
|
3094
|
-
"disabled",
|
|
3095
|
-
"aria-label",
|
|
3096
|
-
"name",
|
|
3097
|
-
];
|
|
3098
|
-
}
|
|
3099
|
-
|
|
3100
|
-
get value() {
|
|
3101
|
-
return this.angle;
|
|
3102
|
-
}
|
|
3103
|
-
|
|
3104
|
-
get adjacent() {
|
|
3105
|
-
return this.#adjacent;
|
|
3106
|
-
}
|
|
3107
|
-
|
|
3108
|
-
get opposite() {
|
|
3109
|
-
return this.#opposite;
|
|
3110
|
-
}
|
|
3111
|
-
|
|
3112
|
-
set value(value) {
|
|
3113
|
-
if (isNaN(value)) {
|
|
3114
|
-
console.error("Invalid value: must be a number.");
|
|
3115
|
-
return;
|
|
3116
|
-
}
|
|
3117
|
-
this.#setValue(Number(value));
|
|
3118
|
-
}
|
|
3119
|
-
|
|
3120
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
3121
|
-
switch (name) {
|
|
3122
|
-
case "value":
|
|
3123
|
-
if (this.isDragging) break;
|
|
3124
|
-
if (newValue !== null) this.#setValue(Number(newValue), { reflect: false });
|
|
3125
|
-
break;
|
|
3126
|
-
case "precision":
|
|
3127
|
-
this.precision = parseInt(newValue);
|
|
3128
|
-
break;
|
|
3129
|
-
case "text":
|
|
3130
|
-
if (newValue !== oldValue) {
|
|
3131
|
-
this.text = newValue?.toLowerCase() === "true";
|
|
3132
|
-
if (this.isConnected) {
|
|
3133
|
-
this.#render();
|
|
3134
|
-
this.#setupListeners();
|
|
3135
|
-
this.#syncHandlePosition();
|
|
3136
|
-
}
|
|
3137
|
-
}
|
|
3138
|
-
break;
|
|
3139
|
-
case "dial":
|
|
3140
|
-
this.dial = this.#readBooleanAttribute("dial", true);
|
|
3141
|
-
if (this.isConnected) {
|
|
3142
|
-
this.#render();
|
|
3143
|
-
this.#setupListeners();
|
|
3144
|
-
this.#syncHandlePosition();
|
|
3145
|
-
}
|
|
3146
|
-
break;
|
|
3147
|
-
case "units": {
|
|
3148
|
-
let units = newValue || "°";
|
|
3149
|
-
if (units === "deg") units = "°";
|
|
3150
|
-
this.units = units;
|
|
3151
|
-
if (this.isConnected) {
|
|
3152
|
-
this.#render();
|
|
3153
|
-
this.#setupListeners();
|
|
3154
|
-
this.#syncHandlePosition();
|
|
3155
|
-
}
|
|
3156
|
-
break;
|
|
3157
|
-
}
|
|
3158
|
-
case "min":
|
|
3159
|
-
this.min = newValue !== null ? Number(newValue) : null;
|
|
3160
|
-
this.#setValue(this.angle);
|
|
3161
|
-
if (this.isConnected) {
|
|
3162
|
-
this.#render();
|
|
3163
|
-
this.#setupListeners();
|
|
3164
|
-
this.#syncHandlePosition();
|
|
3165
|
-
}
|
|
3166
|
-
break;
|
|
3167
|
-
case "max":
|
|
3168
|
-
this.max = newValue !== null ? Number(newValue) : null;
|
|
3169
|
-
this.#setValue(this.angle);
|
|
3170
|
-
if (this.isConnected) {
|
|
3171
|
-
this.#render();
|
|
3172
|
-
this.#setupListeners();
|
|
3173
|
-
this.#syncHandlePosition();
|
|
3174
|
-
}
|
|
3175
|
-
break;
|
|
3176
|
-
case "disabled":
|
|
3177
|
-
this.#cancelGesture();
|
|
3178
|
-
if (this.isConnected) {
|
|
3179
|
-
this.#render();
|
|
3180
|
-
this.#setupListeners();
|
|
3181
|
-
this.#syncHandlePosition();
|
|
3182
|
-
}
|
|
3183
|
-
break;
|
|
3184
|
-
case "aria-label":
|
|
3185
|
-
case "name":
|
|
3186
|
-
if (this.isConnected) {
|
|
3187
|
-
this.#render();
|
|
3188
|
-
this.#setupListeners();
|
|
3189
|
-
this.#syncHandlePosition();
|
|
3190
|
-
}
|
|
3191
|
-
break;
|
|
3192
|
-
case "rotations":
|
|
3193
|
-
case "show-rotations":
|
|
3194
|
-
this.showRotations = this.#readRotationsEnabled();
|
|
3195
|
-
if (this.isConnected) {
|
|
3196
|
-
this.#render();
|
|
3197
|
-
this.#setupListeners();
|
|
3198
|
-
this.#syncHandlePosition();
|
|
3199
|
-
}
|
|
3200
|
-
break;
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
}
|
|
3204
|
-
figLabDefineElement("fig-input-angle", FigInputAngle);
|
|
3205
|
-
|
|
3206
2536
|
/* Reorder wrapper */
|
|
3207
2537
|
class FigReorder extends HTMLElement {
|
|
3208
2538
|
static observedAttributes = ["axis", "handle", "disabled"];
|
|
@@ -3219,7 +2549,7 @@ class FigReorder extends HTMLElement {
|
|
|
3219
2549
|
"fig-input-number",
|
|
3220
2550
|
"fig-input-gradient",
|
|
3221
2551
|
"fig-easing-curve",
|
|
3222
|
-
"fig-
|
|
2552
|
+
"fig-angle",
|
|
3223
2553
|
"fig-input-wheel",
|
|
3224
2554
|
"fig-joystick",
|
|
3225
2555
|
"fig-origin-grid",
|