@rogieking/figui3 11.5.0 → 11.6.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.js CHANGED
@@ -10889,7 +10889,6 @@ class FigInputPalette extends HTMLElement {
10889
10889
  #renderRAF = null;
10890
10890
  #pickerDefinitionPromise = null;
10891
10891
  #pickerPopup = null;
10892
- #pickerEditor = null;
10893
10892
  #syncingValueAttribute = false;
10894
10893
  #boundHandleKeyDown = this.#handleKeyDown.bind(this);
10895
10894
  #boundHandleHostFocus = () => this.focus();
@@ -11168,13 +11167,13 @@ class FigInputPalette extends HTMLElement {
11168
11167
  #renderPickerWhenAvailable() {
11169
11168
  if (
11170
11169
  this.#editMode !== "picker" ||
11171
- customElements.get("fig-palette-editor")
11170
+ customElements.get("fig-popup-palette")
11172
11171
  ) {
11173
11172
  this.#syncPickerOpenState();
11174
11173
  return;
11175
11174
  }
11176
11175
  this.#pickerDefinitionPromise ??= customElements
11177
- .whenDefined("fig-palette-editor")
11176
+ .whenDefined("fig-popup-palette")
11178
11177
  .then(() => {
11179
11178
  if (!this.isConnected || this.#editMode !== "picker") return;
11180
11179
  this.#syncPickerOpenState();
@@ -11183,28 +11182,16 @@ class FigInputPalette extends HTMLElement {
11183
11182
 
11184
11183
  #ensurePickerPopup() {
11185
11184
  if (this.#pickerPopup?.isConnected) return this.#pickerPopup;
11186
- if (!customElements.get("fig-palette-editor")) return null;
11185
+ if (!customElements.get("fig-popup-palette")) return null;
11187
11186
 
11188
- const popup = document.createElement("dialog", { is: "fig-popup" });
11189
- popup.setAttribute("is", "fig-popup");
11190
- popup.setAttribute("aria-label", "Edit palette");
11191
- popup.setAttribute("position", "bottom left");
11192
- popup.setAttribute("offset", "0 8");
11193
- popup.setAttribute("viewport-margin", "8");
11194
- popup.setAttribute("popover", "manual");
11195
- popup.className = "fig-palette-editor-popup";
11187
+ const popup = document.createElement("dialog", {
11188
+ is: "fig-popup-palette",
11189
+ });
11190
+ popup.setAttribute("is", "fig-popup-palette");
11196
11191
  popup.id = figUniqueId();
11197
11192
  popup.anchor = this;
11198
11193
 
11199
- const editor = document.createElement("fig-palette-editor");
11200
- const content = figCreateElement(
11201
- "fig-content",
11202
- { className: "fig-palette-editor-content" },
11203
- editor,
11204
- );
11205
- popup.append(content);
11206
-
11207
- const handleEditorEvent = (event) => {
11194
+ const handlePopupEvent = (event) => {
11208
11195
  event.stopPropagation();
11209
11196
  if (!Array.isArray(event.detail)) return;
11210
11197
  this.#colors = event.detail.map((entry) => ({
@@ -11224,14 +11211,13 @@ class FigInputPalette extends HTMLElement {
11224
11211
  }),
11225
11212
  );
11226
11213
  };
11227
- editor.addEventListener("input", handleEditorEvent);
11228
- editor.addEventListener("change", handleEditorEvent);
11214
+ popup.addEventListener("input", handlePopupEvent);
11215
+ popup.addEventListener("change", handlePopupEvent);
11229
11216
  popup.addEventListener("close", this.#boundPickerClosed);
11230
11217
  popup.addEventListener("toggle", this.#boundPickerClosed);
11231
11218
 
11232
11219
  (figGetOverlayRoot() ?? document.body).append(popup);
11233
11220
  this.#pickerPopup = popup;
11234
- this.#pickerEditor = editor;
11235
11221
  this.querySelector(".palette-colors-inline")?.setAttribute(
11236
11222
  "aria-controls",
11237
11223
  popup.id,
@@ -11241,21 +11227,21 @@ class FigInputPalette extends HTMLElement {
11241
11227
  }
11242
11228
 
11243
11229
  #syncPickerEditor() {
11244
- const editor = this.#pickerEditor;
11245
- if (!editor) return;
11246
- editor.setAttribute("value", JSON.stringify(this.#colors));
11247
- editor.setAttribute("min", String(this.#min));
11248
- editor.setAttribute("max", String(this.#max));
11249
- editor.toggleAttribute("fixed", this.#isFixed);
11230
+ const popup = this.#pickerPopup;
11231
+ if (!popup) return;
11232
+ popup.setAttribute("value", JSON.stringify(this.#colors));
11233
+ popup.setAttribute("min", String(this.#min));
11234
+ popup.setAttribute("max", String(this.#max));
11235
+ popup.toggleAttribute("fixed", this.#isFixed);
11250
11236
  const disabled =
11251
11237
  this.hasAttribute("disabled") &&
11252
11238
  this.getAttribute("disabled") !== "false";
11253
- editor.toggleAttribute("disabled", disabled);
11239
+ popup.toggleAttribute("disabled", disabled);
11254
11240
  }
11255
11241
 
11256
11242
  #syncPickerOpenState() {
11257
11243
  if (!this.isConnected || this.#editMode !== "picker") return;
11258
- if (!customElements.get("fig-palette-editor")) {
11244
+ if (!customElements.get("fig-popup-palette")) {
11259
11245
  this.#renderPickerWhenAvailable();
11260
11246
  return;
11261
11247
  }
@@ -11286,7 +11272,6 @@ class FigInputPalette extends HTMLElement {
11286
11272
  "aria-controls",
11287
11273
  );
11288
11274
  this.#pickerPopup = null;
11289
- this.#pickerEditor = null;
11290
11275
  }
11291
11276
 
11292
11277
  #createPicker(entry, index, disabled, { inline = false } = {}) {
@@ -11545,10 +11530,11 @@ figDefineElement("fig-input-palette", FigInputPalette);
11545
11530
 
11546
11531
  /* Input Gradient */
11547
11532
  /**
11548
- * A gradient-only fill input built on top of fig-fill-picker.
11533
+ * A gradient-only fill input with inline and reusable popup editing modes.
11549
11534
  * @attr {string} value - JSON string with gradient fill data
11550
11535
  * @attr {string} size - Passed through to the internal fig-swatch (e.g. "large")
11551
11536
  * @attr {boolean} disabled - Whether the input is disabled
11537
+ * @attr {string} edit - `"true"` for inline stops, `"false"` for preview, or `"picker"` for fig-popup-gradient
11552
11538
  * @fires input - When the gradient value changes
11553
11539
  * @fires change - When the gradient value is committed
11554
11540
  */
@@ -11561,7 +11547,20 @@ class FigInputGradient extends HTMLElement {
11561
11547
  #colorObserver = null;
11562
11548
  #repositionRAF = null;
11563
11549
  #pickerDefinitionPromise = null;
11550
+ #pickerPopup = null;
11551
+ #syncingValueAttribute = false;
11564
11552
  anchorElement = null;
11553
+ #boundPickerClosed = () => {
11554
+ if (!this.#pickerPopup) return;
11555
+ if (
11556
+ this.#pickerPopup.hasAttribute("open") ||
11557
+ this.#pickerPopup.matches?.(":open") ||
11558
+ this.#pickerPopup.matches?.(":popover-open")
11559
+ ) {
11560
+ return;
11561
+ }
11562
+ this.classList.remove("has-popup-open");
11563
+ };
11565
11564
  #gradient = {
11566
11565
  type: "linear",
11567
11566
  angle: 90,
@@ -11660,15 +11659,22 @@ class FigInputGradient extends HTMLElement {
11660
11659
  this.#parseValue();
11661
11660
  this.#render();
11662
11661
  this.#renderPickerWhenAvailable();
11662
+ this.removeEventListener("click", this.#onPickerClick);
11663
+ this.addEventListener("click", this.#onPickerClick);
11663
11664
  this.removeEventListener("keydown", this.#onPickerKeyDown);
11664
11665
  this.addEventListener("keydown", this.#onPickerKeyDown);
11665
11666
  if (this.#isEditable) document.addEventListener("keydown", this.#onKeyDown);
11666
11667
  }
11667
11668
 
11668
11669
  #renderPickerWhenAvailable() {
11669
- if (this.#editMode !== "picker" || hasFigFillPicker()) return;
11670
+ if (
11671
+ this.#editMode !== "picker" ||
11672
+ customElements.get("fig-popup-gradient")
11673
+ ) {
11674
+ return;
11675
+ }
11670
11676
  this.#pickerDefinitionPromise ??= customElements
11671
- .whenDefined("fig-fill-picker")
11677
+ .whenDefined("fig-popup-gradient")
11672
11678
  .then(() => {
11673
11679
  if (!this.isConnected || this.#editMode !== "picker") return;
11674
11680
  this.#render();
@@ -11677,7 +11683,9 @@ class FigInputGradient extends HTMLElement {
11677
11683
 
11678
11684
  disconnectedCallback() {
11679
11685
  document.removeEventListener("keydown", this.#onKeyDown);
11686
+ this.removeEventListener("click", this.#onPickerClick);
11680
11687
  this.removeEventListener("keydown", this.#onPickerKeyDown);
11688
+ this.#destroyPickerPopup();
11681
11689
  if (this.#colorObserver) {
11682
11690
  this.#colorObserver.disconnect();
11683
11691
  this.#colorObserver = null;
@@ -11785,10 +11793,18 @@ class FigInputGradient extends HTMLElement {
11785
11793
  if (e.key !== "Enter" && e.key !== " ") return;
11786
11794
  if (e.altKey || e.ctrlKey || e.metaKey) return;
11787
11795
  if (figBooleanAttribute(this, "disabled")) return;
11788
- const picker = this.querySelector("fig-fill-picker");
11789
- if (!picker || typeof picker.open !== "function") return;
11790
11796
  e.preventDefault();
11791
- picker.open();
11797
+ this.#openPickerPopup();
11798
+ };
11799
+
11800
+ #onPickerClick = () => {
11801
+ if (
11802
+ this.#editMode !== "picker" ||
11803
+ figBooleanAttribute(this, "disabled")
11804
+ ) {
11805
+ return;
11806
+ }
11807
+ this.#openPickerPopup();
11792
11808
  };
11793
11809
 
11794
11810
  #parseValue() {
@@ -11900,25 +11916,16 @@ class FigInputGradient extends HTMLElement {
11900
11916
  disabled,
11901
11917
  });
11902
11918
 
11903
- if (mode === "picker" && hasFigFillPicker()) {
11904
- const gradientValue = JSON.stringify(this.value);
11905
- const picker = figCreateElement(
11906
- "fig-fill-picker",
11907
- {
11908
- mode: "gradient",
11909
- value: gradientValue,
11910
- disabled,
11911
- },
11912
- swatch,
11913
- );
11914
- this.replaceChildren(picker);
11919
+ if (mode === "picker") {
11920
+ this.setAttribute("aria-haspopup", "dialog");
11921
+ this.replaceChildren(swatch);
11915
11922
  this.#swatch = swatch;
11916
11923
  this.#track = null;
11917
- this.#setupPickerEvents();
11918
11924
  this.#syncFocusTarget();
11919
11925
  return;
11920
11926
  }
11921
11927
 
11928
+ this.removeAttribute("aria-haspopup");
11922
11929
  const editable = mode === "true" || mode === "picker";
11923
11930
  const track = editable
11924
11931
  ? figCreateElement(
@@ -11938,36 +11945,83 @@ class FigInputGradient extends HTMLElement {
11938
11945
  this.#syncFocusTarget();
11939
11946
  }
11940
11947
 
11941
- #setupPickerEvents() {
11942
- const picker = this.querySelector("fig-fill-picker");
11943
- if (!picker) return;
11944
- picker.anchorElement = this.anchorElement || this;
11948
+ #ensurePickerPopup() {
11949
+ if (this.#pickerPopup?.isConnected) return this.#pickerPopup;
11950
+ if (!customElements.get("fig-popup-gradient")) return null;
11945
11951
 
11946
- const syncFromPicker = (e) => {
11947
- e.stopPropagation();
11948
- const detail = e.detail;
11949
- if (!detail?.gradient) return;
11952
+ const popup = document.createElement("dialog", {
11953
+ is: "fig-popup-gradient",
11954
+ });
11955
+ popup.setAttribute("is", "fig-popup-gradient");
11956
+ popup.id = figUniqueId();
11957
+ popup.anchor = this.anchorElement || this;
11958
+
11959
+ const handlePopupEvent = (event) => {
11960
+ event.stopPropagation();
11961
+ if (!event.detail?.gradient) return;
11950
11962
  this.#gradient = this.#normalizeGradient({
11951
11963
  ...this.#gradient,
11952
- ...detail.gradient,
11964
+ ...event.detail.gradient,
11953
11965
  });
11954
- if (this.#editMode === "picker" && typeof detail.css === "string" && detail.css) {
11955
- this.#swatch?.setAttribute("background", detail.css);
11966
+ this.#syncingValueAttribute = true;
11967
+ this.setAttribute("value", JSON.stringify(this.value));
11968
+ this.#syncingValueAttribute = false;
11969
+ if (typeof event.detail.css === "string" && event.detail.css) {
11970
+ this.#swatch?.setAttribute("background", event.detail.css);
11956
11971
  this.#syncSwatchSize();
11957
- return;
11972
+ } else {
11973
+ this.#syncSwatch();
11958
11974
  }
11959
- this.#syncSwatch();
11975
+ this.dispatchEvent(
11976
+ new CustomEvent(event.type, {
11977
+ bubbles: true,
11978
+ composed: true,
11979
+ detail: this.value,
11980
+ }),
11981
+ );
11960
11982
  };
11983
+ popup.addEventListener("input", handlePopupEvent);
11984
+ popup.addEventListener("change", handlePopupEvent);
11985
+ popup.addEventListener("close", this.#boundPickerClosed);
11986
+ popup.addEventListener("toggle", this.#boundPickerClosed);
11961
11987
 
11962
- picker.addEventListener("input", (e) => {
11963
- syncFromPicker(e);
11964
- this.#emitInput();
11965
- });
11988
+ (figGetOverlayRoot() ?? document.body).append(popup);
11989
+ this.#pickerPopup = popup;
11990
+ this.setAttribute("aria-controls", popup.id);
11991
+ this.#syncPickerPopup();
11992
+ return popup;
11993
+ }
11966
11994
 
11967
- picker.addEventListener("change", (e) => {
11968
- syncFromPicker(e);
11969
- this.#emitChange();
11970
- });
11995
+ #syncPickerPopup() {
11996
+ if (!this.#pickerPopup) return;
11997
+ this.#pickerPopup.setAttribute("value", JSON.stringify(this.#gradient));
11998
+ this.#pickerPopup.toggleAttribute(
11999
+ "disabled",
12000
+ figBooleanAttribute(this, "disabled"),
12001
+ );
12002
+ this.#pickerPopup.anchor = this.anchorElement || this;
12003
+ }
12004
+
12005
+ #openPickerPopup() {
12006
+ const popup = this.#ensurePickerPopup();
12007
+ if (!popup) {
12008
+ this.#renderPickerWhenAvailable();
12009
+ return;
12010
+ }
12011
+ this.#syncPickerPopup();
12012
+ popup.open = true;
12013
+ this.classList.add("has-popup-open");
12014
+ }
12015
+
12016
+ #destroyPickerPopup() {
12017
+ if (!this.#pickerPopup) return;
12018
+ this.#pickerPopup.removeEventListener("close", this.#boundPickerClosed);
12019
+ this.#pickerPopup.removeEventListener("toggle", this.#boundPickerClosed);
12020
+ this.#pickerPopup.open = false;
12021
+ this.#pickerPopup.remove();
12022
+ this.#pickerPopup = null;
12023
+ this.removeAttribute("aria-controls");
12024
+ this.classList.remove("has-popup-open");
11971
12025
  }
11972
12026
 
11973
12027
  #sampleGradientColor(position) {
@@ -12487,17 +12541,20 @@ class FigInputGradient extends HTMLElement {
12487
12541
  }
12488
12542
 
12489
12543
  attributeChangedCallback(name, oldValue, newValue) {
12490
- if (oldValue === newValue) return;
12544
+ if (oldValue === newValue || this.#syncingValueAttribute) return;
12491
12545
  switch (name) {
12492
12546
  case "value":
12493
12547
  this.#parseValue();
12494
12548
  this.#syncSwatch();
12495
12549
  this.#syncHandles();
12550
+ this.#syncPickerPopup();
12496
12551
  break;
12497
12552
  case "disabled":
12498
12553
  this.#syncDisabled();
12554
+ this.#syncPickerPopup();
12499
12555
  break;
12500
12556
  case "edit":
12557
+ if (this.#editMode !== "picker") this.#destroyPickerPopup();
12501
12558
  this.#render();
12502
12559
  this.#renderPickerWhenAvailable();
12503
12560
  if (this.#isEditable) {
@@ -15955,7 +16012,7 @@ class FigInputFile extends HTMLElement {
15955
16012
  ),
15956
16013
  )
15957
16014
  ) {
15958
- return "sound";
16015
+ return "audio";
15959
16016
  }
15960
16017
  if (
15961
16018
  files.every((file) =>
@@ -15984,7 +16041,7 @@ class FigInputFile extends HTMLElement {
15984
16041
  FIG_INPUT_FILE_AUDIO_EXTENSIONS,
15985
16042
  )
15986
16043
  ) {
15987
- return "sound";
16044
+ return "audio";
15988
16045
  }
15989
16046
  if (
15990
16047
  this.#hasKnownExtension(
@@ -16143,11 +16200,13 @@ class FigInputFile extends HTMLElement {
16143
16200
  this.#audioIcon = null;
16144
16201
  this.#audioWaveform = null;
16145
16202
  this.#audioPlaying = false;
16203
+ this.removeAttribute("data-audio-playing");
16146
16204
  }
16147
16205
 
16148
16206
  #setAudioPlaybackUi(playing) {
16149
16207
  this.#audioPlaying = playing;
16150
16208
  this.#audioIcon?.setAttribute("name", playing ? "pause" : "play");
16209
+ this.toggleAttribute("data-audio-playing", playing);
16151
16210
  this.#uploadBtn?.toggleAttribute("selected", playing);
16152
16211
  this.#uploadBtn?.setAttribute(
16153
16212
  "aria-label",
@@ -16351,26 +16410,27 @@ class FigInputFile extends HTMLElement {
16351
16410
  ? this.#createMultipleFileIcon(this.#files.length)
16352
16411
  : showsImagePreview
16353
16412
  ? this.#createImagePreviewSwatch()
16354
- : createFigIcon(
16355
- showsAudioPreview ? "play" : this.#selectedFileIcon(),
16356
- );
16413
+ : createFigIcon(this.#selectedFileIcon());
16414
+ const truncEl = document.createElement("fig-truncate");
16415
+ truncEl.setAttribute("position", "middle");
16416
+ truncEl.setAttribute("tooltip", "");
16417
+ const buttonLabel = showsMultiplePreview
16418
+ ? "Multiple files"
16419
+ : filename;
16420
+ const dotIdx = buttonLabel.lastIndexOf(".");
16421
+ if (!showsMultiplePreview && dotIdx > 0) {
16422
+ truncEl.setAttribute("tail", buttonLabel.slice(dotIdx));
16423
+ }
16424
+ truncEl.textContent = buttonLabel;
16425
+ fileIcon.setAttribute("slot", "prepend");
16357
16426
  if (showsAudioPreview) {
16358
- this.#audioIcon = fileIcon;
16359
- fileIcon.setAttribute("slot", "prepend");
16360
- this.#uploadBtn.append(fileIcon);
16427
+ fileIcon.classList.add("fig-input-file-audio-icon");
16428
+ truncEl.classList.add("fig-input-file-audio-label");
16429
+ this.#audioIcon = createFigIcon("play");
16430
+ this.#audioIcon.classList.add("fig-input-file-audio-control");
16431
+ this.#audioIcon.setAttribute("slot", "prepend");
16432
+ this.#uploadBtn.append(fileIcon, this.#audioIcon, truncEl);
16361
16433
  } else {
16362
- const truncEl = document.createElement("fig-truncate");
16363
- truncEl.setAttribute("position", "middle");
16364
- truncEl.setAttribute("tooltip", "");
16365
- const buttonLabel = showsMultiplePreview
16366
- ? "Multiple files"
16367
- : filename;
16368
- const dotIdx = buttonLabel.lastIndexOf(".");
16369
- if (!showsMultiplePreview && dotIdx > 0) {
16370
- truncEl.setAttribute("tail", buttonLabel.slice(dotIdx));
16371
- }
16372
- truncEl.textContent = buttonLabel;
16373
- fileIcon.setAttribute("slot", "prepend");
16374
16434
  this.#uploadBtn.append(fileIcon, truncEl);
16375
16435
  }
16376
16436
 
@@ -16489,6 +16549,8 @@ class FigEasingCurve extends HTMLElement {
16489
16549
  #durationBarWidth = 10;
16490
16550
  #durationBarHeight = 10;
16491
16551
  #durationBarRadius = 3;
16552
+ #pickerDefinitionPromise = null;
16553
+ #pickerPopup = null;
16492
16554
 
16493
16555
  static PRESETS = [
16494
16556
  { group: null, name: "Linear", type: "bezier", value: [0, 0, 1, 1] },
@@ -16587,6 +16649,7 @@ class FigEasingCurve extends HTMLElement {
16587
16649
  this.#resizeObserver.disconnect();
16588
16650
  this.#resizeObserver = null;
16589
16651
  }
16652
+ this.#destroyPickerPopup();
16590
16653
  }
16591
16654
 
16592
16655
  attributeChangedCallback(name, oldValue, newValue) {
@@ -16602,6 +16665,9 @@ class FigEasingCurve extends HTMLElement {
16602
16665
  }
16603
16666
 
16604
16667
  if (name === "edit" || name === "presets" || name === "text") {
16668
+ if (name === "edit" && newValue !== "false") {
16669
+ this.#destroyPickerPopup();
16670
+ }
16605
16671
  if (this.isConnected) this.#render();
16606
16672
  return;
16607
16673
  }
@@ -16613,6 +16679,7 @@ class FigEasingCurve extends HTMLElement {
16613
16679
  this.#applyModeConstraint();
16614
16680
  this.#presetName = this.#matchPreset();
16615
16681
  if (this.isConnected) this.#render();
16682
+ this.#syncPickerPopup();
16616
16683
  return;
16617
16684
  }
16618
16685
 
@@ -16631,6 +16698,9 @@ class FigEasingCurve extends HTMLElement {
16631
16698
  if (name === "precision") {
16632
16699
  this.#precision = parseInt(newValue || "2");
16633
16700
  }
16701
+ if (name === "value" || name === "mode" || name === "precision") {
16702
+ this.#syncPickerPopup();
16703
+ }
16634
16704
  }
16635
16705
 
16636
16706
  get value() {
@@ -17092,7 +17162,14 @@ class FigEasingCurve extends HTMLElement {
17092
17162
  let parent = dropdown;
17093
17163
  for (const preset of FigEasingCurve.PRESETS) {
17094
17164
  if (!this.#allowsPreset(preset)) continue;
17095
- if (!this.#isEditEnabled() && !preset.value && !preset.spring) continue;
17165
+ if (
17166
+ !this.#isEditEnabled() &&
17167
+ !customElements.get("fig-popup-easing") &&
17168
+ !preset.value &&
17169
+ !preset.spring
17170
+ ) {
17171
+ continue;
17172
+ }
17096
17173
  if (preset.group !== currentGroup) {
17097
17174
  currentGroup = preset.group;
17098
17175
  parent = currentGroup
@@ -17114,7 +17191,14 @@ class FigEasingCurve extends HTMLElement {
17114
17191
  let currentGroup = undefined;
17115
17192
  for (const p of FigEasingCurve.PRESETS) {
17116
17193
  if (!this.#allowsPreset(p)) continue;
17117
- if (!this.#isEditEnabled() && !p.value && !p.spring) continue;
17194
+ if (
17195
+ !this.#isEditEnabled() &&
17196
+ !customElements.get("fig-popup-easing") &&
17197
+ !p.value &&
17198
+ !p.spring
17199
+ ) {
17200
+ continue;
17201
+ }
17118
17202
  if (p.group !== currentGroup) {
17119
17203
  if (p.group) {
17120
17204
  options.appendChild(
@@ -17733,7 +17817,12 @@ class FigEasingCurve extends HTMLElement {
17733
17817
 
17734
17818
  #refreshCustomPresetIcons() {
17735
17819
  if (!this.#select) return;
17736
- if (!this.#isEditEnabled()) return;
17820
+ if (
17821
+ !this.#isEditEnabled() &&
17822
+ !customElements.get("fig-popup-easing")
17823
+ ) {
17824
+ return;
17825
+ }
17737
17826
  const bezierIcon = FigEasingCurve.#createCurveIcon(
17738
17827
  this.#cp1.x,
17739
17828
  this.#cp1.y,
@@ -17942,6 +18031,22 @@ class FigEasingCurve extends HTMLElement {
17942
18031
  }
17943
18032
 
17944
18033
  if (this.#select) {
18034
+ if (!this.#isEditEnabled() && this.#select.tagName === "FIG-SELECT") {
18035
+ const openPicker = (event) => {
18036
+ if (!customElements.get("fig-popup-easing")) return;
18037
+ event.preventDefault();
18038
+ event.stopImmediatePropagation();
18039
+ this.#openPickerPopup();
18040
+ };
18041
+ this.#select.addEventListener("click", openPicker, true);
18042
+ this.#select.addEventListener(
18043
+ "keydown",
18044
+ (event) => {
18045
+ if (event.key === "Enter" || event.key === " ") openPicker(event);
18046
+ },
18047
+ true,
18048
+ );
18049
+ }
17945
18050
  this.#select.addEventListener("input", (e) => {
17946
18051
  e.stopPropagation();
17947
18052
  });
@@ -18009,6 +18114,59 @@ class FigEasingCurve extends HTMLElement {
18009
18114
  }
18010
18115
  }
18011
18116
 
18117
+ #openPickerPopup() {
18118
+ if (!customElements.get("fig-popup-easing")) {
18119
+ this.#pickerDefinitionPromise ??= customElements
18120
+ .whenDefined("fig-popup-easing")
18121
+ .then(() => {
18122
+ if (!this.isConnected || this.#isEditEnabled()) return;
18123
+ this.#openPickerPopup();
18124
+ });
18125
+ return;
18126
+ }
18127
+
18128
+ let popup = this.#pickerPopup;
18129
+ if (!popup?.isConnected) {
18130
+ popup = document.createElement("dialog", { is: "fig-popup-easing" });
18131
+ popup.setAttribute("is", "fig-popup-easing");
18132
+ popup.id = figUniqueId();
18133
+ popup.anchor = this.#select;
18134
+ const handlePopupEvent = (event) => {
18135
+ event.stopPropagation();
18136
+ this.value = popup.value;
18137
+ this.#emit(event.type);
18138
+ };
18139
+ popup.addEventListener("input", handlePopupEvent);
18140
+ popup.addEventListener("change", handlePopupEvent);
18141
+ (figGetOverlayRoot() ?? document.body).append(popup);
18142
+ this.#pickerPopup = popup;
18143
+ this.#select?.setAttribute("aria-controls", popup.id);
18144
+ }
18145
+
18146
+ this.#syncPickerPopup();
18147
+ popup.open = true;
18148
+ }
18149
+
18150
+ #syncPickerPopup() {
18151
+ const popup = this.#pickerPopup;
18152
+ if (!popup) return;
18153
+ popup.anchor = this.#select;
18154
+ popup.setAttribute("value", this.value);
18155
+ for (const name of ["mode", "precision"]) {
18156
+ const value = this.getAttribute(name);
18157
+ if (value === null) popup.removeAttribute(name);
18158
+ else popup.setAttribute(name, value);
18159
+ }
18160
+ }
18161
+
18162
+ #destroyPickerPopup() {
18163
+ if (!this.#pickerPopup) return;
18164
+ this.#pickerPopup.open = false;
18165
+ this.#pickerPopup.remove();
18166
+ this.#select?.removeAttribute("aria-controls");
18167
+ this.#pickerPopup = null;
18168
+ }
18169
+
18012
18170
  #clientToSVG(e) {
18013
18171
  const ctm = this.#svg.getScreenCTM();
18014
18172
  if (!ctm) return { x: 0, y: 0 };
@@ -20783,6 +20941,7 @@ const FIG_ICON_TOKENS = {
20783
20941
  upload: "--icon-24-upload",
20784
20942
  image: "--icon-24-image",
20785
20943
  video: { medium: "--icon-24-video", small: "--icon-16-video" },
20944
+ audio: "--icon-24-audio",
20786
20945
  sound: { medium: "--icon-24-sound", small: "--icon-16-sound" },
20787
20946
  "sound-off": {
20788
20947
  medium: "--icon-24-sound-off",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "11.5.0",
3
+ "version": "11.6.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": "SEE LICENSE IN LICENSE",
@@ -38,11 +38,19 @@
38
38
  "fig.js",
39
39
  "fig-layer.js",
40
40
  "fig-property.js",
41
+ "fig-gradient-editor.js",
42
+ "fig-popup-easing.js",
43
+ "fig-popup-gradient.js",
44
+ "fig-popup-palette.js",
41
45
  "fig-editor.js",
42
46
  "fig-lab.js",
43
47
  "fig.css",
44
48
  "fig-layer.css",
45
49
  "fig-property.css",
50
+ "fig-gradient-editor.css",
51
+ "fig-popup-easing.css",
52
+ "fig-popup-gradient.css",
53
+ "fig-popup-palette.css",
46
54
  "fig-editor.css",
47
55
  "fig-lab.css",
48
56
  "base.css",