@rogieking/figui3 4.0.0 → 4.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/components.css +9 -310
- package/fig.js +182 -1831
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -4472,268 +4472,6 @@ class FigField extends HTMLElement {
|
|
|
4472
4472
|
}
|
|
4473
4473
|
customElements.define("fig-field", FigField);
|
|
4474
4474
|
|
|
4475
|
-
/* Field + Slider wrapper */
|
|
4476
|
-
class FigFieldSlider extends HTMLElement {
|
|
4477
|
-
#field = null;
|
|
4478
|
-
#label = null;
|
|
4479
|
-
#slider = null;
|
|
4480
|
-
#observer = null;
|
|
4481
|
-
#managedSliderAttrs = new Set();
|
|
4482
|
-
#steppersSyncFrame = 0;
|
|
4483
|
-
#boundHandleSliderInput = null;
|
|
4484
|
-
#boundHandleSliderChange = null;
|
|
4485
|
-
#ignoredSliderAttrs = new Set(["variant", "color", "text", "full"]);
|
|
4486
|
-
|
|
4487
|
-
static get observedAttributes() {
|
|
4488
|
-
return ["label", "direction"];
|
|
4489
|
-
}
|
|
4490
|
-
|
|
4491
|
-
connectedCallback() {
|
|
4492
|
-
if (!this.#field) {
|
|
4493
|
-
this.#initialize();
|
|
4494
|
-
}
|
|
4495
|
-
|
|
4496
|
-
this.#syncField();
|
|
4497
|
-
this.#syncSliderAttributes();
|
|
4498
|
-
this.#bindSliderEvents();
|
|
4499
|
-
|
|
4500
|
-
if (!this.#observer) {
|
|
4501
|
-
this.#observer = new MutationObserver((mutations) => {
|
|
4502
|
-
let syncField = false;
|
|
4503
|
-
let syncSlider = false;
|
|
4504
|
-
|
|
4505
|
-
for (const mutation of mutations) {
|
|
4506
|
-
if (mutation.type === "attributes") {
|
|
4507
|
-
if (
|
|
4508
|
-
mutation.attributeName &&
|
|
4509
|
-
this.#ignoredSliderAttrs.has(mutation.attributeName)
|
|
4510
|
-
) {
|
|
4511
|
-
continue;
|
|
4512
|
-
}
|
|
4513
|
-
if (
|
|
4514
|
-
mutation.attributeName === "label" ||
|
|
4515
|
-
mutation.attributeName === "direction"
|
|
4516
|
-
) {
|
|
4517
|
-
syncField = true;
|
|
4518
|
-
} else {
|
|
4519
|
-
syncSlider = true;
|
|
4520
|
-
}
|
|
4521
|
-
}
|
|
4522
|
-
}
|
|
4523
|
-
|
|
4524
|
-
if (syncField) this.#syncField();
|
|
4525
|
-
if (syncSlider) this.#syncSliderAttributes();
|
|
4526
|
-
});
|
|
4527
|
-
}
|
|
4528
|
-
|
|
4529
|
-
this.#observer.observe(this, { attributes: true });
|
|
4530
|
-
}
|
|
4531
|
-
|
|
4532
|
-
disconnectedCallback() {
|
|
4533
|
-
this.#observer?.disconnect();
|
|
4534
|
-
if (this.#steppersSyncFrame) {
|
|
4535
|
-
cancelAnimationFrame(this.#steppersSyncFrame);
|
|
4536
|
-
this.#steppersSyncFrame = 0;
|
|
4537
|
-
}
|
|
4538
|
-
this.#unbindSliderEvents();
|
|
4539
|
-
}
|
|
4540
|
-
|
|
4541
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
4542
|
-
if (oldValue === newValue || !this.#field) return;
|
|
4543
|
-
if (name === "label" || name === "direction") {
|
|
4544
|
-
this.#syncField();
|
|
4545
|
-
}
|
|
4546
|
-
}
|
|
4547
|
-
|
|
4548
|
-
#initialize() {
|
|
4549
|
-
const initialChildren = Array.from(this.childNodes).filter((node) => {
|
|
4550
|
-
return (
|
|
4551
|
-
node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim())
|
|
4552
|
-
);
|
|
4553
|
-
});
|
|
4554
|
-
|
|
4555
|
-
const field = document.createElement("fig-field");
|
|
4556
|
-
const label = document.createElement("label");
|
|
4557
|
-
const slider = document.createElement("fig-slider");
|
|
4558
|
-
slider.setAttribute("text", "true");
|
|
4559
|
-
for (const attrName of this.#getForwardedSliderAttrNames()) {
|
|
4560
|
-
const value = this.getAttribute(attrName);
|
|
4561
|
-
slider.setAttribute(attrName, value ?? "");
|
|
4562
|
-
}
|
|
4563
|
-
|
|
4564
|
-
field.append(label, slider);
|
|
4565
|
-
|
|
4566
|
-
this.#field = field;
|
|
4567
|
-
this.#label = label;
|
|
4568
|
-
this.#slider = slider;
|
|
4569
|
-
|
|
4570
|
-
this.replaceChildren(field);
|
|
4571
|
-
|
|
4572
|
-
for (const node of initialChildren) {
|
|
4573
|
-
this.#slider.appendChild(node);
|
|
4574
|
-
}
|
|
4575
|
-
}
|
|
4576
|
-
|
|
4577
|
-
#syncField() {
|
|
4578
|
-
if (!this.#field || !this.#label) return;
|
|
4579
|
-
const hasLabelAttr = this.hasAttribute("label");
|
|
4580
|
-
const rawLabel = this.getAttribute("label");
|
|
4581
|
-
const isBlankLabel = hasLabelAttr && (rawLabel ?? "").trim() === "";
|
|
4582
|
-
|
|
4583
|
-
if (isBlankLabel) {
|
|
4584
|
-
if (this.#label.parentElement === this.#field) {
|
|
4585
|
-
this.#label.remove();
|
|
4586
|
-
}
|
|
4587
|
-
} else {
|
|
4588
|
-
this.#label.textContent = hasLabelAttr ? (rawLabel ?? "") : "Label";
|
|
4589
|
-
if (this.#label.parentElement !== this.#field) {
|
|
4590
|
-
this.#field.prepend(this.#label);
|
|
4591
|
-
}
|
|
4592
|
-
}
|
|
4593
|
-
|
|
4594
|
-
this.#field.setAttribute(
|
|
4595
|
-
"direction",
|
|
4596
|
-
this.getAttribute("direction") || "horizontal",
|
|
4597
|
-
);
|
|
4598
|
-
}
|
|
4599
|
-
|
|
4600
|
-
#syncSliderAttributes() {
|
|
4601
|
-
if (!this.#slider) return;
|
|
4602
|
-
const hostAttrs = this.#getForwardedSliderAttrNames();
|
|
4603
|
-
|
|
4604
|
-
const nextManaged = new Set(hostAttrs.filter((name) => name !== "text"));
|
|
4605
|
-
|
|
4606
|
-
for (const attrName of this.#managedSliderAttrs) {
|
|
4607
|
-
if (!nextManaged.has(attrName)) {
|
|
4608
|
-
this.#slider.removeAttribute(attrName);
|
|
4609
|
-
}
|
|
4610
|
-
}
|
|
4611
|
-
|
|
4612
|
-
for (const attrName of hostAttrs) {
|
|
4613
|
-
if (attrName === "text") continue;
|
|
4614
|
-
const value = this.getAttribute(attrName);
|
|
4615
|
-
this.#slider.setAttribute(attrName, value ?? "");
|
|
4616
|
-
}
|
|
4617
|
-
|
|
4618
|
-
this.#slider.removeAttribute("variant");
|
|
4619
|
-
this.#slider.removeAttribute("color");
|
|
4620
|
-
this.#slider.removeAttribute("transform");
|
|
4621
|
-
this.#slider.removeAttribute("full");
|
|
4622
|
-
this.#slider.setAttribute("text", "true");
|
|
4623
|
-
|
|
4624
|
-
const sliderType = (this.getAttribute("type") || "range").toLowerCase();
|
|
4625
|
-
if (sliderType === "delta" || sliderType === "stepper") {
|
|
4626
|
-
this.#slider.setAttribute(
|
|
4627
|
-
"default",
|
|
4628
|
-
this.getAttribute("default") ?? "50",
|
|
4629
|
-
);
|
|
4630
|
-
} else if (!this.hasAttribute("default")) {
|
|
4631
|
-
this.#slider.removeAttribute("default");
|
|
4632
|
-
}
|
|
4633
|
-
if (sliderType === "stepper") {
|
|
4634
|
-
this.#slider.setAttribute("step", this.getAttribute("step") ?? "10");
|
|
4635
|
-
} else if (!this.hasAttribute("step")) {
|
|
4636
|
-
this.#slider.removeAttribute("step");
|
|
4637
|
-
}
|
|
4638
|
-
if (sliderType === "opacity") {
|
|
4639
|
-
this.#slider.style.setProperty(
|
|
4640
|
-
"--color",
|
|
4641
|
-
"var(--figma-color-bg-tertiary)",
|
|
4642
|
-
);
|
|
4643
|
-
} else {
|
|
4644
|
-
this.#slider.style.removeProperty("--color");
|
|
4645
|
-
}
|
|
4646
|
-
|
|
4647
|
-
this.#managedSliderAttrs = nextManaged;
|
|
4648
|
-
this.#queueSteppersSync();
|
|
4649
|
-
}
|
|
4650
|
-
|
|
4651
|
-
#getForwardedSliderAttrNames() {
|
|
4652
|
-
const reserved = new Set([
|
|
4653
|
-
"label",
|
|
4654
|
-
"direction",
|
|
4655
|
-
"oninput",
|
|
4656
|
-
"onchange",
|
|
4657
|
-
"steppers",
|
|
4658
|
-
]);
|
|
4659
|
-
return this.getAttributeNames().filter(
|
|
4660
|
-
(name) => !reserved.has(name) && !this.#ignoredSliderAttrs.has(name),
|
|
4661
|
-
);
|
|
4662
|
-
}
|
|
4663
|
-
|
|
4664
|
-
#queueSteppersSync() {
|
|
4665
|
-
if (this.#steppersSyncFrame) {
|
|
4666
|
-
cancelAnimationFrame(this.#steppersSyncFrame);
|
|
4667
|
-
}
|
|
4668
|
-
this.#steppersSyncFrame = requestAnimationFrame(() => {
|
|
4669
|
-
this.#steppersSyncFrame = 0;
|
|
4670
|
-
this.#syncSteppersToNumberInput();
|
|
4671
|
-
});
|
|
4672
|
-
}
|
|
4673
|
-
|
|
4674
|
-
#syncSteppersToNumberInput() {
|
|
4675
|
-
if (!this.#slider) return;
|
|
4676
|
-
const numberInput = this.#slider.querySelector("fig-input-number");
|
|
4677
|
-
if (!numberInput) return;
|
|
4678
|
-
|
|
4679
|
-
const hasSteppers =
|
|
4680
|
-
this.hasAttribute("steppers") &&
|
|
4681
|
-
this.getAttribute("steppers") !== "false";
|
|
4682
|
-
if (!hasSteppers) {
|
|
4683
|
-
numberInput.removeAttribute("steppers");
|
|
4684
|
-
return;
|
|
4685
|
-
}
|
|
4686
|
-
|
|
4687
|
-
const steppersValue = this.getAttribute("steppers");
|
|
4688
|
-
numberInput.setAttribute("steppers", steppersValue ?? "");
|
|
4689
|
-
}
|
|
4690
|
-
|
|
4691
|
-
#bindSliderEvents() {
|
|
4692
|
-
if (!this.#slider) return;
|
|
4693
|
-
if (!this.#boundHandleSliderInput) {
|
|
4694
|
-
this.#boundHandleSliderInput = this.#forwardSliderEvent.bind(
|
|
4695
|
-
this,
|
|
4696
|
-
"input",
|
|
4697
|
-
);
|
|
4698
|
-
}
|
|
4699
|
-
if (!this.#boundHandleSliderChange) {
|
|
4700
|
-
this.#boundHandleSliderChange = this.#forwardSliderEvent.bind(
|
|
4701
|
-
this,
|
|
4702
|
-
"change",
|
|
4703
|
-
);
|
|
4704
|
-
}
|
|
4705
|
-
this.#slider.addEventListener("input", this.#boundHandleSliderInput);
|
|
4706
|
-
this.#slider.addEventListener("change", this.#boundHandleSliderChange);
|
|
4707
|
-
}
|
|
4708
|
-
|
|
4709
|
-
#unbindSliderEvents() {
|
|
4710
|
-
if (!this.#slider) return;
|
|
4711
|
-
if (this.#boundHandleSliderInput) {
|
|
4712
|
-
this.#slider.removeEventListener("input", this.#boundHandleSliderInput);
|
|
4713
|
-
}
|
|
4714
|
-
if (this.#boundHandleSliderChange) {
|
|
4715
|
-
this.#slider.removeEventListener("change", this.#boundHandleSliderChange);
|
|
4716
|
-
}
|
|
4717
|
-
}
|
|
4718
|
-
|
|
4719
|
-
#forwardSliderEvent(type, event) {
|
|
4720
|
-
event.stopPropagation();
|
|
4721
|
-
const detail =
|
|
4722
|
-
event instanceof CustomEvent && event.detail !== undefined
|
|
4723
|
-
? event.detail
|
|
4724
|
-
: this.#slider?.value;
|
|
4725
|
-
this.dispatchEvent(
|
|
4726
|
-
new CustomEvent(type, {
|
|
4727
|
-
detail,
|
|
4728
|
-
bubbles: true,
|
|
4729
|
-
cancelable: true,
|
|
4730
|
-
composed: true,
|
|
4731
|
-
}),
|
|
4732
|
-
);
|
|
4733
|
-
}
|
|
4734
|
-
}
|
|
4735
|
-
customElements.define("fig-field-slider", FigFieldSlider);
|
|
4736
|
-
|
|
4737
4475
|
/* Color swatch */
|
|
4738
4476
|
class FigInputColor extends HTMLElement {
|
|
4739
4477
|
rgba;
|
|
@@ -7695,142 +7433,206 @@ figDefineCustomizedBuiltIn("fig-toast", FigToast, { extends: "dialog" });
|
|
|
7695
7433
|
* @attr {string} options - Comma-separated list of dropdown options
|
|
7696
7434
|
* @attr {string} placeholder - Placeholder text for the input
|
|
7697
7435
|
* @attr {string} value - The current input value
|
|
7436
|
+
* @attr {boolean} disabled - Disables the input and dropdown button
|
|
7437
|
+
* @attr {string} experimental - Feature flag passed to internal fig-dropdown
|
|
7698
7438
|
*/
|
|
7699
7439
|
class FigComboInput extends HTMLElement {
|
|
7440
|
+
static observedAttributes = [
|
|
7441
|
+
"options",
|
|
7442
|
+
"placeholder",
|
|
7443
|
+
"value",
|
|
7444
|
+
"disabled",
|
|
7445
|
+
"experimental",
|
|
7446
|
+
];
|
|
7447
|
+
|
|
7700
7448
|
#usesCustomDropdown = false;
|
|
7701
|
-
#
|
|
7449
|
+
#input = null;
|
|
7450
|
+
#dropdown = null;
|
|
7451
|
+
#button = null;
|
|
7452
|
+
#customDropdown = null;
|
|
7453
|
+
#internalUpdate = false;
|
|
7702
7454
|
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7455
|
+
#boundHandleDropdownInput = this.#handleDropdownInput.bind(this);
|
|
7456
|
+
#boundHandleTextInput = this.#handleTextInput.bind(this);
|
|
7457
|
+
#boundHandleTextChange = this.#handleTextChange.bind(this);
|
|
7458
|
+
|
|
7459
|
+
get value() {
|
|
7460
|
+
return this.getAttribute("value") || "";
|
|
7706
7461
|
}
|
|
7707
|
-
|
|
7708
|
-
|
|
7462
|
+
|
|
7463
|
+
set value(val) {
|
|
7464
|
+
this.setAttribute("value", val ?? "");
|
|
7709
7465
|
}
|
|
7466
|
+
|
|
7710
7467
|
connectedCallback() {
|
|
7711
|
-
|
|
7468
|
+
this.#customDropdown =
|
|
7712
7469
|
Array.from(this.children).find(
|
|
7713
7470
|
(child) => child.tagName === "FIG-DROPDOWN",
|
|
7714
7471
|
) || null;
|
|
7715
|
-
this.#usesCustomDropdown = customDropdown !== null;
|
|
7716
|
-
if (customDropdown) {
|
|
7717
|
-
customDropdown.remove();
|
|
7472
|
+
this.#usesCustomDropdown = this.#customDropdown !== null;
|
|
7473
|
+
if (this.#customDropdown) {
|
|
7474
|
+
this.#customDropdown.remove();
|
|
7718
7475
|
}
|
|
7719
7476
|
|
|
7720
|
-
this
|
|
7721
|
-
this
|
|
7722
|
-
|
|
7477
|
+
this.#render();
|
|
7478
|
+
this.#setupListeners();
|
|
7479
|
+
|
|
7480
|
+
if (this.hasAttribute("disabled")) {
|
|
7481
|
+
this.#applyDisabled(true);
|
|
7482
|
+
}
|
|
7483
|
+
}
|
|
7484
|
+
|
|
7485
|
+
disconnectedCallback() {
|
|
7486
|
+
this.#teardownListeners();
|
|
7487
|
+
}
|
|
7488
|
+
|
|
7489
|
+
#render() {
|
|
7490
|
+
const options = this.#getOptions();
|
|
7491
|
+
const placeholder = this.getAttribute("placeholder") || "";
|
|
7492
|
+
const currentValue = this.value;
|
|
7723
7493
|
const experimental = this.getAttribute("experimental");
|
|
7724
|
-
const expAttr = experimental ? `experimental="${experimental}"` : "";
|
|
7494
|
+
const expAttr = experimental ? ` experimental="${experimental}"` : "";
|
|
7495
|
+
|
|
7725
7496
|
const dropdownHTML = this.#usesCustomDropdown
|
|
7726
7497
|
? ""
|
|
7727
|
-
: `<fig-dropdown type="dropdown"
|
|
7728
|
-
|
|
7729
|
-
.map((option) => `<option>${option}</option>`)
|
|
7730
|
-
.join("")}
|
|
7731
|
-
</fig-dropdown>`;
|
|
7498
|
+
: `<fig-dropdown type="dropdown"${expAttr}>${options.map((o) => `<option>${o.trim()}</option>`).join("")}</fig-dropdown>`;
|
|
7499
|
+
|
|
7732
7500
|
this.innerHTML = `<div class="input-combo">
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
</div>`;
|
|
7742
|
-
requestAnimationFrame(() => {
|
|
7743
|
-
this.input = this.querySelector("fig-input-text");
|
|
7744
|
-
const button = this.querySelector("fig-button");
|
|
7501
|
+
<fig-input-text placeholder="${placeholder}" value="${currentValue}"></fig-input-text>
|
|
7502
|
+
<fig-button type="select" variant="input" icon>
|
|
7503
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
7504
|
+
<path d="M5.87868 7.12132L8 9.24264L10.1213 7.12132" stroke="currentColor" stroke-opacity="0.9" stroke-linecap="round"/>
|
|
7505
|
+
</svg>
|
|
7506
|
+
${dropdownHTML}
|
|
7507
|
+
</fig-button>
|
|
7508
|
+
</div>`;
|
|
7745
7509
|
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
}
|
|
7753
|
-
button.append(customDropdown);
|
|
7510
|
+
this.#input = this.querySelector("fig-input-text");
|
|
7511
|
+
this.#button = this.querySelector("fig-button");
|
|
7512
|
+
|
|
7513
|
+
if (this.#usesCustomDropdown && this.#customDropdown && this.#button) {
|
|
7514
|
+
if (!this.#customDropdown.hasAttribute("type")) {
|
|
7515
|
+
this.#customDropdown.setAttribute("type", "dropdown");
|
|
7754
7516
|
}
|
|
7755
|
-
|
|
7517
|
+
if (experimental) {
|
|
7518
|
+
this.#customDropdown.setAttribute("experimental", experimental);
|
|
7519
|
+
}
|
|
7520
|
+
this.#button.append(this.#customDropdown);
|
|
7521
|
+
}
|
|
7756
7522
|
|
|
7757
|
-
|
|
7758
|
-
|
|
7523
|
+
this.#dropdown = this.querySelector("fig-dropdown");
|
|
7524
|
+
}
|
|
7759
7525
|
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7526
|
+
#setupListeners() {
|
|
7527
|
+
this.#dropdown?.addEventListener("input", this.#boundHandleDropdownInput);
|
|
7528
|
+
this.#input?.addEventListener("input", this.#boundHandleTextInput);
|
|
7529
|
+
this.#input?.addEventListener("change", this.#boundHandleTextChange);
|
|
7530
|
+
}
|
|
7763
7531
|
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
});
|
|
7532
|
+
#teardownListeners() {
|
|
7533
|
+
this.#dropdown?.removeEventListener("input", this.#boundHandleDropdownInput);
|
|
7534
|
+
this.#input?.removeEventListener("input", this.#boundHandleTextInput);
|
|
7535
|
+
this.#input?.removeEventListener("change", this.#boundHandleTextChange);
|
|
7769
7536
|
}
|
|
7770
|
-
|
|
7771
|
-
|
|
7537
|
+
|
|
7538
|
+
#handleDropdownInput(e) {
|
|
7539
|
+
e.stopPropagation();
|
|
7540
|
+
const val = e.target.closest("fig-dropdown")?.value ?? "";
|
|
7541
|
+
this.#internalUpdate = true;
|
|
7542
|
+
this.setAttribute("value", val);
|
|
7543
|
+
this.#internalUpdate = false;
|
|
7544
|
+
if (this.#input) this.#input.setAttribute("value", val);
|
|
7545
|
+
this.#emitInput();
|
|
7546
|
+
this.#emitChange();
|
|
7547
|
+
}
|
|
7548
|
+
|
|
7549
|
+
#handleTextInput(e) {
|
|
7550
|
+
e.stopPropagation();
|
|
7551
|
+
const val = e.target.value ?? "";
|
|
7552
|
+
this.#internalUpdate = true;
|
|
7553
|
+
this.setAttribute("value", val);
|
|
7554
|
+
this.#internalUpdate = false;
|
|
7555
|
+
this.#emitInput();
|
|
7772
7556
|
}
|
|
7773
|
-
|
|
7774
|
-
|
|
7557
|
+
|
|
7558
|
+
#handleTextChange(e) {
|
|
7559
|
+
e.stopPropagation();
|
|
7560
|
+
const val = e.target.value ?? "";
|
|
7561
|
+
this.#internalUpdate = true;
|
|
7562
|
+
this.setAttribute("value", val);
|
|
7563
|
+
this.#internalUpdate = false;
|
|
7564
|
+
this.#emitChange();
|
|
7775
7565
|
}
|
|
7776
|
-
|
|
7777
|
-
|
|
7566
|
+
|
|
7567
|
+
#emitInput() {
|
|
7568
|
+
this.dispatchEvent(
|
|
7569
|
+
new CustomEvent("input", {
|
|
7570
|
+
bubbles: true,
|
|
7571
|
+
cancelable: true,
|
|
7572
|
+
detail: { value: this.value },
|
|
7573
|
+
}),
|
|
7574
|
+
);
|
|
7778
7575
|
}
|
|
7779
|
-
|
|
7780
|
-
|
|
7576
|
+
|
|
7577
|
+
#emitChange() {
|
|
7578
|
+
this.dispatchEvent(
|
|
7579
|
+
new CustomEvent("change", {
|
|
7580
|
+
bubbles: true,
|
|
7581
|
+
cancelable: true,
|
|
7582
|
+
detail: { value: this.value },
|
|
7583
|
+
}),
|
|
7584
|
+
);
|
|
7781
7585
|
}
|
|
7782
|
-
|
|
7783
|
-
|
|
7586
|
+
|
|
7587
|
+
#getOptions() {
|
|
7588
|
+
return (this.getAttribute("options") || "")
|
|
7589
|
+
.split(",")
|
|
7590
|
+
.map((s) => s.trim())
|
|
7591
|
+
.filter(Boolean);
|
|
7784
7592
|
}
|
|
7593
|
+
|
|
7785
7594
|
#applyDisabled(disabled) {
|
|
7786
|
-
if (this
|
|
7787
|
-
if (disabled)
|
|
7788
|
-
|
|
7789
|
-
} else {
|
|
7790
|
-
this.input.removeAttribute("disabled");
|
|
7791
|
-
}
|
|
7595
|
+
if (this.#input) {
|
|
7596
|
+
if (disabled) this.#input.setAttribute("disabled", "");
|
|
7597
|
+
else this.#input.removeAttribute("disabled");
|
|
7792
7598
|
}
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
button.setAttribute("disabled", "");
|
|
7797
|
-
} else {
|
|
7798
|
-
button.removeAttribute("disabled");
|
|
7799
|
-
}
|
|
7599
|
+
if (this.#button) {
|
|
7600
|
+
if (disabled) this.#button.setAttribute("disabled", "");
|
|
7601
|
+
else this.#button.removeAttribute("disabled");
|
|
7800
7602
|
}
|
|
7801
7603
|
}
|
|
7604
|
+
|
|
7605
|
+
focus() {
|
|
7606
|
+
this.#input?.focus();
|
|
7607
|
+
}
|
|
7608
|
+
|
|
7802
7609
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
7610
|
+
if (oldValue === newValue) return;
|
|
7803
7611
|
switch (name) {
|
|
7804
7612
|
case "options":
|
|
7805
|
-
this
|
|
7806
|
-
|
|
7807
|
-
this
|
|
7808
|
-
.map((
|
|
7613
|
+
if (this.#dropdown && !this.#usesCustomDropdown) {
|
|
7614
|
+
const options = this.#getOptions();
|
|
7615
|
+
this.#dropdown.innerHTML = options
|
|
7616
|
+
.map((o) => `<option>${o}</option>`)
|
|
7809
7617
|
.join("");
|
|
7810
7618
|
}
|
|
7811
7619
|
break;
|
|
7812
7620
|
case "placeholder":
|
|
7813
|
-
this.placeholder
|
|
7814
|
-
if (this.input) {
|
|
7815
|
-
this.input.setAttribute("placeholder", newValue);
|
|
7816
|
-
}
|
|
7621
|
+
if (this.#input) this.#input.setAttribute("placeholder", newValue || "");
|
|
7817
7622
|
break;
|
|
7818
7623
|
case "value":
|
|
7819
|
-
this
|
|
7820
|
-
|
|
7821
|
-
this.input.setAttribute("value", newValue);
|
|
7624
|
+
if (!this.#internalUpdate && this.#input) {
|
|
7625
|
+
this.#input.setAttribute("value", newValue || "");
|
|
7822
7626
|
}
|
|
7823
7627
|
break;
|
|
7824
7628
|
case "disabled":
|
|
7825
7629
|
this.#applyDisabled(newValue !== null && newValue !== "false");
|
|
7826
7630
|
break;
|
|
7827
7631
|
case "experimental":
|
|
7828
|
-
if (this
|
|
7829
|
-
if (newValue)
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
this.dropdown.removeAttribute("experimental");
|
|
7833
|
-
}
|
|
7632
|
+
if (this.#dropdown) {
|
|
7633
|
+
if (newValue) this.#dropdown.setAttribute("experimental", newValue);
|
|
7634
|
+
else if (!this.#usesCustomDropdown)
|
|
7635
|
+
this.#dropdown.removeAttribute("experimental");
|
|
7834
7636
|
}
|
|
7835
7637
|
break;
|
|
7836
7638
|
}
|
|
@@ -8336,16 +8138,30 @@ class FigInputFile extends HTMLElement {
|
|
|
8336
8138
|
|
|
8337
8139
|
#onDragOver = (e) => {
|
|
8338
8140
|
e.preventDefault();
|
|
8339
|
-
this.
|
|
8141
|
+
if (!this.hasAttribute("dragover")) {
|
|
8142
|
+
this.setAttribute("dragover", "");
|
|
8143
|
+
if (this.#uploadBtn) {
|
|
8144
|
+
this.#uploadBtn.dataset.prevText = this.#uploadBtn.textContent;
|
|
8145
|
+
this.#uploadBtn.textContent = "Drop file";
|
|
8146
|
+
}
|
|
8147
|
+
}
|
|
8340
8148
|
};
|
|
8341
8149
|
|
|
8342
8150
|
#onDragLeave = () => {
|
|
8343
8151
|
this.removeAttribute("dragover");
|
|
8152
|
+
if (this.#uploadBtn && this.#uploadBtn.dataset.prevText !== undefined) {
|
|
8153
|
+
this.#uploadBtn.textContent = this.#uploadBtn.dataset.prevText;
|
|
8154
|
+
delete this.#uploadBtn.dataset.prevText;
|
|
8155
|
+
}
|
|
8344
8156
|
};
|
|
8345
8157
|
|
|
8346
8158
|
#onDrop = (e) => {
|
|
8347
8159
|
e.preventDefault();
|
|
8348
8160
|
this.removeAttribute("dragover");
|
|
8161
|
+
if (this.#uploadBtn && this.#uploadBtn.dataset.prevText !== undefined) {
|
|
8162
|
+
this.#uploadBtn.textContent = this.#uploadBtn.dataset.prevText;
|
|
8163
|
+
delete this.#uploadBtn.dataset.prevText;
|
|
8164
|
+
}
|
|
8349
8165
|
if (
|
|
8350
8166
|
this.hasAttribute("disabled") &&
|
|
8351
8167
|
this.getAttribute("disabled") !== "false"
|
|
@@ -10568,563 +10384,23 @@ class FigInputJoystick extends HTMLElement {
|
|
|
10568
10384
|
|
|
10569
10385
|
customElements.define("fig-joystick", FigInputJoystick);
|
|
10570
10386
|
|
|
10571
|
-
/**
|
|
10572
|
-
* A custom angle chooser input element.
|
|
10573
|
-
* @attr {number} value - The current angle of the handle in degrees.
|
|
10574
|
-
* @attr {number} precision - The number of decimal places for the output.
|
|
10575
|
-
* @attr {boolean} text - Whether to display a text input for the angle value.
|
|
10576
|
-
* @attr {boolean} dial - Whether to display the circular dial control. Defaults to true.
|
|
10577
|
-
* @attr {number} adjacent - The adjacent value of the angle.
|
|
10578
|
-
* @attr {number} opposite - The opposite value of the angle.
|
|
10579
|
-
* @attr {boolean} rotations - Whether to display a rotation count (×N) when rotations > 1. Defaults to false.
|
|
10580
|
-
*/
|
|
10581
|
-
class FigInputAngle extends HTMLElement {
|
|
10582
|
-
// Private fields
|
|
10583
|
-
#adjacent;
|
|
10584
|
-
#opposite;
|
|
10585
|
-
#prevRawAngle = null;
|
|
10586
|
-
#boundHandleRawChange;
|
|
10587
|
-
#boundHandleMouseDown;
|
|
10588
|
-
#boundHandleTouchStart;
|
|
10589
|
-
#boundHandleKeyDown;
|
|
10590
|
-
#boundHandleKeyUp;
|
|
10591
|
-
#boundHandleAngleInput;
|
|
10592
|
-
|
|
10593
|
-
constructor() {
|
|
10594
|
-
super();
|
|
10595
|
-
|
|
10596
|
-
this.angle = 0;
|
|
10597
|
-
this.#adjacent = 1;
|
|
10598
|
-
this.#opposite = 0;
|
|
10599
|
-
this.isDragging = false;
|
|
10600
|
-
this.isShiftHeld = false;
|
|
10601
|
-
this.handle = null;
|
|
10602
|
-
this.angleInput = null;
|
|
10603
|
-
this.plane = null;
|
|
10604
|
-
this.units = "°";
|
|
10605
|
-
this.min = null;
|
|
10606
|
-
this.max = null;
|
|
10607
|
-
this.dial = true;
|
|
10608
|
-
this.showRotations = false;
|
|
10609
|
-
this.rotationSpan = null;
|
|
10610
|
-
|
|
10611
|
-
this.#boundHandleRawChange = this.#handleRawChange.bind(this);
|
|
10612
|
-
this.#boundHandleMouseDown = this.#handleMouseDown.bind(this);
|
|
10613
|
-
this.#boundHandleTouchStart = this.#handleTouchStart.bind(this);
|
|
10614
|
-
this.#boundHandleKeyDown = this.#handleKeyDown.bind(this);
|
|
10615
|
-
this.#boundHandleKeyUp = this.#handleKeyUp.bind(this);
|
|
10616
|
-
this.#boundHandleAngleInput = this.#handleAngleInput.bind(this);
|
|
10617
|
-
}
|
|
10618
10387
|
|
|
10388
|
+
// FigInputAngle moved to fig-lab.js
|
|
10389
|
+
// FigShimmer
|
|
10390
|
+
class FigShimmer extends HTMLElement {
|
|
10619
10391
|
connectedCallback() {
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
this.
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
let rawUnits = this.getAttribute("units") || "°";
|
|
10626
|
-
if (rawUnits === "deg") rawUnits = "°";
|
|
10627
|
-
this.units = rawUnits;
|
|
10628
|
-
|
|
10629
|
-
this.min = this.hasAttribute("min")
|
|
10630
|
-
? Number(this.getAttribute("min"))
|
|
10631
|
-
: null;
|
|
10632
|
-
this.max = this.hasAttribute("max")
|
|
10633
|
-
? Number(this.getAttribute("max"))
|
|
10634
|
-
: null;
|
|
10635
|
-
this.dial = this.#readBooleanAttribute("dial", true);
|
|
10636
|
-
this.showRotations = this.#readRotationsEnabled();
|
|
10637
|
-
|
|
10638
|
-
this.#render();
|
|
10639
|
-
this.#setupListeners();
|
|
10640
|
-
|
|
10641
|
-
this.#syncHandlePosition();
|
|
10642
|
-
if (this.text && this.angleInput) {
|
|
10643
|
-
this.angleInput.setAttribute(
|
|
10644
|
-
"value",
|
|
10645
|
-
this.angle.toFixed(this.precision),
|
|
10646
|
-
);
|
|
10647
|
-
}
|
|
10648
|
-
});
|
|
10649
|
-
}
|
|
10650
|
-
|
|
10651
|
-
disconnectedCallback() {
|
|
10652
|
-
this.#cleanupListeners();
|
|
10392
|
+
const duration = this.getAttribute("duration");
|
|
10393
|
+
if (duration) {
|
|
10394
|
+
this.style.setProperty("--shimmer-duration", duration);
|
|
10395
|
+
}
|
|
10653
10396
|
}
|
|
10654
10397
|
|
|
10655
|
-
|
|
10656
|
-
|
|
10398
|
+
static get observedAttributes() {
|
|
10399
|
+
return ["duration", "playing"];
|
|
10657
10400
|
}
|
|
10658
10401
|
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
if (value === null) return defaultValue;
|
|
10662
|
-
const normalized = value.trim().toLowerCase();
|
|
10663
|
-
if (normalized === "" || normalized === "true") return true;
|
|
10664
|
-
if (normalized === "false") return false;
|
|
10665
|
-
return true;
|
|
10666
|
-
}
|
|
10667
|
-
|
|
10668
|
-
#readRotationsEnabled() {
|
|
10669
|
-
if (this.hasAttribute("rotations")) {
|
|
10670
|
-
return this.#readBooleanAttribute("rotations", false);
|
|
10671
|
-
}
|
|
10672
|
-
// Backward-compat alias
|
|
10673
|
-
if (this.hasAttribute("show-rotations")) {
|
|
10674
|
-
return this.#readBooleanAttribute("show-rotations", false);
|
|
10675
|
-
}
|
|
10676
|
-
return false;
|
|
10677
|
-
}
|
|
10678
|
-
|
|
10679
|
-
#getInnerHTML() {
|
|
10680
|
-
const step = this.#getStepForUnit();
|
|
10681
|
-
const minAttr = this.min !== null ? `min="${this.min}"` : "";
|
|
10682
|
-
const maxAttr = this.max !== null ? `max="${this.max}"` : "";
|
|
10683
|
-
return `
|
|
10684
|
-
${
|
|
10685
|
-
this.dial
|
|
10686
|
-
? `<div class="fig-input-angle-plane" tabindex="0">
|
|
10687
|
-
<div class="fig-input-angle-handle"></div>
|
|
10688
|
-
</div>`
|
|
10689
|
-
: ""
|
|
10690
|
-
}
|
|
10691
|
-
${
|
|
10692
|
-
this.text
|
|
10693
|
-
? `<fig-input-number
|
|
10694
|
-
name="angle"
|
|
10695
|
-
step="${step}"
|
|
10696
|
-
value="${this.angle}"
|
|
10697
|
-
${minAttr}
|
|
10698
|
-
${maxAttr}
|
|
10699
|
-
units="${this.units}">
|
|
10700
|
-
${this.showRotations ? `<span slot="append" class="fig-input-angle-rotations"></span>` : ""}
|
|
10701
|
-
</fig-input-number>`
|
|
10702
|
-
: ""
|
|
10703
|
-
}
|
|
10704
|
-
`;
|
|
10705
|
-
}
|
|
10706
|
-
|
|
10707
|
-
#getRotationCount() {
|
|
10708
|
-
const degrees = Math.abs(this.#toDegrees(this.angle));
|
|
10709
|
-
return Math.floor(degrees / 360);
|
|
10710
|
-
}
|
|
10711
|
-
|
|
10712
|
-
#updateRotationDisplay() {
|
|
10713
|
-
if (!this.rotationSpan) return;
|
|
10714
|
-
const rotations = this.#getRotationCount();
|
|
10715
|
-
if (rotations > 1) {
|
|
10716
|
-
this.rotationSpan.textContent = `\u00d7${rotations}`;
|
|
10717
|
-
this.rotationSpan.style.display = "";
|
|
10718
|
-
} else {
|
|
10719
|
-
this.rotationSpan.textContent = "";
|
|
10720
|
-
this.rotationSpan.style.display = "none";
|
|
10721
|
-
}
|
|
10722
|
-
}
|
|
10723
|
-
|
|
10724
|
-
#getStepForUnit() {
|
|
10725
|
-
switch (this.units) {
|
|
10726
|
-
case "rad":
|
|
10727
|
-
return 0.01;
|
|
10728
|
-
case "turn":
|
|
10729
|
-
return 0.001;
|
|
10730
|
-
default:
|
|
10731
|
-
return 0.1;
|
|
10732
|
-
}
|
|
10733
|
-
}
|
|
10734
|
-
|
|
10735
|
-
// --- Unit conversion helpers ---
|
|
10736
|
-
|
|
10737
|
-
#toDegrees(value) {
|
|
10738
|
-
switch (this.units) {
|
|
10739
|
-
case "rad":
|
|
10740
|
-
return (value * 180) / Math.PI;
|
|
10741
|
-
case "turn":
|
|
10742
|
-
return value * 360;
|
|
10743
|
-
default:
|
|
10744
|
-
return value;
|
|
10745
|
-
}
|
|
10746
|
-
}
|
|
10747
|
-
|
|
10748
|
-
#fromDegrees(degrees) {
|
|
10749
|
-
switch (this.units) {
|
|
10750
|
-
case "rad":
|
|
10751
|
-
return (degrees * Math.PI) / 180;
|
|
10752
|
-
case "turn":
|
|
10753
|
-
return degrees / 360;
|
|
10754
|
-
default:
|
|
10755
|
-
return degrees;
|
|
10756
|
-
}
|
|
10757
|
-
}
|
|
10758
|
-
|
|
10759
|
-
#convertAngle(value, fromUnit, toUnit) {
|
|
10760
|
-
// Convert to degrees first
|
|
10761
|
-
let degrees;
|
|
10762
|
-
switch (fromUnit) {
|
|
10763
|
-
case "rad":
|
|
10764
|
-
degrees = (value * 180) / Math.PI;
|
|
10765
|
-
break;
|
|
10766
|
-
case "turn":
|
|
10767
|
-
degrees = value * 360;
|
|
10768
|
-
break;
|
|
10769
|
-
default:
|
|
10770
|
-
degrees = value;
|
|
10771
|
-
}
|
|
10772
|
-
// Convert from degrees to target
|
|
10773
|
-
switch (toUnit) {
|
|
10774
|
-
case "rad":
|
|
10775
|
-
return (degrees * Math.PI) / 180;
|
|
10776
|
-
case "turn":
|
|
10777
|
-
return degrees / 360;
|
|
10778
|
-
default:
|
|
10779
|
-
return degrees;
|
|
10780
|
-
}
|
|
10781
|
-
}
|
|
10782
|
-
|
|
10783
|
-
// --- Event listeners ---
|
|
10784
|
-
|
|
10785
|
-
#setupListeners() {
|
|
10786
|
-
this.handle = this.querySelector(".fig-input-angle-handle");
|
|
10787
|
-
this.plane = this.querySelector(".fig-input-angle-plane");
|
|
10788
|
-
this.angleInput = this.querySelector("fig-input-number[name='angle']");
|
|
10789
|
-
this.rotationSpan = this.querySelector(".fig-input-angle-rotations");
|
|
10790
|
-
this.#updateRotationDisplay();
|
|
10791
|
-
this.plane?.addEventListener("mousedown", this.#boundHandleMouseDown);
|
|
10792
|
-
this.plane?.addEventListener("touchstart", this.#boundHandleTouchStart);
|
|
10793
|
-
window.addEventListener("keydown", this.#boundHandleKeyDown);
|
|
10794
|
-
window.addEventListener("keyup", this.#boundHandleKeyUp);
|
|
10795
|
-
if (this.text && this.angleInput) {
|
|
10796
|
-
this.angleInput.addEventListener("input", this.#boundHandleAngleInput);
|
|
10797
|
-
}
|
|
10798
|
-
this.addEventListener("change", this.#boundHandleRawChange, true);
|
|
10799
|
-
}
|
|
10800
|
-
|
|
10801
|
-
#cleanupListeners() {
|
|
10802
|
-
this.plane?.removeEventListener("mousedown", this.#boundHandleMouseDown);
|
|
10803
|
-
this.plane?.removeEventListener("touchstart", this.#boundHandleTouchStart);
|
|
10804
|
-
window.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
10805
|
-
window.removeEventListener("keyup", this.#boundHandleKeyUp);
|
|
10806
|
-
if (this.text && this.angleInput) {
|
|
10807
|
-
this.angleInput.removeEventListener("input", this.#boundHandleAngleInput);
|
|
10808
|
-
}
|
|
10809
|
-
this.removeEventListener("change", this.#boundHandleRawChange, true);
|
|
10810
|
-
}
|
|
10811
|
-
|
|
10812
|
-
#handleRawChange(e) {
|
|
10813
|
-
// Only intercept native change events from the raw <input> element
|
|
10814
|
-
if (!e.target?.matches?.("input")) return;
|
|
10815
|
-
const raw = e.target.value;
|
|
10816
|
-
const match = raw.match(/^(-?\d*\.?\d+)\s*(turn|rad|deg|°)$/i);
|
|
10817
|
-
if (match) {
|
|
10818
|
-
const num = parseFloat(match[1]);
|
|
10819
|
-
let fromUnit = match[2].toLowerCase();
|
|
10820
|
-
if (fromUnit === "deg") fromUnit = "°";
|
|
10821
|
-
if (fromUnit !== this.units) {
|
|
10822
|
-
const converted = this.#convertAngle(num, fromUnit, this.units);
|
|
10823
|
-
e.target.value = String(converted);
|
|
10824
|
-
}
|
|
10825
|
-
}
|
|
10826
|
-
}
|
|
10827
|
-
|
|
10828
|
-
#handleAngleInput(e) {
|
|
10829
|
-
e.stopPropagation();
|
|
10830
|
-
this.angle = Number(e.target.value);
|
|
10831
|
-
this.#calculateAdjacentAndOpposite();
|
|
10832
|
-
this.#syncHandlePosition();
|
|
10833
|
-
this.#updateRotationDisplay();
|
|
10834
|
-
this.#emitInputEvent();
|
|
10835
|
-
this.#emitChangeEvent();
|
|
10836
|
-
}
|
|
10837
|
-
|
|
10838
|
-
// --- Angle calculation ---
|
|
10839
|
-
|
|
10840
|
-
#calculateAdjacentAndOpposite() {
|
|
10841
|
-
const degrees = this.#toDegrees(this.angle);
|
|
10842
|
-
const radians = (degrees * Math.PI) / 180;
|
|
10843
|
-
this.#adjacent = Math.cos(radians);
|
|
10844
|
-
this.#opposite = Math.sin(radians);
|
|
10845
|
-
}
|
|
10846
|
-
|
|
10847
|
-
#snapToIncrement(angle) {
|
|
10848
|
-
if (!this.isShiftHeld) return angle;
|
|
10849
|
-
const increment = 45;
|
|
10850
|
-
return Math.round(angle / increment) * increment;
|
|
10851
|
-
}
|
|
10852
|
-
|
|
10853
|
-
#getRawAngle(e) {
|
|
10854
|
-
const rect = this.plane.getBoundingClientRect();
|
|
10855
|
-
const centerX = rect.left + rect.width / 2;
|
|
10856
|
-
const centerY = rect.top + rect.height / 2;
|
|
10857
|
-
const deltaX = e.clientX - centerX;
|
|
10858
|
-
const deltaY = e.clientY - centerY;
|
|
10859
|
-
return (Math.atan2(deltaY, deltaX) * 180) / Math.PI;
|
|
10860
|
-
}
|
|
10861
|
-
|
|
10862
|
-
#updateAngle(e) {
|
|
10863
|
-
let rawAngle = this.#getRawAngle(e);
|
|
10864
|
-
// Normalize to 0-360 for snap and positioning
|
|
10865
|
-
let normalizedAngle = ((rawAngle % 360) + 360) % 360;
|
|
10866
|
-
normalizedAngle = this.#snapToIncrement(normalizedAngle);
|
|
10867
|
-
|
|
10868
|
-
const isBounded = this.min !== null || this.max !== null;
|
|
10869
|
-
|
|
10870
|
-
if (isBounded) {
|
|
10871
|
-
// Bounded: absolute position
|
|
10872
|
-
this.angle = this.#fromDegrees(normalizedAngle);
|
|
10873
|
-
} else {
|
|
10874
|
-
// Unbounded: cumulative winding
|
|
10875
|
-
if (this.#prevRawAngle === null) {
|
|
10876
|
-
// First event of this drag — snap to clicked position, preserving revolution
|
|
10877
|
-
this.#prevRawAngle = normalizedAngle;
|
|
10878
|
-
const currentDeg = this.#toDegrees(this.angle);
|
|
10879
|
-
const currentMod = ((currentDeg % 360) + 360) % 360;
|
|
10880
|
-
let delta = normalizedAngle - currentMod;
|
|
10881
|
-
if (delta > 180) delta -= 360;
|
|
10882
|
-
if (delta < -180) delta += 360;
|
|
10883
|
-
this.angle += this.#fromDegrees(delta);
|
|
10884
|
-
} else {
|
|
10885
|
-
// Subsequent events — accumulate delta
|
|
10886
|
-
let delta = normalizedAngle - this.#prevRawAngle;
|
|
10887
|
-
if (delta > 180) delta -= 360;
|
|
10888
|
-
if (delta < -180) delta += 360;
|
|
10889
|
-
this.angle += this.#fromDegrees(delta);
|
|
10890
|
-
this.#prevRawAngle = normalizedAngle;
|
|
10891
|
-
}
|
|
10892
|
-
}
|
|
10893
|
-
|
|
10894
|
-
this.#calculateAdjacentAndOpposite();
|
|
10895
|
-
|
|
10896
|
-
this.#syncHandlePosition();
|
|
10897
|
-
if (this.text && this.angleInput) {
|
|
10898
|
-
this.angleInput.setAttribute("value", this.angle.toFixed(this.precision));
|
|
10899
|
-
}
|
|
10900
|
-
this.#updateRotationDisplay();
|
|
10901
|
-
|
|
10902
|
-
this.#emitInputEvent();
|
|
10903
|
-
}
|
|
10904
|
-
|
|
10905
|
-
// --- Event dispatching ---
|
|
10906
|
-
|
|
10907
|
-
#emitInputEvent() {
|
|
10908
|
-
this.dispatchEvent(
|
|
10909
|
-
new CustomEvent("input", {
|
|
10910
|
-
bubbles: true,
|
|
10911
|
-
cancelable: true,
|
|
10912
|
-
detail: { value: this.value, angle: this.angle },
|
|
10913
|
-
}),
|
|
10914
|
-
);
|
|
10915
|
-
}
|
|
10916
|
-
|
|
10917
|
-
#emitChangeEvent() {
|
|
10918
|
-
this.dispatchEvent(
|
|
10919
|
-
new CustomEvent("change", {
|
|
10920
|
-
bubbles: true,
|
|
10921
|
-
cancelable: true,
|
|
10922
|
-
detail: { value: this.value, angle: this.angle },
|
|
10923
|
-
}),
|
|
10924
|
-
);
|
|
10925
|
-
}
|
|
10926
|
-
|
|
10927
|
-
// --- Handle position ---
|
|
10928
|
-
|
|
10929
|
-
#syncHandlePosition() {
|
|
10930
|
-
if (this.handle) {
|
|
10931
|
-
const degrees = this.#toDegrees(this.angle);
|
|
10932
|
-
const radians = (degrees * Math.PI) / 180;
|
|
10933
|
-
const radius = this.plane.offsetWidth / 2 - this.handle.offsetWidth / 2;
|
|
10934
|
-
const x = Math.cos(radians) * radius;
|
|
10935
|
-
const y = Math.sin(radians) * radius;
|
|
10936
|
-
this.handle.style.transform = `translate(${x}px, ${y}px)`;
|
|
10937
|
-
}
|
|
10938
|
-
}
|
|
10939
|
-
|
|
10940
|
-
// --- Mouse/Touch handlers ---
|
|
10941
|
-
|
|
10942
|
-
#handleMouseDown(e) {
|
|
10943
|
-
this.isDragging = true;
|
|
10944
|
-
this.#prevRawAngle = null;
|
|
10945
|
-
this.#updateAngle(e);
|
|
10946
|
-
|
|
10947
|
-
const handleMouseMove = (e) => {
|
|
10948
|
-
this.plane.classList.add("dragging");
|
|
10949
|
-
if (this.isDragging) this.#updateAngle(e);
|
|
10950
|
-
};
|
|
10951
|
-
|
|
10952
|
-
const handleMouseUp = () => {
|
|
10953
|
-
this.isDragging = false;
|
|
10954
|
-
this.#prevRawAngle = null;
|
|
10955
|
-
this.plane.classList.remove("dragging");
|
|
10956
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
10957
|
-
window.removeEventListener("mouseup", handleMouseUp);
|
|
10958
|
-
this.#emitChangeEvent();
|
|
10959
|
-
};
|
|
10960
|
-
|
|
10961
|
-
window.addEventListener("mousemove", handleMouseMove);
|
|
10962
|
-
window.addEventListener("mouseup", handleMouseUp);
|
|
10963
|
-
}
|
|
10964
|
-
|
|
10965
|
-
#handleTouchStart(e) {
|
|
10966
|
-
e.preventDefault();
|
|
10967
|
-
this.isDragging = true;
|
|
10968
|
-
this.#prevRawAngle = null;
|
|
10969
|
-
this.#updateAngle(e.touches[0]);
|
|
10970
|
-
|
|
10971
|
-
const handleTouchMove = (e) => {
|
|
10972
|
-
this.plane.classList.add("dragging");
|
|
10973
|
-
if (this.isDragging) this.#updateAngle(e.touches[0]);
|
|
10974
|
-
};
|
|
10975
|
-
|
|
10976
|
-
const handleTouchEnd = () => {
|
|
10977
|
-
this.isDragging = false;
|
|
10978
|
-
this.#prevRawAngle = null;
|
|
10979
|
-
this.plane.classList.remove("dragging");
|
|
10980
|
-
window.removeEventListener("touchmove", handleTouchMove);
|
|
10981
|
-
window.removeEventListener("touchend", handleTouchEnd);
|
|
10982
|
-
this.#emitChangeEvent();
|
|
10983
|
-
};
|
|
10984
|
-
|
|
10985
|
-
window.addEventListener("touchmove", handleTouchMove);
|
|
10986
|
-
window.addEventListener("touchend", handleTouchEnd);
|
|
10987
|
-
}
|
|
10988
|
-
|
|
10989
|
-
// --- Keyboard handlers ---
|
|
10990
|
-
|
|
10991
|
-
#handleKeyDown(e) {
|
|
10992
|
-
if (e.key === "Shift") this.isShiftHeld = true;
|
|
10993
|
-
}
|
|
10994
|
-
|
|
10995
|
-
#handleKeyUp(e) {
|
|
10996
|
-
if (e.key === "Shift") this.isShiftHeld = false;
|
|
10997
|
-
}
|
|
10998
|
-
|
|
10999
|
-
focus() {
|
|
11000
|
-
this.plane?.focus();
|
|
11001
|
-
}
|
|
11002
|
-
|
|
11003
|
-
// --- Attributes ---
|
|
11004
|
-
|
|
11005
|
-
static get observedAttributes() {
|
|
11006
|
-
return [
|
|
11007
|
-
"value",
|
|
11008
|
-
"precision",
|
|
11009
|
-
"text",
|
|
11010
|
-
"min",
|
|
11011
|
-
"max",
|
|
11012
|
-
"units",
|
|
11013
|
-
"dial",
|
|
11014
|
-
"rotations",
|
|
11015
|
-
"show-rotations",
|
|
11016
|
-
];
|
|
11017
|
-
}
|
|
11018
|
-
|
|
11019
|
-
get value() {
|
|
11020
|
-
return this.angle;
|
|
11021
|
-
}
|
|
11022
|
-
|
|
11023
|
-
get adjacent() {
|
|
11024
|
-
return this.#adjacent;
|
|
11025
|
-
}
|
|
11026
|
-
|
|
11027
|
-
get opposite() {
|
|
11028
|
-
return this.#opposite;
|
|
11029
|
-
}
|
|
11030
|
-
|
|
11031
|
-
set value(value) {
|
|
11032
|
-
if (isNaN(value)) {
|
|
11033
|
-
console.error("Invalid value: must be a number.");
|
|
11034
|
-
return;
|
|
11035
|
-
}
|
|
11036
|
-
this.angle = value;
|
|
11037
|
-
this.#calculateAdjacentAndOpposite();
|
|
11038
|
-
this.#syncHandlePosition();
|
|
11039
|
-
if (this.angleInput) {
|
|
11040
|
-
this.angleInput.setAttribute("value", this.angle.toFixed(this.precision));
|
|
11041
|
-
}
|
|
11042
|
-
this.#updateRotationDisplay();
|
|
11043
|
-
}
|
|
11044
|
-
|
|
11045
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
11046
|
-
switch (name) {
|
|
11047
|
-
case "value":
|
|
11048
|
-
if (this.isDragging) break;
|
|
11049
|
-
this.value = Number(newValue);
|
|
11050
|
-
break;
|
|
11051
|
-
case "precision":
|
|
11052
|
-
this.precision = parseInt(newValue);
|
|
11053
|
-
break;
|
|
11054
|
-
case "text":
|
|
11055
|
-
if (newValue !== oldValue) {
|
|
11056
|
-
this.text = newValue?.toLowerCase() === "true";
|
|
11057
|
-
if (this.isConnected) {
|
|
11058
|
-
this.#render();
|
|
11059
|
-
this.#setupListeners();
|
|
11060
|
-
this.#syncHandlePosition();
|
|
11061
|
-
}
|
|
11062
|
-
}
|
|
11063
|
-
break;
|
|
11064
|
-
case "dial":
|
|
11065
|
-
this.dial = this.#readBooleanAttribute("dial", true);
|
|
11066
|
-
if (this.isConnected) {
|
|
11067
|
-
this.#render();
|
|
11068
|
-
this.#setupListeners();
|
|
11069
|
-
this.#syncHandlePosition();
|
|
11070
|
-
}
|
|
11071
|
-
break;
|
|
11072
|
-
case "units": {
|
|
11073
|
-
let units = newValue || "°";
|
|
11074
|
-
if (units === "deg") units = "°";
|
|
11075
|
-
this.units = units;
|
|
11076
|
-
if (this.isConnected) {
|
|
11077
|
-
this.#render();
|
|
11078
|
-
this.#setupListeners();
|
|
11079
|
-
this.#syncHandlePosition();
|
|
11080
|
-
}
|
|
11081
|
-
break;
|
|
11082
|
-
}
|
|
11083
|
-
case "min":
|
|
11084
|
-
this.min = newValue !== null ? Number(newValue) : null;
|
|
11085
|
-
if (this.isConnected) {
|
|
11086
|
-
this.#render();
|
|
11087
|
-
this.#setupListeners();
|
|
11088
|
-
this.#syncHandlePosition();
|
|
11089
|
-
}
|
|
11090
|
-
break;
|
|
11091
|
-
case "max":
|
|
11092
|
-
this.max = newValue !== null ? Number(newValue) : null;
|
|
11093
|
-
if (this.isConnected) {
|
|
11094
|
-
this.#render();
|
|
11095
|
-
this.#setupListeners();
|
|
11096
|
-
this.#syncHandlePosition();
|
|
11097
|
-
}
|
|
11098
|
-
break;
|
|
11099
|
-
case "rotations":
|
|
11100
|
-
case "show-rotations":
|
|
11101
|
-
this.showRotations = this.#readRotationsEnabled();
|
|
11102
|
-
if (this.isConnected) {
|
|
11103
|
-
this.#render();
|
|
11104
|
-
this.#setupListeners();
|
|
11105
|
-
this.#syncHandlePosition();
|
|
11106
|
-
}
|
|
11107
|
-
break;
|
|
11108
|
-
}
|
|
11109
|
-
}
|
|
11110
|
-
}
|
|
11111
|
-
customElements.define("fig-input-angle", FigInputAngle);
|
|
11112
|
-
|
|
11113
|
-
// FigShimmer
|
|
11114
|
-
class FigShimmer extends HTMLElement {
|
|
11115
|
-
connectedCallback() {
|
|
11116
|
-
const duration = this.getAttribute("duration");
|
|
11117
|
-
if (duration) {
|
|
11118
|
-
this.style.setProperty("--shimmer-duration", duration);
|
|
11119
|
-
}
|
|
11120
|
-
}
|
|
11121
|
-
|
|
11122
|
-
static get observedAttributes() {
|
|
11123
|
-
return ["duration", "playing"];
|
|
11124
|
-
}
|
|
11125
|
-
|
|
11126
|
-
get playing() {
|
|
11127
|
-
return this.getAttribute("playing") !== "false";
|
|
10402
|
+
get playing() {
|
|
10403
|
+
return this.getAttribute("playing") !== "false";
|
|
11128
10404
|
}
|
|
11129
10405
|
|
|
11130
10406
|
set playing(value) {
|
|
@@ -12421,9 +11697,9 @@ class FigFillPicker extends HTMLElement {
|
|
|
12421
11697
|
<option value="angular">Angular</option>
|
|
12422
11698
|
</fig-dropdown>
|
|
12423
11699
|
<fig-tooltip text="Rotate gradient">
|
|
12424
|
-
<fig-input-
|
|
11700
|
+
<fig-input-number class="fig-fill-picker-gradient-angle" value="${
|
|
12425
11701
|
(this.#gradient.angle - 90 + 360) % 360
|
|
12426
|
-
}"></fig-input-
|
|
11702
|
+
}" min="0" max="360" units="°" wrap></fig-input-number>
|
|
12427
11703
|
</fig-tooltip>
|
|
12428
11704
|
<div class="fig-fill-picker-gradient-center input-combo" style="display: none;">
|
|
12429
11705
|
<fig-input-number min="0" max="100" value="${
|
|
@@ -12520,7 +11796,6 @@ class FigFillPicker extends HTMLElement {
|
|
|
12520
11796
|
);
|
|
12521
11797
|
|
|
12522
11798
|
// Angle input
|
|
12523
|
-
// Convert from fig-input-angle coordinates (0° = right) to CSS coordinates (0° = up)
|
|
12524
11799
|
const angleInput = container.querySelector(
|
|
12525
11800
|
".fig-fill-picker-gradient-angle",
|
|
12526
11801
|
);
|
|
@@ -14401,930 +13676,6 @@ class FigChooser extends HTMLElement {
|
|
|
14401
13676
|
}
|
|
14402
13677
|
customElements.define("fig-chooser", FigChooser);
|
|
14403
13678
|
|
|
14404
|
-
/* Canvas Control */
|
|
14405
|
-
class FigCanvasControl extends HTMLElement {
|
|
14406
|
-
static observedAttributes = [
|
|
14407
|
-
"type",
|
|
14408
|
-
"value",
|
|
14409
|
-
"color",
|
|
14410
|
-
"name",
|
|
14411
|
-
"tooltips",
|
|
14412
|
-
"disabled",
|
|
14413
|
-
"drag-surface",
|
|
14414
|
-
"snapping",
|
|
14415
|
-
];
|
|
14416
|
-
|
|
14417
|
-
#x = 50;
|
|
14418
|
-
#y = 50;
|
|
14419
|
-
#x2 = 75;
|
|
14420
|
-
#y2 = 75;
|
|
14421
|
-
#radius = 0;
|
|
14422
|
-
#radiusIsPercent = false;
|
|
14423
|
-
#angle = 0;
|
|
14424
|
-
#pointHandle = null;
|
|
14425
|
-
#secondHandle = null;
|
|
14426
|
-
#angleHandle = null;
|
|
14427
|
-
#radiusSvg = null;
|
|
14428
|
-
#angleSvg = null;
|
|
14429
|
-
#pointTooltip = null;
|
|
14430
|
-
#secondTooltip = null;
|
|
14431
|
-
#radiusTooltip = null;
|
|
14432
|
-
#angleTooltip = null;
|
|
14433
|
-
#isDragging = false;
|
|
14434
|
-
#isSecondDragging = false;
|
|
14435
|
-
#isRadiusDragging = false;
|
|
14436
|
-
#isAngleDragging = false;
|
|
14437
|
-
#prevBodyCursor = "";
|
|
14438
|
-
|
|
14439
|
-
get #type() {
|
|
14440
|
-
return this.getAttribute("type") || "point";
|
|
14441
|
-
}
|
|
14442
|
-
|
|
14443
|
-
get #hasRadius() {
|
|
14444
|
-
return this.#type === "point-radius" || this.#type === "point-radius-angle";
|
|
14445
|
-
}
|
|
14446
|
-
|
|
14447
|
-
get #hasAngle() {
|
|
14448
|
-
return this.#type === "point-radius-angle";
|
|
14449
|
-
}
|
|
14450
|
-
|
|
14451
|
-
get #hasSecondPoint() {
|
|
14452
|
-
return this.#type === "point-point";
|
|
14453
|
-
}
|
|
14454
|
-
|
|
14455
|
-
get #hasLine() {
|
|
14456
|
-
return this.#type === "point-radius-angle" || this.#type === "point-point";
|
|
14457
|
-
}
|
|
14458
|
-
|
|
14459
|
-
get #tooltipsEnabled() {
|
|
14460
|
-
const v = this.getAttribute("tooltips");
|
|
14461
|
-
return v === null || v !== "false";
|
|
14462
|
-
}
|
|
14463
|
-
|
|
14464
|
-
get #snappingMode() {
|
|
14465
|
-
const raw = this.getAttribute("snapping");
|
|
14466
|
-
if (raw === null) return "false";
|
|
14467
|
-
const n = raw.trim().toLowerCase();
|
|
14468
|
-
if (n === "modifier") return "modifier";
|
|
14469
|
-
if (n === "" || n === "true") return "true";
|
|
14470
|
-
return "false";
|
|
14471
|
-
}
|
|
14472
|
-
|
|
14473
|
-
#shouldSnap(shiftKey) {
|
|
14474
|
-
const mode = this.#snappingMode;
|
|
14475
|
-
if (mode === "true") return true;
|
|
14476
|
-
if (mode === "modifier") return !!shiftKey;
|
|
14477
|
-
return false;
|
|
14478
|
-
}
|
|
14479
|
-
|
|
14480
|
-
get #pointTipText() {
|
|
14481
|
-
const name = this.getAttribute("name");
|
|
14482
|
-
if (name) {
|
|
14483
|
-
const parts = name.split(",");
|
|
14484
|
-
return parts[0].trim();
|
|
14485
|
-
}
|
|
14486
|
-
return `${Math.round(this.#x)}%, ${Math.round(this.#y)}%`;
|
|
14487
|
-
}
|
|
14488
|
-
|
|
14489
|
-
get #secondTipText() {
|
|
14490
|
-
const name = this.getAttribute("name");
|
|
14491
|
-
if (name) {
|
|
14492
|
-
const parts = name.split(",");
|
|
14493
|
-
if (parts.length > 1) return parts[1].trim();
|
|
14494
|
-
}
|
|
14495
|
-
return `${Math.round(this.#x2)}%, ${Math.round(this.#y2)}%`;
|
|
14496
|
-
}
|
|
14497
|
-
|
|
14498
|
-
get #dragSurface() {
|
|
14499
|
-
return this.getAttribute("drag-surface") || "parent";
|
|
14500
|
-
}
|
|
14501
|
-
|
|
14502
|
-
get #container() {
|
|
14503
|
-
const surface = this.#dragSurface;
|
|
14504
|
-
if (surface === "parent") return this.parentElement;
|
|
14505
|
-
return this.closest(surface);
|
|
14506
|
-
}
|
|
14507
|
-
|
|
14508
|
-
get #handleDragSurface() {
|
|
14509
|
-
const surface = this.#dragSurface;
|
|
14510
|
-
if (surface === "parent") {
|
|
14511
|
-
const container = this.parentElement;
|
|
14512
|
-
if (container) {
|
|
14513
|
-
container.setAttribute("data-fig-canvas-control-surface", "");
|
|
14514
|
-
return "[data-fig-canvas-control-surface]";
|
|
14515
|
-
}
|
|
14516
|
-
}
|
|
14517
|
-
return surface;
|
|
14518
|
-
}
|
|
14519
|
-
|
|
14520
|
-
#resolveRadius(containerWidth) {
|
|
14521
|
-
if (this.#radiusIsPercent) return (this.#radius / 100) * containerWidth;
|
|
14522
|
-
return this.#radius;
|
|
14523
|
-
}
|
|
14524
|
-
|
|
14525
|
-
#formatRadius() {
|
|
14526
|
-
if (this.#radiusIsPercent) return `Radius ${Math.round(this.#radius)}%`;
|
|
14527
|
-
return `Radius ${Math.round(this.#radius)}`;
|
|
14528
|
-
}
|
|
14529
|
-
|
|
14530
|
-
connectedCallback() {
|
|
14531
|
-
this.#parseValue();
|
|
14532
|
-
this.#render();
|
|
14533
|
-
}
|
|
14534
|
-
|
|
14535
|
-
disconnectedCallback() {
|
|
14536
|
-
this.#teardownRadiusDrag();
|
|
14537
|
-
}
|
|
14538
|
-
|
|
14539
|
-
attributeChangedCallback(name, oldVal, newVal) {
|
|
14540
|
-
if (oldVal === newVal) return;
|
|
14541
|
-
if (
|
|
14542
|
-
name === "value" &&
|
|
14543
|
-
!this.#isDragging &&
|
|
14544
|
-
!this.#isSecondDragging &&
|
|
14545
|
-
!this.#isRadiusDragging &&
|
|
14546
|
-
!this.#isAngleDragging
|
|
14547
|
-
) {
|
|
14548
|
-
this.#parseValue();
|
|
14549
|
-
if (this.#pointHandle) this.#syncPositions();
|
|
14550
|
-
else this.#render();
|
|
14551
|
-
}
|
|
14552
|
-
if (name === "type") {
|
|
14553
|
-
this.#parseValue();
|
|
14554
|
-
this.#render();
|
|
14555
|
-
}
|
|
14556
|
-
if (name === "color" && this.#pointHandle) {
|
|
14557
|
-
if (newVal) this.#pointHandle.setAttribute("color", newVal);
|
|
14558
|
-
else this.#pointHandle.removeAttribute("color");
|
|
14559
|
-
}
|
|
14560
|
-
if (name === "disabled") {
|
|
14561
|
-
this.#render();
|
|
14562
|
-
}
|
|
14563
|
-
if (name === "tooltips") {
|
|
14564
|
-
this.#render();
|
|
14565
|
-
}
|
|
14566
|
-
if (name === "snapping" && this.#pointHandle) {
|
|
14567
|
-
this.#pointHandle.setAttribute("drag-snapping", newVal || "false");
|
|
14568
|
-
if (this.#secondHandle)
|
|
14569
|
-
this.#secondHandle.setAttribute("drag-snapping", newVal || "false");
|
|
14570
|
-
}
|
|
14571
|
-
if (name === "name") {
|
|
14572
|
-
if (this.#pointTooltip)
|
|
14573
|
-
this.#pointTooltip.setAttribute("text", this.#pointTipText);
|
|
14574
|
-
if (this.#secondTooltip)
|
|
14575
|
-
this.#secondTooltip.setAttribute("text", this.#secondTipText);
|
|
14576
|
-
}
|
|
14577
|
-
}
|
|
14578
|
-
|
|
14579
|
-
#parseValue() {
|
|
14580
|
-
const raw = this.getAttribute("value");
|
|
14581
|
-
if (!raw) return;
|
|
14582
|
-
try {
|
|
14583
|
-
const v = JSON.parse(raw);
|
|
14584
|
-
if (typeof v.x === "number") this.#x = v.x;
|
|
14585
|
-
if (typeof v.y === "number") this.#y = v.y;
|
|
14586
|
-
if (v.radius !== undefined) {
|
|
14587
|
-
const rs = String(v.radius);
|
|
14588
|
-
if (rs.endsWith("%")) {
|
|
14589
|
-
this.#radiusIsPercent = true;
|
|
14590
|
-
this.#radius = parseFloat(rs);
|
|
14591
|
-
} else {
|
|
14592
|
-
this.#radiusIsPercent = false;
|
|
14593
|
-
this.#radius = parseFloat(rs);
|
|
14594
|
-
}
|
|
14595
|
-
if (!Number.isFinite(this.#radius)) this.#radius = 0;
|
|
14596
|
-
}
|
|
14597
|
-
if (typeof v.angle === "number") this.#angle = v.angle;
|
|
14598
|
-
if (typeof v.x2 === "number") this.#x2 = v.x2;
|
|
14599
|
-
if (typeof v.y2 === "number") this.#y2 = v.y2;
|
|
14600
|
-
} catch {
|
|
14601
|
-
/* ignore */
|
|
14602
|
-
}
|
|
14603
|
-
}
|
|
14604
|
-
|
|
14605
|
-
get value() {
|
|
14606
|
-
const v = { x: this.#x, y: this.#y };
|
|
14607
|
-
if (this.#type === "color") {
|
|
14608
|
-
const color =
|
|
14609
|
-
this.getAttribute("color") || this.#pointHandle?.getAttribute("color");
|
|
14610
|
-
if (color) v.color = color;
|
|
14611
|
-
}
|
|
14612
|
-
if (this.#hasRadius) {
|
|
14613
|
-
v.radius = this.#radiusIsPercent ? `${this.#radius}%` : this.#radius;
|
|
14614
|
-
}
|
|
14615
|
-
if (this.#hasAngle) v.angle = this.#angle;
|
|
14616
|
-
if (this.#hasSecondPoint) {
|
|
14617
|
-
v.x2 = this.#x2;
|
|
14618
|
-
v.y2 = this.#y2;
|
|
14619
|
-
}
|
|
14620
|
-
return v;
|
|
14621
|
-
}
|
|
14622
|
-
|
|
14623
|
-
set value(val) {
|
|
14624
|
-
if (typeof val === "object") {
|
|
14625
|
-
this.setAttribute("value", JSON.stringify(val));
|
|
14626
|
-
} else if (typeof val === "string") {
|
|
14627
|
-
this.setAttribute("value", val);
|
|
14628
|
-
}
|
|
14629
|
-
}
|
|
14630
|
-
|
|
14631
|
-
#render() {
|
|
14632
|
-
this.innerHTML = "";
|
|
14633
|
-
this.#pointHandle = null;
|
|
14634
|
-
this.#secondHandle = null;
|
|
14635
|
-
this.#angleHandle = null;
|
|
14636
|
-
this.#radiusSvg = null;
|
|
14637
|
-
this.#angleSvg = null;
|
|
14638
|
-
this.#pointTooltip = null;
|
|
14639
|
-
this.#secondTooltip = null;
|
|
14640
|
-
this.#radiusTooltip = null;
|
|
14641
|
-
this.#angleTooltip = null;
|
|
14642
|
-
|
|
14643
|
-
const disabled = this.hasAttribute("disabled");
|
|
14644
|
-
const type = this.#type;
|
|
14645
|
-
const tooltips = this.#tooltipsEnabled;
|
|
14646
|
-
|
|
14647
|
-
const handleSurface = this.#handleDragSurface;
|
|
14648
|
-
|
|
14649
|
-
const handle = document.createElement("fig-handle");
|
|
14650
|
-
handle.setAttribute("drag", "true");
|
|
14651
|
-
handle.setAttribute("drag-surface", handleSurface);
|
|
14652
|
-
handle.setAttribute("drag-axes", "x,y");
|
|
14653
|
-
handle.setAttribute("drag-snapping", this.#snappingMode);
|
|
14654
|
-
handle.setAttribute("value", `${this.#x}% ${this.#y}%`);
|
|
14655
|
-
if (disabled) handle.setAttribute("disabled", "");
|
|
14656
|
-
if (type === "color") {
|
|
14657
|
-
handle.setAttribute("type", "color");
|
|
14658
|
-
const color = this.getAttribute("color");
|
|
14659
|
-
if (color) handle.setAttribute("color", color);
|
|
14660
|
-
}
|
|
14661
|
-
if (this.#hasSecondPoint) {
|
|
14662
|
-
handle.setAttribute("hit-area", "12 circle");
|
|
14663
|
-
handle.setAttribute("hit-area-mode", "delegate");
|
|
14664
|
-
}
|
|
14665
|
-
this.#pointHandle = handle;
|
|
14666
|
-
|
|
14667
|
-
if (this.#hasRadius) {
|
|
14668
|
-
this.#createRadiusSvg();
|
|
14669
|
-
}
|
|
14670
|
-
|
|
14671
|
-
if (this.#hasLine) {
|
|
14672
|
-
this.#createAngleSvg();
|
|
14673
|
-
}
|
|
14674
|
-
|
|
14675
|
-
if (tooltips) {
|
|
14676
|
-
const tip = document.createElement("fig-tooltip");
|
|
14677
|
-
tip.setAttribute("action", "manual");
|
|
14678
|
-
tip.setAttribute("theme", "brand");
|
|
14679
|
-
tip.setAttribute("pointer", "false");
|
|
14680
|
-
tip.setAttribute("text", this.#pointTipText);
|
|
14681
|
-
tip.appendChild(handle);
|
|
14682
|
-
this.appendChild(tip);
|
|
14683
|
-
this.#pointTooltip = tip;
|
|
14684
|
-
} else {
|
|
14685
|
-
this.appendChild(handle);
|
|
14686
|
-
}
|
|
14687
|
-
|
|
14688
|
-
if (this.#hasAngle) {
|
|
14689
|
-
this.#createAngleHandle(disabled, tooltips, handleSurface);
|
|
14690
|
-
}
|
|
14691
|
-
|
|
14692
|
-
if (this.#hasSecondPoint) {
|
|
14693
|
-
this.#createSecondHandle(disabled, tooltips, handleSurface);
|
|
14694
|
-
}
|
|
14695
|
-
|
|
14696
|
-
this.#setupEventListeners();
|
|
14697
|
-
requestAnimationFrame(() => this.#syncPositions());
|
|
14698
|
-
}
|
|
14699
|
-
|
|
14700
|
-
#createRadiusSvg() {
|
|
14701
|
-
const ns = "http://www.w3.org/2000/svg";
|
|
14702
|
-
const svg = document.createElementNS(ns, "svg");
|
|
14703
|
-
svg.classList.add("fig-canvas-control-radius");
|
|
14704
|
-
svg.setAttribute("overflow", "visible");
|
|
14705
|
-
const hitCircle = document.createElementNS(ns, "circle");
|
|
14706
|
-
hitCircle.classList.add("fig-canvas-control-radius-hit");
|
|
14707
|
-
svg.appendChild(hitCircle);
|
|
14708
|
-
const circle = document.createElementNS(ns, "circle");
|
|
14709
|
-
svg.appendChild(circle);
|
|
14710
|
-
this.#radiusSvg = svg;
|
|
14711
|
-
|
|
14712
|
-
if (this.#tooltipsEnabled) {
|
|
14713
|
-
const tip = document.createElement("fig-tooltip");
|
|
14714
|
-
tip.setAttribute("action", "manual");
|
|
14715
|
-
tip.setAttribute("theme", "brand");
|
|
14716
|
-
tip.setAttribute("pointer", "false");
|
|
14717
|
-
tip.setAttribute("text", this.#formatRadius());
|
|
14718
|
-
tip.appendChild(svg);
|
|
14719
|
-
this.appendChild(tip);
|
|
14720
|
-
this.#radiusTooltip = tip;
|
|
14721
|
-
} else {
|
|
14722
|
-
this.appendChild(svg);
|
|
14723
|
-
}
|
|
14724
|
-
|
|
14725
|
-
this.#setupRadiusDrag(hitCircle);
|
|
14726
|
-
}
|
|
14727
|
-
|
|
14728
|
-
#createAngleSvg() {
|
|
14729
|
-
const ns = "http://www.w3.org/2000/svg";
|
|
14730
|
-
const svg = document.createElementNS(ns, "svg");
|
|
14731
|
-
svg.classList.add("fig-canvas-control-angle-svg");
|
|
14732
|
-
svg.setAttribute("overflow", "visible");
|
|
14733
|
-
svg.style.position = "absolute";
|
|
14734
|
-
svg.style.pointerEvents = "none";
|
|
14735
|
-
const line = document.createElementNS(ns, "line");
|
|
14736
|
-
line.classList.add("fig-canvas-control-angle-line");
|
|
14737
|
-
svg.appendChild(line);
|
|
14738
|
-
this.#angleSvg = svg;
|
|
14739
|
-
this.appendChild(svg);
|
|
14740
|
-
}
|
|
14741
|
-
|
|
14742
|
-
#createAngleHandle(disabled, tooltips, handleSurface) {
|
|
14743
|
-
const handle = document.createElement("fig-handle");
|
|
14744
|
-
handle.setAttribute("drag", "true");
|
|
14745
|
-
handle.setAttribute("drag-surface", handleSurface);
|
|
14746
|
-
handle.setAttribute("drag-axes", "x,y");
|
|
14747
|
-
handle.setAttribute("size", "small");
|
|
14748
|
-
handle.setAttribute("hit-area", "12 circle");
|
|
14749
|
-
handle.setAttribute("hit-area-mode", "delegate");
|
|
14750
|
-
if (disabled) handle.setAttribute("disabled", "");
|
|
14751
|
-
this.#angleHandle = handle;
|
|
14752
|
-
|
|
14753
|
-
if (tooltips) {
|
|
14754
|
-
const tip = document.createElement("fig-tooltip");
|
|
14755
|
-
tip.setAttribute("action", "manual");
|
|
14756
|
-
tip.setAttribute("theme", "brand");
|
|
14757
|
-
tip.setAttribute("pointer", "false");
|
|
14758
|
-
tip.setAttribute("text", `${Math.round(this.#angle)}°`);
|
|
14759
|
-
tip.appendChild(handle);
|
|
14760
|
-
this.appendChild(tip);
|
|
14761
|
-
this.#angleTooltip = tip;
|
|
14762
|
-
} else {
|
|
14763
|
-
this.appendChild(handle);
|
|
14764
|
-
}
|
|
14765
|
-
}
|
|
14766
|
-
|
|
14767
|
-
#createSecondHandle(disabled, tooltips, handleSurface) {
|
|
14768
|
-
const handle = document.createElement("fig-handle");
|
|
14769
|
-
handle.setAttribute("drag", "true");
|
|
14770
|
-
handle.setAttribute("drag-surface", handleSurface);
|
|
14771
|
-
handle.setAttribute("drag-axes", "x,y");
|
|
14772
|
-
handle.setAttribute("drag-snapping", this.#snappingMode);
|
|
14773
|
-
handle.setAttribute("hit-area", "12 circle");
|
|
14774
|
-
handle.setAttribute("hit-area-mode", "delegate");
|
|
14775
|
-
handle.setAttribute("value", `${this.#x2}% ${this.#y2}%`);
|
|
14776
|
-
if (disabled) handle.setAttribute("disabled", "");
|
|
14777
|
-
this.#secondHandle = handle;
|
|
14778
|
-
|
|
14779
|
-
if (tooltips) {
|
|
14780
|
-
const tip = document.createElement("fig-tooltip");
|
|
14781
|
-
tip.setAttribute("action", "manual");
|
|
14782
|
-
tip.setAttribute("theme", "brand");
|
|
14783
|
-
tip.setAttribute("pointer", "false");
|
|
14784
|
-
tip.setAttribute("text", this.#secondTipText);
|
|
14785
|
-
tip.appendChild(handle);
|
|
14786
|
-
this.appendChild(tip);
|
|
14787
|
-
this.#secondTooltip = tip;
|
|
14788
|
-
} else {
|
|
14789
|
-
this.appendChild(handle);
|
|
14790
|
-
}
|
|
14791
|
-
}
|
|
14792
|
-
|
|
14793
|
-
#resizeCursorSvg(deg) {
|
|
14794
|
-
const r = Math.round(deg);
|
|
14795
|
-
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`;
|
|
14796
|
-
}
|
|
14797
|
-
|
|
14798
|
-
#rotateCursorSvg(deg) {
|
|
14799
|
-
const r = Math.round(deg - 45);
|
|
14800
|
-
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`;
|
|
14801
|
-
}
|
|
14802
|
-
|
|
14803
|
-
#syncAngleCursor() {
|
|
14804
|
-
if (!this.#angleHandle || !this.#hasAngle) return;
|
|
14805
|
-
const hitArea = this.#angleHandle.querySelector(".fig-handle-hit-area");
|
|
14806
|
-
if (!hitArea) return;
|
|
14807
|
-
hitArea.style.cursor = this.#rotateCursorSvg(this.#angle);
|
|
14808
|
-
}
|
|
14809
|
-
|
|
14810
|
-
#pointPointLineDeg() {
|
|
14811
|
-
return (Math.atan2(this.#y2 - this.#y, this.#x2 - this.#x) * 180) / Math.PI;
|
|
14812
|
-
}
|
|
14813
|
-
|
|
14814
|
-
#syncPointPointCursors() {
|
|
14815
|
-
if (!this.#hasSecondPoint) return;
|
|
14816
|
-
const deg = this.#pointPointLineDeg();
|
|
14817
|
-
const setHitCursor = (handle, rotateDeg) => {
|
|
14818
|
-
if (!handle) return;
|
|
14819
|
-
const hitArea = handle.querySelector(".fig-handle-hit-area");
|
|
14820
|
-
if (hitArea) hitArea.style.cursor = this.#rotateCursorSvg(rotateDeg);
|
|
14821
|
-
};
|
|
14822
|
-
setHitCursor(this.#pointHandle, deg + 180);
|
|
14823
|
-
setHitCursor(this.#secondHandle, deg);
|
|
14824
|
-
}
|
|
14825
|
-
|
|
14826
|
-
#positionHandle(handle, xPct, yPct, rect) {
|
|
14827
|
-
const hw = handle.offsetWidth / 2;
|
|
14828
|
-
const hh = handle.offsetHeight / 2;
|
|
14829
|
-
handle.style.left = `${(xPct / 100) * rect.width - hw}px`;
|
|
14830
|
-
handle.style.top = `${(yPct / 100) * rect.height - hh}px`;
|
|
14831
|
-
}
|
|
14832
|
-
|
|
14833
|
-
#syncPositions() {
|
|
14834
|
-
const container = this.#container;
|
|
14835
|
-
if (!container || !this.#pointHandle) return;
|
|
14836
|
-
const rect = container.getBoundingClientRect();
|
|
14837
|
-
|
|
14838
|
-
this.#positionHandle(this.#pointHandle, this.#x, this.#y, rect);
|
|
14839
|
-
|
|
14840
|
-
if (this.#radiusSvg) {
|
|
14841
|
-
const cx = (this.#x / 100) * rect.width;
|
|
14842
|
-
const cy = (this.#y / 100) * rect.height;
|
|
14843
|
-
const r = this.#resolveRadius(rect.width);
|
|
14844
|
-
const svg = this.#radiusSvg;
|
|
14845
|
-
const d = Math.max(r * 2, 1);
|
|
14846
|
-
svg.style.position = "absolute";
|
|
14847
|
-
svg.style.width = `${d}px`;
|
|
14848
|
-
svg.style.height = `${d}px`;
|
|
14849
|
-
svg.style.left = `${cx - r}px`;
|
|
14850
|
-
svg.style.top = `${cy - r}px`;
|
|
14851
|
-
svg.setAttribute("viewBox", `0 0 ${d} ${d}`);
|
|
14852
|
-
const circles = svg.querySelectorAll("circle");
|
|
14853
|
-
for (const c of circles) {
|
|
14854
|
-
c.setAttribute("cx", String(r));
|
|
14855
|
-
c.setAttribute("cy", String(r));
|
|
14856
|
-
c.setAttribute("r", String(Math.max(r - 1, 0)));
|
|
14857
|
-
}
|
|
14858
|
-
}
|
|
14859
|
-
|
|
14860
|
-
if (this.#angleSvg && this.#hasLine) {
|
|
14861
|
-
const cx = (this.#x / 100) * rect.width;
|
|
14862
|
-
const cy = (this.#y / 100) * rect.height;
|
|
14863
|
-
let lx2, ly2;
|
|
14864
|
-
if (this.#hasSecondPoint) {
|
|
14865
|
-
lx2 = (this.#x2 / 100) * rect.width;
|
|
14866
|
-
ly2 = (this.#y2 / 100) * rect.height;
|
|
14867
|
-
} else {
|
|
14868
|
-
const r = this.#resolveRadius(rect.width);
|
|
14869
|
-
const angleRad = (this.#angle * Math.PI) / 180;
|
|
14870
|
-
lx2 = cx + r * Math.cos(angleRad);
|
|
14871
|
-
ly2 = cy + r * Math.sin(angleRad);
|
|
14872
|
-
}
|
|
14873
|
-
|
|
14874
|
-
const svg = this.#angleSvg;
|
|
14875
|
-
svg.style.width = `${rect.width}px`;
|
|
14876
|
-
svg.style.height = `${rect.height}px`;
|
|
14877
|
-
svg.style.left = "0";
|
|
14878
|
-
svg.style.top = "0";
|
|
14879
|
-
svg.setAttribute("viewBox", `0 0 ${rect.width} ${rect.height}`);
|
|
14880
|
-
const line = svg.querySelector("line");
|
|
14881
|
-
if (line) {
|
|
14882
|
-
line.setAttribute("x1", String(cx));
|
|
14883
|
-
line.setAttribute("y1", String(cy));
|
|
14884
|
-
line.setAttribute("x2", String(lx2));
|
|
14885
|
-
line.setAttribute("y2", String(ly2));
|
|
14886
|
-
}
|
|
14887
|
-
}
|
|
14888
|
-
|
|
14889
|
-
if (this.#angleHandle && this.#hasAngle) {
|
|
14890
|
-
const cx = (this.#x / 100) * rect.width;
|
|
14891
|
-
const cy = (this.#y / 100) * rect.height;
|
|
14892
|
-
const r = this.#resolveRadius(rect.width);
|
|
14893
|
-
const angleRad = (this.#angle * Math.PI) / 180;
|
|
14894
|
-
const ax = cx + r * Math.cos(angleRad);
|
|
14895
|
-
const ay = cy + r * Math.sin(angleRad);
|
|
14896
|
-
const pxPct = rect.width > 0 ? (ax / rect.width) * 100 : 0;
|
|
14897
|
-
const pyPct = rect.height > 0 ? (ay / rect.height) * 100 : 0;
|
|
14898
|
-
this.#positionHandle(this.#angleHandle, pxPct, pyPct, rect);
|
|
14899
|
-
}
|
|
14900
|
-
|
|
14901
|
-
if (this.#secondHandle && this.#hasSecondPoint) {
|
|
14902
|
-
this.#positionHandle(this.#secondHandle, this.#x2, this.#y2, rect);
|
|
14903
|
-
}
|
|
14904
|
-
|
|
14905
|
-
this.#syncAngleCursor();
|
|
14906
|
-
this.#syncPointPointCursors();
|
|
14907
|
-
}
|
|
14908
|
-
|
|
14909
|
-
#emitInput() {
|
|
14910
|
-
this.dispatchEvent(
|
|
14911
|
-
new CustomEvent("input", { bubbles: true, detail: this.value }),
|
|
14912
|
-
);
|
|
14913
|
-
}
|
|
14914
|
-
|
|
14915
|
-
#emitChange() {
|
|
14916
|
-
this.dispatchEvent(
|
|
14917
|
-
new CustomEvent("change", { bubbles: true, detail: this.value }),
|
|
14918
|
-
);
|
|
14919
|
-
}
|
|
14920
|
-
|
|
14921
|
-
#syncValueAttribute() {
|
|
14922
|
-
this.setAttribute("value", JSON.stringify(this.value));
|
|
14923
|
-
}
|
|
14924
|
-
|
|
14925
|
-
#setupEventListeners() {
|
|
14926
|
-
if (!this.#pointHandle) return;
|
|
14927
|
-
|
|
14928
|
-
this.#pointHandle.addEventListener("input", (e) => {
|
|
14929
|
-
e.stopPropagation();
|
|
14930
|
-
if (e.detail?.color) {
|
|
14931
|
-
this.setAttribute("color", e.detail.color);
|
|
14932
|
-
this.#emitInput();
|
|
14933
|
-
return;
|
|
14934
|
-
}
|
|
14935
|
-
if (!this.#isDragging && this.#hasSecondPoint) {
|
|
14936
|
-
this.#prevBodyCursor = document.body.style.cursor;
|
|
14937
|
-
}
|
|
14938
|
-
this.#isDragging = true;
|
|
14939
|
-
const px = e.detail?.px ?? this.#x / 100;
|
|
14940
|
-
const py = e.detail?.py ?? this.#y / 100;
|
|
14941
|
-
this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
|
|
14942
|
-
this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
|
|
14943
|
-
if (this.#pointTooltip && this.#type !== "color") {
|
|
14944
|
-
this.#pointTooltip.setAttribute("text", this.#pointTipText);
|
|
14945
|
-
this.#pointTooltip.setAttribute("show", "true");
|
|
14946
|
-
this.#pointTooltip.showPopup?.();
|
|
14947
|
-
}
|
|
14948
|
-
this.#syncPositions();
|
|
14949
|
-
if (this.#hasSecondPoint) {
|
|
14950
|
-
document.body.style.cursor = this.#resizeCursorSvg(
|
|
14951
|
-
this.#pointPointLineDeg(),
|
|
14952
|
-
);
|
|
14953
|
-
}
|
|
14954
|
-
this.#emitInput();
|
|
14955
|
-
});
|
|
14956
|
-
|
|
14957
|
-
this.#pointHandle.addEventListener("change", (e) => {
|
|
14958
|
-
e.stopPropagation();
|
|
14959
|
-
if (e.detail?.color) {
|
|
14960
|
-
this.setAttribute("color", e.detail.color);
|
|
14961
|
-
this.#emitChange();
|
|
14962
|
-
return;
|
|
14963
|
-
}
|
|
14964
|
-
const px = e.detail?.px ?? this.#x / 100;
|
|
14965
|
-
const py = e.detail?.py ?? this.#y / 100;
|
|
14966
|
-
this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
|
|
14967
|
-
this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
|
|
14968
|
-
if (this.#pointTooltip && this.#type !== "color")
|
|
14969
|
-
this.#pointTooltip.removeAttribute("show");
|
|
14970
|
-
if (this.#hasSecondPoint) {
|
|
14971
|
-
document.body.style.cursor = this.#prevBodyCursor ?? "";
|
|
14972
|
-
}
|
|
14973
|
-
this.#syncPositions();
|
|
14974
|
-
this.#syncValueAttribute();
|
|
14975
|
-
this.#emitChange();
|
|
14976
|
-
requestAnimationFrame(() => {
|
|
14977
|
-
this.#isDragging = false;
|
|
14978
|
-
});
|
|
14979
|
-
});
|
|
14980
|
-
|
|
14981
|
-
if (this.#angleHandle) {
|
|
14982
|
-
this.#angleHandle.addEventListener("input", (e) => {
|
|
14983
|
-
e.stopPropagation();
|
|
14984
|
-
this.#isAngleDragging = true;
|
|
14985
|
-
this.classList.add("fig-canvas-control-ring-active");
|
|
14986
|
-
const container = this.#container;
|
|
14987
|
-
if (!container) return;
|
|
14988
|
-
const rect = container.getBoundingClientRect();
|
|
14989
|
-
const cx = (this.#x / 100) * rect.width;
|
|
14990
|
-
const cy = (this.#y / 100) * rect.height;
|
|
14991
|
-
const hx = e.detail?.x ?? 0;
|
|
14992
|
-
const hy = e.detail?.y ?? 0;
|
|
14993
|
-
const hw = this.#angleHandle.offsetWidth / 2;
|
|
14994
|
-
const hh = this.#angleHandle.offsetHeight / 2;
|
|
14995
|
-
const dx = hx + hw - cx;
|
|
14996
|
-
const dy = hy + hh - cy;
|
|
14997
|
-
let angle = (Math.atan2(dy, dx) * 180) / Math.PI;
|
|
14998
|
-
if (this.#shouldSnap(e.detail?.shiftKey)) {
|
|
14999
|
-
angle = Math.round(angle / 15) * 15;
|
|
15000
|
-
}
|
|
15001
|
-
this.#angle = angle;
|
|
15002
|
-
|
|
15003
|
-
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
15004
|
-
if (this.#shouldSnap(e.detail?.shiftKey)) {
|
|
15005
|
-
const step = this.#radiusIsPercent ? 5 : 10;
|
|
15006
|
-
if (this.#radiusIsPercent) {
|
|
15007
|
-
let pct = (dist / rect.width) * 100;
|
|
15008
|
-
pct = Math.round(pct / step) * step;
|
|
15009
|
-
dist = (pct / 100) * rect.width;
|
|
15010
|
-
} else {
|
|
15011
|
-
dist = Math.round(dist / step) * step;
|
|
15012
|
-
}
|
|
15013
|
-
}
|
|
15014
|
-
if (this.#radiusIsPercent) {
|
|
15015
|
-
this.#radius = Math.max(0, (dist / rect.width) * 100);
|
|
15016
|
-
} else {
|
|
15017
|
-
this.#radius = Math.max(0, dist);
|
|
15018
|
-
}
|
|
15019
|
-
|
|
15020
|
-
if (this.#angleTooltip) {
|
|
15021
|
-
this.#angleTooltip.setAttribute(
|
|
15022
|
-
"text",
|
|
15023
|
-
`Angle ${Math.round(this.#angle)}°`,
|
|
15024
|
-
);
|
|
15025
|
-
this.#angleTooltip.setAttribute("show", "true");
|
|
15026
|
-
this.#angleTooltip.showPopup?.();
|
|
15027
|
-
}
|
|
15028
|
-
this.#syncPositions();
|
|
15029
|
-
this.#emitInput();
|
|
15030
|
-
});
|
|
15031
|
-
|
|
15032
|
-
this.#angleHandle.addEventListener("change", (e) => {
|
|
15033
|
-
e.stopPropagation();
|
|
15034
|
-
this.classList.remove("fig-canvas-control-ring-active");
|
|
15035
|
-
if (this.#angleTooltip) this.#angleTooltip.removeAttribute("show");
|
|
15036
|
-
this.#syncPositions();
|
|
15037
|
-
this.#syncValueAttribute();
|
|
15038
|
-
this.#emitChange();
|
|
15039
|
-
requestAnimationFrame(() => {
|
|
15040
|
-
this.#isAngleDragging = false;
|
|
15041
|
-
});
|
|
15042
|
-
});
|
|
15043
|
-
|
|
15044
|
-
this.#angleHandle.addEventListener("hitareadown", (e) => {
|
|
15045
|
-
e.stopPropagation();
|
|
15046
|
-
const origEvent = e.detail?.originalEvent;
|
|
15047
|
-
if (!origEvent) return;
|
|
15048
|
-
origEvent.preventDefault();
|
|
15049
|
-
this.#isAngleDragging = true;
|
|
15050
|
-
this.classList.add("fig-canvas-control-ring-active");
|
|
15051
|
-
const container = this.#container;
|
|
15052
|
-
if (!container) return;
|
|
15053
|
-
|
|
15054
|
-
if (this.#angleTooltip) {
|
|
15055
|
-
this.#angleTooltip.setAttribute("show", "true");
|
|
15056
|
-
this.#angleTooltip.showPopup?.();
|
|
15057
|
-
}
|
|
15058
|
-
|
|
15059
|
-
const prevBodyCursor = document.body.style.cursor;
|
|
15060
|
-
let lastCursorDeg = Math.round(this.#angle);
|
|
15061
|
-
document.body.style.cursor = this.#rotateCursorSvg(lastCursorDeg);
|
|
15062
|
-
|
|
15063
|
-
const onMove = (ev) => {
|
|
15064
|
-
const rect = container.getBoundingClientRect();
|
|
15065
|
-
const cx = (this.#x / 100) * rect.width;
|
|
15066
|
-
const cy = (this.#y / 100) * rect.height;
|
|
15067
|
-
const dx = ev.clientX - rect.left - cx;
|
|
15068
|
-
const dy = ev.clientY - rect.top - cy;
|
|
15069
|
-
let angle = (Math.atan2(dy, dx) * 180) / Math.PI;
|
|
15070
|
-
if (this.#shouldSnap(ev.shiftKey)) {
|
|
15071
|
-
angle = Math.round(angle / 15) * 15;
|
|
15072
|
-
}
|
|
15073
|
-
this.#angle = angle;
|
|
15074
|
-
if (this.#angleTooltip)
|
|
15075
|
-
this.#angleTooltip.setAttribute(
|
|
15076
|
-
"text",
|
|
15077
|
-
`Angle ${Math.round(angle)}°`,
|
|
15078
|
-
);
|
|
15079
|
-
this.#syncPositions();
|
|
15080
|
-
const curDeg = Math.round(angle);
|
|
15081
|
-
if (curDeg !== lastCursorDeg) {
|
|
15082
|
-
lastCursorDeg = curDeg;
|
|
15083
|
-
document.body.style.cursor = this.#rotateCursorSvg(curDeg);
|
|
15084
|
-
}
|
|
15085
|
-
this.#emitInput();
|
|
15086
|
-
};
|
|
15087
|
-
|
|
15088
|
-
const onUp = () => {
|
|
15089
|
-
this.#isAngleDragging = false;
|
|
15090
|
-
this.classList.remove("fig-canvas-control-ring-active");
|
|
15091
|
-
document.body.style.cursor = prevBodyCursor;
|
|
15092
|
-
if (this.#angleTooltip) this.#angleTooltip.removeAttribute("show");
|
|
15093
|
-
this.#syncValueAttribute();
|
|
15094
|
-
this.#emitChange();
|
|
15095
|
-
window.removeEventListener("pointermove", onMove);
|
|
15096
|
-
window.removeEventListener("pointerup", onUp);
|
|
15097
|
-
};
|
|
15098
|
-
|
|
15099
|
-
window.addEventListener("pointermove", onMove);
|
|
15100
|
-
window.addEventListener("pointerup", onUp);
|
|
15101
|
-
});
|
|
15102
|
-
}
|
|
15103
|
-
|
|
15104
|
-
if (this.#secondHandle) {
|
|
15105
|
-
this.#secondHandle.addEventListener("input", (e) => {
|
|
15106
|
-
e.stopPropagation();
|
|
15107
|
-
if (!this.#isSecondDragging) {
|
|
15108
|
-
this.#prevBodyCursor = document.body.style.cursor;
|
|
15109
|
-
}
|
|
15110
|
-
this.#isSecondDragging = true;
|
|
15111
|
-
const px = e.detail?.px ?? this.#x2 / 100;
|
|
15112
|
-
const py = e.detail?.py ?? this.#y2 / 100;
|
|
15113
|
-
this.#x2 = Math.round(Math.max(0, Math.min(100, px * 100)));
|
|
15114
|
-
this.#y2 = Math.round(Math.max(0, Math.min(100, py * 100)));
|
|
15115
|
-
if (this.#secondTooltip) {
|
|
15116
|
-
this.#secondTooltip.setAttribute("text", this.#secondTipText);
|
|
15117
|
-
this.#secondTooltip.setAttribute("show", "true");
|
|
15118
|
-
this.#secondTooltip.showPopup?.();
|
|
15119
|
-
}
|
|
15120
|
-
this.#syncPositions();
|
|
15121
|
-
document.body.style.cursor = this.#resizeCursorSvg(
|
|
15122
|
-
this.#pointPointLineDeg(),
|
|
15123
|
-
);
|
|
15124
|
-
this.#emitInput();
|
|
15125
|
-
});
|
|
15126
|
-
|
|
15127
|
-
this.#secondHandle.addEventListener("change", (e) => {
|
|
15128
|
-
e.stopPropagation();
|
|
15129
|
-
document.body.style.cursor = this.#prevBodyCursor ?? "";
|
|
15130
|
-
if (this.#secondTooltip) this.#secondTooltip.removeAttribute("show");
|
|
15131
|
-
this.#syncPositions();
|
|
15132
|
-
this.#syncValueAttribute();
|
|
15133
|
-
this.#emitChange();
|
|
15134
|
-
requestAnimationFrame(() => {
|
|
15135
|
-
this.#isSecondDragging = false;
|
|
15136
|
-
});
|
|
15137
|
-
});
|
|
15138
|
-
|
|
15139
|
-
this.#setupPointPointHitArea(this.#pointHandle, true);
|
|
15140
|
-
this.#setupPointPointHitArea(this.#secondHandle, false);
|
|
15141
|
-
}
|
|
15142
|
-
}
|
|
15143
|
-
|
|
15144
|
-
#setupPointPointHitArea(handle, isFirst) {
|
|
15145
|
-
if (!handle) return;
|
|
15146
|
-
handle.addEventListener("hitareadown", (e) => {
|
|
15147
|
-
e.stopPropagation();
|
|
15148
|
-
const origEvent = e.detail?.originalEvent;
|
|
15149
|
-
if (!origEvent) return;
|
|
15150
|
-
origEvent.preventDefault();
|
|
15151
|
-
this.#isDragging = true;
|
|
15152
|
-
const container = this.#container;
|
|
15153
|
-
if (!container) return;
|
|
15154
|
-
const rect = container.getBoundingClientRect();
|
|
15155
|
-
|
|
15156
|
-
const pivotX = isFirst ? this.#x2 : this.#x;
|
|
15157
|
-
const pivotY = isFirst ? this.#y2 : this.#y;
|
|
15158
|
-
const movingX = isFirst ? this.#x : this.#x2;
|
|
15159
|
-
const movingY = isFirst ? this.#y : this.#y2;
|
|
15160
|
-
const pcx = (pivotX / 100) * rect.width;
|
|
15161
|
-
const pcy = (pivotY / 100) * rect.height;
|
|
15162
|
-
const mcx = (movingX / 100) * rect.width;
|
|
15163
|
-
const mcy = (movingY / 100) * rect.height;
|
|
15164
|
-
const fixedLen = Math.sqrt((mcx - pcx) ** 2 + (mcy - pcy) ** 2);
|
|
15165
|
-
|
|
15166
|
-
const tooltip = isFirst ? this.#pointTooltip : this.#secondTooltip;
|
|
15167
|
-
if (tooltip) {
|
|
15168
|
-
tooltip.setAttribute("show", "true");
|
|
15169
|
-
tooltip.showPopup?.();
|
|
15170
|
-
}
|
|
15171
|
-
|
|
15172
|
-
const prevBodyCursor = document.body.style.cursor;
|
|
15173
|
-
const initDeg = this.#pointPointLineDeg();
|
|
15174
|
-
let lastCursorDeg = Math.round(isFirst ? initDeg + 180 : initDeg);
|
|
15175
|
-
document.body.style.cursor = this.#rotateCursorSvg(lastCursorDeg);
|
|
15176
|
-
|
|
15177
|
-
const onMove = (ev) => {
|
|
15178
|
-
const r = container.getBoundingClientRect();
|
|
15179
|
-
const px = (pivotX / 100) * r.width;
|
|
15180
|
-
const py = (pivotY / 100) * r.height;
|
|
15181
|
-
const dx = ev.clientX - r.left - px;
|
|
15182
|
-
const dy = ev.clientY - r.top - py;
|
|
15183
|
-
let angle = Math.atan2(dy, dx);
|
|
15184
|
-
if (this.#shouldSnap(ev.shiftKey)) {
|
|
15185
|
-
const snapDeg = Math.round((angle * 180) / Math.PI / 15) * 15;
|
|
15186
|
-
angle = (snapDeg * Math.PI) / 180;
|
|
15187
|
-
}
|
|
15188
|
-
const nx = px + fixedLen * Math.cos(angle);
|
|
15189
|
-
const ny = py + fixedLen * Math.sin(angle);
|
|
15190
|
-
const newPctX = Math.max(0, Math.min(100, (nx / r.width) * 100));
|
|
15191
|
-
const newPctY = Math.max(0, Math.min(100, (ny / r.height) * 100));
|
|
15192
|
-
if (isFirst) {
|
|
15193
|
-
this.#x = newPctX;
|
|
15194
|
-
this.#y = newPctY;
|
|
15195
|
-
} else {
|
|
15196
|
-
this.#x2 = newPctX;
|
|
15197
|
-
this.#y2 = newPctY;
|
|
15198
|
-
}
|
|
15199
|
-
this.#syncPositions();
|
|
15200
|
-
const curDeg = Math.round(
|
|
15201
|
-
isFirst ? this.#pointPointLineDeg() + 180 : this.#pointPointLineDeg(),
|
|
15202
|
-
);
|
|
15203
|
-
if (curDeg !== lastCursorDeg) {
|
|
15204
|
-
lastCursorDeg = curDeg;
|
|
15205
|
-
document.body.style.cursor = this.#rotateCursorSvg(curDeg);
|
|
15206
|
-
}
|
|
15207
|
-
this.#emitInput();
|
|
15208
|
-
};
|
|
15209
|
-
|
|
15210
|
-
const onUp = () => {
|
|
15211
|
-
this.#isDragging = false;
|
|
15212
|
-
document.body.style.cursor = prevBodyCursor;
|
|
15213
|
-
if (tooltip) tooltip.removeAttribute("show");
|
|
15214
|
-
this.#syncValueAttribute();
|
|
15215
|
-
this.#emitChange();
|
|
15216
|
-
window.removeEventListener("pointermove", onMove);
|
|
15217
|
-
window.removeEventListener("pointerup", onUp);
|
|
15218
|
-
};
|
|
15219
|
-
|
|
15220
|
-
window.addEventListener("pointermove", onMove);
|
|
15221
|
-
window.addEventListener("pointerup", onUp);
|
|
15222
|
-
});
|
|
15223
|
-
}
|
|
15224
|
-
|
|
15225
|
-
#setupRadiusDrag(circle) {
|
|
15226
|
-
if (!circle) return;
|
|
15227
|
-
circle.addEventListener("pointermove", (e) => {
|
|
15228
|
-
if (this.#isRadiusDragging) return;
|
|
15229
|
-
const container = this.#container;
|
|
15230
|
-
if (!container) return;
|
|
15231
|
-
const rect = container.getBoundingClientRect();
|
|
15232
|
-
const cx = (this.#x / 100) * rect.width;
|
|
15233
|
-
const cy = (this.#y / 100) * rect.height;
|
|
15234
|
-
const deg =
|
|
15235
|
-
(Math.atan2(e.clientY - rect.top - cy, e.clientX - rect.left - cx) *
|
|
15236
|
-
180) /
|
|
15237
|
-
Math.PI;
|
|
15238
|
-
circle.style.cursor = this.#resizeCursorSvg(deg);
|
|
15239
|
-
});
|
|
15240
|
-
const onDown = (e) => {
|
|
15241
|
-
if (this.hasAttribute("disabled")) return;
|
|
15242
|
-
e.preventDefault();
|
|
15243
|
-
e.stopPropagation();
|
|
15244
|
-
this.#isRadiusDragging = true;
|
|
15245
|
-
this.classList.add("fig-canvas-control-ring-active");
|
|
15246
|
-
const container = this.#container;
|
|
15247
|
-
if (!container) return;
|
|
15248
|
-
|
|
15249
|
-
if (this.#radiusTooltip) {
|
|
15250
|
-
this.#radiusTooltip.setAttribute("show", "true");
|
|
15251
|
-
this.#radiusTooltip.showPopup?.();
|
|
15252
|
-
}
|
|
15253
|
-
|
|
15254
|
-
const prevBodyCursor = document.body.style.cursor;
|
|
15255
|
-
circle.style.pointerEvents = "none";
|
|
15256
|
-
const rect0 = container.getBoundingClientRect();
|
|
15257
|
-
const cx0 = (this.#x / 100) * rect0.width;
|
|
15258
|
-
const cy0 = (this.#y / 100) * rect0.height;
|
|
15259
|
-
const initDeg =
|
|
15260
|
-
(Math.atan2(e.clientY - rect0.top - cy0, e.clientX - rect0.left - cx0) *
|
|
15261
|
-
180) /
|
|
15262
|
-
Math.PI;
|
|
15263
|
-
let lastCursorDeg = Math.round(initDeg);
|
|
15264
|
-
document.body.style.cursor = this.#resizeCursorSvg(lastCursorDeg);
|
|
15265
|
-
|
|
15266
|
-
const onMove = (ev) => {
|
|
15267
|
-
const rect = container.getBoundingClientRect();
|
|
15268
|
-
const cx = (this.#x / 100) * rect.width;
|
|
15269
|
-
const cy = (this.#y / 100) * rect.height;
|
|
15270
|
-
const dx = ev.clientX - rect.left - cx;
|
|
15271
|
-
const dy = ev.clientY - rect.top - cy;
|
|
15272
|
-
const curDeg = Math.round((Math.atan2(dy, dx) * 180) / Math.PI);
|
|
15273
|
-
if (curDeg !== lastCursorDeg) {
|
|
15274
|
-
lastCursorDeg = curDeg;
|
|
15275
|
-
document.body.style.cursor = this.#resizeCursorSvg(curDeg);
|
|
15276
|
-
}
|
|
15277
|
-
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
15278
|
-
if (this.#shouldSnap(ev.shiftKey)) {
|
|
15279
|
-
const step = this.#radiusIsPercent ? 5 : 10;
|
|
15280
|
-
if (this.#radiusIsPercent) {
|
|
15281
|
-
let pct = (dist / rect.width) * 100;
|
|
15282
|
-
pct = Math.round(pct / step) * step;
|
|
15283
|
-
dist = (pct / 100) * rect.width;
|
|
15284
|
-
} else {
|
|
15285
|
-
dist = Math.round(dist / step) * step;
|
|
15286
|
-
}
|
|
15287
|
-
}
|
|
15288
|
-
if (this.#radiusIsPercent) {
|
|
15289
|
-
this.#radius = Math.max(0, (dist / rect.width) * 100);
|
|
15290
|
-
} else {
|
|
15291
|
-
this.#radius = Math.max(0, dist);
|
|
15292
|
-
}
|
|
15293
|
-
if (this.#radiusTooltip)
|
|
15294
|
-
this.#radiusTooltip.setAttribute("text", this.#formatRadius());
|
|
15295
|
-
this.#syncPositions();
|
|
15296
|
-
this.#emitInput();
|
|
15297
|
-
};
|
|
15298
|
-
|
|
15299
|
-
const onUp = () => {
|
|
15300
|
-
this.#isRadiusDragging = false;
|
|
15301
|
-
this.classList.remove("fig-canvas-control-ring-active");
|
|
15302
|
-
circle.style.pointerEvents = "";
|
|
15303
|
-
document.body.style.cursor = prevBodyCursor;
|
|
15304
|
-
if (this.#radiusTooltip) this.#radiusTooltip.removeAttribute("show");
|
|
15305
|
-
this.#syncValueAttribute();
|
|
15306
|
-
this.#emitChange();
|
|
15307
|
-
window.removeEventListener("pointermove", onMove);
|
|
15308
|
-
window.removeEventListener("pointerup", onUp);
|
|
15309
|
-
};
|
|
15310
|
-
|
|
15311
|
-
window.addEventListener("pointermove", onMove);
|
|
15312
|
-
window.addEventListener("pointerup", onUp);
|
|
15313
|
-
};
|
|
15314
|
-
circle.addEventListener("pointerdown", onDown);
|
|
15315
|
-
this._radiusDragCleanup = () =>
|
|
15316
|
-
circle.removeEventListener("pointerdown", onDown);
|
|
15317
|
-
}
|
|
15318
|
-
|
|
15319
|
-
#teardownRadiusDrag() {
|
|
15320
|
-
if (this._radiusDragCleanup) {
|
|
15321
|
-
this._radiusDragCleanup();
|
|
15322
|
-
this._radiusDragCleanup = null;
|
|
15323
|
-
}
|
|
15324
|
-
}
|
|
15325
|
-
}
|
|
15326
|
-
customElements.define("fig-canvas-control", FigCanvasControl);
|
|
15327
|
-
|
|
15328
13679
|
/* Handle */
|
|
15329
13680
|
class FigHandle extends HTMLElement {
|
|
15330
13681
|
static observedAttributes = [
|