@rogieking/figui3 2.13.1 → 2.14.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.
Files changed (4) hide show
  1. package/components.css +89 -58
  2. package/fig.js +84 -38
  3. package/index.html +110 -0
  4. package/package.json +1 -1
package/components.css CHANGED
@@ -193,9 +193,10 @@
193
193
  :root {
194
194
  /* Typography & Sizing */
195
195
  --font-size: 16px;
196
- --font-family: "Inter", AppleSystemUIFont, BlinkMacSystemFont, avenir next,
197
- avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto,
198
- arial, sans-serif;
196
+ --font-family:
197
+ "Inter", AppleSystemUIFont, BlinkMacSystemFont, avenir next, avenir,
198
+ segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial,
199
+ sans-serif;
199
200
  --body-medium-fontSize: 0.6875rem;
200
201
  --body-large-fontSize: 0.8125rem;
201
202
  --body-letter-spacing: 0.055px;
@@ -206,6 +207,7 @@
206
207
  /* Spacing */
207
208
  --spacer-1: 0.25rem;
208
209
  --spacer-2: 0.5rem;
210
+ --spacer-2-5: 0.75rem;
209
211
  --spacer-3: 1rem;
210
212
  --spacer-4: 1.5rem;
211
213
  --spacer-5: 2rem;
@@ -304,15 +306,19 @@
304
306
  --icon-swap: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.35355 6.35355C8.54882 6.15829 8.54882 5.84171 8.35355 5.64645C8.15829 5.45118 7.84171 5.45118 7.64645 5.64645L5.14645 8.14645C4.95118 8.34171 4.95118 8.65829 5.14645 8.85355L7.64645 11.3536C7.84171 11.5488 8.15829 11.5488 8.35355 11.3536C8.54882 11.1583 8.54882 10.8417 8.35355 10.6464L6.70711 9H18.5C18.7761 9 19 8.77614 19 8.5C19 8.22386 18.7761 8 18.5 8H6.70711L8.35355 6.35355ZM15.6464 13.3536C15.4512 13.1583 15.4512 12.8417 15.6464 12.6464C15.8417 12.4512 16.1583 12.4512 16.3536 12.6464L18.8536 15.1464C19.0488 15.3417 19.0488 15.6583 18.8536 15.8536L16.3536 18.3536C16.1583 18.5488 15.8417 18.5488 15.6464 18.3536C15.4512 18.1583 15.4512 17.8417 15.6464 17.6464L17.2929 16H5.5C5.22386 16 5 15.7761 5 15.5C5 15.2239 5.22386 15 5.5 15H17.2929L15.6464 13.3536Z' fill='currentColor'/%3E%3C/svg%3E");
305
307
 
306
308
  /* Elevations - light theme defaults */
307
- --elevation-500-modal-window: 0px 0px 0.5px rgba(0, 0, 0, 0.08),
308
- 0px 10px 24px rgba(0, 0, 0, 0.18), 0px 2px 5px rgba(0, 0, 0, 0.15);
309
- --figma-elevation-100: 0px 0px 0.5px 0px rgba(0, 0, 0, 0.3),
310
- 0px 1px 3px 0px rgba(0, 0, 0, 0.15);
311
- --figma-elevation-200: 0 0 0.5px 0 rgba(0, 0, 0, 0.18),
312
- 0 3px 8px 0 rgba(0, 0, 0, 0.1), 0 1px 3px 0 rgba(0, 0, 0, 0.1);
313
- --figma-elevation-400-menu-panel: 0px 0px 0.5px 0px rgba(0, 0, 0, 0.12),
309
+ --elevation-500-modal-window:
310
+ 0px 0px 0.5px rgba(0, 0, 0, 0.08), 0px 10px 24px rgba(0, 0, 0, 0.18),
311
+ 0px 2px 5px rgba(0, 0, 0, 0.15);
312
+ --figma-elevation-100:
313
+ 0px 0px 0.5px 0px rgba(0, 0, 0, 0.3), 0px 1px 3px 0px rgba(0, 0, 0, 0.15);
314
+ --figma-elevation-200:
315
+ 0 0 0.5px 0 rgba(0, 0, 0, 0.18), 0 3px 8px 0 rgba(0, 0, 0, 0.1),
316
+ 0 1px 3px 0 rgba(0, 0, 0, 0.1);
317
+ --figma-elevation-400-menu-panel:
318
+ 0px 0px 0.5px 0px rgba(0, 0, 0, 0.12),
314
319
  0px 10px 16px 0px rgba(0, 0, 0, 0.12), 0px 2px 5px 0px rgba(0, 0, 0, 0.15);
315
- --figma-elevation-500-modal-window: 0px 0px 0.5px 0px rgba(0, 0, 0, 0.08),
320
+ --figma-elevation-500-modal-window:
321
+ 0px 0px 0.5px 0px rgba(0, 0, 0, 0.08),
316
322
  0px 10px 24px 0px rgba(0, 0, 0, 0.18), 0px 2px 5px 0px rgba(0, 0, 0, 0.15);
317
323
  --handle-shadow: 0px 0 0 0.5px rgba(0, 0, 0, 0.1), var(--figma-elevation-100);
318
324
  }
@@ -321,44 +327,48 @@
321
327
  /* These cannot use light-dark() as they are complex values */
322
328
  @media (prefers-color-scheme: dark) {
323
329
  :root {
324
- --handle-shadow: 0px 0 0 0.75px rgba(0, 0, 0, 0.1),
330
+ --handle-shadow:
331
+ 0px 0 0 0.75px rgba(0, 0, 0, 0.1),
325
332
  0px 0px 0.5px 0px rgba(255, 255, 255, 0.1);
326
- --figma-elevation-100: 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
333
+ --figma-elevation-100:
334
+ 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
327
335
  0px 0.75px 0px 0px rgba(255, 255, 255, 0.1) inset,
328
336
  0px 1px 3px 0px rgba(0, 0, 0, 0.4);
329
- --figma-elevation-200: 0px 3px 8px rgba(0, 0, 0, 0.35),
330
- 0px 1px 3px rgba(0, 0, 0, 0.5),
337
+ --figma-elevation-200:
338
+ 0px 3px 8px rgba(0, 0, 0, 0.35), 0px 1px 3px rgba(0, 0, 0, 0.5),
331
339
  inset 0px 0.5px 0px rgba(255, 255, 255, 0.08),
332
340
  inset 0px 0px 0.5px rgba(255, 255, 255, 0.3);
333
- --figma-elevation-400-menu-panel: 0px 0.5px 0px 0px
334
- rgba(255, 255, 255, 0.08) inset,
341
+ --figma-elevation-400-menu-panel:
342
+ 0px 0.5px 0px 0px rgba(255, 255, 255, 0.08) inset,
335
343
  0px 10px 16px 0px rgba(0, 0, 0, 0.35),
336
344
  inset 0px 0.75px 0px rgba(255, 255, 255, 0.075),
337
345
  0px 2px 5px 0px rgba(0, 0, 0, 0.35);
338
- --figma-elevation-500-modal-window: 0px 10px 24px rgba(0, 0, 0, 0.45),
339
- 0px 3px 5px rgba(0, 0, 0, 0.35),
346
+ --figma-elevation-500-modal-window:
347
+ 0px 10px 24px rgba(0, 0, 0, 0.45), 0px 3px 5px rgba(0, 0, 0, 0.35),
340
348
  inset 0px 0.75px 0px rgba(255, 255, 255, 0.1);
341
349
  }
342
350
  }
343
351
 
344
352
  /* Class-based dark theme override (for manual theme switching) */
345
353
  :root.figma-dark {
346
- --handle-shadow: 0px 0 0 0.75px rgba(0, 0, 0, 0.1),
354
+ --handle-shadow:
355
+ 0px 0 0 0.75px rgba(0, 0, 0, 0.1),
347
356
  0px 0px 0.5px 0px rgba(255, 255, 255, 0.1);
348
- --figma-elevation-100: 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
357
+ --figma-elevation-100:
358
+ 0px 0px 0.5px 0px rgba(0, 0, 0, 0.5),
349
359
  0px 0.75px 0px 0px rgba(255, 255, 255, 0.1) inset,
350
360
  0px 1px 3px 0px rgba(0, 0, 0, 0.4);
351
- --figma-elevation-200: 0px 3px 8px rgba(0, 0, 0, 0.35),
352
- 0px 1px 3px rgba(0, 0, 0, 0.5),
361
+ --figma-elevation-200:
362
+ 0px 3px 8px rgba(0, 0, 0, 0.35), 0px 1px 3px rgba(0, 0, 0, 0.5),
353
363
  inset 0px 0.5px 0px rgba(255, 255, 255, 0.08),
354
364
  inset 0px 0px 0.5px rgba(255, 255, 255, 0.3);
355
- --figma-elevation-400-menu-panel: 0px 0.5px 0px 0px rgba(255, 255, 255, 0.08)
356
- inset,
365
+ --figma-elevation-400-menu-panel:
366
+ 0px 0.5px 0px 0px rgba(255, 255, 255, 0.08) inset,
357
367
  0px 10px 16px 0px rgba(0, 0, 0, 0.35),
358
368
  inset 0px 0.75px 0px rgba(255, 255, 255, 0.075),
359
369
  0px 2px 5px 0px rgba(0, 0, 0, 0.35);
360
- --figma-elevation-500-modal-window: 0px 10px 24px rgba(0, 0, 0, 0.45),
361
- 0px 3px 5px rgba(0, 0, 0, 0.35),
370
+ --figma-elevation-500-modal-window:
371
+ 0px 10px 24px rgba(0, 0, 0, 0.45), 0px 3px 5px rgba(0, 0, 0, 0.35),
362
372
  inset 0px 0.75px 0px rgba(255, 255, 255, 0.1);
363
373
  }
364
374
 
@@ -639,6 +649,11 @@ fig-dropdown,
639
649
  background-color: var(--figma-color-icon);
640
650
  pointer-events: none;
641
651
  }
652
+
653
+ /* Autoresize to content width */
654
+ &[autoresize] select {
655
+ field-sizing: content;
656
+ }
642
657
  }
643
658
 
644
659
  /* Button */
@@ -1119,7 +1134,8 @@ fig-chit {
1119
1134
  }
1120
1135
 
1121
1136
  &[selected]:not([selected="false"])::before {
1122
- box-shadow: inset 0 0 0 1px var(--figma-color-border-selected),
1137
+ box-shadow:
1138
+ inset 0 0 0 1px var(--figma-color-border-selected),
1123
1139
  inset 0 0 0 3px var(--figma-color-bg);
1124
1140
  }
1125
1141
 
@@ -1475,8 +1491,10 @@ input[type="radio"] {
1475
1491
  border-radius: 100%;
1476
1492
  background-color: var(--figma-color-icon-onbrand);
1477
1493
  transform: scale(0);
1478
- box-shadow: 0px 0 0 0.75px rgba(0, 0, 0, 0.1),
1479
- 0px 1px 3px 0px rgba(0, 0, 0, 0.1), 0px 3px 8px 0px rgba(0, 0, 0, 0.1),
1494
+ box-shadow:
1495
+ 0px 0 0 0.75px rgba(0, 0, 0, 0.1),
1496
+ 0px 1px 3px 0px rgba(0, 0, 0, 0.1),
1497
+ 0px 3px 8px 0px rgba(0, 0, 0, 0.1),
1480
1498
  0px 0px 0.5px 0px rgba(0, 0, 0, 0.18);
1481
1499
  }
1482
1500
  }
@@ -1573,12 +1591,12 @@ fig-slider {
1573
1591
  --slider-percent: calc(var(--slider-complete) * 100%);
1574
1592
  --start-percent: calc(var(--default, 0) * 100%);
1575
1593
  --slider-tick-size: calc(var(--slider-height) / 4);
1576
- --slider-handle-shadow: inset 0 0 0 calc(4px + 0.5rem * var(--unchanged))
1577
- var(--handle-color),
1594
+ --slider-handle-shadow:
1595
+ inset 0 0 0 calc(4px + 0.5rem * var(--unchanged)) var(--handle-color),
1578
1596
  0px 0 0 0.5px rgba(0, 0, 0, 0.1), var(--figma-elevation-100);
1579
- --slider-handle-shadow-focus: inset 0 0 0 4px var(--handle-color),
1580
- inset 0 0 0 5px rgba(0, 0, 0, 0.1), var(--handle-shadow),
1581
- 0 0 0 1px var(--figma-color-border-selected);
1597
+ --slider-handle-shadow-focus:
1598
+ inset 0 0 0 4px var(--handle-color), inset 0 0 0 5px rgba(0, 0, 0, 0.1),
1599
+ var(--handle-shadow), 0 0 0 1px var(--figma-color-border-selected);
1582
1600
  --slider-transition: all 0.1s ease-in-out;
1583
1601
  --handle-transition: var(--slider-transition);
1584
1602
 
@@ -1642,11 +1660,10 @@ fig-slider {
1642
1660
  );
1643
1661
  --width: calc(var(--slider-percent) - var(--start-percent));
1644
1662
  --abs-width: max(
1645
- var(--width) + var(--slider-height) / 2 + (1 - var(--slider-complete)) *
1646
- var(--slider-height),
1647
- -1 * var(--width) + var(--slider-height) / 2 + (
1648
- var(--slider-complete)
1649
- ) * var(--slider-height)
1663
+ var(--width) + var(--slider-height) / 2 +
1664
+ (1 - var(--slider-complete)) * var(--slider-height),
1665
+ -1 * var(--width) + var(--slider-height) / 2 +
1666
+ (var(--slider-complete)) * var(--slider-height)
1650
1667
  );
1651
1668
  width: var(--abs-width);
1652
1669
  --delta: calc(var(--slider-complete) - var(--default));
@@ -1736,7 +1753,8 @@ fig-slider {
1736
1753
  }
1737
1754
 
1738
1755
  &.opacity::-webkit-slider-runnable-track {
1739
- background: linear-gradient(to right, transparent, var(--color)),
1756
+ background:
1757
+ linear-gradient(to right, transparent, var(--color)),
1740
1758
  var(--checkerboard);
1741
1759
  }
1742
1760
  }
@@ -1810,7 +1828,8 @@ fig-slider {
1810
1828
  }
1811
1829
 
1812
1830
  &.opacity::-moz-range-track {
1813
- background: linear-gradient(to right, transparent, var(--color)),
1831
+ background:
1832
+ linear-gradient(to right, transparent, var(--color)),
1814
1833
  var(--checkerboard);
1815
1834
  }
1816
1835
  }
@@ -1859,8 +1878,8 @@ fig-slider {
1859
1878
  --slider-tick-size: calc(var(--slider-height) / 2);
1860
1879
  --handle-transition: none;
1861
1880
  --slider-transition: none;
1862
- --slider-handle-shadow: inset 0 0 0 calc(6px + 0.5rem * var(--unchanged))
1863
- var(--handle-color),
1881
+ --slider-handle-shadow:
1882
+ inset 0 0 0 calc(6px + 0.5rem * var(--unchanged)) var(--handle-color),
1864
1883
  0 0 0 0.75px rgba(0, 0, 0, 0.075), inset 0 0 0 5px rgba(0, 0, 0, 0.1),
1865
1884
  var(--figma-elevation-100);
1866
1885
 
@@ -1888,8 +1907,8 @@ fig-slider {
1888
1907
  --slider-tick-size: calc(var(--slider-height) / 2);
1889
1908
  --handle-transition: none;
1890
1909
  --slider-transition: none;
1891
- --slider-handle-shadow: inset 0 0 0 calc(6px + 0.5rem * var(--unchanged))
1892
- var(--handle-color),
1910
+ --slider-handle-shadow:
1911
+ inset 0 0 0 calc(6px + 0.5rem * var(--unchanged)) var(--handle-color),
1893
1912
  0 0 0 0.75px rgba(0, 0, 0, 0.075), inset 0 0 0 5px rgba(0, 0, 0, 0.1),
1894
1913
  var(--figma-elevation-100);
1895
1914
 
@@ -1994,8 +2013,10 @@ dialog,
1994
2013
  text-overflow: ellipsis;
1995
2014
 
1996
2015
  border-radius: var(--radius-medium, 0.3125rem);
1997
- box-shadow: 0px 0px 0.5px 0px rgba(0, 0, 0, 0.15),
1998
- 0px 5px 12px 0px rgba(0, 0, 0, 0.13), 0px 1px 3px 0px rgba(0, 0, 0, 0.1);
2016
+ box-shadow:
2017
+ 0px 0px 0.5px 0px rgba(0, 0, 0, 0.15),
2018
+ 0px 5px 12px 0px rgba(0, 0, 0, 0.13),
2019
+ 0px 1px 3px 0px rgba(0, 0, 0, 0.1);
1999
2020
 
2000
2021
  @supports (-webkit-line-clamp: 2) {
2001
2022
  white-space: initial;
@@ -2391,7 +2412,7 @@ fig-slider {
2391
2412
  }
2392
2413
 
2393
2414
  & fig-input-number {
2394
- width: 5rem;
2415
+ flex-basis: 5rem;
2395
2416
  }
2396
2417
  }
2397
2418
 
@@ -2580,7 +2601,8 @@ fig-input-joystick {
2580
2601
  }
2581
2602
  }
2582
2603
  .fig-input-joystick-plane.dragging .fig-input-joystick-guides {
2583
- background: linear-gradient(
2604
+ background:
2605
+ linear-gradient(
2584
2606
  45deg,
2585
2607
  transparent calc(50% - 0.5px),
2586
2608
  var(--figma-color-border) calc(50% - 0.5px),
@@ -2892,13 +2914,17 @@ fig-fill-picker {
2892
2914
  height: 1rem;
2893
2915
  border-radius: 50%;
2894
2916
  background: var(--picker-color);
2895
- box-shadow: inset 0 0 0 0.125rem var(--handle-color),
2896
- 0px 0 0 0.5px rgba(0, 0, 0, 0.1), var(--figma-elevation-200);
2917
+ box-shadow:
2918
+ inset 0 0 0 0.125rem var(--handle-color),
2919
+ 0px 0 0 0.5px rgba(0, 0, 0, 0.1),
2920
+ var(--figma-elevation-200);
2897
2921
  transform: translate(-50%, -50%);
2898
2922
  z-index: 1;
2899
2923
  &:hover {
2900
- box-shadow: inset 0 0 0 0.25rem var(--handle-color),
2901
- 0px 0 0 0.5px rgba(0, 0, 0, 0.1), var(--figma-elevation-200);
2924
+ box-shadow:
2925
+ inset 0 0 0 0.25rem var(--handle-color),
2926
+ 0px 0 0 0.5px rgba(0, 0, 0, 0.1),
2927
+ var(--figma-elevation-200);
2902
2928
  transform: translate(-50%, -50%);
2903
2929
  }
2904
2930
  }
@@ -3054,16 +3080,21 @@ fig-fill-picker {
3054
3080
  .fig-fill-picker-checkerboard {
3055
3081
  position: absolute;
3056
3082
  inset: 0;
3057
- background-image: linear-gradient(
3058
- 45deg,
3083
+ background-image:
3084
+ linear-gradient(45deg, var(--figma-color-bg-tertiary) 25%, transparent 25%),
3085
+ linear-gradient(
3086
+ -45deg,
3059
3087
  var(--figma-color-bg-tertiary) 25%,
3060
3088
  transparent 25%
3061
3089
  ),
3062
- linear-gradient(-45deg, var(--figma-color-bg-tertiary) 25%, transparent 25%),
3063
3090
  linear-gradient(45deg, transparent 75%, var(--figma-color-bg-tertiary) 75%),
3064
3091
  linear-gradient(-45deg, transparent 75%, var(--figma-color-bg-tertiary) 75%);
3065
3092
  background-size: 16px 16px;
3066
- background-position: 0 0, 0 8px, 8px -8px, -8px 0px;
3093
+ background-position:
3094
+ 0 0,
3095
+ 0 8px,
3096
+ 8px -8px,
3097
+ -8px 0px;
3067
3098
  }
3068
3099
 
3069
3100
  .fig-fill-picker-image-preview {
package/fig.js CHANGED
@@ -1445,6 +1445,9 @@ class FigSlider extends HTMLElement {
1445
1445
  this.units = this.getAttribute("units") || "";
1446
1446
  this.transform = Number(this.getAttribute("transform") || 1);
1447
1447
  this.disabled = this.getAttribute("disabled") ? true : false;
1448
+ this.precision = this.hasAttribute("precision")
1449
+ ? Number(this.getAttribute("precision"))
1450
+ : null;
1448
1451
 
1449
1452
  const defaults = this.#typeDefaults[this.type];
1450
1453
  this.min = Number(this.getAttribute("min") || defaults.min);
@@ -1481,7 +1484,8 @@ class FigSlider extends HTMLElement {
1481
1484
  transform="${this.transform}"
1482
1485
  step="${this.step}"
1483
1486
  value="${this.value}"
1484
- ${this.units ? `units="${this.units}"` : ""}>
1487
+ ${this.units ? `units="${this.units}"` : ""}
1488
+ ${this.precision !== null ? `precision="${this.precision}"` : ""}>
1485
1489
  </fig-input-number>`;
1486
1490
  } else {
1487
1491
  html = slider;
@@ -1654,6 +1658,7 @@ class FigSlider extends HTMLElement {
1654
1658
  "transform",
1655
1659
  "text",
1656
1660
  "default",
1661
+ "precision",
1657
1662
  ];
1658
1663
  }
1659
1664
 
@@ -1690,6 +1695,16 @@ class FigSlider extends HTMLElement {
1690
1695
  this.figInputNumber.setAttribute("transform", this.transform);
1691
1696
  }
1692
1697
  break;
1698
+ case "precision":
1699
+ this.precision = newValue !== null ? Number(newValue) : null;
1700
+ if (this.figInputNumber) {
1701
+ if (this.precision !== null) {
1702
+ this.figInputNumber.setAttribute("precision", this.precision);
1703
+ } else {
1704
+ this.figInputNumber.removeAttribute("precision");
1705
+ }
1706
+ }
1707
+ break;
1693
1708
  case "default":
1694
1709
  this.default = newValue;
1695
1710
  this.#syncProperties();
@@ -2011,6 +2026,7 @@ class FigInputNumber extends HTMLElement {
2011
2026
  #boundKeyDown;
2012
2027
  #units;
2013
2028
  #unitPosition;
2029
+ #precision;
2014
2030
 
2015
2031
  constructor() {
2016
2032
  super();
@@ -2045,6 +2061,9 @@ class FigInputNumber extends HTMLElement {
2045
2061
  this.name = this.getAttribute("name") || null;
2046
2062
  this.#units = this.getAttribute("units") || "";
2047
2063
  this.#unitPosition = this.getAttribute("unit-position") || "suffix";
2064
+ this.#precision = this.hasAttribute("precision")
2065
+ ? Number(this.getAttribute("precision"))
2066
+ : 2;
2048
2067
 
2049
2068
  if (this.getAttribute("step")) {
2050
2069
  this.step = Number(this.getAttribute("step"));
@@ -2331,10 +2350,12 @@ class FigInputNumber extends HTMLElement {
2331
2350
  return sanitized;
2332
2351
  }
2333
2352
 
2334
- #formatNumber(num, precision = 2) {
2335
- // Check if the number has any decimal places after rounding
2336
- const rounded = Math.round(num * 100) / 100;
2337
- return Number.isInteger(rounded) ? rounded : rounded.toFixed(precision);
2353
+ #formatNumber(num) {
2354
+ const precision = this.#precision ?? 2;
2355
+ const factor = Math.pow(10, precision);
2356
+ const rounded = Math.round(num * factor) / factor;
2357
+ // Only show decimals if needed and up to precision
2358
+ return Number.isInteger(rounded) ? rounded : parseFloat(rounded.toFixed(precision));
2338
2359
  }
2339
2360
 
2340
2361
  static get observedAttributes() {
@@ -2350,6 +2371,7 @@ class FigInputNumber extends HTMLElement {
2350
2371
  "units",
2351
2372
  "unit-position",
2352
2373
  "steppers",
2374
+ "precision",
2353
2375
  ];
2354
2376
  }
2355
2377
 
@@ -2386,6 +2408,10 @@ class FigInputNumber extends HTMLElement {
2386
2408
  case "step":
2387
2409
  this[name] = Number(newValue);
2388
2410
  break;
2411
+ case "precision":
2412
+ this.#precision = newValue !== null ? Number(newValue) : 2;
2413
+ this.input.value = this.#formatWithUnit(this.value);
2414
+ break;
2389
2415
  case "name":
2390
2416
  this[name] = this.input[name] = newValue;
2391
2417
  this.input.setAttribute("name", newValue);
@@ -2928,7 +2954,7 @@ class FigInputFill extends HTMLElement {
2928
2954
  }
2929
2955
 
2930
2956
  static get observedAttributes() {
2931
- return ["value", "disabled"];
2957
+ return ["value", "disabled", "mode"];
2932
2958
  }
2933
2959
 
2934
2960
  connectedCallback() {
@@ -3078,11 +3104,12 @@ class FigInputFill extends HTMLElement {
3078
3104
  break;
3079
3105
  }
3080
3106
 
3107
+ const modeAttr = this.getAttribute("mode");
3081
3108
  this.innerHTML = `
3082
3109
  <div class="input-combo">
3083
3110
  <fig-fill-picker value='${fillPickerValue}' ${
3084
3111
  disabled ? "disabled" : ""
3085
- }></fig-fill-picker>
3112
+ } ${modeAttr ? `mode="${modeAttr}"` : ""}></fig-fill-picker>
3086
3113
  ${controlsHtml}
3087
3114
  </div>`;
3088
3115
 
@@ -5554,31 +5581,43 @@ class FigFillPicker extends HTMLElement {
5554
5581
  this.#dialog.setAttribute("position", dialogPosition);
5555
5582
  }
5556
5583
 
5557
- // Check for locked mode
5584
+ // Check for allowed modes (supports comma-separated values like "solid,gradient")
5558
5585
  const mode = this.getAttribute("mode");
5559
- const validModes = ["solid", "gradient", "image", "video", "webcam"];
5560
- const lockedMode = validModes.includes(mode) ? mode : null;
5561
-
5562
- // If locked mode, force fillType
5563
- if (lockedMode) {
5564
- this.#fillType = lockedMode;
5565
- this.#activeTab = lockedMode;
5566
- }
5567
-
5568
- // Build header content - dropdown or label
5569
- const headerContent = lockedMode
5570
- ? `<span class="fig-fill-picker-type-label">${
5571
- lockedMode.charAt(0).toUpperCase() + lockedMode.slice(1)
5572
- }</span>`
5573
- : `<fig-dropdown class="fig-fill-picker-type" variant="neue" value="${
5574
- this.#fillType
5575
- }">
5576
- <option value="solid">Solid</option>
5577
- <option value="gradient">Gradient</option>
5578
- <option value="image">Image</option>
5579
- <option value="video">Video</option>
5580
- <option value="webcam">Webcam</option>
5586
+ const allModes = ["solid", "gradient", "image", "video", "webcam"];
5587
+ const modeLabels = {
5588
+ solid: "Solid",
5589
+ gradient: "Gradient",
5590
+ image: "Image",
5591
+ video: "Video",
5592
+ webcam: "Webcam",
5593
+ };
5594
+
5595
+ // Parse allowed modes
5596
+ let allowedModes = allModes;
5597
+ if (mode) {
5598
+ const requestedModes = mode.split(",").map((m) => m.trim().toLowerCase());
5599
+ allowedModes = requestedModes.filter((m) => allModes.includes(m));
5600
+ if (allowedModes.length === 0) allowedModes = allModes;
5601
+ }
5602
+
5603
+ // If current fillType not in allowed modes, switch to first allowed
5604
+ if (!allowedModes.includes(this.#fillType)) {
5605
+ this.#fillType = allowedModes[0];
5606
+ this.#activeTab = allowedModes[0];
5607
+ }
5608
+
5609
+ // Build header content - label if single mode, dropdown if multiple
5610
+ let headerContent;
5611
+ if (allowedModes.length === 1) {
5612
+ headerContent = `<span class="fig-fill-picker-type-label">${modeLabels[allowedModes[0]]}</span>`;
5613
+ } else {
5614
+ const options = allowedModes
5615
+ .map((m) => `<option value="${m}">${modeLabels[m]}</option>`)
5616
+ .join("\n ");
5617
+ headerContent = `<fig-dropdown class="fig-fill-picker-type" variant="neue" value="${this.#fillType}">
5618
+ ${options}
5581
5619
  </fig-dropdown>`;
5620
+ }
5582
5621
 
5583
5622
  this.#dialog.innerHTML = `
5584
5623
  <fig-header>
@@ -5627,13 +5666,19 @@ class FigFillPicker extends HTMLElement {
5627
5666
  }
5628
5667
 
5629
5668
  #switchTab(tabName) {
5630
- // Check for locked mode - prevent switching if locked
5669
+ // Check for allowed modes - prevent switching to disallowed mode
5631
5670
  const mode = this.getAttribute("mode");
5632
- const validModes = ["solid", "gradient", "image", "video", "webcam"];
5633
- const lockedMode = validModes.includes(mode) ? mode : null;
5671
+ const allModes = ["solid", "gradient", "image", "video", "webcam"];
5672
+
5673
+ let allowedModes = allModes;
5674
+ if (mode) {
5675
+ const requestedModes = mode.split(",").map((m) => m.trim().toLowerCase());
5676
+ allowedModes = requestedModes.filter((m) => allModes.includes(m));
5677
+ if (allowedModes.length === 0) allowedModes = allModes;
5678
+ }
5634
5679
 
5635
- if (lockedMode && tabName !== lockedMode) {
5636
- return; // Don't allow switching away from locked mode
5680
+ if (!allowedModes.includes(tabName)) {
5681
+ return; // Don't allow switching to disallowed mode
5637
5682
  }
5638
5683
 
5639
5684
  this.#activeTab = tabName;
@@ -5901,7 +5946,7 @@ class FigFillPicker extends HTMLElement {
5901
5946
 
5902
5947
  container.innerHTML = `
5903
5948
  <div class="fig-fill-picker-gradient-header">
5904
- <fig-dropdown class="fig-fill-picker-gradient-type" value="${
5949
+ <fig-dropdown class="fig-fill-picker-gradient-type" variant="neue" value="${
5905
5950
  this.#gradient.type
5906
5951
  }">
5907
5952
  <option value="linear" selected>Linear</option>
@@ -6133,8 +6178,9 @@ class FigFillPicker extends HTMLElement {
6133
6178
  this.#gradient.centerY
6134
6179
  }%, ${stops})`;
6135
6180
  case "angular":
6136
- // Offset by 90° to align with fig-input-angle (0° = right) vs CSS conic (0° = top)
6137
- return `conic-gradient(from ${this.#gradient.angle + 90}deg, ${stops})`;
6181
+ // Internal gradient.angle is already in CSS coordinate system (0° = top)
6182
+ // because it's converted when reading from fig-input-angle
6183
+ return `conic-gradient(from ${this.#gradient.angle}deg, ${stops})`;
6138
6184
  default:
6139
6185
  return `linear-gradient(${this.#gradient.angle}deg, ${stops})`;
6140
6186
  }
package/index.html CHANGED
@@ -861,6 +861,20 @@
861
861
  <option>Option Three</option>
862
862
  </fig-dropdown>
863
863
 
864
+ <h3>Autoresize to Content</h3>
865
+ <p style="font-size: 12px; color: var(--figma-color-text-secondary); margin-bottom: 8px;">
866
+ Uses <code>field-sizing: content</code> to resize based on selected option.
867
+ </p>
868
+ <fig-dropdown autoresize
869
+ variant="neue">
870
+ <option>XS</option>
871
+ <option>Small</option>
872
+ <option>Medium</option>
873
+ <option>Large</option>
874
+ <option>Extra Large with Long Text</option>
875
+ <option>XXL Super Extended Option</option>
876
+ </fig-dropdown>
877
+
864
878
  <h3>Dropdown Neue Variant (with many options)</h3>
865
879
  <fig-dropdown variant="neue"
866
880
  type="dropdown"
@@ -1264,6 +1278,19 @@
1264
1278
  <h4>Image Only</h4>
1265
1279
  <fig-fill-picker mode="image"></fig-fill-picker>
1266
1280
 
1281
+ <h3>Limited Mode Selection</h3>
1282
+ <p class="description">Use comma-separated values in the <code>mode</code> attribute to limit available modes
1283
+ while still allowing switching between them.</p>
1284
+
1285
+ <h4>Solid and Gradient Only</h4>
1286
+ <fig-fill-picker mode="solid,gradient"></fig-fill-picker>
1287
+
1288
+ <h4>Solid, Gradient, and Image</h4>
1289
+ <fig-fill-picker mode="solid,gradient,image"></fig-fill-picker>
1290
+
1291
+ <h4>With fig-input-fill (mode passed through)</h4>
1292
+ <fig-input-fill mode="solid,gradient" value='{"type":"solid","color":"#667eea"}'></fig-input-fill>
1293
+
1267
1294
  <h3>Event Listening</h3>
1268
1295
  <fig-fill-picker id="fill-picker-events"
1269
1296
  value='{"type":"solid","color":"#FCBAD3"}'></fig-fill-picker>
@@ -1813,6 +1840,31 @@
1813
1840
  steppers="true"></fig-input-number>
1814
1841
  </fig-field>
1815
1842
 
1843
+ <h3>With Precision</h3>
1844
+ <p class="description">Control decimal places with the <code>precision</code> attribute.</p>
1845
+ <fig-field direction="horizontal">
1846
+ <label>Default (2)</label>
1847
+ <fig-input-number value="3.14159"
1848
+ step="0.001"></fig-input-number>
1849
+ </fig-field>
1850
+ <fig-field direction="horizontal">
1851
+ <label>Precision 0</label>
1852
+ <fig-input-number value="3.14159"
1853
+ precision="0"></fig-input-number>
1854
+ </fig-field>
1855
+ <fig-field direction="horizontal">
1856
+ <label>Precision 1</label>
1857
+ <fig-input-number value="3.14159"
1858
+ precision="1"
1859
+ step="0.1"></fig-input-number>
1860
+ </fig-field>
1861
+ <fig-field direction="horizontal">
1862
+ <label>Precision 4</label>
1863
+ <fig-input-number value="3.14159"
1864
+ precision="4"
1865
+ step="0.0001"></fig-input-number>
1866
+ </fig-field>
1867
+
1816
1868
  <h3>States</h3>
1817
1869
  <fig-field direction="horizontal">
1818
1870
  <label>Disabled</label>
@@ -2618,6 +2670,64 @@
2618
2670
  text="true"
2619
2671
  units="%"></fig-slider>
2620
2672
 
2673
+ <h3>With Precision</h3>
2674
+ <p class="description">Control decimal places in the text input with the <code>precision</code> attribute.</p>
2675
+ <fig-field direction="horizontal">
2676
+ <label>Precision 0</label>
2677
+ <fig-slider min="0"
2678
+ max="100"
2679
+ value="33"
2680
+ text="true"
2681
+ precision="0"></fig-slider>
2682
+ </fig-field>
2683
+ <fig-field direction="horizontal">
2684
+ <label>Precision 1</label>
2685
+ <fig-slider min="0"
2686
+ max="10"
2687
+ value="3.5"
2688
+ step="0.1"
2689
+ text="true"
2690
+ precision="1"></fig-slider>
2691
+ </fig-field>
2692
+ <fig-field direction="horizontal">
2693
+ <label>Precision 3</label>
2694
+ <fig-slider min="0"
2695
+ max="1"
2696
+ value="0.333"
2697
+ step="0.001"
2698
+ text="true"
2699
+ precision="3"></fig-slider>
2700
+ </fig-field>
2701
+ <fig-field direction="horizontal">
2702
+ <label>Neue - Precision 0</label>
2703
+ <fig-slider min="0"
2704
+ max="100"
2705
+ value="33"
2706
+ text="true"
2707
+ precision="0"
2708
+ variant="neue"></fig-slider>
2709
+ </fig-field>
2710
+ <fig-field direction="horizontal">
2711
+ <label>Neue - Precision 1</label>
2712
+ <fig-slider min="0"
2713
+ max="10"
2714
+ value="3.5"
2715
+ step="0.1"
2716
+ text="true"
2717
+ precision="1"
2718
+ variant="neue"></fig-slider>
2719
+ </fig-field>
2720
+ <fig-field direction="horizontal">
2721
+ <label>Neue - Precision 3</label>
2722
+ <fig-slider min="0"
2723
+ max="1"
2724
+ value="0.333"
2725
+ step="0.001"
2726
+ text="true"
2727
+ precision="3"
2728
+ variant="neue"></fig-slider>
2729
+ </fig-field>
2730
+
2621
2731
  <h3>Variants</h3>
2622
2732
  <fig-field direction="horizontal">
2623
2733
  <label>Default</label>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "2.13.1",
3
+ "version": "2.14.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",