@rogieking/figui3 2.14.0 → 2.15.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 CHANGED
@@ -911,6 +911,36 @@ const onChange = (e) => {
911
911
 
912
912
  ---
913
913
 
914
+ ## Breaking Changes / Migration
915
+
916
+ ### v2.15.0: Experimental Features
917
+
918
+ The `experimental` attribute now controls experimental CSS features instead of `variant="neue"`.
919
+
920
+ **Before (deprecated):**
921
+ ```html
922
+ <fig-dropdown variant="neue">
923
+ <option>Option 1</option>
924
+ </fig-dropdown>
925
+ ```
926
+
927
+ **After:**
928
+ ```html
929
+ <fig-dropdown experimental="modern">
930
+ <option>Option 1</option>
931
+ </fig-dropdown>
932
+ ```
933
+
934
+ The `experimental` attribute uses space-separated feature names for granular control:
935
+ - `experimental="modern"` - Enables the customizable select picker (`::picker(select)`, `appearance: base-select`)
936
+ - Future features can be added: `experimental="modern popover"`
937
+
938
+ Note: `variant="neue"` on `fig-slider` continues to work for visual styling.
939
+
940
+ See [CHANGELOG.md](CHANGELOG.md) for full details.
941
+
942
+ ---
943
+
914
944
  ## Theming
915
945
 
916
946
  FigUI3 automatically adapts to light and dark themes using CSS custom properties. The library uses Figma's color variable naming convention:
package/components.css CHANGED
@@ -478,7 +478,7 @@ input[type="text"][list] {
478
478
  }
479
479
 
480
480
  @supports (appearance: base-select) {
481
- fig-dropdown[variant="neue"] {
481
+ fig-dropdown[experimental~="modern"] {
482
482
  &[type="dropdown"] {
483
483
  select:before {
484
484
  content: attr(aria-label);
@@ -517,11 +517,14 @@ input[type="text"][list] {
517
517
 
518
518
  option {
519
519
  display: flex;
520
- gap: var(--spacer-1);
520
+ gap: var(--spacer-2);
521
521
  padding: 0 var(--spacer-4) 0 calc(var(--spacer-1) * 2 + var(--spacer-1));
522
522
  font-weight: var(--body-medium-fontWeight);
523
523
  color: var(--figma-color-text-menu);
524
524
  position: relative;
525
+ & * {
526
+ color: inherit;
527
+ }
525
528
  &[hidden] {
526
529
  display: none;
527
530
  }
@@ -598,6 +601,7 @@ input[type="text"][list] {
598
601
  /* https://codepen.io/editor/argyleink/pen/019c1f28-bbc2-7bac-ad4a-a7e41d3730f1 */
599
602
  ::picker(select) {
600
603
  appearance: base-select;
604
+ color-scheme: dark;
601
605
 
602
606
  /* Appearance/style */
603
607
  scrollbar-width: thin;
package/fig.js CHANGED
@@ -2538,6 +2538,8 @@ class FigInputColor extends HTMLElement {
2538
2538
  const useFigmaPicker = this.picker === "figma";
2539
2539
  const hidePicker = this.picker === "false";
2540
2540
  const showAlpha = this.getAttribute("alpha") === "true";
2541
+ const experimental = this.getAttribute("experimental");
2542
+ const expAttr = experimental ? `experimental="${experimental}"` : "";
2541
2543
 
2542
2544
  let html = ``;
2543
2545
  if (this.getAttribute("text")) {
@@ -2562,7 +2564,7 @@ class FigInputColor extends HTMLElement {
2562
2564
  let swatchElement = "";
2563
2565
  if (!hidePicker) {
2564
2566
  swatchElement = useFigmaPicker
2565
- ? `<fig-fill-picker mode="solid" ${
2567
+ ? `<fig-fill-picker mode="solid" ${expAttr} ${
2566
2568
  showAlpha ? "" : 'alpha="false"'
2567
2569
  } value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
2568
2570
  this.alpha
@@ -2580,7 +2582,7 @@ class FigInputColor extends HTMLElement {
2580
2582
  html = ``;
2581
2583
  } else {
2582
2584
  html = useFigmaPicker
2583
- ? `<fig-fill-picker mode="solid" ${
2585
+ ? `<fig-fill-picker mode="solid" ${expAttr} ${
2584
2586
  showAlpha ? "" : 'alpha="false"'
2585
2587
  } value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
2586
2588
  this.alpha
@@ -2772,7 +2774,7 @@ class FigInputColor extends HTMLElement {
2772
2774
  }
2773
2775
 
2774
2776
  static get observedAttributes() {
2775
- return ["value", "style", "mode", "picker"];
2777
+ return ["value", "style", "mode", "picker", "experimental"];
2776
2778
  }
2777
2779
 
2778
2780
  get mode() {
@@ -2954,7 +2956,7 @@ class FigInputFill extends HTMLElement {
2954
2956
  }
2955
2957
 
2956
2958
  static get observedAttributes() {
2957
- return ["value", "disabled", "mode"];
2959
+ return ["value", "disabled", "mode", "experimental"];
2958
2960
  }
2959
2961
 
2960
2962
  connectedCallback() {
@@ -3105,11 +3107,12 @@ class FigInputFill extends HTMLElement {
3105
3107
  }
3106
3108
 
3107
3109
  const modeAttr = this.getAttribute("mode");
3110
+ const experimentalAttr = this.getAttribute("experimental");
3108
3111
  this.innerHTML = `
3109
3112
  <div class="input-combo">
3110
3113
  <fig-fill-picker value='${fillPickerValue}' ${
3111
3114
  disabled ? "disabled" : ""
3112
- } ${modeAttr ? `mode="${modeAttr}"` : ""}></fig-fill-picker>
3115
+ } ${modeAttr ? `mode="${modeAttr}"` : ""} ${experimentalAttr ? `experimental="${experimentalAttr}"` : ""}></fig-fill-picker>
3113
3116
  ${controlsHtml}
3114
3117
  </div>`;
3115
3118
 
@@ -3551,6 +3554,17 @@ class FigInputFill extends HTMLElement {
3551
3554
  this.#render();
3552
3555
  }
3553
3556
  break;
3557
+ case "mode":
3558
+ case "experimental":
3559
+ // Pass through to internal fill picker
3560
+ if (this.#fillPicker) {
3561
+ if (newValue) {
3562
+ this.#fillPicker.setAttribute(name, newValue);
3563
+ } else {
3564
+ this.#fillPicker.removeAttribute(name);
3565
+ }
3566
+ }
3567
+ break;
3554
3568
  }
3555
3569
  }
3556
3570
  }
@@ -3896,6 +3910,8 @@ class FigComboInput extends HTMLElement {
3896
3910
  this.options = this.getOptionsFromAttribute();
3897
3911
  this.placeholder = this.getAttribute("placeholder") || "";
3898
3912
  this.value = this.getAttribute("value") || "";
3913
+ const experimental = this.getAttribute("experimental");
3914
+ const expAttr = experimental ? `experimental="${experimental}"` : "";
3899
3915
  this.innerHTML = `<div class="input-combo">
3900
3916
  <fig-input-text placeholder="${this.placeholder}">
3901
3917
  </fig-input-text>
@@ -3903,7 +3919,7 @@ class FigComboInput extends HTMLElement {
3903
3919
  <svg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
3904
3920
  <path d='M5.87868 7.12132L8 9.24264L10.1213 7.12132' stroke='currentColor' stroke-opacity="0.9" stroke-linecap='round'/>
3905
3921
  </svg>
3906
- <fig-dropdown type="dropdown">
3922
+ <fig-dropdown type="dropdown" ${expAttr}>
3907
3923
  ${this.options
3908
3924
  .map((option) => `<option>${option}</option>`)
3909
3925
  .join("")}
@@ -3932,7 +3948,7 @@ class FigComboInput extends HTMLElement {
3932
3948
  this.value = this.input.value;
3933
3949
  }
3934
3950
  static get observedAttributes() {
3935
- return ["options", "placeholder", "value", "disabled"];
3951
+ return ["options", "placeholder", "value", "disabled", "experimental"];
3936
3952
  }
3937
3953
  focus() {
3938
3954
  this.input.focus();
@@ -3979,6 +3995,15 @@ class FigComboInput extends HTMLElement {
3979
3995
  case "disabled":
3980
3996
  this.#applyDisabled(newValue !== null && newValue !== "false");
3981
3997
  break;
3998
+ case "experimental":
3999
+ if (this.dropdown) {
4000
+ if (newValue) {
4001
+ this.dropdown.setAttribute("experimental", newValue);
4002
+ } else {
4003
+ this.dropdown.removeAttribute("experimental");
4004
+ }
4005
+ }
4006
+ break;
3982
4007
  }
3983
4008
  }
3984
4009
  }
@@ -5323,7 +5348,7 @@ class FigFillPicker extends HTMLElement {
5323
5348
  }
5324
5349
 
5325
5350
  static get observedAttributes() {
5326
- return ["value", "disabled", "alpha", "mode"];
5351
+ return ["value", "disabled", "alpha", "mode", "experimental"];
5327
5352
  }
5328
5353
 
5329
5354
  connectedCallback() {
@@ -5607,6 +5632,9 @@ class FigFillPicker extends HTMLElement {
5607
5632
  }
5608
5633
 
5609
5634
  // Build header content - label if single mode, dropdown if multiple
5635
+ const experimental = this.getAttribute("experimental");
5636
+ const expAttr = experimental ? `experimental="${experimental}"` : "";
5637
+
5610
5638
  let headerContent;
5611
5639
  if (allowedModes.length === 1) {
5612
5640
  headerContent = `<span class="fig-fill-picker-type-label">${modeLabels[allowedModes[0]]}</span>`;
@@ -5614,7 +5642,7 @@ class FigFillPicker extends HTMLElement {
5614
5642
  const options = allowedModes
5615
5643
  .map((m) => `<option value="${m}">${modeLabels[m]}</option>`)
5616
5644
  .join("\n ");
5617
- headerContent = `<fig-dropdown class="fig-fill-picker-type" variant="neue" value="${this.#fillType}">
5645
+ headerContent = `<fig-dropdown class="fig-fill-picker-type" ${expAttr} value="${this.#fillType}">
5618
5646
  ${options}
5619
5647
  </fig-dropdown>`;
5620
5648
  }
@@ -5853,10 +5881,17 @@ class FigFillPicker extends HTMLElement {
5853
5881
  ctx.fillRect(0, 0, width, height);
5854
5882
  }
5855
5883
 
5856
- #updateHandlePosition() {
5884
+ #updateHandlePosition(retryCount = 0) {
5857
5885
  if (!this.#colorAreaHandle || !this.#colorArea) return;
5858
5886
 
5859
5887
  const rect = this.#colorArea.getBoundingClientRect();
5888
+
5889
+ // If the canvas isn't visible yet (0 dimensions), schedule a retry (max 5 attempts)
5890
+ if ((rect.width === 0 || rect.height === 0) && retryCount < 5) {
5891
+ requestAnimationFrame(() => this.#updateHandlePosition(retryCount + 1));
5892
+ return;
5893
+ }
5894
+
5860
5895
  const x = (this.#color.s / 100) * rect.width;
5861
5896
  const y = ((100 - this.#color.v) / 100) * rect.height;
5862
5897
 
@@ -5943,10 +5978,12 @@ class FigFillPicker extends HTMLElement {
5943
5978
  // ============ GRADIENT TAB ============
5944
5979
  #initGradientTab() {
5945
5980
  const container = this.#dialog.querySelector('[data-tab="gradient"]');
5981
+ const experimental = this.getAttribute("experimental");
5982
+ const expAttr = experimental ? `experimental="${experimental}"` : "";
5946
5983
 
5947
5984
  container.innerHTML = `
5948
5985
  <div class="fig-fill-picker-gradient-header">
5949
- <fig-dropdown class="fig-fill-picker-gradient-type" variant="neue" value="${
5986
+ <fig-dropdown class="fig-fill-picker-gradient-type" ${expAttr} value="${
5950
5987
  this.#gradient.type
5951
5988
  }">
5952
5989
  <option value="linear" selected>Linear</option>
package/index.html CHANGED
@@ -654,6 +654,12 @@
654
654
  <fig-combo-input options="Red, Green, Blue, Yellow, Purple"
655
655
  value="Blue"
656
656
  placeholder="Select a color"></fig-combo-input>
657
+
658
+ <h3>Experimental Modern</h3>
659
+ <p class="description">Uses the modern CSS picker styling for the dropdown.</p>
660
+ <fig-combo-input options="House, Apartment, Condo, Townhouse, Other"
661
+ experimental="modern"
662
+ placeholder="Type of residence"></fig-combo-input>
657
663
  </section>
658
664
  <hr>
659
665
 
@@ -840,7 +846,7 @@
840
846
  <p class="description">A select dropdown menu with options.</p>
841
847
 
842
848
  <h3>Default</h3>
843
- <fig-dropdown variant="neue">
849
+ <fig-dropdown>
844
850
  <optgroup label="Numbers">
845
851
  <option>One</option>
846
852
  <option>Two</option>
@@ -854,8 +860,7 @@
854
860
  </fig-dropdown>
855
861
 
856
862
  <h3>Full Width</h3>
857
- <fig-dropdown full
858
- variant="neue">
863
+ <fig-dropdown full>
859
864
  <option>Full Width Dropdown</option>
860
865
  <option>Option Two</option>
861
866
  <option>Option Three</option>
@@ -865,8 +870,7 @@
865
870
  <p style="font-size: 12px; color: var(--figma-color-text-secondary); margin-bottom: 8px;">
866
871
  Uses <code>field-sizing: content</code> to resize based on selected option.
867
872
  </p>
868
- <fig-dropdown autoresize
869
- variant="neue">
873
+ <fig-dropdown autoresize>
870
874
  <option>XS</option>
871
875
  <option>Small</option>
872
876
  <option>Medium</option>
@@ -875,8 +879,27 @@
875
879
  <option>XXL Super Extended Option</option>
876
880
  </fig-dropdown>
877
881
 
878
- <h3>Dropdown Neue Variant (with many options)</h3>
879
- <fig-dropdown variant="neue"
882
+ <h3>Experimental Modern</h3>
883
+ <p class="description">
884
+ Use <code>experimental="modern"</code> to enable the customizable select picker using
885
+ <code>::picker(select)</code> and <code>appearance: base-select</code>. This is a progressive
886
+ enhancement that falls back to native select on unsupported browsers.
887
+ </p>
888
+ <fig-dropdown experimental="modern">
889
+ <optgroup label="Numbers">
890
+ <option>One</option>
891
+ <option>Two</option>
892
+ <option>Three</option>
893
+ </optgroup>
894
+ <optgroup label="Fruits">
895
+ <option>Apple</option>
896
+ <option>Banana</option>
897
+ <option>Cherry</option>
898
+ </optgroup>
899
+ </fig-dropdown>
900
+
901
+ <h3>Experimental Modern - Dropdown Type (with many options)</h3>
902
+ <fig-dropdown experimental="modern"
880
903
  type="dropdown"
881
904
  label="Choose">
882
905
  <optgroup label="North America">
@@ -1007,9 +1030,9 @@
1007
1030
  </optgroup>
1008
1031
  </fig-dropdown>
1009
1032
 
1010
- <h3>Select Neue Variant (with many options)</h3>
1033
+ <h3>Experimental Modern - Select Type (with many options)</h3>
1011
1034
 
1012
- <fig-dropdown variant="neue">
1035
+ <fig-dropdown experimental="modern">
1013
1036
  <optgroup label="North America">
1014
1037
  <option value="us">United States</option>
1015
1038
  <option value="ca">Canada</option>
@@ -1137,6 +1160,41 @@
1137
1160
  <option value="zeplin">Zeplin</option>
1138
1161
  </optgroup>
1139
1162
  </fig-dropdown>
1163
+
1164
+ <h3>Experimental Modern - With Icons (Bleeding Edge)</h3>
1165
+ <p class="description">
1166
+ Using icons inside options requires <code>appearance: base-select</code> support.
1167
+ This is bleeding edge and may not work in all browsers.
1168
+ </p>
1169
+ <fig-dropdown experimental="modern">
1170
+ <option value="home">
1171
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
1172
+ <path d="M2 6.5L8 2L14 6.5V13.5C14 14 13.5 14.5 13 14.5H3C2.5 14.5 2 14 2 13.5V6.5Z" stroke="currentColor" stroke-opacity="0.9" fill="none"/>
1173
+ </svg>
1174
+ <label>Home</label>
1175
+ </option>
1176
+ <option value="settings">
1177
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
1178
+ <circle cx="8" cy="8" r="2" stroke="currentColor" stroke-opacity="0.9" fill="none"/>
1179
+ <path d="M8 1V3M8 13V15M1 8H3M13 8H15M2.5 2.5L4 4M12 12L13.5 13.5M2.5 13.5L4 12M12 4L13.5 2.5" stroke="currentColor" stroke-opacity="0.9"/>
1180
+ </svg>
1181
+ <label>Settings</label>
1182
+ </option>
1183
+ <option value="user">
1184
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
1185
+ <circle cx="8" cy="5" r="3" stroke="currentColor" stroke-opacity="0.9" fill="none"/>
1186
+ <path d="M2 14C2 11 4.5 9 8 9C11.5 9 14 11 14 14" stroke="currentColor" stroke-opacity="0.9" fill="none"/>
1187
+ </svg>
1188
+ <label>User Profile</label>
1189
+ </option>
1190
+ <option value="search">
1191
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
1192
+ <circle cx="6.5" cy="6.5" r="4" stroke="currentColor" stroke-opacity="0.9" fill="none"/>
1193
+ <path d="M10 10L14 14" stroke="currentColor" stroke-opacity="0.9"/>
1194
+ </svg>
1195
+ <label>Search</label>
1196
+ </option>
1197
+ </fig-dropdown>
1140
1198
  </section>
1141
1199
  <hr>
1142
1200
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "2.14.0",
3
+ "version": "2.15.0",
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": "MIT",