@rogieking/figui3 1.2.8 → 1.3.0

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 +56 -23
  2. package/fig.css +89 -2
  3. package/fig.js +200 -1
  4. package/package.json +1 -1
package/example.html CHANGED
@@ -23,14 +23,18 @@
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"
27
+ onInput="console.log(event.target.value)"></fig-input-joystick>
28
+ <fig-header>
29
+ <h2>Details</h2>
30
+ </fig-header>
28
31
  <details>
29
32
  <summary>Advanced settings</summary>
30
33
  <p>Some more content here</p>
31
34
  </details>
32
-
33
- <h2>Avatar</h2>
35
+ <fig-header>
36
+ <h2>Avatar</h2>
37
+ </fig-header>
34
38
  <fig-field>
35
39
  <label>Default</label>
36
40
  <fig-avatar src="https://avatars.githubusercontent.com/u/12345678?v=4"
@@ -53,15 +57,18 @@
53
57
  name="Rogie King"></fig-avatar>
54
58
  </fig-field>
55
59
 
56
- <h2>Tabs</h2>
60
+ <fig-header>
61
+ <h2>Tabs</h2>
62
+ </fig-header>
57
63
  <fig-field>
58
64
  <fig-tabs>
59
65
  <fig-tab selected>Tab #1</fig-tab>
60
66
  <fig-tab>Tab #2</fig-tab>
61
67
  </fig-tabs>
62
68
  </fig-field>
63
- <h2>Segmented control</h2>
64
-
69
+ <fig-header>
70
+ <h2>Segmented control</h2>
71
+ </fig-header>
65
72
  <fig-field>
66
73
  <fig-segmented-control>
67
74
  <fig-segment selected>One</fig-segment>
@@ -69,7 +76,9 @@
69
76
  <fig-segment>Three</fig-segment>
70
77
  </fig-segmented-control>
71
78
  </fig-field>
72
- <h2>Image</h2>
79
+ <fig-header>
80
+ <h2>Image</h2>
81
+ </fig-header>
73
82
  <fig-field>
74
83
  <label>Default</label>
75
84
  <fig-image src="https://avatars.githubusercontent.com/u/12345678?v=4"></fig-image>
@@ -86,7 +95,9 @@
86
95
  size="large"></fig-image>
87
96
  </fig-field>
88
97
  <br /><br />
89
- <h2>Button</h2>
98
+ <fig-header>
99
+ <h2>Button</h2>
100
+ </fig-header>
90
101
  <fig-field>
91
102
  <label>Primary Button</label>
92
103
  <fig-button>Primary</fig-button>
@@ -127,8 +138,9 @@
127
138
  fill="currentColor"></path>
128
139
  </svg></fig-button>
129
140
  </fig-field>
130
-
131
- <h2>Combo button</h2>
141
+ <fig-header>
142
+ <h2>Combo button</h2>
143
+ </fig-header>
132
144
  <fig-field>
133
145
  <label>Primary (compact dropdown</label>
134
146
  <fig-button-combo>
@@ -315,8 +327,9 @@
315
327
  <fig-button>Save</fig-button>
316
328
  </footer>
317
329
  </fig-dialog>
318
-
319
- <h2>Dropdown</h2>
330
+ <fig-header>
331
+ <h2>Dropdown</h2>
332
+ </fig-header>
320
333
  <fig-field>
321
334
  <label>Dropdown</label>
322
335
  <fig-dropdown>
@@ -392,7 +405,9 @@
392
405
  <span slot="prepend">X</span>
393
406
  </fig-input-text>
394
407
  </fig-field>
395
- <h2>Color input</h2>
408
+ <fig-header>
409
+ <h2>Color input</h2>
410
+ </fig-header>
396
411
  <fig-field>
397
412
  <label>Color swatch</label>
398
413
  <fig-input-color value="#FF000066"></fig-input-color>
@@ -408,11 +423,15 @@
408
423
  alpha="true"
409
424
  text="true"></fig-input-color>
410
425
  </fig-field>
411
- <h2>Checkbox</h2>
426
+ <fig-header>
427
+ <h2>Checkbox</h2>
428
+ </fig-header>
412
429
  <fig-field>
413
430
  <fig-checkbox label="Checkbox"></fig-checkbox>
414
431
  </fig-field>
415
- <h2>Radio</h2>
432
+ <fig-header>
433
+ <h2>Radio</h2>
434
+ </fig-header>
416
435
  <fig-field>
417
436
  <label>Radio buttons</label>
418
437
  <fig-radio label="Radio #1"
@@ -420,7 +439,9 @@
420
439
  <fig-radio label="Radio #2"
421
440
  name="r1"></fig-radio>
422
441
  </fig-field>
423
- <h2>Switch</h2>
442
+ <fig-header>
443
+ <h2>Switch</h2>
444
+ </fig-header>
424
445
  <fig-field>
425
446
  <label>Switches</label>
426
447
  <fig-switch on="true"
@@ -430,7 +451,9 @@
430
451
  <fig-switch disabled
431
452
  label="Disabled"></fig-switch>
432
453
  </fig-field>
433
- <h2>Chit</h2>
454
+ <fig-header>
455
+ <h2>Chit</h2>
456
+ </fig-header>
434
457
  <fig-field>
435
458
  <label>Chit</label>
436
459
  <hstack>
@@ -473,13 +496,17 @@
473
496
  </hstack>
474
497
  </fig-field>
475
498
 
476
- <h2>Tooltip</h2>
499
+ <fig-header>
500
+ <h2>Tooltip</h2>
501
+ </fig-header>
477
502
  <p>Some paragraph text here with a
478
503
  <fig-tooltip text="Tooltip text">
479
504
  <em>Tooltip</em>
480
505
  </fig-tooltip> for more information.
481
506
  </p>
482
- <h2>Field</h2>
507
+ <fig-header>
508
+ <h2>Field</h2>
509
+ </fig-header>
483
510
  <fig-field direction="horizontal">
484
511
  <label>Horizontal</label>
485
512
  <fig-input-text value=""
@@ -490,7 +517,9 @@
490
517
  <fig-input-text value=""
491
518
  placeholder="Field placeholder"></fig-input-text>
492
519
  </fig-field>
493
- <h2>Slider</h2>
520
+ <fig-header>
521
+ <h2>Slider</h2>
522
+ </fig-header>
494
523
  <fig-field>
495
524
  <label>Default slider</label>
496
525
  <fig-slider default="25"
@@ -559,10 +588,14 @@
559
588
  </datalist>
560
589
  </fig-slider>
561
590
  </fig-field>
562
- <h2>Spinner</h2>
591
+ <fig-header>
592
+ <h2>Spinner</h2>
593
+ </fig-header>
563
594
  <fig-spinner></fig-spinner>
564
595
 
565
- <h2>Misc.</h2>
596
+ <fig-header>
597
+ <h2>Misc.</h2>
598
+ </fig-header>
566
599
  <hstack>
567
600
  <fig-tooltip text="Tooltip"
568
601
  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,194 @@ 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
+ e.stopPropagation();
1796
+ this.position.x = e.target.value;
1797
+ this.#syncHandlePosition();
1798
+ this.#emitInputEvent();
1799
+ this.#emitChangeEvent();
1800
+ }
1801
+
1802
+ handleYInput(e) {
1803
+ e.stopPropagation();
1804
+ this.position.y = e.target.value;
1805
+ this.#syncHandlePosition();
1806
+ this.#emitInputEvent();
1807
+ this.#emitChangeEvent();
1808
+ }
1809
+
1810
+ snapToGuide(value) {
1811
+ if (!this.isShiftHeld) return value;
1812
+ if (value < 0.1) return 0;
1813
+ if (value > 0.9) return 1;
1814
+ if (value > 0.4 && value < 0.6) return 0.5;
1815
+ return value;
1816
+ }
1817
+
1818
+ snapToDiagonal(x, y) {
1819
+ if (!this.isShiftHeld) return { x, y };
1820
+ const diff = Math.abs(x - y);
1821
+ if (diff < 0.1) return { x: (x + y) / 2, y: (x + y) / 2 };
1822
+ if (Math.abs(1 - x - y) < 0.1) return { x, y: 1 - x };
1823
+ return { x, y };
1824
+ }
1825
+
1826
+ #updatePosition(e) {
1827
+ const rect = this.plane.getBoundingClientRect();
1828
+ let x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
1829
+ let y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height));
1830
+
1831
+ // Invert Y coordinate to match typical coordinate system
1832
+ y = 1 - y;
1833
+
1834
+ x = this.snapToGuide(x);
1835
+ y = this.snapToGuide(y);
1836
+
1837
+ const snapped = this.snapToDiagonal(x, y);
1838
+ this.position = snapped;
1839
+ this.value = [snapped.x, snapped.y];
1840
+
1841
+ this.cursor.style.left = `${snapped.x * 100}%`;
1842
+ this.cursor.style.top = `${(1 - snapped.y) * 100}%`; // Invert Y for display
1843
+ if (this.text) {
1844
+ this.xInput.setAttribute("value", snapped.x.toFixed(3));
1845
+ this.yInput.setAttribute("value", snapped.y.toFixed(3));
1846
+ }
1847
+
1848
+ this.#emitInputEvent();
1849
+ }
1850
+ #emitInputEvent() {
1851
+ this.dispatchEvent(
1852
+ new CustomEvent("input", {
1853
+ bubbles: true,
1854
+ cancelable: true,
1855
+ })
1856
+ );
1857
+ }
1858
+
1859
+ #emitChangeEvent() {
1860
+ this.dispatchEvent(
1861
+ new CustomEvent("change", {
1862
+ bubbles: true,
1863
+ cancelable: true,
1864
+ })
1865
+ );
1866
+ }
1867
+
1868
+ #syncHandlePosition() {
1869
+ this.cursor.style.left = `${this.position.x * 100}%`;
1870
+ this.cursor.style.top = `${(1 - this.position.y) * 100}%`;
1871
+ }
1872
+
1873
+ handleMouseDown(e) {
1874
+ this.isDragging = true;
1875
+ this.#updatePosition(e);
1876
+
1877
+ this.plane.style.cursor = "grabbing";
1878
+
1879
+ const handleMouseMove = (e) => {
1880
+ if (this.isDragging) this.#updatePosition(e);
1881
+ };
1882
+
1883
+ const handleMouseUp = () => {
1884
+ this.isDragging = false;
1885
+ this.plane.style.cursor = "";
1886
+ window.removeEventListener("mousemove", handleMouseMove);
1887
+ window.removeEventListener("mouseup", handleMouseUp);
1888
+ this.#emitChangeEvent();
1889
+ };
1890
+
1891
+ window.addEventListener("mousemove", handleMouseMove);
1892
+ window.addEventListener("mouseup", handleMouseUp);
1893
+ }
1894
+
1895
+ handleKeyDown(e) {
1896
+ if (e.key === "Shift") this.isShiftHeld = true;
1897
+ }
1898
+
1899
+ handleKeyUp(e) {
1900
+ if (e.key === "Shift") this.isShiftHeld = false;
1901
+ }
1902
+ }
1903
+
1904
+ 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.3.0",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "devDependencies": {