@rogieking/figui3 8.9.28 → 8.9.30

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-lab.js CHANGED
@@ -4639,6 +4639,7 @@ class PropskitSlider extends HTMLElement {
4639
4639
  "data-wave-index",
4640
4640
  "data-active",
4641
4641
  "data-elastic-dragging",
4642
+ "default",
4642
4643
  "style",
4643
4644
  ]);
4644
4645
 
@@ -4761,8 +4762,11 @@ class PropskitSlider extends HTMLElement {
4761
4762
  }
4762
4763
 
4763
4764
  #initialize() {
4765
+ const sliderType = (this.getAttribute("type") || "range").toLowerCase();
4764
4766
  this.#initialValue =
4765
- this.getAttribute("value") ?? this.getAttribute("min") ?? "0";
4767
+ this.getAttribute("value") ??
4768
+ this.getAttribute("default") ??
4769
+ (sliderType === "delta" ? "0" : this.getAttribute("min") ?? "0");
4766
4770
  const initialChildren = Array.from(this.childNodes).filter((node) => {
4767
4771
  return (
4768
4772
  node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim())
@@ -4866,12 +4870,15 @@ class PropskitSlider extends HTMLElement {
4866
4870
  this.#slider.setAttribute("text", "true");
4867
4871
 
4868
4872
  const sliderType = (this.getAttribute("type") || "range").toLowerCase();
4869
- if (sliderType === "delta" || sliderType === "stepper") {
4870
- this.#slider.setAttribute(
4871
- "default",
4872
- this.getAttribute("default") ?? "50",
4873
- );
4874
- } else if (!this.hasAttribute("default")) {
4873
+ if (sliderType === "delta") {
4874
+ const min = Number(this.#slider.min);
4875
+ const max = Number(this.#slider.max);
4876
+ const midpoint =
4877
+ Number.isFinite(min) && Number.isFinite(max)
4878
+ ? min + (max - min) / 2
4879
+ : 0;
4880
+ this.#slider.setAttribute("default", String(midpoint));
4881
+ } else {
4875
4882
  this.#slider.removeAttribute("default");
4876
4883
  }
4877
4884
  if (sliderType === "stepper") {
@@ -4910,8 +4917,10 @@ class PropskitSlider extends HTMLElement {
4910
4917
  }
4911
4918
 
4912
4919
  #pushExternalValueToSlider() {
4913
- if (!this.#slider || !this.hasAttribute("value")) return;
4914
- const next = this.getAttribute("value") ?? "";
4920
+ if (!this.#slider) return;
4921
+ const next = this.hasAttribute("value")
4922
+ ? this.getAttribute("value") ?? ""
4923
+ : this.#initialValue ?? "";
4915
4924
  if (String(this.#slider.value) !== next) {
4916
4925
  this.#slider.value = next;
4917
4926
  }
@@ -5313,7 +5322,6 @@ class PropskitSlider extends HTMLElement {
5313
5322
  #defaultValue() {
5314
5323
  return (
5315
5324
  this.getAttribute("default") ??
5316
- this.#slider?.getAttribute("default") ??
5317
5325
  this.#initialValue ??
5318
5326
  this.#rangeInput?.min ??
5319
5327
  "0"
@@ -7163,6 +7171,7 @@ class PropskitOscillator extends HTMLElement {
7163
7171
  { text: label },
7164
7172
  figLabCreateElement("fig-handle", {
7165
7173
  size: "small",
7174
+ type: "minimal",
7166
7175
  "aria-label": `Oscillator ${type} handle`,
7167
7176
  }),
7168
7177
  ),
package/fig.js CHANGED
@@ -600,7 +600,10 @@ class FigButton extends HTMLElement {
600
600
  #a11yAttributes = ["aria-label", "aria-labelledby", "aria-describedby", "title"];
601
601
  #boundHandleControlKeydown = this.#handleControlKeydown.bind(this);
602
602
  #boundHandleClick = this.#handleClick.bind(this);
603
- #boundHandleSlotChange = () => this.#syncSlottedControlDisabled();
603
+ #boundHandleSlotChange = () => {
604
+ this.#syncSlottedControlDisabled();
605
+ this.#syncSelectChevron();
606
+ };
604
607
  #boundHandleFocus = () => {
605
608
  if (this.button?.matches(":focus-visible")) {
606
609
  this.setAttribute("data-focus-visible", "");
@@ -653,14 +656,22 @@ class FigButton extends HTMLElement {
653
656
  :host([size="large"][icon]) .fig-button-control {
654
657
  padding: 0;
655
658
  }
659
+ :host([type="select"]) .fig-button-control {
660
+ gap: var(--spacer-1);
661
+ }
656
662
  `;
663
+ const prependSlot = document.createElement("slot");
664
+ prependSlot.name = "prepend";
665
+ const defaultSlot = document.createElement("slot");
666
+ const appendSlot = document.createElement("slot");
667
+ appendSlot.name = "append";
657
668
  const control = figCreateElement(
658
669
  controlTag,
659
670
  {
660
671
  className: "fig-button-control",
661
672
  type: isControlWrapper ? null : "button",
662
673
  },
663
- document.createElement("slot"),
674
+ [prependSlot, defaultSlot, appendSlot],
664
675
  );
665
676
  this.shadowRoot.replaceChildren(style, control);
666
677
 
@@ -670,9 +681,10 @@ class FigButton extends HTMLElement {
670
681
  this.button.addEventListener("blur", this.#boundHandleBlur);
671
682
  }
672
683
 
673
- const slot = this.shadowRoot.querySelector("slot");
674
- slot?.removeEventListener("slotchange", this.#boundHandleSlotChange);
675
- slot?.addEventListener("slotchange", this.#boundHandleSlotChange);
684
+ this.shadowRoot.querySelectorAll("slot").forEach((slot) => {
685
+ slot.removeEventListener("slotchange", this.#boundHandleSlotChange);
686
+ slot.addEventListener("slotchange", this.#boundHandleSlotChange);
687
+ });
676
688
  this.removeEventListener("keydown", this.#boundHandleControlKeydown);
677
689
  this.addEventListener("keydown", this.#boundHandleControlKeydown);
678
690
 
@@ -681,6 +693,7 @@ class FigButton extends HTMLElement {
681
693
  this.getAttribute("selected") !== "false";
682
694
 
683
695
  this.#syncButtonAttributes();
696
+ this.#syncSelectChevron();
684
697
  }
685
698
 
686
699
  get type() {
@@ -767,6 +780,29 @@ class FigButton extends HTMLElement {
767
780
  this.setAttribute("aria-pressed", pressed ? "true" : "false");
768
781
  this.button.setAttribute("aria-pressed", pressed ? "true" : "false");
769
782
  }
783
+ #syncSelectChevron() {
784
+ const selector =
785
+ ':scope > fig-icon[slot="append"][data-generated="select-chevron"]';
786
+ const generatedIcons = Array.from(this.querySelectorAll(selector));
787
+ const generated = generatedIcons[0];
788
+ generatedIcons.slice(1).forEach((icon) => icon.remove());
789
+ if (this.type !== "select") {
790
+ generated?.remove();
791
+ return;
792
+ }
793
+ const authoredAppend = this.querySelector(
794
+ ':scope > [slot="append"]:not([data-generated])',
795
+ );
796
+ if (authoredAppend) {
797
+ generated?.remove();
798
+ return;
799
+ }
800
+ if (generated) return;
801
+ const chevron = createFigIcon("chevron", { size: "small" });
802
+ chevron.setAttribute("slot", "append");
803
+ chevron.setAttribute("data-generated", "select-chevron");
804
+ this.append(chevron);
805
+ }
770
806
  #syncA11yAttributes() {
771
807
  if (!this.button) return;
772
808
  if (!(this.button instanceof HTMLButtonElement)) return;
@@ -835,6 +871,7 @@ class FigButton extends HTMLElement {
835
871
  break;
836
872
  }
837
873
  this.#syncButtonAttributes();
874
+ this.#syncSelectChevron();
838
875
  break;
839
876
  }
840
877
  case "disabled":
@@ -851,9 +888,9 @@ class FigButton extends HTMLElement {
851
888
  }
852
889
  disconnectedCallback() {
853
890
  this.removeEventListener("keydown", this.#boundHandleControlKeydown);
854
- this.shadowRoot
855
- ?.querySelector("slot")
856
- ?.removeEventListener("slotchange", this.#boundHandleSlotChange);
891
+ this.shadowRoot?.querySelectorAll("slot").forEach((slot) => {
892
+ slot.removeEventListener("slotchange", this.#boundHandleSlotChange);
893
+ });
857
894
  }
858
895
  }
859
896
  figDefineElement("fig-button", FigButton);
@@ -5663,11 +5700,7 @@ class FigSlider extends HTMLElement {
5663
5700
  this.step = readNumber("step", defaults.step);
5664
5701
  if (!(this.step > 0)) this.step = defaults.step > 0 ? defaults.step : 1;
5665
5702
  this.color = this.getAttribute("color") || defaults?.color;
5666
- this.default = this.hasAttribute("default")
5667
- ? this.getAttribute("default")
5668
- : this.type === "delta"
5669
- ? 0
5670
- : this.min;
5703
+ this.default = this.#resolveDefaultValue();
5671
5704
  this.value = this.#normalizeSliderValue(rawValue);
5672
5705
  }
5673
5706
 
@@ -5735,12 +5768,7 @@ class FigSlider extends HTMLElement {
5735
5768
  this.input.removeEventListener("pointercancel", this.#boundRangePointerUp);
5736
5769
  this.input.addEventListener("pointercancel", this.#boundRangePointerUp);
5737
5770
 
5738
- if (this.default) {
5739
- this.style.setProperty(
5740
- "--default",
5741
- this.#calculateNormal(this.default),
5742
- );
5743
- }
5771
+ this.style.setProperty("--default", this.#calculateNormal(this.default));
5744
5772
 
5745
5773
  if (this.figInputNumber) {
5746
5774
  this.#syncTextInputA11yAttributes();
@@ -5827,6 +5855,7 @@ class FigSlider extends HTMLElement {
5827
5855
  this.input.setAttribute("list", this.datalist.getAttribute("id"));
5828
5856
  } else if (this.type === "stepper") {
5829
5857
  this.datalist = document.createElement("datalist");
5858
+ this.datalist.setAttribute("data-generated", "slider-stepper");
5830
5859
  this.datalist.setAttribute("id", figUniqueId());
5831
5860
  const steps = Math.min(
5832
5861
  1001,
@@ -5841,6 +5870,7 @@ class FigSlider extends HTMLElement {
5841
5870
  this.input.setAttribute("list", this.datalist.getAttribute("id"));
5842
5871
  } else if (this.type === "delta") {
5843
5872
  this.datalist = document.createElement("datalist");
5873
+ this.datalist.setAttribute("data-generated", "slider-delta");
5844
5874
  this.datalist.setAttribute("id", figUniqueId());
5845
5875
  let option = document.createElement("option");
5846
5876
  option.setAttribute("value", this.default);
@@ -5848,14 +5878,7 @@ class FigSlider extends HTMLElement {
5848
5878
  this.inputContainer.append(this.datalist);
5849
5879
  this.input.setAttribute("list", this.datalist.getAttribute("id"));
5850
5880
  }
5851
- if (this.datalist) {
5852
- const defaultOption = [...this.datalist.querySelectorAll("option")].find(
5853
- (option) => option.value === String(this.default),
5854
- );
5855
- if (defaultOption) {
5856
- defaultOption.setAttribute("default", "true");
5857
- }
5858
- }
5881
+ this.#syncDefaultDatalist();
5859
5882
  this.#syncValue();
5860
5883
  }
5861
5884
 
@@ -5901,6 +5924,10 @@ class FigSlider extends HTMLElement {
5901
5924
  if (this.input) this.#syncProperties();
5902
5925
  }
5903
5926
 
5927
+ get defaultValue() {
5928
+ return this.#resolveDefaultValue();
5929
+ }
5930
+
5904
5931
  disconnectedCallback() {
5905
5932
  this.#pendingRegeneration = false;
5906
5933
  if (this.input) {
@@ -5961,6 +5988,28 @@ class FigSlider extends HTMLElement {
5961
5988
  const { min, max } = this.#getBounds();
5962
5989
  return Math.min(max, Math.max(min, value));
5963
5990
  }
5991
+ #isStepAligned(value) {
5992
+ const { min } = this.#getBounds();
5993
+ const step = this.#toFiniteNumber(this.step);
5994
+ if (!(step > 0)) return true;
5995
+ const steps = (value - min) / step;
5996
+ const nearest = Math.round(steps);
5997
+ const tolerance = Number.EPSILON * Math.max(1, Math.abs(steps)) * 16;
5998
+ return Math.abs(steps - nearest) <= tolerance;
5999
+ }
6000
+ #resolveDefaultValue(rawDefault = this.getAttribute("default")) {
6001
+ const { min, max } = this.#getBounds();
6002
+ const parsed = this.#toFiniteNumber(rawDefault);
6003
+ const isValid =
6004
+ parsed !== null &&
6005
+ parsed >= min &&
6006
+ parsed <= max &&
6007
+ (this.type !== "stepper" || this.#isStepAligned(parsed));
6008
+ if (isValid) return parsed;
6009
+ return this.type === "delta"
6010
+ ? Math.min(max, Math.max(min, 0))
6011
+ : min;
6012
+ }
5964
6013
  #getFallbackValue() {
5965
6014
  if (this.type === "delta") {
5966
6015
  const deltaDefault = this.#toFiniteNumber(this.default);
@@ -6126,10 +6175,13 @@ class FigSlider extends HTMLElement {
6126
6175
  }
6127
6176
  if (!this.hasAttribute("default") && this.type !== "delta") {
6128
6177
  this.default = this.min;
6178
+ } else {
6179
+ this.default = this.#resolveDefaultValue();
6129
6180
  }
6130
6181
  this.value = this.#normalizeSliderValue(this.value);
6131
6182
  this.#syncProperties();
6132
6183
  this.#syncStepperDatalist();
6184
+ this.#syncDefaultDatalist();
6133
6185
  }
6134
6186
 
6135
6187
  #syncStepperDatalist() {
@@ -6146,6 +6198,25 @@ class FigSlider extends HTMLElement {
6146
6198
  }
6147
6199
  this.datalist.replaceChildren(fragment);
6148
6200
  this.input.setAttribute("list", this.datalist.id);
6201
+ this.#syncDefaultDatalist();
6202
+ }
6203
+
6204
+ #syncDefaultDatalist() {
6205
+ if (!this.datalist) return;
6206
+ const defaultValue = String(this.default);
6207
+ const options = [...this.datalist.querySelectorAll("option")];
6208
+ options.forEach((option) => option.removeAttribute("default"));
6209
+ let defaultOption = options.find((option) => option.value === defaultValue);
6210
+ if (
6211
+ !defaultOption &&
6212
+ this.type === "delta" &&
6213
+ this.datalist.getAttribute("data-generated") === "slider-delta"
6214
+ ) {
6215
+ defaultOption = document.createElement("option");
6216
+ defaultOption.value = defaultValue;
6217
+ this.datalist.replaceChildren(defaultOption);
6218
+ }
6219
+ defaultOption?.setAttribute("default", "true");
6149
6220
  }
6150
6221
 
6151
6222
  #regenerateWhenIdle() {
@@ -6235,9 +6306,9 @@ class FigSlider extends HTMLElement {
6235
6306
  }
6236
6307
  break;
6237
6308
  case "default":
6238
- this.default =
6239
- newValue !== null ? newValue : this.type === "delta" ? 0 : this.min;
6309
+ this.default = this.#resolveDefaultValue(newValue);
6240
6310
  this.#syncProperties();
6311
+ this.#syncDefaultDatalist();
6241
6312
  break;
6242
6313
  case "min":
6243
6314
  case "max":
@@ -11673,6 +11744,7 @@ class FigComboInput extends HTMLElement {
11673
11744
  ];
11674
11745
 
11675
11746
  #boundHandleDropdownInput = this.#handleDropdownInput.bind(this);
11747
+ #boundHandleDropdownChange = this.#handleDropdownChange.bind(this);
11676
11748
  #boundHandleTextInput = this.#handleTextInput.bind(this);
11677
11749
  #boundHandleTextChange = this.#handleTextChange.bind(this);
11678
11750
 
@@ -11737,21 +11809,6 @@ class FigComboInput extends HTMLElement {
11737
11809
  placeholder,
11738
11810
  value: currentValue,
11739
11811
  });
11740
- const icon = figCreateSvgElement(
11741
- "svg",
11742
- {
11743
- width: "16",
11744
- height: "16",
11745
- viewBox: "0 0 16 16",
11746
- fill: "none",
11747
- },
11748
- figCreateSvgElement("path", {
11749
- d: "M5.87868 7.12132L8 9.24264L10.1213 7.12132",
11750
- stroke: "currentColor",
11751
- "stroke-opacity": "0.9",
11752
- "stroke-linecap": "round",
11753
- }),
11754
- );
11755
11812
  const button = figCreateElement(
11756
11813
  "fig-button",
11757
11814
  {
@@ -11759,7 +11816,6 @@ class FigComboInput extends HTMLElement {
11759
11816
  variant: "input",
11760
11817
  icon: true,
11761
11818
  },
11762
- icon,
11763
11819
  );
11764
11820
  if (!this.#usesCustomDropdown) {
11765
11821
  button.appendChild(
@@ -11799,25 +11855,36 @@ class FigComboInput extends HTMLElement {
11799
11855
  #setupListeners() {
11800
11856
  this.#teardownListeners();
11801
11857
  this.#dropdown?.addEventListener("input", this.#boundHandleDropdownInput);
11858
+ this.#dropdown?.addEventListener("change", this.#boundHandleDropdownChange);
11802
11859
  this.#input?.addEventListener("input", this.#boundHandleTextInput);
11803
11860
  this.#input?.addEventListener("change", this.#boundHandleTextChange);
11804
11861
  }
11805
11862
 
11806
11863
  #teardownListeners() {
11807
11864
  this.#dropdown?.removeEventListener("input", this.#boundHandleDropdownInput);
11865
+ this.#dropdown?.removeEventListener("change", this.#boundHandleDropdownChange);
11808
11866
  this.#input?.removeEventListener("input", this.#boundHandleTextInput);
11809
11867
  this.#input?.removeEventListener("change", this.#boundHandleTextChange);
11810
11868
  }
11811
11869
 
11812
11870
  #handleDropdownInput(e) {
11813
11871
  e.stopPropagation();
11872
+ this.#syncDropdownValue(e);
11873
+ this.#emitInput();
11874
+ }
11875
+
11876
+ #handleDropdownChange(e) {
11877
+ e.stopPropagation();
11878
+ this.#syncDropdownValue(e);
11879
+ this.#emitChange();
11880
+ }
11881
+
11882
+ #syncDropdownValue(e) {
11814
11883
  const val = e.target.closest("fig-dropdown")?.value ?? "";
11815
11884
  this.#internalUpdate = true;
11816
11885
  this.setAttribute("value", val);
11817
11886
  this.#internalUpdate = false;
11818
11887
  if (this.#input) this.#input.setAttribute("value", val);
11819
- this.#emitInput();
11820
- this.#emitChange();
11821
11888
  }
11822
11889
 
11823
11890
  #handleTextInput(e) {
@@ -16817,6 +16884,7 @@ class FigGroup extends HTMLElement {
16817
16884
 
16818
16885
  attributeChangedCallback(name, oldValue, newValue) {
16819
16886
  if (oldValue === newValue) return;
16887
+ if (!this.isConnected) return;
16820
16888
  if (name === "open") {
16821
16889
  this.#header?.setAttribute("aria-expanded", String(this.open));
16822
16890
  return;
@@ -16865,7 +16933,9 @@ class FigGroup extends HTMLElement {
16865
16933
  const label = nameAttr || (isCollapsible ? "Group" : null);
16866
16934
 
16867
16935
  // Check if user supplied their own fig-header
16868
- const userHeader = this.querySelector(":scope > fig-header");
16936
+ const userHeader = this.querySelector(
16937
+ ":scope > fig-header:not([data-generated])",
16938
+ );
16869
16939
 
16870
16940
  if (!label && !isCollapsible && !userHeader) {
16871
16941
  if (this.#header && this.#header.dataset.generated) {
@@ -16877,6 +16947,9 @@ class FigGroup extends HTMLElement {
16877
16947
  }
16878
16948
 
16879
16949
  if (userHeader) {
16950
+ this.querySelectorAll(":scope > fig-header[data-generated]").forEach(
16951
+ (header) => header.remove(),
16952
+ );
16880
16953
  this.#header = userHeader;
16881
16954
  } else if (!this.#header || !this.#header.dataset.generated) {
16882
16955
  this.#header = document.createElement("fig-header");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "8.9.28",
3
+ "version": "8.9.30",
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",