@rogieking/figui3 8.9.49 → 8.10.1

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
@@ -849,6 +849,16 @@ fig-select-option {
849
849
  }
850
850
  }
851
851
 
852
+ &[subtle]:not([subtle="false"]),
853
+ fig-select[subtle]:not([subtle="false"]) &:not([subtle="false"]) {
854
+ &:hover,
855
+ &:focus-visible {
856
+ &::before {
857
+ background-color: var(--figma-color-bg-secondary);
858
+ }
859
+ }
860
+ }
861
+
852
862
  &[selected]:not([selected="false"])::after,
853
863
  &[aria-selected="true"]::after {
854
864
  visibility: visible;
package/fig-editor.js CHANGED
@@ -499,6 +499,9 @@ class FigSelect extends HTMLElement {
499
499
  #button = null;
500
500
  #popup = null;
501
501
  #prependEl = null;
502
+ #prependSlot = null;
503
+ #prependFallback = null;
504
+ #customTriggerSlot = null;
502
505
  #labelEl = null;
503
506
  #panelSlot = null;
504
507
  #observer = null;
@@ -534,6 +537,7 @@ class FigSelect extends HTMLElement {
534
537
  "closedby",
535
538
  "open",
536
539
  "variant",
540
+ "aria-label",
537
541
  ];
538
542
  }
539
543
 
@@ -581,7 +585,7 @@ class FigSelect extends HTMLElement {
581
585
  this.#syncValue();
582
586
  return;
583
587
  }
584
- if (name === "value" || name === "label") {
588
+ if (name === "value" || name === "label" || name === "aria-label") {
585
589
  this.#syncValue();
586
590
  return;
587
591
  }
@@ -728,7 +732,7 @@ class FigSelect extends HTMLElement {
728
732
  text-overflow: ellipsis;
729
733
  cursor: default;
730
734
  }
731
- .fig-select-trigger:has(.fig-select-prepend:not(:empty)) {
735
+ .fig-select-trigger:has(.fig-select-prepend.has-content) {
732
736
  padding-left: 0;
733
737
  }
734
738
  .fig-select-trigger:hover,
@@ -763,7 +767,20 @@ class FigSelect extends HTMLElement {
763
767
  margin-right: var(--spacer-1, 0.25rem);
764
768
  pointer-events: none;
765
769
  }
766
- .fig-select-prepend:empty {
770
+ .fig-select-prepend:not(.has-content) {
771
+ display: none;
772
+ }
773
+ .fig-select-prepend > slot,
774
+ .fig-select-prepend-fallback,
775
+ slot[name="trigger"] {
776
+ display: contents;
777
+ }
778
+ slot[name="prepend-trigger"]::slotted([data-fig-select-generated]) {
779
+ display: contents;
780
+ }
781
+ .fig-select-label[hidden],
782
+ .fig-select-prepend[hidden],
783
+ slot[name="trigger"][hidden] {
767
784
  display: none;
768
785
  }
769
786
  /* Listbox chrome from document fig-select::part(listbox).
@@ -795,11 +812,20 @@ class FigSelect extends HTMLElement {
795
812
  prependEl.className = "fig-select-prepend";
796
813
  prependEl.setAttribute("part", "prepend");
797
814
  prependEl.setAttribute("aria-hidden", "true");
815
+ const prependSlot = document.createElement("slot");
816
+ prependSlot.setAttribute("name", "prepend-trigger");
817
+ prependSlot.hidden = true;
818
+ const prependFallback = document.createElement("span");
819
+ prependFallback.className = "fig-select-prepend-fallback";
820
+ prependEl.append(prependSlot, prependFallback);
798
821
 
799
822
  const labelEl = document.createElement("span");
800
823
  labelEl.className = "fig-select-label";
801
824
  labelEl.setAttribute("part", "label");
802
- button.append(prependEl, labelEl);
825
+ const customTriggerSlot = document.createElement("slot");
826
+ customTriggerSlot.setAttribute("name", "trigger");
827
+ customTriggerSlot.hidden = true;
828
+ button.append(prependEl, labelEl, customTriggerSlot);
803
829
 
804
830
  const popup = document.createElement("dialog", { is: "fig-popup" });
805
831
  popup.setAttribute("is", "fig-popup");
@@ -823,6 +849,9 @@ class FigSelect extends HTMLElement {
823
849
 
824
850
  this.#button = button;
825
851
  this.#prependEl = prependEl;
852
+ this.#prependSlot = prependSlot;
853
+ this.#prependFallback = prependFallback;
854
+ this.#customTriggerSlot = customTriggerSlot;
826
855
  this.#labelEl = labelEl;
827
856
  this.#popup = popup;
828
857
  this.#panelSlot = panelSlot;
@@ -871,8 +900,20 @@ class FigSelect extends HTMLElement {
871
900
  return { top: 8, right: 8, bottom: 8, left: 8 };
872
901
  }
873
902
 
903
+ #getTriggerAlignmentRect() {
904
+ const labelRect = this.#labelEl?.getBoundingClientRect();
905
+ if (labelRect?.width && labelRect?.height) return labelRect;
906
+ const customTriggerRect = this.querySelector(
907
+ ':scope > [slot="trigger"]',
908
+ )?.getBoundingClientRect();
909
+ if (customTriggerRect?.width && customTriggerRect?.height) {
910
+ return customTriggerRect;
911
+ }
912
+ return this.#prependEl?.getBoundingClientRect() ?? null;
913
+ }
914
+
874
915
  #readLabelRectSnapshot() {
875
- const rect = this.#labelEl?.getBoundingClientRect();
916
+ const rect = this.#getTriggerAlignmentRect();
876
917
  if (!rect) return null;
877
918
  return {
878
919
  x: rect.x,
@@ -959,7 +1000,7 @@ class FigSelect extends HTMLElement {
959
1000
  this.#originalPositionPopup?.();
960
1001
 
961
1002
  const popupRect = popup.getBoundingClientRect();
962
- const labelRect = label.getBoundingClientRect();
1003
+ const labelRect = this.#getTriggerAlignmentRect();
963
1004
  const optionTextRect = this.#getOptionTextRect(selected);
964
1005
  if (
965
1006
  !popupRect.width ||
@@ -1146,11 +1187,57 @@ class FigSelect extends HTMLElement {
1146
1187
  }
1147
1188
 
1148
1189
  #syncPrepend(option) {
1149
- if (!this.#prependEl) return;
1190
+ if (
1191
+ !this.#prependEl ||
1192
+ !this.#prependSlot ||
1193
+ !this.#prependFallback ||
1194
+ !this.#customTriggerSlot
1195
+ ) {
1196
+ return;
1197
+ }
1198
+ const customTrigger = this.querySelector(':scope > [slot="trigger"]');
1199
+ const generatedTrigger = this.querySelector(
1200
+ ':scope > [slot="prepend-trigger"][data-fig-select-generated]',
1201
+ );
1202
+ let triggerPrepend = this.querySelector(
1203
+ ':scope > [slot="prepend-trigger"]:not([data-fig-select-generated])',
1204
+ );
1150
1205
  const source = option?.querySelector?.(':scope > [slot="prepend"]');
1151
- this.#prependEl.replaceChildren(
1152
- ...Array.from(source?.childNodes ?? [], (node) => node.cloneNode(true)),
1206
+ this.#customTriggerSlot.hidden = !customTrigger;
1207
+ this.#labelEl.hidden = Boolean(customTrigger);
1208
+ this.#prependEl.hidden = Boolean(customTrigger);
1209
+ if (customTrigger) {
1210
+ generatedTrigger?.remove();
1211
+ this.#prependEl.classList.remove("has-content");
1212
+ this.#prependSlot.hidden = true;
1213
+ this.#prependFallback.replaceChildren();
1214
+ return;
1215
+ }
1216
+ if (!triggerPrepend && source?.childNodes.length) {
1217
+ const generated = generatedTrigger || document.createElement("span");
1218
+ generated.setAttribute("slot", "prepend-trigger");
1219
+ generated.setAttribute("data-fig-select-generated", "");
1220
+ generated.replaceChildren(
1221
+ ...Array.from(source.childNodes, (node) => node.cloneNode(true)),
1222
+ );
1223
+ if (generated.parentElement !== this) this.append(generated);
1224
+ triggerPrepend = generated;
1225
+ } else if (triggerPrepend) {
1226
+ generatedTrigger?.remove();
1227
+ } else {
1228
+ generatedTrigger?.remove();
1229
+ }
1230
+ const hasContent = Boolean(
1231
+ triggerPrepend,
1153
1232
  );
1233
+ this.#prependEl.classList.toggle("has-content", hasContent);
1234
+ this.#prependSlot.hidden = !triggerPrepend;
1235
+ this.#prependFallback.hidden = Boolean(triggerPrepend);
1236
+ if (triggerPrepend) {
1237
+ this.#prependFallback.replaceChildren();
1238
+ return;
1239
+ }
1240
+ this.#prependFallback.replaceChildren();
1154
1241
  }
1155
1242
 
1156
1243
  #syncPopupAttrs() {
@@ -1266,7 +1353,10 @@ class FigSelect extends HTMLElement {
1266
1353
  if (this.#labelEl) this.#labelEl.textContent = label;
1267
1354
  this.#syncPrepend(match);
1268
1355
 
1269
- const ariaLabel = this.getAttribute("label") || "Select";
1356
+ const ariaLabel =
1357
+ this.getAttribute("aria-label") ||
1358
+ this.getAttribute("label") ||
1359
+ "Select";
1270
1360
  this.#button?.setAttribute("aria-label", ariaLabel);
1271
1361
 
1272
1362
  // Don't scrollToOption while open — reposition/sync would fight overflow paging.