@rogieking/figui3 1.4.1 → 1.4.3

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/example.html CHANGED
@@ -24,37 +24,13 @@
24
24
  <h2><label>Effects/</label>UI3 Components</h2>
25
25
  </fig-header>
26
26
  <fig-content>
27
- <fig-field>
28
- <fig-tooltip text="Options">
29
- <fig-dropdown>
30
- <option>One</option>
31
- <option>Two</option>
32
- </fig-dropdown>
33
- </fig-tooltip>
34
-
35
- </fig-field>
36
- <fig-field>
37
- <fig-tooltip text="Delta slider">
38
- <fig-slider type="delta"
39
- value=".25"
40
- default="2"
41
- step="0.1"
42
- max="5"
43
- min="-5">
44
- </fig-slider>
45
- </fig-tooltip>
46
- </fig-field>
47
27
 
48
- <br /><br />
49
- <fig-input-angle value="45"
50
- text="true"></fig-input-angle>
51
- <br /><br />
52
- <fig-slider min="0.01"
53
- max="0.25"
28
+ <fig-slider min="-1"
29
+ max="1"
54
30
  name="u_thickness"
55
31
  variant="minimal"
56
32
  step="0.01"
57
- value="1"
33
+ value="0"
58
34
  text="true"
59
35
  type="range"
60
36
  units="%"
@@ -785,7 +761,12 @@
785
761
  </fig-button>
786
762
  </fig-field>
787
763
  </fig-content>
788
-
764
+ <script>
765
+ let elements = Array.from(document.querySelectorAll('*'));
766
+ elements.filter(n => n.tagName.toLowerCase().indexOf("fig-slider") > -1).forEach(n => n.addEventListener('input', (e) => {
767
+ console.log('input:', n.tagName, e.target)
768
+ }))
769
+ </script>
789
770
  </body>
790
771
 
791
772
  </html>
package/fig.css CHANGED
@@ -2255,8 +2255,7 @@ fig-segmented-control {
2255
2255
  display: inline-flex;
2256
2256
  align-items: stretch;
2257
2257
 
2258
- & > fig-segment,
2259
- & > label {
2258
+ & fig-segment {
2260
2259
  flex: 1;
2261
2260
  display: flex;
2262
2261
  align-items: center;
package/fig.js CHANGED
@@ -5,23 +5,22 @@ function figSupportsPopover() {
5
5
  return HTMLElement.prototype.hasOwnProperty("popover");
6
6
  }
7
7
 
8
- if (window.customElements && !window.customElements.get("fig-button")) {
9
- /**
10
- * A custom button element that supports different types and states.
11
- * @attr {string} type - The button type: "button" (default), "toggle", or "submit"
12
- * @attr {boolean} selected - Whether the button is in a selected state
13
- * @attr {boolean} disabled - Whether the button is disabled
14
- */
15
- class FigButton extends HTMLElement {
16
- type;
17
- #selected;
18
- constructor() {
19
- super();
20
- this.attachShadow({ mode: "open" });
21
- }
22
- connectedCallback() {
23
- this.type = this.getAttribute("type") || "button";
24
- this.shadowRoot.innerHTML = `
8
+ /**
9
+ * A custom button element that supports different types and states.
10
+ * @attr {string} type - The button type: "button" (default), "toggle", or "submit"
11
+ * @attr {boolean} selected - Whether the button is in a selected state
12
+ * @attr {boolean} disabled - Whether the button is disabled
13
+ */
14
+ class FigButton extends HTMLElement {
15
+ type;
16
+ #selected;
17
+ constructor() {
18
+ super();
19
+ this.attachShadow({ mode: "open" });
20
+ }
21
+ connectedCallback() {
22
+ this.type = this.getAttribute("type") || "button";
23
+ this.shadowRoot.innerHTML = `
25
24
  <style>
26
25
  button, button:hover, button:active {
27
26
  padding: 0 var(--spacer-2);
@@ -46,168 +45,159 @@ if (window.customElements && !window.customElements.get("fig-button")) {
46
45
  </button>
47
46
  `;
48
47
 
49
- this.#selected =
50
- this.hasAttribute("selected") &&
51
- this.getAttribute("selected") !== "false";
48
+ this.#selected =
49
+ this.hasAttribute("selected") &&
50
+ this.getAttribute("selected") !== "false";
52
51
 
53
- requestAnimationFrame(() => {
54
- this.button = this.shadowRoot.querySelector("button");
55
- this.button.addEventListener("click", this.#handleClick.bind(this));
56
- });
57
- }
52
+ requestAnimationFrame(() => {
53
+ this.button = this.shadowRoot.querySelector("button");
54
+ this.button.addEventListener("click", this.#handleClick.bind(this));
55
+ });
56
+ }
58
57
 
59
- get type() {
60
- return this.type;
61
- }
62
- set type(value) {
63
- this.setAttribute("type", value);
64
- }
65
- get selected() {
66
- return this.#selected;
67
- }
68
- set selected(value) {
69
- this.setAttribute("selected", value);
70
- }
58
+ get type() {
59
+ return this.type;
60
+ }
61
+ set type(value) {
62
+ this.setAttribute("type", value);
63
+ }
64
+ get selected() {
65
+ return this.#selected;
66
+ }
67
+ set selected(value) {
68
+ this.setAttribute("selected", value);
69
+ }
71
70
 
72
- #handleClick() {
73
- if (this.type === "toggle") {
74
- this.toggleAttribute("selected", !this.hasAttribute("selected"));
75
- }
76
- if (this.type === "submit") {
77
- this.closest("form").dispatchEvent(new Event("submit"));
78
- }
71
+ #handleClick() {
72
+ if (this.type === "toggle") {
73
+ this.toggleAttribute("selected", !this.hasAttribute("selected"));
79
74
  }
80
- static get observedAttributes() {
81
- return ["disabled", "selected"];
82
- }
83
- attributeChangedCallback(name, oldValue, newValue) {
84
- if (this.button) {
85
- this.button[name] = newValue;
86
- switch (name) {
87
- case "disabled":
88
- this.disabled = this.button.disabled =
89
- newValue === "true" ||
90
- (newValue === undefined && newValue !== null);
91
- break;
92
- case "type":
93
- this.type = newValue;
94
- this.button.type = this.type;
95
- this.button.setAttribute("type", this.type);
96
- break;
97
- case "selected":
98
- this.#selected = newValue === "true";
99
- break;
100
- }
75
+ if (this.type === "submit") {
76
+ this.closest("form").dispatchEvent(new Event("submit"));
77
+ }
78
+ }
79
+ static get observedAttributes() {
80
+ return ["disabled", "selected"];
81
+ }
82
+ attributeChangedCallback(name, oldValue, newValue) {
83
+ if (this.button) {
84
+ this.button[name] = newValue;
85
+ switch (name) {
86
+ case "disabled":
87
+ this.disabled = this.button.disabled =
88
+ newValue === "true" ||
89
+ (newValue === undefined && newValue !== null);
90
+ break;
91
+ case "type":
92
+ this.type = newValue;
93
+ this.button.type = this.type;
94
+ this.button.setAttribute("type", this.type);
95
+ break;
96
+ case "selected":
97
+ this.#selected = newValue === "true";
98
+ break;
101
99
  }
102
100
  }
103
101
  }
104
- window.customElements.define("fig-button", FigButton);
105
102
  }
103
+ window.customElements.define("fig-button", FigButton);
106
104
 
107
- if (window.customElements && !window.customElements.get("fig-dropdown")) {
108
- /**
109
- * A custom dropdown/select element.
110
- * @attr {string} type - The dropdown type: "select" (default) or "dropdown"
111
- * @attr {string} value - The currently selected value
112
- */
113
- class FigDropdown extends HTMLElement {
114
- constructor() {
115
- super();
116
- this.select = document.createElement("select");
117
- this.optionsSlot = document.createElement("slot");
118
- this.attachShadow({ mode: "open" });
119
- }
120
-
121
- #addEventListeners() {
122
- this.select.addEventListener("input", this.#handleSelectInput.bind(this));
123
- this.select.addEventListener(
124
- "change",
125
- this.#handleSelectChange.bind(this)
126
- );
127
- }
105
+ /**
106
+ * A custom dropdown/select element.
107
+ * @attr {string} type - The dropdown type: "select" (default) or "dropdown"
108
+ * @attr {string} value - The currently selected value
109
+ */
110
+ class FigDropdown extends HTMLElement {
111
+ constructor() {
112
+ super();
113
+ this.select = document.createElement("select");
114
+ this.optionsSlot = document.createElement("slot");
115
+ this.attachShadow({ mode: "open" });
116
+ }
128
117
 
129
- connectedCallback() {
130
- this.type = this.getAttribute("type") || "select";
118
+ #addEventListeners() {
119
+ this.select.addEventListener("input", this.#handleSelectInput.bind(this));
120
+ this.select.addEventListener("change", this.#handleSelectChange.bind(this));
121
+ }
131
122
 
132
- this.appendChild(this.select);
133
- this.shadowRoot.appendChild(this.optionsSlot);
123
+ connectedCallback() {
124
+ this.type = this.getAttribute("type") || "select";
134
125
 
135
- this.optionsSlot.addEventListener(
136
- "slotchange",
137
- this.slotChange.bind(this)
138
- );
126
+ this.appendChild(this.select);
127
+ this.shadowRoot.appendChild(this.optionsSlot);
139
128
 
140
- this.#addEventListeners();
141
- }
129
+ this.optionsSlot.addEventListener("slotchange", this.slotChange.bind(this));
142
130
 
143
- slotChange() {
144
- while (this.select.firstChild) {
145
- this.select.firstChild.remove();
146
- }
131
+ this.#addEventListeners();
132
+ }
147
133
 
148
- if (this.type === "dropdown") {
149
- const hiddenOption = document.createElement("option");
150
- hiddenOption.setAttribute("hidden", "true");
151
- hiddenOption.setAttribute("selected", "true");
152
- this.select.appendChild(hiddenOption);
153
- }
154
- this.optionsSlot.assignedNodes().forEach((option) => {
155
- if (option.nodeName === "OPTION" || option.nodeName === "OPTGROUP") {
156
- this.select.appendChild(option.cloneNode(true));
157
- }
158
- });
159
- this.#syncSelectedValue(this.value);
160
- if (this.type === "dropdown") {
161
- this.select.selectedIndex = -1;
162
- }
134
+ slotChange() {
135
+ while (this.select.firstChild) {
136
+ this.select.firstChild.remove();
163
137
  }
164
138
 
165
- #handleSelectInput(e) {
166
- this.value = e.target.value;
167
- this.setAttribute("value", this.value);
139
+ if (this.type === "dropdown") {
140
+ const hiddenOption = document.createElement("option");
141
+ hiddenOption.setAttribute("hidden", "true");
142
+ hiddenOption.setAttribute("selected", "true");
143
+ this.select.appendChild(hiddenOption);
168
144
  }
169
- #handleSelectChange() {
170
- if (this.type === "dropdown") {
171
- this.select.selectedIndex = -1;
145
+ this.optionsSlot.assignedNodes().forEach((option) => {
146
+ if (option.nodeName === "OPTION" || option.nodeName === "OPTGROUP") {
147
+ this.select.appendChild(option.cloneNode(true));
172
148
  }
149
+ });
150
+ this.#syncSelectedValue(this.value);
151
+ if (this.type === "dropdown") {
152
+ this.select.selectedIndex = -1;
173
153
  }
174
- focus() {
175
- this.select.focus();
176
- }
177
- blur() {
178
- this.select.blur();
179
- }
180
- get value() {
181
- return this.select?.value;
182
- }
183
- set value(value) {
184
- this.setAttribute("value", value);
154
+ }
155
+
156
+ #handleSelectInput(e) {
157
+ this.value = e.target.value;
158
+ this.setAttribute("value", this.value);
159
+ }
160
+ #handleSelectChange() {
161
+ if (this.type === "dropdown") {
162
+ this.select.selectedIndex = -1;
185
163
  }
186
- static get observedAttributes() {
187
- return ["value", "type"];
164
+ }
165
+ focus() {
166
+ this.select.focus();
167
+ }
168
+ blur() {
169
+ this.select.blur();
170
+ }
171
+ get value() {
172
+ return this.select?.value;
173
+ }
174
+ set value(value) {
175
+ this.setAttribute("value", value);
176
+ }
177
+ static get observedAttributes() {
178
+ return ["value", "type"];
179
+ }
180
+ #syncSelectedValue(value) {
181
+ if (this.select) {
182
+ this.select.querySelectorAll("option").forEach((o, i) => {
183
+ if (o.value === this.getAttribute("value")) {
184
+ this.select.selectedIndex = i;
185
+ }
186
+ });
188
187
  }
189
- #syncSelectedValue(value) {
190
- if (this.select) {
191
- this.select.querySelectorAll("option").forEach((o, i) => {
192
- if (o.value === this.getAttribute("value")) {
193
- this.select.selectedIndex = i;
194
- }
195
- });
196
- }
188
+ }
189
+ attributeChangedCallback(name, oldValue, newValue) {
190
+ if (name === "value") {
191
+ this.#syncSelectedValue(newValue);
197
192
  }
198
- attributeChangedCallback(name, oldValue, newValue) {
199
- if (name === "value") {
200
- this.#syncSelectedValue(newValue);
201
- }
202
- if (name === "type") {
203
- this.type = newValue;
204
- }
193
+ if (name === "type") {
194
+ this.type = newValue;
205
195
  }
206
196
  }
207
-
208
- customElements.define("fig-dropdown", FigDropdown);
209
197
  }
210
198
 
199
+ customElements.define("fig-dropdown", FigDropdown);
200
+
211
201
  /* Tooltip */
212
202
  /**
213
203
  * A custom tooltip element that displays on hover or click.
@@ -752,6 +742,7 @@ window.customElements.define("fig-segmented-control", FigSegmentedControl);
752
742
  * @attr {string} color - The color for the slider track (for opacity type)
753
743
  */
754
744
  class FigSlider extends HTMLElement {
745
+ // Private fields declarations
755
746
  #typeDefaults = {
756
747
  range: { min: 0, max: 100, step: 1 },
757
748
  hue: { min: 0, max: 255, step: 1 },
@@ -759,9 +750,25 @@ class FigSlider extends HTMLElement {
759
750
  stepper: { min: 0, max: 100, step: 25 },
760
751
  opacity: { min: 0, max: 100, step: 0.1, color: "#FF0000" },
761
752
  };
753
+
754
+ #boundHandleInput;
755
+ #boundHandleTextInput;
756
+
762
757
  constructor() {
763
758
  super();
759
+
760
+ // Bind the event handlers
761
+ this.#boundHandleInput = (e) => {
762
+ e.stopPropagation();
763
+ this.#handleInput();
764
+ };
765
+
766
+ this.#boundHandleTextInput = (e) => {
767
+ e.stopPropagation();
768
+ this.#handleTextInput();
769
+ };
764
770
  }
771
+
765
772
  #regenerateInnerHTML() {
766
773
  this.value = Number(this.getAttribute("value") || 0);
767
774
  this.type = this.getAttribute("type") || "range";
@@ -818,8 +825,8 @@ class FigSlider extends HTMLElement {
818
825
  requestAnimationFrame(() => {
819
826
  this.input = this.querySelector("[type=range]");
820
827
  this.inputContainer = this.querySelector(".fig-slider-input-container");
821
- this.input.removeEventListener("input", this.handleInput);
822
- this.input.addEventListener("input", this.handleInput.bind(this));
828
+ this.input.removeEventListener("input", this.#boundHandleInput);
829
+ this.input.addEventListener("input", this.#boundHandleInput);
823
830
 
824
831
  if (this.default) {
825
832
  this.style.setProperty(
@@ -866,14 +873,14 @@ class FigSlider extends HTMLElement {
866
873
  }
867
874
  }
868
875
  if (this.figInputText) {
869
- this.figInputText.removeEventListener("input", this.handleTextInput);
870
- this.figInputText.addEventListener(
876
+ this.figInputText.removeEventListener(
871
877
  "input",
872
- this.handleTextInput.bind(this)
878
+ this.#boundHandleTextInput
873
879
  );
880
+ this.figInputText.addEventListener("input", this.#boundHandleTextInput);
874
881
  }
875
882
 
876
- this.handleInput();
883
+ this.#syncValue();
877
884
  });
878
885
  }
879
886
 
@@ -881,6 +888,40 @@ class FigSlider extends HTMLElement {
881
888
  this.initialInnerHTML = this.innerHTML;
882
889
  this.#regenerateInnerHTML();
883
890
  }
891
+
892
+ #handleTextInput() {
893
+ if (this.figInputText) {
894
+ this.value = this.input.value = this.figInputText.value;
895
+ this.#syncProperties();
896
+ this.dispatchEvent(new CustomEvent("input", { bubbles: true }));
897
+ }
898
+ }
899
+ #calculateNormal(value) {
900
+ let min = Number(this.min);
901
+ let max = Number(this.max);
902
+ return (Number(value) - min) / (max - min);
903
+ }
904
+ #syncProperties() {
905
+ let complete = this.#calculateNormal(this.value);
906
+ this.style.setProperty("--slider-complete", complete);
907
+ let defaultValue = this.#calculateNormal(this.default);
908
+ this.style.setProperty("--default", defaultValue);
909
+ this.style.setProperty("--unchanged", complete === defaultValue ? 1 : 0);
910
+ }
911
+ #syncValue() {
912
+ let val = this.input.value;
913
+ this.value = val;
914
+ this.#syncProperties();
915
+ if (this.figInputText) {
916
+ this.figInputText.setAttribute("value", val);
917
+ }
918
+ }
919
+
920
+ #handleInput() {
921
+ this.#syncValue();
922
+ this.dispatchEvent(new CustomEvent("input", { bubbles: true }));
923
+ }
924
+
884
925
  static get observedAttributes() {
885
926
  return [
886
927
  "value",
@@ -938,38 +979,11 @@ class FigSlider extends HTMLElement {
938
979
  break;
939
980
  default:
940
981
  this[name] = this.input[name] = newValue;
941
- this.handleInput();
982
+ this.#syncValue();
942
983
  break;
943
984
  }
944
985
  }
945
986
  }
946
- handleTextInput() {
947
- if (this.figInputText) {
948
- this.value = this.input.value = this.figInputText.value;
949
- this.#syncProperties();
950
- }
951
- }
952
- #calculateNormal(value) {
953
- let min = Number(this.min);
954
- let max = Number(this.max);
955
- return (Number(value) - min) / (max - min);
956
- }
957
- #syncProperties() {
958
- let complete = this.#calculateNormal(this.value);
959
- this.style.setProperty("--slider-complete", complete);
960
- let defaultValue = this.#calculateNormal(this.default);
961
- this.style.setProperty("--default", defaultValue);
962
- this.style.setProperty("--unchanged", complete === defaultValue ? 1 : 0);
963
- }
964
-
965
- handleInput() {
966
- let val = this.input.value;
967
- this.value = val;
968
- this.#syncProperties();
969
- if (this.figInputText) {
970
- this.figInputText.setAttribute("value", val);
971
- }
972
- }
973
987
  }
974
988
  window.customElements.define("fig-slider", FigSlider);
975
989
 
@@ -989,6 +1003,7 @@ class FigInputText extends HTMLElement {
989
1003
  #boundMouseMove;
990
1004
  #boundMouseUp;
991
1005
  #boundMouseDown;
1006
+ #boundInputChange;
992
1007
 
993
1008
  constructor() {
994
1009
  super();
@@ -996,6 +1011,10 @@ class FigInputText extends HTMLElement {
996
1011
  this.#boundMouseMove = this.#handleMouseMove.bind(this);
997
1012
  this.#boundMouseUp = this.#handleMouseUp.bind(this);
998
1013
  this.#boundMouseDown = this.#handleMouseDown.bind(this);
1014
+ this.#boundInputChange = (e) => {
1015
+ e.stopPropagation();
1016
+ this.#handleInputChange(e);
1017
+ };
999
1018
  }
1000
1019
 
1001
1020
  connectedCallback() {
@@ -1063,7 +1082,8 @@ class FigInputText extends HTMLElement {
1063
1082
  }
1064
1083
  this.addEventListener("pointerdown", this.#boundMouseDown);
1065
1084
  }
1066
- this.input.addEventListener("input", this.#handleInput.bind(this));
1085
+ this.input.removeEventListener("change", this.#boundInputChange);
1086
+ this.input.addEventListener("change", this.#boundInputChange);
1067
1087
  });
1068
1088
  }
1069
1089
  focus() {
@@ -1075,14 +1095,14 @@ class FigInputText extends HTMLElement {
1075
1095
  transformed = this.#formatNumber(transformed);
1076
1096
  return transformed;
1077
1097
  }
1078
- #handleInput(e) {
1079
- console.log("handleInput", e.target.value);
1098
+ #handleInputChange(e) {
1080
1099
  e.stopPropagation();
1081
1100
  let value = e.target.value;
1082
1101
  let valueTransformed = value;
1083
1102
  if (this.type === "number") {
1084
1103
  value = value / (this.transform || 1);
1085
1104
  value = this.#sanitizeInput(value, false);
1105
+ console.log("sanitizeInput", value);
1086
1106
  valueTransformed = value * (this.transform || 1);
1087
1107
  }
1088
1108
  this.value = value;
@@ -1155,6 +1175,16 @@ class FigInputText extends HTMLElement {
1155
1175
  return Number.isInteger(rounded) ? rounded : rounded.toFixed(precision);
1156
1176
  }
1157
1177
 
1178
+ /*
1179
+ get value() {
1180
+ return this.value;
1181
+ }
1182
+
1183
+ set value(val) {
1184
+ this.value = val;
1185
+ this.setAttribute("value", val);
1186
+ }*/
1187
+
1158
1188
  static get observedAttributes() {
1159
1189
  return [
1160
1190
  "value",
@@ -1194,7 +1224,6 @@ class FigInputText extends HTMLElement {
1194
1224
  this.value = value;
1195
1225
  this.input.value = value;
1196
1226
  }
1197
- this.dispatchEvent(new CustomEvent("input", { bubbles: true }));
1198
1227
  break;
1199
1228
  case "min":
1200
1229
  case "max":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "devDependencies": {
package/test.html ADDED
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport"
7
+ content="width=device-width, initial-scale=1.0">
8
+ <title>Figma UI3 Web Components</title>
9
+ <link rel="stylesheet"
10
+ type="text/css"
11
+ href="fig.css">
12
+ <script src="fig.js"></script>
13
+ <style>
14
+ body {
15
+ width: 480px;
16
+ margin: 0 auto;
17
+ }
18
+ </style>
19
+ </head>
20
+
21
+ <body>
22
+ <fig-content>
23
+ <fig-slider type="delta"
24
+ value=".25"
25
+ default="2"
26
+ step="0.1"
27
+ max="5"
28
+ min="-5">
29
+ </fig-slider>
30
+ </fig-content>
31
+ <script>
32
+ let elements = Array.from(document.querySelectorAll('*'));
33
+ elements.filter(n => n.tagName.toLowerCase().indexOf("fig-") > -1).forEach(n => n.addEventListener('input', (e) => {
34
+ console.log('input:', n.tagName, e.target)
35
+ }))
36
+ </script>
37
+ </body>
38
+
39
+ </html>