@rogieking/figui3 3.17.0 → 3.19.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/README.md +52 -0
- package/components.css +105 -6
- package/dist/components.css +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +31 -31
- package/fig.js +926 -2
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -13343,6 +13343,857 @@ class FigChooser extends HTMLElement {
|
|
|
13343
13343
|
}
|
|
13344
13344
|
customElements.define("fig-chooser", FigChooser);
|
|
13345
13345
|
|
|
13346
|
+
/* Canvas Control */
|
|
13347
|
+
class FigCanvasControl extends HTMLElement {
|
|
13348
|
+
static observedAttributes = [
|
|
13349
|
+
"type",
|
|
13350
|
+
"value",
|
|
13351
|
+
"color",
|
|
13352
|
+
"name",
|
|
13353
|
+
"tooltips",
|
|
13354
|
+
"disabled",
|
|
13355
|
+
"drag-surface",
|
|
13356
|
+
"snapping",
|
|
13357
|
+
];
|
|
13358
|
+
|
|
13359
|
+
#x = 50;
|
|
13360
|
+
#y = 50;
|
|
13361
|
+
#x2 = 75;
|
|
13362
|
+
#y2 = 75;
|
|
13363
|
+
#radius = 0;
|
|
13364
|
+
#radiusIsPercent = false;
|
|
13365
|
+
#angle = 0;
|
|
13366
|
+
#pointHandle = null;
|
|
13367
|
+
#secondHandle = null;
|
|
13368
|
+
#angleHandle = null;
|
|
13369
|
+
#radiusSvg = null;
|
|
13370
|
+
#angleSvg = null;
|
|
13371
|
+
#pointTooltip = null;
|
|
13372
|
+
#secondTooltip = null;
|
|
13373
|
+
#radiusTooltip = null;
|
|
13374
|
+
#angleTooltip = null;
|
|
13375
|
+
#isDragging = false;
|
|
13376
|
+
#isSecondDragging = false;
|
|
13377
|
+
#isRadiusDragging = false;
|
|
13378
|
+
#isAngleDragging = false;
|
|
13379
|
+
#prevBodyCursor = "";
|
|
13380
|
+
|
|
13381
|
+
get #type() {
|
|
13382
|
+
return this.getAttribute("type") || "point";
|
|
13383
|
+
}
|
|
13384
|
+
|
|
13385
|
+
get #hasRadius() {
|
|
13386
|
+
return this.#type === "point-radius" || this.#type === "point-radius-angle";
|
|
13387
|
+
}
|
|
13388
|
+
|
|
13389
|
+
get #hasAngle() {
|
|
13390
|
+
return this.#type === "point-radius-angle";
|
|
13391
|
+
}
|
|
13392
|
+
|
|
13393
|
+
get #hasSecondPoint() {
|
|
13394
|
+
return this.#type === "point-point";
|
|
13395
|
+
}
|
|
13396
|
+
|
|
13397
|
+
get #hasLine() {
|
|
13398
|
+
return this.#type === "point-radius-angle" || this.#type === "point-point";
|
|
13399
|
+
}
|
|
13400
|
+
|
|
13401
|
+
get #tooltipsEnabled() {
|
|
13402
|
+
const v = this.getAttribute("tooltips");
|
|
13403
|
+
return v === null || v !== "false";
|
|
13404
|
+
}
|
|
13405
|
+
|
|
13406
|
+
get #snappingMode() {
|
|
13407
|
+
const raw = this.getAttribute("snapping");
|
|
13408
|
+
if (raw === null) return "false";
|
|
13409
|
+
const n = raw.trim().toLowerCase();
|
|
13410
|
+
if (n === "modifier") return "modifier";
|
|
13411
|
+
if (n === "" || n === "true") return "true";
|
|
13412
|
+
return "false";
|
|
13413
|
+
}
|
|
13414
|
+
|
|
13415
|
+
#shouldSnap(shiftKey) {
|
|
13416
|
+
const mode = this.#snappingMode;
|
|
13417
|
+
if (mode === "true") return true;
|
|
13418
|
+
if (mode === "modifier") return !!shiftKey;
|
|
13419
|
+
return false;
|
|
13420
|
+
}
|
|
13421
|
+
|
|
13422
|
+
get #pointTipText() {
|
|
13423
|
+
const name = this.getAttribute("name");
|
|
13424
|
+
if (name) {
|
|
13425
|
+
const parts = name.split(",");
|
|
13426
|
+
return parts[0].trim();
|
|
13427
|
+
}
|
|
13428
|
+
return `${Math.round(this.#x)}%, ${Math.round(this.#y)}%`;
|
|
13429
|
+
}
|
|
13430
|
+
|
|
13431
|
+
get #secondTipText() {
|
|
13432
|
+
const name = this.getAttribute("name");
|
|
13433
|
+
if (name) {
|
|
13434
|
+
const parts = name.split(",");
|
|
13435
|
+
if (parts.length > 1) return parts[1].trim();
|
|
13436
|
+
}
|
|
13437
|
+
return `${Math.round(this.#x2)}%, ${Math.round(this.#y2)}%`;
|
|
13438
|
+
}
|
|
13439
|
+
|
|
13440
|
+
get #dragSurface() {
|
|
13441
|
+
return this.getAttribute("drag-surface") || "parent";
|
|
13442
|
+
}
|
|
13443
|
+
|
|
13444
|
+
get #container() {
|
|
13445
|
+
const surface = this.#dragSurface;
|
|
13446
|
+
if (surface === "parent") return this.parentElement;
|
|
13447
|
+
return this.closest(surface);
|
|
13448
|
+
}
|
|
13449
|
+
|
|
13450
|
+
get #handleDragSurface() {
|
|
13451
|
+
const surface = this.#dragSurface;
|
|
13452
|
+
if (surface === "parent") {
|
|
13453
|
+
const container = this.parentElement;
|
|
13454
|
+
if (container) {
|
|
13455
|
+
container.setAttribute("data-fig-canvas-control-surface", "");
|
|
13456
|
+
return "[data-fig-canvas-control-surface]";
|
|
13457
|
+
}
|
|
13458
|
+
}
|
|
13459
|
+
return surface;
|
|
13460
|
+
}
|
|
13461
|
+
|
|
13462
|
+
#resolveRadius(containerWidth) {
|
|
13463
|
+
if (this.#radiusIsPercent) return (this.#radius / 100) * containerWidth;
|
|
13464
|
+
return this.#radius;
|
|
13465
|
+
}
|
|
13466
|
+
|
|
13467
|
+
#formatRadius() {
|
|
13468
|
+
if (this.#radiusIsPercent) return `${Math.round(this.#radius)}%`;
|
|
13469
|
+
return `${Math.round(this.#radius)}px`;
|
|
13470
|
+
}
|
|
13471
|
+
|
|
13472
|
+
connectedCallback() {
|
|
13473
|
+
this.#parseValue();
|
|
13474
|
+
this.#render();
|
|
13475
|
+
}
|
|
13476
|
+
|
|
13477
|
+
disconnectedCallback() {
|
|
13478
|
+
this.#teardownRadiusDrag();
|
|
13479
|
+
}
|
|
13480
|
+
|
|
13481
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
13482
|
+
if (oldVal === newVal) return;
|
|
13483
|
+
if (name === "value" && !this.#isDragging && !this.#isSecondDragging && !this.#isRadiusDragging && !this.#isAngleDragging) {
|
|
13484
|
+
this.#parseValue();
|
|
13485
|
+
if (this.#pointHandle) this.#syncPositions();
|
|
13486
|
+
else this.#render();
|
|
13487
|
+
}
|
|
13488
|
+
if (name === "type") {
|
|
13489
|
+
this.#parseValue();
|
|
13490
|
+
this.#render();
|
|
13491
|
+
}
|
|
13492
|
+
if (name === "color" && this.#pointHandle) {
|
|
13493
|
+
if (newVal) this.#pointHandle.setAttribute("color", newVal);
|
|
13494
|
+
else this.#pointHandle.removeAttribute("color");
|
|
13495
|
+
}
|
|
13496
|
+
if (name === "disabled") {
|
|
13497
|
+
this.#render();
|
|
13498
|
+
}
|
|
13499
|
+
if (name === "tooltips") {
|
|
13500
|
+
this.#render();
|
|
13501
|
+
}
|
|
13502
|
+
if (name === "snapping" && this.#pointHandle) {
|
|
13503
|
+
this.#pointHandle.setAttribute("drag-snapping", newVal || "false");
|
|
13504
|
+
if (this.#secondHandle) this.#secondHandle.setAttribute("drag-snapping", newVal || "false");
|
|
13505
|
+
}
|
|
13506
|
+
if (name === "name") {
|
|
13507
|
+
if (this.#pointTooltip) this.#pointTooltip.setAttribute("text", this.#pointTipText);
|
|
13508
|
+
if (this.#secondTooltip) this.#secondTooltip.setAttribute("text", this.#secondTipText);
|
|
13509
|
+
}
|
|
13510
|
+
}
|
|
13511
|
+
|
|
13512
|
+
#parseValue() {
|
|
13513
|
+
const raw = this.getAttribute("value");
|
|
13514
|
+
if (!raw) return;
|
|
13515
|
+
try {
|
|
13516
|
+
const v = JSON.parse(raw);
|
|
13517
|
+
if (typeof v.x === "number") this.#x = v.x;
|
|
13518
|
+
if (typeof v.y === "number") this.#y = v.y;
|
|
13519
|
+
if (v.radius !== undefined) {
|
|
13520
|
+
const rs = String(v.radius);
|
|
13521
|
+
if (rs.endsWith("%")) {
|
|
13522
|
+
this.#radiusIsPercent = true;
|
|
13523
|
+
this.#radius = parseFloat(rs);
|
|
13524
|
+
} else {
|
|
13525
|
+
this.#radiusIsPercent = false;
|
|
13526
|
+
this.#radius = parseFloat(rs);
|
|
13527
|
+
}
|
|
13528
|
+
if (!Number.isFinite(this.#radius)) this.#radius = 0;
|
|
13529
|
+
}
|
|
13530
|
+
if (typeof v.angle === "number") this.#angle = v.angle;
|
|
13531
|
+
if (typeof v.x2 === "number") this.#x2 = v.x2;
|
|
13532
|
+
if (typeof v.y2 === "number") this.#y2 = v.y2;
|
|
13533
|
+
} catch { /* ignore */ }
|
|
13534
|
+
}
|
|
13535
|
+
|
|
13536
|
+
get value() {
|
|
13537
|
+
const v = { x: this.#x, y: this.#y };
|
|
13538
|
+
if (this.#hasRadius) {
|
|
13539
|
+
v.radius = this.#radiusIsPercent ? `${this.#radius}%` : this.#radius;
|
|
13540
|
+
}
|
|
13541
|
+
if (this.#hasAngle) v.angle = this.#angle;
|
|
13542
|
+
if (this.#hasSecondPoint) {
|
|
13543
|
+
v.x2 = this.#x2;
|
|
13544
|
+
v.y2 = this.#y2;
|
|
13545
|
+
}
|
|
13546
|
+
return v;
|
|
13547
|
+
}
|
|
13548
|
+
|
|
13549
|
+
set value(val) {
|
|
13550
|
+
if (typeof val === "object") {
|
|
13551
|
+
this.setAttribute("value", JSON.stringify(val));
|
|
13552
|
+
} else if (typeof val === "string") {
|
|
13553
|
+
this.setAttribute("value", val);
|
|
13554
|
+
}
|
|
13555
|
+
}
|
|
13556
|
+
|
|
13557
|
+
#render() {
|
|
13558
|
+
this.innerHTML = "";
|
|
13559
|
+
this.#pointHandle = null;
|
|
13560
|
+
this.#secondHandle = null;
|
|
13561
|
+
this.#angleHandle = null;
|
|
13562
|
+
this.#radiusSvg = null;
|
|
13563
|
+
this.#angleSvg = null;
|
|
13564
|
+
this.#pointTooltip = null;
|
|
13565
|
+
this.#secondTooltip = null;
|
|
13566
|
+
this.#radiusTooltip = null;
|
|
13567
|
+
this.#angleTooltip = null;
|
|
13568
|
+
|
|
13569
|
+
const disabled = this.hasAttribute("disabled");
|
|
13570
|
+
const type = this.#type;
|
|
13571
|
+
const tooltips = this.#tooltipsEnabled;
|
|
13572
|
+
|
|
13573
|
+
const handleSurface = this.#handleDragSurface;
|
|
13574
|
+
|
|
13575
|
+
const handle = document.createElement("fig-handle");
|
|
13576
|
+
handle.setAttribute("drag", "true");
|
|
13577
|
+
handle.setAttribute("drag-surface", handleSurface);
|
|
13578
|
+
handle.setAttribute("drag-axes", "x,y");
|
|
13579
|
+
handle.setAttribute("drag-snapping", this.#snappingMode);
|
|
13580
|
+
handle.setAttribute("value", `${this.#x}% ${this.#y}%`);
|
|
13581
|
+
if (disabled) handle.setAttribute("disabled", "");
|
|
13582
|
+
if (type === "color") {
|
|
13583
|
+
handle.setAttribute("type", "color");
|
|
13584
|
+
const color = this.getAttribute("color");
|
|
13585
|
+
if (color) handle.setAttribute("color", color);
|
|
13586
|
+
}
|
|
13587
|
+
if (this.#hasSecondPoint) {
|
|
13588
|
+
handle.setAttribute("hit-area", "12 circle");
|
|
13589
|
+
handle.setAttribute("hit-area-mode", "delegate");
|
|
13590
|
+
}
|
|
13591
|
+
this.#pointHandle = handle;
|
|
13592
|
+
|
|
13593
|
+
if (this.#hasRadius) {
|
|
13594
|
+
this.#createRadiusSvg();
|
|
13595
|
+
}
|
|
13596
|
+
|
|
13597
|
+
if (this.#hasLine) {
|
|
13598
|
+
this.#createAngleSvg();
|
|
13599
|
+
}
|
|
13600
|
+
|
|
13601
|
+
if (tooltips) {
|
|
13602
|
+
const tip = document.createElement("fig-tooltip");
|
|
13603
|
+
tip.setAttribute("action", "manual");
|
|
13604
|
+
tip.setAttribute("text", this.#pointTipText);
|
|
13605
|
+
tip.appendChild(handle);
|
|
13606
|
+
this.appendChild(tip);
|
|
13607
|
+
this.#pointTooltip = tip;
|
|
13608
|
+
} else {
|
|
13609
|
+
this.appendChild(handle);
|
|
13610
|
+
}
|
|
13611
|
+
|
|
13612
|
+
if (this.#hasAngle) {
|
|
13613
|
+
this.#createAngleHandle(disabled, tooltips, handleSurface);
|
|
13614
|
+
}
|
|
13615
|
+
|
|
13616
|
+
if (this.#hasSecondPoint) {
|
|
13617
|
+
this.#createSecondHandle(disabled, tooltips, handleSurface);
|
|
13618
|
+
}
|
|
13619
|
+
|
|
13620
|
+
this.#setupEventListeners();
|
|
13621
|
+
requestAnimationFrame(() => this.#syncPositions());
|
|
13622
|
+
}
|
|
13623
|
+
|
|
13624
|
+
#createRadiusSvg() {
|
|
13625
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
13626
|
+
const svg = document.createElementNS(ns, "svg");
|
|
13627
|
+
svg.classList.add("fig-canvas-control-radius");
|
|
13628
|
+
svg.setAttribute("overflow", "visible");
|
|
13629
|
+
const hitCircle = document.createElementNS(ns, "circle");
|
|
13630
|
+
hitCircle.classList.add("fig-canvas-control-radius-hit");
|
|
13631
|
+
svg.appendChild(hitCircle);
|
|
13632
|
+
const circle = document.createElementNS(ns, "circle");
|
|
13633
|
+
svg.appendChild(circle);
|
|
13634
|
+
this.#radiusSvg = svg;
|
|
13635
|
+
|
|
13636
|
+
if (this.#tooltipsEnabled) {
|
|
13637
|
+
const tip = document.createElement("fig-tooltip");
|
|
13638
|
+
tip.setAttribute("action", "manual");
|
|
13639
|
+
tip.setAttribute("text", this.#formatRadius());
|
|
13640
|
+
tip.appendChild(svg);
|
|
13641
|
+
this.appendChild(tip);
|
|
13642
|
+
this.#radiusTooltip = tip;
|
|
13643
|
+
} else {
|
|
13644
|
+
this.appendChild(svg);
|
|
13645
|
+
}
|
|
13646
|
+
|
|
13647
|
+
this.#setupRadiusDrag(hitCircle);
|
|
13648
|
+
}
|
|
13649
|
+
|
|
13650
|
+
#createAngleSvg() {
|
|
13651
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
13652
|
+
const svg = document.createElementNS(ns, "svg");
|
|
13653
|
+
svg.classList.add("fig-canvas-control-angle-svg");
|
|
13654
|
+
svg.setAttribute("overflow", "visible");
|
|
13655
|
+
svg.style.position = "absolute";
|
|
13656
|
+
svg.style.pointerEvents = "none";
|
|
13657
|
+
const line = document.createElementNS(ns, "line");
|
|
13658
|
+
line.classList.add("fig-canvas-control-angle-line");
|
|
13659
|
+
svg.appendChild(line);
|
|
13660
|
+
this.#angleSvg = svg;
|
|
13661
|
+
this.appendChild(svg);
|
|
13662
|
+
}
|
|
13663
|
+
|
|
13664
|
+
#createAngleHandle(disabled, tooltips, handleSurface) {
|
|
13665
|
+
const handle = document.createElement("fig-handle");
|
|
13666
|
+
handle.setAttribute("drag", "true");
|
|
13667
|
+
handle.setAttribute("drag-surface", handleSurface);
|
|
13668
|
+
handle.setAttribute("drag-axes", "x,y");
|
|
13669
|
+
handle.setAttribute("size", "small");
|
|
13670
|
+
handle.setAttribute("hit-area", "12 circle");
|
|
13671
|
+
handle.setAttribute("hit-area-mode", "delegate");
|
|
13672
|
+
if (disabled) handle.setAttribute("disabled", "");
|
|
13673
|
+
this.#angleHandle = handle;
|
|
13674
|
+
|
|
13675
|
+
if (tooltips) {
|
|
13676
|
+
const tip = document.createElement("fig-tooltip");
|
|
13677
|
+
tip.setAttribute("action", "manual");
|
|
13678
|
+
tip.setAttribute("text", `${Math.round(this.#angle)}°`);
|
|
13679
|
+
tip.appendChild(handle);
|
|
13680
|
+
this.appendChild(tip);
|
|
13681
|
+
this.#angleTooltip = tip;
|
|
13682
|
+
} else {
|
|
13683
|
+
this.appendChild(handle);
|
|
13684
|
+
}
|
|
13685
|
+
}
|
|
13686
|
+
|
|
13687
|
+
#createSecondHandle(disabled, tooltips, handleSurface) {
|
|
13688
|
+
const handle = document.createElement("fig-handle");
|
|
13689
|
+
handle.setAttribute("drag", "true");
|
|
13690
|
+
handle.setAttribute("drag-surface", handleSurface);
|
|
13691
|
+
handle.setAttribute("drag-axes", "x,y");
|
|
13692
|
+
handle.setAttribute("drag-snapping", this.#snappingMode);
|
|
13693
|
+
handle.setAttribute("hit-area", "12 circle");
|
|
13694
|
+
handle.setAttribute("hit-area-mode", "delegate");
|
|
13695
|
+
handle.setAttribute("value", `${this.#x2}% ${this.#y2}%`);
|
|
13696
|
+
if (disabled) handle.setAttribute("disabled", "");
|
|
13697
|
+
this.#secondHandle = handle;
|
|
13698
|
+
|
|
13699
|
+
if (tooltips) {
|
|
13700
|
+
const tip = document.createElement("fig-tooltip");
|
|
13701
|
+
tip.setAttribute("action", "manual");
|
|
13702
|
+
tip.setAttribute("text", this.#secondTipText);
|
|
13703
|
+
tip.appendChild(handle);
|
|
13704
|
+
this.appendChild(tip);
|
|
13705
|
+
this.#secondTooltip = tip;
|
|
13706
|
+
} else {
|
|
13707
|
+
this.appendChild(handle);
|
|
13708
|
+
}
|
|
13709
|
+
}
|
|
13710
|
+
|
|
13711
|
+
#resizeCursorSvg(deg) {
|
|
13712
|
+
const r = Math.round(deg);
|
|
13713
|
+
return `url("data:image/svg+xml,%3Csvg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform='rotate(${r} 16 16)'%3E%3Cg filter='url(%23f)'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M11.1212 16.9998L11.5607 17.4394C12.1465 18.0252 12.1464 18.975 11.5606 19.5607C10.9748 20.1465 10.0251 20.1465 9.4393 19.5606L6.4393 16.5604C5.85354 15.9746 5.85357 15.0249 6.43938 14.4391L9.43938 11.4393C10.0252 10.8535 10.9749 10.8536 11.5607 11.4394C12.1465 12.0252 12.1464 12.9749 11.5606 13.5607L11.1215 13.9998L20.8786 13.9999L20.4394 13.5607C19.8536 12.9749 19.8535 12.0252 20.4393 11.4394C21.0251 10.8536 21.9749 10.8536 22.5606 11.4394L25.5606 14.4393C25.842 14.7206 26 15.1021 26 15.4999C26 15.8978 25.842 16.2793 25.5607 16.5606L22.5607 19.5607C21.9749 20.1465 21.0251 20.1465 20.4393 19.5607C19.8536 18.9749 19.8535 18.0252 20.4393 17.4394L20.8788 16.9999L11.1212 16.9998Z' fill='white'/%3E%3C/g%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.8536 12.1465C11.0488 12.3417 11.0488 12.6583 10.8535 12.8536L8.70715 14.9998L23.2929 14.9999L21.1465 12.8536C20.9512 12.6583 20.9512 12.3417 21.1464 12.1465C21.3417 11.9512 21.6583 11.9512 21.8535 12.1465L24.8535 15.1464C24.9473 15.2402 25 15.3673 25 15.4999C25 15.6326 24.9473 15.7597 24.8536 15.8535L21.8536 18.8536C21.6583 19.0488 21.3417 19.0488 21.1465 18.8536C20.9512 18.6583 20.9512 18.3417 21.1464 18.1465L23.2929 15.9999L8.70705 15.9998L10.8536 18.1465C11.0488 18.3417 11.0488 18.6583 10.8535 18.8536C10.6583 19.0488 10.3417 19.0488 10.1464 18.8535L7.14643 15.8533C6.95118 15.658 6.95119 15.3415 7.14646 15.1462L10.1465 12.1464C10.3417 11.9512 10.6583 11.9512 10.8536 12.1465Z' fill='black'/%3E%3C/g%3E%3Cdefs%3E%3Cfilter id='f' x='3' y='9' width='26' height='15' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'%3E%3CfeFlood flood-opacity='0' result='a'/%3E%3CfeColorMatrix in='SourceAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0' result='b'/%3E%3CfeOffset dy='1'/%3E%3CfeGaussianBlur stdDeviation='1.5'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.35 0'/%3E%3CfeBlend in2='a' result='c'/%3E%3CfeBlend in='SourceGraphic' in2='c'/%3E%3C/filter%3E%3C/defs%3E%3C/svg%3E") 16 16, nwse-resize`;
|
|
13714
|
+
}
|
|
13715
|
+
|
|
13716
|
+
#rotateCursorSvg(deg) {
|
|
13717
|
+
const r = Math.round(deg - 45);
|
|
13718
|
+
return `url("data:image/svg+xml,%3Csvg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform='rotate(${r} 16 16)'%3E%3Cg filter='url(%23f)'%3E%3Cpath d='M12.5607 22.4393L12.0216 21.9002C17.1558 21.2216 21.2216 17.1558 21.9002 12.0216L22.4393 12.5607C23.0251 13.1464 23.9749 13.1464 24.5607 12.5607C25.1464 11.9749 25.1464 11.0251 24.5607 10.4393L21.5607 7.43934C20.9749 6.85355 20.0251 6.85355 19.4393 7.43934L16.4393 10.4393C15.8536 11.0251 15.8536 11.9749 16.4393 12.5607C17.0251 13.1464 17.9749 13.1464 18.5607 12.5607L18.8056 12.3157C18.1013 15.5527 15.5527 18.1013 12.3157 18.8056L12.5607 18.5607C13.1464 17.9749 13.1464 17.0251 12.5607 16.4393C11.9749 15.8536 11.0251 15.8536 10.4393 16.4393L7.43934 19.4393C6.85356 20.0251 6.85356 20.9749 7.43934 21.5607L10.4393 24.5607C11.0251 25.1464 11.9749 25.1464 12.5607 24.5607C13.1464 23.9749 13.1464 23.0251 12.5607 22.4393Z' fill='white'/%3E%3C/g%3E%3Cpath d='M23.8536 11.8536C23.6583 12.0488 23.3417 12.0488 23.1464 11.8536L21 9.70711V10.5C21 16.299 16.299 21 10.5 21H9.70711L11.8536 23.1464C12.0488 23.3417 12.0488 23.6583 11.8536 23.8536C11.6583 24.0488 11.3417 24.0488 11.1464 23.8536L8.14645 20.8536C7.95119 20.6583 7.95119 20.3417 8.14645 20.1464L11.1464 17.1464C11.3417 16.9512 11.6583 16.9512 11.8536 17.1464C12.0488 17.3417 12.0488 17.6583 11.8536 17.8536L9.70711 20H10.5C15.7467 20 20 15.7467 20 10.5V9.70711L17.8536 11.8536C17.6583 12.0488 17.3417 12.0488 17.1464 11.8536C16.9512 11.6583 16.9512 11.3417 17.1464 11.1464L20.1464 8.14645C20.3417 7.95119 20.6583 7.95119 20.8536 8.14645L23.8536 11.1464C24.0488 11.3417 24.0488 11.6583 23.8536 11.8536Z' fill='black'/%3E%3C/g%3E%3Cdefs%3E%3Cfilter id='f' x='4' y='5' width='24' height='24' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'%3E%3CfeFlood flood-opacity='0' result='a'/%3E%3CfeColorMatrix in='SourceAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0' result='b'/%3E%3CfeOffset dy='1'/%3E%3CfeGaussianBlur stdDeviation='1.5'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.35 0'/%3E%3CfeBlend in2='a' result='c'/%3E%3CfeBlend in='SourceGraphic' in2='c'/%3E%3C/filter%3E%3C/defs%3E%3C/svg%3E") 16 16, pointer`;
|
|
13719
|
+
}
|
|
13720
|
+
|
|
13721
|
+
#syncAngleCursor() {
|
|
13722
|
+
if (!this.#angleHandle || !this.#hasAngle) return;
|
|
13723
|
+
const hitArea = this.#angleHandle.querySelector(".fig-handle-hit-area");
|
|
13724
|
+
if (!hitArea) return;
|
|
13725
|
+
hitArea.style.cursor = this.#rotateCursorSvg(this.#angle);
|
|
13726
|
+
}
|
|
13727
|
+
|
|
13728
|
+
#pointPointLineDeg() {
|
|
13729
|
+
return (Math.atan2(this.#y2 - this.#y, this.#x2 - this.#x) * 180) / Math.PI;
|
|
13730
|
+
}
|
|
13731
|
+
|
|
13732
|
+
#syncPointPointCursors() {
|
|
13733
|
+
if (!this.#hasSecondPoint) return;
|
|
13734
|
+
const deg = this.#pointPointLineDeg();
|
|
13735
|
+
const setHitCursor = (handle, rotateDeg) => {
|
|
13736
|
+
if (!handle) return;
|
|
13737
|
+
const hitArea = handle.querySelector(".fig-handle-hit-area");
|
|
13738
|
+
if (hitArea) hitArea.style.cursor = this.#rotateCursorSvg(rotateDeg);
|
|
13739
|
+
};
|
|
13740
|
+
setHitCursor(this.#pointHandle, deg + 180);
|
|
13741
|
+
setHitCursor(this.#secondHandle, deg);
|
|
13742
|
+
}
|
|
13743
|
+
|
|
13744
|
+
#syncPositions() {
|
|
13745
|
+
const container = this.#container;
|
|
13746
|
+
if (!container || !this.#pointHandle) return;
|
|
13747
|
+
const rect = container.getBoundingClientRect();
|
|
13748
|
+
|
|
13749
|
+
this.#pointHandle.setAttribute("value", `${this.#x}% ${this.#y}%`);
|
|
13750
|
+
|
|
13751
|
+
if (this.#radiusSvg) {
|
|
13752
|
+
const cx = (this.#x / 100) * rect.width;
|
|
13753
|
+
const cy = (this.#y / 100) * rect.height;
|
|
13754
|
+
const r = this.#resolveRadius(rect.width);
|
|
13755
|
+
const svg = this.#radiusSvg;
|
|
13756
|
+
const d = Math.max(r * 2, 1);
|
|
13757
|
+
svg.style.position = "absolute";
|
|
13758
|
+
svg.style.width = `${d}px`;
|
|
13759
|
+
svg.style.height = `${d}px`;
|
|
13760
|
+
svg.style.left = `${cx - r}px`;
|
|
13761
|
+
svg.style.top = `${cy - r}px`;
|
|
13762
|
+
svg.setAttribute("viewBox", `0 0 ${d} ${d}`);
|
|
13763
|
+
const circles = svg.querySelectorAll("circle");
|
|
13764
|
+
for (const c of circles) {
|
|
13765
|
+
c.setAttribute("cx", String(r));
|
|
13766
|
+
c.setAttribute("cy", String(r));
|
|
13767
|
+
c.setAttribute("r", String(Math.max(r - 1, 0)));
|
|
13768
|
+
}
|
|
13769
|
+
if (this.#radiusTooltip) {
|
|
13770
|
+
this.#radiusTooltip.setAttribute("text", this.#formatRadius());
|
|
13771
|
+
}
|
|
13772
|
+
}
|
|
13773
|
+
|
|
13774
|
+
if (this.#angleSvg && this.#hasLine) {
|
|
13775
|
+
const cx = (this.#x / 100) * rect.width;
|
|
13776
|
+
const cy = (this.#y / 100) * rect.height;
|
|
13777
|
+
let lx2, ly2;
|
|
13778
|
+
if (this.#hasSecondPoint) {
|
|
13779
|
+
lx2 = (this.#x2 / 100) * rect.width;
|
|
13780
|
+
ly2 = (this.#y2 / 100) * rect.height;
|
|
13781
|
+
} else {
|
|
13782
|
+
const r = this.#resolveRadius(rect.width);
|
|
13783
|
+
const angleRad = (this.#angle * Math.PI) / 180;
|
|
13784
|
+
lx2 = cx + r * Math.cos(angleRad);
|
|
13785
|
+
ly2 = cy + r * Math.sin(angleRad);
|
|
13786
|
+
}
|
|
13787
|
+
|
|
13788
|
+
const svg = this.#angleSvg;
|
|
13789
|
+
svg.style.width = `${rect.width}px`;
|
|
13790
|
+
svg.style.height = `${rect.height}px`;
|
|
13791
|
+
svg.style.left = "0";
|
|
13792
|
+
svg.style.top = "0";
|
|
13793
|
+
svg.setAttribute("viewBox", `0 0 ${rect.width} ${rect.height}`);
|
|
13794
|
+
const line = svg.querySelector("line");
|
|
13795
|
+
if (line) {
|
|
13796
|
+
line.setAttribute("x1", String(cx));
|
|
13797
|
+
line.setAttribute("y1", String(cy));
|
|
13798
|
+
line.setAttribute("x2", String(lx2));
|
|
13799
|
+
line.setAttribute("y2", String(ly2));
|
|
13800
|
+
}
|
|
13801
|
+
}
|
|
13802
|
+
|
|
13803
|
+
if (this.#angleHandle && this.#hasAngle) {
|
|
13804
|
+
const cx = (this.#x / 100) * rect.width;
|
|
13805
|
+
const cy = (this.#y / 100) * rect.height;
|
|
13806
|
+
const r = this.#resolveRadius(rect.width);
|
|
13807
|
+
const angleRad = (this.#angle * Math.PI) / 180;
|
|
13808
|
+
const ax = cx + r * Math.cos(angleRad);
|
|
13809
|
+
const ay = cy + r * Math.sin(angleRad);
|
|
13810
|
+
const pxPct = rect.width > 0 ? (ax / rect.width) * 100 : 0;
|
|
13811
|
+
const pyPct = rect.height > 0 ? (ay / rect.height) * 100 : 0;
|
|
13812
|
+
this.#angleHandle.setAttribute("value", `${pxPct}% ${pyPct}%`);
|
|
13813
|
+
|
|
13814
|
+
if (this.#angleTooltip) {
|
|
13815
|
+
this.#angleTooltip.setAttribute("text", `${Math.round(this.#angle)}°`);
|
|
13816
|
+
}
|
|
13817
|
+
}
|
|
13818
|
+
|
|
13819
|
+
if (this.#secondHandle && this.#hasSecondPoint) {
|
|
13820
|
+
this.#secondHandle.setAttribute("value", `${this.#x2}% ${this.#y2}%`);
|
|
13821
|
+
if (this.#secondTooltip) {
|
|
13822
|
+
this.#secondTooltip.setAttribute("text", this.#secondTipText);
|
|
13823
|
+
}
|
|
13824
|
+
}
|
|
13825
|
+
|
|
13826
|
+
this.#syncAngleCursor();
|
|
13827
|
+
this.#syncPointPointCursors();
|
|
13828
|
+
}
|
|
13829
|
+
|
|
13830
|
+
#emitInput() {
|
|
13831
|
+
this.dispatchEvent(new CustomEvent("input", { bubbles: true, detail: this.value }));
|
|
13832
|
+
}
|
|
13833
|
+
|
|
13834
|
+
#emitChange() {
|
|
13835
|
+
this.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: this.value }));
|
|
13836
|
+
}
|
|
13837
|
+
|
|
13838
|
+
#syncValueAttribute() {
|
|
13839
|
+
this.setAttribute("value", JSON.stringify(this.value));
|
|
13840
|
+
}
|
|
13841
|
+
|
|
13842
|
+
#setupEventListeners() {
|
|
13843
|
+
if (!this.#pointHandle) return;
|
|
13844
|
+
|
|
13845
|
+
this.#pointHandle.addEventListener("input", (e) => {
|
|
13846
|
+
e.stopPropagation();
|
|
13847
|
+
if (!this.#isDragging && this.#hasSecondPoint) {
|
|
13848
|
+
this.#prevBodyCursor = document.body.style.cursor;
|
|
13849
|
+
}
|
|
13850
|
+
this.#isDragging = true;
|
|
13851
|
+
const px = e.detail?.px ?? this.#x / 100;
|
|
13852
|
+
const py = e.detail?.py ?? this.#y / 100;
|
|
13853
|
+
this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
|
|
13854
|
+
this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
|
|
13855
|
+
if (this.#pointTooltip) {
|
|
13856
|
+
this.#pointTooltip.setAttribute("text", this.#pointTipText);
|
|
13857
|
+
this.#pointTooltip.setAttribute("show", "true");
|
|
13858
|
+
this.#pointTooltip.showPopup?.();
|
|
13859
|
+
}
|
|
13860
|
+
this.#syncPositions();
|
|
13861
|
+
if (this.#hasSecondPoint) {
|
|
13862
|
+
document.body.style.cursor = this.#resizeCursorSvg(this.#pointPointLineDeg());
|
|
13863
|
+
}
|
|
13864
|
+
this.#emitInput();
|
|
13865
|
+
});
|
|
13866
|
+
|
|
13867
|
+
this.#pointHandle.addEventListener("change", (e) => {
|
|
13868
|
+
e.stopPropagation();
|
|
13869
|
+
const px = e.detail?.px ?? this.#x / 100;
|
|
13870
|
+
const py = e.detail?.py ?? this.#y / 100;
|
|
13871
|
+
this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
|
|
13872
|
+
this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
|
|
13873
|
+
if (this.#pointTooltip) this.#pointTooltip.removeAttribute("show");
|
|
13874
|
+
if (this.#hasSecondPoint) {
|
|
13875
|
+
document.body.style.cursor = this.#prevBodyCursor ?? "";
|
|
13876
|
+
}
|
|
13877
|
+
this.#syncPositions();
|
|
13878
|
+
this.#syncValueAttribute();
|
|
13879
|
+
this.#emitChange();
|
|
13880
|
+
requestAnimationFrame(() => { this.#isDragging = false; });
|
|
13881
|
+
});
|
|
13882
|
+
|
|
13883
|
+
if (this.#angleHandle) {
|
|
13884
|
+
this.#angleHandle.addEventListener("input", (e) => {
|
|
13885
|
+
e.stopPropagation();
|
|
13886
|
+
this.#isAngleDragging = true;
|
|
13887
|
+
this.classList.add("fig-canvas-control-ring-active");
|
|
13888
|
+
const container = this.#container;
|
|
13889
|
+
if (!container) return;
|
|
13890
|
+
const rect = container.getBoundingClientRect();
|
|
13891
|
+
const cx = (this.#x / 100) * rect.width;
|
|
13892
|
+
const cy = (this.#y / 100) * rect.height;
|
|
13893
|
+
const hx = e.detail?.x ?? 0;
|
|
13894
|
+
const hy = e.detail?.y ?? 0;
|
|
13895
|
+
const hw = this.#angleHandle.offsetWidth / 2;
|
|
13896
|
+
const hh = this.#angleHandle.offsetHeight / 2;
|
|
13897
|
+
const dx = (hx + hw) - cx;
|
|
13898
|
+
const dy = (hy + hh) - cy;
|
|
13899
|
+
let angle = (Math.atan2(dy, dx) * 180) / Math.PI;
|
|
13900
|
+
if (this.#shouldSnap(e.detail?.shiftKey)) {
|
|
13901
|
+
angle = Math.round(angle / 15) * 15;
|
|
13902
|
+
}
|
|
13903
|
+
this.#angle = angle;
|
|
13904
|
+
|
|
13905
|
+
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
13906
|
+
if (this.#shouldSnap(e.detail?.shiftKey)) {
|
|
13907
|
+
const step = this.#radiusIsPercent ? 5 : 10;
|
|
13908
|
+
if (this.#radiusIsPercent) {
|
|
13909
|
+
let pct = (dist / rect.width) * 100;
|
|
13910
|
+
pct = Math.round(pct / step) * step;
|
|
13911
|
+
dist = (pct / 100) * rect.width;
|
|
13912
|
+
} else {
|
|
13913
|
+
dist = Math.round(dist / step) * step;
|
|
13914
|
+
}
|
|
13915
|
+
}
|
|
13916
|
+
if (this.#radiusIsPercent) {
|
|
13917
|
+
this.#radius = Math.max(0, (dist / rect.width) * 100);
|
|
13918
|
+
} else {
|
|
13919
|
+
this.#radius = Math.max(0, dist);
|
|
13920
|
+
}
|
|
13921
|
+
|
|
13922
|
+
this.#syncPositions();
|
|
13923
|
+
if (this.#angleTooltip) {
|
|
13924
|
+
this.#angleTooltip.setAttribute("text", `${Math.round(this.#angle)}°`);
|
|
13925
|
+
this.#angleTooltip.setAttribute("show", "true");
|
|
13926
|
+
this.#angleTooltip.showPopup?.();
|
|
13927
|
+
}
|
|
13928
|
+
if (this.#radiusTooltip) {
|
|
13929
|
+
this.#radiusTooltip.setAttribute("text", this.#formatRadius());
|
|
13930
|
+
}
|
|
13931
|
+
this.#emitInput();
|
|
13932
|
+
});
|
|
13933
|
+
|
|
13934
|
+
this.#angleHandle.addEventListener("change", (e) => {
|
|
13935
|
+
e.stopPropagation();
|
|
13936
|
+
this.classList.remove("fig-canvas-control-ring-active");
|
|
13937
|
+
if (this.#angleTooltip) this.#angleTooltip.removeAttribute("show");
|
|
13938
|
+
this.#syncPositions();
|
|
13939
|
+
this.#syncValueAttribute();
|
|
13940
|
+
this.#emitChange();
|
|
13941
|
+
requestAnimationFrame(() => { this.#isAngleDragging = false; });
|
|
13942
|
+
});
|
|
13943
|
+
|
|
13944
|
+
this.#angleHandle.addEventListener("hitareadown", (e) => {
|
|
13945
|
+
e.stopPropagation();
|
|
13946
|
+
const origEvent = e.detail?.originalEvent;
|
|
13947
|
+
if (!origEvent) return;
|
|
13948
|
+
origEvent.preventDefault();
|
|
13949
|
+
this.#isAngleDragging = true;
|
|
13950
|
+
this.classList.add("fig-canvas-control-ring-active");
|
|
13951
|
+
const container = this.#container;
|
|
13952
|
+
if (!container) return;
|
|
13953
|
+
|
|
13954
|
+
if (this.#angleTooltip) {
|
|
13955
|
+
this.#angleTooltip.setAttribute("show", "true");
|
|
13956
|
+
this.#angleTooltip.showPopup?.();
|
|
13957
|
+
}
|
|
13958
|
+
|
|
13959
|
+
const prevBodyCursor = document.body.style.cursor;
|
|
13960
|
+
document.body.style.cursor = this.#rotateCursorSvg(this.#angle);
|
|
13961
|
+
|
|
13962
|
+
const onMove = (ev) => {
|
|
13963
|
+
const rect = container.getBoundingClientRect();
|
|
13964
|
+
const cx = (this.#x / 100) * rect.width;
|
|
13965
|
+
const cy = (this.#y / 100) * rect.height;
|
|
13966
|
+
const dx = ev.clientX - rect.left - cx;
|
|
13967
|
+
const dy = ev.clientY - rect.top - cy;
|
|
13968
|
+
let angle = (Math.atan2(dy, dx) * 180) / Math.PI;
|
|
13969
|
+
if (this.#shouldSnap(ev.shiftKey)) {
|
|
13970
|
+
angle = Math.round(angle / 15) * 15;
|
|
13971
|
+
}
|
|
13972
|
+
this.#angle = angle;
|
|
13973
|
+
this.#syncPositions();
|
|
13974
|
+
document.body.style.cursor = this.#rotateCursorSvg(this.#angle);
|
|
13975
|
+
if (this.#angleTooltip) {
|
|
13976
|
+
this.#angleTooltip.setAttribute("text", `${Math.round(this.#angle)}°`);
|
|
13977
|
+
}
|
|
13978
|
+
this.#emitInput();
|
|
13979
|
+
};
|
|
13980
|
+
|
|
13981
|
+
const onUp = () => {
|
|
13982
|
+
this.#isAngleDragging = false;
|
|
13983
|
+
this.classList.remove("fig-canvas-control-ring-active");
|
|
13984
|
+
document.body.style.cursor = prevBodyCursor;
|
|
13985
|
+
if (this.#angleTooltip) this.#angleTooltip.removeAttribute("show");
|
|
13986
|
+
this.#syncValueAttribute();
|
|
13987
|
+
this.#emitChange();
|
|
13988
|
+
window.removeEventListener("pointermove", onMove);
|
|
13989
|
+
window.removeEventListener("pointerup", onUp);
|
|
13990
|
+
};
|
|
13991
|
+
|
|
13992
|
+
window.addEventListener("pointermove", onMove);
|
|
13993
|
+
window.addEventListener("pointerup", onUp);
|
|
13994
|
+
});
|
|
13995
|
+
}
|
|
13996
|
+
|
|
13997
|
+
if (this.#secondHandle) {
|
|
13998
|
+
this.#secondHandle.addEventListener("input", (e) => {
|
|
13999
|
+
e.stopPropagation();
|
|
14000
|
+
if (!this.#isSecondDragging) {
|
|
14001
|
+
this.#prevBodyCursor = document.body.style.cursor;
|
|
14002
|
+
}
|
|
14003
|
+
this.#isSecondDragging = true;
|
|
14004
|
+
const px = e.detail?.px ?? this.#x2 / 100;
|
|
14005
|
+
const py = e.detail?.py ?? this.#y2 / 100;
|
|
14006
|
+
this.#x2 = Math.round(Math.max(0, Math.min(100, px * 100)));
|
|
14007
|
+
this.#y2 = Math.round(Math.max(0, Math.min(100, py * 100)));
|
|
14008
|
+
if (this.#secondTooltip) {
|
|
14009
|
+
this.#secondTooltip.setAttribute("text", this.#secondTipText);
|
|
14010
|
+
this.#secondTooltip.setAttribute("show", "true");
|
|
14011
|
+
this.#secondTooltip.showPopup?.();
|
|
14012
|
+
}
|
|
14013
|
+
this.#syncPositions();
|
|
14014
|
+
document.body.style.cursor = this.#resizeCursorSvg(this.#pointPointLineDeg());
|
|
14015
|
+
this.#emitInput();
|
|
14016
|
+
});
|
|
14017
|
+
|
|
14018
|
+
this.#secondHandle.addEventListener("change", (e) => {
|
|
14019
|
+
e.stopPropagation();
|
|
14020
|
+
document.body.style.cursor = this.#prevBodyCursor ?? "";
|
|
14021
|
+
if (this.#secondTooltip) this.#secondTooltip.removeAttribute("show");
|
|
14022
|
+
this.#syncPositions();
|
|
14023
|
+
this.#syncValueAttribute();
|
|
14024
|
+
this.#emitChange();
|
|
14025
|
+
requestAnimationFrame(() => { this.#isSecondDragging = false; });
|
|
14026
|
+
});
|
|
14027
|
+
|
|
14028
|
+
this.#setupPointPointHitArea(this.#pointHandle, true);
|
|
14029
|
+
this.#setupPointPointHitArea(this.#secondHandle, false);
|
|
14030
|
+
}
|
|
14031
|
+
}
|
|
14032
|
+
|
|
14033
|
+
#setupPointPointHitArea(handle, isFirst) {
|
|
14034
|
+
if (!handle) return;
|
|
14035
|
+
handle.addEventListener("hitareadown", (e) => {
|
|
14036
|
+
e.stopPropagation();
|
|
14037
|
+
const origEvent = e.detail?.originalEvent;
|
|
14038
|
+
if (!origEvent) return;
|
|
14039
|
+
origEvent.preventDefault();
|
|
14040
|
+
this.#isDragging = true;
|
|
14041
|
+
const container = this.#container;
|
|
14042
|
+
if (!container) return;
|
|
14043
|
+
const rect = container.getBoundingClientRect();
|
|
14044
|
+
|
|
14045
|
+
const pivotX = isFirst ? this.#x2 : this.#x;
|
|
14046
|
+
const pivotY = isFirst ? this.#y2 : this.#y;
|
|
14047
|
+
const movingX = isFirst ? this.#x : this.#x2;
|
|
14048
|
+
const movingY = isFirst ? this.#y : this.#y2;
|
|
14049
|
+
const pcx = (pivotX / 100) * rect.width;
|
|
14050
|
+
const pcy = (pivotY / 100) * rect.height;
|
|
14051
|
+
const mcx = (movingX / 100) * rect.width;
|
|
14052
|
+
const mcy = (movingY / 100) * rect.height;
|
|
14053
|
+
const fixedLen = Math.sqrt((mcx - pcx) ** 2 + (mcy - pcy) ** 2);
|
|
14054
|
+
|
|
14055
|
+
const tooltip = isFirst ? this.#pointTooltip : this.#secondTooltip;
|
|
14056
|
+
if (tooltip) {
|
|
14057
|
+
tooltip.setAttribute("show", "true");
|
|
14058
|
+
tooltip.showPopup?.();
|
|
14059
|
+
}
|
|
14060
|
+
|
|
14061
|
+
const prevBodyCursor = document.body.style.cursor;
|
|
14062
|
+
const initDeg = this.#pointPointLineDeg();
|
|
14063
|
+
document.body.style.cursor = this.#rotateCursorSvg(isFirst ? initDeg + 180 : initDeg);
|
|
14064
|
+
|
|
14065
|
+
const onMove = (ev) => {
|
|
14066
|
+
const r = container.getBoundingClientRect();
|
|
14067
|
+
const px = (pivotX / 100) * r.width;
|
|
14068
|
+
const py = (pivotY / 100) * r.height;
|
|
14069
|
+
const dx = ev.clientX - r.left - px;
|
|
14070
|
+
const dy = ev.clientY - r.top - py;
|
|
14071
|
+
let angle = Math.atan2(dy, dx);
|
|
14072
|
+
if (this.#shouldSnap(ev.shiftKey)) {
|
|
14073
|
+
const snapDeg = Math.round((angle * 180) / Math.PI / 15) * 15;
|
|
14074
|
+
angle = (snapDeg * Math.PI) / 180;
|
|
14075
|
+
}
|
|
14076
|
+
const nx = px + fixedLen * Math.cos(angle);
|
|
14077
|
+
const ny = py + fixedLen * Math.sin(angle);
|
|
14078
|
+
const newPctX = Math.round(Math.max(0, Math.min(100, (nx / r.width) * 100)));
|
|
14079
|
+
const newPctY = Math.round(Math.max(0, Math.min(100, (ny / r.height) * 100)));
|
|
14080
|
+
if (isFirst) {
|
|
14081
|
+
this.#x = newPctX;
|
|
14082
|
+
this.#y = newPctY;
|
|
14083
|
+
} else {
|
|
14084
|
+
this.#x2 = newPctX;
|
|
14085
|
+
this.#y2 = newPctY;
|
|
14086
|
+
}
|
|
14087
|
+
this.#syncPositions();
|
|
14088
|
+
const curDeg = this.#pointPointLineDeg();
|
|
14089
|
+
document.body.style.cursor = this.#rotateCursorSvg(isFirst ? curDeg + 180 : curDeg);
|
|
14090
|
+
this.#emitInput();
|
|
14091
|
+
};
|
|
14092
|
+
|
|
14093
|
+
const onUp = () => {
|
|
14094
|
+
this.#isDragging = false;
|
|
14095
|
+
document.body.style.cursor = prevBodyCursor;
|
|
14096
|
+
if (tooltip) tooltip.removeAttribute("show");
|
|
14097
|
+
this.#syncValueAttribute();
|
|
14098
|
+
this.#emitChange();
|
|
14099
|
+
window.removeEventListener("pointermove", onMove);
|
|
14100
|
+
window.removeEventListener("pointerup", onUp);
|
|
14101
|
+
};
|
|
14102
|
+
|
|
14103
|
+
window.addEventListener("pointermove", onMove);
|
|
14104
|
+
window.addEventListener("pointerup", onUp);
|
|
14105
|
+
});
|
|
14106
|
+
}
|
|
14107
|
+
|
|
14108
|
+
#setupRadiusDrag(circle) {
|
|
14109
|
+
if (!circle) return;
|
|
14110
|
+
circle.addEventListener("pointermove", (e) => {
|
|
14111
|
+
if (this.#isRadiusDragging) return;
|
|
14112
|
+
const container = this.#container;
|
|
14113
|
+
if (!container) return;
|
|
14114
|
+
const rect = container.getBoundingClientRect();
|
|
14115
|
+
const cx = (this.#x / 100) * rect.width;
|
|
14116
|
+
const cy = (this.#y / 100) * rect.height;
|
|
14117
|
+
const deg = (Math.atan2(e.clientY - rect.top - cy, e.clientX - rect.left - cx) * 180) / Math.PI;
|
|
14118
|
+
circle.style.cursor = this.#resizeCursorSvg(deg);
|
|
14119
|
+
});
|
|
14120
|
+
const onDown = (e) => {
|
|
14121
|
+
if (this.hasAttribute("disabled")) return;
|
|
14122
|
+
e.preventDefault();
|
|
14123
|
+
e.stopPropagation();
|
|
14124
|
+
this.#isRadiusDragging = true;
|
|
14125
|
+
this.classList.add("fig-canvas-control-ring-active");
|
|
14126
|
+
const container = this.#container;
|
|
14127
|
+
if (!container) return;
|
|
14128
|
+
|
|
14129
|
+
if (this.#radiusTooltip) {
|
|
14130
|
+
this.#radiusTooltip.setAttribute("show", "true");
|
|
14131
|
+
this.#radiusTooltip.showPopup?.();
|
|
14132
|
+
}
|
|
14133
|
+
|
|
14134
|
+
const prevBodyCursor = document.body.style.cursor;
|
|
14135
|
+
circle.style.pointerEvents = "none";
|
|
14136
|
+
const rect0 = container.getBoundingClientRect();
|
|
14137
|
+
const cx0 = (this.#x / 100) * rect0.width;
|
|
14138
|
+
const cy0 = (this.#y / 100) * rect0.height;
|
|
14139
|
+
const initDeg = (Math.atan2(e.clientY - rect0.top - cy0, e.clientX - rect0.left - cx0) * 180) / Math.PI;
|
|
14140
|
+
document.body.style.cursor = this.#resizeCursorSvg(initDeg);
|
|
14141
|
+
|
|
14142
|
+
const onMove = (ev) => {
|
|
14143
|
+
const rect = container.getBoundingClientRect();
|
|
14144
|
+
const cx = (this.#x / 100) * rect.width;
|
|
14145
|
+
const cy = (this.#y / 100) * rect.height;
|
|
14146
|
+
const dx = ev.clientX - rect.left - cx;
|
|
14147
|
+
const dy = ev.clientY - rect.top - cy;
|
|
14148
|
+
document.body.style.cursor = this.#resizeCursorSvg((Math.atan2(dy, dx) * 180) / Math.PI);
|
|
14149
|
+
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
14150
|
+
if (this.#shouldSnap(ev.shiftKey)) {
|
|
14151
|
+
const step = this.#radiusIsPercent ? 5 : 10;
|
|
14152
|
+
if (this.#radiusIsPercent) {
|
|
14153
|
+
let pct = (dist / rect.width) * 100;
|
|
14154
|
+
pct = Math.round(pct / step) * step;
|
|
14155
|
+
dist = (pct / 100) * rect.width;
|
|
14156
|
+
} else {
|
|
14157
|
+
dist = Math.round(dist / step) * step;
|
|
14158
|
+
}
|
|
14159
|
+
}
|
|
14160
|
+
if (this.#radiusIsPercent) {
|
|
14161
|
+
this.#radius = Math.max(0, (dist / rect.width) * 100);
|
|
14162
|
+
} else {
|
|
14163
|
+
this.#radius = Math.max(0, dist);
|
|
14164
|
+
}
|
|
14165
|
+
this.#syncPositions();
|
|
14166
|
+
this.#emitInput();
|
|
14167
|
+
};
|
|
14168
|
+
|
|
14169
|
+
const onUp = () => {
|
|
14170
|
+
this.#isRadiusDragging = false;
|
|
14171
|
+
this.classList.remove("fig-canvas-control-ring-active");
|
|
14172
|
+
circle.style.pointerEvents = "";
|
|
14173
|
+
document.body.style.cursor = prevBodyCursor;
|
|
14174
|
+
if (this.#radiusTooltip) this.#radiusTooltip.removeAttribute("show");
|
|
14175
|
+
this.#syncValueAttribute();
|
|
14176
|
+
this.#emitChange();
|
|
14177
|
+
window.removeEventListener("pointermove", onMove);
|
|
14178
|
+
window.removeEventListener("pointerup", onUp);
|
|
14179
|
+
};
|
|
14180
|
+
|
|
14181
|
+
window.addEventListener("pointermove", onMove);
|
|
14182
|
+
window.addEventListener("pointerup", onUp);
|
|
14183
|
+
};
|
|
14184
|
+
circle.addEventListener("pointerdown", onDown);
|
|
14185
|
+
this._radiusDragCleanup = () => circle.removeEventListener("pointerdown", onDown);
|
|
14186
|
+
}
|
|
14187
|
+
|
|
14188
|
+
#teardownRadiusDrag() {
|
|
14189
|
+
if (this._radiusDragCleanup) {
|
|
14190
|
+
this._radiusDragCleanup();
|
|
14191
|
+
this._radiusDragCleanup = null;
|
|
14192
|
+
}
|
|
14193
|
+
}
|
|
14194
|
+
}
|
|
14195
|
+
customElements.define("fig-canvas-control", FigCanvasControl);
|
|
14196
|
+
|
|
13346
14197
|
/* Handle */
|
|
13347
14198
|
class FigHandle extends HTMLElement {
|
|
13348
14199
|
static observedAttributes = [
|
|
@@ -13356,6 +14207,8 @@ class FigHandle extends HTMLElement {
|
|
|
13356
14207
|
"value",
|
|
13357
14208
|
"type",
|
|
13358
14209
|
"control",
|
|
14210
|
+
"hit-area",
|
|
14211
|
+
"hit-area-mode",
|
|
13359
14212
|
];
|
|
13360
14213
|
|
|
13361
14214
|
#isDragging = false;
|
|
@@ -13363,6 +14216,7 @@ class FigHandle extends HTMLElement {
|
|
|
13363
14216
|
#boundPointerDown = null;
|
|
13364
14217
|
#applyingValue = false;
|
|
13365
14218
|
#colorTip = null;
|
|
14219
|
+
#hitAreaEl = null;
|
|
13366
14220
|
|
|
13367
14221
|
get #controlMode() {
|
|
13368
14222
|
return this.getAttribute("control") || null;
|
|
@@ -13500,8 +14354,70 @@ class FigHandle extends HTMLElement {
|
|
|
13500
14354
|
this.#applyingValue = false;
|
|
13501
14355
|
}
|
|
13502
14356
|
|
|
14357
|
+
get #hitAreaMode() {
|
|
14358
|
+
return this.getAttribute("hit-area-mode") || "handle";
|
|
14359
|
+
}
|
|
14360
|
+
|
|
14361
|
+
#parseHitArea() {
|
|
14362
|
+
const raw = this.getAttribute("hit-area");
|
|
14363
|
+
if (!raw) return null;
|
|
14364
|
+
const tokens = raw.trim().split(/\s+/);
|
|
14365
|
+
let vPad = 0, hPad = 0, circle = false;
|
|
14366
|
+
const nums = [];
|
|
14367
|
+
for (const t of tokens) {
|
|
14368
|
+
if (t === "circle") { circle = true; continue; }
|
|
14369
|
+
const n = parseFloat(t);
|
|
14370
|
+
if (Number.isFinite(n)) nums.push(n);
|
|
14371
|
+
}
|
|
14372
|
+
if (nums.length >= 2) { vPad = nums[0]; hPad = nums[1]; }
|
|
14373
|
+
else if (nums.length === 1) { vPad = nums[0]; hPad = nums[0]; }
|
|
14374
|
+
else return null;
|
|
14375
|
+
return { vPad, hPad, circle };
|
|
14376
|
+
}
|
|
14377
|
+
|
|
14378
|
+
#syncHitArea() {
|
|
14379
|
+
const parsed = this.#parseHitArea();
|
|
14380
|
+
if (!parsed) {
|
|
14381
|
+
if (this.#hitAreaEl) {
|
|
14382
|
+
this.#hitAreaEl.remove();
|
|
14383
|
+
this.#hitAreaEl = null;
|
|
14384
|
+
}
|
|
14385
|
+
this.style.removeProperty("--fig-handle-hit-area-size");
|
|
14386
|
+
return;
|
|
14387
|
+
}
|
|
14388
|
+
if (!this.#hitAreaEl) {
|
|
14389
|
+
const el = document.createElement("div");
|
|
14390
|
+
el.classList.add("fig-handle-hit-area");
|
|
14391
|
+
el.addEventListener("pointerdown", (e) => this.#onHitAreaPointerDown(e));
|
|
14392
|
+
this.prepend(el);
|
|
14393
|
+
this.#hitAreaEl = el;
|
|
14394
|
+
}
|
|
14395
|
+
this.style.setProperty("--fig-handle-hit-area-size", String(parsed.hPad * 2));
|
|
14396
|
+
if (parsed.circle) {
|
|
14397
|
+
this.#hitAreaEl.style.borderRadius = "50%";
|
|
14398
|
+
} else {
|
|
14399
|
+
this.#hitAreaEl.style.borderRadius = "inherit";
|
|
14400
|
+
}
|
|
14401
|
+
}
|
|
14402
|
+
|
|
14403
|
+
#onHitAreaPointerDown(e) {
|
|
14404
|
+
if (this.hasAttribute("disabled")) return;
|
|
14405
|
+
if (e.target !== this.#hitAreaEl) return;
|
|
14406
|
+
if (this.#hitAreaMode === "delegate") {
|
|
14407
|
+
e.preventDefault();
|
|
14408
|
+
e.stopPropagation();
|
|
14409
|
+
this.dispatchEvent(new CustomEvent("hitareadown", {
|
|
14410
|
+
bubbles: true,
|
|
14411
|
+
detail: { originalEvent: e },
|
|
14412
|
+
}));
|
|
14413
|
+
} else {
|
|
14414
|
+
this.#onPointerDown(e);
|
|
14415
|
+
}
|
|
14416
|
+
}
|
|
14417
|
+
|
|
13503
14418
|
connectedCallback() {
|
|
13504
14419
|
this.#syncDrag();
|
|
14420
|
+
this.#syncHitArea();
|
|
13505
14421
|
this.addEventListener("click", this.#handleSelect);
|
|
13506
14422
|
document.addEventListener("pointerdown", this.#handleDeselect);
|
|
13507
14423
|
document.addEventListener("keydown", this.#handleKeyDown);
|
|
@@ -13513,6 +14429,7 @@ class FigHandle extends HTMLElement {
|
|
|
13513
14429
|
disconnectedCallback() {
|
|
13514
14430
|
this.#teardownDrag();
|
|
13515
14431
|
this.#hideColorTip();
|
|
14432
|
+
if (this.#hitAreaEl) { this.#hitAreaEl.remove(); this.#hitAreaEl = null; }
|
|
13516
14433
|
this.removeEventListener("click", this.#handleSelect);
|
|
13517
14434
|
document.removeEventListener("pointerdown", this.#handleDeselect);
|
|
13518
14435
|
document.removeEventListener("keydown", this.#handleKeyDown);
|
|
@@ -13566,6 +14483,7 @@ class FigHandle extends HTMLElement {
|
|
|
13566
14483
|
}
|
|
13567
14484
|
}
|
|
13568
14485
|
if (name === "drag") this.#syncDrag();
|
|
14486
|
+
if (name === "hit-area") this.#syncHitArea();
|
|
13569
14487
|
if (name === "value" && !this.#applyingValue && !this.#isDragging) {
|
|
13570
14488
|
this.#applyValue(value);
|
|
13571
14489
|
}
|
|
@@ -13608,13 +14526,19 @@ class FigHandle extends HTMLElement {
|
|
|
13608
14526
|
const handleH = this.offsetHeight;
|
|
13609
14527
|
let lastRect = null;
|
|
13610
14528
|
|
|
14529
|
+
const handleRect = this.getBoundingClientRect();
|
|
14530
|
+
const handleCenterX = handleRect.left + handleRect.width / 2;
|
|
14531
|
+
const handleCenterY = handleRect.top + handleRect.height / 2;
|
|
14532
|
+
const offsetX = e.clientX - handleCenterX;
|
|
14533
|
+
const offsetY = e.clientY - handleCenterY;
|
|
14534
|
+
|
|
13611
14535
|
const clampAndApply = (clientX, clientY, shiftKey = false) => {
|
|
13612
14536
|
const rect = container.getBoundingClientRect();
|
|
13613
14537
|
lastRect = rect;
|
|
13614
14538
|
const currentLeft = parseFloat(this.style.left) || 0;
|
|
13615
14539
|
const currentTop = parseFloat(this.style.top) || 0;
|
|
13616
|
-
const rawX = clientX - rect.left - handleW / 2;
|
|
13617
|
-
const rawY = clientY - rect.top - handleH / 2;
|
|
14540
|
+
const rawX = (clientX - offsetX) - rect.left - handleW / 2;
|
|
14541
|
+
const rawY = (clientY - offsetY) - rect.top - handleH / 2;
|
|
13618
14542
|
|
|
13619
14543
|
const clampedX = Math.max(
|
|
13620
14544
|
-handleW / 2,
|