@rogieking/figui3 2.12.0 → 2.12.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.
package/README.md CHANGED
@@ -9,6 +9,8 @@ A lightweight, zero-dependency web components library for building Figma plugin
9
9
 
10
10
  View the interactive component documentation at **[rogie.github.io/figui3](https://rogie.github.io/figui3/)**
11
11
 
12
+ The demo page (`index.html`) is included in the npm package, so you can also open it locally after installing.
13
+
12
14
  ## Features
13
15
 
14
16
  - 🎨 Figma UI3 design system
@@ -157,7 +159,7 @@ A native select wrapper with Figma styling.
157
159
 
158
160
  ### Tooltip (`<fig-tooltip>`)
159
161
 
160
- Displays contextual information on hover or click.
162
+ Displays contextual information on hover or click. The tooltip automatically repositions itself when its child element moves (e.g. during drag), using a MutationObserver to track attribute changes on the child.
161
163
 
162
164
  | Attribute | Type | Default | Description |
163
165
  |-----------|------|---------|-------------|
@@ -165,6 +167,7 @@ Displays contextual information on hover or click.
165
167
  | `action` | string | `"hover"` | Trigger: `"hover"` or `"click"` |
166
168
  | `delay` | number | `500` | Delay in ms before showing |
167
169
  | `offset` | string | — | Position offset: `"left,top,right,bottom"` |
170
+ | `open` | boolean | `false` | Programmatically show/hide the tooltip |
168
171
 
169
172
  ```html
170
173
  <fig-tooltip text="This is helpful info" action="hover">
@@ -174,6 +177,11 @@ Displays contextual information on hover or click.
174
177
  <fig-tooltip text="Click triggered" action="click" delay="0">
175
178
  <fig-button>Click me</fig-button>
176
179
  </fig-tooltip>
180
+
181
+ <!-- Instant tooltip (no delay) -->
182
+ <fig-tooltip text="Instant!" delay="0">
183
+ <fig-button>No delay</fig-button>
184
+ </fig-tooltip>
177
185
  ```
178
186
 
179
187
  ---
@@ -283,6 +291,7 @@ A range slider with multiple types and optional text input.
283
291
  | `min` | number | `0` | Minimum value |
284
292
  | `max` | number | `100` | Maximum value |
285
293
  | `step` | number | `1` | Step increment |
294
+ | `default` | number | — | Default/reset value (shown as a marker on the track) |
286
295
  | `text` | boolean | `false` | Show text input |
287
296
  | `units` | string | — | Unit label (e.g., `"%"`, `"px"`) |
288
297
  | `transform` | number | — | Multiplier for display value |
@@ -369,12 +378,16 @@ A numeric input with units support.
369
378
  | `units` | string | — | Unit string (e.g., `"px"`, `"%"`) |
370
379
  | `unit-position` | string | `"suffix"` | `"suffix"` or `"prefix"` |
371
380
  | `transform` | number | — | Display multiplier |
381
+ | `steppers` | boolean | `false` | Show native spin buttons (up/down arrows) |
372
382
  | `disabled` | boolean | `false` | Disable input |
373
383
 
374
384
  ```html
375
385
  <fig-input-number value="100" units="px"></fig-input-number>
376
386
  <fig-input-number value="50" units="%" min="0" max="100"></fig-input-number>
377
387
  <fig-input-number value="45" units="°" step="15"></fig-input-number>
388
+
389
+ <!-- With native stepper arrows -->
390
+ <fig-input-number value="10" steppers="true" step="1"></fig-input-number>
378
391
  ```
379
392
 
380
393
  ---
@@ -389,6 +402,7 @@ A color picker with hex/alpha support.
389
402
  | `text` | boolean | `false` | Show hex text input |
390
403
  | `alpha` | boolean | `false` | Show alpha slider |
391
404
  | `picker` | string | `"native"` | Picker type: `"native"`, `"figma"`, `"false"` |
405
+ | `mode` | string | — | Color mode (e.g., `"hex"`, `"rgb"`, `"hsl"`) |
392
406
  | `disabled` | boolean | `false` | Disable input |
393
407
 
394
408
  ```html
@@ -483,11 +497,13 @@ A checkbox input with indeterminate state support.
483
497
  | `disabled` | boolean | `false` | Disable checkbox |
484
498
  | `name` | string | — | Form field name |
485
499
  | `value` | string | — | Value when checked |
500
+ | `label` | string | — | Programmatic label text (alternative to slotted content) |
486
501
 
487
502
  ```html
488
503
  <fig-checkbox>Accept terms</fig-checkbox>
489
504
  <fig-checkbox checked>Selected option</fig-checkbox>
490
505
  <fig-checkbox indeterminate>Parent option</fig-checkbox>
506
+ <fig-checkbox label="Via attribute"></fig-checkbox>
491
507
  ```
492
508
 
493
509
  ---
@@ -531,11 +547,12 @@ A toggle switch component.
531
547
 
532
548
  ### Field (`<fig-field>`)
533
549
 
534
- A form field wrapper with flexible layout.
550
+ A form field wrapper with flexible layout. Automatically links `<label>` elements to the first `fig-*` child for accessibility.
535
551
 
536
552
  | Attribute | Type | Default | Description |
537
553
  |-----------|------|---------|-------------|
538
554
  | `direction` | string | `"column"` | Layout: `"column"`, `"row"`, `"horizontal"` |
555
+ | `label` | string | — | Programmatically set the label text |
539
556
 
540
557
  ```html
541
558
  <!-- Vertical (default) -->
@@ -618,27 +635,55 @@ A 2D position input control.
618
635
  | `precision` | number | — | Decimal places |
619
636
  | `transform` | number | — | Output scaling |
620
637
  | `text` | boolean | `false` | Show X/Y inputs |
638
+ | `coordinates` | string | `"screen"` | Coordinate system: `"screen"` (0,0 top-left) or `"math"` (0,0 bottom-left) |
621
639
 
622
640
  ```html
623
641
  <fig-input-joystick value="0.5,0.5"></fig-input-joystick>
624
642
  <fig-input-joystick value="0.5,0.5" text="true" precision="2"></fig-input-joystick>
643
+
644
+ <!-- Math coordinates (Y-axis inverted: 0,0 at bottom-left) -->
645
+ <fig-input-joystick value="0.5,0.5" coordinates="math" text="true"></fig-input-joystick>
625
646
  ```
626
647
 
627
648
  ---
628
649
 
629
650
  ### Input Angle (`<fig-input-angle>`)
630
651
 
631
- An angle/rotation input control.
652
+ An angle/rotation input control with optional min/max clamping, multi-unit support, and unbounded winding (values beyond 360°).
653
+
654
+ When `min` and `max` are omitted, the input is unbounded — dragging continuously winds the angle past full revolutions (e.g. 720°, 1080°, or negative values). The text input also accepts values with unit suffixes (`90deg`, `3.14rad`, `0.5turn`) and converts them to the component's `units` format.
632
655
 
633
656
  | Attribute | Type | Default | Description |
634
657
  |-----------|------|---------|-------------|
635
- | `value` | number | | Angle in degrees |
636
- | `precision` | number | | Decimal places |
637
- | `text` | boolean | `false` | Show text input |
658
+ | `value` | number | `0` | Angle value (in the unit specified by `units`) |
659
+ | `precision` | number | `1` | Decimal places for display |
660
+ | `text` | boolean | `false` | Show numeric text input alongside the dial |
661
+ | `min` | number | — | Minimum angle (omit for unbounded) |
662
+ | `max` | number | — | Maximum angle (omit for unbounded) |
663
+ | `units` | string | `"°"` | Display unit: `"°"` (or `"deg"`), `"rad"`, `"turn"` |
664
+ | `show-rotations` | boolean | `false` | Show a ×N rotation counter when angle exceeds 1 full rotation |
638
665
 
639
666
  ```html
667
+ <!-- Basic dial -->
640
668
  <fig-input-angle value="45"></fig-input-angle>
669
+
670
+ <!-- With text input -->
641
671
  <fig-input-angle value="90" text="true"></fig-input-angle>
672
+
673
+ <!-- Unbounded (supports winding past 360°) -->
674
+ <fig-input-angle text="true" value="720"></fig-input-angle>
675
+
676
+ <!-- Clamped to a range -->
677
+ <fig-input-angle text="true" value="90" min="0" max="180"></fig-input-angle>
678
+
679
+ <!-- Radians -->
680
+ <fig-input-angle text="true" units="rad" value="3.14159"></fig-input-angle>
681
+
682
+ <!-- Turns -->
683
+ <fig-input-angle text="true" units="turn" value="0.5"></fig-input-angle>
684
+
685
+ <!-- Show rotation count (×2 at 720°, ×3 at 1080°, etc.) -->
686
+ <fig-input-angle text="true" show-rotations="true" value="1080"></fig-input-angle>
642
687
  ```
643
688
 
644
689
  ---
@@ -680,20 +725,49 @@ A loading spinner indicator.
680
725
 
681
726
  A loading placeholder with shimmer animation.
682
727
 
728
+ | Attribute | Type | Default | Description |
729
+ |-----------|------|---------|-------------|
730
+ | `duration` | string | `"1.5s"` | Animation cycle duration (CSS time value) |
731
+ | `playing` | boolean | `true` | Whether the animation is running |
732
+
683
733
  ```html
684
734
  <fig-shimmer style="width: 200px; height: 20px;"></fig-shimmer>
735
+ <fig-shimmer style="width: 100px; height: 14px;" duration="2s"></fig-shimmer>
685
736
  ```
686
737
 
687
738
  ---
688
739
 
689
740
  ### Layer (`<fig-layer>`)
690
741
 
691
- A layer list item component (for layer panels).
742
+ A layer list item component (for layer panels). Supports nesting, expand/collapse via chevron, and visibility toggling.
743
+
744
+ | Attribute | Type | Default | Description |
745
+ |-----------|------|---------|-------------|
746
+ | `open` | boolean | `false` | Whether child layers are expanded |
747
+ | `visible` | boolean | `true` | Whether the layer is visible |
748
+
749
+ **Events:** `openchange` (detail: `{ open }`) and `visibilitychange` (detail: `{ visible }`)
692
750
 
693
751
  ```html
694
- <fig-layer name="Rectangle 1" type="rectangle" selected></fig-layer>
695
- <fig-layer name="Group 1" type="group" expanded>
696
- <fig-layer name="Child 1" type="frame"></fig-layer>
752
+ <fig-layer>
753
+ <div class="fig-layer-row">
754
+ <span class="fig-layer-icon"></span>
755
+ <span class="fig-layer-name">Rectangle 1</span>
756
+ </div>
757
+ </fig-layer>
758
+
759
+ <!-- Nested with children -->
760
+ <fig-layer open="true">
761
+ <div class="fig-layer-row">
762
+ <span class="fig-layer-icon"></span>
763
+ <span class="fig-layer-name">Group 1</span>
764
+ </div>
765
+ <fig-layer>
766
+ <div class="fig-layer-row">
767
+ <span class="fig-layer-icon"></span>
768
+ <span class="fig-layer-name">Child 1</span>
769
+ </div>
770
+ </fig-layer>
697
771
  </fig-layer>
698
772
  ```
699
773
 
package/components.css CHANGED
@@ -1523,7 +1523,7 @@ details {
1523
1523
  display: flex;
1524
1524
  align-items: center;
1525
1525
  padding: 0 1rem 0 0;
1526
- height: var(--spacer-6);
1526
+ height: var(--spacer-5);
1527
1527
  user-select: none;
1528
1528
  color: var(--figma-color-text-secondary);
1529
1529
 
package/fig.js CHANGED
@@ -719,12 +719,15 @@ customElements.define("fig-popover", FigPopover);
719
719
  */
720
720
  class FigDialog extends HTMLDialogElement {
721
721
  #isDragging = false;
722
+ #dragPending = false;
723
+ #dragStartPos = { x: 0, y: 0 };
722
724
  #dragOffset = { x: 0, y: 0 };
723
725
  #boundPointerDown;
724
726
  #boundPointerMove;
725
727
  #boundPointerUp;
726
728
  #offset = 16; // 1rem in pixels
727
729
  #positionInitialized = false;
730
+ #dragThreshold = 3; // pixels before drag starts
728
731
 
729
732
  constructor() {
730
733
  super();
@@ -886,7 +889,7 @@ class FigDialog extends HTMLDialogElement {
886
889
  }
887
890
 
888
891
  #handlePointerDown(e) {
889
- if (!this.drag || this.#isInteractiveElement(e.target)) {
892
+ if (!this.drag) {
890
893
  return;
891
894
  }
892
895
 
@@ -901,31 +904,48 @@ class FigDialog extends HTMLDialogElement {
901
904
  }
902
905
  // No handle specified = drag from anywhere (original behavior)
903
906
 
904
- this.#isDragging = true;
905
- this.setPointerCapture(e.pointerId);
907
+ // Don't prevent default yet - just set up pending drag
908
+ // This allows clicks on interactive elements like <details> to work
909
+ this.#dragPending = true;
910
+ this.#dragStartPos.x = e.clientX;
911
+ this.#dragStartPos.y = e.clientY;
906
912
 
907
913
  // Get current position from computed style
908
914
  const rect = this.getBoundingClientRect();
909
915
 
910
- // Convert to pixel-based top/left positioning for dragging
911
- // (clears margin: auto centering)
912
- this.style.top = `${rect.top}px`;
913
- this.style.left = `${rect.left}px`;
914
- this.style.bottom = "auto";
915
- this.style.right = "auto";
916
- this.style.margin = "0";
917
-
918
916
  // Store offset from pointer to dialog top-left corner
919
917
  this.#dragOffset.x = e.clientX - rect.left;
920
918
  this.#dragOffset.y = e.clientY - rect.top;
921
919
 
922
920
  document.addEventListener("pointermove", this.#boundPointerMove);
923
921
  document.addEventListener("pointerup", this.#boundPointerUp);
924
-
925
- e.preventDefault();
926
922
  }
927
923
 
928
924
  #handlePointerMove(e) {
925
+ // Check if we should start dragging (threshold exceeded)
926
+ if (this.#dragPending && !this.#isDragging) {
927
+ const dx = Math.abs(e.clientX - this.#dragStartPos.x);
928
+ const dy = Math.abs(e.clientY - this.#dragStartPos.y);
929
+
930
+ if (dx > this.#dragThreshold || dy > this.#dragThreshold) {
931
+ // Start actual drag
932
+ this.#isDragging = true;
933
+ this.#dragPending = false;
934
+ this.setPointerCapture(e.pointerId);
935
+
936
+ // Get current position from computed style
937
+ const rect = this.getBoundingClientRect();
938
+
939
+ // Convert to pixel-based top/left positioning for dragging
940
+ // (clears margin: auto centering)
941
+ this.style.top = `${rect.top}px`;
942
+ this.style.left = `${rect.left}px`;
943
+ this.style.bottom = "auto";
944
+ this.style.right = "auto";
945
+ this.style.margin = "0";
946
+ }
947
+ }
948
+
929
949
  if (!this.#isDragging) return;
930
950
 
931
951
  // Calculate new position based on pointer position minus offset
@@ -940,10 +960,13 @@ class FigDialog extends HTMLDialogElement {
940
960
  }
941
961
 
942
962
  #handlePointerUp(e) {
943
- if (!this.#isDragging) return;
963
+ // Clean up pending or active drag
964
+ if (this.#isDragging) {
965
+ this.releasePointerCapture(e.pointerId);
966
+ }
944
967
 
945
968
  this.#isDragging = false;
946
- this.releasePointerCapture(e.pointerId);
969
+ this.#dragPending = false;
947
970
 
948
971
  document.removeEventListener("pointermove", this.#boundPointerMove);
949
972
  document.removeEventListener("pointerup", this.#boundPointerUp);