@rogieking/figui3 1.9.2 → 1.9.4

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/AUDIT.md ADDED
@@ -0,0 +1,183 @@
1
+ # Code Audit Report for fig.js
2
+
3
+ ## Critical Bugs
4
+
5
+ ### 1. **Infinite Recursion in FigButton.type getter (Line 58-59)**
6
+ **Location:** `FigButton` class, lines 58-59
7
+ **Issue:** The getter returns `this.type`, which calls itself infinitely
8
+ ```javascript
9
+ get type() {
10
+ return this.type; // ❌ Infinite recursion!
11
+ }
12
+ ```
13
+ **Fix:** Should return `this.getAttribute("type")` or use a private field
14
+ ```javascript
15
+ get type() {
16
+ return this.getAttribute("type") || "button";
17
+ }
18
+ ```
19
+
20
+ ### 2. **Impossible Logic Condition (Multiple locations)**
21
+ **Locations:**
22
+ - Line 103 (FigButton)
23
+ - Line 1043 (FigSlider)
24
+ - Line 1304 (FigInputText)
25
+
26
+ **Issue:** The condition `newValue === undefined && newValue !== null` is logically impossible. A value cannot be both `undefined` and not `null` simultaneously.
27
+
28
+ **Current code:**
29
+ ```javascript
30
+ this.disabled = this.input.disabled =
31
+ newValue === "true" ||
32
+ (newValue === undefined && newValue !== null);
33
+ ```
34
+
35
+ **Fix:** Should be:
36
+ ```javascript
37
+ this.disabled = this.input.disabled =
38
+ newValue !== null && newValue !== "false";
39
+ ```
40
+
41
+ ## Documentation Issues
42
+
43
+ ### 3. **Missing Documentation for Utility Functions**
44
+ **Location:** Lines 1-6
45
+ **Issue:** `figUniqueId()` and `figSupportsPopover()` lack JSDoc comments
46
+
47
+ **Fix:** Add documentation:
48
+ ```javascript
49
+ /**
50
+ * Generates a unique ID string using timestamp and random values
51
+ * @returns {string} A unique identifier
52
+ */
53
+ function figUniqueId() {
54
+ return Date.now().toString(36) + Math.random().toString(36).substring(2);
55
+ }
56
+
57
+ /**
58
+ * Checks if the browser supports the native popover API
59
+ * @returns {boolean} True if popover is supported
60
+ */
61
+ function figSupportsPopover() {
62
+ return HTMLElement.prototype.hasOwnProperty("popover");
63
+ }
64
+ ```
65
+
66
+ ### 4. **Incomplete Documentation for FigButton**
67
+ **Location:** Line 10
68
+ **Issue:** Documentation mentions "button", "toggle", "submit" but code also supports "link" type (line 81)
69
+
70
+ **Fix:** Update documentation:
71
+ ```javascript
72
+ * @attr {string} type - The button type: "button" (default), "toggle", "submit", or "link"
73
+ ```
74
+
75
+ ### 5. **Missing Documentation for FigButton Attributes**
76
+ **Location:** FigButton class
77
+ **Issue:** Missing documentation for `href` and `target` attributes used in link type
78
+
79
+ **Fix:** Add to JSDoc:
80
+ ```javascript
81
+ * @attr {string} href - URL for link type buttons
82
+ * @attr {string} target - Target window for link type buttons
83
+ ```
84
+
85
+ ### 6. **Missing Documentation for FigInputNumber**
86
+ **Location:** Line 1358
87
+ **Issue:** Missing `composed` property in CustomEvent documentation (though it's used in code)
88
+
89
+ ## Potential Issues
90
+
91
+ ### 7. **Unused Function**
92
+ **Location:** Line 4-6
93
+ **Issue:** `figSupportsPopover()` is defined but never used in the codebase
94
+
95
+ **Recommendation:** Either use it or remove it
96
+
97
+ ### 8. **Inconsistent Disabled Attribute Handling**
98
+ **Location:** Multiple components
99
+ **Issue:** Different components handle disabled attributes slightly differently:
100
+ - Some check `newValue === "true" || (newValue === undefined && newValue !== null)`
101
+ - Others check `newValue !== null && newValue !== "false"`
102
+
103
+ **Recommendation:** Standardize on one approach (preferably the second, which is correct)
104
+
105
+ ### 9. **Missing Error Handling**
106
+ **Location:** Multiple locations
107
+ **Issue:** No error handling for:
108
+ - `querySelector` calls that might return null
109
+ - `getAttribute` calls that might return unexpected values
110
+ - Event listener setup failures
111
+
112
+ **Example:** Line 54 - `this.button` might be null if querySelector fails
113
+
114
+ ### 10. **Potential Memory Leak**
115
+ **Location:** FigTooltip, line 310
116
+ **Issue:** Event listener added in `destroy()` method:
117
+ ```javascript
118
+ destroy() {
119
+ if (this.popup) {
120
+ this.popup.remove();
121
+ }
122
+ document.body.addEventListener("click", this.hidePopupOutsideClick); // ❌ Added but never removed
123
+ }
124
+ ```
125
+
126
+ ### 11. **Inconsistent Value Type Handling**
127
+ **Location:** FigInputNumber, line 1393-1394
128
+ **Issue:** Value is converted to Number but can be empty string:
129
+ ```javascript
130
+ this.value = valueAttr !== null && valueAttr !== "" ? Number(valueAttr) : "";
131
+ ```
132
+ This creates inconsistent types (Number vs String)
133
+
134
+ ### 12. **Missing Input Validation**
135
+ **Location:** FigInputNumber, line 1551
136
+ **Issue:** No validation that `numericValue` is a valid number before division:
137
+ ```javascript
138
+ this.value = Number(numericValue) / (this.transform || 1);
139
+ ```
140
+ If `numericValue` is empty or invalid, this could result in NaN
141
+
142
+ ## Code Quality Issues
143
+
144
+ ### 13. **Magic Numbers**
145
+ **Location:** Multiple locations
146
+ **Issue:** Hard-coded values without explanation:
147
+ - Line 246: `delay = 500` (default delay)
148
+ - Line 1617: `precision = 2` (default precision)
149
+ - Line 1635: `Math.round(num * 100) / 100` (rounding logic)
150
+
151
+ **Recommendation:** Extract to named constants or document them
152
+
153
+ ### 14. **Inconsistent Naming**
154
+ **Location:** FigSlider
155
+ **Issue:** Method named `#handleTextInput` but now works with `figInputNumber`
156
+
157
+ **Recommendation:** Rename to `#handleNumberInput` for clarity
158
+
159
+ ### 15. **Missing Type Checks**
160
+ **Location:** Multiple locations
161
+ **Issue:** No type validation for attributes that should be numbers:
162
+ - `min`, `max`, `step`, `transform` attributes
163
+
164
+ **Recommendation:** Add validation or use `Number()` with NaN checks
165
+
166
+ ## Summary
167
+
168
+ **Critical Bugs:** 2
169
+ **Documentation Issues:** 4
170
+ **Potential Issues:** 6
171
+ **Code Quality Issues:** 3
172
+
173
+ **Total Issues Found:** 15
174
+
175
+ ## Priority Fixes
176
+
177
+ 1. **HIGH:** Fix infinite recursion in FigButton.type getter
178
+ 2. **HIGH:** Fix impossible logic condition in disabled attribute handling
179
+ 3. **MEDIUM:** Add missing documentation
180
+ 4. **MEDIUM:** Fix memory leak in FigTooltip.destroy()
181
+ 5. **LOW:** Standardize disabled attribute handling
182
+ 6. **LOW:** Add input validation
183
+
package/components.css CHANGED
@@ -513,7 +513,8 @@ input[type="password"],
513
513
  }
514
514
  }
515
515
 
516
- fig-input-text {
516
+ fig-input-text,
517
+ fig-input-number {
517
518
  &:has([slot="append"]) input[type="number"] {
518
519
  &::-webkit-outer-spin-button,
519
520
  &::-webkit-inner-spin-button {
@@ -572,109 +573,170 @@ input[type="text"][list] {
572
573
  }
573
574
  }
574
575
 
575
- /* Not enough support for this yet
576
576
  @supports (appearance: base-select) {
577
- select {
578
- appearance: base-select;
577
+ fig-dropdown[variant="neue"] {
578
+ select {
579
+ appearance: base-select;
580
+ --option-height: 1.5rem;
579
581
 
580
- option::checkmark {
581
- content: var(--icon-checkmark);
582
- display: block;
583
- width: 1rem;
584
- height: 1rem;
585
- }
586
-
587
- option {
588
- display: flex;
589
- gap: var(--spacer-1);
590
- padding: 0 var(--spacer-4) 0 calc(var(--spacer-1) * 2 + var(--spacer-1));
591
- font-weight: var(--body-medium-fontWeight);
592
- color: var(--figma-color-text-menu);
593
- position: relative;
594
- &[hidden] {
595
- display: none;
596
- }
597
- &:not(:checked):before {
598
- content: "";
582
+ option::checkmark {
583
+ content: var(--icon-checkmark);
599
584
  display: block;
600
- position: absolute;
601
- inset: 0 var(--spacer-2);
602
- border-radius: var(--radius-medium);
603
- z-index: -1;
604
- background-color: transparent;
585
+ width: 1rem;
586
+ height: 1rem;
605
587
  }
606
- &:not(:disabled) {
607
- &:hover,
608
- &:active,
609
- &:focus {
588
+
589
+ option {
590
+ display: flex;
591
+ gap: var(--spacer-1);
592
+ padding: 0 var(--spacer-4) 0 calc(var(--spacer-1) * 2 + var(--spacer-1));
593
+ font-weight: var(--body-medium-fontWeight);
594
+ color: var(--figma-color-text-menu);
595
+ position: relative;
596
+ &[hidden] {
597
+ display: none;
598
+ }
599
+ &:not(:checked):before {
600
+ content: "";
601
+ display: block;
602
+ position: absolute;
603
+ inset: 0 var(--spacer-2);
604
+ border-radius: var(--radius-medium);
605
+ z-index: -1;
610
606
  background-color: transparent;
611
- outline: 0;
607
+ }
608
+ &:not(:disabled) {
609
+ &:hover,
610
+ &:active,
611
+ &:focus {
612
+ background-color: transparent;
613
+ outline: 0;
614
+ &:before {
615
+ background-color: var(--figma-color-bg-menu-hover);
616
+ }
617
+ }
618
+ }
619
+ }
620
+
621
+ optgroup {
622
+ color: var(--figma-color-text-menu-secondary);
623
+ text-align: left;
624
+ position: relative;
625
+ padding: 0 var(--spacer-1) 0 calc(var(--spacer-1) * 2 + var(--spacer-1));
626
+ font-weight: var(--body-medium-fontWeight);
627
+ &::-internal-optgroup-label {
628
+ display: none;
629
+ }
630
+ legend {
631
+ padding: var(--spacer-1, 0.3rem) var(--spacer-1, 1rem);
632
+ }
633
+ option {
634
+ margin: 0 calc(var(--spacer-1) * -1);
635
+ margin-left: calc((var(--spacer-1) * 2 + var(--spacer-1)) * -1);
636
+ }
637
+ &:not(:first-child) {
638
+ margin-top: var(--spacer-2);
639
+ padding-top: var(--spacer-2);
640
+
612
641
  &:before {
613
- background-color: var(--figma-color-bg-menu-hover);
642
+ content: "";
643
+ display: block;
644
+ position: absolute;
645
+ left: 0;
646
+ right: 0;
647
+ top: 1px;
648
+ height: 1px;
649
+ background-color: var(--figma-color-border-menu);
650
+ margin-bottom: var(--spacer-2);
614
651
  }
615
652
  }
616
653
  }
654
+ option[hidden="true"]:first-child + optgroup {
655
+ margin-top: 0;
656
+ padding-top: 0;
657
+ &:before {
658
+ display: none;
659
+ }
660
+ }
661
+ }
662
+ ::picker-icon {
663
+ display: none;
617
664
  }
618
665
 
619
- optgroup {
620
- color: var(--figma-color-text-menu-secondary);
621
- text-align: left;
622
- position: relative;
623
- padding: 0 var(--spacer-1) 0 calc(var(--spacer-1) * 2 + var(--spacer-1));
624
- font-weight: var(--body-medium-fontWeight);
625
- &::-internal-optgroup-label {
626
- display: none;
666
+ ::picker(select) {
667
+ position-area: auto;
668
+ align-self: auto;
669
+ position-try-fallbacks: none;
670
+ max-block-size: 100vh;
671
+ appearance: base-select;
672
+ scrollbar-width: thin;
673
+ outline: 0;
674
+ scrollbar-color: var(--figma-color-text-menu-tertiary)
675
+ var(--figma-color-bg-menu);
676
+ border-radius: var(--radius-large);
677
+ border: 0;
678
+ background-color: var(--figma-color-bg-menu);
679
+ padding: var(--spacer-2) 0;
680
+ box-shadow: var(--figma-elevation-400-menu-panel);
681
+ }
682
+ select {
683
+ &:has(:nth-child(1):checked) {
684
+ &::picker(select) {
685
+ top: calc(var(--option-height) * -1 - var(--spacer-2));
686
+ }
627
687
  }
628
- legend {
629
- padding: var(--spacer-1, 0.3rem) var(--spacer-1, 1rem);
688
+
689
+ &:has(:nth-child(2):checked) {
690
+ &::picker(select) {
691
+ top: calc(var(--option-height) * -2 - var(--spacer-2));
692
+ }
630
693
  }
631
- option {
632
- margin: 0 calc(var(--spacer-1) * -1);
633
- margin-left: calc((var(--spacer-1) * 2 + var(--spacer-1)) * -1);
694
+
695
+ &:has(:nth-child(3):checked) {
696
+ &::picker(select) {
697
+ top: calc(var(--option-height) * -3 - var(--spacer-2));
698
+ }
634
699
  }
635
- &:not(:first-child) {
636
- margin-top: var(--spacer-2);
637
- padding-top: var(--spacer-2);
638
700
 
639
- &:before {
640
- content: "";
641
- display: block;
642
- position: absolute;
643
- left: 0;
644
- right: 0;
645
- top: 1px;
646
- height: 1px;
647
- background-color: var(--figma-color-border-menu);
648
- margin-bottom: var(--spacer-2);
701
+ &:has(:nth-child(4):checked) {
702
+ &::picker(select) {
703
+ top: calc(var(--option-height) * -4 - var(--spacer-2));
649
704
  }
650
705
  }
651
- }
652
- option[hidden="true"]:first-child + optgroup {
653
- margin-top: 0;
654
- padding-top: 0;
655
- &:before {
656
- display: none;
706
+ &:has(:nth-child(5):checked) {
707
+ &::picker(select) {
708
+ top: calc(var(--option-height) * -5 - var(--spacer-2));
709
+ }
710
+ }
711
+ &:has(:nth-child(6):checked) {
712
+ &::picker(select) {
713
+ top: calc(var(--option-height) * -6 - var(--spacer-2));
714
+ }
715
+ }
716
+ &:has(:nth-child(7):checked) {
717
+ &::picker(select) {
718
+ top: calc(var(--option-height) * -7 - var(--spacer-2));
719
+ }
720
+ }
721
+ &:has(:nth-child(8):checked) {
722
+ &::picker(select) {
723
+ top: calc(var(--option-height) * -8 - var(--spacer-2));
724
+ }
725
+ }
726
+ &:has(:nth-child(9):checked) {
727
+ &::picker(select) {
728
+ top: calc(var(--option-height) * -9 - var(--spacer-2));
729
+ }
730
+ }
731
+ &:has(:nth-child(10):checked) {
732
+ &::picker(select) {
733
+ top: calc(var(--option-height) * -10 - var(--spacer-2));
734
+ }
657
735
  }
658
736
  }
659
737
  }
660
- ::picker-icon {
661
- display: none;
662
- }
663
-
664
- ::picker(select) {
665
- appearance: base-select;
666
- scrollbar-width: thin;
667
- outline: 0;
668
- scrollbar-color: var(--figma-color-text-menu-tertiary)
669
- var(--figma-color-bg-menu);
670
- border-radius: var(--radius-large);
671
- border: 0;
672
- background-color: var(--figma-color-bg-menu);
673
- padding: var(--spacer-2) 0;
674
- box-shadow: var(--figma-elevation-400-menu-panel);
675
- }
676
738
  }
677
- */
739
+
678
740
  input[type="text"][list]:hover,
679
741
  input[type="text"][list]:active,
680
742
  input[type="text"][list]:focus,
@@ -1913,7 +1975,8 @@ fig-slider {
1913
1975
  box-shadow: none;
1914
1976
  background-color: var(--figma-color-bg-tertiary);
1915
1977
  }
1916
- fig-input-text {
1978
+ fig-input-text,
1979
+ fig-input-number {
1917
1980
  border-top-left-radius: 0;
1918
1981
  border-bottom-left-radius: 0;
1919
1982
  border-left: 1px solid var(--figma-color-bg);
@@ -1921,7 +1984,8 @@ fig-slider {
1921
1984
 
1922
1985
  &:hover,
1923
1986
  &:focus-within {
1924
- fig-input-text {
1987
+ fig-input-text,
1988
+ fig-input-number {
1925
1989
  height: auto;
1926
1990
  }
1927
1991
  }
@@ -2235,7 +2299,8 @@ hstack,
2235
2299
  flex-wrap: nowrap;
2236
2300
  }
2237
2301
 
2238
- fig-input-text {
2302
+ fig-input-text,
2303
+ fig-input-number {
2239
2304
  background-color: var(--figma-color-bg-secondary);
2240
2305
  border: 0;
2241
2306
  border-radius: var(--radius-medium);
@@ -2322,8 +2387,9 @@ fig-input-text {
2322
2387
 
2323
2388
  fig-input-color {
2324
2389
  & fig-input-text:not([type]),
2325
- & fig-input-text[type="text"] {
2326
- min-width: 6em;
2390
+ & fig-input-text[type="text"],
2391
+ & fig-input-number {
2392
+ flex-basis: 6em;
2327
2393
  display: inline-flex;
2328
2394
  flex-direction: column;
2329
2395
  align-items: stretch;
@@ -2334,7 +2400,8 @@ fig-input-color {
2334
2400
  }
2335
2401
  }
2336
2402
 
2337
- & fig-input-text[type="number"] {
2403
+ & fig-input-text[type="number"],
2404
+ & fig-input-number {
2338
2405
  width: 3.5rem;
2339
2406
  display: inline-flex;
2340
2407
  }
@@ -2347,7 +2414,8 @@ fig-slider {
2347
2414
  flex-grow: 1;
2348
2415
  }
2349
2416
 
2350
- & fig-input-text[type="number"] {
2417
+ & fig-input-text[type="number"],
2418
+ & fig-input-number {
2351
2419
  width: 3.5rem;
2352
2420
  }
2353
2421
  }
package/example.html CHANGED
@@ -27,6 +27,28 @@
27
27
  <fig-spinner></fig-spinner>
28
28
  </fig-header>
29
29
 
30
+ <br /><br /><br /><br /><br /><br /><br /><br />
31
+
32
+
33
+ <fig-field direction="horizontal">
34
+ <label>Position</label>
35
+ <fig-dropdown value="outside"
36
+ variant="neue">
37
+ <option value="inside">Inside</option>
38
+ <option value="center">Center</option>
39
+ <option value="outside">Outside</option>
40
+ </fig-dropdown>
41
+ </fig-field>
42
+
43
+ <fig-field direction="horizontal">
44
+ <label>Position</label>
45
+ <fig-dropdown value="outside">
46
+ <option value="inside">Inside</option>
47
+ <option value="center">Center</option>
48
+ <option value="outside">Outside</option>
49
+ </fig-dropdown>
50
+ </fig-field>
51
+
30
52
 
31
53
 
32
54
 
@@ -665,6 +687,111 @@
665
687
  <span slot="prepend">X</span>
666
688
  </fig-input-text>
667
689
  </fig-field>
690
+ <fig-header>
691
+ <h2>Number input</h2>
692
+ </fig-header>
693
+ <fig-field>
694
+ <label>Basic number input</label>
695
+ <fig-input-number value="100"></fig-input-number>
696
+ </fig-field>
697
+ <fig-field>
698
+ <label>With units (percentage)</label>
699
+ <fig-input-number value="100"
700
+ units="%"></fig-input-number>
701
+ </fig-field>
702
+ <fig-field>
703
+ <label>With units (degrees)</label>
704
+ <fig-input-number value="45"
705
+ units="°"
706
+ min="0"
707
+ max="360"
708
+ step="1"></fig-input-number>
709
+ </fig-field>
710
+ <fig-field>
711
+ <label>With prefix unit (currency)</label>
712
+ <fig-input-number value="50"
713
+ units="$"
714
+ unit-position="prefix"
715
+ min="0"
716
+ step="0.01"></fig-input-number>
717
+ </fig-field>
718
+ <fig-field>
719
+ <label>With transform (like slider)</label>
720
+ <fig-input-number value="0.5"
721
+ units="%"
722
+ transform="100"
723
+ min="0"
724
+ max="1"
725
+ step="0.01"></fig-input-number>
726
+ </fig-field>
727
+ <fig-field>
728
+ <label>With min/max constraints</label>
729
+ <fig-input-number value="25"
730
+ units="px"
731
+ min="0"
732
+ max="1000"
733
+ step="1"></fig-input-number>
734
+ </fig-field>
735
+ <fig-field>
736
+ <label>With placeholder</label>
737
+ <fig-input-number placeholder="Enter value"
738
+ units="%"
739
+ min="0"
740
+ max="100"></fig-input-number>
741
+ </fig-field>
742
+ <fig-field>
743
+ <label>Disabled</label>
744
+ <fig-input-number value="75"
745
+ units="%"
746
+ disabled></fig-input-number>
747
+ </fig-field>
748
+ <fig-field>
749
+ <label>With name attribute</label>
750
+ <fig-input-number value="42"
751
+ units="px"
752
+ name="width"></fig-input-number>
753
+ </fig-field>
754
+ <fig-field>
755
+ <label>Small step (decimals)</label>
756
+ <fig-input-number value="1.5"
757
+ units="rem"
758
+ min="0"
759
+ max="10"
760
+ step="0.1"></fig-input-number>
761
+ </fig-field>
762
+ <fig-field>
763
+ <label>Large range with transform</label>
764
+ <fig-input-number value="0.75"
765
+ units="%"
766
+ transform="100"
767
+ min="0"
768
+ max="1"
769
+ step="0.01"></fig-input-number>
770
+ </fig-field>
771
+ <fig-field>
772
+ <label>Negative values allowed</label>
773
+ <fig-input-number value="-10"
774
+ units="px"
775
+ min="-100"
776
+ max="100"
777
+ step="1"></fig-input-number>
778
+ </fig-field>
779
+ <fig-field>
780
+ <label>With append slot</label>
781
+ <fig-input-number value="90"
782
+ units="°"
783
+ min="0"
784
+ max="360">
785
+ <span slot="append">deg</span>
786
+ </fig-input-number>
787
+ </fig-field>
788
+ <fig-field>
789
+ <label>With prepend slot</label>
790
+ <fig-input-number value="50"
791
+ units="%">
792
+ <span slot="prepend">O</span>
793
+ </fig-input-number>
794
+ </fig-field>
668
795
  <fig-header>
669
796
  <h2>Color input</h2>
670
797
  </fig-header>
@@ -981,6 +1108,13 @@
981
1108
  </fig-field>
982
1109
  </fig-content>
983
1110
 
1111
+ <script>
1112
+ document.addEventListener("input", (e) => {
1113
+ console.log("input", e.target, e.target.value);
1114
+ });
1115
+
1116
+ </script>
1117
+
984
1118
  </body>
985
1119
 
986
1120
  </html>
package/fig.js CHANGED
@@ -1,15 +1,25 @@
1
+ /**
2
+ * Generates a unique ID string using timestamp and random values
3
+ * @returns {string} A unique identifier
4
+ */
1
5
  function figUniqueId() {
2
6
  return Date.now().toString(36) + Math.random().toString(36).substring(2);
3
7
  }
8
+ /**
9
+ * Checks if the browser supports the native popover API
10
+ * @returns {boolean} True if popover is supported
11
+ */
4
12
  function figSupportsPopover() {
5
13
  return HTMLElement.prototype.hasOwnProperty("popover");
6
14
  }
7
15
 
8
16
  /**
9
17
  * A custom button element that supports different types and states.
10
- * @attr {string} type - The button type: "button" (default), "toggle", or "submit"
18
+ * @attr {string} type - The button type: "button" (default), "toggle", "submit", or "link"
11
19
  * @attr {boolean} selected - Whether the button is in a selected state
12
20
  * @attr {boolean} disabled - Whether the button is disabled
21
+ * @attr {string} href - URL for link type buttons
22
+ * @attr {string} target - Target window for link type buttons (e.g., "_blank")
13
23
  */
14
24
  class FigButton extends HTMLElement {
15
25
  type;
@@ -56,7 +66,7 @@ class FigButton extends HTMLElement {
56
66
  }
57
67
 
58
68
  get type() {
59
- return this.type;
69
+ return this.getAttribute("type") || "button";
60
70
  }
61
71
  set type(value) {
62
72
  this.setAttribute("type", value);
@@ -99,8 +109,7 @@ class FigButton extends HTMLElement {
99
109
  switch (name) {
100
110
  case "disabled":
101
111
  this.disabled = this.button.disabled =
102
- newValue === "true" ||
103
- (newValue === undefined && newValue !== null);
112
+ newValue !== null && newValue !== "false";
104
113
  break;
105
114
  case "type":
106
115
  this.type = newValue;
@@ -170,11 +179,25 @@ class FigDropdown extends HTMLElement {
170
179
  #handleSelectInput(e) {
171
180
  this.value = e.target.value;
172
181
  this.setAttribute("value", this.value);
182
+ this.dispatchEvent(
183
+ new CustomEvent("input", {
184
+ detail: this.value,
185
+ bubbles: true,
186
+ composed: true,
187
+ })
188
+ );
173
189
  }
174
- #handleSelectChange() {
190
+ #handleSelectChange(e) {
175
191
  if (this.type === "dropdown") {
176
192
  this.select.selectedIndex = -1;
177
193
  }
194
+ this.dispatchEvent(
195
+ new CustomEvent("change", {
196
+ detail: this.value,
197
+ bubbles: true,
198
+ composed: true,
199
+ })
200
+ );
178
201
  }
179
202
  focus() {
180
203
  this.select.focus();
@@ -223,6 +246,7 @@ customElements.define("fig-dropdown", FigDropdown);
223
246
  class FigTooltip extends HTMLElement {
224
247
  #boundHideOnChromeOpen;
225
248
  #boundHideOnDragStart;
249
+ #boundHidePopupOutsideClick;
226
250
  #touchTimeout;
227
251
  #isTouching = false;
228
252
  constructor() {
@@ -234,6 +258,7 @@ class FigTooltip extends HTMLElement {
234
258
  // Bind methods that will be used as event listeners
235
259
  this.#boundHideOnChromeOpen = this.#hideOnChromeOpen.bind(this);
236
260
  this.#boundHideOnDragStart = this.hidePopup.bind(this);
261
+ this.#boundHidePopupOutsideClick = this.hidePopupOutsideClick.bind(this);
237
262
  }
238
263
  connectedCallback() {
239
264
  this.setup();
@@ -251,6 +276,14 @@ class FigTooltip extends HTMLElement {
251
276
  // Remove mousedown listener
252
277
  this.removeEventListener("mousedown", this.#boundHideOnDragStart);
253
278
 
279
+ // Remove click outside listener for click action
280
+ if (this.action === "click") {
281
+ document.body.removeEventListener(
282
+ "click",
283
+ this.#boundHidePopupOutsideClick
284
+ );
285
+ }
286
+
254
287
  // Clean up touch-related timers and listeners
255
288
  clearTimeout(this.#touchTimeout);
256
289
  if (this.action === "hover") {
@@ -293,7 +326,13 @@ class FigTooltip extends HTMLElement {
293
326
  if (this.popup) {
294
327
  this.popup.remove();
295
328
  }
296
- document.body.addEventListener("click", this.hidePopupOutsideClick);
329
+ // Remove the click outside listener if it was added
330
+ if (this.action === "click") {
331
+ document.body.removeEventListener(
332
+ "click",
333
+ this.#boundHidePopupOutsideClick
334
+ );
335
+ }
297
336
  }
298
337
  isTouchDevice() {
299
338
  return (
@@ -330,10 +369,7 @@ class FigTooltip extends HTMLElement {
330
369
  });
331
370
  } else if (this.action === "click") {
332
371
  this.addEventListener("click", this.showDelayedPopup.bind(this));
333
- document.body.addEventListener(
334
- "click",
335
- this.hidePopupOutsideClick.bind(this)
336
- );
372
+ document.body.addEventListener("click", this.#boundHidePopupOutsideClick);
337
373
 
338
374
  // Touch support for better mobile responsiveness
339
375
  this.addEventListener("touchstart", this.showDelayedPopup.bind(this), {
@@ -651,10 +687,12 @@ class FigTab extends HTMLElement {
651
687
  requestAnimationFrame(() => {
652
688
  if (typeof this.getAttribute("content") === "string") {
653
689
  this.content = document.querySelector(this.getAttribute("content"));
654
- if (this.#selected) {
655
- this.content.style.display = "block";
656
- } else {
657
- this.content.style.display = "none";
690
+ if (this.content) {
691
+ if (this.#selected) {
692
+ this.content.style.display = "block";
693
+ } else {
694
+ this.content.style.display = "none";
695
+ }
658
696
  }
659
697
  }
660
698
  });
@@ -875,20 +913,15 @@ class FigSlider extends HTMLElement {
875
913
  </div>`;
876
914
  if (this.text) {
877
915
  html = `${slider}
878
- <fig-input-text
916
+ <fig-input-number
879
917
  placeholder="##"
880
- type="number"
881
918
  min="${this.min}"
882
919
  max="${this.max}"
883
920
  transform="${this.transform}"
884
921
  step="${this.step}"
885
- value="${this.value}">
886
- ${
887
- this.units
888
- ? `<span slot="append">${this.units}</span>`
889
- : ""
890
- }
891
- </fig-input-text>`;
922
+ value="${this.value}"
923
+ ${this.units ? `units="${this.units}"` : ""}>
924
+ </fig-input-number>`;
892
925
  } else {
893
926
  html = slider;
894
927
  }
@@ -909,7 +942,7 @@ class FigSlider extends HTMLElement {
909
942
  }
910
943
 
911
944
  this.datalist = this.querySelector("datalist");
912
- this.figInputText = this.querySelector("fig-input-text");
945
+ this.figInputNumber = this.querySelector("fig-input-number");
913
946
  if (this.datalist) {
914
947
  this.inputContainer.append(this.datalist);
915
948
  this.datalist.setAttribute(
@@ -945,12 +978,15 @@ class FigSlider extends HTMLElement {
945
978
  defaultOption.setAttribute("default", "true");
946
979
  }
947
980
  }
948
- if (this.figInputText) {
949
- this.figInputText.removeEventListener(
981
+ if (this.figInputNumber) {
982
+ this.figInputNumber.removeEventListener(
983
+ "input",
984
+ this.#boundHandleTextInput
985
+ );
986
+ this.figInputNumber.addEventListener(
950
987
  "input",
951
988
  this.#boundHandleTextInput
952
989
  );
953
- this.figInputText.addEventListener("input", this.#boundHandleTextInput);
954
990
  }
955
991
 
956
992
  this.#syncValue();
@@ -962,10 +998,12 @@ class FigSlider extends HTMLElement {
962
998
  }
963
999
 
964
1000
  #handleTextInput() {
965
- if (this.figInputText) {
966
- this.value = this.input.value = this.figInputText.value;
1001
+ if (this.figInputNumber) {
1002
+ this.value = this.input.value = this.figInputNumber.value;
967
1003
  this.#syncProperties();
968
- this.dispatchEvent(new CustomEvent("input", { bubbles: true }));
1004
+ this.dispatchEvent(
1005
+ new CustomEvent("input", { detail: this.value, bubbles: true })
1006
+ );
969
1007
  }
970
1008
  }
971
1009
  #calculateNormal(value) {
@@ -984,14 +1022,16 @@ class FigSlider extends HTMLElement {
984
1022
  let val = this.input.value;
985
1023
  this.value = val;
986
1024
  this.#syncProperties();
987
- if (this.figInputText) {
988
- this.figInputText.setAttribute("value", val);
1025
+ if (this.figInputNumber) {
1026
+ this.figInputNumber.setAttribute("value", val);
989
1027
  }
990
1028
  }
991
1029
 
992
1030
  #handleInput() {
993
1031
  this.#syncValue();
994
- this.dispatchEvent(new CustomEvent("input", { bubbles: true }));
1032
+ this.dispatchEvent(
1033
+ new CustomEvent("input", { detail: this.value, bubbles: true })
1034
+ );
995
1035
  }
996
1036
 
997
1037
  static get observedAttributes() {
@@ -1021,23 +1061,22 @@ class FigSlider extends HTMLElement {
1021
1061
  break;
1022
1062
  case "disabled":
1023
1063
  this.disabled = this.input.disabled =
1024
- newValue === "true" ||
1025
- (newValue === undefined && newValue !== null);
1026
- if (this.figInputText) {
1027
- this.figInputText.disabled = this.disabled;
1028
- this.figInputText.setAttribute("disabled", this.disabled);
1064
+ newValue !== null && newValue !== "false";
1065
+ if (this.figInputNumber) {
1066
+ this.figInputNumber.disabled = this.disabled;
1067
+ this.figInputNumber.setAttribute("disabled", this.disabled);
1029
1068
  }
1030
1069
  break;
1031
1070
  case "value":
1032
1071
  this.value = newValue;
1033
- if (this.figInputText) {
1034
- this.figInputText.setAttribute("value", newValue);
1072
+ if (this.figInputNumber) {
1073
+ this.figInputNumber.setAttribute("value", newValue);
1035
1074
  }
1036
1075
  break;
1037
1076
  case "transform":
1038
1077
  this.transform = Number(newValue) || 1;
1039
- if (this.figInputText) {
1040
- this.figInputText.setAttribute("transform", this.transform);
1078
+ if (this.figInputNumber) {
1079
+ this.figInputNumber.setAttribute("transform", this.transform);
1041
1080
  }
1042
1081
  break;
1043
1082
  case "min":
@@ -1178,8 +1217,12 @@ class FigInputText extends HTMLElement {
1178
1217
  }
1179
1218
  this.value = value;
1180
1219
  this.input.value = valueTransformed;
1181
- this.dispatchEvent(new CustomEvent("input", { bubbles: true }));
1182
- this.dispatchEvent(new CustomEvent("change", { bubbles: true }));
1220
+ this.dispatchEvent(
1221
+ new CustomEvent("input", { detail: this.value, bubbles: true })
1222
+ );
1223
+ this.dispatchEvent(
1224
+ new CustomEvent("change", { detail: this.value, bubbles: true })
1225
+ );
1183
1226
  }
1184
1227
  #handleMouseMove(e) {
1185
1228
  if (this.type !== "number") return;
@@ -1278,8 +1321,7 @@ class FigInputText extends HTMLElement {
1278
1321
  switch (name) {
1279
1322
  case "disabled":
1280
1323
  this.disabled = this.input.disabled =
1281
- newValue === "true" ||
1282
- (newValue === undefined && newValue !== null);
1324
+ newValue !== null && newValue !== "false";
1283
1325
  break;
1284
1326
  case "transform":
1285
1327
  if (this.type === "number") {
@@ -1319,6 +1361,362 @@ class FigInputText extends HTMLElement {
1319
1361
  }
1320
1362
  window.customElements.define("fig-input-text", FigInputText);
1321
1363
 
1364
+ /**
1365
+ * A custom numeric input element that uses type="text" with inputmode="decimal".
1366
+ * Supports units display and all standard number input attributes.
1367
+ * @attr {string} value - The current numeric value
1368
+ * @attr {string} placeholder - Placeholder text
1369
+ * @attr {boolean} disabled - Whether the input is disabled
1370
+ * @attr {number} min - Minimum value
1371
+ * @attr {number} max - Maximum value
1372
+ * @attr {number} step - Step increment
1373
+ * @attr {number} transform - A multiplier for displayed number values
1374
+ * @attr {string} units - Unit string to append/prepend to displayed value (e.g., "%", "°", "$")
1375
+ * @attr {string} unit-position - Position of unit: "suffix" (default) or "prefix"
1376
+ * @attr {string} name - Form field name
1377
+ */
1378
+ class FigInputNumber extends HTMLElement {
1379
+ #boundMouseMove;
1380
+ #boundMouseUp;
1381
+ #boundMouseDown;
1382
+ #boundInputChange;
1383
+ #boundInput;
1384
+ #boundFocus;
1385
+ #boundBlur;
1386
+ #units;
1387
+ #unitPosition;
1388
+
1389
+ constructor() {
1390
+ super();
1391
+ // Pre-bind the event handlers once
1392
+ this.#boundMouseMove = this.#handleMouseMove.bind(this);
1393
+ this.#boundMouseUp = this.#handleMouseUp.bind(this);
1394
+ this.#boundMouseDown = this.#handleMouseDown.bind(this);
1395
+ this.#boundInputChange = (e) => {
1396
+ e.stopPropagation();
1397
+ this.#handleInputChange(e);
1398
+ };
1399
+ this.#boundInput = (e) => {
1400
+ e.stopPropagation();
1401
+ this.#handleInput(e);
1402
+ };
1403
+ this.#boundFocus = (e) => {
1404
+ this.#handleFocus(e);
1405
+ };
1406
+ this.#boundBlur = (e) => {
1407
+ this.#handleBlur(e);
1408
+ };
1409
+ }
1410
+
1411
+ connectedCallback() {
1412
+ const valueAttr = this.getAttribute("value");
1413
+ this.value =
1414
+ valueAttr !== null && valueAttr !== "" ? Number(valueAttr) : "";
1415
+ this.placeholder = this.getAttribute("placeholder") || "";
1416
+ this.name = this.getAttribute("name") || null;
1417
+ this.#units = this.getAttribute("units") || "";
1418
+ this.#unitPosition = this.getAttribute("unit-position") || "suffix";
1419
+
1420
+ if (this.getAttribute("step")) {
1421
+ this.step = Number(this.getAttribute("step"));
1422
+ }
1423
+ if (this.getAttribute("min")) {
1424
+ this.min = Number(this.getAttribute("min"));
1425
+ }
1426
+ if (this.getAttribute("max")) {
1427
+ this.max = Number(this.getAttribute("max"));
1428
+ }
1429
+ this.transform = Number(this.getAttribute("transform") || 1);
1430
+
1431
+ let html = `<input
1432
+ type="text"
1433
+ inputmode="decimal"
1434
+ ${this.name ? `name="${this.name}"` : ""}
1435
+ placeholder="${this.placeholder}"
1436
+ value="${this.#formatWithUnit(this.value)}" />`;
1437
+
1438
+ //child nodes hack
1439
+ requestAnimationFrame(() => {
1440
+ let append = this.querySelector("[slot=append]");
1441
+ let prepend = this.querySelector("[slot=prepend]");
1442
+
1443
+ this.innerHTML = html;
1444
+
1445
+ if (prepend) {
1446
+ prepend.addEventListener("click", this.focus.bind(this));
1447
+ this.prepend(prepend);
1448
+ }
1449
+ if (append) {
1450
+ append.addEventListener("click", this.focus.bind(this));
1451
+ this.append(append);
1452
+ }
1453
+
1454
+ this.input = this.querySelector("input");
1455
+
1456
+ if (this.getAttribute("min")) {
1457
+ this.min = Number(this.getAttribute("min"));
1458
+ }
1459
+ if (this.getAttribute("max")) {
1460
+ this.max = Number(this.getAttribute("max"));
1461
+ }
1462
+ if (this.getAttribute("step")) {
1463
+ this.step = Number(this.getAttribute("step"));
1464
+ }
1465
+
1466
+ // Set disabled state if present
1467
+ if (this.hasAttribute("disabled")) {
1468
+ const disabledAttr = this.getAttribute("disabled");
1469
+ this.disabled = this.input.disabled = disabledAttr !== "false";
1470
+ }
1471
+
1472
+ this.addEventListener("pointerdown", this.#boundMouseDown);
1473
+ this.input.removeEventListener("change", this.#boundInputChange);
1474
+ this.input.addEventListener("change", this.#boundInputChange);
1475
+ this.input.removeEventListener("input", this.#boundInput);
1476
+ this.input.addEventListener("input", this.#boundInput);
1477
+ this.input.removeEventListener("focus", this.#boundFocus);
1478
+ this.input.addEventListener("focus", this.#boundFocus);
1479
+ this.input.removeEventListener("blur", this.#boundBlur);
1480
+ this.input.addEventListener("blur", this.#boundBlur);
1481
+ });
1482
+ }
1483
+
1484
+ focus() {
1485
+ this.input.focus();
1486
+ }
1487
+
1488
+ #getNumericValue(str) {
1489
+ if (!str) return "";
1490
+ if (!this.#units) {
1491
+ // No units, just extract numeric value
1492
+ let value = str.replace(/[^\d.-]/g, "");
1493
+ // Prevent multiple decimal points
1494
+ const parts = value.split(".");
1495
+ if (parts.length > 2) {
1496
+ value = parts[0] + "." + parts.slice(1).join("");
1497
+ }
1498
+ return value;
1499
+ }
1500
+ let value = str.replace(this.#units, "").trim();
1501
+ value = value.replace(/[^\d.-]/g, "");
1502
+ // Prevent multiple decimal points
1503
+ const parts = value.split(".");
1504
+ if (parts.length > 2) {
1505
+ value = parts[0] + "." + parts.slice(1).join("");
1506
+ }
1507
+ return value;
1508
+ }
1509
+
1510
+ #formatWithUnit(numericValue) {
1511
+ if (
1512
+ numericValue === "" ||
1513
+ numericValue === null ||
1514
+ numericValue === undefined
1515
+ )
1516
+ return "";
1517
+ // numericValue is the internal (non-transformed) value
1518
+ // For display, we apply transform and format
1519
+ let displayValue = Number(numericValue) * (this.transform || 1);
1520
+ if (isNaN(displayValue)) return "";
1521
+ displayValue = this.#formatNumber(displayValue);
1522
+ if (!this.#units) return displayValue.toString();
1523
+ if (this.#unitPosition === "prefix") {
1524
+ return this.#units + displayValue;
1525
+ } else {
1526
+ return displayValue + this.#units;
1527
+ }
1528
+ }
1529
+
1530
+ #transformNumber(value) {
1531
+ if (value === "" || value === null || value === undefined) return "";
1532
+ let transformed = Number(value) * (this.transform || 1);
1533
+ transformed = this.#formatNumber(transformed);
1534
+ return transformed.toString();
1535
+ }
1536
+
1537
+ #handleFocus(e) {
1538
+ setTimeout(() => {
1539
+ const value = e.target.value;
1540
+ if (value && this.#units) {
1541
+ if (this.#unitPosition === "prefix") {
1542
+ e.target.setSelectionRange(this.#units.length, value.length);
1543
+ } else {
1544
+ const unitPos = value.indexOf(this.#units);
1545
+ if (unitPos > -1) {
1546
+ e.target.setSelectionRange(0, unitPos);
1547
+ }
1548
+ }
1549
+ }
1550
+ }, 0);
1551
+ }
1552
+
1553
+ #handleBlur(e) {
1554
+ let numericValue = this.#getNumericValue(e.target.value);
1555
+ if (numericValue !== "") {
1556
+ let val = Number(numericValue) / (this.transform || 1);
1557
+ val = this.#sanitizeInput(val, false);
1558
+ this.value = val;
1559
+ e.target.value = this.#formatWithUnit(this.value);
1560
+ } else {
1561
+ this.value = "";
1562
+ e.target.value = "";
1563
+ }
1564
+ this.dispatchEvent(
1565
+ new CustomEvent("change", { detail: this.value, bubbles: true })
1566
+ );
1567
+ }
1568
+
1569
+ #handleInput(e) {
1570
+ let numericValue = this.#getNumericValue(e.target.value);
1571
+ if (numericValue !== "") {
1572
+ this.value = Number(numericValue) / (this.transform || 1);
1573
+ } else {
1574
+ this.value = "";
1575
+ }
1576
+ this.dispatchEvent(
1577
+ new CustomEvent("input", { detail: this.value, bubbles: true })
1578
+ );
1579
+ }
1580
+
1581
+ #handleInputChange(e) {
1582
+ e.stopPropagation();
1583
+ let numericValue = this.#getNumericValue(e.target.value);
1584
+ if (numericValue !== "") {
1585
+ let val = Number(numericValue) / (this.transform || 1);
1586
+ val = this.#sanitizeInput(val, false);
1587
+ this.value = val;
1588
+ e.target.value = this.#formatWithUnit(this.value);
1589
+ } else {
1590
+ this.value = "";
1591
+ e.target.value = "";
1592
+ }
1593
+ this.dispatchEvent(
1594
+ new CustomEvent("input", { detail: this.value, bubbles: true })
1595
+ );
1596
+ this.dispatchEvent(
1597
+ new CustomEvent("change", { detail: this.value, bubbles: true })
1598
+ );
1599
+ }
1600
+
1601
+ #handleMouseMove(e) {
1602
+ if (this.disabled) return;
1603
+ if (e.altKey) {
1604
+ let step = (this.step || 1) * e.movementX;
1605
+ let numericValue = this.#getNumericValue(this.input.value);
1606
+ let value = Number(numericValue) / (this.transform || 1) + step;
1607
+ value = this.#sanitizeInput(value, false);
1608
+ this.value = value;
1609
+ this.input.value = this.#formatWithUnit(this.value);
1610
+ this.dispatchEvent(new CustomEvent("input", { bubbles: true }));
1611
+ this.dispatchEvent(new CustomEvent("change", { bubbles: true }));
1612
+ }
1613
+ }
1614
+
1615
+ #handleMouseDown(e) {
1616
+ if (this.disabled) return;
1617
+ if (e.altKey) {
1618
+ this.input.style.cursor =
1619
+ this.style.cursor =
1620
+ document.body.style.cursor =
1621
+ "ew-resize";
1622
+ this.style.userSelect = "none";
1623
+ // Use the pre-bound handlers
1624
+ window.addEventListener("pointermove", this.#boundMouseMove);
1625
+ window.addEventListener("pointerup", this.#boundMouseUp);
1626
+ }
1627
+ }
1628
+
1629
+ #handleMouseUp(e) {
1630
+ this.input.style.cursor =
1631
+ this.style.cursor =
1632
+ document.body.style.cursor =
1633
+ "";
1634
+ this.style.userSelect = "all";
1635
+ // Remove the pre-bound handlers
1636
+ window.removeEventListener("pointermove", this.#boundMouseMove);
1637
+ window.removeEventListener("pointerup", this.#boundMouseUp);
1638
+ }
1639
+
1640
+ #sanitizeInput(value, transform = true) {
1641
+ let sanitized = Number(value);
1642
+ if (isNaN(sanitized)) return "";
1643
+ if (typeof this.min === "number") {
1644
+ sanitized = Math.max(this.min, sanitized);
1645
+ }
1646
+ if (typeof this.max === "number") {
1647
+ sanitized = Math.min(this.max, sanitized);
1648
+ }
1649
+ sanitized = this.#formatNumber(sanitized);
1650
+ return sanitized;
1651
+ }
1652
+
1653
+ #formatNumber(num, precision = 2) {
1654
+ // Check if the number has any decimal places after rounding
1655
+ const rounded = Math.round(num * 100) / 100;
1656
+ return Number.isInteger(rounded) ? rounded : rounded.toFixed(precision);
1657
+ }
1658
+
1659
+ static get observedAttributes() {
1660
+ return [
1661
+ "value",
1662
+ "placeholder",
1663
+ "disabled",
1664
+ "step",
1665
+ "min",
1666
+ "max",
1667
+ "transform",
1668
+ "name",
1669
+ "units",
1670
+ "unit-position",
1671
+ ];
1672
+ }
1673
+
1674
+ attributeChangedCallback(name, oldValue, newValue) {
1675
+ if (this.input) {
1676
+ switch (name) {
1677
+ case "disabled":
1678
+ this.disabled = this.input.disabled =
1679
+ newValue !== null && newValue !== "false";
1680
+ break;
1681
+ case "units":
1682
+ this.#units = newValue || "";
1683
+ this.input.value = this.#formatWithUnit(this.value);
1684
+ break;
1685
+ case "unit-position":
1686
+ this.#unitPosition = newValue || "suffix";
1687
+ this.input.value = this.#formatWithUnit(this.value);
1688
+ break;
1689
+ case "transform":
1690
+ this.transform = Number(newValue) || 1;
1691
+ this.input.value = this.#formatWithUnit(this.value);
1692
+ break;
1693
+ case "value":
1694
+ let value =
1695
+ newValue !== null && newValue !== "" ? Number(newValue) : "";
1696
+ if (value !== "") {
1697
+ value = this.#sanitizeInput(value, false);
1698
+ }
1699
+ this.value = value;
1700
+ this.input.value = this.#formatWithUnit(this.value);
1701
+ break;
1702
+ case "min":
1703
+ case "max":
1704
+ case "step":
1705
+ this[name] = Number(newValue);
1706
+ break;
1707
+ case "name":
1708
+ this[name] = this.input[name] = newValue;
1709
+ this.input.setAttribute("name", newValue);
1710
+ break;
1711
+ default:
1712
+ this[name] = this.input[name] = newValue;
1713
+ break;
1714
+ }
1715
+ }
1716
+ }
1717
+ }
1718
+ window.customElements.define("fig-input-number", FigInputNumber);
1719
+
1322
1720
  /* Avatar */
1323
1721
  class FigAvatar extends HTMLElement {
1324
1722
  constructor() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "devDependencies": {