@rogieking/figui3 2.0.2 → 2.0.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.
Files changed (4) hide show
  1. package/components.css +115 -18
  2. package/example.html +334 -241
  3. package/fig.js +156 -46
  4. package/package.json +1 -1
package/fig.js CHANGED
@@ -5,6 +5,25 @@
5
5
  function figUniqueId() {
6
6
  return Date.now().toString(36) + Math.random().toString(36).substring(2);
7
7
  }
8
+
9
+ /**
10
+ * Gets the highest z-index currently in use on the page
11
+ * @returns {number} The highest z-index found, minimum of 1000
12
+ */
13
+ function figGetHighestZIndex() {
14
+ let highest = 1000; // Baseline minimum
15
+
16
+ // Check all elements with inline z-index or computed z-index
17
+ const elements = document.querySelectorAll("*");
18
+ for (const el of elements) {
19
+ const zIndex = parseInt(getComputedStyle(el).zIndex, 10);
20
+ if (!isNaN(zIndex) && zIndex > highest) {
21
+ highest = zIndex;
22
+ }
23
+ }
24
+
25
+ return highest;
26
+ }
8
27
  /**
9
28
  * Checks if the browser supports the native popover API
10
29
  * @returns {boolean} True if popover is supported
@@ -326,7 +345,15 @@ class FigTooltip extends HTMLElement {
326
345
  this.popup.style.pointerEvents = "none";
327
346
  this.popup.append(content);
328
347
  content.innerText = this.getAttribute("text");
329
- document.body.append(this.popup);
348
+
349
+ // If tooltip is inside a dialog, append to dialog to stay in top layer
350
+ const parentDialog = this.closest("dialog");
351
+ if (parentDialog && parentDialog.open) {
352
+ parentDialog.append(this.popup);
353
+ } else {
354
+ document.body.append(this.popup);
355
+ }
356
+
330
357
  const text = content.childNodes[0];
331
358
  if (text) {
332
359
  const range = document.createRange();
@@ -451,7 +478,7 @@ class FigTooltip extends HTMLElement {
451
478
  this.popup.style.visibility = "visible";
452
479
  this.popup.style.display = "block";
453
480
  this.popup.style.pointerEvents = "all";
454
- this.popup.style.zIndex = parseInt(new Date().getTime() / 1000);
481
+ this.popup.style.zIndex = figGetHighestZIndex() + 1;
455
482
 
456
483
  this.isOpen = true;
457
484
  }
@@ -2635,10 +2662,15 @@ class FigComboInput extends HTMLElement {
2635
2662
  }
2636
2663
  if (name === "placeholder") {
2637
2664
  this.placeholder = newValue;
2665
+ if (this.input) {
2666
+ this.input.setAttribute("placeholder", newValue);
2667
+ }
2638
2668
  }
2639
2669
  if (name === "value") {
2640
2670
  this.value = newValue;
2641
- this.input.setAttribute("value", newValue);
2671
+ if (this.input) {
2672
+ this.input.setAttribute("value", newValue);
2673
+ }
2642
2674
  }
2643
2675
  }
2644
2676
  }
@@ -2690,6 +2722,9 @@ class FigChit extends HTMLElement {
2690
2722
  this.#src = value;
2691
2723
  this.setAttribute("src", value);
2692
2724
  }
2725
+ focus() {
2726
+ this.input?.focus();
2727
+ }
2693
2728
  attributeChangedCallback(name, oldValue, newValue) {
2694
2729
  switch (name) {
2695
2730
  case "src":
@@ -2918,15 +2953,20 @@ class FigInputJoystick extends HTMLElement {
2918
2953
  constructor() {
2919
2954
  super();
2920
2955
 
2921
- this.position = { x: 0.5, y: 0.5 };
2922
- this.value = [0.5, 0.5];
2956
+ this.position = { x: 0.5, y: 0.5 }; // Internal position (0-1)
2923
2957
  this.isDragging = false;
2924
2958
  this.isShiftHeld = false;
2925
2959
  this.plane = null;
2926
2960
  this.cursor = null;
2927
2961
  this.xInput = null;
2928
2962
  this.yInput = null;
2963
+ this.coordinates = "screen"; // "screen" (0,0 top-left) or "math" (0,0 bottom-left)
2964
+ this.#initialized = false;
2965
+ }
2966
+
2967
+ #initialized = false;
2929
2968
 
2969
+ connectedCallback() {
2930
2970
  // Initialize position
2931
2971
  requestAnimationFrame(() => {
2932
2972
  this.precision = this.getAttribute("precision") || 3;
@@ -2934,24 +2974,19 @@ class FigInputJoystick extends HTMLElement {
2934
2974
  this.transform = this.getAttribute("transform") || 1;
2935
2975
  this.transform = Number(this.transform);
2936
2976
  this.text = this.getAttribute("text") === "true";
2977
+ this.coordinates = this.getAttribute("coordinates") || "screen";
2937
2978
 
2938
2979
  this.#render();
2939
-
2940
2980
  this.#setupListeners();
2941
-
2942
2981
  this.#syncHandlePosition();
2943
- if (this.text && this.xInput && this.yInput) {
2944
- this.xInput.setAttribute(
2945
- "value",
2946
- this.position.x.toFixed(this.precision)
2947
- );
2948
- this.yInput.setAttribute(
2949
- "value",
2950
- this.position.y.toFixed(this.precision)
2951
- );
2952
- }
2982
+ this.#initialized = true;
2953
2983
  });
2954
2984
  }
2985
+
2986
+ // Convert Y for display (CSS uses top-down, math uses bottom-up)
2987
+ #displayY(y) {
2988
+ return this.coordinates === "math" ? 1 - y : y;
2989
+ }
2955
2990
  disconnectedCallback() {
2956
2991
  this.#cleanupListeners();
2957
2992
  }
@@ -2969,24 +3004,24 @@ class FigInputJoystick extends HTMLElement {
2969
3004
  </div>
2970
3005
  ${
2971
3006
  this.text
2972
- ? `<fig-input-text
2973
- type="number"
3007
+ ? `<fig-input-number
2974
3008
  name="x"
2975
- step="0.01"
2976
- value="${this.position.x}"
3009
+ step="1"
3010
+ value="${this.position.x * 100}"
2977
3011
  min="0"
2978
- max="1">
3012
+ max="100"
3013
+ units="%">
2979
3014
  <span slot="prepend">X</span>
2980
- </fig-input-text>
2981
- <fig-input-text
2982
- type="number"
2983
- name="y"
2984
- step="0.01"
2985
- min="0"
2986
- max="1"
2987
- value="${this.position.y}">
3015
+ </fig-input-number>
3016
+ <fig-input-number
3017
+ name="y"
3018
+ step="1"
3019
+ min="0"
3020
+ max="100"
3021
+ value="${this.position.y * 100}"
3022
+ units="%">
2988
3023
  <span slot="prepend">Y</span>
2989
- </fig-input-text>`
3024
+ </fig-input-number>`
2990
3025
  : ""
2991
3026
  }
2992
3027
  `;
@@ -2995,8 +3030,8 @@ class FigInputJoystick extends HTMLElement {
2995
3030
  #setupListeners() {
2996
3031
  this.plane = this.querySelector(".fig-input-joystick-plane");
2997
3032
  this.cursor = this.querySelector(".fig-input-joystick-handle");
2998
- this.xInput = this.querySelector("fig-input-text[name='x']");
2999
- this.yInput = this.querySelector("fig-input-text[name='y']");
3033
+ this.xInput = this.querySelector("fig-input-number[name='x']");
3034
+ this.yInput = this.querySelector("fig-input-number[name='y']");
3000
3035
  this.plane.addEventListener("mousedown", this.#handleMouseDown.bind(this));
3001
3036
  this.plane.addEventListener(
3002
3037
  "touchstart",
@@ -3011,7 +3046,10 @@ class FigInputJoystick extends HTMLElement {
3011
3046
  }
3012
3047
 
3013
3048
  #cleanupListeners() {
3014
- this.plane.removeEventListener("mousedown", this.#handleMouseDown);
3049
+ if (this.plane) {
3050
+ this.plane.removeEventListener("mousedown", this.#handleMouseDown);
3051
+ this.plane.removeEventListener("touchstart", this.#handleTouchStart);
3052
+ }
3015
3053
  window.removeEventListener("keydown", this.#handleKeyDown);
3016
3054
  window.removeEventListener("keyup", this.#handleKeyUp);
3017
3055
  if (this.text && this.xInput && this.yInput) {
@@ -3022,7 +3060,7 @@ class FigInputJoystick extends HTMLElement {
3022
3060
 
3023
3061
  #handleXInput(e) {
3024
3062
  e.stopPropagation();
3025
- this.position.x = Number(e.target.value);
3063
+ this.position.x = Number(e.target.value) / 100; // Convert from percentage to decimal
3026
3064
  this.#syncHandlePosition();
3027
3065
  this.#emitInputEvent();
3028
3066
  this.#emitChangeEvent();
@@ -3030,7 +3068,7 @@ class FigInputJoystick extends HTMLElement {
3030
3068
 
3031
3069
  #handleYInput(e) {
3032
3070
  e.stopPropagation();
3033
- this.position.y = Number(e.target.value);
3071
+ this.position.y = Number(e.target.value) / 100; // Convert from percentage to decimal
3034
3072
  this.#syncHandlePosition();
3035
3073
  this.#emitInputEvent();
3036
3074
  this.#emitChangeEvent();
@@ -3055,7 +3093,13 @@ class FigInputJoystick extends HTMLElement {
3055
3093
  #updatePosition(e) {
3056
3094
  const rect = this.plane.getBoundingClientRect();
3057
3095
  let x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
3058
- let y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height));
3096
+ let screenY = Math.max(
3097
+ 0,
3098
+ Math.min(1, (e.clientY - rect.top) / rect.height)
3099
+ );
3100
+
3101
+ // Convert screen Y to internal Y (flip for math coordinates)
3102
+ let y = this.coordinates === "math" ? 1 - screenY : screenY;
3059
3103
 
3060
3104
  x = this.#snapToGuide(x);
3061
3105
  y = this.#snapToGuide(y);
@@ -3063,11 +3107,12 @@ class FigInputJoystick extends HTMLElement {
3063
3107
  const snapped = this.#snapToDiagonal(x, y);
3064
3108
  this.position = snapped;
3065
3109
 
3110
+ const displayY = this.#displayY(snapped.y);
3066
3111
  this.cursor.style.left = `${snapped.x * 100}%`;
3067
- this.cursor.style.top = `${snapped.y * 100}%`; // Invert Y for display
3112
+ this.cursor.style.top = `${displayY * 100}%`;
3068
3113
  if (this.text && this.xInput && this.yInput) {
3069
- this.xInput.setAttribute("value", snapped.x.toFixed(3));
3070
- this.yInput.setAttribute("value", snapped.y.toFixed(3));
3114
+ this.xInput.setAttribute("value", Math.round(snapped.x * 100));
3115
+ this.yInput.setAttribute("value", Math.round(snapped.y * 100));
3071
3116
  }
3072
3117
 
3073
3118
  this.#emitInputEvent();
@@ -3092,9 +3137,15 @@ class FigInputJoystick extends HTMLElement {
3092
3137
  }
3093
3138
 
3094
3139
  #syncHandlePosition() {
3140
+ const displayY = this.#displayY(this.position.y);
3095
3141
  if (this.cursor) {
3096
3142
  this.cursor.style.left = `${this.position.x * 100}%`;
3097
- this.cursor.style.top = `${this.position.y * 100}%`;
3143
+ this.cursor.style.top = `${displayY * 100}%`;
3144
+ }
3145
+ // Also sync text inputs if they exist (convert to percentage 0-100)
3146
+ if (this.text && this.xInput && this.yInput) {
3147
+ this.xInput.setAttribute("value", Math.round(this.position.x * 100));
3148
+ this.yInput.setAttribute("value", Math.round(this.position.y * 100));
3098
3149
  }
3099
3150
  }
3100
3151
 
@@ -3152,16 +3203,33 @@ class FigInputJoystick extends HTMLElement {
3152
3203
  #handleKeyUp(e) {
3153
3204
  if (e.key === "Shift") this.isShiftHeld = false;
3154
3205
  }
3206
+ focus() {
3207
+ const container = this.querySelector(".fig-input-joystick-plane-container");
3208
+ container?.focus();
3209
+ }
3155
3210
  static get observedAttributes() {
3156
- return ["value", "precision", "transform", "text"];
3211
+ return ["value", "precision", "transform", "text", "coordinates"];
3157
3212
  }
3158
3213
  get value() {
3159
- return [this.position.x, this.position.y];
3214
+ // Return as percentage values (0-100)
3215
+ return [
3216
+ Math.round(this.position.x * 100),
3217
+ Math.round(this.position.y * 100),
3218
+ ];
3160
3219
  }
3161
3220
  set value(value) {
3162
- let v = value.toString().split(",").map(Number);
3163
- this.position = { x: v[0], y: v[1] };
3164
- this.#syncHandlePosition();
3221
+ // Parse value, strip % symbols if present, convert from 0-100 to 0-1
3222
+ const v = value
3223
+ .toString()
3224
+ .split(",")
3225
+ .map((s) => {
3226
+ const num = parseFloat(s.replace(/%/g, "").trim());
3227
+ return isNaN(num) ? 0.5 : num / 100; // Convert from percentage to decimal, default to 0.5 if invalid
3228
+ });
3229
+ this.position = { x: v[0] ?? 0.5, y: v[1] ?? 0.5 };
3230
+ if (this.#initialized) {
3231
+ this.#syncHandlePosition();
3232
+ }
3165
3233
  }
3166
3234
  attributeChangedCallback(name, oldValue, newValue) {
3167
3235
  if (name === "value") {
@@ -3177,6 +3245,10 @@ class FigInputJoystick extends HTMLElement {
3177
3245
  this.text = newValue.toLowerCase() === "true";
3178
3246
  this.#render();
3179
3247
  }
3248
+ if (name === "coordinates") {
3249
+ this.coordinates = newValue || "screen";
3250
+ this.#syncHandlePosition();
3251
+ }
3180
3252
  }
3181
3253
  }
3182
3254
 
@@ -3406,6 +3478,10 @@ class FigInputAngle extends HTMLElement {
3406
3478
  if (e.key === "Shift") this.isShiftHeld = false;
3407
3479
  }
3408
3480
 
3481
+ focus() {
3482
+ this.plane?.focus();
3483
+ }
3484
+
3409
3485
  static get observedAttributes() {
3410
3486
  return ["value", "precision", "text"];
3411
3487
  }
@@ -3446,3 +3522,37 @@ class FigInputAngle extends HTMLElement {
3446
3522
  }
3447
3523
  }
3448
3524
  customElements.define("fig-input-angle", FigInputAngle);
3525
+
3526
+ // FigShimmer
3527
+ class FigShimmer extends HTMLElement {
3528
+ connectedCallback() {
3529
+ const duration = this.getAttribute("duration");
3530
+ if (duration) {
3531
+ this.style.setProperty("--shimmer-duration", duration);
3532
+ }
3533
+ }
3534
+
3535
+ static get observedAttributes() {
3536
+ return ["duration", "playing"];
3537
+ }
3538
+
3539
+ get playing() {
3540
+ return this.getAttribute("playing") !== "false";
3541
+ }
3542
+
3543
+ set playing(value) {
3544
+ if (value) {
3545
+ this.removeAttribute("playing"); // Default is playing
3546
+ } else {
3547
+ this.setAttribute("playing", "false");
3548
+ }
3549
+ }
3550
+
3551
+ attributeChangedCallback(name, oldValue, newValue) {
3552
+ if (name === "duration") {
3553
+ this.style.setProperty("--shimmer-duration", newValue || "1.5s");
3554
+ }
3555
+ // playing is handled purely by CSS attribute selectors
3556
+ }
3557
+ }
3558
+ customElements.define("fig-shimmer", FigShimmer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "devDependencies": {