@rogieking/figui3 11.0.3 → 11.1.1

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/fig-editor.css CHANGED
@@ -735,7 +735,31 @@ fig-select {
735
735
  box-shadow: none;
736
736
  background-color: transparent;
737
737
 
738
- &:hover:not([disabled]):not([disabled="false"]) {
738
+ &:hover:not([disabled]):not([disabled="false"]):not(
739
+ [open]:not([open="false"])
740
+ ) {
741
+ background-color: var(--figma-color-bg-secondary);
742
+ }
743
+ }
744
+
745
+ &[variant="input"] {
746
+ box-shadow: none;
747
+ background-color: var(--figma-color-bg-secondary);
748
+ color: var(--figma-color-text);
749
+
750
+ &:hover:not([disabled]):not([disabled="false"]):not(
751
+ [open]:not([open="false"])
752
+ ) {
753
+ box-shadow: inset 0 0 0 1px var(--figma-color-bordertranslucent);
754
+ background-color: var(--figma-color-bg-transparent);
755
+ }
756
+
757
+ &:active:not([disabled]):not([disabled="false"]) {
758
+ background-color: var(--figma-color-bg-transparent-pressed);
759
+ color: var(--figma-color-text);
760
+ }
761
+
762
+ &:focus-within {
739
763
  background-color: var(--figma-color-bg-secondary);
740
764
  }
741
765
  }
package/fig-editor.js CHANGED
@@ -499,7 +499,7 @@ figEditorDefineElement("fig-select-options", FigSelectOptions);
499
499
 
500
500
  /**
501
501
  * A dropdown-styled select.
502
- * @attr {string} variant - Visual style. Use `ghost` for a borderless control.
502
+ * @attr {string} variant - Visual style: `ghost` or input-like `input`.
503
503
  * @attr {string} size - Control size. Use `large` for a 32px-tall control.
504
504
  * @attr {boolean} icon - Shows a square chevron-only trigger.
505
505
  * @attr {string} menu-anchor - CSS selector for an alternate menu anchor.
package/fig.js CHANGED
@@ -6586,6 +6586,7 @@ class FigSlider extends HTMLElement {
6586
6586
  value: this.value,
6587
6587
  units: this.units || null,
6588
6588
  precision: this.precision,
6589
+ disabled: this.disabled,
6589
6590
  }),
6590
6591
  );
6591
6592
  }
@@ -7025,8 +7026,7 @@ class FigSlider extends HTMLElement {
7025
7026
  this.disabled = this.input.disabled =
7026
7027
  newValue !== null && newValue !== "false";
7027
7028
  if (this.figInputNumber) {
7028
- this.figInputNumber.disabled = this.disabled;
7029
- this.figInputNumber.setAttribute("disabled", this.disabled);
7029
+ this.figInputNumber.toggleAttribute("disabled", this.disabled);
7030
7030
  }
7031
7031
  break;
7032
7032
  case "value":
@@ -12469,16 +12469,19 @@ class FigSwitch extends FigCheckbox {
12469
12469
  }
12470
12470
  figDefineElement("fig-switch", FigSwitch);
12471
12471
 
12472
- /* Combo Input */
12472
+ /* Split Input */
12473
12473
  /**
12474
- * A custom combo input with text and dropdown.
12474
+ * A split input with text and dropdown controls.
12475
+ * @attr {string} type - Main input type: "text" (default) or "number"
12475
12476
  * @attr {string} options - Comma-separated list of dropdown options
12476
12477
  * @attr {string} placeholder - Placeholder text for the input
12477
12478
  * @attr {string} value - The current input value
12478
12479
  * @attr {boolean} disabled - Disables the input and dropdown button
12480
+ * @attr {boolean} full - Expands the split input to the available width
12479
12481
  */
12480
- class FigComboInput extends HTMLElement {
12482
+ class FigSplitInput extends HTMLElement {
12481
12483
  static observedAttributes = [
12484
+ "type",
12482
12485
  "options",
12483
12486
  "placeholder",
12484
12487
  "value",
@@ -12490,11 +12493,11 @@ class FigComboInput extends HTMLElement {
12490
12493
  "aria-required",
12491
12494
  ];
12492
12495
 
12493
- #usesCustomDropdown = false;
12496
+ #usesCustomSelector = false;
12494
12497
  #input = null;
12495
- #dropdown = null;
12498
+ #selector = null;
12496
12499
  #button = null;
12497
- #customDropdown = null;
12500
+ #customSelector = null;
12498
12501
  #internalUpdate = false;
12499
12502
  #a11yAttributes = [
12500
12503
  "aria-label",
@@ -12504,10 +12507,10 @@ class FigComboInput extends HTMLElement {
12504
12507
  "aria-required",
12505
12508
  ];
12506
12509
 
12507
- #boundHandleDropdownInput = this.#handleDropdownInput.bind(this);
12508
- #boundHandleDropdownChange = this.#handleDropdownChange.bind(this);
12509
- #boundHandleTextInput = this.#handleTextInput.bind(this);
12510
- #boundHandleTextChange = this.#handleTextChange.bind(this);
12510
+ #boundHandleSelectorInput = this.#handleSelectorInput.bind(this);
12511
+ #boundHandleSelectorChange = this.#handleSelectorChange.bind(this);
12512
+ #boundHandleMainInput = this.#handleMainInput.bind(this);
12513
+ #boundHandleMainChange = this.#handleMainChange.bind(this);
12511
12514
 
12512
12515
  get value() {
12513
12516
  return this.getAttribute("value") || "";
@@ -12517,14 +12520,38 @@ class FigComboInput extends HTMLElement {
12517
12520
  this.setAttribute("value", val ?? "");
12518
12521
  }
12519
12522
 
12523
+ #inputTagName() {
12524
+ return this.getAttribute("type") === "number"
12525
+ ? "fig-input-number"
12526
+ : "fig-input-text";
12527
+ }
12528
+
12529
+ #createInput() {
12530
+ return figCreateElement(this.#inputTagName(), {
12531
+ placeholder: this.getAttribute("placeholder") || "",
12532
+ value: this.value,
12533
+ });
12534
+ }
12535
+
12520
12536
  #canReuseMarkup() {
12521
- return !!this.querySelector(":scope > .input-combo");
12537
+ const input = this.querySelector(
12538
+ ":scope > .input-combo > :is(fig-input-text, fig-input-number)",
12539
+ );
12540
+ return input?.localName === this.#inputTagName();
12522
12541
  }
12523
12542
 
12524
12543
  #refreshMarkup() {
12525
- this.#input = this.querySelector("fig-input-text");
12544
+ this.#input = this.querySelector(
12545
+ ":scope > .input-combo > :is(fig-input-text, fig-input-number)",
12546
+ );
12526
12547
  this.#button = this.querySelector("fig-button");
12527
- this.#dropdown = this.querySelector("fig-dropdown");
12548
+ this.#selector = this.querySelector("fig-select, fig-dropdown");
12549
+ if (
12550
+ this.#selector?.localName === "fig-select" &&
12551
+ customElements.get("fig-select")
12552
+ ) {
12553
+ this.#selector.menuAnchor = this;
12554
+ }
12528
12555
  if (this.#input) {
12529
12556
  this.#input.setAttribute("value", this.value);
12530
12557
  this.#input.setAttribute(
@@ -12536,17 +12563,24 @@ class FigComboInput extends HTMLElement {
12536
12563
  }
12537
12564
 
12538
12565
  connectedCallback() {
12539
- this.#customDropdown =
12566
+ this.#customSelector =
12540
12567
  Array.from(this.children).find(
12541
- (child) => child.tagName === "FIG-DROPDOWN",
12568
+ (child) =>
12569
+ child.tagName === "FIG-SELECT" || child.tagName === "FIG-DROPDOWN",
12542
12570
  ) || null;
12543
- this.#usesCustomDropdown = this.#customDropdown !== null;
12544
- if (this.#customDropdown) {
12545
- this.#customDropdown.remove();
12571
+ this.#usesCustomSelector = this.#customSelector !== null;
12572
+ if (this.#customSelector) {
12573
+ this.#customSelector.remove();
12546
12574
  }
12547
12575
 
12548
12576
  if (this.#canReuseMarkup()) {
12549
12577
  this.#refreshMarkup();
12578
+ if (!this.#customSelector) {
12579
+ this.#usesCustomSelector =
12580
+ !!this.#selector &&
12581
+ !this.#selector.hasAttribute("data-fig-split-generated");
12582
+ this.#customSelector = this.#usesCustomSelector ? this.#selector : null;
12583
+ }
12550
12584
  this.#setupListeners();
12551
12585
  } else {
12552
12586
  this.#render();
@@ -12562,93 +12596,112 @@ class FigComboInput extends HTMLElement {
12562
12596
 
12563
12597
  #render() {
12564
12598
  const options = this.#getOptions();
12565
- const placeholder = this.getAttribute("placeholder") || "";
12566
- const currentValue = this.value;
12567
12599
  const dropdownLabel = this.#dropdownLabel();
12568
12600
 
12569
- const input = figCreateElement("fig-input-text", {
12570
- placeholder,
12571
- value: currentValue,
12572
- });
12573
- const button = figCreateElement(
12574
- "fig-button",
12575
- {
12576
- type: "select",
12577
- variant: "input",
12601
+ const input = this.#createInput();
12602
+ let selector = this.#customSelector;
12603
+ if (!selector && customElements.get("fig-select")) {
12604
+ selector = figCreateElement("fig-select", {
12578
12605
  icon: true,
12579
- },
12580
- );
12581
- if (!this.#usesCustomDropdown) {
12582
- button.appendChild(
12583
- figCreateElement(
12584
- "fig-dropdown",
12585
- {
12586
- type: "dropdown",
12587
- label: dropdownLabel,
12588
- },
12589
- options.map((option) =>
12590
- figCreateElement("option", {}, option.trim()),
12591
- ),
12606
+ variant: "input",
12607
+ options: options.join(","),
12608
+ label: dropdownLabel,
12609
+ "aria-label": dropdownLabel,
12610
+ "data-fig-split-generated": true,
12611
+ });
12612
+ }
12613
+ if (!selector) {
12614
+ selector = figCreateElement(
12615
+ "fig-dropdown",
12616
+ {
12617
+ type: "dropdown",
12618
+ label: dropdownLabel,
12619
+ "data-fig-split-generated": true,
12620
+ },
12621
+ options.map((option) =>
12622
+ figCreateElement("option", {}, option.trim()),
12592
12623
  ),
12593
12624
  );
12594
12625
  }
12626
+
12627
+ let action = selector;
12628
+ let button = null;
12629
+ if (selector.localName === "fig-dropdown") {
12630
+ if (!selector.hasAttribute("type")) {
12631
+ selector.setAttribute("type", "dropdown");
12632
+ }
12633
+ button = figCreateElement(
12634
+ "fig-button",
12635
+ {
12636
+ type: "select",
12637
+ variant: "input",
12638
+ icon: true,
12639
+ },
12640
+ figCreateElement("fig-icon", {
12641
+ name: "chevron",
12642
+ size: "small",
12643
+ slot: "append",
12644
+ }),
12645
+ );
12646
+ button.append(selector);
12647
+ action = button;
12648
+ }
12649
+ if (!selector.hasAttribute("label")) {
12650
+ selector.setAttribute("label", dropdownLabel);
12651
+ }
12652
+
12595
12653
  this.replaceChildren(
12596
- figCreateElement("div", { className: "input-combo" }, [input, button]),
12654
+ figCreateElement("div", { className: "input-combo" }, [input, action]),
12597
12655
  );
12598
12656
 
12599
12657
  this.#input = input;
12600
12658
  this.#button = button;
12601
-
12602
- if (this.#usesCustomDropdown && this.#customDropdown && this.#button) {
12603
- if (!this.#customDropdown.hasAttribute("type")) {
12604
- this.#customDropdown.setAttribute("type", "dropdown");
12605
- }
12606
- if (!this.#customDropdown.hasAttribute("label")) {
12607
- this.#customDropdown.setAttribute("label", dropdownLabel);
12608
- }
12609
- this.#button.append(this.#customDropdown);
12659
+ this.#selector = selector;
12660
+ if (
12661
+ selector.localName === "fig-select" &&
12662
+ customElements.get("fig-select")
12663
+ ) {
12664
+ selector.menuAnchor = this;
12610
12665
  }
12611
-
12612
- this.#dropdown = this.querySelector("fig-dropdown");
12613
12666
  this.#syncA11yAttributes();
12614
12667
  }
12615
12668
 
12616
12669
  #setupListeners() {
12617
12670
  this.#teardownListeners();
12618
- this.#dropdown?.addEventListener("input", this.#boundHandleDropdownInput);
12619
- this.#dropdown?.addEventListener("change", this.#boundHandleDropdownChange);
12620
- this.#input?.addEventListener("input", this.#boundHandleTextInput);
12621
- this.#input?.addEventListener("change", this.#boundHandleTextChange);
12671
+ this.#selector?.addEventListener("input", this.#boundHandleSelectorInput);
12672
+ this.#selector?.addEventListener("change", this.#boundHandleSelectorChange);
12673
+ this.#input?.addEventListener("input", this.#boundHandleMainInput);
12674
+ this.#input?.addEventListener("change", this.#boundHandleMainChange);
12622
12675
  }
12623
12676
 
12624
12677
  #teardownListeners() {
12625
- this.#dropdown?.removeEventListener("input", this.#boundHandleDropdownInput);
12626
- this.#dropdown?.removeEventListener("change", this.#boundHandleDropdownChange);
12627
- this.#input?.removeEventListener("input", this.#boundHandleTextInput);
12628
- this.#input?.removeEventListener("change", this.#boundHandleTextChange);
12678
+ this.#selector?.removeEventListener("input", this.#boundHandleSelectorInput);
12679
+ this.#selector?.removeEventListener("change", this.#boundHandleSelectorChange);
12680
+ this.#input?.removeEventListener("input", this.#boundHandleMainInput);
12681
+ this.#input?.removeEventListener("change", this.#boundHandleMainChange);
12629
12682
  }
12630
12683
 
12631
- #handleDropdownInput(e) {
12684
+ #handleSelectorInput(e) {
12632
12685
  e.stopPropagation();
12633
- this.#syncDropdownValue(e);
12686
+ this.#syncSelectorValue(e);
12634
12687
  this.#emitInput();
12635
12688
  }
12636
12689
 
12637
- #handleDropdownChange(e) {
12690
+ #handleSelectorChange(e) {
12638
12691
  e.stopPropagation();
12639
- this.#syncDropdownValue(e);
12692
+ this.#syncSelectorValue(e);
12640
12693
  this.#emitChange();
12641
12694
  }
12642
12695
 
12643
- #syncDropdownValue(e) {
12644
- const val = e.target.closest("fig-dropdown")?.value ?? "";
12696
+ #syncSelectorValue(e) {
12697
+ const val = e.target.closest("fig-select, fig-dropdown")?.value ?? "";
12645
12698
  this.#internalUpdate = true;
12646
12699
  this.setAttribute("value", val);
12647
12700
  this.#internalUpdate = false;
12648
12701
  if (this.#input) this.#input.setAttribute("value", val);
12649
12702
  }
12650
12703
 
12651
- #handleTextInput(e) {
12704
+ #handleMainInput(e) {
12652
12705
  e.stopPropagation();
12653
12706
  const val = e.target.value ?? "";
12654
12707
  this.#internalUpdate = true;
@@ -12657,7 +12710,7 @@ class FigComboInput extends HTMLElement {
12657
12710
  this.#emitInput();
12658
12711
  }
12659
12712
 
12660
- #handleTextChange(e) {
12713
+ #handleMainChange(e) {
12661
12714
  e.stopPropagation();
12662
12715
  const val = e.target.value ?? "";
12663
12716
  this.#internalUpdate = true;
@@ -12697,7 +12750,7 @@ class FigComboInput extends HTMLElement {
12697
12750
  return (
12698
12751
  this.getAttribute("aria-label") ||
12699
12752
  this.getAttribute("placeholder") ||
12700
- "Combo input"
12753
+ "Split input"
12701
12754
  ).trim();
12702
12755
  }
12703
12756
 
@@ -12713,8 +12766,19 @@ class FigComboInput extends HTMLElement {
12713
12766
  else this.#input.setAttribute(name, value);
12714
12767
  });
12715
12768
  }
12716
- if (this.#dropdown && !this.#dropdown.hasAttribute("aria-label")) {
12717
- this.#dropdown.setAttribute("label", this.#dropdownLabel());
12769
+ if (this.#selector) {
12770
+ const label = this.#dropdownLabel();
12771
+ if (!this.#usesCustomSelector) {
12772
+ this.#selector.setAttribute("label", label);
12773
+ if (this.#selector.localName === "fig-select") {
12774
+ this.#selector.setAttribute("aria-label", label);
12775
+ }
12776
+ } else if (
12777
+ !this.#selector.hasAttribute("aria-label") &&
12778
+ !this.#selector.hasAttribute("label")
12779
+ ) {
12780
+ this.#selector.setAttribute("label", label);
12781
+ }
12718
12782
  }
12719
12783
  }
12720
12784
 
@@ -12727,9 +12791,9 @@ class FigComboInput extends HTMLElement {
12727
12791
  if (disabled) this.#button.setAttribute("disabled", "");
12728
12792
  else this.#button.removeAttribute("disabled");
12729
12793
  }
12730
- if (this.#dropdown) {
12731
- if (disabled) this.#dropdown.setAttribute("disabled", "");
12732
- else this.#dropdown.removeAttribute("disabled");
12794
+ if (this.#selector) {
12795
+ if (disabled) this.#selector.setAttribute("disabled", "");
12796
+ else this.#selector.removeAttribute("disabled");
12733
12797
  }
12734
12798
  }
12735
12799
 
@@ -12740,16 +12804,33 @@ class FigComboInput extends HTMLElement {
12740
12804
  attributeChangedCallback(name, oldValue, newValue) {
12741
12805
  if (oldValue === newValue) return;
12742
12806
  switch (name) {
12807
+ case "type": {
12808
+ if (!this.#input || this.#input.localName === this.#inputTagName()) break;
12809
+ const restoreFocus = this.#input.matches(":focus-within");
12810
+ this.#teardownListeners();
12811
+ const input = this.#createInput();
12812
+ this.#input.replaceWith(input);
12813
+ this.#input = input;
12814
+ this.#syncA11yAttributes();
12815
+ this.#applyDisabled(figBooleanAttribute(this, "disabled"));
12816
+ this.#setupListeners();
12817
+ if (restoreFocus) this.focus();
12818
+ break;
12819
+ }
12743
12820
  case "options":
12744
- if (this.#dropdown && !this.#usesCustomDropdown) {
12745
- const options = this.#getOptions();
12746
- this.#dropdown.replaceChildren(
12747
- ...options.map((value) => {
12748
- const option = document.createElement("option");
12749
- option.textContent = value;
12750
- return option;
12751
- }),
12752
- );
12821
+ if (this.#selector && !this.#usesCustomSelector) {
12822
+ if (this.#selector.localName === "fig-select") {
12823
+ this.#selector.setAttribute("options", newValue || "");
12824
+ } else {
12825
+ const options = this.#getOptions();
12826
+ this.#selector.replaceChildren(
12827
+ ...options.map((value) => {
12828
+ const option = document.createElement("option");
12829
+ option.textContent = value;
12830
+ return option;
12831
+ }),
12832
+ );
12833
+ }
12753
12834
  }
12754
12835
  break;
12755
12836
  case "placeholder":
@@ -12774,6 +12855,10 @@ class FigComboInput extends HTMLElement {
12774
12855
  }
12775
12856
  }
12776
12857
  }
12858
+ figDefineElement("fig-split-input", FigSplitInput);
12859
+
12860
+ /** @deprecated Use fig-split-input. */
12861
+ class FigComboInput extends FigSplitInput {}
12777
12862
  figDefineElement("fig-combo-input", FigComboInput);
12778
12863
 
12779
12864
  /* Swatch */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "11.0.3",
3
+ "version": "11.1.1",
4
4
  "description": "A lightweight web components library for building Figma plugin and widget UIs with native look and feel",
5
5
  "author": "Rogie King",
6
6
  "license": "SEE LICENSE IN LICENSE",