@rogieking/figui3 2.36.0 → 2.37.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
@@ -319,12 +319,13 @@ A range slider with multiple types and optional text input.
319
319
  | Attribute | Type | Default | Description |
320
320
  |-----------|------|---------|-------------|
321
321
  | `type` | string | `"range"` | Type: `"range"`, `"hue"`, `"opacity"`, `"delta"`, `"stepper"` |
322
- | `value` | number | — | Current value |
322
+ | `value` | number | — | Current value; missing/invalid values fall back to `min` (or `default` for `type="delta"`, then `0`) |
323
323
  | `min` | number | `0` | Minimum value |
324
324
  | `max` | number | `100` | Maximum value |
325
325
  | `step` | number | `1` | Step increment |
326
326
  | `default` | number | — | Default/reset value (shown as a marker on the track) |
327
327
  | `text` | boolean | `false` | Show text input |
328
+ | `placeholder` | string | `"##"` | Placeholder for text input mode (`text="true"`) |
328
329
  | `units` | string | — | Unit label (e.g., `"%"`, `"px"`) |
329
330
  | `transform` | number | — | Multiplier for display value |
330
331
  | `color` | string | — | Track color (for opacity type) |
package/components.css CHANGED
@@ -1959,7 +1959,7 @@ fig-slider {
1959
1959
  --slider-handle-shadow-focus:
1960
1960
  inset 0 0 0 4px var(--handle-color), inset 0 0 0 5px rgba(0, 0, 0, 0.1),
1961
1961
  var(--handle-shadow), 0 0 0 1px var(--figma-color-border-selected);
1962
- --slider-transition: all 0.1s ease-in-out;
1962
+ --slider-transition: none;
1963
1963
  --handle-transition: var(--slider-transition);
1964
1964
 
1965
1965
  display: inline-flex;
package/fig.js CHANGED
@@ -2357,6 +2357,7 @@ customElements.define("fig-segmented-control", FigSegmentedControl);
2357
2357
  * @attr {number} max - The maximum value
2358
2358
  * @attr {number} step - The step increment
2359
2359
  * @attr {boolean} text - Whether to show a text input alongside the slider
2360
+ * @attr {string} placeholder - Placeholder for the number input when text is enabled
2360
2361
  * @attr {string} units - The units to display after the value
2361
2362
  * @attr {number} transform - A multiplier for the displayed value
2362
2363
  * @attr {boolean} disabled - Whether the slider is disabled
@@ -2364,6 +2365,7 @@ customElements.define("fig-segmented-control", FigSegmentedControl);
2364
2365
  */
2365
2366
  class FigSlider extends HTMLElement {
2366
2367
  #isInteracting = false;
2368
+ #showEmptyTextValue = false;
2367
2369
  // Private fields declarations
2368
2370
  #typeDefaults = {
2369
2371
  range: { min: 0, max: 100, step: 1 },
@@ -2405,7 +2407,7 @@ class FigSlider extends HTMLElement {
2405
2407
  }
2406
2408
 
2407
2409
  #regenerateInnerHTML() {
2408
- this.value = Number(this.getAttribute("value") || 0);
2410
+ const rawValue = this.getAttribute("value");
2409
2411
  this.type = this.getAttribute("type") || "range";
2410
2412
  this.variant = this.getAttribute("variant") || "default";
2411
2413
  this.text =
@@ -2416,13 +2418,25 @@ class FigSlider extends HTMLElement {
2416
2418
  this.precision = this.hasAttribute("precision")
2417
2419
  ? Number(this.getAttribute("precision"))
2418
2420
  : null;
2421
+ this.placeholder =
2422
+ this.getAttribute("placeholder") !== null
2423
+ ? this.getAttribute("placeholder")
2424
+ : "##";
2419
2425
 
2420
2426
  const defaults = this.#typeDefaults[this.type];
2421
2427
  this.min = Number(this.getAttribute("min") || defaults.min);
2422
2428
  this.max = Number(this.getAttribute("max") || defaults.max);
2423
2429
  this.step = Number(this.getAttribute("step") || defaults.step);
2424
2430
  this.color = this.getAttribute("color") || defaults?.color;
2425
- this.default = this.getAttribute("default") || this.min;
2431
+ this.default = this.hasAttribute("default")
2432
+ ? this.getAttribute("default")
2433
+ : this.type === "delta"
2434
+ ? 0
2435
+ : this.min;
2436
+ this.#showEmptyTextValue =
2437
+ rawValue === null ||
2438
+ (typeof rawValue === "string" && rawValue.trim() === "");
2439
+ this.value = this.#normalizeSliderValue(rawValue);
2426
2440
 
2427
2441
  if (this.color) {
2428
2442
  this.style.setProperty("--color", this.color);
@@ -2447,12 +2461,12 @@ class FigSlider extends HTMLElement {
2447
2461
  if (this.text) {
2448
2462
  html = `${slider}
2449
2463
  <fig-input-number
2450
- placeholder="##"
2464
+ placeholder="${this.placeholder}"
2451
2465
  min="${this.min}"
2452
2466
  max="${this.max}"
2453
2467
  transform="${this.transform}"
2454
2468
  step="${this.step}"
2455
- value="${this.value}"
2469
+ value="${this.#showEmptyTextValue ? "" : this.value}"
2456
2470
  ${this.units ? `units="${this.units}"` : ""}
2457
2471
  ${this.precision !== null ? `precision="${this.precision}"` : ""}>
2458
2472
  </fig-input-number>`;
@@ -2566,17 +2580,59 @@ class FigSlider extends HTMLElement {
2566
2580
 
2567
2581
  #handleTextInput() {
2568
2582
  if (this.figInputNumber) {
2569
- this.value = this.input.value = this.figInputNumber.value;
2570
- this.#syncProperties();
2583
+ const rawTextValue = this.figInputNumber.value;
2584
+ this.#showEmptyTextValue =
2585
+ rawTextValue === null ||
2586
+ rawTextValue === undefined ||
2587
+ (typeof rawTextValue === "string" && rawTextValue.trim() === "");
2588
+ const normalized = this.#normalizeSliderValue(rawTextValue);
2589
+ this.value = normalized;
2590
+ this.input.value = String(normalized);
2591
+ this.#syncValue();
2571
2592
  this.dispatchEvent(
2572
2593
  new CustomEvent("input", { detail: this.value, bubbles: true }),
2573
2594
  );
2574
2595
  }
2575
2596
  }
2576
2597
  #calculateNormal(value) {
2577
- let min = Number(this.min);
2578
- let max = Number(this.max);
2579
- return (Number(value) - min) / (max - min);
2598
+ const { min, max } = this.#getBounds();
2599
+ const range = max - min;
2600
+ if (range === 0) return 0;
2601
+ return (Number(value) - min) / range;
2602
+ }
2603
+ #toFiniteNumber(value) {
2604
+ if (value === null || value === undefined) return null;
2605
+ if (typeof value === "string" && value.trim() === "") return null;
2606
+ const parsed = Number(value);
2607
+ return Number.isFinite(parsed) ? parsed : null;
2608
+ }
2609
+ #getBounds() {
2610
+ let min = this.#toFiniteNumber(this.min);
2611
+ let max = this.#toFiniteNumber(this.max);
2612
+ if (min === null) min = 0;
2613
+ if (max === null) max = min;
2614
+ if (min > max) {
2615
+ [min, max] = [max, min];
2616
+ }
2617
+ return { min, max };
2618
+ }
2619
+ #clampToBounds(value) {
2620
+ const { min, max } = this.#getBounds();
2621
+ return Math.min(max, Math.max(min, value));
2622
+ }
2623
+ #getFallbackValue() {
2624
+ if (this.type === "delta") {
2625
+ const deltaDefault = this.#toFiniteNumber(this.default);
2626
+ if (deltaDefault !== null) return this.#clampToBounds(deltaDefault);
2627
+ return this.#clampToBounds(0);
2628
+ }
2629
+ const { min } = this.#getBounds();
2630
+ return min;
2631
+ }
2632
+ #normalizeSliderValue(rawValue) {
2633
+ const parsed = this.#toFiniteNumber(rawValue);
2634
+ if (parsed === null) return this.#getFallbackValue();
2635
+ return this.#clampToBounds(parsed);
2580
2636
  }
2581
2637
  #syncProperties() {
2582
2638
  let complete = this.#calculateNormal(this.value);
@@ -2592,11 +2648,15 @@ class FigSlider extends HTMLElement {
2592
2648
  // Update ARIA value
2593
2649
  this.input.setAttribute("aria-valuenow", val);
2594
2650
  if (this.figInputNumber) {
2595
- this.figInputNumber.setAttribute("value", val);
2651
+ this.figInputNumber.setAttribute(
2652
+ "value",
2653
+ this.#showEmptyTextValue ? "" : val,
2654
+ );
2596
2655
  }
2597
2656
  }
2598
2657
 
2599
2658
  #handleInput() {
2659
+ this.#showEmptyTextValue = false;
2600
2660
  this.#syncValue();
2601
2661
  this.dispatchEvent(
2602
2662
  new CustomEvent("input", { detail: this.value, bubbles: true }),
@@ -2605,6 +2665,7 @@ class FigSlider extends HTMLElement {
2605
2665
 
2606
2666
  #handleChange() {
2607
2667
  this.#isInteracting = false;
2668
+ this.#showEmptyTextValue = false;
2608
2669
  this.#syncValue();
2609
2670
  this.dispatchEvent(
2610
2671
  new CustomEvent("change", { detail: this.value, bubbles: true }),
@@ -2613,8 +2674,15 @@ class FigSlider extends HTMLElement {
2613
2674
 
2614
2675
  #handleTextChange() {
2615
2676
  if (this.figInputNumber) {
2616
- this.value = this.input.value = this.figInputNumber.value;
2617
- this.#syncProperties();
2677
+ const rawTextValue = this.figInputNumber.value;
2678
+ this.#showEmptyTextValue =
2679
+ rawTextValue === null ||
2680
+ rawTextValue === undefined ||
2681
+ (typeof rawTextValue === "string" && rawTextValue.trim() === "");
2682
+ const normalized = this.#normalizeSliderValue(rawTextValue);
2683
+ this.value = normalized;
2684
+ this.input.value = String(normalized);
2685
+ this.#syncValue();
2618
2686
  this.dispatchEvent(
2619
2687
  new CustomEvent("change", { detail: this.value, bubbles: true }),
2620
2688
  );
@@ -2634,6 +2702,7 @@ class FigSlider extends HTMLElement {
2634
2702
  "units",
2635
2703
  "transform",
2636
2704
  "text",
2705
+ "placeholder",
2637
2706
  "default",
2638
2707
  "precision",
2639
2708
  ];
@@ -2660,11 +2729,17 @@ class FigSlider extends HTMLElement {
2660
2729
  break;
2661
2730
  case "value":
2662
2731
  if (this.#isInteracting) break;
2663
- this.value = newValue;
2664
- this.input.value = newValue;
2665
- this.#syncProperties();
2732
+ this.#showEmptyTextValue =
2733
+ newValue === null ||
2734
+ (typeof newValue === "string" && newValue.trim() === "");
2735
+ this.value = this.#normalizeSliderValue(newValue);
2736
+ this.input.value = String(this.value);
2737
+ this.#syncValue();
2666
2738
  if (this.figInputNumber) {
2667
- this.figInputNumber.setAttribute("value", newValue);
2739
+ this.figInputNumber.setAttribute(
2740
+ "value",
2741
+ this.#showEmptyTextValue ? "" : this.value,
2742
+ );
2668
2743
  }
2669
2744
  break;
2670
2745
  case "transform":
@@ -2683,8 +2758,19 @@ class FigSlider extends HTMLElement {
2683
2758
  }
2684
2759
  }
2685
2760
  break;
2761
+ case "placeholder":
2762
+ this.placeholder = newValue !== null ? newValue : "##";
2763
+ if (this.figInputNumber) {
2764
+ this.figInputNumber.setAttribute("placeholder", this.placeholder);
2765
+ }
2766
+ break;
2686
2767
  case "default":
2687
- this.default = newValue;
2768
+ this.default =
2769
+ newValue !== null
2770
+ ? newValue
2771
+ : this.type === "delta"
2772
+ ? 0
2773
+ : this.min;
2688
2774
  this.#syncProperties();
2689
2775
  break;
2690
2776
  case "min":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "2.36.0",
3
+ "version": "2.37.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",