@rogieking/figui3 1.4.0 → 1.4.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.
Files changed (5) hide show
  1. package/example.html +11 -29
  2. package/fig.css +1 -2
  3. package/fig.js +219 -190
  4. package/package.json +1 -1
  5. package/test.html +39 -0
package/example.html CHANGED
@@ -20,40 +20,17 @@
20
20
 
21
21
  <body>
22
22
  <fig-header>
23
- <h2>UI3 Components</h2>
23
+
24
+ <h2><label>Effects/</label>UI3 Components</h2>
24
25
  </fig-header>
25
26
  <fig-content>
26
- <fig-field>
27
- <fig-tooltip text="Options">
28
- <fig-dropdown>
29
- <option>One</option>
30
- <option>Two</option>
31
- </fig-dropdown>
32
- </fig-tooltip>
33
27
 
34
- </fig-field>
35
- <fig-field>
36
- <fig-tooltip text="Delta slider">
37
- <fig-slider type="delta"
38
- value=".25"
39
- default="2"
40
- step="0.1"
41
- max="5"
42
- min="-5">
43
- </fig-slider>
44
- </fig-tooltip>
45
- </fig-field>
46
-
47
- <br /><br />
48
- <fig-input-angle value="45"
49
- text="true"></fig-input-angle>
50
- <br /><br />
51
- <fig-slider min="0.01"
52
- max="0.25"
28
+ <fig-slider min="-1"
29
+ max="1"
53
30
  name="u_thickness"
54
31
  variant="minimal"
55
32
  step="0.01"
56
- value="1"
33
+ value="0"
57
34
  text="true"
58
35
  type="range"
59
36
  units="%"
@@ -784,7 +761,12 @@
784
761
  </fig-button>
785
762
  </fig-field>
786
763
  </fig-content>
787
-
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>
788
770
  </body>
789
771
 
790
772
  </html>
package/fig.css CHANGED
@@ -2054,14 +2054,13 @@ fig-input-color {
2054
2054
  fig-header {
2055
2055
  height: var(--spacer-6);
2056
2056
  margin: 0;
2057
- padding: var(--spacer-2);
2057
+ padding: var(--spacer-1) var(--spacer-3);
2058
2058
  display: flex;
2059
2059
  align-items: center;
2060
2060
  box-shadow: inset 0 -1px 0 0 var(--figma-color-border);
2061
2061
  gap: 0.25rem;
2062
2062
 
2063
2063
  & h3 {
2064
- padding-left: var(--spacer-2);
2065
2064
  font-weight: var(--body-medium-strong-fontWeight);
2066
2065
  flex-grow: 1;
2067
2066
  }
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.0",
3
+ "version": "1.4.2",
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>