@rogieking/figui3 1.2.8 → 1.2.9

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 (4) hide show
  1. package/example.html +55 -23
  2. package/fig.css +89 -2
  3. package/fig.js +177 -1
  4. package/package.json +1 -1
package/example.html CHANGED
@@ -23,14 +23,17 @@
23
23
  <h2>UI3 Components</h2>
24
24
  </fig-header>
25
25
  <fig-content>
26
-
27
- <h2>Details</h2>
26
+ <fig-input-joystick text="true"></fig-input-joystick>
27
+ <fig-header>
28
+ <h2>Details</h2>
29
+ </fig-header>
28
30
  <details>
29
31
  <summary>Advanced settings</summary>
30
32
  <p>Some more content here</p>
31
33
  </details>
32
-
33
- <h2>Avatar</h2>
34
+ <fig-header>
35
+ <h2>Avatar</h2>
36
+ </fig-header>
34
37
  <fig-field>
35
38
  <label>Default</label>
36
39
  <fig-avatar src="https://avatars.githubusercontent.com/u/12345678?v=4"
@@ -53,15 +56,18 @@
53
56
  name="Rogie King"></fig-avatar>
54
57
  </fig-field>
55
58
 
56
- <h2>Tabs</h2>
59
+ <fig-header>
60
+ <h2>Tabs</h2>
61
+ </fig-header>
57
62
  <fig-field>
58
63
  <fig-tabs>
59
64
  <fig-tab selected>Tab #1</fig-tab>
60
65
  <fig-tab>Tab #2</fig-tab>
61
66
  </fig-tabs>
62
67
  </fig-field>
63
- <h2>Segmented control</h2>
64
-
68
+ <fig-header>
69
+ <h2>Segmented control</h2>
70
+ </fig-header>
65
71
  <fig-field>
66
72
  <fig-segmented-control>
67
73
  <fig-segment selected>One</fig-segment>
@@ -69,7 +75,9 @@
69
75
  <fig-segment>Three</fig-segment>
70
76
  </fig-segmented-control>
71
77
  </fig-field>
72
- <h2>Image</h2>
78
+ <fig-header>
79
+ <h2>Image</h2>
80
+ </fig-header>
73
81
  <fig-field>
74
82
  <label>Default</label>
75
83
  <fig-image src="https://avatars.githubusercontent.com/u/12345678?v=4"></fig-image>
@@ -86,7 +94,9 @@
86
94
  size="large"></fig-image>
87
95
  </fig-field>
88
96
  <br /><br />
89
- <h2>Button</h2>
97
+ <fig-header>
98
+ <h2>Button</h2>
99
+ </fig-header>
90
100
  <fig-field>
91
101
  <label>Primary Button</label>
92
102
  <fig-button>Primary</fig-button>
@@ -127,8 +137,9 @@
127
137
  fill="currentColor"></path>
128
138
  </svg></fig-button>
129
139
  </fig-field>
130
-
131
- <h2>Combo button</h2>
140
+ <fig-header>
141
+ <h2>Combo button</h2>
142
+ </fig-header>
132
143
  <fig-field>
133
144
  <label>Primary (compact dropdown</label>
134
145
  <fig-button-combo>
@@ -315,8 +326,9 @@
315
326
  <fig-button>Save</fig-button>
316
327
  </footer>
317
328
  </fig-dialog>
318
-
319
- <h2>Dropdown</h2>
329
+ <fig-header>
330
+ <h2>Dropdown</h2>
331
+ </fig-header>
320
332
  <fig-field>
321
333
  <label>Dropdown</label>
322
334
  <fig-dropdown>
@@ -392,7 +404,9 @@
392
404
  <span slot="prepend">X</span>
393
405
  </fig-input-text>
394
406
  </fig-field>
395
- <h2>Color input</h2>
407
+ <fig-header>
408
+ <h2>Color input</h2>
409
+ </fig-header>
396
410
  <fig-field>
397
411
  <label>Color swatch</label>
398
412
  <fig-input-color value="#FF000066"></fig-input-color>
@@ -408,11 +422,15 @@
408
422
  alpha="true"
409
423
  text="true"></fig-input-color>
410
424
  </fig-field>
411
- <h2>Checkbox</h2>
425
+ <fig-header>
426
+ <h2>Checkbox</h2>
427
+ </fig-header>
412
428
  <fig-field>
413
429
  <fig-checkbox label="Checkbox"></fig-checkbox>
414
430
  </fig-field>
415
- <h2>Radio</h2>
431
+ <fig-header>
432
+ <h2>Radio</h2>
433
+ </fig-header>
416
434
  <fig-field>
417
435
  <label>Radio buttons</label>
418
436
  <fig-radio label="Radio #1"
@@ -420,7 +438,9 @@
420
438
  <fig-radio label="Radio #2"
421
439
  name="r1"></fig-radio>
422
440
  </fig-field>
423
- <h2>Switch</h2>
441
+ <fig-header>
442
+ <h2>Switch</h2>
443
+ </fig-header>
424
444
  <fig-field>
425
445
  <label>Switches</label>
426
446
  <fig-switch on="true"
@@ -430,7 +450,9 @@
430
450
  <fig-switch disabled
431
451
  label="Disabled"></fig-switch>
432
452
  </fig-field>
433
- <h2>Chit</h2>
453
+ <fig-header>
454
+ <h2>Chit</h2>
455
+ </fig-header>
434
456
  <fig-field>
435
457
  <label>Chit</label>
436
458
  <hstack>
@@ -473,13 +495,17 @@
473
495
  </hstack>
474
496
  </fig-field>
475
497
 
476
- <h2>Tooltip</h2>
498
+ <fig-header>
499
+ <h2>Tooltip</h2>
500
+ </fig-header>
477
501
  <p>Some paragraph text here with a
478
502
  <fig-tooltip text="Tooltip text">
479
503
  <em>Tooltip</em>
480
504
  </fig-tooltip> for more information.
481
505
  </p>
482
- <h2>Field</h2>
506
+ <fig-header>
507
+ <h2>Field</h2>
508
+ </fig-header>
483
509
  <fig-field direction="horizontal">
484
510
  <label>Horizontal</label>
485
511
  <fig-input-text value=""
@@ -490,7 +516,9 @@
490
516
  <fig-input-text value=""
491
517
  placeholder="Field placeholder"></fig-input-text>
492
518
  </fig-field>
493
- <h2>Slider</h2>
519
+ <fig-header>
520
+ <h2>Slider</h2>
521
+ </fig-header>
494
522
  <fig-field>
495
523
  <label>Default slider</label>
496
524
  <fig-slider default="25"
@@ -559,10 +587,14 @@
559
587
  </datalist>
560
588
  </fig-slider>
561
589
  </fig-field>
562
- <h2>Spinner</h2>
590
+ <fig-header>
591
+ <h2>Spinner</h2>
592
+ </fig-header>
563
593
  <fig-spinner></fig-spinner>
564
594
 
565
- <h2>Misc.</h2>
595
+ <fig-header>
596
+ <h2>Misc.</h2>
597
+ </fig-header>
566
598
  <hstack>
567
599
  <fig-tooltip text="Tooltip"
568
600
  delay="0">
package/fig.css CHANGED
@@ -444,7 +444,7 @@
444
444
  @media (prefers-color-scheme: dark) {
445
445
  :root {
446
446
  --icon-chevron: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5.87868 7.12132L8 9.24264L10.1213 7.12132' stroke='rgb(255 255 255 / 100%)' stroke-linecap='round'/%3E%3C/svg%3E%0A");
447
- --handle-shadow: 0px 0 0 0.75px rgba(255, 255, 255, 0.1),
447
+ --handle-shadow: 0px 0 0 0.75px 0px 0 0 0.75px rgba(0, 0, 0, 0.1),
448
448
  0px 0px 0.5px 0px rgba(255, 255, 255, 0.1);
449
449
  --figma-color-bg-ghost-hover: rgba(255, 255, 255, 0.05);
450
450
  --figma-color-bordertranslucent: rgba(255, 255, 255, 0.1);
@@ -1224,7 +1224,7 @@ input[type="checkbox"].switch:after {
1224
1224
  height: calc(1rem - 2px);
1225
1225
  border-radius: 0.5rem;
1226
1226
  margin: 1px;
1227
- transform: translate(calc(-0.5rem - 1px));
1227
+ transform: translate(calc(-0.5rem));
1228
1228
  transition: var(--input-transition);
1229
1229
  box-shadow: var(--handle-shadow);
1230
1230
  }
@@ -1247,6 +1247,9 @@ input[type="checkbox"].switch:checked:after {
1247
1247
  input[type="checkbox"].switch:disabled {
1248
1248
  background-color: var(--figma-color-bg-tertiary);
1249
1249
  cursor: not-allowed;
1250
+ &:after {
1251
+ background-color: var(--figma-color-bg);
1252
+ }
1250
1253
  }
1251
1254
 
1252
1255
  input[type="checkbox"].switch:focus {
@@ -2271,3 +2274,87 @@ fig-segmented-control {
2271
2274
  transform: rotate(360deg);
2272
2275
  }
2273
2276
  }
2277
+
2278
+ fig-input-joystick {
2279
+ --size: 1.5rem;
2280
+ display: inline-flex;
2281
+ gap: var(--spacer-2);
2282
+ .fig-input-joystick-plane-container {
2283
+ display: flex;
2284
+ width: 1.5rem;
2285
+ height: 1.5rem;
2286
+ place-items: center;
2287
+ }
2288
+ .fig-input-joystick-plane {
2289
+ display: inline-grid;
2290
+ place-items: center;
2291
+ position: relative;
2292
+ width: var(--size);
2293
+ height: var(--size);
2294
+ flex-shrink: 0;
2295
+ &:hover {
2296
+ cursor: grab;
2297
+ --size: 3rem;
2298
+ }
2299
+ }
2300
+ .fig-input-joystick-plane > * {
2301
+ grid-area: 1/1;
2302
+ }
2303
+ .fig-input-joystick-guides {
2304
+ position: absolute;
2305
+ width: var(--size);
2306
+ height: var(--size);
2307
+ border: 1px solid var(--figma-color-border);
2308
+ border-radius: var(--radius-medium);
2309
+ overflow: hidden;
2310
+ background: var(--figma-color-bg-secondary);
2311
+
2312
+ &:before {
2313
+ content: "";
2314
+ position: absolute;
2315
+ height: 0;
2316
+ border-left: 1px solid var(--figma-color-border);
2317
+ height: 100%;
2318
+ top: 0;
2319
+ left: calc(50% - 0.5px);
2320
+ pointer-events: none;
2321
+ }
2322
+ &:after {
2323
+ content: "";
2324
+ position: absolute;
2325
+ height: 0;
2326
+ border-top: 1px solid var(--figma-color-border);
2327
+ width: 100%;
2328
+ top: calc(50% - 0.5px);
2329
+ left: 0;
2330
+ pointer-events: none;
2331
+ }
2332
+ }
2333
+ .fig-input-joystick-plane:hover .fig-input-joystick-guides {
2334
+ background: linear-gradient(
2335
+ 45deg,
2336
+ transparent calc(50% - 0.5px),
2337
+ var(--figma-color-border) calc(50% - 0.5px),
2338
+ var(--figma-color-border) calc(50% + 0.5px),
2339
+ transparent calc(50% + 0.5px)
2340
+ ),
2341
+ linear-gradient(
2342
+ -45deg,
2343
+ transparent calc(50% - 0.5px),
2344
+ var(--figma-color-border) calc(50% - 0.5px),
2345
+ var(--figma-color-border) calc(50% + 0.5px),
2346
+ transparent calc(50% + 0.5px)
2347
+ ),
2348
+ var(--figma-color-bg-secondary);
2349
+ }
2350
+ .fig-input-joystick-handle {
2351
+ position: absolute;
2352
+ z-index: 1;
2353
+ width: 0.5rem;
2354
+ height: 0.5rem;
2355
+ background: var(--figma-color-icon-onbrand);
2356
+ box-shadow: var(--handle-shadow);
2357
+ border-radius: 50%;
2358
+ transform: translate(-50%, -50%);
2359
+ }
2360
+ }
package/fig.js CHANGED
@@ -892,6 +892,8 @@ class FigInputText extends HTMLElement {
892
892
  this.value = this.getAttribute("value") || "";
893
893
  this.type = this.getAttribute("type") || "text";
894
894
  this.placeholder = this.getAttribute("placeholder") || "";
895
+ this.name = this.getAttribute("name") || null;
896
+
895
897
  if (this.type === "number") {
896
898
  if (this.getAttribute("step")) {
897
899
  this.step = Number(this.getAttribute("step"));
@@ -910,6 +912,7 @@ class FigInputText extends HTMLElement {
910
912
 
911
913
  let html = `<input
912
914
  type="${this.type}"
915
+ ${this.name ? `name="${this.name}"` : ""}
913
916
  placeholder="${this.placeholder}"
914
917
  value="${
915
918
  this.type === "number" ? this.#transformNumber(this.value) : this.value
@@ -1038,6 +1041,7 @@ class FigInputText extends HTMLElement {
1038
1041
  "min",
1039
1042
  "max",
1040
1043
  "transform",
1044
+ "name",
1041
1045
  ];
1042
1046
  }
1043
1047
 
@@ -1078,8 +1082,12 @@ class FigInputText extends HTMLElement {
1078
1082
  this.input.setAttribute(name, newValue);
1079
1083
  }
1080
1084
  break;
1085
+ case "name":
1086
+ this[name] = this.input[name] = newValue;
1087
+ this.input.setAttribute("name", newValue);
1088
+ break;
1081
1089
  default:
1082
- this[name] = this.input[name] = value;
1090
+ this[name] = this.input[name] = newValue;
1083
1091
  break;
1084
1092
  }
1085
1093
  }
@@ -1703,3 +1711,171 @@ class FigImage extends HTMLElement {
1703
1711
  }
1704
1712
  }
1705
1713
  window.customElements.define("fig-image", FigImage);
1714
+
1715
+ class FigInputJoystick extends HTMLElement {
1716
+ constructor() {
1717
+ super();
1718
+
1719
+ this.position = { x: 0.5, y: 0.5 };
1720
+ this.value = [0.5, 0.5];
1721
+ this.isDragging = false;
1722
+ this.isShiftHeld = false;
1723
+
1724
+ // Initialize position
1725
+ requestAnimationFrame(() => {
1726
+ this.precision = this.getAttribute("precision") || 3;
1727
+ this.precision = parseInt(this.precision);
1728
+ this.transform = this.getAttribute("transform") || 1;
1729
+ this.transform = Number(this.transform);
1730
+ this.text = this.getAttribute("text") === "true";
1731
+
1732
+ this.render();
1733
+
1734
+ this.setupListeners();
1735
+
1736
+ this.cursor.style.left = `${this.position.x * 100}%`;
1737
+ this.cursor.style.top = `${this.position.y * 100}%`;
1738
+ if (this.text) {
1739
+ this.xInput.value = this.position.x.toFixed(this.precision);
1740
+ this.yInput.value = this.position.y.toFixed(this.precision);
1741
+ }
1742
+ });
1743
+ }
1744
+
1745
+ render() {
1746
+ this.innerHTML = `
1747
+ <div class="fig-input-joystick-plane-container">
1748
+ <div class="fig-input-joystick-plane">
1749
+ <div class="fig-input-joystick-guides"></div>
1750
+ <div class="fig-input-joystick-handle"></div>
1751
+ </div>
1752
+ </div>
1753
+ ${
1754
+ this.text
1755
+ ? `<fig-input-text
1756
+ type="number"
1757
+ name="x"
1758
+ step="0.01"
1759
+ value="${this.position.x}"
1760
+ min="0"
1761
+ max="1">
1762
+ <span slot="prepend">X</span>
1763
+ </fig-input-text>
1764
+ <fig-input-text
1765
+ type="number"
1766
+ name="y"
1767
+ step="0.01"
1768
+ min="0"
1769
+ max="1"
1770
+ value="${this.position.y}">
1771
+ <span slot="prepend">Y</span>
1772
+ </fig-input-text>`
1773
+ : ""
1774
+ }
1775
+ `;
1776
+ }
1777
+
1778
+ setupListeners() {
1779
+ this.plane = this.querySelector(".fig-input-joystick-plane");
1780
+ this.cursor = this.querySelector(".fig-input-joystick-handle");
1781
+ if (this.text) {
1782
+ this.xInput = this.querySelector("fig-input-text[name='x']");
1783
+ this.yInput = this.querySelector("fig-input-text[name='y']");
1784
+ }
1785
+
1786
+ this.plane.addEventListener("mousedown", this.handleMouseDown.bind(this));
1787
+ window.addEventListener("keydown", this.handleKeyDown.bind(this));
1788
+ window.addEventListener("keyup", this.handleKeyUp.bind(this));
1789
+
1790
+ this.xInput.addEventListener("input", this.handleXInput.bind(this));
1791
+ this.yInput.addEventListener("input", this.handleYInput.bind(this));
1792
+ }
1793
+
1794
+ handleXInput(e) {
1795
+ this.position.x = e.target.value;
1796
+ this.#syncHandlePosition();
1797
+ }
1798
+
1799
+ handleYInput(e) {
1800
+ this.position.y = e.target.value;
1801
+ this.#syncHandlePosition();
1802
+ }
1803
+
1804
+ snapToGuide(value) {
1805
+ if (!this.isShiftHeld) return value;
1806
+ if (value < 0.1) return 0;
1807
+ if (value > 0.9) return 1;
1808
+ if (value > 0.4 && value < 0.6) return 0.5;
1809
+ return value;
1810
+ }
1811
+
1812
+ snapToDiagonal(x, y) {
1813
+ if (!this.isShiftHeld) return { x, y };
1814
+ const diff = Math.abs(x - y);
1815
+ if (diff < 0.1) return { x: (x + y) / 2, y: (x + y) / 2 };
1816
+ if (Math.abs(1 - x - y) < 0.1) return { x, y: 1 - x };
1817
+ return { x, y };
1818
+ }
1819
+
1820
+ updatePosition(e) {
1821
+ const rect = this.plane.getBoundingClientRect();
1822
+ let x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
1823
+ let y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height));
1824
+
1825
+ // Invert Y coordinate to match typical coordinate system
1826
+ y = 1 - y;
1827
+
1828
+ x = this.snapToGuide(x);
1829
+ y = this.snapToGuide(y);
1830
+
1831
+ const snapped = this.snapToDiagonal(x, y);
1832
+ this.position = snapped;
1833
+ this.value = [snapped.x, snapped.y];
1834
+
1835
+ this.cursor.style.left = `${snapped.x * 100}%`;
1836
+ this.cursor.style.top = `${(1 - snapped.y) * 100}%`; // Invert Y for display
1837
+ if (this.text) {
1838
+ this.xInput.setAttribute("value", snapped.x.toFixed(3));
1839
+ this.yInput.setAttribute("value", snapped.y.toFixed(3));
1840
+ }
1841
+
1842
+ this.dispatchEvent(new CustomEvent("input", { detail: this.position }));
1843
+ }
1844
+
1845
+ #syncHandlePosition() {
1846
+ this.cursor.style.left = `${this.position.x * 100}%`;
1847
+ this.cursor.style.top = `${(1 - this.position.y) * 100}%`;
1848
+ }
1849
+
1850
+ handleMouseDown(e) {
1851
+ this.isDragging = true;
1852
+ this.updatePosition(e);
1853
+
1854
+ this.plane.style.cursor = "grabbing";
1855
+
1856
+ const handleMouseMove = (e) => {
1857
+ if (this.isDragging) this.updatePosition(e);
1858
+ };
1859
+
1860
+ const handleMouseUp = () => {
1861
+ this.isDragging = false;
1862
+ this.plane.style.cursor = "";
1863
+ window.removeEventListener("mousemove", handleMouseMove);
1864
+ window.removeEventListener("mouseup", handleMouseUp);
1865
+ this.dispatchEvent(new CustomEvent("change", { detail: this.position }));
1866
+ };
1867
+
1868
+ window.addEventListener("mousemove", handleMouseMove);
1869
+ window.addEventListener("mouseup", handleMouseUp);
1870
+ }
1871
+
1872
+ handleKeyDown(e) {
1873
+ if (e.key === "Shift") this.isShiftHeld = true;
1874
+ }
1875
+
1876
+ handleKeyUp(e) {
1877
+ if (e.key === "Shift") this.isShiftHeld = false;
1878
+ }
1879
+ }
1880
+
1881
+ customElements.define("fig-input-joystick", FigInputJoystick);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "1.2.8",
3
+ "version": "1.2.9",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "devDependencies": {