@rogieking/figui3 8.2.1 → 8.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.
package/fig-editor.css CHANGED
@@ -702,7 +702,7 @@ fig-select-options,
702
702
 
703
703
  }
704
704
 
705
- fig-select-options > fig-separator {
705
+ fig-select-options > :is(fig-separator, fig-menu-separator) {
706
706
  /* Align labels with select option text after the checkmark column. */
707
707
  &[label]:not([label=""]) {
708
708
  --fig-menu-inline-padding: 0 var(--spacer-2) 0 var(--spacer-5);
package/fig.js CHANGED
@@ -9841,6 +9841,30 @@ class FigInputGradient extends HTMLElement {
9841
9841
  );
9842
9842
  }
9843
9843
 
9844
+ #activeStopHandle() {
9845
+ if (!this.#track) return null;
9846
+ const selected = this.#track.querySelector(
9847
+ "fig-handle[selected]:not(.fig-input-gradient-ghost)",
9848
+ );
9849
+ if (selected) return selected;
9850
+ const active = document.activeElement;
9851
+ if (!(active instanceof Element) || !this.#track.contains(active)) {
9852
+ return null;
9853
+ }
9854
+ return active.closest("fig-handle:not(.fig-input-gradient-ghost)");
9855
+ }
9856
+
9857
+ #activateStopHandle(handle) {
9858
+ if (!handle || !this.#track) return;
9859
+ this.#track
9860
+ .querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)")
9861
+ .forEach((other) => {
9862
+ if (other !== handle) other.deselect();
9863
+ });
9864
+ handle.select();
9865
+ handle.focus();
9866
+ }
9867
+
9844
9868
  #syncFocusTarget() {
9845
9869
  const disabled =
9846
9870
  this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
@@ -9910,7 +9934,15 @@ class FigInputGradient extends HTMLElement {
9910
9934
 
9911
9935
  #onKeyDown = (e) => {
9912
9936
  const active = document.activeElement;
9913
- if (!(active instanceof Node) || !this.contains(active)) return;
9937
+ const activeIsInside = active instanceof Node && this.contains(active);
9938
+ const selectedHandle = this.#track?.querySelector(
9939
+ "fig-handle[selected]:not(.fig-input-gradient-ghost)",
9940
+ );
9941
+ const focusLostToBody =
9942
+ !active ||
9943
+ active === document.body ||
9944
+ active === document.documentElement;
9945
+ if (!activeIsInside && !(selectedHandle && focusLostToBody)) return;
9914
9946
  const isTyping =
9915
9947
  active &&
9916
9948
  (active.tagName === "INPUT" ||
@@ -9919,9 +9951,7 @@ class FigInputGradient extends HTMLElement {
9919
9951
  if (!this.#track) return;
9920
9952
 
9921
9953
  if (e.key === "Tab" && !isTyping) {
9922
- const selected = this.#track.querySelector(
9923
- "fig-handle[selected]:not(.fig-input-gradient-ghost)",
9924
- );
9954
+ const selected = this.#activeStopHandle();
9925
9955
  if (!selected) return;
9926
9956
  e.preventDefault();
9927
9957
  const handles = [
@@ -9934,14 +9964,12 @@ class FigInputGradient extends HTMLElement {
9934
9964
  ? (curIdx - 1 + handles.length) % handles.length
9935
9965
  : (curIdx + 1) % handles.length;
9936
9966
  selected.deselect();
9937
- handles[next].select();
9967
+ this.#activateStopHandle(handles[next]);
9938
9968
  return;
9939
9969
  }
9940
9970
 
9941
9971
  if ((e.key === "ArrowLeft" || e.key === "ArrowRight") && !isTyping) {
9942
- const selected = this.#track.querySelector(
9943
- "fig-handle[selected]:not(.fig-input-gradient-ghost)",
9944
- );
9972
+ const selected = this.#activeStopHandle();
9945
9973
  if (!selected) return;
9946
9974
  const idx = parseInt(selected.dataset.stopIndex, 10);
9947
9975
  if (isNaN(idx) || !this.#gradient.stops[idx]) return;
@@ -9973,19 +10001,21 @@ class FigInputGradient extends HTMLElement {
9973
10001
  if (e.key !== "Delete" && e.key !== "Backspace") return;
9974
10002
  if (isTyping) return;
9975
10003
  if (this.#gradient.stops.length <= 2) return;
9976
- const selected = this.#track.querySelector(
9977
- "fig-handle[selected]:not(.fig-input-gradient-ghost)",
9978
- );
10004
+ const selected = this.#activeStopHandle();
9979
10005
  if (!selected) return;
9980
10006
  const idx = parseInt(selected.dataset.stopIndex, 10);
9981
10007
  if (isNaN(idx) || !this.#gradient.stops[idx]) return;
9982
10008
  e.preventDefault();
9983
- selected.removeAttribute("selected");
9984
10009
  this.#gradient.stops.splice(idx, 1);
9985
10010
  this.#syncHandles();
9986
10011
  this.#syncSwatch();
9987
10012
  this.#emitInput();
9988
10013
  this.#emitChange();
10014
+ const nextIdx = Math.min(idx, this.#gradient.stops.length - 1);
10015
+ const next = this.#track.querySelectorAll(
10016
+ "fig-handle:not(.fig-input-gradient-ghost)",
10017
+ )[nextIdx];
10018
+ this.#activateStopHandle(next);
9989
10019
  };
9990
10020
 
9991
10021
  #onPickerKeyDown = (e) => {
@@ -10279,7 +10309,7 @@ class FigInputGradient extends HTMLElement {
10279
10309
  "fig-handle:not(.fig-input-gradient-ghost)",
10280
10310
  );
10281
10311
  const newHandle = handles[newIndex];
10282
- if (newHandle) newHandle.click();
10312
+ if (newHandle) this.#activateStopHandle(newHandle);
10283
10313
  });
10284
10314
  };
10285
10315
 
@@ -10446,12 +10476,7 @@ class FigInputGradient extends HTMLElement {
10446
10476
  );
10447
10477
  const newHandle = handles[newIndex];
10448
10478
  if (newHandle) {
10449
- this.#track
10450
- .querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)")
10451
- .forEach((h) => {
10452
- if (h !== newHandle) h.deselect();
10453
- });
10454
- newHandle.select();
10479
+ this.#activateStopHandle(newHandle);
10455
10480
  newHandle.dispatchEvent(
10456
10481
  new PointerEvent("pointerdown", {
10457
10482
  bubbles: true,
@@ -17795,15 +17820,14 @@ class FigHandle extends HTMLElement {
17795
17820
  this.#didDrag = false;
17796
17821
  return;
17797
17822
  }
17823
+ this.select();
17798
17824
  if (
17799
17825
  this.getAttribute("type") === "color" &&
17800
17826
  this.#canOpenColorPicker &&
17801
17827
  !this.#tipMode
17802
17828
  ) {
17803
17829
  this.#openDirectColorPicker();
17804
- return;
17805
17830
  }
17806
- this.select();
17807
17831
  };
17808
17832
 
17809
17833
  #handleDeselect = (e) => {
@@ -17985,6 +18009,8 @@ class FigHandle extends HTMLElement {
17985
18009
  #onPointerDown(e) {
17986
18010
  if (!this.#dragEnabled || figBooleanAttribute(this, "disabled")) return;
17987
18011
  e.preventDefault();
18012
+ this.focus();
18013
+ this.select();
17988
18014
  const container = this.#getContainer();
17989
18015
  if (!container) return;
17990
18016
 
@@ -18384,17 +18410,19 @@ class FigHandle extends HTMLElement {
18384
18410
  e.stopPropagation();
18385
18411
  const detail = this.#detailFromNativeColor(e.target.value);
18386
18412
  this.setAttribute("color", this.#colorWithOpacity(detail.color, detail.opacity));
18387
- this.removeAttribute("selected");
18388
18413
  this.dispatchEvent(
18389
18414
  new CustomEvent("change", {
18390
18415
  bubbles: true,
18391
18416
  detail,
18392
18417
  }),
18393
18418
  );
18419
+ if (this.isConnected) this.focus();
18394
18420
  };
18395
18421
 
18396
18422
  #handleDirectColorPickerClose = () => {
18397
- this.removeAttribute("selected");
18423
+ if (!this.isConnected) return;
18424
+ this.setAttribute("selected", "");
18425
+ this.focus();
18398
18426
  };
18399
18427
 
18400
18428
  #handleColorTipInput = (e) => {
@@ -18558,6 +18586,19 @@ class FigSeparator extends HTMLElement {
18558
18586
  }
18559
18587
  figDefineElement("fig-separator", FigSeparator);
18560
18588
 
18589
+ // Backwards-compatible alias. Custom element constructors cannot be registered
18590
+ // under multiple tag names, so the legacy tag uses an otherwise empty subclass.
18591
+ class FigMenuSeparator extends FigSeparator {}
18592
+ figDefineElement("fig-menu-separator", FigMenuSeparator);
18593
+
18594
+ const FIG_MENU_CHILD_SELECTOR =
18595
+ ":scope > fig-menu-item, :scope > fig-separator, :scope > fig-menu-separator";
18596
+ const FIG_MENU_CHILD_TAGS = new Set([
18597
+ "FIG-MENU-ITEM",
18598
+ "FIG-SEPARATOR",
18599
+ "FIG-MENU-SEPARATOR",
18600
+ ]);
18601
+
18561
18602
  class FigMenu extends HTMLElement {
18562
18603
  #popup = null;
18563
18604
  #panel = null;
@@ -18634,9 +18675,7 @@ class FigMenu extends HTMLElement {
18634
18675
  if (this.#popup) {
18635
18676
  this.#popup.removeEventListener("close", this.#boundPopupClose);
18636
18677
  const items = Array.from(
18637
- this.#panel?.querySelectorAll(
18638
- ":scope > fig-menu-item, :scope > fig-separator",
18639
- ) ?? [],
18678
+ this.#panel?.querySelectorAll(FIG_MENU_CHILD_SELECTOR) ?? [],
18640
18679
  );
18641
18680
  for (const item of items) this.insertBefore(item, this.#popup);
18642
18681
  this.#popup.remove();
@@ -18678,7 +18717,9 @@ class FigMenu extends HTMLElement {
18678
18717
  #detectTrigger() {
18679
18718
  this.#trigger =
18680
18719
  this.querySelector("[fig-menu-trigger]") ||
18681
- this.querySelector(":scope > :not(fig-menu-item):not(fig-separator)");
18720
+ this.querySelector(
18721
+ ":scope > :not(fig-menu-item):not(fig-separator):not(fig-menu-separator)",
18722
+ );
18682
18723
  }
18683
18724
 
18684
18725
  #usesContextMenuTrigger() {
@@ -18717,9 +18758,7 @@ class FigMenu extends HTMLElement {
18717
18758
 
18718
18759
  #moveItemsToPopup() {
18719
18760
  if (!this.#panel) return;
18720
- const items = Array.from(this.querySelectorAll(
18721
- ":scope > fig-menu-item, :scope > fig-separator"
18722
- ));
18761
+ const items = Array.from(this.querySelectorAll(FIG_MENU_CHILD_SELECTOR));
18723
18762
  for (const item of items) {
18724
18763
  this.#panel.appendChild(item);
18725
18764
  }
@@ -18758,8 +18797,7 @@ class FigMenu extends HTMLElement {
18758
18797
  for (const node of mutation.addedNodes) {
18759
18798
  if (node.nodeType !== 1 || node === this.#popup) continue;
18760
18799
  if (
18761
- (node.tagName === "FIG-MENU-ITEM" ||
18762
- node.tagName === "FIG-SEPARATOR") &&
18800
+ FIG_MENU_CHILD_TAGS.has(node.tagName) &&
18763
18801
  node.parentElement === this
18764
18802
  ) {
18765
18803
  this.#panel?.appendChild(node);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "8.2.1",
3
+ "version": "8.3.0",
4
4
  "description": "A lightweight web components library for building Figma plugin and widget UIs with native look and feel",
5
5
  "author": "Rogie King",
6
6
  "license": "MIT",