@rogieking/figui3 2.14.0 → 2.17.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;
@@ -2518,6 +2522,16 @@ fig-segmented-control {
2518
2522
  }
2519
2523
  }
2520
2524
  }
2525
+
2526
+ &[disabled]:not([disabled="false"]) {
2527
+ pointer-events: none;
2528
+
2529
+ fig-segment {
2530
+ color: var(--figma-color-text-disabled);
2531
+ background-color: transparent;
2532
+ box-shadow: none;
2533
+ }
2534
+ }
2521
2535
  }
2522
2536
 
2523
2537
  fig-input-joystick {
package/fig.js CHANGED
@@ -167,6 +167,8 @@ customElements.define("fig-button", FigButton);
167
167
  class FigDropdown extends HTMLElement {
168
168
  #label = "Menu";
169
169
  #selectedValue = null; // Stores last selected value for dropdown type
170
+ #boundHandleSelectInput;
171
+ #boundHandleSelectChange;
170
172
 
171
173
  get label() {
172
174
  return this.#label;
@@ -179,11 +181,33 @@ class FigDropdown extends HTMLElement {
179
181
  this.select = document.createElement("select");
180
182
  this.optionsSlot = document.createElement("slot");
181
183
  this.attachShadow({ mode: "open" });
184
+ this.#boundHandleSelectInput = this.#handleSelectInput.bind(this);
185
+ this.#boundHandleSelectChange = this.#handleSelectChange.bind(this);
182
186
  }
183
187
 
184
188
  #addEventListeners() {
185
- this.select.addEventListener("input", this.#handleSelectInput.bind(this));
186
- this.select.addEventListener("change", this.#handleSelectChange.bind(this));
189
+ this.select.addEventListener("input", this.#boundHandleSelectInput);
190
+ this.select.addEventListener("change", this.#boundHandleSelectChange);
191
+ }
192
+
193
+ #hasPersistentControl(optionEl) {
194
+ if (!optionEl || !(optionEl instanceof Element)) return false;
195
+ return !!optionEl.querySelector(
196
+ 'fig-checkbox, fig-switch, input[type="checkbox"]'
197
+ );
198
+ }
199
+
200
+ #keepPickerOpen() {
201
+ // Keep menu open for interactive controls inside option content.
202
+ if (typeof this.select.showPicker === "function") {
203
+ requestAnimationFrame(() => {
204
+ try {
205
+ this.select.showPicker();
206
+ } catch {
207
+ // Ignore if browser blocks reopening picker
208
+ }
209
+ });
210
+ }
187
211
  }
188
212
 
189
213
  connectedCallback() {
@@ -224,6 +248,15 @@ class FigDropdown extends HTMLElement {
224
248
  }
225
249
 
226
250
  #handleSelectInput(e) {
251
+ const selectedOption = e.target.selectedOptions?.[0];
252
+ if (this.#hasPersistentControl(selectedOption)) {
253
+ if (this.type === "dropdown") {
254
+ this.select.selectedIndex = -1;
255
+ }
256
+ this.#keepPickerOpen();
257
+ return;
258
+ }
259
+
227
260
  const selectedValue = e.target.value;
228
261
  // Store the selected value for dropdown type (before select gets reset)
229
262
  if (this.type === "dropdown") {
@@ -240,6 +273,15 @@ class FigDropdown extends HTMLElement {
240
273
  }
241
274
 
242
275
  #handleSelectChange(e) {
276
+ const selectedOption = e.target.selectedOptions?.[0];
277
+ if (this.#hasPersistentControl(selectedOption)) {
278
+ if (this.type === "dropdown") {
279
+ this.select.selectedIndex = -1;
280
+ }
281
+ this.#keepPickerOpen();
282
+ return;
283
+ }
284
+
243
285
  // Get the value before resetting (use stored value for dropdown type)
244
286
  const selectedValue =
245
287
  this.type === "dropdown" ? this.#selectedValue : this.select.value;
@@ -1292,6 +1334,14 @@ class FigSegment extends HTMLElement {
1292
1334
  this.removeEventListener("click", this.handleClick);
1293
1335
  }
1294
1336
  handleClick() {
1337
+ const parentControl = this.closest("fig-segmented-control");
1338
+ if (
1339
+ parentControl &&
1340
+ parentControl.hasAttribute("disabled") &&
1341
+ parentControl.getAttribute("disabled") !== "false"
1342
+ ) {
1343
+ return;
1344
+ }
1295
1345
  this.setAttribute("selected", "true");
1296
1346
  }
1297
1347
  get value() {
@@ -1335,9 +1385,16 @@ class FigSegmentedControl extends HTMLElement {
1335
1385
  super();
1336
1386
  }
1337
1387
 
1388
+ static get observedAttributes() {
1389
+ return ["disabled"];
1390
+ }
1391
+
1338
1392
  connectedCallback() {
1339
1393
  this.name = this.getAttribute("name") || "segmented-control";
1340
1394
  this.addEventListener("click", this.handleClick.bind(this));
1395
+ this.#applyDisabled(
1396
+ this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false"
1397
+ );
1341
1398
 
1342
1399
  // Ensure at least one segment is selected (default to first)
1343
1400
  requestAnimationFrame(() => {
@@ -1368,6 +1425,12 @@ class FigSegmentedControl extends HTMLElement {
1368
1425
  }
1369
1426
 
1370
1427
  handleClick(event) {
1428
+ if (
1429
+ this.hasAttribute("disabled") &&
1430
+ this.getAttribute("disabled") !== "false"
1431
+ ) {
1432
+ return;
1433
+ }
1371
1434
  const segment = event.target.closest("fig-segment");
1372
1435
  if (segment) {
1373
1436
  const segments = this.querySelectorAll("fig-segment");
@@ -1380,6 +1443,25 @@ class FigSegmentedControl extends HTMLElement {
1380
1443
  }
1381
1444
  }
1382
1445
  }
1446
+
1447
+ #applyDisabled(disabled) {
1448
+ this.setAttribute("aria-disabled", disabled ? "true" : "false");
1449
+ this.querySelectorAll("fig-segment").forEach((segment) => {
1450
+ if (disabled) {
1451
+ segment.setAttribute("disabled", "");
1452
+ segment.setAttribute("aria-disabled", "true");
1453
+ } else {
1454
+ segment.removeAttribute("disabled");
1455
+ segment.removeAttribute("aria-disabled");
1456
+ }
1457
+ });
1458
+ }
1459
+
1460
+ attributeChangedCallback(name, oldValue, newValue) {
1461
+ if (name === "disabled" && oldValue !== newValue) {
1462
+ this.#applyDisabled(newValue !== null && newValue !== "false");
1463
+ }
1464
+ }
1383
1465
  }
1384
1466
  customElements.define("fig-segmented-control", FigSegmentedControl);
1385
1467
 
@@ -2538,6 +2620,8 @@ class FigInputColor extends HTMLElement {
2538
2620
  const useFigmaPicker = this.picker === "figma";
2539
2621
  const hidePicker = this.picker === "false";
2540
2622
  const showAlpha = this.getAttribute("alpha") === "true";
2623
+ const experimental = this.getAttribute("experimental");
2624
+ const expAttr = experimental ? `experimental="${experimental}"` : "";
2541
2625
 
2542
2626
  let html = ``;
2543
2627
  if (this.getAttribute("text")) {
@@ -2562,7 +2646,7 @@ class FigInputColor extends HTMLElement {
2562
2646
  let swatchElement = "";
2563
2647
  if (!hidePicker) {
2564
2648
  swatchElement = useFigmaPicker
2565
- ? `<fig-fill-picker mode="solid" ${
2649
+ ? `<fig-fill-picker mode="solid" ${expAttr} ${
2566
2650
  showAlpha ? "" : 'alpha="false"'
2567
2651
  } value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
2568
2652
  this.alpha
@@ -2580,7 +2664,7 @@ class FigInputColor extends HTMLElement {
2580
2664
  html = ``;
2581
2665
  } else {
2582
2666
  html = useFigmaPicker
2583
- ? `<fig-fill-picker mode="solid" ${
2667
+ ? `<fig-fill-picker mode="solid" ${expAttr} ${
2584
2668
  showAlpha ? "" : 'alpha="false"'
2585
2669
  } value='{"type":"solid","color":"${this.hexOpaque}","opacity":${
2586
2670
  this.alpha
@@ -2772,7 +2856,7 @@ class FigInputColor extends HTMLElement {
2772
2856
  }
2773
2857
 
2774
2858
  static get observedAttributes() {
2775
- return ["value", "style", "mode", "picker"];
2859
+ return ["value", "style", "mode", "picker", "experimental"];
2776
2860
  }
2777
2861
 
2778
2862
  get mode() {
@@ -2954,7 +3038,7 @@ class FigInputFill extends HTMLElement {
2954
3038
  }
2955
3039
 
2956
3040
  static get observedAttributes() {
2957
- return ["value", "disabled", "mode"];
3041
+ return ["value", "disabled", "mode", "experimental"];
2958
3042
  }
2959
3043
 
2960
3044
  connectedCallback() {
@@ -3105,11 +3189,12 @@ class FigInputFill extends HTMLElement {
3105
3189
  }
3106
3190
 
3107
3191
  const modeAttr = this.getAttribute("mode");
3192
+ const experimentalAttr = this.getAttribute("experimental");
3108
3193
  this.innerHTML = `
3109
3194
  <div class="input-combo">
3110
3195
  <fig-fill-picker value='${fillPickerValue}' ${
3111
3196
  disabled ? "disabled" : ""
3112
- } ${modeAttr ? `mode="${modeAttr}"` : ""}></fig-fill-picker>
3197
+ } ${modeAttr ? `mode="${modeAttr}"` : ""} ${experimentalAttr ? `experimental="${experimentalAttr}"` : ""}></fig-fill-picker>
3113
3198
  ${controlsHtml}
3114
3199
  </div>`;
3115
3200
 
@@ -3551,6 +3636,17 @@ class FigInputFill extends HTMLElement {
3551
3636
  this.#render();
3552
3637
  }
3553
3638
  break;
3639
+ case "mode":
3640
+ case "experimental":
3641
+ // Pass through to internal fill picker
3642
+ if (this.#fillPicker) {
3643
+ if (newValue) {
3644
+ this.#fillPicker.setAttribute(name, newValue);
3645
+ } else {
3646
+ this.#fillPicker.removeAttribute(name);
3647
+ }
3648
+ }
3649
+ break;
3554
3650
  }
3555
3651
  }
3556
3652
  }
@@ -3567,6 +3663,7 @@ customElements.define("fig-input-fill", FigInputFill);
3567
3663
  */
3568
3664
  class FigCheckbox extends HTMLElement {
3569
3665
  #labelElement = null;
3666
+ #boundHandleInput;
3570
3667
 
3571
3668
  constructor() {
3572
3669
  super();
@@ -3577,11 +3674,21 @@ class FigCheckbox extends HTMLElement {
3577
3674
  this.input.setAttribute("name", this.name);
3578
3675
  this.input.setAttribute("type", "checkbox");
3579
3676
  this.input.setAttribute("role", "checkbox");
3677
+ this.#boundHandleInput = this.handleInput.bind(this);
3580
3678
  }
3581
3679
  connectedCallback() {
3680
+ // Reuse cloned internals when this element is duplicated via option.cloneNode(true).
3681
+ const existingInput = this.querySelector(":scope > input");
3682
+ if (existingInput) {
3683
+ this.input = existingInput;
3684
+ } else if (!this.input.parentNode) {
3685
+ this.append(this.input);
3686
+ }
3687
+
3582
3688
  this.input.checked =
3583
3689
  this.hasAttribute("checked") && this.getAttribute("checked") !== "false";
3584
- this.input.addEventListener("change", this.handleInput.bind(this));
3690
+ this.input.removeEventListener("change", this.#boundHandleInput);
3691
+ this.input.addEventListener("change", this.#boundHandleInput);
3585
3692
 
3586
3693
  if (this.hasAttribute("disabled")) {
3587
3694
  this.input.disabled = true;
@@ -3591,7 +3698,11 @@ class FigCheckbox extends HTMLElement {
3591
3698
  this.input.setAttribute("indeterminate", "true");
3592
3699
  }
3593
3700
 
3594
- this.append(this.input);
3701
+ const existingLabel = this.querySelector(":scope > label");
3702
+ if (existingLabel) {
3703
+ this.#labelElement = existingLabel;
3704
+ this.#labelElement.setAttribute("for", this.input.id);
3705
+ }
3595
3706
 
3596
3707
  // Only create label if label attribute is present
3597
3708
  if (this.hasAttribute("label")) {
@@ -3649,6 +3760,7 @@ class FigCheckbox extends HTMLElement {
3649
3760
  }
3650
3761
 
3651
3762
  disconnectedCallback() {
3763
+ this.input.removeEventListener("change", this.#boundHandleInput);
3652
3764
  this.input.remove();
3653
3765
  }
3654
3766
 
@@ -3896,6 +4008,8 @@ class FigComboInput extends HTMLElement {
3896
4008
  this.options = this.getOptionsFromAttribute();
3897
4009
  this.placeholder = this.getAttribute("placeholder") || "";
3898
4010
  this.value = this.getAttribute("value") || "";
4011
+ const experimental = this.getAttribute("experimental");
4012
+ const expAttr = experimental ? `experimental="${experimental}"` : "";
3899
4013
  this.innerHTML = `<div class="input-combo">
3900
4014
  <fig-input-text placeholder="${this.placeholder}">
3901
4015
  </fig-input-text>
@@ -3903,7 +4017,7 @@ class FigComboInput extends HTMLElement {
3903
4017
  <svg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
3904
4018
  <path d='M5.87868 7.12132L8 9.24264L10.1213 7.12132' stroke='currentColor' stroke-opacity="0.9" stroke-linecap='round'/>
3905
4019
  </svg>
3906
- <fig-dropdown type="dropdown">
4020
+ <fig-dropdown type="dropdown" ${expAttr}>
3907
4021
  ${this.options
3908
4022
  .map((option) => `<option>${option}</option>`)
3909
4023
  .join("")}
@@ -3932,7 +4046,7 @@ class FigComboInput extends HTMLElement {
3932
4046
  this.value = this.input.value;
3933
4047
  }
3934
4048
  static get observedAttributes() {
3935
- return ["options", "placeholder", "value", "disabled"];
4049
+ return ["options", "placeholder", "value", "disabled", "experimental"];
3936
4050
  }
3937
4051
  focus() {
3938
4052
  this.input.focus();
@@ -3979,6 +4093,15 @@ class FigComboInput extends HTMLElement {
3979
4093
  case "disabled":
3980
4094
  this.#applyDisabled(newValue !== null && newValue !== "false");
3981
4095
  break;
4096
+ case "experimental":
4097
+ if (this.dropdown) {
4098
+ if (newValue) {
4099
+ this.dropdown.setAttribute("experimental", newValue);
4100
+ } else {
4101
+ this.dropdown.removeAttribute("experimental");
4102
+ }
4103
+ }
4104
+ break;
3982
4105
  }
3983
4106
  }
3984
4107
  }
@@ -5323,7 +5446,7 @@ class FigFillPicker extends HTMLElement {
5323
5446
  }
5324
5447
 
5325
5448
  static get observedAttributes() {
5326
- return ["value", "disabled", "alpha", "mode"];
5449
+ return ["value", "disabled", "alpha", "mode", "experimental"];
5327
5450
  }
5328
5451
 
5329
5452
  connectedCallback() {
@@ -5607,6 +5730,9 @@ class FigFillPicker extends HTMLElement {
5607
5730
  }
5608
5731
 
5609
5732
  // Build header content - label if single mode, dropdown if multiple
5733
+ const experimental = this.getAttribute("experimental");
5734
+ const expAttr = experimental ? `experimental="${experimental}"` : "";
5735
+
5610
5736
  let headerContent;
5611
5737
  if (allowedModes.length === 1) {
5612
5738
  headerContent = `<span class="fig-fill-picker-type-label">${modeLabels[allowedModes[0]]}</span>`;
@@ -5614,7 +5740,7 @@ class FigFillPicker extends HTMLElement {
5614
5740
  const options = allowedModes
5615
5741
  .map((m) => `<option value="${m}">${modeLabels[m]}</option>`)
5616
5742
  .join("\n ");
5617
- headerContent = `<fig-dropdown class="fig-fill-picker-type" variant="neue" value="${this.#fillType}">
5743
+ headerContent = `<fig-dropdown class="fig-fill-picker-type" ${expAttr} value="${this.#fillType}">
5618
5744
  ${options}
5619
5745
  </fig-dropdown>`;
5620
5746
  }
@@ -5853,10 +5979,17 @@ class FigFillPicker extends HTMLElement {
5853
5979
  ctx.fillRect(0, 0, width, height);
5854
5980
  }
5855
5981
 
5856
- #updateHandlePosition() {
5982
+ #updateHandlePosition(retryCount = 0) {
5857
5983
  if (!this.#colorAreaHandle || !this.#colorArea) return;
5858
5984
 
5859
5985
  const rect = this.#colorArea.getBoundingClientRect();
5986
+
5987
+ // If the canvas isn't visible yet (0 dimensions), schedule a retry (max 5 attempts)
5988
+ if ((rect.width === 0 || rect.height === 0) && retryCount < 5) {
5989
+ requestAnimationFrame(() => this.#updateHandlePosition(retryCount + 1));
5990
+ return;
5991
+ }
5992
+
5860
5993
  const x = (this.#color.s / 100) * rect.width;
5861
5994
  const y = ((100 - this.#color.v) / 100) * rect.height;
5862
5995
 
@@ -5943,10 +6076,12 @@ class FigFillPicker extends HTMLElement {
5943
6076
  // ============ GRADIENT TAB ============
5944
6077
  #initGradientTab() {
5945
6078
  const container = this.#dialog.querySelector('[data-tab="gradient"]');
6079
+ const experimental = this.getAttribute("experimental");
6080
+ const expAttr = experimental ? `experimental="${experimental}"` : "";
5946
6081
 
5947
6082
  container.innerHTML = `
5948
6083
  <div class="fig-fill-picker-gradient-header">
5949
- <fig-dropdown class="fig-fill-picker-gradient-type" variant="neue" value="${
6084
+ <fig-dropdown class="fig-fill-picker-gradient-type" ${expAttr} value="${
5950
6085
  this.#gradient.type
5951
6086
  }">
5952
6087
  <option value="linear" selected>Linear</option>
package/index.html CHANGED
@@ -107,8 +107,10 @@
107
107
  main {
108
108
  margin-left: 180px;
109
109
  padding: 24px 32px;
110
- overflow-y: auto;
111
110
  max-width: 800px;
111
+ min-height: 100vh;
112
+ display: flex;
113
+ flex-direction: column;
112
114
  }
113
115
 
114
116
  section {
@@ -156,6 +158,23 @@
156
158
  border-radius: 4px;
157
159
  color: var(--figma-color-text);
158
160
  }
161
+
162
+ fig-footer {
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: flex-end;
166
+ gap: var(--spacer-2);
167
+ }
168
+
169
+ fig-footer[sticky="true"] {
170
+ position: sticky;
171
+ bottom: 0;
172
+ z-index: 10;
173
+ margin-top: auto;
174
+ padding: var(--spacer-2) 0;
175
+ background: var(--figma-color-bg);
176
+ border-top: 1px solid var(--figma-color-border);
177
+ }
159
178
  </style>
160
179
  </head>
161
180
 
@@ -654,6 +673,12 @@
654
673
  <fig-combo-input options="Red, Green, Blue, Yellow, Purple"
655
674
  value="Blue"
656
675
  placeholder="Select a color"></fig-combo-input>
676
+
677
+ <h3>Experimental Modern</h3>
678
+ <p class="description">Uses the modern CSS picker styling for the dropdown.</p>
679
+ <fig-combo-input options="House, Apartment, Condo, Townhouse, Other"
680
+ experimental="modern"
681
+ placeholder="Type of residence"></fig-combo-input>
657
682
  </section>
658
683
  <hr>
659
684
 
@@ -840,7 +865,7 @@
840
865
  <p class="description">A select dropdown menu with options.</p>
841
866
 
842
867
  <h3>Default</h3>
843
- <fig-dropdown variant="neue">
868
+ <fig-dropdown>
844
869
  <optgroup label="Numbers">
845
870
  <option>One</option>
846
871
  <option>Two</option>
@@ -854,8 +879,7 @@
854
879
  </fig-dropdown>
855
880
 
856
881
  <h3>Full Width</h3>
857
- <fig-dropdown full
858
- variant="neue">
882
+ <fig-dropdown full>
859
883
  <option>Full Width Dropdown</option>
860
884
  <option>Option Two</option>
861
885
  <option>Option Three</option>
@@ -865,8 +889,7 @@
865
889
  <p style="font-size: 12px; color: var(--figma-color-text-secondary); margin-bottom: 8px;">
866
890
  Uses <code>field-sizing: content</code> to resize based on selected option.
867
891
  </p>
868
- <fig-dropdown autoresize
869
- variant="neue">
892
+ <fig-dropdown autoresize>
870
893
  <option>XS</option>
871
894
  <option>Small</option>
872
895
  <option>Medium</option>
@@ -875,8 +898,27 @@
875
898
  <option>XXL Super Extended Option</option>
876
899
  </fig-dropdown>
877
900
 
878
- <h3>Dropdown Neue Variant (with many options)</h3>
879
- <fig-dropdown variant="neue"
901
+ <h3>Experimental Modern</h3>
902
+ <p class="description">
903
+ Use <code>experimental="modern"</code> to enable the customizable select picker using
904
+ <code>::picker(select)</code> and <code>appearance: base-select</code>. This is a progressive
905
+ enhancement that falls back to native select on unsupported browsers.
906
+ </p>
907
+ <fig-dropdown experimental="modern">
908
+ <optgroup label="Numbers">
909
+ <option>One</option>
910
+ <option>Two</option>
911
+ <option>Three</option>
912
+ </optgroup>
913
+ <optgroup label="Fruits">
914
+ <option>Apple</option>
915
+ <option>Banana</option>
916
+ <option>Cherry</option>
917
+ </optgroup>
918
+ </fig-dropdown>
919
+
920
+ <h3>Experimental Modern - Dropdown Type (with many options)</h3>
921
+ <fig-dropdown experimental="modern"
880
922
  type="dropdown"
881
923
  label="Choose">
882
924
  <optgroup label="North America">
@@ -1007,9 +1049,9 @@
1007
1049
  </optgroup>
1008
1050
  </fig-dropdown>
1009
1051
 
1010
- <h3>Select Neue Variant (with many options)</h3>
1052
+ <h3>Experimental Modern - Select Type (with many options)</h3>
1011
1053
 
1012
- <fig-dropdown variant="neue">
1054
+ <fig-dropdown experimental="modern">
1013
1055
  <optgroup label="North America">
1014
1056
  <option value="us">United States</option>
1015
1057
  <option value="ca">Canada</option>
@@ -1137,6 +1179,82 @@
1137
1179
  <option value="zeplin">Zeplin</option>
1138
1180
  </optgroup>
1139
1181
  </fig-dropdown>
1182
+
1183
+ <h3>Experimental Modern - With Icons (Bleeding Edge)</h3>
1184
+ <p class="description">
1185
+ Using icons inside options requires <code>appearance: base-select</code> support.
1186
+ This is bleeding edge and may not work in all browsers.
1187
+ </p>
1188
+ <fig-dropdown experimental="modern">
1189
+ <option value="home">
1190
+ <svg width="16"
1191
+ height="16"
1192
+ viewBox="0 0 16 16"
1193
+ fill="none"
1194
+ xmlns="http://www.w3.org/2000/svg">
1195
+ <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"
1196
+ stroke="currentColor"
1197
+ stroke-opacity="0.9"
1198
+ fill="none" />
1199
+ </svg>
1200
+ <label>Home</label>
1201
+ </option>
1202
+ <option value="settings">
1203
+ <svg width="16"
1204
+ height="16"
1205
+ viewBox="0 0 16 16"
1206
+ fill="none"
1207
+ xmlns="http://www.w3.org/2000/svg">
1208
+ <circle cx="8"
1209
+ cy="8"
1210
+ r="2"
1211
+ stroke="currentColor"
1212
+ stroke-opacity="0.9"
1213
+ fill="none" />
1214
+ <path d="M8 1V3M8 13V15M1 8H3M13 8H15M2.5 2.5L4 4M12 12L13.5 13.5M2.5 13.5L4 12M12 4L13.5 2.5"
1215
+ stroke="currentColor"
1216
+ stroke-opacity="0.9" />
1217
+ </svg>
1218
+ <label>Settings</label>
1219
+ </option>
1220
+ <option value="user">
1221
+ <svg width="16"
1222
+ height="16"
1223
+ viewBox="0 0 16 16"
1224
+ fill="none"
1225
+ xmlns="http://www.w3.org/2000/svg">
1226
+ <circle cx="8"
1227
+ cy="5"
1228
+ r="3"
1229
+ stroke="currentColor"
1230
+ stroke-opacity="0.9"
1231
+ fill="none" />
1232
+ <path d="M2 14C2 11 4.5 9 8 9C11.5 9 14 11 14 14"
1233
+ stroke="currentColor"
1234
+ stroke-opacity="0.9"
1235
+ fill="none" />
1236
+ </svg>
1237
+ <label>User Profile</label>
1238
+ </option>
1239
+ <option value="search">
1240
+ <svg width="16"
1241
+ height="16"
1242
+ viewBox="0 0 16 16"
1243
+ fill="none"
1244
+ xmlns="http://www.w3.org/2000/svg">
1245
+ <circle cx="6.5"
1246
+ cy="6.5"
1247
+ r="4"
1248
+ stroke="currentColor"
1249
+ stroke-opacity="0.9"
1250
+ fill="none" />
1251
+ <path d="M10 10L14 14"
1252
+ stroke="currentColor"
1253
+ stroke-opacity="0.9" />
1254
+ </svg>
1255
+ <label>Search</label>
1256
+ </option>
1257
+ </fig-dropdown>
1140
1258
  </section>
1141
1259
  <hr>
1142
1260
 
@@ -1279,7 +1397,8 @@
1279
1397
  <fig-fill-picker mode="image"></fig-fill-picker>
1280
1398
 
1281
1399
  <h3>Limited Mode Selection</h3>
1282
- <p class="description">Use comma-separated values in the <code>mode</code> attribute to limit available modes
1400
+ <p class="description">Use comma-separated values in the <code>mode</code> attribute to limit available
1401
+ modes
1283
1402
  while still allowing switching between them.</p>
1284
1403
 
1285
1404
  <h4>Solid and Gradient Only</h4>
@@ -1289,7 +1408,8 @@
1289
1408
  <fig-fill-picker mode="solid,gradient,image"></fig-fill-picker>
1290
1409
 
1291
1410
  <h4>With fig-input-fill (mode passed through)</h4>
1292
- <fig-input-fill mode="solid,gradient" value='{"type":"solid","color":"#667eea"}'></fig-input-fill>
1411
+ <fig-input-fill mode="solid,gradient"
1412
+ value='{"type":"solid","color":"#667eea"}'></fig-input-fill>
1293
1413
 
1294
1414
  <h3>Event Listening</h3>
1295
1415
  <fig-fill-picker id="fill-picker-events"
@@ -2593,13 +2713,25 @@
2593
2713
  <h3>With Icons</h3>
2594
2714
  <fig-segmented-control>
2595
2715
  <fig-segment selected>
2596
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2597
- <path d="M8.5 5.59686C8.80936 5.41829 9.19064 5.41829 9.5 5.59686L15 8.77264C15.6185 9.12984 15.9998 9.78984 16 10.5041V16.8557C15.9999 17.2128 15.8093 17.5433 15.5 17.7219C15.1907 17.9003 14.8093 17.9003 14.5 17.7219L14.2988 17.6057L14.1025 17.4924L13.9111 17.381L13.7236 17.2736L13.541 17.1672L13.3613 17.0637L13.1846 16.9621L13.0117 16.8625L12.8418 16.7639L12.6738 16.6672L12.5078 16.5715L12.3438 16.4767L12.1816 16.383L12.0205 16.2902L11.8613 16.1974L11.7012 16.1057L11.543 16.0139L11.0654 15.7385L10.9053 15.6457L10.7432 15.5519L10.5801 15.4582L10.416 15.3635L10.249 15.2668L10.0801 15.1691L9.9082 15.0705L9.73438 14.9699L9.55664 14.8674L9.375 14.7619L9.18945 14.6555L9.00098 14.5461C8.38287 14.1892 8.00002 13.5289 8 12.8137V6.46307C8 6.10581 8.1906 5.7755 8.5 5.59686ZM9 12.8137C9.00001 13.1709 9.1916 13.5012 9.50098 13.6799C11.542 14.8584 12.8291 15.6022 15 16.8557V10.5051C15 10.1925 14.8541 9.89983 14.6104 9.7121L14.5 9.63885L9 6.46307V12.8137Z" fill="black" fill-opacity="0.9"/>
2716
+ <svg width="24"
2717
+ height="24"
2718
+ viewBox="0 0 24 24"
2719
+ fill="none"
2720
+ xmlns="http://www.w3.org/2000/svg">
2721
+ <path d="M8.5 5.59686C8.80936 5.41829 9.19064 5.41829 9.5 5.59686L15 8.77264C15.6185 9.12984 15.9998 9.78984 16 10.5041V16.8557C15.9999 17.2128 15.8093 17.5433 15.5 17.7219C15.1907 17.9003 14.8093 17.9003 14.5 17.7219L14.2988 17.6057L14.1025 17.4924L13.9111 17.381L13.7236 17.2736L13.541 17.1672L13.3613 17.0637L13.1846 16.9621L13.0117 16.8625L12.8418 16.7639L12.6738 16.6672L12.5078 16.5715L12.3438 16.4767L12.1816 16.383L12.0205 16.2902L11.8613 16.1974L11.7012 16.1057L11.543 16.0139L11.0654 15.7385L10.9053 15.6457L10.7432 15.5519L10.5801 15.4582L10.416 15.3635L10.249 15.2668L10.0801 15.1691L9.9082 15.0705L9.73438 14.9699L9.55664 14.8674L9.375 14.7619L9.18945 14.6555L9.00098 14.5461C8.38287 14.1892 8.00002 13.5289 8 12.8137V6.46307C8 6.10581 8.1906 5.7755 8.5 5.59686ZM9 12.8137C9.00001 13.1709 9.1916 13.5012 9.50098 13.6799C11.542 14.8584 12.8291 15.6022 15 16.8557V10.5051C15 10.1925 14.8541 9.89983 14.6104 9.7121L14.5 9.63885L9 6.46307V12.8137Z"
2722
+ fill="black"
2723
+ fill-opacity="0.9" />
2598
2724
  </svg>
2599
2725
  </fig-segment>
2600
2726
  <fig-segment>
2601
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2602
- <path d="M14.4998 5.5968C14.8091 5.41823 15.1904 5.41823 15.4998 5.5968C15.8092 5.77543 15.9998 6.10575 15.9998 6.46301V12.8136C15.9997 13.5281 15.6185 14.1888 14.9998 14.546L9.49976 17.7218C9.19046 17.9003 8.80905 17.9003 8.49976 17.7218C8.19043 17.5432 7.99986 17.2127 7.99976 16.8556V10.504C7.99995 9.78978 8.38122 9.12978 8.99976 8.77258L14.4998 5.5968ZM9.49976 9.63879L9.3894 9.71204C9.14564 9.89977 8.99977 10.1924 8.99976 10.505V16.8556L14.4998 13.6798C14.7704 13.5235 14.9502 13.2513 14.991 12.9464L14.9998 12.8136V6.46301L9.49976 9.63879Z" fill="black" fill-opacity="0.9"/>
2727
+ <svg width="24"
2728
+ height="24"
2729
+ viewBox="0 0 24 24"
2730
+ fill="none"
2731
+ xmlns="http://www.w3.org/2000/svg">
2732
+ <path d="M14.4998 5.5968C14.8091 5.41823 15.1904 5.41823 15.4998 5.5968C15.8092 5.77543 15.9998 6.10575 15.9998 6.46301V12.8136C15.9997 13.5281 15.6185 14.1888 14.9998 14.546L9.49976 17.7218C9.19046 17.9003 8.80905 17.9003 8.49976 17.7218C8.19043 17.5432 7.99986 17.2127 7.99976 16.8556V10.504C7.99995 9.78978 8.38122 9.12978 8.99976 8.77258L14.4998 5.5968ZM9.49976 9.63879L9.3894 9.71204C9.14564 9.89977 8.99977 10.1924 8.99976 10.505V16.8556L14.4998 13.6798C14.7704 13.5235 14.9502 13.2513 14.991 12.9464L14.9998 12.8136V6.46301L9.49976 9.63879Z"
2733
+ fill="black"
2734
+ fill-opacity="0.9" />
2603
2735
  </svg>
2604
2736
  </fig-segment>
2605
2737
  </fig-segmented-control>
@@ -2612,6 +2744,39 @@
2612
2744
  <fig-segment>L</fig-segment>
2613
2745
  <fig-segment>XL</fig-segment>
2614
2746
  </fig-segmented-control>
2747
+
2748
+ <h3>Disabled (Text)</h3>
2749
+ <fig-segmented-control disabled>
2750
+ <fig-segment>List</fig-segment>
2751
+ <fig-segment selected>Grid</fig-segment>
2752
+ <fig-segment>Board</fig-segment>
2753
+ </fig-segmented-control>
2754
+
2755
+ <h3>Disabled (Icons)</h3>
2756
+ <fig-segmented-control disabled>
2757
+ <fig-segment selected>
2758
+ <svg width="24"
2759
+ height="24"
2760
+ viewBox="0 0 24 24"
2761
+ fill="none"
2762
+ xmlns="http://www.w3.org/2000/svg">
2763
+ <path d="M8.5 5.59686C8.80936 5.41829 9.19064 5.41829 9.5 5.59686L15 8.77264C15.6185 9.12984 15.9998 9.78984 16 10.5041V16.8557C15.9999 17.2128 15.8093 17.5433 15.5 17.7219C15.1907 17.9003 14.8093 17.9003 14.5 17.7219L14.2988 17.6057L14.1025 17.4924L13.9111 17.381L13.7236 17.2736L13.541 17.1672L13.3613 17.0637L13.1846 16.9621L13.0117 16.8625L12.8418 16.7639L12.6738 16.6672L12.5078 16.5715L12.3438 16.4767L12.1816 16.383L12.0205 16.2902L11.8613 16.1974L11.7012 16.1057L11.543 16.0139L11.0654 15.7385L10.9053 15.6457L10.7432 15.5519L10.5801 15.4582L10.416 15.3635L10.249 15.2668L10.0801 15.1691L9.9082 15.0705L9.73438 14.9699L9.55664 14.8674L9.375 14.7619L9.18945 14.6555L9.00098 14.5461C8.38287 14.1892 8.00002 13.5289 8 12.8137V6.46307C8 6.10581 8.1906 5.7755 8.5 5.59686ZM9 12.8137C9.00001 13.1709 9.1916 13.5012 9.50098 13.6799C11.542 14.8584 12.8291 15.6022 15 16.8557V10.5051C15 10.1925 14.8541 9.89983 14.6104 9.7121L14.5 9.63885L9 6.46307V12.8137Z"
2764
+ fill="black"
2765
+ fill-opacity="0.9" />
2766
+ </svg>
2767
+ </fig-segment>
2768
+ <fig-segment>
2769
+ <svg width="24"
2770
+ height="24"
2771
+ viewBox="0 0 24 24"
2772
+ fill="none"
2773
+ xmlns="http://www.w3.org/2000/svg">
2774
+ <path d="M14.4998 5.5968C14.8091 5.41823 15.1904 5.41823 15.4998 5.5968C15.8092 5.77543 15.9998 6.10575 15.9998 6.46301V12.8136C15.9997 13.5281 15.6185 14.1888 14.9998 14.546L9.49976 17.7218C9.19046 17.9003 8.80905 17.9003 8.49976 17.7218C8.19043 17.5432 7.99986 17.2127 7.99976 16.8556V10.504C7.99995 9.78978 8.38122 9.12978 8.99976 8.77258L14.4998 5.5968ZM9.49976 9.63879L9.3894 9.71204C9.14564 9.89977 8.99977 10.1924 8.99976 10.505V16.8556L14.4998 13.6798C14.7704 13.5235 14.9502 13.2513 14.991 12.9464L14.9998 12.8136V6.46301L9.49976 9.63879Z"
2775
+ fill="black"
2776
+ fill-opacity="0.9" />
2777
+ </svg>
2778
+ </fig-segment>
2779
+ </fig-segmented-control>
2615
2780
  </section>
2616
2781
  <hr>
2617
2782
 
@@ -2671,7 +2836,8 @@
2671
2836
  units="%"></fig-slider>
2672
2837
 
2673
2838
  <h3>With Precision</h3>
2674
- <p class="description">Control decimal places in the text input with the <code>precision</code> attribute.</p>
2839
+ <p class="description">Control decimal places in the text input with the <code>precision</code> attribute.
2840
+ </p>
2675
2841
  <fig-field direction="horizontal">
2676
2842
  <label>Precision 0</label>
2677
2843
  <fig-slider min="0"
@@ -3591,6 +3757,36 @@ button.addEventListener('click', () => {
3591
3757
  </vstack>
3592
3758
  </details>
3593
3759
  </section>
3760
+ <fig-footer sticky="true">
3761
+ <fig-button icon="true"
3762
+ type="select"
3763
+ variant="ghost"
3764
+ title="Fill settings">
3765
+ <svg width="16"
3766
+ height="16"
3767
+ viewBox="0 0 16 16"
3768
+ fill="none"
3769
+ xmlns="http://www.w3.org/2000/svg">
3770
+ <path d="M8 3.5A4.5 4.5 0 1 0 8 12.5A4.5 4.5 0 1 0 8 3.5Z"
3771
+ stroke="currentColor"
3772
+ stroke-opacity="0.9" />
3773
+ <path d="M8 1V2.25M8 13.75V15M15 8H13.75M2.25 8H1M12.95 3.05L12.06 3.94M3.94 12.06L3.05 12.95M12.95 12.95L12.06 12.06M3.94 3.94L3.05 3.05"
3774
+ stroke="currentColor"
3775
+ stroke-opacity="0.9"
3776
+ stroke-linecap="round" />
3777
+ </svg>
3778
+ <fig-dropdown experimental="modern"
3779
+ type="dropdown">
3780
+ <option value="existing-strokes">
3781
+ <fig-checkbox checked
3782
+ label="Apply to existing strokes"></fig-checkbox>
3783
+ </option>
3784
+ <option value="shapes-with-fills">
3785
+ <fig-checkbox label="Apply to shapes with fills"></fig-checkbox>
3786
+ </option>
3787
+ </fig-dropdown>
3788
+ </fig-button>
3789
+ </fig-footer>
3594
3790
  </main>
3595
3791
 
3596
3792
  <script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "2.14.0",
3
+ "version": "2.17.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",