@rogieking/figui3 2.10.1 → 2.10.2

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 (3) hide show
  1. package/components.css +1 -11
  2. package/fig.js +37 -74
  3. package/package.json +1 -1
package/components.css CHANGED
@@ -581,10 +581,8 @@ input[type="text"][list] {
581
581
  }
582
582
 
583
583
  ::picker(select) {
584
- position-area: auto;
585
- align-self: auto;
586
- max-block-size: 100vh;
587
584
  appearance: base-select;
585
+
588
586
  scrollbar-width: thin;
589
587
  outline: 0;
590
588
  scrollbar-color: var(--figma-color-text-menu-tertiary)
@@ -595,14 +593,6 @@ input[type="text"][list] {
595
593
  padding: var(--spacer-2) 0;
596
594
  box-shadow: var(--figma-elevation-400-menu-panel);
597
595
  }
598
- select {
599
- &::picker(select) {
600
- /* --picker-top and --picker-max-height are calculated by JavaScript */
601
- top: var(--picker-top, 0px);
602
- max-height: var(--picker-max-height, 100vh);
603
- overflow-y: auto;
604
- }
605
- }
606
596
  }
607
597
  }
608
598
 
package/fig.js CHANGED
@@ -167,9 +167,6 @@ 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
- #optionHeight = 28; // Default option height in pixels
171
- #pickerPadding = 8; // Padding inside picker (spacer-2)
172
- #viewportMargin = 8; // Minimum margin from viewport edges
173
170
 
174
171
  get label() {
175
172
  return this.#label;
@@ -187,12 +184,6 @@ class FigDropdown extends HTMLElement {
187
184
  #addEventListeners() {
188
185
  this.select.addEventListener("input", this.#handleSelectInput.bind(this));
189
186
  this.select.addEventListener("change", this.#handleSelectChange.bind(this));
190
- // Calculate picker position before it opens
191
- this.select.addEventListener(
192
- "mousedown",
193
- this.#calculatePickerPosition.bind(this),
194
- );
195
- this.select.addEventListener("keydown", this.#handleKeyDown.bind(this));
196
187
  }
197
188
 
198
189
  connectedCallback() {
@@ -230,7 +221,6 @@ class FigDropdown extends HTMLElement {
230
221
  if (this.type === "dropdown") {
231
222
  this.select.selectedIndex = -1;
232
223
  }
233
- this.#calculatePickerPosition();
234
224
  }
235
225
 
236
226
  #handleSelectInput(e) {
@@ -257,7 +247,6 @@ class FigDropdown extends HTMLElement {
257
247
  if (this.type === "dropdown") {
258
248
  this.select.selectedIndex = -1;
259
249
  }
260
- this.#calculatePickerPosition();
261
250
  this.dispatchEvent(
262
251
  new CustomEvent("change", {
263
252
  detail: selectedValue,
@@ -267,64 +256,6 @@ class FigDropdown extends HTMLElement {
267
256
  );
268
257
  }
269
258
 
270
- #handleKeyDown(e) {
271
- // Recalculate on keyboard navigation that might open the picker
272
- if (
273
- e.key === " " ||
274
- e.key === "Enter" ||
275
- e.key === "ArrowDown" ||
276
- e.key === "ArrowUp"
277
- ) {
278
- this.#calculatePickerPosition();
279
- }
280
- }
281
-
282
- #calculatePickerPosition() {
283
- // Only apply positioning for neue variant
284
- if (this.getAttribute("variant") !== "neue") {
285
- return;
286
- }
287
-
288
- // Get positions in viewport
289
- const dropdownRect = this.getBoundingClientRect();
290
- const selectRect = this.select.getBoundingClientRect();
291
- const viewportHeight = window.innerHeight;
292
-
293
- // Get selected index (use 0 for dropdown type since nothing is visually selected)
294
- let selectedIndex = this.select.selectedIndex;
295
- if (this.type === "dropdown" || selectedIndex < 0) {
296
- selectedIndex = 0;
297
- }
298
-
299
- // Calculate the top offset to align selected item with trigger
300
- // This is relative to the anchor point (bottom of select by default)
301
- const pickerTopOffset =
302
- -1 * (selectedIndex + 1) * this.#optionHeight - this.#pickerPadding;
303
-
304
- // The picker is anchored to the select, and top offset is relative to the anchor
305
- // Anchor is typically at the bottom of the select element
306
- // Actual picker top = select.bottom + topOffset (topOffset is negative)
307
- const pickerActualTop = selectRect.bottom + pickerTopOffset;
308
-
309
- // Ensure picker doesn't go above viewport
310
- let adjustedTopOffset = pickerTopOffset;
311
- if (pickerActualTop < this.#viewportMargin) {
312
- adjustedTopOffset = this.#viewportMargin - selectRect.bottom;
313
- }
314
-
315
- // Recalculate actual top with adjusted offset
316
- const adjustedActualTop = selectRect.bottom + adjustedTopOffset;
317
-
318
- // Calculate max-height: from picker's actual top to 1rem (16px) from bottom of viewport
319
- const bottomMargin = 16; // 1rem
320
- const maxHeight = viewportHeight - adjustedActualTop - bottomMargin;
321
-
322
- // Set CSS variables for the picker
323
- this.select.style.setProperty("--selected-index", selectedIndex);
324
- this.select.style.setProperty("--picker-top", `${adjustedTopOffset}px`);
325
- this.select.style.setProperty("--picker-max-height", `${Math.max(0, maxHeight)}px`);
326
- }
327
-
328
259
  focus() {
329
260
  this.select.focus();
330
261
  }
@@ -346,7 +277,7 @@ class FigDropdown extends HTMLElement {
346
277
  this.setAttribute("value", value);
347
278
  }
348
279
  static get observedAttributes() {
349
- return ["value", "type", "variant"];
280
+ return ["value", "type"];
350
281
  }
351
282
  #syncSelectedValue(value) {
352
283
  // For dropdown type, don't sync the visual selection - it should always show the hidden placeholder
@@ -359,7 +290,6 @@ class FigDropdown extends HTMLElement {
359
290
  this.select.selectedIndex = i;
360
291
  }
361
292
  });
362
- this.#calculatePickerPosition();
363
293
  }
364
294
  }
365
295
  attributeChangedCallback(name, oldValue, newValue) {
@@ -373,9 +303,6 @@ class FigDropdown extends HTMLElement {
373
303
  this.#label = newValue;
374
304
  this.select.setAttribute("aria-label", this.#label);
375
305
  }
376
- if (name === "variant") {
377
- this.#calculatePickerPosition();
378
- }
379
306
  }
380
307
  }
381
308
 
@@ -2008,6 +1935,7 @@ class FigInputNumber extends HTMLElement {
2008
1935
  #boundInput;
2009
1936
  #boundFocus;
2010
1937
  #boundBlur;
1938
+ #boundKeyDown;
2011
1939
  #units;
2012
1940
  #unitPosition;
2013
1941
 
@@ -2031,6 +1959,9 @@ class FigInputNumber extends HTMLElement {
2031
1959
  this.#boundBlur = (e) => {
2032
1960
  this.#handleBlur(e);
2033
1961
  };
1962
+ this.#boundKeyDown = (e) => {
1963
+ this.#handleKeyDown(e);
1964
+ };
2034
1965
  }
2035
1966
 
2036
1967
  connectedCallback() {
@@ -2103,6 +2034,8 @@ class FigInputNumber extends HTMLElement {
2103
2034
  this.input.addEventListener("focus", this.#boundFocus);
2104
2035
  this.input.removeEventListener("blur", this.#boundBlur);
2105
2036
  this.input.addEventListener("blur", this.#boundBlur);
2037
+ this.input.removeEventListener("keydown", this.#boundKeyDown);
2038
+ this.input.addEventListener("keydown", this.#boundKeyDown);
2106
2039
  });
2107
2040
  }
2108
2041
 
@@ -2112,6 +2045,7 @@ class FigInputNumber extends HTMLElement {
2112
2045
  this.input.removeEventListener("input", this.#boundInput);
2113
2046
  this.input.removeEventListener("focus", this.#boundFocus);
2114
2047
  this.input.removeEventListener("blur", this.#boundBlur);
2048
+ this.input.removeEventListener("keydown", this.#boundKeyDown);
2115
2049
  }
2116
2050
  this.removeEventListener("pointerdown", this.#boundMouseDown);
2117
2051
  window.removeEventListener("pointermove", this.#boundMouseMove);
@@ -2203,6 +2137,35 @@ class FigInputNumber extends HTMLElement {
2203
2137
  );
2204
2138
  }
2205
2139
 
2140
+ #handleKeyDown(e) {
2141
+ if (this.disabled) return;
2142
+
2143
+ // Only handle arrow up/down
2144
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
2145
+
2146
+ e.preventDefault();
2147
+
2148
+ const step = this.step || 1;
2149
+ // Shift multiplies step by 10
2150
+ const multiplier = e.shiftKey ? 10 : 1;
2151
+ const delta = step * multiplier * (e.key === "ArrowUp" ? 1 : -1);
2152
+
2153
+ let numericValue = this.#getNumericValue(this.input.value);
2154
+ let value =
2155
+ (numericValue !== "" ? Number(numericValue) / (this.transform || 1) : 0) +
2156
+ delta;
2157
+ value = this.#sanitizeInput(value, false);
2158
+ this.value = value;
2159
+ this.input.value = this.#formatWithUnit(this.value);
2160
+
2161
+ this.dispatchEvent(
2162
+ new CustomEvent("input", { detail: this.value, bubbles: true }),
2163
+ );
2164
+ this.dispatchEvent(
2165
+ new CustomEvent("change", { detail: this.value, bubbles: true }),
2166
+ );
2167
+ }
2168
+
2206
2169
  #handleInput(e) {
2207
2170
  let numericValue = this.#getNumericValue(e.target.value);
2208
2171
  if (numericValue !== "") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "2.10.1",
3
+ "version": "2.10.2",
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",