@rogieking/figui3 11.4.2 → 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
@@ -23,6 +23,7 @@ function createFigIcon(name, options = {}) {
23
23
  const icon = document.createElement("fig-icon");
24
24
  if (name) icon.setAttribute("name", name);
25
25
  if (options.size) icon.setAttribute("size", options.size);
26
+ if (options.slot) icon.setAttribute("slot", options.slot);
26
27
  if (options.className) icon.className = options.className;
27
28
  return icon;
28
29
  }
@@ -8684,7 +8685,11 @@ class FigField extends HTMLElement {
8684
8685
  this.input = Array.from(this.childNodes).find((node) =>
8685
8686
  node.nodeType === Node.ELEMENT_NODE &&
8686
8687
  node.nodeName.toLowerCase().startsWith("fig-") &&
8687
- !(node instanceof Element && node.classList.contains("fig-field-chevron")),
8688
+ !(
8689
+ node instanceof Element &&
8690
+ (node.classList.contains("fig-field-chevron") ||
8691
+ ["leading", "trailing"].includes(node.getAttribute("slot")))
8692
+ ),
8688
8693
  );
8689
8694
  this.#observeInputAttributes();
8690
8695
 
@@ -8714,6 +8719,7 @@ class FigField extends HTMLElement {
8714
8719
  if (!this.#chevron || !this.#chevron.isConnected) {
8715
8720
  this.#chevron = createFigIcon("chevron", {
8716
8721
  size: "small",
8722
+ slot: "leading",
8717
8723
  className: "fig-field-chevron",
8718
8724
  });
8719
8725
  this.insertBefore(this.#chevron, this.label);
@@ -10883,7 +10889,6 @@ class FigInputPalette extends HTMLElement {
10883
10889
  #renderRAF = null;
10884
10890
  #pickerDefinitionPromise = null;
10885
10891
  #pickerPopup = null;
10886
- #pickerEditor = null;
10887
10892
  #syncingValueAttribute = false;
10888
10893
  #boundHandleKeyDown = this.#handleKeyDown.bind(this);
10889
10894
  #boundHandleHostFocus = () => this.focus();
@@ -11162,13 +11167,13 @@ class FigInputPalette extends HTMLElement {
11162
11167
  #renderPickerWhenAvailable() {
11163
11168
  if (
11164
11169
  this.#editMode !== "picker" ||
11165
- customElements.get("fig-palette-editor")
11170
+ customElements.get("fig-popup-palette")
11166
11171
  ) {
11167
11172
  this.#syncPickerOpenState();
11168
11173
  return;
11169
11174
  }
11170
11175
  this.#pickerDefinitionPromise ??= customElements
11171
- .whenDefined("fig-palette-editor")
11176
+ .whenDefined("fig-popup-palette")
11172
11177
  .then(() => {
11173
11178
  if (!this.isConnected || this.#editMode !== "picker") return;
11174
11179
  this.#syncPickerOpenState();
@@ -11177,28 +11182,16 @@ class FigInputPalette extends HTMLElement {
11177
11182
 
11178
11183
  #ensurePickerPopup() {
11179
11184
  if (this.#pickerPopup?.isConnected) return this.#pickerPopup;
11180
- if (!customElements.get("fig-palette-editor")) return null;
11185
+ if (!customElements.get("fig-popup-palette")) return null;
11181
11186
 
11182
- const popup = document.createElement("dialog", { is: "fig-popup" });
11183
- popup.setAttribute("is", "fig-popup");
11184
- popup.setAttribute("aria-label", "Edit palette");
11185
- popup.setAttribute("position", "bottom left");
11186
- popup.setAttribute("offset", "0 8");
11187
- popup.setAttribute("viewport-margin", "8");
11188
- popup.setAttribute("popover", "manual");
11189
- 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");
11190
11191
  popup.id = figUniqueId();
11191
11192
  popup.anchor = this;
11192
11193
 
11193
- const editor = document.createElement("fig-palette-editor");
11194
- const content = figCreateElement(
11195
- "fig-content",
11196
- { className: "fig-palette-editor-content" },
11197
- editor,
11198
- );
11199
- popup.append(content);
11200
-
11201
- const handleEditorEvent = (event) => {
11194
+ const handlePopupEvent = (event) => {
11202
11195
  event.stopPropagation();
11203
11196
  if (!Array.isArray(event.detail)) return;
11204
11197
  this.#colors = event.detail.map((entry) => ({
@@ -11218,14 +11211,13 @@ class FigInputPalette extends HTMLElement {
11218
11211
  }),
11219
11212
  );
11220
11213
  };
11221
- editor.addEventListener("input", handleEditorEvent);
11222
- editor.addEventListener("change", handleEditorEvent);
11214
+ popup.addEventListener("input", handlePopupEvent);
11215
+ popup.addEventListener("change", handlePopupEvent);
11223
11216
  popup.addEventListener("close", this.#boundPickerClosed);
11224
11217
  popup.addEventListener("toggle", this.#boundPickerClosed);
11225
11218
 
11226
11219
  (figGetOverlayRoot() ?? document.body).append(popup);
11227
11220
  this.#pickerPopup = popup;
11228
- this.#pickerEditor = editor;
11229
11221
  this.querySelector(".palette-colors-inline")?.setAttribute(
11230
11222
  "aria-controls",
11231
11223
  popup.id,
@@ -11235,21 +11227,21 @@ class FigInputPalette extends HTMLElement {
11235
11227
  }
11236
11228
 
11237
11229
  #syncPickerEditor() {
11238
- const editor = this.#pickerEditor;
11239
- if (!editor) return;
11240
- editor.setAttribute("value", JSON.stringify(this.#colors));
11241
- editor.setAttribute("min", String(this.#min));
11242
- editor.setAttribute("max", String(this.#max));
11243
- 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);
11244
11236
  const disabled =
11245
11237
  this.hasAttribute("disabled") &&
11246
11238
  this.getAttribute("disabled") !== "false";
11247
- editor.toggleAttribute("disabled", disabled);
11239
+ popup.toggleAttribute("disabled", disabled);
11248
11240
  }
11249
11241
 
11250
11242
  #syncPickerOpenState() {
11251
11243
  if (!this.isConnected || this.#editMode !== "picker") return;
11252
- if (!customElements.get("fig-palette-editor")) {
11244
+ if (!customElements.get("fig-popup-palette")) {
11253
11245
  this.#renderPickerWhenAvailable();
11254
11246
  return;
11255
11247
  }
@@ -11280,7 +11272,6 @@ class FigInputPalette extends HTMLElement {
11280
11272
  "aria-controls",
11281
11273
  );
11282
11274
  this.#pickerPopup = null;
11283
- this.#pickerEditor = null;
11284
11275
  }
11285
11276
 
11286
11277
  #createPicker(entry, index, disabled, { inline = false } = {}) {
@@ -11539,10 +11530,11 @@ figDefineElement("fig-input-palette", FigInputPalette);
11539
11530
 
11540
11531
  /* Input Gradient */
11541
11532
  /**
11542
- * 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.
11543
11534
  * @attr {string} value - JSON string with gradient fill data
11544
11535
  * @attr {string} size - Passed through to the internal fig-swatch (e.g. "large")
11545
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
11546
11538
  * @fires input - When the gradient value changes
11547
11539
  * @fires change - When the gradient value is committed
11548
11540
  */
@@ -11555,7 +11547,20 @@ class FigInputGradient extends HTMLElement {
11555
11547
  #colorObserver = null;
11556
11548
  #repositionRAF = null;
11557
11549
  #pickerDefinitionPromise = null;
11550
+ #pickerPopup = null;
11551
+ #syncingValueAttribute = false;
11558
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
+ };
11559
11564
  #gradient = {
11560
11565
  type: "linear",
11561
11566
  angle: 90,
@@ -11654,15 +11659,22 @@ class FigInputGradient extends HTMLElement {
11654
11659
  this.#parseValue();
11655
11660
  this.#render();
11656
11661
  this.#renderPickerWhenAvailable();
11662
+ this.removeEventListener("click", this.#onPickerClick);
11663
+ this.addEventListener("click", this.#onPickerClick);
11657
11664
  this.removeEventListener("keydown", this.#onPickerKeyDown);
11658
11665
  this.addEventListener("keydown", this.#onPickerKeyDown);
11659
11666
  if (this.#isEditable) document.addEventListener("keydown", this.#onKeyDown);
11660
11667
  }
11661
11668
 
11662
11669
  #renderPickerWhenAvailable() {
11663
- if (this.#editMode !== "picker" || hasFigFillPicker()) return;
11670
+ if (
11671
+ this.#editMode !== "picker" ||
11672
+ customElements.get("fig-popup-gradient")
11673
+ ) {
11674
+ return;
11675
+ }
11664
11676
  this.#pickerDefinitionPromise ??= customElements
11665
- .whenDefined("fig-fill-picker")
11677
+ .whenDefined("fig-popup-gradient")
11666
11678
  .then(() => {
11667
11679
  if (!this.isConnected || this.#editMode !== "picker") return;
11668
11680
  this.#render();
@@ -11671,7 +11683,9 @@ class FigInputGradient extends HTMLElement {
11671
11683
 
11672
11684
  disconnectedCallback() {
11673
11685
  document.removeEventListener("keydown", this.#onKeyDown);
11686
+ this.removeEventListener("click", this.#onPickerClick);
11674
11687
  this.removeEventListener("keydown", this.#onPickerKeyDown);
11688
+ this.#destroyPickerPopup();
11675
11689
  if (this.#colorObserver) {
11676
11690
  this.#colorObserver.disconnect();
11677
11691
  this.#colorObserver = null;
@@ -11779,10 +11793,18 @@ class FigInputGradient extends HTMLElement {
11779
11793
  if (e.key !== "Enter" && e.key !== " ") return;
11780
11794
  if (e.altKey || e.ctrlKey || e.metaKey) return;
11781
11795
  if (figBooleanAttribute(this, "disabled")) return;
11782
- const picker = this.querySelector("fig-fill-picker");
11783
- if (!picker || typeof picker.open !== "function") return;
11784
11796
  e.preventDefault();
11785
- 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();
11786
11808
  };
11787
11809
 
11788
11810
  #parseValue() {
@@ -11894,25 +11916,16 @@ class FigInputGradient extends HTMLElement {
11894
11916
  disabled,
11895
11917
  });
11896
11918
 
11897
- if (mode === "picker" && hasFigFillPicker()) {
11898
- const gradientValue = JSON.stringify(this.value);
11899
- const picker = figCreateElement(
11900
- "fig-fill-picker",
11901
- {
11902
- mode: "gradient",
11903
- value: gradientValue,
11904
- disabled,
11905
- },
11906
- swatch,
11907
- );
11908
- this.replaceChildren(picker);
11919
+ if (mode === "picker") {
11920
+ this.setAttribute("aria-haspopup", "dialog");
11921
+ this.replaceChildren(swatch);
11909
11922
  this.#swatch = swatch;
11910
11923
  this.#track = null;
11911
- this.#setupPickerEvents();
11912
11924
  this.#syncFocusTarget();
11913
11925
  return;
11914
11926
  }
11915
11927
 
11928
+ this.removeAttribute("aria-haspopup");
11916
11929
  const editable = mode === "true" || mode === "picker";
11917
11930
  const track = editable
11918
11931
  ? figCreateElement(
@@ -11932,36 +11945,83 @@ class FigInputGradient extends HTMLElement {
11932
11945
  this.#syncFocusTarget();
11933
11946
  }
11934
11947
 
11935
- #setupPickerEvents() {
11936
- const picker = this.querySelector("fig-fill-picker");
11937
- if (!picker) return;
11938
- picker.anchorElement = this.anchorElement || this;
11948
+ #ensurePickerPopup() {
11949
+ if (this.#pickerPopup?.isConnected) return this.#pickerPopup;
11950
+ if (!customElements.get("fig-popup-gradient")) return null;
11939
11951
 
11940
- const syncFromPicker = (e) => {
11941
- e.stopPropagation();
11942
- const detail = e.detail;
11943
- 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;
11944
11962
  this.#gradient = this.#normalizeGradient({
11945
11963
  ...this.#gradient,
11946
- ...detail.gradient,
11964
+ ...event.detail.gradient,
11947
11965
  });
11948
- if (this.#editMode === "picker" && typeof detail.css === "string" && detail.css) {
11949
- 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);
11950
11971
  this.#syncSwatchSize();
11951
- return;
11972
+ } else {
11973
+ this.#syncSwatch();
11952
11974
  }
11953
- this.#syncSwatch();
11975
+ this.dispatchEvent(
11976
+ new CustomEvent(event.type, {
11977
+ bubbles: true,
11978
+ composed: true,
11979
+ detail: this.value,
11980
+ }),
11981
+ );
11954
11982
  };
11983
+ popup.addEventListener("input", handlePopupEvent);
11984
+ popup.addEventListener("change", handlePopupEvent);
11985
+ popup.addEventListener("close", this.#boundPickerClosed);
11986
+ popup.addEventListener("toggle", this.#boundPickerClosed);
11955
11987
 
11956
- picker.addEventListener("input", (e) => {
11957
- syncFromPicker(e);
11958
- this.#emitInput();
11959
- });
11988
+ (figGetOverlayRoot() ?? document.body).append(popup);
11989
+ this.#pickerPopup = popup;
11990
+ this.setAttribute("aria-controls", popup.id);
11991
+ this.#syncPickerPopup();
11992
+ return popup;
11993
+ }
11960
11994
 
11961
- picker.addEventListener("change", (e) => {
11962
- syncFromPicker(e);
11963
- this.#emitChange();
11964
- });
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");
11965
12025
  }
11966
12026
 
11967
12027
  #sampleGradientColor(position) {
@@ -12481,17 +12541,20 @@ class FigInputGradient extends HTMLElement {
12481
12541
  }
12482
12542
 
12483
12543
  attributeChangedCallback(name, oldValue, newValue) {
12484
- if (oldValue === newValue) return;
12544
+ if (oldValue === newValue || this.#syncingValueAttribute) return;
12485
12545
  switch (name) {
12486
12546
  case "value":
12487
12547
  this.#parseValue();
12488
12548
  this.#syncSwatch();
12489
12549
  this.#syncHandles();
12550
+ this.#syncPickerPopup();
12490
12551
  break;
12491
12552
  case "disabled":
12492
12553
  this.#syncDisabled();
12554
+ this.#syncPickerPopup();
12493
12555
  break;
12494
12556
  case "edit":
12557
+ if (this.#editMode !== "picker") this.#destroyPickerPopup();
12495
12558
  this.#render();
12496
12559
  this.#renderPickerWhenAvailable();
12497
12560
  if (this.#isEditable) {
@@ -15949,7 +16012,7 @@ class FigInputFile extends HTMLElement {
15949
16012
  ),
15950
16013
  )
15951
16014
  ) {
15952
- return "sound";
16015
+ return "audio";
15953
16016
  }
15954
16017
  if (
15955
16018
  files.every((file) =>
@@ -15978,7 +16041,7 @@ class FigInputFile extends HTMLElement {
15978
16041
  FIG_INPUT_FILE_AUDIO_EXTENSIONS,
15979
16042
  )
15980
16043
  ) {
15981
- return "sound";
16044
+ return "audio";
15982
16045
  }
15983
16046
  if (
15984
16047
  this.#hasKnownExtension(
@@ -16137,11 +16200,13 @@ class FigInputFile extends HTMLElement {
16137
16200
  this.#audioIcon = null;
16138
16201
  this.#audioWaveform = null;
16139
16202
  this.#audioPlaying = false;
16203
+ this.removeAttribute("data-audio-playing");
16140
16204
  }
16141
16205
 
16142
16206
  #setAudioPlaybackUi(playing) {
16143
16207
  this.#audioPlaying = playing;
16144
16208
  this.#audioIcon?.setAttribute("name", playing ? "pause" : "play");
16209
+ this.toggleAttribute("data-audio-playing", playing);
16145
16210
  this.#uploadBtn?.toggleAttribute("selected", playing);
16146
16211
  this.#uploadBtn?.setAttribute(
16147
16212
  "aria-label",
@@ -16345,26 +16410,27 @@ class FigInputFile extends HTMLElement {
16345
16410
  ? this.#createMultipleFileIcon(this.#files.length)
16346
16411
  : showsImagePreview
16347
16412
  ? this.#createImagePreviewSwatch()
16348
- : createFigIcon(
16349
- showsAudioPreview ? "play" : this.#selectedFileIcon(),
16350
- );
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");
16351
16426
  if (showsAudioPreview) {
16352
- this.#audioIcon = fileIcon;
16353
- fileIcon.setAttribute("slot", "prepend");
16354
- 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);
16355
16433
  } else {
16356
- const truncEl = document.createElement("fig-truncate");
16357
- truncEl.setAttribute("position", "middle");
16358
- truncEl.setAttribute("tooltip", "");
16359
- const buttonLabel = showsMultiplePreview
16360
- ? "Multiple files"
16361
- : filename;
16362
- const dotIdx = buttonLabel.lastIndexOf(".");
16363
- if (!showsMultiplePreview && dotIdx > 0) {
16364
- truncEl.setAttribute("tail", buttonLabel.slice(dotIdx));
16365
- }
16366
- truncEl.textContent = buttonLabel;
16367
- fileIcon.setAttribute("slot", "prepend");
16368
16434
  this.#uploadBtn.append(fileIcon, truncEl);
16369
16435
  }
16370
16436
 
@@ -16483,6 +16549,8 @@ class FigEasingCurve extends HTMLElement {
16483
16549
  #durationBarWidth = 10;
16484
16550
  #durationBarHeight = 10;
16485
16551
  #durationBarRadius = 3;
16552
+ #pickerDefinitionPromise = null;
16553
+ #pickerPopup = null;
16486
16554
 
16487
16555
  static PRESETS = [
16488
16556
  { group: null, name: "Linear", type: "bezier", value: [0, 0, 1, 1] },
@@ -16581,6 +16649,7 @@ class FigEasingCurve extends HTMLElement {
16581
16649
  this.#resizeObserver.disconnect();
16582
16650
  this.#resizeObserver = null;
16583
16651
  }
16652
+ this.#destroyPickerPopup();
16584
16653
  }
16585
16654
 
16586
16655
  attributeChangedCallback(name, oldValue, newValue) {
@@ -16596,6 +16665,9 @@ class FigEasingCurve extends HTMLElement {
16596
16665
  }
16597
16666
 
16598
16667
  if (name === "edit" || name === "presets" || name === "text") {
16668
+ if (name === "edit" && newValue !== "false") {
16669
+ this.#destroyPickerPopup();
16670
+ }
16599
16671
  if (this.isConnected) this.#render();
16600
16672
  return;
16601
16673
  }
@@ -16607,6 +16679,7 @@ class FigEasingCurve extends HTMLElement {
16607
16679
  this.#applyModeConstraint();
16608
16680
  this.#presetName = this.#matchPreset();
16609
16681
  if (this.isConnected) this.#render();
16682
+ this.#syncPickerPopup();
16610
16683
  return;
16611
16684
  }
16612
16685
 
@@ -16625,6 +16698,9 @@ class FigEasingCurve extends HTMLElement {
16625
16698
  if (name === "precision") {
16626
16699
  this.#precision = parseInt(newValue || "2");
16627
16700
  }
16701
+ if (name === "value" || name === "mode" || name === "precision") {
16702
+ this.#syncPickerPopup();
16703
+ }
16628
16704
  }
16629
16705
 
16630
16706
  get value() {
@@ -17086,7 +17162,14 @@ class FigEasingCurve extends HTMLElement {
17086
17162
  let parent = dropdown;
17087
17163
  for (const preset of FigEasingCurve.PRESETS) {
17088
17164
  if (!this.#allowsPreset(preset)) continue;
17089
- 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
+ }
17090
17173
  if (preset.group !== currentGroup) {
17091
17174
  currentGroup = preset.group;
17092
17175
  parent = currentGroup
@@ -17108,7 +17191,14 @@ class FigEasingCurve extends HTMLElement {
17108
17191
  let currentGroup = undefined;
17109
17192
  for (const p of FigEasingCurve.PRESETS) {
17110
17193
  if (!this.#allowsPreset(p)) continue;
17111
- 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
+ }
17112
17202
  if (p.group !== currentGroup) {
17113
17203
  if (p.group) {
17114
17204
  options.appendChild(
@@ -17138,7 +17228,7 @@ class FigEasingCurve extends HTMLElement {
17138
17228
  label: p.name,
17139
17229
  },
17140
17230
  [
17141
- figCreateElement("span", { slot: "prepend" }, icon),
17231
+ figCreateElement("fig-icon", { slot: "prepend" }, icon),
17142
17232
  figCreateElement("span", {}, p.name),
17143
17233
  ],
17144
17234
  ),
@@ -17727,7 +17817,12 @@ class FigEasingCurve extends HTMLElement {
17727
17817
 
17728
17818
  #refreshCustomPresetIcons() {
17729
17819
  if (!this.#select) return;
17730
- if (!this.#isEditEnabled()) return;
17820
+ if (
17821
+ !this.#isEditEnabled() &&
17822
+ !customElements.get("fig-popup-easing")
17823
+ ) {
17824
+ return;
17825
+ }
17731
17826
  const bezierIcon = FigEasingCurve.#createCurveIcon(
17732
17827
  this.#cp1.x,
17733
17828
  this.#cp1.y,
@@ -17936,6 +18031,22 @@ class FigEasingCurve extends HTMLElement {
17936
18031
  }
17937
18032
 
17938
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
+ }
17939
18050
  this.#select.addEventListener("input", (e) => {
17940
18051
  e.stopPropagation();
17941
18052
  });
@@ -18003,6 +18114,59 @@ class FigEasingCurve extends HTMLElement {
18003
18114
  }
18004
18115
  }
18005
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
+
18006
18170
  #clientToSVG(e) {
18007
18171
  const ctm = this.#svg.getScreenCTM();
18008
18172
  if (!ctm) return { x: 0, y: 0 };
@@ -20777,6 +20941,7 @@ const FIG_ICON_TOKENS = {
20777
20941
  upload: "--icon-24-upload",
20778
20942
  image: "--icon-24-image",
20779
20943
  video: { medium: "--icon-24-video", small: "--icon-16-video" },
20944
+ audio: "--icon-24-audio",
20780
20945
  sound: { medium: "--icon-24-sound", small: "--icon-16-sound" },
20781
20946
  "sound-off": {
20782
20947
  medium: "--icon-24-sound-off",
@@ -20793,6 +20958,7 @@ const FIG_ICON_TOKENS = {
20793
20958
  warning: { medium: "--icon-24-warning", small: "--icon-16-warning" },
20794
20959
  copy: { medium: "--icon-24-copy", small: "--icon-16-copy" },
20795
20960
  trash: { medium: "--icon-24-trash", small: "--icon-16-trash" },
20961
+ "drag-handle": "--icon-16-drag-handle",
20796
20962
  };
20797
20963
 
20798
20964
  function figIconCssVar(name, size = "medium") {