@rogieking/figui3 3.23.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/README.md +3 -4
- package/components.css +15 -331
- package/fig.js +1088 -2730
- 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
|
}
|
|
@@ -8007,57 +7809,58 @@ customElements.define("fig-chit", FigChit);
|
|
|
8007
7809
|
class FigSwatch extends FigChit {}
|
|
8008
7810
|
customElements.define("fig-swatch", FigSwatch);
|
|
8009
7811
|
|
|
8010
|
-
/*
|
|
7812
|
+
/* Image */
|
|
8011
7813
|
/**
|
|
8012
|
-
*
|
|
8013
|
-
* @attr {
|
|
8014
|
-
* @attr {
|
|
8015
|
-
* @attr {string}
|
|
8016
|
-
* @attr {string}
|
|
7814
|
+
* @attr {string} src - Image source URL
|
|
7815
|
+
* @attr {boolean} upload - Show upload overlay (generates fig-input-file)
|
|
7816
|
+
* @attr {string} label - Upload button label (default "Upload")
|
|
7817
|
+
* @attr {string} size - small | medium | large | auto
|
|
7818
|
+
* @attr {string} aspect-ratio - CSS aspect-ratio or "auto" (lazy dimension sniff)
|
|
7819
|
+
* @attr {string} fit - CSS object-fit value
|
|
7820
|
+
* @attr {boolean} checkerboard - Show checkerboard behind transparent images
|
|
8017
7821
|
*/
|
|
8018
7822
|
class FigImage extends HTMLElement {
|
|
8019
7823
|
#src = null;
|
|
7824
|
+
#chit = null;
|
|
7825
|
+
#fileInput = null;
|
|
7826
|
+
#blobUrl = null;
|
|
7827
|
+
#file = null;
|
|
8020
7828
|
#boundHandleFileInput = this.#handleFileInput.bind(this);
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
7829
|
+
|
|
7830
|
+
static get observedAttributes() {
|
|
7831
|
+
return ["src", "upload", "aspect-ratio", "fit", "checkerboard"];
|
|
8024
7832
|
}
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
? `<fig-button variant="overlay" type="upload">
|
|
8037
|
-
${this.label}
|
|
8038
|
-
<input type="file" accept="image/*" />
|
|
8039
|
-
</fig-button>`
|
|
8040
|
-
: ""
|
|
8041
|
-
} ${
|
|
8042
|
-
this.download
|
|
8043
|
-
? `<fig-button variant="overlay" icon="true" type="download">
|
|
8044
|
-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
8045
|
-
<path d="M17.5 13C17.7761 13 18 13.2239 18 13.5V16.5C18 17.3284 17.3284 18 16.5 18H7.5C6.67157 18 6 17.3284 6 16.5V13.5C6 13.2239 6.22386 13 6.5 13C6.77614 13 7 13.2239 7 13.5V16.5C7 16.7761 7.22386 17 7.5 17H16.5C16.7761 17 17 16.7761 17 16.5V13.5C17 13.2239 17.2239 13 17.5 13ZM12 6C12.2761 6 12.5 6.22386 12.5 6.5V12.293L14.6465 10.1465C14.8417 9.95122 15.1583 9.95122 15.3535 10.1465C15.5488 10.3417 15.5488 10.6583 15.3535 10.8535L12.3535 13.8535C12.2597 13.9473 12.1326 14 12 14C11.9006 14 11.8042 13.9704 11.7227 13.916L11.6465 13.8535L8.64648 10.8535C8.45122 10.6583 8.45122 10.3417 8.64648 10.1465C8.84175 9.95122 9.15825 9.95122 9.35352 10.1465L11.5 12.293V6.5C11.5 6.22386 11.7239 6 12 6Z" fill="black"/>
|
|
8046
|
-
</svg></fig-button>`
|
|
8047
|
-
: ""
|
|
8048
|
-
}</div>`;
|
|
7833
|
+
|
|
7834
|
+
get src() {
|
|
7835
|
+
return this.#src;
|
|
7836
|
+
}
|
|
7837
|
+
set src(value) {
|
|
7838
|
+
this.#src = value;
|
|
7839
|
+
this.setAttribute("src", value);
|
|
7840
|
+
}
|
|
7841
|
+
|
|
7842
|
+
get file() {
|
|
7843
|
+
return this.#file;
|
|
8049
7844
|
}
|
|
7845
|
+
|
|
7846
|
+
async getBase64() {
|
|
7847
|
+
const src = this.#src;
|
|
7848
|
+
if (!src) return null;
|
|
7849
|
+
const res = await fetch(src);
|
|
7850
|
+
const blob = await res.blob();
|
|
7851
|
+
const bitmap = await createImageBitmap(blob);
|
|
7852
|
+
const canvas = document.createElement("canvas");
|
|
7853
|
+
canvas.width = bitmap.width;
|
|
7854
|
+
canvas.height = bitmap.height;
|
|
7855
|
+
canvas.getContext("2d").drawImage(bitmap, 0, 0);
|
|
7856
|
+
bitmap.close();
|
|
7857
|
+
const dataUrl = canvas.toDataURL();
|
|
7858
|
+
return dataUrl;
|
|
7859
|
+
}
|
|
7860
|
+
|
|
8050
7861
|
connectedCallback() {
|
|
8051
7862
|
this.#src = this.getAttribute("src") || "";
|
|
8052
|
-
|
|
8053
|
-
this.hasAttribute("upload") && this.getAttribute("upload") !== "false";
|
|
8054
|
-
this.download =
|
|
8055
|
-
this.hasAttribute("download") &&
|
|
8056
|
-
this.getAttribute("download") !== "false";
|
|
8057
|
-
this.label = this.getAttribute("label") || "Upload";
|
|
8058
|
-
this.size = this.getAttribute("size") || "small";
|
|
8059
|
-
this.innerHTML = this.#getInnerHTML();
|
|
8060
|
-
this.#updateRefs();
|
|
7863
|
+
|
|
8061
7864
|
const ar = this.getAttribute("aspect-ratio");
|
|
8062
7865
|
if (ar && ar !== "auto") {
|
|
8063
7866
|
this.style.setProperty("--aspect-ratio", ar);
|
|
@@ -8066,182 +7869,169 @@ class FigImage extends HTMLElement {
|
|
|
8066
7869
|
if (fit) {
|
|
8067
7870
|
this.style.setProperty("--fit", fit);
|
|
8068
7871
|
}
|
|
7872
|
+
|
|
7873
|
+
if (!this.querySelector("fig-chit")) {
|
|
7874
|
+
const chit = document.createElement("fig-chit");
|
|
7875
|
+
chit.setAttribute("data-generated", "");
|
|
7876
|
+
chit.setAttribute("size", "large");
|
|
7877
|
+
chit.setAttribute("data-type", "image");
|
|
7878
|
+
chit.setAttribute("disabled", "");
|
|
7879
|
+
this.#applyChitBackground(chit);
|
|
7880
|
+
if (this.hasAttribute("checkerboard") && this.getAttribute("checkerboard") !== "false") {
|
|
7881
|
+
chit.setAttribute("checkerboard", "");
|
|
7882
|
+
}
|
|
7883
|
+
this.prepend(chit);
|
|
7884
|
+
}
|
|
7885
|
+
this.#chit = this.querySelector("fig-chit");
|
|
7886
|
+
|
|
7887
|
+
const isUpload = this.hasAttribute("upload") && this.getAttribute("upload") !== "false";
|
|
7888
|
+
if (isUpload && !this.querySelector("fig-input-file[data-generated]")) {
|
|
7889
|
+
this.#createFileInput();
|
|
7890
|
+
}
|
|
7891
|
+
|
|
7892
|
+
if (this.#src && this.getAttribute("aspect-ratio") === "auto") {
|
|
7893
|
+
this.#sniffDimensions(this.#src);
|
|
7894
|
+
}
|
|
8069
7895
|
}
|
|
7896
|
+
|
|
8070
7897
|
disconnectedCallback() {
|
|
8071
|
-
this
|
|
8072
|
-
this
|
|
8073
|
-
|
|
8074
|
-
this.#
|
|
8075
|
-
|
|
7898
|
+
this.#fileInput?.removeEventListener("change", this.#boundHandleFileInput);
|
|
7899
|
+
if (this.#blobUrl) {
|
|
7900
|
+
URL.revokeObjectURL(this.#blobUrl);
|
|
7901
|
+
this.#blobUrl = null;
|
|
7902
|
+
}
|
|
8076
7903
|
}
|
|
8077
7904
|
|
|
8078
|
-
#
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
"change",
|
|
8086
|
-
this.#boundHandleFileInput,
|
|
8087
|
-
);
|
|
8088
|
-
this.fileInput?.addEventListener("change", this.#boundHandleFileInput);
|
|
8089
|
-
}
|
|
8090
|
-
if (this.download) {
|
|
8091
|
-
this.downloadButton = this.querySelector("fig-button[type='download']");
|
|
8092
|
-
this.downloadButton?.removeEventListener(
|
|
8093
|
-
"click",
|
|
8094
|
-
this.#boundHandleDownload,
|
|
8095
|
-
);
|
|
8096
|
-
this.downloadButton?.addEventListener(
|
|
8097
|
-
"click",
|
|
8098
|
-
this.#boundHandleDownload,
|
|
8099
|
-
);
|
|
8100
|
-
}
|
|
8101
|
-
});
|
|
7905
|
+
#applyChitBackground(chit) {
|
|
7906
|
+
const cb = this.hasAttribute("checkerboard") && this.getAttribute("checkerboard") !== "false";
|
|
7907
|
+
if (this.#src) {
|
|
7908
|
+
chit.setAttribute("background", `url(${this.#src})`);
|
|
7909
|
+
} else {
|
|
7910
|
+
chit.setAttribute("background", cb ? "url()" : "var(--figma-color-bg-secondary)");
|
|
7911
|
+
}
|
|
8102
7912
|
}
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
const
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
this.image.onload = async () => {
|
|
8116
|
-
this.aspectRatio = this.image.width / this.image.height;
|
|
8117
|
-
const ar = this.getAttribute("aspect-ratio");
|
|
8118
|
-
if (!ar || ar === "auto") {
|
|
8119
|
-
this.style.setProperty(
|
|
8120
|
-
"--aspect-ratio",
|
|
8121
|
-
`${this.image.width}/${this.image.height}`,
|
|
8122
|
-
);
|
|
8123
|
-
}
|
|
8124
|
-
this.dispatchEvent(
|
|
8125
|
-
new CustomEvent("loaded", {
|
|
8126
|
-
bubbles: true,
|
|
8127
|
-
cancelable: true,
|
|
8128
|
-
detail: {
|
|
8129
|
-
blob: this.blob,
|
|
8130
|
-
base64: this.base64,
|
|
8131
|
-
},
|
|
8132
|
-
}),
|
|
8133
|
-
);
|
|
8134
|
-
resolve();
|
|
8135
|
-
|
|
8136
|
-
// Create canvas to extract blob and base64 from image
|
|
8137
|
-
const canvas = document.createElement("canvas");
|
|
8138
|
-
const ctx = canvas.getContext("2d");
|
|
8139
|
-
canvas.width = this.image.width;
|
|
8140
|
-
canvas.height = this.image.height;
|
|
8141
|
-
ctx.drawImage(this.image, 0, 0);
|
|
8142
|
-
|
|
8143
|
-
// Get base64 from canvas
|
|
8144
|
-
this.base64 = canvas.toDataURL();
|
|
8145
|
-
|
|
8146
|
-
// Get blob from canvas
|
|
8147
|
-
canvas.toBlob((blob) => {
|
|
8148
|
-
if (this.blob) {
|
|
8149
|
-
URL.revokeObjectURL(this.blob);
|
|
8150
|
-
}
|
|
8151
|
-
if (blob) {
|
|
8152
|
-
this.blob = URL.createObjectURL(blob);
|
|
8153
|
-
}
|
|
8154
|
-
});
|
|
8155
|
-
};
|
|
8156
|
-
this.image.src = src;
|
|
8157
|
-
});
|
|
7913
|
+
|
|
7914
|
+
#createFileInput() {
|
|
7915
|
+
const fi = document.createElement("fig-input-file");
|
|
7916
|
+
fi.setAttribute("data-generated", "");
|
|
7917
|
+
fi.setAttribute("accepts", "image/*");
|
|
7918
|
+
fi.setAttribute("variant", "overlay");
|
|
7919
|
+
const defaultLabel = this.getAttribute("label") || "Upload";
|
|
7920
|
+
fi.setAttribute("label", this.#src ? "Replace" : defaultLabel);
|
|
7921
|
+
if (this.#src) fi.setAttribute("url", this.#src);
|
|
7922
|
+
fi.addEventListener("change", this.#boundHandleFileInput);
|
|
7923
|
+
this.append(fi);
|
|
7924
|
+
this.#fileInput = fi;
|
|
8158
7925
|
}
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
7926
|
+
|
|
7927
|
+
#removeFileInput() {
|
|
7928
|
+
if (this.#fileInput) {
|
|
7929
|
+
this.#fileInput.removeEventListener("change", this.#boundHandleFileInput);
|
|
7930
|
+
this.#fileInput.remove();
|
|
7931
|
+
this.#fileInput = null;
|
|
7932
|
+
}
|
|
7933
|
+
}
|
|
7934
|
+
|
|
7935
|
+
#handleFileInput(e) {
|
|
7936
|
+
const file = e.detail?.files?.[0];
|
|
7937
|
+
|
|
7938
|
+
if (!file) {
|
|
7939
|
+
if (this.#blobUrl) {
|
|
7940
|
+
URL.revokeObjectURL(this.#blobUrl);
|
|
7941
|
+
this.#blobUrl = null;
|
|
7942
|
+
}
|
|
7943
|
+
this.#file = null;
|
|
7944
|
+
this.removeAttribute("src");
|
|
7945
|
+
this.dispatchEvent(
|
|
7946
|
+
new CustomEvent("change", { bubbles: true, cancelable: true }),
|
|
7947
|
+
);
|
|
7948
|
+
return;
|
|
7949
|
+
}
|
|
7950
|
+
|
|
7951
|
+
if (this.#blobUrl) {
|
|
7952
|
+
URL.revokeObjectURL(this.#blobUrl);
|
|
7953
|
+
}
|
|
7954
|
+
this.#file = file;
|
|
7955
|
+
this.#blobUrl = URL.createObjectURL(file);
|
|
7956
|
+
|
|
7957
|
+
this.setAttribute("src", this.#blobUrl);
|
|
7958
|
+
|
|
8175
7959
|
this.dispatchEvent(
|
|
8176
7960
|
new CustomEvent("loaded", {
|
|
8177
7961
|
bubbles: true,
|
|
8178
7962
|
cancelable: true,
|
|
8179
|
-
detail: {
|
|
8180
|
-
blob: this.blob,
|
|
8181
|
-
base64: this.base64,
|
|
8182
|
-
},
|
|
7963
|
+
detail: { file, src: this.#blobUrl },
|
|
8183
7964
|
}),
|
|
8184
7965
|
);
|
|
8185
|
-
//emit for change too
|
|
8186
7966
|
this.dispatchEvent(
|
|
8187
|
-
new CustomEvent("change", {
|
|
8188
|
-
bubbles: true,
|
|
8189
|
-
cancelable: true,
|
|
8190
|
-
}),
|
|
7967
|
+
new CustomEvent("change", { bubbles: true, cancelable: true }),
|
|
8191
7968
|
);
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
get src() {
|
|
8198
|
-
return this.#src;
|
|
7969
|
+
|
|
7970
|
+
if (this.#fileInput) {
|
|
7971
|
+
this.#fileInput.clear();
|
|
7972
|
+
this.#fileInput.setAttribute("label", "Replace");
|
|
7973
|
+
}
|
|
8199
7974
|
}
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
7975
|
+
|
|
7976
|
+
async #sniffDimensions(src) {
|
|
7977
|
+
try {
|
|
7978
|
+
let blob;
|
|
7979
|
+
if (src.startsWith("blob:")) {
|
|
7980
|
+
const res = await fetch(src);
|
|
7981
|
+
blob = await res.blob();
|
|
7982
|
+
} else {
|
|
7983
|
+
const res = await fetch(src, { mode: "cors" });
|
|
7984
|
+
blob = await res.blob();
|
|
7985
|
+
}
|
|
7986
|
+
const bitmap = await createImageBitmap(blob);
|
|
7987
|
+
this.style.setProperty("--aspect-ratio", `${bitmap.width}/${bitmap.height}`);
|
|
7988
|
+
bitmap.close();
|
|
7989
|
+
} catch {
|
|
7990
|
+
// Non-critical — CSS aspect-ratio fallback handles it
|
|
7991
|
+
}
|
|
8203
7992
|
}
|
|
8204
7993
|
|
|
8205
7994
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
7995
|
+
if (oldValue === newValue) return;
|
|
7996
|
+
|
|
8206
7997
|
if (name === "src") {
|
|
8207
7998
|
this.#src = newValue;
|
|
8208
|
-
if (this
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
7999
|
+
if (this.#chit) {
|
|
8000
|
+
this.#applyChitBackground(this.#chit);
|
|
8001
|
+
}
|
|
8002
|
+
if (this.#fileInput) {
|
|
8003
|
+
const defaultLabel = this.getAttribute("label") || "Upload";
|
|
8004
|
+
this.#fileInput.setAttribute("label", this.#src ? "Replace" : defaultLabel);
|
|
8212
8005
|
if (this.#src) {
|
|
8213
|
-
this.
|
|
8006
|
+
this.#fileInput.setAttribute("url", this.#src);
|
|
8214
8007
|
} else {
|
|
8215
|
-
this.
|
|
8216
|
-
"background",
|
|
8217
|
-
hasCb ? "url()" : "var(--figma-color-bg-secondary)",
|
|
8218
|
-
);
|
|
8008
|
+
this.#fileInput.removeAttribute("url");
|
|
8219
8009
|
}
|
|
8220
8010
|
}
|
|
8221
|
-
if (this.#src) {
|
|
8222
|
-
this.#
|
|
8011
|
+
if (this.#src && this.getAttribute("aspect-ratio") === "auto") {
|
|
8012
|
+
this.#sniffDimensions(this.#src);
|
|
8223
8013
|
}
|
|
8224
8014
|
}
|
|
8015
|
+
|
|
8225
8016
|
if (name === "upload") {
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
this.innerHTML = this.#getInnerHTML();
|
|
8233
|
-
this.#updateRefs();
|
|
8234
|
-
}
|
|
8235
|
-
if (name === "size") {
|
|
8236
|
-
this.size = newValue;
|
|
8017
|
+
const on = newValue !== null && newValue !== "false";
|
|
8018
|
+
if (on && !this.#fileInput) {
|
|
8019
|
+
this.#createFileInput();
|
|
8020
|
+
} else if (!on) {
|
|
8021
|
+
this.#removeFileInput();
|
|
8022
|
+
}
|
|
8237
8023
|
}
|
|
8024
|
+
|
|
8238
8025
|
if (name === "aspect-ratio") {
|
|
8239
8026
|
if (newValue && newValue !== "auto") {
|
|
8240
8027
|
this.style.setProperty("--aspect-ratio", newValue);
|
|
8241
8028
|
} else if (!newValue) {
|
|
8242
8029
|
this.style.removeProperty("--aspect-ratio");
|
|
8030
|
+
} else if (newValue === "auto" && this.#src) {
|
|
8031
|
+
this.#sniffDimensions(this.#src);
|
|
8243
8032
|
}
|
|
8244
8033
|
}
|
|
8034
|
+
|
|
8245
8035
|
if (name === "fit") {
|
|
8246
8036
|
if (newValue) {
|
|
8247
8037
|
this.style.setProperty("--fit", newValue);
|
|
@@ -8249,12 +8039,13 @@ class FigImage extends HTMLElement {
|
|
|
8249
8039
|
this.style.removeProperty("--fit");
|
|
8250
8040
|
}
|
|
8251
8041
|
}
|
|
8042
|
+
|
|
8252
8043
|
if (name === "checkerboard") {
|
|
8253
|
-
if (this
|
|
8044
|
+
if (this.#chit) {
|
|
8254
8045
|
if (newValue !== null && newValue !== "false") {
|
|
8255
|
-
this
|
|
8046
|
+
this.#chit.setAttribute("checkerboard", "");
|
|
8256
8047
|
} else {
|
|
8257
|
-
this
|
|
8048
|
+
this.#chit.removeAttribute("checkerboard");
|
|
8258
8049
|
}
|
|
8259
8050
|
}
|
|
8260
8051
|
}
|
|
@@ -8264,7 +8055,7 @@ customElements.define("fig-image", FigImage);
|
|
|
8264
8055
|
|
|
8265
8056
|
/* File Upload Input */
|
|
8266
8057
|
class FigInputFile extends HTMLElement {
|
|
8267
|
-
static observedAttributes = ["accepts", "label", "disabled", "multiple"];
|
|
8058
|
+
static observedAttributes = ["accepts", "label", "disabled", "multiple", "variant", "url"];
|
|
8268
8059
|
|
|
8269
8060
|
#fileInput = null;
|
|
8270
8061
|
#filenameEl = null;
|
|
@@ -8277,10 +8068,24 @@ class FigInputFile extends HTMLElement {
|
|
|
8277
8068
|
return this.#files;
|
|
8278
8069
|
}
|
|
8279
8070
|
|
|
8071
|
+
get #urlFilename() {
|
|
8072
|
+
const url = this.getAttribute("url");
|
|
8073
|
+
if (!url) return "";
|
|
8074
|
+
try {
|
|
8075
|
+
const path = new URL(url, location.href).pathname;
|
|
8076
|
+
const name = path.split("/").pop();
|
|
8077
|
+
return name ? decodeURIComponent(name) : url;
|
|
8078
|
+
} catch {
|
|
8079
|
+
return url;
|
|
8080
|
+
}
|
|
8081
|
+
}
|
|
8082
|
+
|
|
8280
8083
|
get value() {
|
|
8281
|
-
if (
|
|
8282
|
-
|
|
8283
|
-
|
|
8084
|
+
if (this.#files && this.#files.length > 0) {
|
|
8085
|
+
if (this.#files.length === 1) return this.#files[0].name;
|
|
8086
|
+
return `${this.#files.length} files`;
|
|
8087
|
+
}
|
|
8088
|
+
return this.#urlFilename;
|
|
8284
8089
|
}
|
|
8285
8090
|
|
|
8286
8091
|
connectedCallback() {
|
|
@@ -8306,6 +8111,7 @@ class FigInputFile extends HTMLElement {
|
|
|
8306
8111
|
clear() {
|
|
8307
8112
|
this.#files = null;
|
|
8308
8113
|
if (this.#fileInput) this.#fileInput.value = "";
|
|
8114
|
+
this.removeAttribute("url");
|
|
8309
8115
|
this.#render();
|
|
8310
8116
|
this.#emitEvents();
|
|
8311
8117
|
}
|
|
@@ -8319,6 +8125,7 @@ class FigInputFile extends HTMLElement {
|
|
|
8319
8125
|
#onFileChange = () => {
|
|
8320
8126
|
if (this.#fileInput.files.length > 0) {
|
|
8321
8127
|
this.#files = this.#fileInput.files;
|
|
8128
|
+
this.removeAttribute("url");
|
|
8322
8129
|
this.#render();
|
|
8323
8130
|
this.#emitEvents();
|
|
8324
8131
|
}
|
|
@@ -8331,16 +8138,30 @@ class FigInputFile extends HTMLElement {
|
|
|
8331
8138
|
|
|
8332
8139
|
#onDragOver = (e) => {
|
|
8333
8140
|
e.preventDefault();
|
|
8334
|
-
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
|
+
}
|
|
8335
8148
|
};
|
|
8336
8149
|
|
|
8337
8150
|
#onDragLeave = () => {
|
|
8338
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
|
+
}
|
|
8339
8156
|
};
|
|
8340
8157
|
|
|
8341
8158
|
#onDrop = (e) => {
|
|
8342
8159
|
e.preventDefault();
|
|
8343
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
|
+
}
|
|
8344
8165
|
if (
|
|
8345
8166
|
this.hasAttribute("disabled") &&
|
|
8346
8167
|
this.getAttribute("disabled") !== "false"
|
|
@@ -8373,6 +8194,7 @@ class FigInputFile extends HTMLElement {
|
|
|
8373
8194
|
if (this.#fileInput) {
|
|
8374
8195
|
this.#fileInput.files = dt.files;
|
|
8375
8196
|
}
|
|
8197
|
+
this.removeAttribute("url");
|
|
8376
8198
|
this.#render();
|
|
8377
8199
|
this.#emitEvents();
|
|
8378
8200
|
};
|
|
@@ -8384,7 +8206,8 @@ class FigInputFile extends HTMLElement {
|
|
|
8384
8206
|
this.hasAttribute("disabled") &&
|
|
8385
8207
|
this.getAttribute("disabled") !== "false";
|
|
8386
8208
|
const multiple = this.hasAttribute("multiple");
|
|
8387
|
-
const
|
|
8209
|
+
const variant = this.getAttribute("variant") || "input";
|
|
8210
|
+
const hasFile = (this.#files && this.#files.length > 0) || !!this.getAttribute("url");
|
|
8388
8211
|
|
|
8389
8212
|
this.innerHTML = "";
|
|
8390
8213
|
|
|
@@ -8397,7 +8220,7 @@ class FigInputFile extends HTMLElement {
|
|
|
8397
8220
|
: "";
|
|
8398
8221
|
|
|
8399
8222
|
this.#uploadBtn = document.createElement("fig-button");
|
|
8400
|
-
this.#uploadBtn.setAttribute("variant",
|
|
8223
|
+
this.#uploadBtn.setAttribute("variant", variant);
|
|
8401
8224
|
this.#uploadBtn.setAttribute("type", "upload");
|
|
8402
8225
|
this.#uploadBtn.className = "fig-input-file-filename";
|
|
8403
8226
|
if (disabled) this.#uploadBtn.setAttribute("disabled", "");
|
|
@@ -8452,7 +8275,7 @@ class FigInputFile extends HTMLElement {
|
|
|
8452
8275
|
}
|
|
8453
8276
|
|
|
8454
8277
|
this.#uploadBtn = document.createElement("fig-button");
|
|
8455
|
-
this.#uploadBtn.setAttribute("variant",
|
|
8278
|
+
this.#uploadBtn.setAttribute("variant", variant);
|
|
8456
8279
|
this.#uploadBtn.setAttribute("type", "upload");
|
|
8457
8280
|
this.#uploadBtn.textContent = label;
|
|
8458
8281
|
if (disabled) this.#uploadBtn.setAttribute("disabled", "");
|
|
@@ -10561,628 +10384,183 @@ class FigInputJoystick extends HTMLElement {
|
|
|
10561
10384
|
|
|
10562
10385
|
customElements.define("fig-joystick", FigInputJoystick);
|
|
10563
10386
|
|
|
10564
|
-
/**
|
|
10565
|
-
* A custom angle chooser input element.
|
|
10566
|
-
* @attr {number} value - The current angle of the handle in degrees.
|
|
10567
|
-
* @attr {number} precision - The number of decimal places for the output.
|
|
10568
|
-
* @attr {boolean} text - Whether to display a text input for the angle value.
|
|
10569
|
-
* @attr {boolean} dial - Whether to display the circular dial control. Defaults to true.
|
|
10570
|
-
* @attr {number} adjacent - The adjacent value of the angle.
|
|
10571
|
-
* @attr {number} opposite - The opposite value of the angle.
|
|
10572
|
-
* @attr {boolean} rotations - Whether to display a rotation count (×N) when rotations > 1. Defaults to false.
|
|
10573
|
-
*/
|
|
10574
|
-
class FigInputAngle extends HTMLElement {
|
|
10575
|
-
// Private fields
|
|
10576
|
-
#adjacent;
|
|
10577
|
-
#opposite;
|
|
10578
|
-
#prevRawAngle = null;
|
|
10579
|
-
#boundHandleRawChange;
|
|
10580
|
-
#boundHandleMouseDown;
|
|
10581
|
-
#boundHandleTouchStart;
|
|
10582
|
-
#boundHandleKeyDown;
|
|
10583
|
-
#boundHandleKeyUp;
|
|
10584
|
-
#boundHandleAngleInput;
|
|
10585
|
-
|
|
10586
|
-
constructor() {
|
|
10587
|
-
super();
|
|
10588
|
-
|
|
10589
|
-
this.angle = 0;
|
|
10590
|
-
this.#adjacent = 1;
|
|
10591
|
-
this.#opposite = 0;
|
|
10592
|
-
this.isDragging = false;
|
|
10593
|
-
this.isShiftHeld = false;
|
|
10594
|
-
this.handle = null;
|
|
10595
|
-
this.angleInput = null;
|
|
10596
|
-
this.plane = null;
|
|
10597
|
-
this.units = "°";
|
|
10598
|
-
this.min = null;
|
|
10599
|
-
this.max = null;
|
|
10600
|
-
this.dial = true;
|
|
10601
|
-
this.showRotations = false;
|
|
10602
|
-
this.rotationSpan = null;
|
|
10603
|
-
|
|
10604
|
-
this.#boundHandleRawChange = this.#handleRawChange.bind(this);
|
|
10605
|
-
this.#boundHandleMouseDown = this.#handleMouseDown.bind(this);
|
|
10606
|
-
this.#boundHandleTouchStart = this.#handleTouchStart.bind(this);
|
|
10607
|
-
this.#boundHandleKeyDown = this.#handleKeyDown.bind(this);
|
|
10608
|
-
this.#boundHandleKeyUp = this.#handleKeyUp.bind(this);
|
|
10609
|
-
this.#boundHandleAngleInput = this.#handleAngleInput.bind(this);
|
|
10610
|
-
}
|
|
10611
10387
|
|
|
10388
|
+
// FigInputAngle moved to fig-lab.js
|
|
10389
|
+
// FigShimmer
|
|
10390
|
+
class FigShimmer extends HTMLElement {
|
|
10612
10391
|
connectedCallback() {
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
this.
|
|
10616
|
-
this.text = this.getAttribute("text") === "true";
|
|
10617
|
-
|
|
10618
|
-
let rawUnits = this.getAttribute("units") || "°";
|
|
10619
|
-
if (rawUnits === "deg") rawUnits = "°";
|
|
10620
|
-
this.units = rawUnits;
|
|
10621
|
-
|
|
10622
|
-
this.min = this.hasAttribute("min")
|
|
10623
|
-
? Number(this.getAttribute("min"))
|
|
10624
|
-
: null;
|
|
10625
|
-
this.max = this.hasAttribute("max")
|
|
10626
|
-
? Number(this.getAttribute("max"))
|
|
10627
|
-
: null;
|
|
10628
|
-
this.dial = this.#readBooleanAttribute("dial", true);
|
|
10629
|
-
this.showRotations = this.#readRotationsEnabled();
|
|
10630
|
-
|
|
10631
|
-
this.#render();
|
|
10632
|
-
this.#setupListeners();
|
|
10633
|
-
|
|
10634
|
-
this.#syncHandlePosition();
|
|
10635
|
-
if (this.text && this.angleInput) {
|
|
10636
|
-
this.angleInput.setAttribute(
|
|
10637
|
-
"value",
|
|
10638
|
-
this.angle.toFixed(this.precision),
|
|
10639
|
-
);
|
|
10640
|
-
}
|
|
10641
|
-
});
|
|
10642
|
-
}
|
|
10643
|
-
|
|
10644
|
-
disconnectedCallback() {
|
|
10645
|
-
this.#cleanupListeners();
|
|
10646
|
-
}
|
|
10647
|
-
|
|
10648
|
-
#render() {
|
|
10649
|
-
this.innerHTML = this.#getInnerHTML();
|
|
10650
|
-
}
|
|
10651
|
-
|
|
10652
|
-
#readBooleanAttribute(name, defaultValue = false) {
|
|
10653
|
-
const value = this.getAttribute(name);
|
|
10654
|
-
if (value === null) return defaultValue;
|
|
10655
|
-
const normalized = value.trim().toLowerCase();
|
|
10656
|
-
if (normalized === "" || normalized === "true") return true;
|
|
10657
|
-
if (normalized === "false") return false;
|
|
10658
|
-
return true;
|
|
10659
|
-
}
|
|
10660
|
-
|
|
10661
|
-
#readRotationsEnabled() {
|
|
10662
|
-
if (this.hasAttribute("rotations")) {
|
|
10663
|
-
return this.#readBooleanAttribute("rotations", false);
|
|
10664
|
-
}
|
|
10665
|
-
// Backward-compat alias
|
|
10666
|
-
if (this.hasAttribute("show-rotations")) {
|
|
10667
|
-
return this.#readBooleanAttribute("show-rotations", false);
|
|
10392
|
+
const duration = this.getAttribute("duration");
|
|
10393
|
+
if (duration) {
|
|
10394
|
+
this.style.setProperty("--shimmer-duration", duration);
|
|
10668
10395
|
}
|
|
10669
|
-
return false;
|
|
10670
10396
|
}
|
|
10671
10397
|
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
const minAttr = this.min !== null ? `min="${this.min}"` : "";
|
|
10675
|
-
const maxAttr = this.max !== null ? `max="${this.max}"` : "";
|
|
10676
|
-
return `
|
|
10677
|
-
${
|
|
10678
|
-
this.dial
|
|
10679
|
-
? `<div class="fig-input-angle-plane" tabindex="0">
|
|
10680
|
-
<div class="fig-input-angle-handle"></div>
|
|
10681
|
-
</div>`
|
|
10682
|
-
: ""
|
|
10683
|
-
}
|
|
10684
|
-
${
|
|
10685
|
-
this.text
|
|
10686
|
-
? `<fig-input-number
|
|
10687
|
-
name="angle"
|
|
10688
|
-
step="${step}"
|
|
10689
|
-
value="${this.angle}"
|
|
10690
|
-
${minAttr}
|
|
10691
|
-
${maxAttr}
|
|
10692
|
-
units="${this.units}">
|
|
10693
|
-
${this.showRotations ? `<span slot="append" class="fig-input-angle-rotations"></span>` : ""}
|
|
10694
|
-
</fig-input-number>`
|
|
10695
|
-
: ""
|
|
10696
|
-
}
|
|
10697
|
-
`;
|
|
10398
|
+
static get observedAttributes() {
|
|
10399
|
+
return ["duration", "playing"];
|
|
10698
10400
|
}
|
|
10699
10401
|
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
return Math.floor(degrees / 360);
|
|
10402
|
+
get playing() {
|
|
10403
|
+
return this.getAttribute("playing") !== "false";
|
|
10703
10404
|
}
|
|
10704
10405
|
|
|
10705
|
-
|
|
10706
|
-
if (
|
|
10707
|
-
|
|
10708
|
-
if (rotations > 1) {
|
|
10709
|
-
this.rotationSpan.textContent = `\u00d7${rotations}`;
|
|
10710
|
-
this.rotationSpan.style.display = "";
|
|
10406
|
+
set playing(value) {
|
|
10407
|
+
if (value) {
|
|
10408
|
+
this.removeAttribute("playing"); // Default is playing
|
|
10711
10409
|
} else {
|
|
10712
|
-
this.
|
|
10713
|
-
this.rotationSpan.style.display = "none";
|
|
10410
|
+
this.setAttribute("playing", "false");
|
|
10714
10411
|
}
|
|
10715
10412
|
}
|
|
10716
10413
|
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
return 0.01;
|
|
10721
|
-
case "turn":
|
|
10722
|
-
return 0.001;
|
|
10723
|
-
default:
|
|
10724
|
-
return 0.1;
|
|
10414
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
10415
|
+
if (name === "duration") {
|
|
10416
|
+
this.style.setProperty("--shimmer-duration", newValue || "1.5s");
|
|
10725
10417
|
}
|
|
10418
|
+
// playing is handled purely by CSS attribute selectors
|
|
10726
10419
|
}
|
|
10420
|
+
}
|
|
10421
|
+
customElements.define("fig-shimmer", FigShimmer);
|
|
10727
10422
|
|
|
10728
|
-
|
|
10423
|
+
// FigSkeleton
|
|
10424
|
+
class FigSkeleton extends FigShimmer {}
|
|
10425
|
+
customElements.define("fig-skeleton", FigSkeleton);
|
|
10729
10426
|
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
case "turn":
|
|
10735
|
-
return value * 360;
|
|
10736
|
-
default:
|
|
10737
|
-
return value;
|
|
10738
|
-
}
|
|
10427
|
+
// FigLayer
|
|
10428
|
+
class FigLayer extends HTMLElement {
|
|
10429
|
+
static get observedAttributes() {
|
|
10430
|
+
return ["open", "visible"];
|
|
10739
10431
|
}
|
|
10740
10432
|
|
|
10741
|
-
#
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
}
|
|
10433
|
+
#chevron = null;
|
|
10434
|
+
#boundHandleChevronClick = null;
|
|
10435
|
+
|
|
10436
|
+
connectedCallback() {
|
|
10437
|
+
// Use requestAnimationFrame to ensure child elements have rendered
|
|
10438
|
+
requestAnimationFrame(() => {
|
|
10439
|
+
this.#injectChevron();
|
|
10440
|
+
});
|
|
10750
10441
|
}
|
|
10751
10442
|
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
switch (fromUnit) {
|
|
10756
|
-
case "rad":
|
|
10757
|
-
degrees = (value * 180) / Math.PI;
|
|
10758
|
-
break;
|
|
10759
|
-
case "turn":
|
|
10760
|
-
degrees = value * 360;
|
|
10761
|
-
break;
|
|
10762
|
-
default:
|
|
10763
|
-
degrees = value;
|
|
10764
|
-
}
|
|
10765
|
-
// Convert from degrees to target
|
|
10766
|
-
switch (toUnit) {
|
|
10767
|
-
case "rad":
|
|
10768
|
-
return (degrees * Math.PI) / 180;
|
|
10769
|
-
case "turn":
|
|
10770
|
-
return degrees / 360;
|
|
10771
|
-
default:
|
|
10772
|
-
return degrees;
|
|
10443
|
+
disconnectedCallback() {
|
|
10444
|
+
if (this.#chevron && this.#boundHandleChevronClick) {
|
|
10445
|
+
this.#chevron.removeEventListener("click", this.#boundHandleChevronClick);
|
|
10773
10446
|
}
|
|
10774
10447
|
}
|
|
10775
10448
|
|
|
10776
|
-
|
|
10449
|
+
#injectChevron() {
|
|
10450
|
+
const row = this.querySelector(":scope > .fig-layer-row");
|
|
10451
|
+
if (!row) return;
|
|
10777
10452
|
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
this.plane = this.querySelector(".fig-input-angle-plane");
|
|
10781
|
-
this.angleInput = this.querySelector("fig-input-number[name='angle']");
|
|
10782
|
-
this.rotationSpan = this.querySelector(".fig-input-angle-rotations");
|
|
10783
|
-
this.#updateRotationDisplay();
|
|
10784
|
-
this.plane?.addEventListener("mousedown", this.#boundHandleMouseDown);
|
|
10785
|
-
this.plane?.addEventListener("touchstart", this.#boundHandleTouchStart);
|
|
10786
|
-
window.addEventListener("keydown", this.#boundHandleKeyDown);
|
|
10787
|
-
window.addEventListener("keyup", this.#boundHandleKeyUp);
|
|
10788
|
-
if (this.text && this.angleInput) {
|
|
10789
|
-
this.angleInput.addEventListener("input", this.#boundHandleAngleInput);
|
|
10790
|
-
}
|
|
10791
|
-
this.addEventListener("change", this.#boundHandleRawChange, true);
|
|
10792
|
-
}
|
|
10453
|
+
// Check if chevron already exists
|
|
10454
|
+
if (row.querySelector(".fig-layer-chevron")) return;
|
|
10793
10455
|
|
|
10794
|
-
|
|
10795
|
-
this.
|
|
10796
|
-
this.
|
|
10797
|
-
|
|
10798
|
-
window.removeEventListener("keyup", this.#boundHandleKeyUp);
|
|
10799
|
-
if (this.text && this.angleInput) {
|
|
10800
|
-
this.angleInput.removeEventListener("input", this.#boundHandleAngleInput);
|
|
10801
|
-
}
|
|
10802
|
-
this.removeEventListener("change", this.#boundHandleRawChange, true);
|
|
10803
|
-
}
|
|
10456
|
+
// Always create chevron element - CSS handles visibility via :has(fig-layer)
|
|
10457
|
+
this.#chevron = document.createElement("span");
|
|
10458
|
+
this.#chevron.className = "fig-layer-chevron";
|
|
10459
|
+
row.prepend(this.#chevron);
|
|
10804
10460
|
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
const raw = e.target.value;
|
|
10809
|
-
const match = raw.match(/^(-?\d*\.?\d+)\s*(turn|rad|deg|°)$/i);
|
|
10810
|
-
if (match) {
|
|
10811
|
-
const num = parseFloat(match[1]);
|
|
10812
|
-
let fromUnit = match[2].toLowerCase();
|
|
10813
|
-
if (fromUnit === "deg") fromUnit = "°";
|
|
10814
|
-
if (fromUnit !== this.units) {
|
|
10815
|
-
const converted = this.#convertAngle(num, fromUnit, this.units);
|
|
10816
|
-
e.target.value = String(converted);
|
|
10817
|
-
}
|
|
10818
|
-
}
|
|
10461
|
+
// Add click listener to chevron only
|
|
10462
|
+
this.#boundHandleChevronClick = this.#handleChevronClick.bind(this);
|
|
10463
|
+
this.#chevron.addEventListener("click", this.#boundHandleChevronClick);
|
|
10819
10464
|
}
|
|
10820
10465
|
|
|
10821
|
-
#
|
|
10466
|
+
#handleChevronClick(e) {
|
|
10822
10467
|
e.stopPropagation();
|
|
10823
|
-
this.
|
|
10824
|
-
this.#calculateAdjacentAndOpposite();
|
|
10825
|
-
this.#syncHandlePosition();
|
|
10826
|
-
this.#updateRotationDisplay();
|
|
10827
|
-
this.#emitInputEvent();
|
|
10828
|
-
this.#emitChangeEvent();
|
|
10468
|
+
this.open = !this.open;
|
|
10829
10469
|
}
|
|
10830
10470
|
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
const degrees = this.#toDegrees(this.angle);
|
|
10835
|
-
const radians = (degrees * Math.PI) / 180;
|
|
10836
|
-
this.#adjacent = Math.cos(radians);
|
|
10837
|
-
this.#opposite = Math.sin(radians);
|
|
10471
|
+
get open() {
|
|
10472
|
+
const attr = this.getAttribute("open");
|
|
10473
|
+
return attr !== null && attr !== "false";
|
|
10838
10474
|
}
|
|
10839
10475
|
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10476
|
+
set open(value) {
|
|
10477
|
+
const oldValue = this.open;
|
|
10478
|
+
if (value) {
|
|
10479
|
+
this.setAttribute("open", "true");
|
|
10480
|
+
} else {
|
|
10481
|
+
this.setAttribute("open", "false");
|
|
10482
|
+
}
|
|
10483
|
+
if (oldValue !== value) {
|
|
10484
|
+
this.dispatchEvent(
|
|
10485
|
+
new CustomEvent("openchange", {
|
|
10486
|
+
detail: { open: value },
|
|
10487
|
+
bubbles: true,
|
|
10488
|
+
}),
|
|
10489
|
+
);
|
|
10490
|
+
}
|
|
10844
10491
|
}
|
|
10845
10492
|
|
|
10846
|
-
|
|
10847
|
-
const
|
|
10848
|
-
|
|
10849
|
-
const centerY = rect.top + rect.height / 2;
|
|
10850
|
-
const deltaX = e.clientX - centerX;
|
|
10851
|
-
const deltaY = e.clientY - centerY;
|
|
10852
|
-
return (Math.atan2(deltaY, deltaX) * 180) / Math.PI;
|
|
10493
|
+
get visible() {
|
|
10494
|
+
const attr = this.getAttribute("visible");
|
|
10495
|
+
return attr !== "false";
|
|
10853
10496
|
}
|
|
10854
10497
|
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
normalizedAngle = this.#snapToIncrement(normalizedAngle);
|
|
10860
|
-
|
|
10861
|
-
const isBounded = this.min !== null || this.max !== null;
|
|
10862
|
-
|
|
10863
|
-
if (isBounded) {
|
|
10864
|
-
// Bounded: absolute position
|
|
10865
|
-
this.angle = this.#fromDegrees(normalizedAngle);
|
|
10498
|
+
set visible(value) {
|
|
10499
|
+
const oldValue = this.visible;
|
|
10500
|
+
if (value) {
|
|
10501
|
+
this.setAttribute("visible", "true");
|
|
10866
10502
|
} else {
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
this.angle += this.#fromDegrees(delta);
|
|
10877
|
-
} else {
|
|
10878
|
-
// Subsequent events — accumulate delta
|
|
10879
|
-
let delta = normalizedAngle - this.#prevRawAngle;
|
|
10880
|
-
if (delta > 180) delta -= 360;
|
|
10881
|
-
if (delta < -180) delta += 360;
|
|
10882
|
-
this.angle += this.#fromDegrees(delta);
|
|
10883
|
-
this.#prevRawAngle = normalizedAngle;
|
|
10884
|
-
}
|
|
10503
|
+
this.setAttribute("visible", "false");
|
|
10504
|
+
}
|
|
10505
|
+
if (oldValue !== value) {
|
|
10506
|
+
this.dispatchEvent(
|
|
10507
|
+
new CustomEvent("visibilitychange", {
|
|
10508
|
+
detail: { visible: value },
|
|
10509
|
+
bubbles: true,
|
|
10510
|
+
}),
|
|
10511
|
+
);
|
|
10885
10512
|
}
|
|
10513
|
+
}
|
|
10886
10514
|
|
|
10887
|
-
|
|
10515
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
10516
|
+
if (oldValue === newValue) return;
|
|
10888
10517
|
|
|
10889
|
-
|
|
10890
|
-
|
|
10891
|
-
this.
|
|
10518
|
+
if (name === "open") {
|
|
10519
|
+
const isOpen = newValue !== null && newValue !== "false";
|
|
10520
|
+
this.dispatchEvent(
|
|
10521
|
+
new CustomEvent("openchange", {
|
|
10522
|
+
detail: { open: isOpen },
|
|
10523
|
+
bubbles: true,
|
|
10524
|
+
}),
|
|
10525
|
+
);
|
|
10892
10526
|
}
|
|
10893
|
-
this.#updateRotationDisplay();
|
|
10894
10527
|
|
|
10895
|
-
|
|
10528
|
+
if (name === "visible") {
|
|
10529
|
+
const isVisible = newValue !== "false";
|
|
10530
|
+
this.dispatchEvent(
|
|
10531
|
+
new CustomEvent("visibilitychange", {
|
|
10532
|
+
detail: { visible: isVisible },
|
|
10533
|
+
bubbles: true,
|
|
10534
|
+
}),
|
|
10535
|
+
);
|
|
10536
|
+
}
|
|
10896
10537
|
}
|
|
10538
|
+
}
|
|
10539
|
+
customElements.define("fig-layer", FigLayer);
|
|
10897
10540
|
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
this.dispatchEvent(
|
|
10902
|
-
new CustomEvent("input", {
|
|
10903
|
-
bubbles: true,
|
|
10904
|
-
cancelable: true,
|
|
10905
|
-
detail: { value: this.value, angle: this.angle },
|
|
10906
|
-
}),
|
|
10907
|
-
);
|
|
10908
|
-
}
|
|
10909
|
-
|
|
10910
|
-
#emitChangeEvent() {
|
|
10911
|
-
this.dispatchEvent(
|
|
10912
|
-
new CustomEvent("change", {
|
|
10913
|
-
bubbles: true,
|
|
10914
|
-
cancelable: true,
|
|
10915
|
-
detail: { value: this.value, angle: this.angle },
|
|
10916
|
-
}),
|
|
10917
|
-
);
|
|
10918
|
-
}
|
|
10919
|
-
|
|
10920
|
-
// --- Handle position ---
|
|
10921
|
-
|
|
10922
|
-
#syncHandlePosition() {
|
|
10923
|
-
if (this.handle) {
|
|
10924
|
-
const degrees = this.#toDegrees(this.angle);
|
|
10925
|
-
const radians = (degrees * Math.PI) / 180;
|
|
10926
|
-
const radius = this.plane.offsetWidth / 2 - this.handle.offsetWidth / 2;
|
|
10927
|
-
const x = Math.cos(radians) * radius;
|
|
10928
|
-
const y = Math.sin(radians) * radius;
|
|
10929
|
-
this.handle.style.transform = `translate(${x}px, ${y}px)`;
|
|
10930
|
-
}
|
|
10931
|
-
}
|
|
10932
|
-
|
|
10933
|
-
// --- Mouse/Touch handlers ---
|
|
10934
|
-
|
|
10935
|
-
#handleMouseDown(e) {
|
|
10936
|
-
this.isDragging = true;
|
|
10937
|
-
this.#prevRawAngle = null;
|
|
10938
|
-
this.#updateAngle(e);
|
|
10939
|
-
|
|
10940
|
-
const handleMouseMove = (e) => {
|
|
10941
|
-
this.plane.classList.add("dragging");
|
|
10942
|
-
if (this.isDragging) this.#updateAngle(e);
|
|
10943
|
-
};
|
|
10944
|
-
|
|
10945
|
-
const handleMouseUp = () => {
|
|
10946
|
-
this.isDragging = false;
|
|
10947
|
-
this.#prevRawAngle = null;
|
|
10948
|
-
this.plane.classList.remove("dragging");
|
|
10949
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
10950
|
-
window.removeEventListener("mouseup", handleMouseUp);
|
|
10951
|
-
this.#emitChangeEvent();
|
|
10952
|
-
};
|
|
10953
|
-
|
|
10954
|
-
window.addEventListener("mousemove", handleMouseMove);
|
|
10955
|
-
window.addEventListener("mouseup", handleMouseUp);
|
|
10956
|
-
}
|
|
10957
|
-
|
|
10958
|
-
#handleTouchStart(e) {
|
|
10959
|
-
e.preventDefault();
|
|
10960
|
-
this.isDragging = true;
|
|
10961
|
-
this.#prevRawAngle = null;
|
|
10962
|
-
this.#updateAngle(e.touches[0]);
|
|
10963
|
-
|
|
10964
|
-
const handleTouchMove = (e) => {
|
|
10965
|
-
this.plane.classList.add("dragging");
|
|
10966
|
-
if (this.isDragging) this.#updateAngle(e.touches[0]);
|
|
10967
|
-
};
|
|
10968
|
-
|
|
10969
|
-
const handleTouchEnd = () => {
|
|
10970
|
-
this.isDragging = false;
|
|
10971
|
-
this.#prevRawAngle = null;
|
|
10972
|
-
this.plane.classList.remove("dragging");
|
|
10973
|
-
window.removeEventListener("touchmove", handleTouchMove);
|
|
10974
|
-
window.removeEventListener("touchend", handleTouchEnd);
|
|
10975
|
-
this.#emitChangeEvent();
|
|
10976
|
-
};
|
|
10977
|
-
|
|
10978
|
-
window.addEventListener("touchmove", handleTouchMove);
|
|
10979
|
-
window.addEventListener("touchend", handleTouchEnd);
|
|
10980
|
-
}
|
|
10981
|
-
|
|
10982
|
-
// --- Keyboard handlers ---
|
|
10983
|
-
|
|
10984
|
-
#handleKeyDown(e) {
|
|
10985
|
-
if (e.key === "Shift") this.isShiftHeld = true;
|
|
10986
|
-
}
|
|
10987
|
-
|
|
10988
|
-
#handleKeyUp(e) {
|
|
10989
|
-
if (e.key === "Shift") this.isShiftHeld = false;
|
|
10990
|
-
}
|
|
10991
|
-
|
|
10992
|
-
focus() {
|
|
10993
|
-
this.plane?.focus();
|
|
10994
|
-
}
|
|
10995
|
-
|
|
10996
|
-
// --- Attributes ---
|
|
10997
|
-
|
|
10998
|
-
static get observedAttributes() {
|
|
10999
|
-
return [
|
|
11000
|
-
"value",
|
|
11001
|
-
"precision",
|
|
11002
|
-
"text",
|
|
11003
|
-
"min",
|
|
11004
|
-
"max",
|
|
11005
|
-
"units",
|
|
11006
|
-
"dial",
|
|
11007
|
-
"rotations",
|
|
11008
|
-
"show-rotations",
|
|
11009
|
-
];
|
|
11010
|
-
}
|
|
11011
|
-
|
|
11012
|
-
get value() {
|
|
11013
|
-
return this.angle;
|
|
11014
|
-
}
|
|
11015
|
-
|
|
11016
|
-
get adjacent() {
|
|
11017
|
-
return this.#adjacent;
|
|
11018
|
-
}
|
|
11019
|
-
|
|
11020
|
-
get opposite() {
|
|
11021
|
-
return this.#opposite;
|
|
11022
|
-
}
|
|
11023
|
-
|
|
11024
|
-
set value(value) {
|
|
11025
|
-
if (isNaN(value)) {
|
|
11026
|
-
console.error("Invalid value: must be a number.");
|
|
11027
|
-
return;
|
|
11028
|
-
}
|
|
11029
|
-
this.angle = value;
|
|
11030
|
-
this.#calculateAdjacentAndOpposite();
|
|
11031
|
-
this.#syncHandlePosition();
|
|
11032
|
-
if (this.angleInput) {
|
|
11033
|
-
this.angleInput.setAttribute("value", this.angle.toFixed(this.precision));
|
|
11034
|
-
}
|
|
11035
|
-
this.#updateRotationDisplay();
|
|
11036
|
-
}
|
|
11037
|
-
|
|
11038
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
11039
|
-
switch (name) {
|
|
11040
|
-
case "value":
|
|
11041
|
-
if (this.isDragging) break;
|
|
11042
|
-
this.value = Number(newValue);
|
|
11043
|
-
break;
|
|
11044
|
-
case "precision":
|
|
11045
|
-
this.precision = parseInt(newValue);
|
|
11046
|
-
break;
|
|
11047
|
-
case "text":
|
|
11048
|
-
if (newValue !== oldValue) {
|
|
11049
|
-
this.text = newValue?.toLowerCase() === "true";
|
|
11050
|
-
if (this.isConnected) {
|
|
11051
|
-
this.#render();
|
|
11052
|
-
this.#setupListeners();
|
|
11053
|
-
this.#syncHandlePosition();
|
|
11054
|
-
}
|
|
11055
|
-
}
|
|
11056
|
-
break;
|
|
11057
|
-
case "dial":
|
|
11058
|
-
this.dial = this.#readBooleanAttribute("dial", true);
|
|
11059
|
-
if (this.isConnected) {
|
|
11060
|
-
this.#render();
|
|
11061
|
-
this.#setupListeners();
|
|
11062
|
-
this.#syncHandlePosition();
|
|
11063
|
-
}
|
|
11064
|
-
break;
|
|
11065
|
-
case "units": {
|
|
11066
|
-
let units = newValue || "°";
|
|
11067
|
-
if (units === "deg") units = "°";
|
|
11068
|
-
this.units = units;
|
|
11069
|
-
if (this.isConnected) {
|
|
11070
|
-
this.#render();
|
|
11071
|
-
this.#setupListeners();
|
|
11072
|
-
this.#syncHandlePosition();
|
|
11073
|
-
}
|
|
11074
|
-
break;
|
|
11075
|
-
}
|
|
11076
|
-
case "min":
|
|
11077
|
-
this.min = newValue !== null ? Number(newValue) : null;
|
|
11078
|
-
if (this.isConnected) {
|
|
11079
|
-
this.#render();
|
|
11080
|
-
this.#setupListeners();
|
|
11081
|
-
this.#syncHandlePosition();
|
|
11082
|
-
}
|
|
11083
|
-
break;
|
|
11084
|
-
case "max":
|
|
11085
|
-
this.max = newValue !== null ? Number(newValue) : null;
|
|
11086
|
-
if (this.isConnected) {
|
|
11087
|
-
this.#render();
|
|
11088
|
-
this.#setupListeners();
|
|
11089
|
-
this.#syncHandlePosition();
|
|
11090
|
-
}
|
|
11091
|
-
break;
|
|
11092
|
-
case "rotations":
|
|
11093
|
-
case "show-rotations":
|
|
11094
|
-
this.showRotations = this.#readRotationsEnabled();
|
|
11095
|
-
if (this.isConnected) {
|
|
11096
|
-
this.#render();
|
|
11097
|
-
this.#setupListeners();
|
|
11098
|
-
this.#syncHandlePosition();
|
|
11099
|
-
}
|
|
11100
|
-
break;
|
|
11101
|
-
}
|
|
11102
|
-
}
|
|
11103
|
-
}
|
|
11104
|
-
customElements.define("fig-input-angle", FigInputAngle);
|
|
11105
|
-
|
|
11106
|
-
// FigShimmer
|
|
11107
|
-
class FigShimmer extends HTMLElement {
|
|
11108
|
-
connectedCallback() {
|
|
11109
|
-
const duration = this.getAttribute("duration");
|
|
11110
|
-
if (duration) {
|
|
11111
|
-
this.style.setProperty("--shimmer-duration", duration);
|
|
11112
|
-
}
|
|
11113
|
-
}
|
|
11114
|
-
|
|
11115
|
-
static get observedAttributes() {
|
|
11116
|
-
return ["duration", "playing"];
|
|
11117
|
-
}
|
|
11118
|
-
|
|
11119
|
-
get playing() {
|
|
11120
|
-
return this.getAttribute("playing") !== "false";
|
|
11121
|
-
}
|
|
11122
|
-
|
|
11123
|
-
set playing(value) {
|
|
11124
|
-
if (value) {
|
|
11125
|
-
this.removeAttribute("playing"); // Default is playing
|
|
11126
|
-
} else {
|
|
11127
|
-
this.setAttribute("playing", "false");
|
|
11128
|
-
}
|
|
11129
|
-
}
|
|
11130
|
-
|
|
11131
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
11132
|
-
if (name === "duration") {
|
|
11133
|
-
this.style.setProperty("--shimmer-duration", newValue || "1.5s");
|
|
11134
|
-
}
|
|
11135
|
-
// playing is handled purely by CSS attribute selectors
|
|
11136
|
-
}
|
|
11137
|
-
}
|
|
11138
|
-
customElements.define("fig-shimmer", FigShimmer);
|
|
11139
|
-
|
|
11140
|
-
// FigSkeleton
|
|
11141
|
-
class FigSkeleton extends FigShimmer {}
|
|
11142
|
-
customElements.define("fig-skeleton", FigSkeleton);
|
|
11143
|
-
|
|
11144
|
-
// FigLayer
|
|
11145
|
-
class FigLayer extends HTMLElement {
|
|
11146
|
-
static get observedAttributes() {
|
|
11147
|
-
return ["open", "visible"];
|
|
11148
|
-
}
|
|
10541
|
+
// FigGroup
|
|
10542
|
+
class FigGroup extends HTMLElement {
|
|
10543
|
+
static observedAttributes = ["name", "collapsible"];
|
|
11149
10544
|
|
|
10545
|
+
#header = null;
|
|
11150
10546
|
#chevron = null;
|
|
11151
|
-
#boundHandleChevronClick = null;
|
|
11152
10547
|
|
|
11153
10548
|
connectedCallback() {
|
|
11154
|
-
|
|
11155
|
-
requestAnimationFrame(() => {
|
|
11156
|
-
this.#injectChevron();
|
|
11157
|
-
});
|
|
10549
|
+
requestAnimationFrame(() => this.#render());
|
|
11158
10550
|
}
|
|
11159
10551
|
|
|
11160
10552
|
disconnectedCallback() {
|
|
11161
|
-
if (this.#chevron
|
|
11162
|
-
this.#chevron.removeEventListener("click", this.#
|
|
10553
|
+
if (this.#chevron) {
|
|
10554
|
+
this.#chevron.removeEventListener("click", this.#handleToggle);
|
|
10555
|
+
}
|
|
10556
|
+
if (this.#header) {
|
|
10557
|
+
this.#header.removeEventListener("click", this.#handleToggle);
|
|
11163
10558
|
}
|
|
11164
10559
|
}
|
|
11165
10560
|
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
// Check if chevron already exists
|
|
11171
|
-
if (row.querySelector(".fig-layer-chevron")) return;
|
|
11172
|
-
|
|
11173
|
-
// Always create chevron element - CSS handles visibility via :has(fig-layer)
|
|
11174
|
-
this.#chevron = document.createElement("span");
|
|
11175
|
-
this.#chevron.className = "fig-layer-chevron";
|
|
11176
|
-
row.prepend(this.#chevron);
|
|
11177
|
-
|
|
11178
|
-
// Add click listener to chevron only
|
|
11179
|
-
this.#boundHandleChevronClick = this.#handleChevronClick.bind(this);
|
|
11180
|
-
this.#chevron.addEventListener("click", this.#boundHandleChevronClick);
|
|
11181
|
-
}
|
|
11182
|
-
|
|
11183
|
-
#handleChevronClick(e) {
|
|
11184
|
-
e.stopPropagation();
|
|
11185
|
-
this.open = !this.open;
|
|
10561
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
10562
|
+
if (oldValue === newValue) return;
|
|
10563
|
+
this.#render();
|
|
11186
10564
|
}
|
|
11187
10565
|
|
|
11188
10566
|
get open() {
|
|
@@ -11191,146 +10569,51 @@ class FigLayer extends HTMLElement {
|
|
|
11191
10569
|
}
|
|
11192
10570
|
|
|
11193
10571
|
set open(value) {
|
|
11194
|
-
const
|
|
10572
|
+
const was = this.open;
|
|
11195
10573
|
if (value) {
|
|
11196
10574
|
this.setAttribute("open", "true");
|
|
11197
10575
|
} else {
|
|
11198
10576
|
this.setAttribute("open", "false");
|
|
11199
10577
|
}
|
|
11200
|
-
if (
|
|
10578
|
+
if (was !== !!value) {
|
|
11201
10579
|
this.dispatchEvent(
|
|
11202
10580
|
new CustomEvent("openchange", {
|
|
11203
|
-
detail: { open: value },
|
|
10581
|
+
detail: { open: !!value },
|
|
11204
10582
|
bubbles: true,
|
|
11205
10583
|
}),
|
|
11206
10584
|
);
|
|
11207
10585
|
}
|
|
11208
10586
|
}
|
|
11209
10587
|
|
|
11210
|
-
|
|
11211
|
-
|
|
11212
|
-
|
|
11213
|
-
}
|
|
10588
|
+
#handleToggle = (e) => {
|
|
10589
|
+
e.stopPropagation();
|
|
10590
|
+
this.open = !this.open;
|
|
10591
|
+
};
|
|
11214
10592
|
|
|
11215
|
-
|
|
11216
|
-
const
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
} else {
|
|
11220
|
-
this.setAttribute("visible", "false");
|
|
11221
|
-
}
|
|
11222
|
-
if (oldValue !== value) {
|
|
11223
|
-
this.dispatchEvent(
|
|
11224
|
-
new CustomEvent("visibilitychange", {
|
|
11225
|
-
detail: { visible: value },
|
|
11226
|
-
bubbles: true,
|
|
11227
|
-
}),
|
|
11228
|
-
);
|
|
11229
|
-
}
|
|
11230
|
-
}
|
|
10593
|
+
#render() {
|
|
10594
|
+
const isCollapsible = this.hasAttribute("collapsible");
|
|
10595
|
+
const nameAttr = this.getAttribute("name");
|
|
10596
|
+
const label = nameAttr || (isCollapsible ? "Group" : null);
|
|
11231
10597
|
|
|
11232
|
-
|
|
11233
|
-
|
|
10598
|
+
// Check if user supplied their own fig-header
|
|
10599
|
+
const userHeader = this.querySelector(":scope > fig-header");
|
|
11234
10600
|
|
|
11235
|
-
if (
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
);
|
|
10601
|
+
if (!label && !isCollapsible && !userHeader) {
|
|
10602
|
+
if (this.#header && this.#header.dataset.generated) {
|
|
10603
|
+
this.#header.remove();
|
|
10604
|
+
this.#header = null;
|
|
10605
|
+
this.#chevron = null;
|
|
10606
|
+
}
|
|
10607
|
+
return;
|
|
11243
10608
|
}
|
|
11244
10609
|
|
|
11245
|
-
if (
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
);
|
|
11253
|
-
}
|
|
11254
|
-
}
|
|
11255
|
-
}
|
|
11256
|
-
customElements.define("fig-layer", FigLayer);
|
|
11257
|
-
|
|
11258
|
-
// FigGroup
|
|
11259
|
-
class FigGroup extends HTMLElement {
|
|
11260
|
-
static observedAttributes = ["name", "collapsible"];
|
|
11261
|
-
|
|
11262
|
-
#header = null;
|
|
11263
|
-
#chevron = null;
|
|
11264
|
-
|
|
11265
|
-
connectedCallback() {
|
|
11266
|
-
requestAnimationFrame(() => this.#render());
|
|
11267
|
-
}
|
|
11268
|
-
|
|
11269
|
-
disconnectedCallback() {
|
|
11270
|
-
if (this.#chevron) {
|
|
11271
|
-
this.#chevron.removeEventListener("click", this.#handleToggle);
|
|
11272
|
-
}
|
|
11273
|
-
if (this.#header) {
|
|
11274
|
-
this.#header.removeEventListener("click", this.#handleToggle);
|
|
11275
|
-
}
|
|
11276
|
-
}
|
|
11277
|
-
|
|
11278
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
11279
|
-
if (oldValue === newValue) return;
|
|
11280
|
-
this.#render();
|
|
11281
|
-
}
|
|
11282
|
-
|
|
11283
|
-
get open() {
|
|
11284
|
-
const attr = this.getAttribute("open");
|
|
11285
|
-
return attr !== null && attr !== "false";
|
|
11286
|
-
}
|
|
11287
|
-
|
|
11288
|
-
set open(value) {
|
|
11289
|
-
const was = this.open;
|
|
11290
|
-
if (value) {
|
|
11291
|
-
this.setAttribute("open", "true");
|
|
11292
|
-
} else {
|
|
11293
|
-
this.setAttribute("open", "false");
|
|
11294
|
-
}
|
|
11295
|
-
if (was !== !!value) {
|
|
11296
|
-
this.dispatchEvent(
|
|
11297
|
-
new CustomEvent("openchange", {
|
|
11298
|
-
detail: { open: !!value },
|
|
11299
|
-
bubbles: true,
|
|
11300
|
-
}),
|
|
11301
|
-
);
|
|
11302
|
-
}
|
|
11303
|
-
}
|
|
11304
|
-
|
|
11305
|
-
#handleToggle = (e) => {
|
|
11306
|
-
e.stopPropagation();
|
|
11307
|
-
this.open = !this.open;
|
|
11308
|
-
};
|
|
11309
|
-
|
|
11310
|
-
#render() {
|
|
11311
|
-
const isCollapsible = this.hasAttribute("collapsible");
|
|
11312
|
-
const nameAttr = this.getAttribute("name");
|
|
11313
|
-
const label = nameAttr || (isCollapsible ? "Group" : null);
|
|
11314
|
-
|
|
11315
|
-
// Check if user supplied their own fig-header
|
|
11316
|
-
const userHeader = this.querySelector(":scope > fig-header");
|
|
11317
|
-
|
|
11318
|
-
if (!label && !isCollapsible && !userHeader) {
|
|
11319
|
-
if (this.#header && this.#header.dataset.generated) {
|
|
11320
|
-
this.#header.remove();
|
|
11321
|
-
this.#header = null;
|
|
11322
|
-
this.#chevron = null;
|
|
11323
|
-
}
|
|
11324
|
-
return;
|
|
11325
|
-
}
|
|
11326
|
-
|
|
11327
|
-
if (userHeader) {
|
|
11328
|
-
this.#header = userHeader;
|
|
11329
|
-
} else if (!this.#header || !this.#header.dataset.generated) {
|
|
11330
|
-
this.#header = document.createElement("fig-header");
|
|
11331
|
-
this.#header.setAttribute("borderless", "");
|
|
11332
|
-
this.#header.dataset.generated = "true";
|
|
11333
|
-
this.prepend(this.#header);
|
|
10610
|
+
if (userHeader) {
|
|
10611
|
+
this.#header = userHeader;
|
|
10612
|
+
} else if (!this.#header || !this.#header.dataset.generated) {
|
|
10613
|
+
this.#header = document.createElement("fig-header");
|
|
10614
|
+
this.#header.setAttribute("borderless", "");
|
|
10615
|
+
this.#header.dataset.generated = "true";
|
|
10616
|
+
this.prepend(this.#header);
|
|
11334
10617
|
}
|
|
11335
10618
|
|
|
11336
10619
|
// Ensure h3 exists inside header
|
|
@@ -12414,9 +11697,9 @@ class FigFillPicker extends HTMLElement {
|
|
|
12414
11697
|
<option value="angular">Angular</option>
|
|
12415
11698
|
</fig-dropdown>
|
|
12416
11699
|
<fig-tooltip text="Rotate gradient">
|
|
12417
|
-
<fig-input-
|
|
11700
|
+
<fig-input-number class="fig-fill-picker-gradient-angle" value="${
|
|
12418
11701
|
(this.#gradient.angle - 90 + 360) % 360
|
|
12419
|
-
}"></fig-input-
|
|
11702
|
+
}" min="0" max="360" units="°" wrap></fig-input-number>
|
|
12420
11703
|
</fig-tooltip>
|
|
12421
11704
|
<div class="fig-fill-picker-gradient-center input-combo" style="display: none;">
|
|
12422
11705
|
<fig-input-number min="0" max="100" value="${
|
|
@@ -12513,7 +11796,6 @@ class FigFillPicker extends HTMLElement {
|
|
|
12513
11796
|
);
|
|
12514
11797
|
|
|
12515
11798
|
// Angle input
|
|
12516
|
-
// Convert from fig-input-angle coordinates (0° = right) to CSS coordinates (0° = up)
|
|
12517
11799
|
const angleInput = container.querySelector(
|
|
12518
11800
|
".fig-fill-picker-gradient-angle",
|
|
12519
11801
|
);
|
|
@@ -13700,1623 +12982,699 @@ class FigColorTip extends HTMLElement {
|
|
|
13700
12982
|
return `#${toHex(rgb[1])}${toHex(rgb[2])}${toHex(rgb[3])}`.toUpperCase();
|
|
13701
12983
|
}
|
|
13702
12984
|
} catch {
|
|
13703
|
-
// Fall through to default.
|
|
13704
|
-
}
|
|
13705
|
-
|
|
13706
|
-
return "#D9D9D9";
|
|
13707
|
-
}
|
|
13708
|
-
|
|
13709
|
-
#syncFromAttributes() {
|
|
13710
|
-
const rawAttr = this.getAttribute("value");
|
|
13711
|
-
const color = this.#normalizeColor(rawAttr);
|
|
13712
|
-
const alpha = this.#extractAlpha(rawAttr);
|
|
13713
|
-
if (rawAttr !== color && alpha >= 1) {
|
|
13714
|
-
this.setAttribute("value", color);
|
|
13715
|
-
return;
|
|
13716
|
-
}
|
|
13717
|
-
|
|
13718
|
-
if (this.#fillPicker) {
|
|
13719
|
-
const pickerVal =
|
|
13720
|
-
alpha < 1
|
|
13721
|
-
? { type: "solid", color, opacity: Math.round(alpha * 100) }
|
|
13722
|
-
: { type: "solid", color };
|
|
13723
|
-
this.#fillPicker.setAttribute("value", JSON.stringify(pickerVal));
|
|
13724
|
-
if (this.#alphaEnabled) {
|
|
13725
|
-
this.#fillPicker.removeAttribute("alpha");
|
|
13726
|
-
} else {
|
|
13727
|
-
this.#fillPicker.setAttribute("alpha", "false");
|
|
13728
|
-
}
|
|
13729
|
-
if (this.hasAttribute("disabled")) {
|
|
13730
|
-
this.#fillPicker.setAttribute("disabled", "");
|
|
13731
|
-
} else {
|
|
13732
|
-
this.#fillPicker.removeAttribute("disabled");
|
|
13733
|
-
}
|
|
13734
|
-
}
|
|
13735
|
-
|
|
13736
|
-
if (this.#chit) {
|
|
13737
|
-
this.#chit.setAttribute("background", color);
|
|
13738
|
-
if (this.hasAttribute("disabled")) {
|
|
13739
|
-
this.#chit.setAttribute("disabled", "");
|
|
13740
|
-
} else {
|
|
13741
|
-
this.#chit.removeAttribute("disabled");
|
|
13742
|
-
}
|
|
13743
|
-
}
|
|
13744
|
-
}
|
|
13745
|
-
|
|
13746
|
-
#updateColorFromPicker(detail, type) {
|
|
13747
|
-
const nextColor = this.#normalizeColor(detail?.color);
|
|
13748
|
-
const prevColor = this.#normalizeColor(this.getAttribute("value"));
|
|
13749
|
-
if (nextColor !== prevColor) {
|
|
13750
|
-
this.setAttribute("value", nextColor);
|
|
13751
|
-
} else {
|
|
13752
|
-
this.#syncFromAttributes();
|
|
13753
|
-
}
|
|
13754
|
-
|
|
13755
|
-
const eventDetail = { color: this.value };
|
|
13756
|
-
if (this.#alphaEnabled) {
|
|
13757
|
-
if (detail?.opacity !== undefined) {
|
|
13758
|
-
eventDetail.opacity = detail.opacity;
|
|
13759
|
-
} else if (detail?.alpha !== undefined) {
|
|
13760
|
-
eventDetail.opacity = Math.round(detail.alpha * 100);
|
|
13761
|
-
}
|
|
13762
|
-
}
|
|
13763
|
-
|
|
13764
|
-
this.dispatchEvent(
|
|
13765
|
-
new CustomEvent(type, {
|
|
13766
|
-
bubbles: true,
|
|
13767
|
-
cancelable: true,
|
|
13768
|
-
composed: true,
|
|
13769
|
-
detail: eventDetail,
|
|
13770
|
-
}),
|
|
13771
|
-
);
|
|
13772
|
-
}
|
|
13773
|
-
|
|
13774
|
-
#handlePickerInput(event) {
|
|
13775
|
-
event.stopPropagation();
|
|
13776
|
-
this.#updateColorFromPicker(event.detail, "input");
|
|
13777
|
-
}
|
|
13778
|
-
|
|
13779
|
-
#handlePickerChange(event) {
|
|
13780
|
-
event.stopPropagation();
|
|
13781
|
-
this.#updateColorFromPicker(event.detail, "change");
|
|
13782
|
-
}
|
|
13783
|
-
|
|
13784
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
13785
|
-
if (oldValue === newValue) return;
|
|
13786
|
-
if (!this.isConnected) return;
|
|
13787
|
-
|
|
13788
|
-
switch (name) {
|
|
13789
|
-
case "control":
|
|
13790
|
-
this.#render();
|
|
13791
|
-
break;
|
|
13792
|
-
case "value":
|
|
13793
|
-
case "selected":
|
|
13794
|
-
case "disabled":
|
|
13795
|
-
this.#syncFromAttributes();
|
|
13796
|
-
break;
|
|
13797
|
-
}
|
|
13798
|
-
}
|
|
13799
|
-
|
|
13800
|
-
get value() {
|
|
13801
|
-
return this.#normalizeColor(this.getAttribute("value"));
|
|
13802
|
-
}
|
|
13803
|
-
set value(value) {
|
|
13804
|
-
if (value === null || value === undefined || value === "") {
|
|
13805
|
-
this.removeAttribute("value");
|
|
13806
|
-
return;
|
|
13807
|
-
}
|
|
13808
|
-
this.setAttribute("value", this.#normalizeColor(value));
|
|
13809
|
-
}
|
|
13810
|
-
|
|
13811
|
-
get selected() {
|
|
13812
|
-
return this.hasAttribute("selected");
|
|
13813
|
-
}
|
|
13814
|
-
set selected(value) {
|
|
13815
|
-
this.toggleAttribute("selected", Boolean(value));
|
|
13816
|
-
}
|
|
13817
|
-
|
|
13818
|
-
get disabled() {
|
|
13819
|
-
return this.hasAttribute("disabled");
|
|
13820
|
-
}
|
|
13821
|
-
set disabled(value) {
|
|
13822
|
-
this.toggleAttribute("disabled", Boolean(value));
|
|
13823
|
-
}
|
|
13824
|
-
}
|
|
13825
|
-
customElements.define("fig-color-tip", FigColorTip);
|
|
13826
|
-
|
|
13827
|
-
/* Choice */
|
|
13828
|
-
/**
|
|
13829
|
-
* A generic choice container for use within FigChooser.
|
|
13830
|
-
* @attr {string} value - Identifier for this choice
|
|
13831
|
-
* @attr {boolean} selected - Whether this choice is currently selected
|
|
13832
|
-
* @attr {boolean} disabled - Whether this choice is disabled
|
|
13833
|
-
*/
|
|
13834
|
-
class FigChoice extends HTMLElement {
|
|
13835
|
-
static get observedAttributes() {
|
|
13836
|
-
return ["selected", "disabled"];
|
|
13837
|
-
}
|
|
13838
|
-
|
|
13839
|
-
connectedCallback() {
|
|
13840
|
-
this.setAttribute("role", "option");
|
|
13841
|
-
if (!this.hasAttribute("tabindex")) {
|
|
13842
|
-
this.setAttribute("tabindex", "0");
|
|
13843
|
-
}
|
|
13844
|
-
this.setAttribute(
|
|
13845
|
-
"aria-selected",
|
|
13846
|
-
this.hasAttribute("selected") ? "true" : "false",
|
|
13847
|
-
);
|
|
13848
|
-
if (this.hasAttribute("disabled")) {
|
|
13849
|
-
this.setAttribute("aria-disabled", "true");
|
|
13850
|
-
}
|
|
13851
|
-
}
|
|
13852
|
-
|
|
13853
|
-
attributeChangedCallback(name) {
|
|
13854
|
-
if (name === "selected") {
|
|
13855
|
-
this.setAttribute(
|
|
13856
|
-
"aria-selected",
|
|
13857
|
-
this.hasAttribute("selected") ? "true" : "false",
|
|
13858
|
-
);
|
|
13859
|
-
}
|
|
13860
|
-
if (name === "disabled") {
|
|
13861
|
-
const isDisabled =
|
|
13862
|
-
this.hasAttribute("disabled") &&
|
|
13863
|
-
this.getAttribute("disabled") !== "false";
|
|
13864
|
-
if (isDisabled) {
|
|
13865
|
-
this.setAttribute("aria-disabled", "true");
|
|
13866
|
-
this.setAttribute("tabindex", "-1");
|
|
13867
|
-
} else {
|
|
13868
|
-
this.removeAttribute("aria-disabled");
|
|
13869
|
-
this.setAttribute("tabindex", "0");
|
|
13870
|
-
}
|
|
13871
|
-
}
|
|
13872
|
-
}
|
|
13873
|
-
}
|
|
13874
|
-
customElements.define("fig-choice", FigChoice);
|
|
13875
|
-
|
|
13876
|
-
/* Chooser */
|
|
13877
|
-
/**
|
|
13878
|
-
* A selection controller for a list of choice elements.
|
|
13879
|
-
* @attr {string} choice-element - CSS selector for child choices (default: "fig-choice")
|
|
13880
|
-
* @attr {string} layout - Layout mode: "vertical" (default), "horizontal", "grid"
|
|
13881
|
-
* @attr {string} value - Value of the currently selected choice
|
|
13882
|
-
* @attr {boolean} disabled - Whether the chooser is disabled
|
|
13883
|
-
*/
|
|
13884
|
-
class FigChooser extends HTMLElement {
|
|
13885
|
-
#selectedChoice = null;
|
|
13886
|
-
#boundHandleClick = this.#handleClick.bind(this);
|
|
13887
|
-
#boundHandleKeyDown = this.#handleKeyDown.bind(this);
|
|
13888
|
-
#boundSyncOverflow = this.#syncOverflow.bind(this);
|
|
13889
|
-
#mutationObserver = null;
|
|
13890
|
-
#resizeObserver = null;
|
|
13891
|
-
#navStart = null;
|
|
13892
|
-
#navEnd = null;
|
|
13893
|
-
#dragState = null;
|
|
13894
|
-
|
|
13895
|
-
constructor() {
|
|
13896
|
-
super();
|
|
13897
|
-
}
|
|
13898
|
-
|
|
13899
|
-
static get observedAttributes() {
|
|
13900
|
-
return [
|
|
13901
|
-
"value",
|
|
13902
|
-
"disabled",
|
|
13903
|
-
"choice-element",
|
|
13904
|
-
"drag",
|
|
13905
|
-
"overflow",
|
|
13906
|
-
"loop",
|
|
13907
|
-
"padding",
|
|
13908
|
-
];
|
|
13909
|
-
}
|
|
13910
|
-
|
|
13911
|
-
get #overflowMode() {
|
|
13912
|
-
return this.getAttribute("overflow") === "scrollbar"
|
|
13913
|
-
? "scrollbar"
|
|
13914
|
-
: "buttons";
|
|
13915
|
-
}
|
|
13916
|
-
|
|
13917
|
-
get #dragEnabled() {
|
|
13918
|
-
const attr = this.getAttribute("drag");
|
|
13919
|
-
return attr === null || attr !== "false";
|
|
13920
|
-
}
|
|
13921
|
-
|
|
13922
|
-
get #choiceSelector() {
|
|
13923
|
-
return this.getAttribute("choice-element") || "fig-choice";
|
|
13924
|
-
}
|
|
13925
|
-
|
|
13926
|
-
get choices() {
|
|
13927
|
-
return Array.from(this.querySelectorAll(this.#choiceSelector));
|
|
13928
|
-
}
|
|
13929
|
-
|
|
13930
|
-
get selectedChoice() {
|
|
13931
|
-
return this.#selectedChoice;
|
|
13932
|
-
}
|
|
13933
|
-
|
|
13934
|
-
set selectedChoice(element) {
|
|
13935
|
-
if (element && !this.contains(element)) return;
|
|
13936
|
-
const choices = this.choices;
|
|
13937
|
-
for (const choice of choices) {
|
|
13938
|
-
const shouldSelect = choice === element;
|
|
13939
|
-
const isSelected = choice.hasAttribute("selected");
|
|
13940
|
-
if (shouldSelect && !isSelected) {
|
|
13941
|
-
choice.setAttribute("selected", "");
|
|
13942
|
-
} else if (!shouldSelect && isSelected) {
|
|
13943
|
-
choice.removeAttribute("selected");
|
|
13944
|
-
}
|
|
13945
|
-
}
|
|
13946
|
-
this.#selectedChoice = element;
|
|
13947
|
-
const val = element?.getAttribute("value") ?? "";
|
|
13948
|
-
if (this.getAttribute("value") !== val) {
|
|
13949
|
-
if (val) {
|
|
13950
|
-
this.setAttribute("value", val);
|
|
13951
|
-
}
|
|
13952
|
-
}
|
|
13953
|
-
this.#scrollToChoice(element);
|
|
13954
|
-
}
|
|
13955
|
-
|
|
13956
|
-
get value() {
|
|
13957
|
-
return this.#selectedChoice?.getAttribute("value") ?? "";
|
|
13958
|
-
}
|
|
13959
|
-
|
|
13960
|
-
set value(val) {
|
|
13961
|
-
if (val === null || val === undefined || val === "") return;
|
|
13962
|
-
this.setAttribute("value", String(val));
|
|
13963
|
-
}
|
|
13964
|
-
|
|
13965
|
-
connectedCallback() {
|
|
13966
|
-
this.setAttribute("role", "listbox");
|
|
13967
|
-
this.addEventListener("click", this.#boundHandleClick);
|
|
13968
|
-
this.addEventListener("keydown", this.#boundHandleKeyDown);
|
|
13969
|
-
this.addEventListener("scroll", this.#boundSyncOverflow);
|
|
13970
|
-
this.#applyOverflowMode();
|
|
13971
|
-
this.#setupDrag();
|
|
13972
|
-
this.#startObserver();
|
|
13973
|
-
this.#startResizeObserver();
|
|
13974
|
-
|
|
13975
|
-
requestAnimationFrame(() => {
|
|
13976
|
-
this.#syncSelection();
|
|
13977
|
-
this.#syncOverflow();
|
|
13978
|
-
});
|
|
13979
|
-
}
|
|
13980
|
-
|
|
13981
|
-
disconnectedCallback() {
|
|
13982
|
-
this.removeEventListener("click", this.#boundHandleClick);
|
|
13983
|
-
this.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
13984
|
-
this.removeEventListener("scroll", this.#boundSyncOverflow);
|
|
13985
|
-
this.#teardownDrag();
|
|
13986
|
-
this.#mutationObserver?.disconnect();
|
|
13987
|
-
this.#mutationObserver = null;
|
|
13988
|
-
this.#resizeObserver?.disconnect();
|
|
13989
|
-
this.#resizeObserver = null;
|
|
13990
|
-
this.#removeNavButtons();
|
|
13991
|
-
}
|
|
13992
|
-
|
|
13993
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
13994
|
-
if (name === "value" && newValue !== oldValue && newValue) {
|
|
13995
|
-
this.#selectByValue(newValue);
|
|
13996
|
-
}
|
|
13997
|
-
if (name === "disabled") {
|
|
13998
|
-
const isDisabled = newValue !== null && newValue !== "false";
|
|
13999
|
-
const choices = this.choices;
|
|
14000
|
-
for (const choice of choices) {
|
|
14001
|
-
if (isDisabled) {
|
|
14002
|
-
choice.setAttribute("aria-disabled", "true");
|
|
14003
|
-
choice.setAttribute("tabindex", "-1");
|
|
14004
|
-
} else {
|
|
14005
|
-
choice.removeAttribute("aria-disabled");
|
|
14006
|
-
choice.setAttribute("tabindex", "0");
|
|
14007
|
-
}
|
|
14008
|
-
}
|
|
14009
|
-
}
|
|
14010
|
-
if (name === "choice-element") {
|
|
14011
|
-
requestAnimationFrame(() => this.#syncSelection());
|
|
14012
|
-
}
|
|
14013
|
-
if (name === "drag") {
|
|
14014
|
-
if (this.#dragEnabled) {
|
|
14015
|
-
this.#setupDrag();
|
|
14016
|
-
} else {
|
|
14017
|
-
this.#teardownDrag();
|
|
14018
|
-
}
|
|
14019
|
-
}
|
|
14020
|
-
if (name === "overflow") {
|
|
14021
|
-
this.#applyOverflowMode();
|
|
14022
|
-
}
|
|
14023
|
-
}
|
|
14024
|
-
|
|
14025
|
-
#syncSelection() {
|
|
14026
|
-
const choices = this.choices;
|
|
14027
|
-
if (!choices.length) {
|
|
14028
|
-
this.#selectedChoice = null;
|
|
14029
|
-
return;
|
|
14030
|
-
}
|
|
14031
|
-
|
|
14032
|
-
const valueAttr = this.getAttribute("value");
|
|
14033
|
-
if (valueAttr && this.#selectByValue(valueAttr)) return;
|
|
14034
|
-
|
|
14035
|
-
const alreadySelected = choices.find((c) => c.hasAttribute("selected"));
|
|
14036
|
-
if (alreadySelected) {
|
|
14037
|
-
this.selectedChoice = alreadySelected;
|
|
14038
|
-
return;
|
|
14039
|
-
}
|
|
14040
|
-
|
|
14041
|
-
this.selectedChoice = choices[0];
|
|
14042
|
-
}
|
|
14043
|
-
|
|
14044
|
-
#selectByValue(value) {
|
|
14045
|
-
const choices = this.choices;
|
|
14046
|
-
for (const choice of choices) {
|
|
14047
|
-
if (choice.getAttribute("value") === value) {
|
|
14048
|
-
this.selectedChoice = choice;
|
|
14049
|
-
return true;
|
|
14050
|
-
}
|
|
14051
|
-
}
|
|
14052
|
-
return false;
|
|
14053
|
-
}
|
|
14054
|
-
|
|
14055
|
-
#findChoiceFromTarget(target) {
|
|
14056
|
-
const selector = this.#choiceSelector;
|
|
14057
|
-
let el = target;
|
|
14058
|
-
while (el && el !== this) {
|
|
14059
|
-
if (el.matches(selector)) return el;
|
|
14060
|
-
el = el.parentElement;
|
|
14061
|
-
}
|
|
14062
|
-
return null;
|
|
14063
|
-
}
|
|
14064
|
-
|
|
14065
|
-
#handleClick(event) {
|
|
14066
|
-
if (
|
|
14067
|
-
this.hasAttribute("disabled") &&
|
|
14068
|
-
this.getAttribute("disabled") !== "false"
|
|
14069
|
-
)
|
|
14070
|
-
return;
|
|
14071
|
-
const choice = this.#findChoiceFromTarget(event.target);
|
|
14072
|
-
if (!choice) return;
|
|
14073
|
-
if (
|
|
14074
|
-
choice.hasAttribute("disabled") &&
|
|
14075
|
-
choice.getAttribute("disabled") !== "false"
|
|
14076
|
-
)
|
|
14077
|
-
return;
|
|
14078
|
-
this.selectedChoice = choice;
|
|
14079
|
-
this.#emitEvents();
|
|
14080
|
-
}
|
|
14081
|
-
|
|
14082
|
-
#handleKeyDown(event) {
|
|
14083
|
-
if (
|
|
14084
|
-
this.hasAttribute("disabled") &&
|
|
14085
|
-
this.getAttribute("disabled") !== "false"
|
|
14086
|
-
)
|
|
14087
|
-
return;
|
|
14088
|
-
const choices = this.choices.filter(
|
|
14089
|
-
(c) =>
|
|
14090
|
-
!c.hasAttribute("disabled") || c.getAttribute("disabled") === "false",
|
|
14091
|
-
);
|
|
14092
|
-
if (!choices.length) return;
|
|
14093
|
-
const currentIndex = choices.indexOf(this.#selectedChoice);
|
|
14094
|
-
let nextIndex = currentIndex;
|
|
14095
|
-
|
|
14096
|
-
const loop = this.hasAttribute("loop");
|
|
14097
|
-
|
|
14098
|
-
switch (event.key) {
|
|
14099
|
-
case "ArrowDown":
|
|
14100
|
-
case "ArrowRight":
|
|
14101
|
-
event.preventDefault();
|
|
14102
|
-
if (currentIndex < choices.length - 1) {
|
|
14103
|
-
nextIndex = currentIndex + 1;
|
|
14104
|
-
} else if (loop) {
|
|
14105
|
-
nextIndex = 0;
|
|
14106
|
-
}
|
|
14107
|
-
break;
|
|
14108
|
-
case "ArrowUp":
|
|
14109
|
-
case "ArrowLeft":
|
|
14110
|
-
event.preventDefault();
|
|
14111
|
-
if (currentIndex > 0) {
|
|
14112
|
-
nextIndex = currentIndex - 1;
|
|
14113
|
-
} else if (loop) {
|
|
14114
|
-
nextIndex = choices.length - 1;
|
|
14115
|
-
}
|
|
14116
|
-
break;
|
|
14117
|
-
case "Home":
|
|
14118
|
-
event.preventDefault();
|
|
14119
|
-
nextIndex = 0;
|
|
14120
|
-
break;
|
|
14121
|
-
case "End":
|
|
14122
|
-
event.preventDefault();
|
|
14123
|
-
nextIndex = choices.length - 1;
|
|
14124
|
-
break;
|
|
14125
|
-
case "Enter":
|
|
14126
|
-
case " ":
|
|
14127
|
-
event.preventDefault();
|
|
14128
|
-
if (document.activeElement?.matches(this.#choiceSelector)) {
|
|
14129
|
-
const focused = this.#findChoiceFromTarget(document.activeElement);
|
|
14130
|
-
if (focused && focused !== this.#selectedChoice) {
|
|
14131
|
-
this.selectedChoice = focused;
|
|
14132
|
-
this.#emitEvents();
|
|
14133
|
-
}
|
|
14134
|
-
}
|
|
14135
|
-
return;
|
|
14136
|
-
default:
|
|
14137
|
-
return;
|
|
14138
|
-
}
|
|
14139
|
-
|
|
14140
|
-
if (nextIndex !== currentIndex && choices[nextIndex]) {
|
|
14141
|
-
this.selectedChoice = choices[nextIndex];
|
|
14142
|
-
choices[nextIndex].focus();
|
|
14143
|
-
this.#emitEvents();
|
|
14144
|
-
}
|
|
14145
|
-
}
|
|
14146
|
-
|
|
14147
|
-
#emitEvents() {
|
|
14148
|
-
const val = this.value;
|
|
14149
|
-
this.dispatchEvent(
|
|
14150
|
-
new CustomEvent("input", { detail: val, bubbles: true }),
|
|
14151
|
-
);
|
|
14152
|
-
this.dispatchEvent(
|
|
14153
|
-
new CustomEvent("change", { detail: val, bubbles: true }),
|
|
14154
|
-
);
|
|
14155
|
-
}
|
|
14156
|
-
|
|
14157
|
-
#syncOverflow() {
|
|
14158
|
-
if (this.#overflowMode === "scrollbar") return;
|
|
14159
|
-
const isHorizontal = this.getAttribute("layout") === "horizontal";
|
|
14160
|
-
const threshold = 2;
|
|
14161
|
-
|
|
14162
|
-
if (isHorizontal) {
|
|
14163
|
-
const atStart = this.scrollLeft <= threshold;
|
|
14164
|
-
const atEnd =
|
|
14165
|
-
this.scrollLeft + this.clientWidth >= this.scrollWidth - threshold;
|
|
14166
|
-
this.classList.toggle("overflow-start", !atStart);
|
|
14167
|
-
this.classList.toggle("overflow-end", !atEnd);
|
|
14168
|
-
} else {
|
|
14169
|
-
const atStart = this.scrollTop <= threshold;
|
|
14170
|
-
const atEnd =
|
|
14171
|
-
this.scrollTop + this.clientHeight >= this.scrollHeight - threshold;
|
|
14172
|
-
this.classList.toggle("overflow-start", !atStart);
|
|
14173
|
-
this.classList.toggle("overflow-end", !atEnd);
|
|
14174
|
-
}
|
|
14175
|
-
}
|
|
14176
|
-
|
|
14177
|
-
#startResizeObserver() {
|
|
14178
|
-
this.#resizeObserver?.disconnect();
|
|
14179
|
-
this.#resizeObserver = new ResizeObserver(() => {
|
|
14180
|
-
this.#syncOverflow();
|
|
14181
|
-
});
|
|
14182
|
-
this.#resizeObserver.observe(this);
|
|
14183
|
-
}
|
|
14184
|
-
|
|
14185
|
-
#setupDrag() {
|
|
14186
|
-
if (this.#dragState?.bound) return;
|
|
14187
|
-
if (!this.#dragEnabled) return;
|
|
14188
|
-
|
|
14189
|
-
const onPointerDown = (e) => {
|
|
14190
|
-
if (e.button !== 0) return;
|
|
14191
|
-
const isHorizontal = this.getAttribute("layout") === "horizontal";
|
|
14192
|
-
const hasOverflow = isHorizontal
|
|
14193
|
-
? this.scrollWidth > this.clientWidth
|
|
14194
|
-
: this.scrollHeight > this.clientHeight;
|
|
14195
|
-
if (!hasOverflow) return;
|
|
14196
|
-
|
|
14197
|
-
this.#dragState.active = true;
|
|
14198
|
-
this.#dragState.didDrag = false;
|
|
14199
|
-
this.#dragState.startX = e.clientX;
|
|
14200
|
-
this.#dragState.startY = e.clientY;
|
|
14201
|
-
this.#dragState.scrollLeft = this.scrollLeft;
|
|
14202
|
-
this.#dragState.scrollTop = this.scrollTop;
|
|
14203
|
-
this.style.cursor = "grab";
|
|
14204
|
-
this.style.userSelect = "none";
|
|
14205
|
-
};
|
|
14206
|
-
|
|
14207
|
-
const onPointerMove = (e) => {
|
|
14208
|
-
if (!this.#dragState.active) return;
|
|
14209
|
-
const isHorizontal = this.getAttribute("layout") === "horizontal";
|
|
14210
|
-
const dx = e.clientX - this.#dragState.startX;
|
|
14211
|
-
const dy = e.clientY - this.#dragState.startY;
|
|
14212
|
-
|
|
14213
|
-
if (!this.#dragState.didDrag && Math.abs(isHorizontal ? dx : dy) > 3) {
|
|
14214
|
-
this.#dragState.didDrag = true;
|
|
14215
|
-
this.style.cursor = "grabbing";
|
|
14216
|
-
this.setPointerCapture(e.pointerId);
|
|
14217
|
-
}
|
|
14218
|
-
|
|
14219
|
-
if (!this.#dragState.didDrag) return;
|
|
14220
|
-
|
|
14221
|
-
if (isHorizontal) {
|
|
14222
|
-
this.scrollLeft = this.#dragState.scrollLeft - dx;
|
|
14223
|
-
} else {
|
|
14224
|
-
this.scrollTop = this.#dragState.scrollTop - dy;
|
|
14225
|
-
}
|
|
14226
|
-
};
|
|
14227
|
-
|
|
14228
|
-
const onPointerUp = (e) => {
|
|
14229
|
-
if (!this.#dragState.active) return;
|
|
14230
|
-
const wasDrag = this.#dragState.didDrag;
|
|
14231
|
-
this.#dragState.active = false;
|
|
14232
|
-
this.#dragState.didDrag = false;
|
|
14233
|
-
this.style.cursor = "";
|
|
14234
|
-
this.style.userSelect = "";
|
|
14235
|
-
if (e.pointerId !== undefined) {
|
|
14236
|
-
try {
|
|
14237
|
-
this.releasePointerCapture(e.pointerId);
|
|
14238
|
-
} catch {}
|
|
14239
|
-
}
|
|
14240
|
-
if (wasDrag) {
|
|
14241
|
-
e.preventDefault();
|
|
14242
|
-
e.stopPropagation();
|
|
14243
|
-
}
|
|
14244
|
-
};
|
|
14245
|
-
|
|
14246
|
-
const onClick = (e) => {
|
|
14247
|
-
if (this.#dragState?.suppressClick) {
|
|
14248
|
-
e.stopPropagation();
|
|
14249
|
-
e.preventDefault();
|
|
14250
|
-
this.#dragState.suppressClick = false;
|
|
14251
|
-
}
|
|
14252
|
-
};
|
|
14253
|
-
|
|
14254
|
-
const onPointerUpCapture = (e) => {
|
|
14255
|
-
if (this.#dragState?.didDrag) {
|
|
14256
|
-
this.#dragState.suppressClick = true;
|
|
14257
|
-
setTimeout(() => {
|
|
14258
|
-
if (this.#dragState) this.#dragState.suppressClick = false;
|
|
14259
|
-
}, 0);
|
|
14260
|
-
}
|
|
14261
|
-
};
|
|
14262
|
-
|
|
14263
|
-
this.#dragState = {
|
|
14264
|
-
active: false,
|
|
14265
|
-
didDrag: false,
|
|
14266
|
-
suppressClick: false,
|
|
14267
|
-
startX: 0,
|
|
14268
|
-
startY: 0,
|
|
14269
|
-
scrollLeft: 0,
|
|
14270
|
-
scrollTop: 0,
|
|
14271
|
-
bound: true,
|
|
14272
|
-
onPointerDown,
|
|
14273
|
-
onPointerMove,
|
|
14274
|
-
onPointerUp,
|
|
14275
|
-
onClick,
|
|
14276
|
-
onPointerUpCapture,
|
|
14277
|
-
};
|
|
14278
|
-
|
|
14279
|
-
this.addEventListener("pointerdown", onPointerDown);
|
|
14280
|
-
window.addEventListener("pointermove", onPointerMove);
|
|
14281
|
-
window.addEventListener("pointerup", onPointerUp);
|
|
14282
|
-
this.addEventListener("pointerup", onPointerUpCapture, true);
|
|
14283
|
-
this.addEventListener("click", onClick, true);
|
|
14284
|
-
}
|
|
14285
|
-
|
|
14286
|
-
#teardownDrag() {
|
|
14287
|
-
if (!this.#dragState?.bound) return;
|
|
14288
|
-
this.removeEventListener("pointerdown", this.#dragState.onPointerDown);
|
|
14289
|
-
window.removeEventListener("pointermove", this.#dragState.onPointerMove);
|
|
14290
|
-
window.removeEventListener("pointerup", this.#dragState.onPointerUp);
|
|
14291
|
-
this.removeEventListener(
|
|
14292
|
-
"pointerup",
|
|
14293
|
-
this.#dragState.onPointerUpCapture,
|
|
14294
|
-
true,
|
|
14295
|
-
);
|
|
14296
|
-
this.removeEventListener("click", this.#dragState.onClick, true);
|
|
14297
|
-
this.style.cursor = "";
|
|
14298
|
-
this.style.userSelect = "";
|
|
14299
|
-
this.#dragState = null;
|
|
14300
|
-
}
|
|
14301
|
-
|
|
14302
|
-
#applyOverflowMode() {
|
|
14303
|
-
if (this.#overflowMode === "scrollbar") {
|
|
14304
|
-
this.#removeNavButtons();
|
|
14305
|
-
} else {
|
|
14306
|
-
this.#createNavButtons();
|
|
14307
|
-
}
|
|
14308
|
-
}
|
|
14309
|
-
|
|
14310
|
-
#removeNavButtons() {
|
|
14311
|
-
this.#navStart?.remove();
|
|
14312
|
-
this.#navEnd?.remove();
|
|
14313
|
-
this.#navStart = null;
|
|
14314
|
-
this.#navEnd = null;
|
|
14315
|
-
this.classList.remove("overflow-start", "overflow-end");
|
|
14316
|
-
}
|
|
14317
|
-
|
|
14318
|
-
#createNavButtons() {
|
|
14319
|
-
if (this.#navStart) return;
|
|
14320
|
-
|
|
14321
|
-
this.#navStart = document.createElement("button");
|
|
14322
|
-
this.#navStart.className = "fig-chooser-nav-start";
|
|
14323
|
-
this.#navStart.setAttribute("tabindex", "-1");
|
|
14324
|
-
this.#navStart.setAttribute("aria-label", "Scroll back");
|
|
14325
|
-
|
|
14326
|
-
this.#navEnd = document.createElement("button");
|
|
14327
|
-
this.#navEnd.className = "fig-chooser-nav-end";
|
|
14328
|
-
this.#navEnd.setAttribute("tabindex", "-1");
|
|
14329
|
-
this.#navEnd.setAttribute("aria-label", "Scroll forward");
|
|
14330
|
-
|
|
14331
|
-
this.#navStart.addEventListener("pointerdown", (e) => {
|
|
14332
|
-
e.stopPropagation();
|
|
14333
|
-
this.#scrollByPage(-1);
|
|
14334
|
-
});
|
|
14335
|
-
|
|
14336
|
-
this.#navEnd.addEventListener("pointerdown", (e) => {
|
|
14337
|
-
e.stopPropagation();
|
|
14338
|
-
this.#scrollByPage(1);
|
|
14339
|
-
});
|
|
14340
|
-
|
|
14341
|
-
this.prepend(this.#navStart);
|
|
14342
|
-
this.append(this.#navEnd);
|
|
14343
|
-
}
|
|
14344
|
-
|
|
14345
|
-
#scrollByPage(direction) {
|
|
14346
|
-
const isHorizontal = this.getAttribute("layout") === "horizontal";
|
|
14347
|
-
const pageSize = isHorizontal ? this.clientWidth : this.clientHeight;
|
|
14348
|
-
const scrollAmount = pageSize * 0.8 * direction;
|
|
14349
|
-
|
|
14350
|
-
this.scrollBy({
|
|
14351
|
-
[isHorizontal ? "left" : "top"]: scrollAmount,
|
|
14352
|
-
behavior: "smooth",
|
|
14353
|
-
});
|
|
12985
|
+
// Fall through to default.
|
|
12986
|
+
}
|
|
12987
|
+
|
|
12988
|
+
return "#D9D9D9";
|
|
14354
12989
|
}
|
|
14355
12990
|
|
|
14356
|
-
#
|
|
14357
|
-
|
|
14358
|
-
|
|
14359
|
-
|
|
14360
|
-
|
|
14361
|
-
|
|
12991
|
+
#syncFromAttributes() {
|
|
12992
|
+
const rawAttr = this.getAttribute("value");
|
|
12993
|
+
const color = this.#normalizeColor(rawAttr);
|
|
12994
|
+
const alpha = this.#extractAlpha(rawAttr);
|
|
12995
|
+
if (rawAttr !== color && alpha >= 1) {
|
|
12996
|
+
this.setAttribute("value", color);
|
|
12997
|
+
return;
|
|
12998
|
+
}
|
|
14362
12999
|
|
|
14363
|
-
|
|
13000
|
+
if (this.#fillPicker) {
|
|
13001
|
+
const pickerVal =
|
|
13002
|
+
alpha < 1
|
|
13003
|
+
? { type: "solid", color, opacity: Math.round(alpha * 100) }
|
|
13004
|
+
: { type: "solid", color };
|
|
13005
|
+
this.#fillPicker.setAttribute("value", JSON.stringify(pickerVal));
|
|
13006
|
+
if (this.#alphaEnabled) {
|
|
13007
|
+
this.#fillPicker.removeAttribute("alpha");
|
|
13008
|
+
} else {
|
|
13009
|
+
this.#fillPicker.setAttribute("alpha", "false");
|
|
13010
|
+
}
|
|
13011
|
+
if (this.hasAttribute("disabled")) {
|
|
13012
|
+
this.#fillPicker.setAttribute("disabled", "");
|
|
13013
|
+
} else {
|
|
13014
|
+
this.#fillPicker.removeAttribute("disabled");
|
|
13015
|
+
}
|
|
13016
|
+
}
|
|
14364
13017
|
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
13018
|
+
if (this.#chit) {
|
|
13019
|
+
this.#chit.setAttribute("background", color);
|
|
13020
|
+
if (this.hasAttribute("disabled")) {
|
|
13021
|
+
this.#chit.setAttribute("disabled", "");
|
|
13022
|
+
} else {
|
|
13023
|
+
this.#chit.removeAttribute("disabled");
|
|
14369
13024
|
}
|
|
13025
|
+
}
|
|
13026
|
+
}
|
|
14370
13027
|
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
13028
|
+
#updateColorFromPicker(detail, type) {
|
|
13029
|
+
const nextColor = this.#normalizeColor(detail?.color);
|
|
13030
|
+
const prevColor = this.#normalizeColor(this.getAttribute("value"));
|
|
13031
|
+
if (nextColor !== prevColor) {
|
|
13032
|
+
this.setAttribute("value", nextColor);
|
|
13033
|
+
} else {
|
|
13034
|
+
this.#syncFromAttributes();
|
|
13035
|
+
}
|
|
13036
|
+
|
|
13037
|
+
const eventDetail = { color: this.value };
|
|
13038
|
+
if (this.#alphaEnabled) {
|
|
13039
|
+
if (detail?.opacity !== undefined) {
|
|
13040
|
+
eventDetail.opacity = detail.opacity;
|
|
13041
|
+
} else if (detail?.alpha !== undefined) {
|
|
13042
|
+
eventDetail.opacity = Math.round(detail.alpha * 100);
|
|
14375
13043
|
}
|
|
13044
|
+
}
|
|
14376
13045
|
|
|
14377
|
-
|
|
14378
|
-
|
|
13046
|
+
this.dispatchEvent(
|
|
13047
|
+
new CustomEvent(type, {
|
|
13048
|
+
bubbles: true,
|
|
13049
|
+
cancelable: true,
|
|
13050
|
+
composed: true,
|
|
13051
|
+
detail: eventDetail,
|
|
13052
|
+
}),
|
|
13053
|
+
);
|
|
14379
13054
|
}
|
|
14380
13055
|
|
|
14381
|
-
#
|
|
14382
|
-
|
|
14383
|
-
this.#
|
|
14384
|
-
const choices = this.choices;
|
|
14385
|
-
if (this.#selectedChoice && !choices.includes(this.#selectedChoice)) {
|
|
14386
|
-
this.#selectedChoice = null;
|
|
14387
|
-
this.#syncSelection();
|
|
14388
|
-
} else if (!this.#selectedChoice && choices.length) {
|
|
14389
|
-
this.#syncSelection();
|
|
14390
|
-
}
|
|
14391
|
-
});
|
|
14392
|
-
this.#mutationObserver.observe(this, { childList: true, subtree: true });
|
|
13056
|
+
#handlePickerInput(event) {
|
|
13057
|
+
event.stopPropagation();
|
|
13058
|
+
this.#updateColorFromPicker(event.detail, "input");
|
|
14393
13059
|
}
|
|
14394
|
-
}
|
|
14395
|
-
customElements.define("fig-chooser", FigChooser);
|
|
14396
13060
|
|
|
14397
|
-
|
|
14398
|
-
|
|
14399
|
-
|
|
14400
|
-
|
|
14401
|
-
"value",
|
|
14402
|
-
"color",
|
|
14403
|
-
"name",
|
|
14404
|
-
"tooltips",
|
|
14405
|
-
"disabled",
|
|
14406
|
-
"drag-surface",
|
|
14407
|
-
"snapping",
|
|
14408
|
-
];
|
|
13061
|
+
#handlePickerChange(event) {
|
|
13062
|
+
event.stopPropagation();
|
|
13063
|
+
this.#updateColorFromPicker(event.detail, "change");
|
|
13064
|
+
}
|
|
14409
13065
|
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
13066
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
13067
|
+
if (oldValue === newValue) return;
|
|
13068
|
+
if (!this.isConnected) return;
|
|
13069
|
+
|
|
13070
|
+
switch (name) {
|
|
13071
|
+
case "control":
|
|
13072
|
+
this.#render();
|
|
13073
|
+
break;
|
|
13074
|
+
case "value":
|
|
13075
|
+
case "selected":
|
|
13076
|
+
case "disabled":
|
|
13077
|
+
this.#syncFromAttributes();
|
|
13078
|
+
break;
|
|
13079
|
+
}
|
|
13080
|
+
}
|
|
13081
|
+
|
|
13082
|
+
get value() {
|
|
13083
|
+
return this.#normalizeColor(this.getAttribute("value"));
|
|
13084
|
+
}
|
|
13085
|
+
set value(value) {
|
|
13086
|
+
if (value === null || value === undefined || value === "") {
|
|
13087
|
+
this.removeAttribute("value");
|
|
13088
|
+
return;
|
|
13089
|
+
}
|
|
13090
|
+
this.setAttribute("value", this.#normalizeColor(value));
|
|
13091
|
+
}
|
|
14431
13092
|
|
|
14432
|
-
get
|
|
14433
|
-
return this.
|
|
13093
|
+
get selected() {
|
|
13094
|
+
return this.hasAttribute("selected");
|
|
13095
|
+
}
|
|
13096
|
+
set selected(value) {
|
|
13097
|
+
this.toggleAttribute("selected", Boolean(value));
|
|
14434
13098
|
}
|
|
14435
13099
|
|
|
14436
|
-
get
|
|
14437
|
-
return this
|
|
13100
|
+
get disabled() {
|
|
13101
|
+
return this.hasAttribute("disabled");
|
|
13102
|
+
}
|
|
13103
|
+
set disabled(value) {
|
|
13104
|
+
this.toggleAttribute("disabled", Boolean(value));
|
|
14438
13105
|
}
|
|
13106
|
+
}
|
|
13107
|
+
customElements.define("fig-color-tip", FigColorTip);
|
|
14439
13108
|
|
|
14440
|
-
|
|
14441
|
-
|
|
13109
|
+
/* Choice */
|
|
13110
|
+
/**
|
|
13111
|
+
* A generic choice container for use within FigChooser.
|
|
13112
|
+
* @attr {string} value - Identifier for this choice
|
|
13113
|
+
* @attr {boolean} selected - Whether this choice is currently selected
|
|
13114
|
+
* @attr {boolean} disabled - Whether this choice is disabled
|
|
13115
|
+
*/
|
|
13116
|
+
class FigChoice extends HTMLElement {
|
|
13117
|
+
static get observedAttributes() {
|
|
13118
|
+
return ["selected", "disabled"];
|
|
14442
13119
|
}
|
|
14443
13120
|
|
|
14444
|
-
|
|
14445
|
-
|
|
13121
|
+
connectedCallback() {
|
|
13122
|
+
this.setAttribute("role", "option");
|
|
13123
|
+
if (!this.hasAttribute("tabindex")) {
|
|
13124
|
+
this.setAttribute("tabindex", "0");
|
|
13125
|
+
}
|
|
13126
|
+
this.setAttribute(
|
|
13127
|
+
"aria-selected",
|
|
13128
|
+
this.hasAttribute("selected") ? "true" : "false",
|
|
13129
|
+
);
|
|
13130
|
+
if (this.hasAttribute("disabled")) {
|
|
13131
|
+
this.setAttribute("aria-disabled", "true");
|
|
13132
|
+
}
|
|
14446
13133
|
}
|
|
14447
13134
|
|
|
14448
|
-
|
|
14449
|
-
|
|
13135
|
+
attributeChangedCallback(name) {
|
|
13136
|
+
if (name === "selected") {
|
|
13137
|
+
this.setAttribute(
|
|
13138
|
+
"aria-selected",
|
|
13139
|
+
this.hasAttribute("selected") ? "true" : "false",
|
|
13140
|
+
);
|
|
13141
|
+
}
|
|
13142
|
+
if (name === "disabled") {
|
|
13143
|
+
const isDisabled =
|
|
13144
|
+
this.hasAttribute("disabled") &&
|
|
13145
|
+
this.getAttribute("disabled") !== "false";
|
|
13146
|
+
if (isDisabled) {
|
|
13147
|
+
this.setAttribute("aria-disabled", "true");
|
|
13148
|
+
this.setAttribute("tabindex", "-1");
|
|
13149
|
+
} else {
|
|
13150
|
+
this.removeAttribute("aria-disabled");
|
|
13151
|
+
this.setAttribute("tabindex", "0");
|
|
13152
|
+
}
|
|
13153
|
+
}
|
|
14450
13154
|
}
|
|
13155
|
+
}
|
|
13156
|
+
customElements.define("fig-choice", FigChoice);
|
|
14451
13157
|
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
13158
|
+
/* Chooser */
|
|
13159
|
+
/**
|
|
13160
|
+
* A selection controller for a list of choice elements.
|
|
13161
|
+
* @attr {string} choice-element - CSS selector for child choices (default: "fig-choice")
|
|
13162
|
+
* @attr {string} layout - Layout mode: "vertical" (default), "horizontal", "grid"
|
|
13163
|
+
* @attr {string} value - Value of the currently selected choice
|
|
13164
|
+
* @attr {boolean} disabled - Whether the chooser is disabled
|
|
13165
|
+
*/
|
|
13166
|
+
class FigChooser extends HTMLElement {
|
|
13167
|
+
#selectedChoice = null;
|
|
13168
|
+
#boundHandleClick = this.#handleClick.bind(this);
|
|
13169
|
+
#boundHandleKeyDown = this.#handleKeyDown.bind(this);
|
|
13170
|
+
#boundSyncOverflow = this.#syncOverflow.bind(this);
|
|
13171
|
+
#mutationObserver = null;
|
|
13172
|
+
#resizeObserver = null;
|
|
13173
|
+
#navStart = null;
|
|
13174
|
+
#navEnd = null;
|
|
13175
|
+
#dragState = null;
|
|
13176
|
+
|
|
13177
|
+
constructor() {
|
|
13178
|
+
super();
|
|
14455
13179
|
}
|
|
14456
13180
|
|
|
14457
|
-
get
|
|
14458
|
-
|
|
14459
|
-
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
|
|
14463
|
-
|
|
13181
|
+
static get observedAttributes() {
|
|
13182
|
+
return [
|
|
13183
|
+
"value",
|
|
13184
|
+
"disabled",
|
|
13185
|
+
"choice-element",
|
|
13186
|
+
"drag",
|
|
13187
|
+
"overflow",
|
|
13188
|
+
"loop",
|
|
13189
|
+
"padding",
|
|
13190
|
+
];
|
|
14464
13191
|
}
|
|
14465
13192
|
|
|
14466
|
-
#
|
|
14467
|
-
|
|
14468
|
-
|
|
14469
|
-
|
|
14470
|
-
return false;
|
|
13193
|
+
get #overflowMode() {
|
|
13194
|
+
return this.getAttribute("overflow") === "scrollbar"
|
|
13195
|
+
? "scrollbar"
|
|
13196
|
+
: "buttons";
|
|
14471
13197
|
}
|
|
14472
13198
|
|
|
14473
|
-
get #
|
|
14474
|
-
const
|
|
14475
|
-
|
|
14476
|
-
const parts = name.split(",");
|
|
14477
|
-
return parts[0].trim();
|
|
14478
|
-
}
|
|
14479
|
-
return `${Math.round(this.#x)}%, ${Math.round(this.#y)}%`;
|
|
13199
|
+
get #dragEnabled() {
|
|
13200
|
+
const attr = this.getAttribute("drag");
|
|
13201
|
+
return attr === null || attr !== "false";
|
|
14480
13202
|
}
|
|
14481
13203
|
|
|
14482
|
-
get #
|
|
14483
|
-
|
|
14484
|
-
if (name) {
|
|
14485
|
-
const parts = name.split(",");
|
|
14486
|
-
if (parts.length > 1) return parts[1].trim();
|
|
14487
|
-
}
|
|
14488
|
-
return `${Math.round(this.#x2)}%, ${Math.round(this.#y2)}%`;
|
|
13204
|
+
get #choiceSelector() {
|
|
13205
|
+
return this.getAttribute("choice-element") || "fig-choice";
|
|
14489
13206
|
}
|
|
14490
13207
|
|
|
14491
|
-
get
|
|
14492
|
-
return this.
|
|
13208
|
+
get choices() {
|
|
13209
|
+
return Array.from(this.querySelectorAll(this.#choiceSelector));
|
|
14493
13210
|
}
|
|
14494
13211
|
|
|
14495
|
-
get
|
|
14496
|
-
|
|
14497
|
-
if (surface === "parent") return this.parentElement;
|
|
14498
|
-
return this.closest(surface);
|
|
13212
|
+
get selectedChoice() {
|
|
13213
|
+
return this.#selectedChoice;
|
|
14499
13214
|
}
|
|
14500
13215
|
|
|
14501
|
-
|
|
14502
|
-
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
|
|
14506
|
-
|
|
14507
|
-
|
|
13216
|
+
set selectedChoice(element) {
|
|
13217
|
+
if (element && !this.contains(element)) return;
|
|
13218
|
+
const choices = this.choices;
|
|
13219
|
+
for (const choice of choices) {
|
|
13220
|
+
const shouldSelect = choice === element;
|
|
13221
|
+
const isSelected = choice.hasAttribute("selected");
|
|
13222
|
+
if (shouldSelect && !isSelected) {
|
|
13223
|
+
choice.setAttribute("selected", "");
|
|
13224
|
+
} else if (!shouldSelect && isSelected) {
|
|
13225
|
+
choice.removeAttribute("selected");
|
|
13226
|
+
}
|
|
13227
|
+
}
|
|
13228
|
+
this.#selectedChoice = element;
|
|
13229
|
+
const val = element?.getAttribute("value") ?? "";
|
|
13230
|
+
if (this.getAttribute("value") !== val) {
|
|
13231
|
+
if (val) {
|
|
13232
|
+
this.setAttribute("value", val);
|
|
14508
13233
|
}
|
|
14509
13234
|
}
|
|
14510
|
-
|
|
13235
|
+
this.#scrollToChoice(element);
|
|
14511
13236
|
}
|
|
14512
13237
|
|
|
14513
|
-
|
|
14514
|
-
|
|
14515
|
-
return this.#radius;
|
|
13238
|
+
get value() {
|
|
13239
|
+
return this.#selectedChoice?.getAttribute("value") ?? "";
|
|
14516
13240
|
}
|
|
14517
13241
|
|
|
14518
|
-
|
|
14519
|
-
if (
|
|
14520
|
-
|
|
13242
|
+
set value(val) {
|
|
13243
|
+
if (val === null || val === undefined || val === "") return;
|
|
13244
|
+
this.setAttribute("value", String(val));
|
|
14521
13245
|
}
|
|
14522
13246
|
|
|
14523
13247
|
connectedCallback() {
|
|
14524
|
-
this
|
|
14525
|
-
this.#
|
|
13248
|
+
this.setAttribute("role", "listbox");
|
|
13249
|
+
this.addEventListener("click", this.#boundHandleClick);
|
|
13250
|
+
this.addEventListener("keydown", this.#boundHandleKeyDown);
|
|
13251
|
+
this.addEventListener("scroll", this.#boundSyncOverflow);
|
|
13252
|
+
this.#applyOverflowMode();
|
|
13253
|
+
this.#setupDrag();
|
|
13254
|
+
this.#startObserver();
|
|
13255
|
+
this.#startResizeObserver();
|
|
13256
|
+
|
|
13257
|
+
requestAnimationFrame(() => {
|
|
13258
|
+
this.#syncSelection();
|
|
13259
|
+
this.#syncOverflow();
|
|
13260
|
+
});
|
|
14526
13261
|
}
|
|
14527
13262
|
|
|
14528
13263
|
disconnectedCallback() {
|
|
14529
|
-
this.#
|
|
13264
|
+
this.removeEventListener("click", this.#boundHandleClick);
|
|
13265
|
+
this.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
13266
|
+
this.removeEventListener("scroll", this.#boundSyncOverflow);
|
|
13267
|
+
this.#teardownDrag();
|
|
13268
|
+
this.#mutationObserver?.disconnect();
|
|
13269
|
+
this.#mutationObserver = null;
|
|
13270
|
+
this.#resizeObserver?.disconnect();
|
|
13271
|
+
this.#resizeObserver = null;
|
|
13272
|
+
this.#removeNavButtons();
|
|
14530
13273
|
}
|
|
14531
13274
|
|
|
14532
|
-
attributeChangedCallback(name,
|
|
14533
|
-
if (
|
|
14534
|
-
|
|
14535
|
-
name === "value" &&
|
|
14536
|
-
!this.#isDragging &&
|
|
14537
|
-
!this.#isSecondDragging &&
|
|
14538
|
-
!this.#isRadiusDragging &&
|
|
14539
|
-
!this.#isAngleDragging
|
|
14540
|
-
) {
|
|
14541
|
-
this.#parseValue();
|
|
14542
|
-
if (this.#pointHandle) this.#syncPositions();
|
|
14543
|
-
else this.#render();
|
|
14544
|
-
}
|
|
14545
|
-
if (name === "type") {
|
|
14546
|
-
this.#parseValue();
|
|
14547
|
-
this.#render();
|
|
14548
|
-
}
|
|
14549
|
-
if (name === "color" && this.#pointHandle) {
|
|
14550
|
-
if (newVal) this.#pointHandle.setAttribute("color", newVal);
|
|
14551
|
-
else this.#pointHandle.removeAttribute("color");
|
|
13275
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
13276
|
+
if (name === "value" && newValue !== oldValue && newValue) {
|
|
13277
|
+
this.#selectByValue(newValue);
|
|
14552
13278
|
}
|
|
14553
13279
|
if (name === "disabled") {
|
|
14554
|
-
|
|
13280
|
+
const isDisabled = newValue !== null && newValue !== "false";
|
|
13281
|
+
const choices = this.choices;
|
|
13282
|
+
for (const choice of choices) {
|
|
13283
|
+
if (isDisabled) {
|
|
13284
|
+
choice.setAttribute("aria-disabled", "true");
|
|
13285
|
+
choice.setAttribute("tabindex", "-1");
|
|
13286
|
+
} else {
|
|
13287
|
+
choice.removeAttribute("aria-disabled");
|
|
13288
|
+
choice.setAttribute("tabindex", "0");
|
|
13289
|
+
}
|
|
13290
|
+
}
|
|
14555
13291
|
}
|
|
14556
|
-
if (name === "
|
|
14557
|
-
this.#
|
|
13292
|
+
if (name === "choice-element") {
|
|
13293
|
+
requestAnimationFrame(() => this.#syncSelection());
|
|
14558
13294
|
}
|
|
14559
|
-
if (name === "
|
|
14560
|
-
this.#
|
|
14561
|
-
|
|
14562
|
-
|
|
13295
|
+
if (name === "drag") {
|
|
13296
|
+
if (this.#dragEnabled) {
|
|
13297
|
+
this.#setupDrag();
|
|
13298
|
+
} else {
|
|
13299
|
+
this.#teardownDrag();
|
|
13300
|
+
}
|
|
14563
13301
|
}
|
|
14564
|
-
if (name === "
|
|
14565
|
-
|
|
14566
|
-
this.#pointTooltip.setAttribute("text", this.#pointTipText);
|
|
14567
|
-
if (this.#secondTooltip)
|
|
14568
|
-
this.#secondTooltip.setAttribute("text", this.#secondTipText);
|
|
13302
|
+
if (name === "overflow") {
|
|
13303
|
+
this.#applyOverflowMode();
|
|
14569
13304
|
}
|
|
14570
13305
|
}
|
|
14571
13306
|
|
|
14572
|
-
#
|
|
14573
|
-
const
|
|
14574
|
-
if (!
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
if (typeof v.x === "number") this.#x = v.x;
|
|
14578
|
-
if (typeof v.y === "number") this.#y = v.y;
|
|
14579
|
-
if (v.radius !== undefined) {
|
|
14580
|
-
const rs = String(v.radius);
|
|
14581
|
-
if (rs.endsWith("%")) {
|
|
14582
|
-
this.#radiusIsPercent = true;
|
|
14583
|
-
this.#radius = parseFloat(rs);
|
|
14584
|
-
} else {
|
|
14585
|
-
this.#radiusIsPercent = false;
|
|
14586
|
-
this.#radius = parseFloat(rs);
|
|
14587
|
-
}
|
|
14588
|
-
if (!Number.isFinite(this.#radius)) this.#radius = 0;
|
|
14589
|
-
}
|
|
14590
|
-
if (typeof v.angle === "number") this.#angle = v.angle;
|
|
14591
|
-
if (typeof v.x2 === "number") this.#x2 = v.x2;
|
|
14592
|
-
if (typeof v.y2 === "number") this.#y2 = v.y2;
|
|
14593
|
-
} catch {
|
|
14594
|
-
/* ignore */
|
|
13307
|
+
#syncSelection() {
|
|
13308
|
+
const choices = this.choices;
|
|
13309
|
+
if (!choices.length) {
|
|
13310
|
+
this.#selectedChoice = null;
|
|
13311
|
+
return;
|
|
14595
13312
|
}
|
|
14596
|
-
}
|
|
14597
13313
|
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
if (this.#hasRadius) {
|
|
14606
|
-
v.radius = this.#radiusIsPercent ? `${this.#radius}%` : this.#radius;
|
|
13314
|
+
const valueAttr = this.getAttribute("value");
|
|
13315
|
+
if (valueAttr && this.#selectByValue(valueAttr)) return;
|
|
13316
|
+
|
|
13317
|
+
const alreadySelected = choices.find((c) => c.hasAttribute("selected"));
|
|
13318
|
+
if (alreadySelected) {
|
|
13319
|
+
this.selectedChoice = alreadySelected;
|
|
13320
|
+
return;
|
|
14607
13321
|
}
|
|
14608
|
-
|
|
14609
|
-
|
|
14610
|
-
|
|
14611
|
-
|
|
13322
|
+
|
|
13323
|
+
this.selectedChoice = choices[0];
|
|
13324
|
+
}
|
|
13325
|
+
|
|
13326
|
+
#selectByValue(value) {
|
|
13327
|
+
const choices = this.choices;
|
|
13328
|
+
for (const choice of choices) {
|
|
13329
|
+
if (choice.getAttribute("value") === value) {
|
|
13330
|
+
this.selectedChoice = choice;
|
|
13331
|
+
return true;
|
|
13332
|
+
}
|
|
14612
13333
|
}
|
|
14613
|
-
return
|
|
13334
|
+
return false;
|
|
14614
13335
|
}
|
|
14615
13336
|
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
13337
|
+
#findChoiceFromTarget(target) {
|
|
13338
|
+
const selector = this.#choiceSelector;
|
|
13339
|
+
let el = target;
|
|
13340
|
+
while (el && el !== this) {
|
|
13341
|
+
if (el.matches(selector)) return el;
|
|
13342
|
+
el = el.parentElement;
|
|
14621
13343
|
}
|
|
13344
|
+
return null;
|
|
14622
13345
|
}
|
|
14623
13346
|
|
|
14624
|
-
#
|
|
14625
|
-
|
|
14626
|
-
|
|
14627
|
-
|
|
14628
|
-
|
|
14629
|
-
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
13347
|
+
#handleClick(event) {
|
|
13348
|
+
if (
|
|
13349
|
+
this.hasAttribute("disabled") &&
|
|
13350
|
+
this.getAttribute("disabled") !== "false"
|
|
13351
|
+
)
|
|
13352
|
+
return;
|
|
13353
|
+
const choice = this.#findChoiceFromTarget(event.target);
|
|
13354
|
+
if (!choice) return;
|
|
13355
|
+
if (
|
|
13356
|
+
choice.hasAttribute("disabled") &&
|
|
13357
|
+
choice.getAttribute("disabled") !== "false"
|
|
13358
|
+
)
|
|
13359
|
+
return;
|
|
13360
|
+
this.selectedChoice = choice;
|
|
13361
|
+
this.#emitEvents();
|
|
13362
|
+
}
|
|
14635
13363
|
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
const
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
if (type === "color") {
|
|
14650
|
-
handle.setAttribute("type", "color");
|
|
14651
|
-
const color = this.getAttribute("color");
|
|
14652
|
-
if (color) handle.setAttribute("color", color);
|
|
14653
|
-
}
|
|
14654
|
-
if (this.#hasSecondPoint) {
|
|
14655
|
-
handle.setAttribute("hit-area", "12 circle");
|
|
14656
|
-
handle.setAttribute("hit-area-mode", "delegate");
|
|
14657
|
-
}
|
|
14658
|
-
this.#pointHandle = handle;
|
|
14659
|
-
|
|
14660
|
-
if (this.#hasRadius) {
|
|
14661
|
-
this.#createRadiusSvg();
|
|
14662
|
-
}
|
|
14663
|
-
|
|
14664
|
-
if (this.#hasLine) {
|
|
14665
|
-
this.#createAngleSvg();
|
|
14666
|
-
}
|
|
14667
|
-
|
|
14668
|
-
if (tooltips) {
|
|
14669
|
-
const tip = document.createElement("fig-tooltip");
|
|
14670
|
-
tip.setAttribute("action", "manual");
|
|
14671
|
-
tip.setAttribute("theme", "brand");
|
|
14672
|
-
tip.setAttribute("pointer", "false");
|
|
14673
|
-
tip.setAttribute("text", this.#pointTipText);
|
|
14674
|
-
tip.appendChild(handle);
|
|
14675
|
-
this.appendChild(tip);
|
|
14676
|
-
this.#pointTooltip = tip;
|
|
14677
|
-
} else {
|
|
14678
|
-
this.appendChild(handle);
|
|
14679
|
-
}
|
|
13364
|
+
#handleKeyDown(event) {
|
|
13365
|
+
if (
|
|
13366
|
+
this.hasAttribute("disabled") &&
|
|
13367
|
+
this.getAttribute("disabled") !== "false"
|
|
13368
|
+
)
|
|
13369
|
+
return;
|
|
13370
|
+
const choices = this.choices.filter(
|
|
13371
|
+
(c) =>
|
|
13372
|
+
!c.hasAttribute("disabled") || c.getAttribute("disabled") === "false",
|
|
13373
|
+
);
|
|
13374
|
+
if (!choices.length) return;
|
|
13375
|
+
const currentIndex = choices.indexOf(this.#selectedChoice);
|
|
13376
|
+
let nextIndex = currentIndex;
|
|
14680
13377
|
|
|
14681
|
-
|
|
14682
|
-
this.#createAngleHandle(disabled, tooltips, handleSurface);
|
|
14683
|
-
}
|
|
13378
|
+
const loop = this.hasAttribute("loop");
|
|
14684
13379
|
|
|
14685
|
-
|
|
14686
|
-
|
|
13380
|
+
switch (event.key) {
|
|
13381
|
+
case "ArrowDown":
|
|
13382
|
+
case "ArrowRight":
|
|
13383
|
+
event.preventDefault();
|
|
13384
|
+
if (currentIndex < choices.length - 1) {
|
|
13385
|
+
nextIndex = currentIndex + 1;
|
|
13386
|
+
} else if (loop) {
|
|
13387
|
+
nextIndex = 0;
|
|
13388
|
+
}
|
|
13389
|
+
break;
|
|
13390
|
+
case "ArrowUp":
|
|
13391
|
+
case "ArrowLeft":
|
|
13392
|
+
event.preventDefault();
|
|
13393
|
+
if (currentIndex > 0) {
|
|
13394
|
+
nextIndex = currentIndex - 1;
|
|
13395
|
+
} else if (loop) {
|
|
13396
|
+
nextIndex = choices.length - 1;
|
|
13397
|
+
}
|
|
13398
|
+
break;
|
|
13399
|
+
case "Home":
|
|
13400
|
+
event.preventDefault();
|
|
13401
|
+
nextIndex = 0;
|
|
13402
|
+
break;
|
|
13403
|
+
case "End":
|
|
13404
|
+
event.preventDefault();
|
|
13405
|
+
nextIndex = choices.length - 1;
|
|
13406
|
+
break;
|
|
13407
|
+
case "Enter":
|
|
13408
|
+
case " ":
|
|
13409
|
+
event.preventDefault();
|
|
13410
|
+
if (document.activeElement?.matches(this.#choiceSelector)) {
|
|
13411
|
+
const focused = this.#findChoiceFromTarget(document.activeElement);
|
|
13412
|
+
if (focused && focused !== this.#selectedChoice) {
|
|
13413
|
+
this.selectedChoice = focused;
|
|
13414
|
+
this.#emitEvents();
|
|
13415
|
+
}
|
|
13416
|
+
}
|
|
13417
|
+
return;
|
|
13418
|
+
default:
|
|
13419
|
+
return;
|
|
14687
13420
|
}
|
|
14688
13421
|
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
#createRadiusSvg() {
|
|
14694
|
-
const ns = "http://www.w3.org/2000/svg";
|
|
14695
|
-
const svg = document.createElementNS(ns, "svg");
|
|
14696
|
-
svg.classList.add("fig-canvas-control-radius");
|
|
14697
|
-
svg.setAttribute("overflow", "visible");
|
|
14698
|
-
const hitCircle = document.createElementNS(ns, "circle");
|
|
14699
|
-
hitCircle.classList.add("fig-canvas-control-radius-hit");
|
|
14700
|
-
svg.appendChild(hitCircle);
|
|
14701
|
-
const circle = document.createElementNS(ns, "circle");
|
|
14702
|
-
svg.appendChild(circle);
|
|
14703
|
-
this.#radiusSvg = svg;
|
|
14704
|
-
|
|
14705
|
-
if (this.#tooltipsEnabled) {
|
|
14706
|
-
const tip = document.createElement("fig-tooltip");
|
|
14707
|
-
tip.setAttribute("action", "manual");
|
|
14708
|
-
tip.setAttribute("theme", "brand");
|
|
14709
|
-
tip.setAttribute("pointer", "false");
|
|
14710
|
-
tip.setAttribute("text", this.#formatRadius());
|
|
14711
|
-
tip.appendChild(svg);
|
|
14712
|
-
this.appendChild(tip);
|
|
14713
|
-
this.#radiusTooltip = tip;
|
|
14714
|
-
} else {
|
|
14715
|
-
this.appendChild(svg);
|
|
14716
|
-
}
|
|
14717
|
-
|
|
14718
|
-
this.#setupRadiusDrag(hitCircle);
|
|
14719
|
-
}
|
|
14720
|
-
|
|
14721
|
-
#createAngleSvg() {
|
|
14722
|
-
const ns = "http://www.w3.org/2000/svg";
|
|
14723
|
-
const svg = document.createElementNS(ns, "svg");
|
|
14724
|
-
svg.classList.add("fig-canvas-control-angle-svg");
|
|
14725
|
-
svg.setAttribute("overflow", "visible");
|
|
14726
|
-
svg.style.position = "absolute";
|
|
14727
|
-
svg.style.pointerEvents = "none";
|
|
14728
|
-
const line = document.createElementNS(ns, "line");
|
|
14729
|
-
line.classList.add("fig-canvas-control-angle-line");
|
|
14730
|
-
svg.appendChild(line);
|
|
14731
|
-
this.#angleSvg = svg;
|
|
14732
|
-
this.appendChild(svg);
|
|
14733
|
-
}
|
|
14734
|
-
|
|
14735
|
-
#createAngleHandle(disabled, tooltips, handleSurface) {
|
|
14736
|
-
const handle = document.createElement("fig-handle");
|
|
14737
|
-
handle.setAttribute("drag", "true");
|
|
14738
|
-
handle.setAttribute("drag-surface", handleSurface);
|
|
14739
|
-
handle.setAttribute("drag-axes", "x,y");
|
|
14740
|
-
handle.setAttribute("size", "small");
|
|
14741
|
-
handle.setAttribute("hit-area", "12 circle");
|
|
14742
|
-
handle.setAttribute("hit-area-mode", "delegate");
|
|
14743
|
-
if (disabled) handle.setAttribute("disabled", "");
|
|
14744
|
-
this.#angleHandle = handle;
|
|
14745
|
-
|
|
14746
|
-
if (tooltips) {
|
|
14747
|
-
const tip = document.createElement("fig-tooltip");
|
|
14748
|
-
tip.setAttribute("action", "manual");
|
|
14749
|
-
tip.setAttribute("theme", "brand");
|
|
14750
|
-
tip.setAttribute("pointer", "false");
|
|
14751
|
-
tip.setAttribute("text", `${Math.round(this.#angle)}°`);
|
|
14752
|
-
tip.appendChild(handle);
|
|
14753
|
-
this.appendChild(tip);
|
|
14754
|
-
this.#angleTooltip = tip;
|
|
14755
|
-
} else {
|
|
14756
|
-
this.appendChild(handle);
|
|
14757
|
-
}
|
|
14758
|
-
}
|
|
14759
|
-
|
|
14760
|
-
#createSecondHandle(disabled, tooltips, handleSurface) {
|
|
14761
|
-
const handle = document.createElement("fig-handle");
|
|
14762
|
-
handle.setAttribute("drag", "true");
|
|
14763
|
-
handle.setAttribute("drag-surface", handleSurface);
|
|
14764
|
-
handle.setAttribute("drag-axes", "x,y");
|
|
14765
|
-
handle.setAttribute("drag-snapping", this.#snappingMode);
|
|
14766
|
-
handle.setAttribute("hit-area", "12 circle");
|
|
14767
|
-
handle.setAttribute("hit-area-mode", "delegate");
|
|
14768
|
-
handle.setAttribute("value", `${this.#x2}% ${this.#y2}%`);
|
|
14769
|
-
if (disabled) handle.setAttribute("disabled", "");
|
|
14770
|
-
this.#secondHandle = handle;
|
|
14771
|
-
|
|
14772
|
-
if (tooltips) {
|
|
14773
|
-
const tip = document.createElement("fig-tooltip");
|
|
14774
|
-
tip.setAttribute("action", "manual");
|
|
14775
|
-
tip.setAttribute("theme", "brand");
|
|
14776
|
-
tip.setAttribute("pointer", "false");
|
|
14777
|
-
tip.setAttribute("text", this.#secondTipText);
|
|
14778
|
-
tip.appendChild(handle);
|
|
14779
|
-
this.appendChild(tip);
|
|
14780
|
-
this.#secondTooltip = tip;
|
|
14781
|
-
} else {
|
|
14782
|
-
this.appendChild(handle);
|
|
13422
|
+
if (nextIndex !== currentIndex && choices[nextIndex]) {
|
|
13423
|
+
this.selectedChoice = choices[nextIndex];
|
|
13424
|
+
choices[nextIndex].focus();
|
|
13425
|
+
this.#emitEvents();
|
|
14783
13426
|
}
|
|
14784
13427
|
}
|
|
14785
13428
|
|
|
14786
|
-
#
|
|
14787
|
-
const
|
|
14788
|
-
|
|
13429
|
+
#emitEvents() {
|
|
13430
|
+
const val = this.value;
|
|
13431
|
+
this.dispatchEvent(
|
|
13432
|
+
new CustomEvent("input", { detail: val, bubbles: true }),
|
|
13433
|
+
);
|
|
13434
|
+
this.dispatchEvent(
|
|
13435
|
+
new CustomEvent("change", { detail: val, bubbles: true }),
|
|
13436
|
+
);
|
|
14789
13437
|
}
|
|
14790
13438
|
|
|
14791
|
-
#
|
|
14792
|
-
|
|
14793
|
-
|
|
14794
|
-
|
|
13439
|
+
#syncOverflow() {
|
|
13440
|
+
if (this.#overflowMode === "scrollbar") return;
|
|
13441
|
+
const isHorizontal = this.getAttribute("layout") === "horizontal";
|
|
13442
|
+
const threshold = 2;
|
|
14795
13443
|
|
|
14796
|
-
|
|
14797
|
-
|
|
14798
|
-
|
|
14799
|
-
|
|
14800
|
-
|
|
13444
|
+
if (isHorizontal) {
|
|
13445
|
+
const atStart = this.scrollLeft <= threshold;
|
|
13446
|
+
const atEnd =
|
|
13447
|
+
this.scrollLeft + this.clientWidth >= this.scrollWidth - threshold;
|
|
13448
|
+
this.classList.toggle("overflow-start", !atStart);
|
|
13449
|
+
this.classList.toggle("overflow-end", !atEnd);
|
|
13450
|
+
} else {
|
|
13451
|
+
const atStart = this.scrollTop <= threshold;
|
|
13452
|
+
const atEnd =
|
|
13453
|
+
this.scrollTop + this.clientHeight >= this.scrollHeight - threshold;
|
|
13454
|
+
this.classList.toggle("overflow-start", !atStart);
|
|
13455
|
+
this.classList.toggle("overflow-end", !atEnd);
|
|
13456
|
+
}
|
|
14801
13457
|
}
|
|
14802
13458
|
|
|
14803
|
-
#
|
|
14804
|
-
|
|
13459
|
+
#startResizeObserver() {
|
|
13460
|
+
this.#resizeObserver?.disconnect();
|
|
13461
|
+
this.#resizeObserver = new ResizeObserver(() => {
|
|
13462
|
+
this.#syncOverflow();
|
|
13463
|
+
});
|
|
13464
|
+
this.#resizeObserver.observe(this);
|
|
14805
13465
|
}
|
|
14806
13466
|
|
|
14807
|
-
#
|
|
14808
|
-
if (
|
|
14809
|
-
|
|
14810
|
-
|
|
14811
|
-
|
|
14812
|
-
|
|
14813
|
-
|
|
13467
|
+
#setupDrag() {
|
|
13468
|
+
if (this.#dragState?.bound) return;
|
|
13469
|
+
if (!this.#dragEnabled) return;
|
|
13470
|
+
|
|
13471
|
+
const onPointerDown = (e) => {
|
|
13472
|
+
if (e.button !== 0) return;
|
|
13473
|
+
const isHorizontal = this.getAttribute("layout") === "horizontal";
|
|
13474
|
+
const hasOverflow = isHorizontal
|
|
13475
|
+
? this.scrollWidth > this.clientWidth
|
|
13476
|
+
: this.scrollHeight > this.clientHeight;
|
|
13477
|
+
if (!hasOverflow) return;
|
|
13478
|
+
|
|
13479
|
+
this.#dragState.active = true;
|
|
13480
|
+
this.#dragState.didDrag = false;
|
|
13481
|
+
this.#dragState.startX = e.clientX;
|
|
13482
|
+
this.#dragState.startY = e.clientY;
|
|
13483
|
+
this.#dragState.scrollLeft = this.scrollLeft;
|
|
13484
|
+
this.#dragState.scrollTop = this.scrollTop;
|
|
13485
|
+
this.style.cursor = "grab";
|
|
13486
|
+
this.style.userSelect = "none";
|
|
14814
13487
|
};
|
|
14815
|
-
setHitCursor(this.#pointHandle, deg + 180);
|
|
14816
|
-
setHitCursor(this.#secondHandle, deg);
|
|
14817
|
-
}
|
|
14818
13488
|
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
|
|
14824
|
-
|
|
13489
|
+
const onPointerMove = (e) => {
|
|
13490
|
+
if (!this.#dragState.active) return;
|
|
13491
|
+
const isHorizontal = this.getAttribute("layout") === "horizontal";
|
|
13492
|
+
const dx = e.clientX - this.#dragState.startX;
|
|
13493
|
+
const dy = e.clientY - this.#dragState.startY;
|
|
13494
|
+
|
|
13495
|
+
if (!this.#dragState.didDrag && Math.abs(isHorizontal ? dx : dy) > 3) {
|
|
13496
|
+
this.#dragState.didDrag = true;
|
|
13497
|
+
this.style.cursor = "grabbing";
|
|
13498
|
+
this.setPointerCapture(e.pointerId);
|
|
13499
|
+
}
|
|
14825
13500
|
|
|
14826
|
-
|
|
14827
|
-
const container = this.#container;
|
|
14828
|
-
if (!container || !this.#pointHandle) return;
|
|
14829
|
-
const rect = container.getBoundingClientRect();
|
|
13501
|
+
if (!this.#dragState.didDrag) return;
|
|
14830
13502
|
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
if (this.#radiusSvg) {
|
|
14834
|
-
const cx = (this.#x / 100) * rect.width;
|
|
14835
|
-
const cy = (this.#y / 100) * rect.height;
|
|
14836
|
-
const r = this.#resolveRadius(rect.width);
|
|
14837
|
-
const svg = this.#radiusSvg;
|
|
14838
|
-
const d = Math.max(r * 2, 1);
|
|
14839
|
-
svg.style.position = "absolute";
|
|
14840
|
-
svg.style.width = `${d}px`;
|
|
14841
|
-
svg.style.height = `${d}px`;
|
|
14842
|
-
svg.style.left = `${cx - r}px`;
|
|
14843
|
-
svg.style.top = `${cy - r}px`;
|
|
14844
|
-
svg.setAttribute("viewBox", `0 0 ${d} ${d}`);
|
|
14845
|
-
const circles = svg.querySelectorAll("circle");
|
|
14846
|
-
for (const c of circles) {
|
|
14847
|
-
c.setAttribute("cx", String(r));
|
|
14848
|
-
c.setAttribute("cy", String(r));
|
|
14849
|
-
c.setAttribute("r", String(Math.max(r - 1, 0)));
|
|
14850
|
-
}
|
|
14851
|
-
}
|
|
14852
|
-
|
|
14853
|
-
if (this.#angleSvg && this.#hasLine) {
|
|
14854
|
-
const cx = (this.#x / 100) * rect.width;
|
|
14855
|
-
const cy = (this.#y / 100) * rect.height;
|
|
14856
|
-
let lx2, ly2;
|
|
14857
|
-
if (this.#hasSecondPoint) {
|
|
14858
|
-
lx2 = (this.#x2 / 100) * rect.width;
|
|
14859
|
-
ly2 = (this.#y2 / 100) * rect.height;
|
|
13503
|
+
if (isHorizontal) {
|
|
13504
|
+
this.scrollLeft = this.#dragState.scrollLeft - dx;
|
|
14860
13505
|
} else {
|
|
14861
|
-
|
|
14862
|
-
const angleRad = (this.#angle * Math.PI) / 180;
|
|
14863
|
-
lx2 = cx + r * Math.cos(angleRad);
|
|
14864
|
-
ly2 = cy + r * Math.sin(angleRad);
|
|
13506
|
+
this.scrollTop = this.#dragState.scrollTop - dy;
|
|
14865
13507
|
}
|
|
13508
|
+
};
|
|
14866
13509
|
|
|
14867
|
-
|
|
14868
|
-
|
|
14869
|
-
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
14873
|
-
|
|
14874
|
-
if (
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
line.setAttribute("y2", String(ly2));
|
|
13510
|
+
const onPointerUp = (e) => {
|
|
13511
|
+
if (!this.#dragState.active) return;
|
|
13512
|
+
const wasDrag = this.#dragState.didDrag;
|
|
13513
|
+
this.#dragState.active = false;
|
|
13514
|
+
this.#dragState.didDrag = false;
|
|
13515
|
+
this.style.cursor = "";
|
|
13516
|
+
this.style.userSelect = "";
|
|
13517
|
+
if (e.pointerId !== undefined) {
|
|
13518
|
+
try {
|
|
13519
|
+
this.releasePointerCapture(e.pointerId);
|
|
13520
|
+
} catch {}
|
|
14879
13521
|
}
|
|
14880
|
-
|
|
13522
|
+
if (wasDrag) {
|
|
13523
|
+
e.preventDefault();
|
|
13524
|
+
e.stopPropagation();
|
|
13525
|
+
}
|
|
13526
|
+
};
|
|
14881
13527
|
|
|
14882
|
-
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
|
|
14888
|
-
|
|
14889
|
-
const pxPct = rect.width > 0 ? (ax / rect.width) * 100 : 0;
|
|
14890
|
-
const pyPct = rect.height > 0 ? (ay / rect.height) * 100 : 0;
|
|
14891
|
-
this.#positionHandle(this.#angleHandle, pxPct, pyPct, rect);
|
|
14892
|
-
}
|
|
13528
|
+
const onClick = (e) => {
|
|
13529
|
+
if (this.#dragState?.suppressClick) {
|
|
13530
|
+
e.stopPropagation();
|
|
13531
|
+
e.preventDefault();
|
|
13532
|
+
this.#dragState.suppressClick = false;
|
|
13533
|
+
}
|
|
13534
|
+
};
|
|
14893
13535
|
|
|
14894
|
-
|
|
14895
|
-
|
|
14896
|
-
|
|
13536
|
+
const onPointerUpCapture = (e) => {
|
|
13537
|
+
if (this.#dragState?.didDrag) {
|
|
13538
|
+
this.#dragState.suppressClick = true;
|
|
13539
|
+
setTimeout(() => {
|
|
13540
|
+
if (this.#dragState) this.#dragState.suppressClick = false;
|
|
13541
|
+
}, 0);
|
|
13542
|
+
}
|
|
13543
|
+
};
|
|
13544
|
+
|
|
13545
|
+
this.#dragState = {
|
|
13546
|
+
active: false,
|
|
13547
|
+
didDrag: false,
|
|
13548
|
+
suppressClick: false,
|
|
13549
|
+
startX: 0,
|
|
13550
|
+
startY: 0,
|
|
13551
|
+
scrollLeft: 0,
|
|
13552
|
+
scrollTop: 0,
|
|
13553
|
+
bound: true,
|
|
13554
|
+
onPointerDown,
|
|
13555
|
+
onPointerMove,
|
|
13556
|
+
onPointerUp,
|
|
13557
|
+
onClick,
|
|
13558
|
+
onPointerUpCapture,
|
|
13559
|
+
};
|
|
14897
13560
|
|
|
14898
|
-
this
|
|
14899
|
-
|
|
13561
|
+
this.addEventListener("pointerdown", onPointerDown);
|
|
13562
|
+
window.addEventListener("pointermove", onPointerMove);
|
|
13563
|
+
window.addEventListener("pointerup", onPointerUp);
|
|
13564
|
+
this.addEventListener("pointerup", onPointerUpCapture, true);
|
|
13565
|
+
this.addEventListener("click", onClick, true);
|
|
14900
13566
|
}
|
|
14901
13567
|
|
|
14902
|
-
#
|
|
14903
|
-
this
|
|
14904
|
-
|
|
13568
|
+
#teardownDrag() {
|
|
13569
|
+
if (!this.#dragState?.bound) return;
|
|
13570
|
+
this.removeEventListener("pointerdown", this.#dragState.onPointerDown);
|
|
13571
|
+
window.removeEventListener("pointermove", this.#dragState.onPointerMove);
|
|
13572
|
+
window.removeEventListener("pointerup", this.#dragState.onPointerUp);
|
|
13573
|
+
this.removeEventListener(
|
|
13574
|
+
"pointerup",
|
|
13575
|
+
this.#dragState.onPointerUpCapture,
|
|
13576
|
+
true,
|
|
14905
13577
|
);
|
|
13578
|
+
this.removeEventListener("click", this.#dragState.onClick, true);
|
|
13579
|
+
this.style.cursor = "";
|
|
13580
|
+
this.style.userSelect = "";
|
|
13581
|
+
this.#dragState = null;
|
|
14906
13582
|
}
|
|
14907
13583
|
|
|
14908
|
-
#
|
|
14909
|
-
this
|
|
14910
|
-
|
|
14911
|
-
|
|
13584
|
+
#applyOverflowMode() {
|
|
13585
|
+
if (this.#overflowMode === "scrollbar") {
|
|
13586
|
+
this.#removeNavButtons();
|
|
13587
|
+
} else {
|
|
13588
|
+
this.#createNavButtons();
|
|
13589
|
+
}
|
|
14912
13590
|
}
|
|
14913
13591
|
|
|
14914
|
-
#
|
|
14915
|
-
this
|
|
13592
|
+
#removeNavButtons() {
|
|
13593
|
+
this.#navStart?.remove();
|
|
13594
|
+
this.#navEnd?.remove();
|
|
13595
|
+
this.#navStart = null;
|
|
13596
|
+
this.#navEnd = null;
|
|
13597
|
+
this.classList.remove("overflow-start", "overflow-end");
|
|
14916
13598
|
}
|
|
14917
13599
|
|
|
14918
|
-
#
|
|
14919
|
-
if (
|
|
13600
|
+
#createNavButtons() {
|
|
13601
|
+
if (this.#navStart) return;
|
|
13602
|
+
|
|
13603
|
+
this.#navStart = document.createElement("button");
|
|
13604
|
+
this.#navStart.className = "fig-chooser-nav-start";
|
|
13605
|
+
this.#navStart.setAttribute("tabindex", "-1");
|
|
13606
|
+
this.#navStart.setAttribute("aria-label", "Scroll back");
|
|
13607
|
+
|
|
13608
|
+
this.#navEnd = document.createElement("button");
|
|
13609
|
+
this.#navEnd.className = "fig-chooser-nav-end";
|
|
13610
|
+
this.#navEnd.setAttribute("tabindex", "-1");
|
|
13611
|
+
this.#navEnd.setAttribute("aria-label", "Scroll forward");
|
|
14920
13612
|
|
|
14921
|
-
this.#
|
|
13613
|
+
this.#navStart.addEventListener("pointerdown", (e) => {
|
|
14922
13614
|
e.stopPropagation();
|
|
14923
|
-
|
|
14924
|
-
this.setAttribute("color", e.detail.color);
|
|
14925
|
-
this.#emitInput();
|
|
14926
|
-
return;
|
|
14927
|
-
}
|
|
14928
|
-
if (!this.#isDragging && this.#hasSecondPoint) {
|
|
14929
|
-
this.#prevBodyCursor = document.body.style.cursor;
|
|
14930
|
-
}
|
|
14931
|
-
this.#isDragging = true;
|
|
14932
|
-
const px = e.detail?.px ?? this.#x / 100;
|
|
14933
|
-
const py = e.detail?.py ?? this.#y / 100;
|
|
14934
|
-
this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
|
|
14935
|
-
this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
|
|
14936
|
-
if (this.#pointTooltip && this.#type !== "color") {
|
|
14937
|
-
this.#pointTooltip.setAttribute("text", this.#pointTipText);
|
|
14938
|
-
this.#pointTooltip.setAttribute("show", "true");
|
|
14939
|
-
this.#pointTooltip.showPopup?.();
|
|
14940
|
-
}
|
|
14941
|
-
this.#syncPositions();
|
|
14942
|
-
if (this.#hasSecondPoint) {
|
|
14943
|
-
document.body.style.cursor = this.#resizeCursorSvg(
|
|
14944
|
-
this.#pointPointLineDeg(),
|
|
14945
|
-
);
|
|
14946
|
-
}
|
|
14947
|
-
this.#emitInput();
|
|
13615
|
+
this.#scrollByPage(-1);
|
|
14948
13616
|
});
|
|
14949
13617
|
|
|
14950
|
-
this.#
|
|
13618
|
+
this.#navEnd.addEventListener("pointerdown", (e) => {
|
|
14951
13619
|
e.stopPropagation();
|
|
14952
|
-
|
|
14953
|
-
this.setAttribute("color", e.detail.color);
|
|
14954
|
-
this.#emitChange();
|
|
14955
|
-
return;
|
|
14956
|
-
}
|
|
14957
|
-
const px = e.detail?.px ?? this.#x / 100;
|
|
14958
|
-
const py = e.detail?.py ?? this.#y / 100;
|
|
14959
|
-
this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
|
|
14960
|
-
this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
|
|
14961
|
-
if (this.#pointTooltip && this.#type !== "color")
|
|
14962
|
-
this.#pointTooltip.removeAttribute("show");
|
|
14963
|
-
if (this.#hasSecondPoint) {
|
|
14964
|
-
document.body.style.cursor = this.#prevBodyCursor ?? "";
|
|
14965
|
-
}
|
|
14966
|
-
this.#syncPositions();
|
|
14967
|
-
this.#syncValueAttribute();
|
|
14968
|
-
this.#emitChange();
|
|
14969
|
-
requestAnimationFrame(() => {
|
|
14970
|
-
this.#isDragging = false;
|
|
14971
|
-
});
|
|
13620
|
+
this.#scrollByPage(1);
|
|
14972
13621
|
});
|
|
14973
13622
|
|
|
14974
|
-
|
|
14975
|
-
|
|
14976
|
-
|
|
14977
|
-
this.#isAngleDragging = true;
|
|
14978
|
-
this.classList.add("fig-canvas-control-ring-active");
|
|
14979
|
-
const container = this.#container;
|
|
14980
|
-
if (!container) return;
|
|
14981
|
-
const rect = container.getBoundingClientRect();
|
|
14982
|
-
const cx = (this.#x / 100) * rect.width;
|
|
14983
|
-
const cy = (this.#y / 100) * rect.height;
|
|
14984
|
-
const hx = e.detail?.x ?? 0;
|
|
14985
|
-
const hy = e.detail?.y ?? 0;
|
|
14986
|
-
const hw = this.#angleHandle.offsetWidth / 2;
|
|
14987
|
-
const hh = this.#angleHandle.offsetHeight / 2;
|
|
14988
|
-
const dx = hx + hw - cx;
|
|
14989
|
-
const dy = hy + hh - cy;
|
|
14990
|
-
let angle = (Math.atan2(dy, dx) * 180) / Math.PI;
|
|
14991
|
-
if (this.#shouldSnap(e.detail?.shiftKey)) {
|
|
14992
|
-
angle = Math.round(angle / 15) * 15;
|
|
14993
|
-
}
|
|
14994
|
-
this.#angle = angle;
|
|
14995
|
-
|
|
14996
|
-
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
14997
|
-
if (this.#shouldSnap(e.detail?.shiftKey)) {
|
|
14998
|
-
const step = this.#radiusIsPercent ? 5 : 10;
|
|
14999
|
-
if (this.#radiusIsPercent) {
|
|
15000
|
-
let pct = (dist / rect.width) * 100;
|
|
15001
|
-
pct = Math.round(pct / step) * step;
|
|
15002
|
-
dist = (pct / 100) * rect.width;
|
|
15003
|
-
} else {
|
|
15004
|
-
dist = Math.round(dist / step) * step;
|
|
15005
|
-
}
|
|
15006
|
-
}
|
|
15007
|
-
if (this.#radiusIsPercent) {
|
|
15008
|
-
this.#radius = Math.max(0, (dist / rect.width) * 100);
|
|
15009
|
-
} else {
|
|
15010
|
-
this.#radius = Math.max(0, dist);
|
|
15011
|
-
}
|
|
15012
|
-
|
|
15013
|
-
if (this.#angleTooltip) {
|
|
15014
|
-
this.#angleTooltip.setAttribute(
|
|
15015
|
-
"text",
|
|
15016
|
-
`Angle ${Math.round(this.#angle)}°`,
|
|
15017
|
-
);
|
|
15018
|
-
this.#angleTooltip.setAttribute("show", "true");
|
|
15019
|
-
this.#angleTooltip.showPopup?.();
|
|
15020
|
-
}
|
|
15021
|
-
this.#syncPositions();
|
|
15022
|
-
this.#emitInput();
|
|
15023
|
-
});
|
|
15024
|
-
|
|
15025
|
-
this.#angleHandle.addEventListener("change", (e) => {
|
|
15026
|
-
e.stopPropagation();
|
|
15027
|
-
this.classList.remove("fig-canvas-control-ring-active");
|
|
15028
|
-
if (this.#angleTooltip) this.#angleTooltip.removeAttribute("show");
|
|
15029
|
-
this.#syncPositions();
|
|
15030
|
-
this.#syncValueAttribute();
|
|
15031
|
-
this.#emitChange();
|
|
15032
|
-
requestAnimationFrame(() => {
|
|
15033
|
-
this.#isAngleDragging = false;
|
|
15034
|
-
});
|
|
15035
|
-
});
|
|
15036
|
-
|
|
15037
|
-
this.#angleHandle.addEventListener("hitareadown", (e) => {
|
|
15038
|
-
e.stopPropagation();
|
|
15039
|
-
const origEvent = e.detail?.originalEvent;
|
|
15040
|
-
if (!origEvent) return;
|
|
15041
|
-
origEvent.preventDefault();
|
|
15042
|
-
this.#isAngleDragging = true;
|
|
15043
|
-
this.classList.add("fig-canvas-control-ring-active");
|
|
15044
|
-
const container = this.#container;
|
|
15045
|
-
if (!container) return;
|
|
15046
|
-
|
|
15047
|
-
if (this.#angleTooltip) {
|
|
15048
|
-
this.#angleTooltip.setAttribute("show", "true");
|
|
15049
|
-
this.#angleTooltip.showPopup?.();
|
|
15050
|
-
}
|
|
15051
|
-
|
|
15052
|
-
const prevBodyCursor = document.body.style.cursor;
|
|
15053
|
-
let lastCursorDeg = Math.round(this.#angle);
|
|
15054
|
-
document.body.style.cursor = this.#rotateCursorSvg(lastCursorDeg);
|
|
15055
|
-
|
|
15056
|
-
const onMove = (ev) => {
|
|
15057
|
-
const rect = container.getBoundingClientRect();
|
|
15058
|
-
const cx = (this.#x / 100) * rect.width;
|
|
15059
|
-
const cy = (this.#y / 100) * rect.height;
|
|
15060
|
-
const dx = ev.clientX - rect.left - cx;
|
|
15061
|
-
const dy = ev.clientY - rect.top - cy;
|
|
15062
|
-
let angle = (Math.atan2(dy, dx) * 180) / Math.PI;
|
|
15063
|
-
if (this.#shouldSnap(ev.shiftKey)) {
|
|
15064
|
-
angle = Math.round(angle / 15) * 15;
|
|
15065
|
-
}
|
|
15066
|
-
this.#angle = angle;
|
|
15067
|
-
if (this.#angleTooltip)
|
|
15068
|
-
this.#angleTooltip.setAttribute(
|
|
15069
|
-
"text",
|
|
15070
|
-
`Angle ${Math.round(angle)}°`,
|
|
15071
|
-
);
|
|
15072
|
-
this.#syncPositions();
|
|
15073
|
-
const curDeg = Math.round(angle);
|
|
15074
|
-
if (curDeg !== lastCursorDeg) {
|
|
15075
|
-
lastCursorDeg = curDeg;
|
|
15076
|
-
document.body.style.cursor = this.#rotateCursorSvg(curDeg);
|
|
15077
|
-
}
|
|
15078
|
-
this.#emitInput();
|
|
15079
|
-
};
|
|
15080
|
-
|
|
15081
|
-
const onUp = () => {
|
|
15082
|
-
this.#isAngleDragging = false;
|
|
15083
|
-
this.classList.remove("fig-canvas-control-ring-active");
|
|
15084
|
-
document.body.style.cursor = prevBodyCursor;
|
|
15085
|
-
if (this.#angleTooltip) this.#angleTooltip.removeAttribute("show");
|
|
15086
|
-
this.#syncValueAttribute();
|
|
15087
|
-
this.#emitChange();
|
|
15088
|
-
window.removeEventListener("pointermove", onMove);
|
|
15089
|
-
window.removeEventListener("pointerup", onUp);
|
|
15090
|
-
};
|
|
15091
|
-
|
|
15092
|
-
window.addEventListener("pointermove", onMove);
|
|
15093
|
-
window.addEventListener("pointerup", onUp);
|
|
15094
|
-
});
|
|
15095
|
-
}
|
|
15096
|
-
|
|
15097
|
-
if (this.#secondHandle) {
|
|
15098
|
-
this.#secondHandle.addEventListener("input", (e) => {
|
|
15099
|
-
e.stopPropagation();
|
|
15100
|
-
if (!this.#isSecondDragging) {
|
|
15101
|
-
this.#prevBodyCursor = document.body.style.cursor;
|
|
15102
|
-
}
|
|
15103
|
-
this.#isSecondDragging = true;
|
|
15104
|
-
const px = e.detail?.px ?? this.#x2 / 100;
|
|
15105
|
-
const py = e.detail?.py ?? this.#y2 / 100;
|
|
15106
|
-
this.#x2 = Math.round(Math.max(0, Math.min(100, px * 100)));
|
|
15107
|
-
this.#y2 = Math.round(Math.max(0, Math.min(100, py * 100)));
|
|
15108
|
-
if (this.#secondTooltip) {
|
|
15109
|
-
this.#secondTooltip.setAttribute("text", this.#secondTipText);
|
|
15110
|
-
this.#secondTooltip.setAttribute("show", "true");
|
|
15111
|
-
this.#secondTooltip.showPopup?.();
|
|
15112
|
-
}
|
|
15113
|
-
this.#syncPositions();
|
|
15114
|
-
document.body.style.cursor = this.#resizeCursorSvg(
|
|
15115
|
-
this.#pointPointLineDeg(),
|
|
15116
|
-
);
|
|
15117
|
-
this.#emitInput();
|
|
15118
|
-
});
|
|
13623
|
+
this.prepend(this.#navStart);
|
|
13624
|
+
this.append(this.#navEnd);
|
|
13625
|
+
}
|
|
15119
13626
|
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
this.#syncPositions();
|
|
15125
|
-
this.#syncValueAttribute();
|
|
15126
|
-
this.#emitChange();
|
|
15127
|
-
requestAnimationFrame(() => {
|
|
15128
|
-
this.#isSecondDragging = false;
|
|
15129
|
-
});
|
|
15130
|
-
});
|
|
13627
|
+
#scrollByPage(direction) {
|
|
13628
|
+
const isHorizontal = this.getAttribute("layout") === "horizontal";
|
|
13629
|
+
const pageSize = isHorizontal ? this.clientWidth : this.clientHeight;
|
|
13630
|
+
const scrollAmount = pageSize * 0.8 * direction;
|
|
15131
13631
|
|
|
15132
|
-
|
|
15133
|
-
|
|
15134
|
-
|
|
13632
|
+
this.scrollBy({
|
|
13633
|
+
[isHorizontal ? "left" : "top"]: scrollAmount,
|
|
13634
|
+
behavior: "smooth",
|
|
13635
|
+
});
|
|
15135
13636
|
}
|
|
15136
13637
|
|
|
15137
|
-
#
|
|
15138
|
-
if (!
|
|
15139
|
-
|
|
15140
|
-
|
|
15141
|
-
const
|
|
15142
|
-
if (!
|
|
15143
|
-
origEvent.preventDefault();
|
|
15144
|
-
this.#isDragging = true;
|
|
15145
|
-
const container = this.#container;
|
|
15146
|
-
if (!container) return;
|
|
15147
|
-
const rect = container.getBoundingClientRect();
|
|
13638
|
+
#scrollToChoice(el) {
|
|
13639
|
+
if (!el) return;
|
|
13640
|
+
requestAnimationFrame(() => {
|
|
13641
|
+
const overflowY = this.scrollHeight > this.clientHeight;
|
|
13642
|
+
const overflowX = this.scrollWidth > this.clientWidth;
|
|
13643
|
+
if (!overflowX && !overflowY) return;
|
|
15148
13644
|
|
|
15149
|
-
const
|
|
15150
|
-
const pivotY = isFirst ? this.#y2 : this.#y;
|
|
15151
|
-
const movingX = isFirst ? this.#x : this.#x2;
|
|
15152
|
-
const movingY = isFirst ? this.#y : this.#y2;
|
|
15153
|
-
const pcx = (pivotX / 100) * rect.width;
|
|
15154
|
-
const pcy = (pivotY / 100) * rect.height;
|
|
15155
|
-
const mcx = (movingX / 100) * rect.width;
|
|
15156
|
-
const mcy = (movingY / 100) * rect.height;
|
|
15157
|
-
const fixedLen = Math.sqrt((mcx - pcx) ** 2 + (mcy - pcy) ** 2);
|
|
15158
|
-
|
|
15159
|
-
const tooltip = isFirst ? this.#pointTooltip : this.#secondTooltip;
|
|
15160
|
-
if (tooltip) {
|
|
15161
|
-
tooltip.setAttribute("show", "true");
|
|
15162
|
-
tooltip.showPopup?.();
|
|
15163
|
-
}
|
|
15164
|
-
|
|
15165
|
-
const prevBodyCursor = document.body.style.cursor;
|
|
15166
|
-
const initDeg = this.#pointPointLineDeg();
|
|
15167
|
-
let lastCursorDeg = Math.round(isFirst ? initDeg + 180 : initDeg);
|
|
15168
|
-
document.body.style.cursor = this.#rotateCursorSvg(lastCursorDeg);
|
|
15169
|
-
|
|
15170
|
-
const onMove = (ev) => {
|
|
15171
|
-
const r = container.getBoundingClientRect();
|
|
15172
|
-
const px = (pivotX / 100) * r.width;
|
|
15173
|
-
const py = (pivotY / 100) * r.height;
|
|
15174
|
-
const dx = ev.clientX - r.left - px;
|
|
15175
|
-
const dy = ev.clientY - r.top - py;
|
|
15176
|
-
let angle = Math.atan2(dy, dx);
|
|
15177
|
-
if (this.#shouldSnap(ev.shiftKey)) {
|
|
15178
|
-
const snapDeg = Math.round((angle * 180) / Math.PI / 15) * 15;
|
|
15179
|
-
angle = (snapDeg * Math.PI) / 180;
|
|
15180
|
-
}
|
|
15181
|
-
const nx = px + fixedLen * Math.cos(angle);
|
|
15182
|
-
const ny = py + fixedLen * Math.sin(angle);
|
|
15183
|
-
const newPctX = Math.max(0, Math.min(100, (nx / r.width) * 100));
|
|
15184
|
-
const newPctY = Math.max(0, Math.min(100, (ny / r.height) * 100));
|
|
15185
|
-
if (isFirst) {
|
|
15186
|
-
this.#x = newPctX;
|
|
15187
|
-
this.#y = newPctY;
|
|
15188
|
-
} else {
|
|
15189
|
-
this.#x2 = newPctX;
|
|
15190
|
-
this.#y2 = newPctY;
|
|
15191
|
-
}
|
|
15192
|
-
this.#syncPositions();
|
|
15193
|
-
const curDeg = Math.round(
|
|
15194
|
-
isFirst ? this.#pointPointLineDeg() + 180 : this.#pointPointLineDeg(),
|
|
15195
|
-
);
|
|
15196
|
-
if (curDeg !== lastCursorDeg) {
|
|
15197
|
-
lastCursorDeg = curDeg;
|
|
15198
|
-
document.body.style.cursor = this.#rotateCursorSvg(curDeg);
|
|
15199
|
-
}
|
|
15200
|
-
this.#emitInput();
|
|
15201
|
-
};
|
|
13645
|
+
const options = { behavior: "smooth" };
|
|
15202
13646
|
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15207
|
-
|
|
15208
|
-
this.#emitChange();
|
|
15209
|
-
window.removeEventListener("pointermove", onMove);
|
|
15210
|
-
window.removeEventListener("pointerup", onUp);
|
|
15211
|
-
};
|
|
13647
|
+
if (overflowY) {
|
|
13648
|
+
const target =
|
|
13649
|
+
el.offsetTop - this.clientHeight / 2 + el.offsetHeight / 2;
|
|
13650
|
+
options.top = target;
|
|
13651
|
+
}
|
|
15212
13652
|
|
|
15213
|
-
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
|
|
13653
|
+
if (overflowX) {
|
|
13654
|
+
const target =
|
|
13655
|
+
el.offsetLeft - this.clientWidth / 2 + el.offsetWidth / 2;
|
|
13656
|
+
options.left = target;
|
|
13657
|
+
}
|
|
15217
13658
|
|
|
15218
|
-
|
|
15219
|
-
if (!circle) return;
|
|
15220
|
-
circle.addEventListener("pointermove", (e) => {
|
|
15221
|
-
if (this.#isRadiusDragging) return;
|
|
15222
|
-
const container = this.#container;
|
|
15223
|
-
if (!container) return;
|
|
15224
|
-
const rect = container.getBoundingClientRect();
|
|
15225
|
-
const cx = (this.#x / 100) * rect.width;
|
|
15226
|
-
const cy = (this.#y / 100) * rect.height;
|
|
15227
|
-
const deg =
|
|
15228
|
-
(Math.atan2(e.clientY - rect.top - cy, e.clientX - rect.left - cx) *
|
|
15229
|
-
180) /
|
|
15230
|
-
Math.PI;
|
|
15231
|
-
circle.style.cursor = this.#resizeCursorSvg(deg);
|
|
13659
|
+
this.scrollTo(options);
|
|
15232
13660
|
});
|
|
15233
|
-
const onDown = (e) => {
|
|
15234
|
-
if (this.hasAttribute("disabled")) return;
|
|
15235
|
-
e.preventDefault();
|
|
15236
|
-
e.stopPropagation();
|
|
15237
|
-
this.#isRadiusDragging = true;
|
|
15238
|
-
this.classList.add("fig-canvas-control-ring-active");
|
|
15239
|
-
const container = this.#container;
|
|
15240
|
-
if (!container) return;
|
|
15241
|
-
|
|
15242
|
-
if (this.#radiusTooltip) {
|
|
15243
|
-
this.#radiusTooltip.setAttribute("show", "true");
|
|
15244
|
-
this.#radiusTooltip.showPopup?.();
|
|
15245
|
-
}
|
|
15246
|
-
|
|
15247
|
-
const prevBodyCursor = document.body.style.cursor;
|
|
15248
|
-
circle.style.pointerEvents = "none";
|
|
15249
|
-
const rect0 = container.getBoundingClientRect();
|
|
15250
|
-
const cx0 = (this.#x / 100) * rect0.width;
|
|
15251
|
-
const cy0 = (this.#y / 100) * rect0.height;
|
|
15252
|
-
const initDeg =
|
|
15253
|
-
(Math.atan2(e.clientY - rect0.top - cy0, e.clientX - rect0.left - cx0) *
|
|
15254
|
-
180) /
|
|
15255
|
-
Math.PI;
|
|
15256
|
-
let lastCursorDeg = Math.round(initDeg);
|
|
15257
|
-
document.body.style.cursor = this.#resizeCursorSvg(lastCursorDeg);
|
|
15258
|
-
|
|
15259
|
-
const onMove = (ev) => {
|
|
15260
|
-
const rect = container.getBoundingClientRect();
|
|
15261
|
-
const cx = (this.#x / 100) * rect.width;
|
|
15262
|
-
const cy = (this.#y / 100) * rect.height;
|
|
15263
|
-
const dx = ev.clientX - rect.left - cx;
|
|
15264
|
-
const dy = ev.clientY - rect.top - cy;
|
|
15265
|
-
const curDeg = Math.round((Math.atan2(dy, dx) * 180) / Math.PI);
|
|
15266
|
-
if (curDeg !== lastCursorDeg) {
|
|
15267
|
-
lastCursorDeg = curDeg;
|
|
15268
|
-
document.body.style.cursor = this.#resizeCursorSvg(curDeg);
|
|
15269
|
-
}
|
|
15270
|
-
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
15271
|
-
if (this.#shouldSnap(ev.shiftKey)) {
|
|
15272
|
-
const step = this.#radiusIsPercent ? 5 : 10;
|
|
15273
|
-
if (this.#radiusIsPercent) {
|
|
15274
|
-
let pct = (dist / rect.width) * 100;
|
|
15275
|
-
pct = Math.round(pct / step) * step;
|
|
15276
|
-
dist = (pct / 100) * rect.width;
|
|
15277
|
-
} else {
|
|
15278
|
-
dist = Math.round(dist / step) * step;
|
|
15279
|
-
}
|
|
15280
|
-
}
|
|
15281
|
-
if (this.#radiusIsPercent) {
|
|
15282
|
-
this.#radius = Math.max(0, (dist / rect.width) * 100);
|
|
15283
|
-
} else {
|
|
15284
|
-
this.#radius = Math.max(0, dist);
|
|
15285
|
-
}
|
|
15286
|
-
if (this.#radiusTooltip)
|
|
15287
|
-
this.#radiusTooltip.setAttribute("text", this.#formatRadius());
|
|
15288
|
-
this.#syncPositions();
|
|
15289
|
-
this.#emitInput();
|
|
15290
|
-
};
|
|
15291
|
-
|
|
15292
|
-
const onUp = () => {
|
|
15293
|
-
this.#isRadiusDragging = false;
|
|
15294
|
-
this.classList.remove("fig-canvas-control-ring-active");
|
|
15295
|
-
circle.style.pointerEvents = "";
|
|
15296
|
-
document.body.style.cursor = prevBodyCursor;
|
|
15297
|
-
if (this.#radiusTooltip) this.#radiusTooltip.removeAttribute("show");
|
|
15298
|
-
this.#syncValueAttribute();
|
|
15299
|
-
this.#emitChange();
|
|
15300
|
-
window.removeEventListener("pointermove", onMove);
|
|
15301
|
-
window.removeEventListener("pointerup", onUp);
|
|
15302
|
-
};
|
|
15303
|
-
|
|
15304
|
-
window.addEventListener("pointermove", onMove);
|
|
15305
|
-
window.addEventListener("pointerup", onUp);
|
|
15306
|
-
};
|
|
15307
|
-
circle.addEventListener("pointerdown", onDown);
|
|
15308
|
-
this._radiusDragCleanup = () =>
|
|
15309
|
-
circle.removeEventListener("pointerdown", onDown);
|
|
15310
13661
|
}
|
|
15311
13662
|
|
|
15312
|
-
#
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
|
|
13663
|
+
#startObserver() {
|
|
13664
|
+
this.#mutationObserver?.disconnect();
|
|
13665
|
+
this.#mutationObserver = new MutationObserver(() => {
|
|
13666
|
+
const choices = this.choices;
|
|
13667
|
+
if (this.#selectedChoice && !choices.includes(this.#selectedChoice)) {
|
|
13668
|
+
this.#selectedChoice = null;
|
|
13669
|
+
this.#syncSelection();
|
|
13670
|
+
} else if (!this.#selectedChoice && choices.length) {
|
|
13671
|
+
this.#syncSelection();
|
|
13672
|
+
}
|
|
13673
|
+
});
|
|
13674
|
+
this.#mutationObserver.observe(this, { childList: true, subtree: true });
|
|
15317
13675
|
}
|
|
15318
13676
|
}
|
|
15319
|
-
customElements.define("fig-
|
|
13677
|
+
customElements.define("fig-chooser", FigChooser);
|
|
15320
13678
|
|
|
15321
13679
|
/* Handle */
|
|
15322
13680
|
class FigHandle extends HTMLElement {
|