@rogieking/figui3 6.9.9 → 6.10.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/fig-lab.css CHANGED
@@ -15,10 +15,15 @@ fig-field-slider {
15
15
  --fig-field-slider-elastic-origin: left center;
16
16
  --fig-field-slider-elastic-transition: 0.18s cubic-bezier(0.2, 1.45, 0.35, 1);
17
17
  --fig-field-slider-height: 2rem;
18
+ --fig-field-slider-vertical-padding: var(--spacer-1);
19
+ --fig-field-slider-horizontal-padding: var(--spacer-3);
18
20
  --figma-focus-outline-offset: 1px;
19
21
  --fig-field-slider-progress-shadow: inset 0px 0.75px 0px 0px light-dark(transparent, rgba(255, 255, 255, 0.1));
20
22
  --field-text-shadow: 0 1px 1.5px
21
23
  light-dark(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2));
24
+ --fig-field-slider-fig-slider-height: calc(var(--fig-field-slider-height) - var(--fig-field-slider-vertical-padding) * 2);
25
+ --fig-field-slider-fig-slider-thumb-height: calc(var(--fig-field-slider-fig-slider-height) - var(--spacer-2-5));
26
+
22
27
  display: block;
23
28
  position: relative;
24
29
  width: 100%;
@@ -26,6 +31,15 @@ fig-field-slider {
26
31
  border-radius: var(--radius-medium);
27
32
  transform-origin: var(--fig-field-slider-elastic-origin);
28
33
  transition: transform var(--fig-field-slider-elastic-transition);
34
+ padding-top: var(--fig-field-slider-vertical-padding);
35
+ padding-bottom: var(--fig-field-slider-vertical-padding);
36
+ padding-left: var(--fig-field-slider-horizontal-padding);
37
+ padding-right: var(--fig-field-slider-horizontal-padding);
38
+
39
+ &[size="large"]{
40
+ --fig-field-slider-vertical-padding: 0px;
41
+ --fig-field-slider-fig-slider-thumb-height: calc(var(--fig-field-slider-fig-slider-height) - var(--spacer-3));
42
+ }
29
43
 
30
44
  label {
31
45
  pointer-events: none;
@@ -57,7 +71,7 @@ fig-field-slider {
57
71
 
58
72
  fig-field:not([direction="vertical"]) {
59
73
  position: relative;
60
- padding-left: var(--spacer-2-5);
74
+ padding-left: var(--spacer-2);
61
75
  display: flex;
62
76
  height: 100%;
63
77
 
@@ -83,8 +97,8 @@ fig-field-slider {
83
97
  inset: 0;
84
98
  padding: 0;
85
99
  background-color: transparent;
86
- --slider-height: 2rem;
87
- --slider-field-height: 2rem;
100
+ --slider-height: var(--fig-field-slider-fig-slider-height);
101
+ --slider-field-height: calc(var(--fig-field-slider-height) - var(--fig-field-slider-vertical-padding) * 2);
88
102
  --slider-thumb-width: var(--spacer-1);
89
103
  --slider-thumb-color: light-dark(
90
104
  var(--figma-color-text-secondary),
@@ -94,7 +108,7 @@ fig-field-slider {
94
108
  var(--spacer-2) + (var(--spacer-3) - var(--spacer-2)) *
95
109
  min(var(--slider-complete), calc(1 - var(--slider-complete))) * 2
96
110
  );*/
97
- --slider-thumb-height: calc(var(--slider-height) - var(--spacer-3));
111
+ --slider-thumb-height: var(--fig-field-slider-fig-slider-thumb-height);
98
112
  --slider-border-radius: var(--radius-medium);
99
113
  --slider-tick-width: 0.125rem;
100
114
  --slider-tick-height: var(--spacer-2);
@@ -539,10 +553,18 @@ fig-input-oscillator {
539
553
 
540
554
  .fig-input-oscillator-svg-container {
541
555
  border-radius: var(--radius-medium);
542
- background: var(--figma-color-bg-secondary);
543
- padding: var(--spacer-3) var(--spacer-2);
556
+ padding: var(--spacer-3) var(--spacer-5);
544
557
  margin-bottom: var(--spacer-2);
545
558
  overflow: hidden;
559
+ position: relative;
560
+
561
+ &::before{
562
+ position: absolute;
563
+ inset: 0 var(--spacer-3);
564
+ content: "";
565
+ background: var(--figma-color-bg-secondary);
566
+ border-radius: var(--radius-medium);
567
+ }
546
568
 
547
569
  &:is(:hover, :active, :focus, :focus-visible, :focus-within) {
548
570
  overflow: visible;
@@ -557,6 +579,8 @@ fig-input-oscillator {
557
579
  width: 100%;
558
580
  aspect-ratio: var(--aspect-ratio);
559
581
  overflow: visible;
582
+ position: relative;
583
+ z-index: 1;
560
584
 
561
585
  .fig-input-oscillator-handle{
562
586
  opacity: 0;
@@ -615,56 +639,31 @@ fig-input-oscillator {
615
639
  .fig-input-oscillator-waves {
616
640
  display: flex;
617
641
  flex-direction: column;
618
- gap: var(--spacer-2);
642
+ gap: 0;
619
643
  }
620
644
 
621
645
  .fig-input-oscillator-wave {
622
- padding: 0;
623
-
624
- > .fig-input-oscillator-wave-summary {
625
- min-height: var(--control-height);
626
- padding: 0;
627
-
628
- > label {
629
- display: inline-flex;
630
- align-items: center;
631
- gap: var(--spacer-1);
632
- min-width: 0;
646
+ margin-bottom: 0;
633
647
 
634
- > svg {
635
- flex: 0 0 auto;
636
- }
637
- }
638
-
639
- > .fig-input-oscillator-remove-button {
640
- margin-left: auto;
648
+ > fig-header {
649
+ .fig-input-oscillator-remove-button,
650
+ .fig-input-oscillator-add-type-button {
651
+ flex-shrink: 0;
641
652
  }
642
653
  }
643
654
  }
644
655
 
645
656
  .fig-input-oscillator-field {
646
- --fig-field-left-padding: 0;
647
- --fig-field-right-padding: 0;
648
-
649
- > fig-slider {
650
- min-width: 0;
651
- }
652
- }
653
-
654
- .fig-input-oscillator-add-wave {
655
- display: grid;
656
- grid-template-columns: auto;
657
- justify-content: end;
658
657
  width: 100%;
658
+ }
659
659
 
660
- > fig-button[type="select"] {
661
- > fig-dropdown,
662
- > fig-dropdown > select {
663
- width: 100%;
664
- height: 100%;
665
- min-width: 100%;
666
- padding: 0;
667
- }
660
+ .fig-input-oscillator-add-type-button {
661
+ > fig-dropdown,
662
+ > fig-dropdown > select {
663
+ width: 100%;
664
+ height: 100%;
665
+ min-width: 100%;
666
+ padding: 0;
668
667
  }
669
668
  }
670
669
 
package/fig-lab.js CHANGED
@@ -40,6 +40,12 @@ class FigFieldSlider extends HTMLElement {
40
40
  "color",
41
41
  "text",
42
42
  "full",
43
+ "elastic",
44
+ "size",
45
+ "name",
46
+ "class",
47
+ "data-wave-index",
48
+ "data-active",
43
49
  "data-elastic-dragging",
44
50
  "style",
45
51
  ]);
@@ -326,6 +332,7 @@ class FigFieldSlider extends HTMLElement {
326
332
 
327
333
  #handleElasticPointerDown(event) {
328
334
  if (event.button !== 0 || this.hasAttribute("disabled")) return;
335
+ if (this.getAttribute("elastic") === "false") return;
329
336
  if (event.target?.closest?.("fig-input-number")) return;
330
337
  const rangeInput =
331
338
  this.#slider?.querySelector('input[type="range"]') ?? this.#rangeInput;
@@ -498,7 +505,6 @@ class FigFieldSlider extends HTMLElement {
498
505
  this.removeAttribute("data-elastic-dragging");
499
506
  this.style.removeProperty("--fig-field-slider-elastic-size");
500
507
  this.style.removeProperty("--fig-field-slider-elastic-scale");
501
- this.style.removeProperty("--fig-field-slider-elastic-origin");
502
508
  }
503
509
 
504
510
  #valueFromPointer(event) {
@@ -594,7 +600,7 @@ class FigFieldSlider extends HTMLElement {
594
600
  }
595
601
 
596
602
  #forwardSliderEvent(type, event) {
597
- event.stopPropagation();
603
+ event.stopImmediatePropagation();
598
604
  if (type === "change") {
599
605
  this.#resetElasticPull();
600
606
  }
@@ -1833,11 +1839,13 @@ class FigInputOscillator extends HTMLElement {
1833
1839
  #bounds = null;
1834
1840
  #handleAmplitude = null;
1835
1841
  #handleFrequency = null;
1836
- #addTypeControl = null;
1837
1842
  #fields = [];
1838
1843
  #typeControls = [];
1839
1844
  #waveRows = [];
1845
+ #waveGroups = [];
1846
+ #expandedWaveIndices = new Set();
1840
1847
  #resizeObserver = null;
1848
+ #activeFieldInput = null;
1841
1849
 
1842
1850
  static TYPES = [
1843
1851
  { name: "Wave", value: "sine" },
@@ -2100,12 +2108,6 @@ class FigInputOscillator extends HTMLElement {
2100
2108
  #getWaveControlsHTML(disabled) {
2101
2109
  return `<div class="fig-input-oscillator-waves">
2102
2110
  ${this.#waves.map((wave, index) => this.#getWaveRowHTML(wave, index, disabled)).join("")}
2103
- <hstack class="fig-input-oscillator-add-wave">
2104
- <fig-button class="fig-input-oscillator-add-type-button" type="select" variant="ghost" icon aria-label="Choose waveform type"${disabled}>
2105
- <fig-icon name="settings"></fig-icon>
2106
- ${this.#getWaveTypeDropdownHTML("fig-input-oscillator-add-type", "sine", disabled)}
2107
- </fig-button>
2108
- </hstack>
2109
2111
  </div>`;
2110
2112
  }
2111
2113
 
@@ -2113,18 +2115,27 @@ class FigInputOscillator extends HTMLElement {
2113
2115
  const removeDisabled = disabled || this.#waves.length <= 1 ? " disabled" : "";
2114
2116
  const active = index === this.#activeWaveIndex ? " data-active" : "";
2115
2117
  const label = FigInputOscillator.#labelForType(wave.type);
2116
- return `<details class="fig-input-oscillator-wave" data-wave-index="${index}" open>
2117
- <summary class="fig-input-oscillator-wave-summary">
2118
- <label>${FigInputOscillator.waveIcon(wave.type, 24)}<span>${label}</span></label>
2119
- <fig-button class="fig-input-oscillator-remove-button" variant="ghost" icon data-wave-index="${index}" aria-label="Remove waveform"${removeDisabled}><fig-icon name="minus"></fig-icon></fig-button>
2120
- </summary>
2118
+ const open = this.#expandedWaveIndices.has(index) ? ' open="true"' : ' open="false"';
2119
+ return `<fig-group class="fig-input-oscillator-wave" collapsible borderless compact="true"${open} data-wave-index="${index}">
2120
+ <fig-header borderless>
2121
+ <h3>${label}</h3>
2122
+ <fig-tooltip text="Remove form">
2123
+ <fig-button class="fig-input-oscillator-remove-button" variant="ghost" icon data-wave-index="${index}" aria-label="Remove form"${removeDisabled}><fig-icon name="minus"></fig-icon></fig-button>
2124
+ </fig-tooltip>
2125
+ <fig-tooltip text="Add form">
2126
+ <fig-button class="fig-input-oscillator-add-type-button" type="select" variant="ghost" icon data-wave-index="${index}" aria-label="Add form"${disabled}>
2127
+ <fig-icon name="add"></fig-icon>
2128
+ ${this.#getWaveTypeDropdownHTML("fig-input-oscillator-add-type", "sine", disabled, index)}
2129
+ </fig-button>
2130
+ </fig-tooltip>
2131
+ </fig-header>
2121
2132
  <div class="fig-input-oscillator-fields" data-wave-index="${index}"${active}>
2122
2133
  ${this.#getNumberFieldHTML(index, "frequency", "Frequency", 0.1, 16, 0.1, "")}
2123
2134
  ${this.#getNumberFieldHTML(index, "amplitude", "Amplitude", -4, 4, 0.1, "")}
2124
2135
  ${this.#getNumberFieldHTML(index, "phase", "Phase", -360, 360, 1, "°")}
2125
2136
  ${this.#getNumberFieldHTML(index, "offset", "Offset", -4, 4, 0.1, "")}
2126
2137
  </div>
2127
- </details>`;
2138
+ </fig-group>`;
2128
2139
  }
2129
2140
 
2130
2141
  #getWaveTypeDropdownHTML(className, value, disabled, index = null) {
@@ -2135,9 +2146,9 @@ class FigInputOscillator extends HTMLElement {
2135
2146
  <label>${type.name}</label>
2136
2147
  </option>`;
2137
2148
  }).join("");
2138
- const indexAttr = index === null ? "" : ` data-wave-index="${index}"`;
2139
- const dropdownAttr = index === null ? ' type="dropdown" label="Choose waveform type"' : "";
2140
- return `<fig-dropdown class="${className}" value="${value}" experimental="modern"${dropdownAttr}${indexAttr}${disabled}>${options}</fig-dropdown>`;
2149
+ const indexAttr =
2150
+ index === null ? "" : ` data-wave-index="${FigInputOscillator.#escapeAttribute(String(index))}"`;
2151
+ return `<fig-dropdown class="${className}" value="${value}" experimental="modern" type="dropdown" label="Add form"${indexAttr}${disabled}>${options}</fig-dropdown>`;
2141
2152
  }
2142
2153
 
2143
2154
  #getNumberFieldHTML(index, name, label, min, max, step, units) {
@@ -2146,11 +2157,7 @@ class FigInputOscillator extends HTMLElement {
2146
2157
  ? ` units="${FigInputOscillator.#escapeAttribute(units)}"`
2147
2158
  : "";
2148
2159
  const wave = this.#waves[index] || FigInputOscillator.#defaultWave();
2149
- return `<fig-field class="fig-input-oscillator-field" direction="horizontal" data-wave-index="${index}">
2150
- <label>${label}</label>
2151
- <fig-slider name="${name}" data-wave-index="${index}" value="${this.#round(wave[name])}" min="${min}" max="${max}" step="${step}" precision="${this.#precision}" text="true"${unitsAttr}${disabled}></fig-slider>
2152
- </fig-field>
2153
- `;
2160
+ return `<fig-field-slider class="fig-input-oscillator-field" label="${label}" direction="horizontal" name="${name}" data-wave-index="${index}" value="${this.#round(wave[name])}" min="${min}" max="${max}" step="${step}" precision="${this.#precision}" elastic="false"${unitsAttr}${disabled}></fig-field-slider>`;
2154
2161
  }
2155
2162
 
2156
2163
  #cacheRefs() {
@@ -2161,16 +2168,36 @@ class FigInputOscillator extends HTMLElement {
2161
2168
  this.#bounds = this.querySelector(".fig-input-oscillator-bounds");
2162
2169
  this.#handleAmplitude = this.querySelector('[data-handle="amplitude"]');
2163
2170
  this.#handleFrequency = this.querySelector('[data-handle="frequency"]');
2164
- this.#addTypeControl = this.querySelector(".fig-input-oscillator-add-type");
2165
2171
  this.#typeControls = Array.from(
2166
2172
  this.querySelectorAll(".fig-input-oscillator-wave-type"),
2167
2173
  );
2168
- this.#fields = Array.from(this.querySelectorAll("fig-slider[name]"));
2169
- this.#waveRows = Array.from(this.querySelectorAll("[data-wave-index]")).filter(
2170
- (row) => row.classList.contains("fig-input-oscillator-field"),
2174
+ this.#fields = Array.from(this.querySelectorAll("fig-field-slider[name]"));
2175
+ this.#waveGroups = Array.from(
2176
+ this.querySelectorAll("fig-group.fig-input-oscillator-wave"),
2177
+ );
2178
+ this.#waveRows = Array.from(
2179
+ this.querySelectorAll(".fig-input-oscillator-fields"),
2171
2180
  );
2172
2181
  }
2173
2182
 
2183
+ #reindexExpandedWaves(removedIndex) {
2184
+ const nextExpanded = new Set();
2185
+ for (const index of this.#expandedWaveIndices) {
2186
+ if (index < removedIndex) nextExpanded.add(index);
2187
+ else if (index > removedIndex) nextExpanded.add(index - 1);
2188
+ }
2189
+ this.#expandedWaveIndices = nextExpanded;
2190
+ }
2191
+
2192
+ #reindexExpandedWavesAfterInsert(insertIndex) {
2193
+ const nextExpanded = new Set();
2194
+ for (const index of this.#expandedWaveIndices) {
2195
+ nextExpanded.add(index >= insertIndex ? index + 1 : index);
2196
+ }
2197
+ nextExpanded.add(insertIndex);
2198
+ this.#expandedWaveIndices = nextExpanded;
2199
+ }
2200
+
2174
2201
  #setupResizeObserver() {
2175
2202
  if (this.#resizeObserver || !window.ResizeObserver) return;
2176
2203
  this.#resizeObserver = new ResizeObserver(() => {
@@ -2344,12 +2371,17 @@ class FigInputOscillator extends HTMLElement {
2344
2371
  for (const field of this.#fields) {
2345
2372
  field.addEventListener("input", (event) => {
2346
2373
  event.stopPropagation();
2347
- this.#applyFieldValue(
2348
- this.#indexFromElement(field),
2349
- field.getAttribute("name"),
2350
- event.detail ?? event.currentTarget?.value ?? event.target?.value,
2351
- "input",
2352
- );
2374
+ this.#activeFieldInput = field;
2375
+ try {
2376
+ this.#applyFieldValue(
2377
+ this.#indexFromElement(field),
2378
+ field.getAttribute("name"),
2379
+ event.detail ?? event.currentTarget?.value ?? event.target?.value,
2380
+ "input",
2381
+ );
2382
+ } finally {
2383
+ this.#activeFieldInput = null;
2384
+ }
2353
2385
  });
2354
2386
  field.addEventListener("change", (event) => {
2355
2387
  event.stopPropagation();
@@ -2362,31 +2394,64 @@ class FigInputOscillator extends HTMLElement {
2362
2394
  });
2363
2395
  }
2364
2396
 
2365
- for (const row of this.#waveRows) {
2366
- row.addEventListener("pointerdown", () => {
2367
- this.#setActiveWave(this.#indexFromElement(row));
2397
+ for (const group of this.#waveGroups) {
2398
+ group.addEventListener("pointerdown", () => {
2399
+ this.#setActiveWave(this.#indexFromElement(group));
2368
2400
  });
2369
- row.addEventListener("focusin", () => {
2370
- this.#setActiveWave(this.#indexFromElement(row));
2401
+ group.addEventListener("focusin", () => {
2402
+ this.#setActiveWave(this.#indexFromElement(group));
2403
+ });
2404
+ group.addEventListener("openchange", (event) => {
2405
+ const index = this.#indexFromElement(group);
2406
+ if (event.detail?.open) {
2407
+ this.#expandedWaveIndices.add(index);
2408
+ } else {
2409
+ this.#expandedWaveIndices.delete(index);
2410
+ }
2371
2411
  });
2372
2412
  }
2373
2413
 
2374
- this.#addTypeControl?.addEventListener("change", (event) => {
2375
- if (this.#isDisabled()) return;
2376
- if (event.target !== this.#addTypeControl) return;
2377
- const type = this.#normalizeType(event.detail ?? this.#addTypeControl?.value);
2378
- this.#waves.push(FigInputOscillator.#defaultWave(type));
2379
- this.#activeWaveIndex = this.#waves.length - 1;
2380
- this.#render();
2381
- this.#emit("input");
2382
- this.#emit("change");
2383
- });
2414
+ for (const control of this.querySelectorAll(".fig-input-oscillator-add-type")) {
2415
+ const stopHeaderToggle = (event) => {
2416
+ event.stopPropagation();
2417
+ };
2418
+ control.addEventListener("pointerdown", stopHeaderToggle);
2419
+ control.addEventListener("click", stopHeaderToggle);
2420
+ control.addEventListener("change", (event) => {
2421
+ if (event.target !== control) return;
2422
+ if (this.#isDisabled()) return;
2423
+ const insertAfter = this.#indexFromElement(control);
2424
+ const insertIndex = insertAfter + 1;
2425
+ const type = this.#normalizeType(event.detail ?? control.value);
2426
+ this.#waves.splice(insertIndex, 0, FigInputOscillator.#defaultWave(type));
2427
+ this.#reindexExpandedWavesAfterInsert(insertIndex);
2428
+ this.#activeWaveIndex = insertIndex;
2429
+ this.#render();
2430
+ this.#emit("input");
2431
+ this.#emit("change");
2432
+ });
2433
+ }
2434
+
2435
+ for (const button of this.querySelectorAll(".fig-input-oscillator-add-type-button")) {
2436
+ const stopHeaderToggle = (event) => {
2437
+ event.stopPropagation();
2438
+ };
2439
+ button.addEventListener("pointerdown", stopHeaderToggle);
2440
+ button.addEventListener("click", stopHeaderToggle);
2441
+ button.closest("fig-tooltip")?.addEventListener("pointerdown", stopHeaderToggle);
2442
+ button.closest("fig-tooltip")?.addEventListener("click", stopHeaderToggle);
2443
+ }
2384
2444
 
2385
2445
  for (const button of this.querySelectorAll(".fig-input-oscillator-remove-button")) {
2386
- button.addEventListener("click", () => {
2446
+ button.addEventListener("pointerdown", (event) => {
2447
+ event.stopPropagation();
2448
+ });
2449
+ button.addEventListener("click", (event) => {
2450
+ event.stopPropagation();
2387
2451
  if (this.#isDisabled() || this.#waves.length <= 1) return;
2388
2452
  const index = this.#indexFromElement(button);
2389
2453
  this.#waves.splice(index, 1);
2454
+ this.#reindexExpandedWaves(index);
2390
2455
  this.#activeWaveIndex = Math.min(this.#activeWaveIndex, this.#waves.length - 1);
2391
2456
  this.#render();
2392
2457
  this.#emit("input");
@@ -2472,7 +2537,7 @@ class FigInputOscillator extends HTMLElement {
2472
2537
  ...this.#waves[index],
2473
2538
  [name]: next,
2474
2539
  });
2475
- this.#syncUI();
2540
+ this.#syncUI({ skipFieldSync: eventType == "input" });
2476
2541
  this.#emit(eventType);
2477
2542
  }
2478
2543
 
@@ -2584,9 +2649,9 @@ class FigInputOscillator extends HTMLElement {
2584
2649
  );
2585
2650
  }
2586
2651
 
2587
- #syncUI() {
2652
+ #syncUI({ skipFieldSync = false } = {}) {
2588
2653
  this.#syncTypeControls();
2589
- this.#syncFields();
2654
+ if (!skipFieldSync) this.#syncFields();
2590
2655
  this.#syncActiveWave();
2591
2656
  this.#updateWaveform();
2592
2657
  }
@@ -2600,9 +2665,16 @@ class FigInputOscillator extends HTMLElement {
2600
2665
 
2601
2666
  #syncFields() {
2602
2667
  for (const field of this.#fields) {
2668
+ if (field === this.#activeFieldInput) continue;
2603
2669
  const index = this.#indexFromElement(field);
2604
2670
  const name = field.getAttribute("name");
2605
- field.setAttribute("value", this.#round(this.#waves[index]?.[name] ?? 0));
2671
+ const next = this.#round(this.#waves[index]?.[name] ?? 0);
2672
+ const slider = field.querySelector("fig-slider");
2673
+ if (slider) {
2674
+ slider.value = next;
2675
+ } else {
2676
+ field.setAttribute("value", String(next));
2677
+ }
2606
2678
  }
2607
2679
  }
2608
2680
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "6.9.9",
3
+ "version": "6.10.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",