@rogieking/figui3 11.2.1 → 11.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/.cursor/skills/fig-lab/SKILL.md +2 -2
- package/.cursor/skills/fig-lab/components.md +0 -23
- package/.cursor/skills/fig-lab/reference.md +0 -18
- package/.cursor/skills/figui3/components.md +18 -2
- package/CHANGELOG.md +27 -0
- package/README.md +36 -18
- package/components.css +116 -33
- package/dist/components.css +1 -1
- package/dist/fig-editor.js +1 -1
- package/dist/fig-lab.css +1 -1
- package/dist/fig-lab.js +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +6 -6
- package/fig-editor.js +1 -1
- package/fig-lab.css +0 -115
- package/fig-lab.js +0 -368
- package/fig.js +766 -24
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -14864,9 +14864,354 @@ class FigMediaControls extends HTMLElement {
|
|
|
14864
14864
|
}
|
|
14865
14865
|
figDefineElement("fig-media-controls", FigMediaControls);
|
|
14866
14866
|
|
|
14867
|
+
/**
|
|
14868
|
+
* Responsive waveform generated from an audio URL.
|
|
14869
|
+
*
|
|
14870
|
+
* @attr {string} url - Audio URL to decode when CORS permits.
|
|
14871
|
+
* @attr {number} polarization - Waveform contrast around half-height. Defaults to 1.5.
|
|
14872
|
+
* @attr {number} progress - Completed fraction from 0 to 1. Defaults to 0.
|
|
14873
|
+
* @attr {boolean|string} full - Makes the waveform fill its container.
|
|
14874
|
+
*/
|
|
14875
|
+
class FigAudioWaveform extends HTMLElement {
|
|
14876
|
+
static #instanceCount = 0;
|
|
14877
|
+
static observedAttributes = ["url", "polarization", "progress"];
|
|
14878
|
+
|
|
14879
|
+
#surface = null;
|
|
14880
|
+
#svg = null;
|
|
14881
|
+
#waveformPath = null;
|
|
14882
|
+
#progressPath = null;
|
|
14883
|
+
#progressClipRect = null;
|
|
14884
|
+
#resizeObserver = null;
|
|
14885
|
+
#abortController = null;
|
|
14886
|
+
#audioContext = null;
|
|
14887
|
+
#levels = [];
|
|
14888
|
+
#generation = 0;
|
|
14889
|
+
#layoutFrame = 0;
|
|
14890
|
+
|
|
14891
|
+
connectedCallback() {
|
|
14892
|
+
if (!this.#surface) this.#render();
|
|
14893
|
+
this.#observeWaveform();
|
|
14894
|
+
this.#syncSource();
|
|
14895
|
+
}
|
|
14896
|
+
|
|
14897
|
+
disconnectedCallback() {
|
|
14898
|
+
this.#cancelDecode();
|
|
14899
|
+
this.#resizeObserver?.disconnect();
|
|
14900
|
+
this.#resizeObserver = null;
|
|
14901
|
+
if (this.#layoutFrame) cancelAnimationFrame(this.#layoutFrame);
|
|
14902
|
+
this.#layoutFrame = 0;
|
|
14903
|
+
}
|
|
14904
|
+
|
|
14905
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
14906
|
+
if (oldValue === newValue || !this.#surface) return;
|
|
14907
|
+
if (name === "url") this.#syncSource();
|
|
14908
|
+
if (name === "polarization") this.#queueWaveformLayout();
|
|
14909
|
+
if (name === "progress") this.#syncProgress();
|
|
14910
|
+
}
|
|
14911
|
+
|
|
14912
|
+
#render() {
|
|
14913
|
+
this.#surface = document.createElement("div");
|
|
14914
|
+
this.#surface.className = "fig-audio-waveform-surface";
|
|
14915
|
+
this.#surface.setAttribute("aria-hidden", "true");
|
|
14916
|
+
|
|
14917
|
+
this.#svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
14918
|
+
this.#svg.setAttribute("aria-hidden", "true");
|
|
14919
|
+
this.#svg.setAttribute("preserveAspectRatio", "none");
|
|
14920
|
+
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
14921
|
+
const progressClip = document.createElementNS(
|
|
14922
|
+
"http://www.w3.org/2000/svg",
|
|
14923
|
+
"clipPath",
|
|
14924
|
+
);
|
|
14925
|
+
const progressClipId = `fig-audio-waveform-progress-${++FigAudioWaveform.#instanceCount}`;
|
|
14926
|
+
progressClip.id = progressClipId;
|
|
14927
|
+
progressClip.setAttribute("clipPathUnits", "userSpaceOnUse");
|
|
14928
|
+
this.#progressClipRect = document.createElementNS(
|
|
14929
|
+
"http://www.w3.org/2000/svg",
|
|
14930
|
+
"rect",
|
|
14931
|
+
);
|
|
14932
|
+
progressClip.append(this.#progressClipRect);
|
|
14933
|
+
defs.append(progressClip);
|
|
14934
|
+
|
|
14935
|
+
this.#waveformPath = document.createElementNS(
|
|
14936
|
+
"http://www.w3.org/2000/svg",
|
|
14937
|
+
"path",
|
|
14938
|
+
);
|
|
14939
|
+
this.#waveformPath.classList.add("fig-audio-waveform-path");
|
|
14940
|
+
this.#progressPath = document.createElementNS(
|
|
14941
|
+
"http://www.w3.org/2000/svg",
|
|
14942
|
+
"path",
|
|
14943
|
+
);
|
|
14944
|
+
this.#progressPath.classList.add("fig-audio-waveform-progress");
|
|
14945
|
+
this.#progressPath.setAttribute("clip-path", `url(#${progressClipId})`);
|
|
14946
|
+
this.#svg.append(defs, this.#waveformPath, this.#progressPath);
|
|
14947
|
+
this.#surface.appendChild(this.#svg);
|
|
14948
|
+
this.replaceChildren(this.#surface);
|
|
14949
|
+
|
|
14950
|
+
this.#observeWaveform();
|
|
14951
|
+
}
|
|
14952
|
+
|
|
14953
|
+
#observeWaveform() {
|
|
14954
|
+
if (this.#resizeObserver || !this.#svg) return;
|
|
14955
|
+
this.#resizeObserver = new ResizeObserver(() => this.#queueWaveformLayout());
|
|
14956
|
+
this.#resizeObserver.observe(this.#svg);
|
|
14957
|
+
}
|
|
14958
|
+
|
|
14959
|
+
#syncSource() {
|
|
14960
|
+
const url = this.getAttribute("url");
|
|
14961
|
+
if (!url) {
|
|
14962
|
+
this.#clearWaveform();
|
|
14963
|
+
return;
|
|
14964
|
+
}
|
|
14965
|
+
|
|
14966
|
+
this.#decodeSource(async (signal) => {
|
|
14967
|
+
const response = await fetch(url, { signal });
|
|
14968
|
+
if (!response.ok) throw new Error(`Audio request failed: ${response.status}`);
|
|
14969
|
+
return response.arrayBuffer();
|
|
14970
|
+
});
|
|
14971
|
+
}
|
|
14972
|
+
|
|
14973
|
+
async #decodeSource(loadArrayBuffer) {
|
|
14974
|
+
this.#cancelDecode();
|
|
14975
|
+
const generation = ++this.#generation;
|
|
14976
|
+
const controller = new AbortController();
|
|
14977
|
+
this.#abortController = controller;
|
|
14978
|
+
this.setAttribute("data-waveform-state", "loading");
|
|
14979
|
+
this.setAttribute("aria-busy", "true");
|
|
14980
|
+
this.#levels = [];
|
|
14981
|
+
this.#queueWaveformLayout();
|
|
14982
|
+
|
|
14983
|
+
let context = null;
|
|
14984
|
+
try {
|
|
14985
|
+
const arrayBuffer = await loadArrayBuffer(controller.signal);
|
|
14986
|
+
if (generation !== this.#generation || !this.isConnected) return;
|
|
14987
|
+
|
|
14988
|
+
const AudioContextConstructor =
|
|
14989
|
+
window.AudioContext || window.webkitAudioContext;
|
|
14990
|
+
if (!AudioContextConstructor) throw new Error("Web Audio is unavailable");
|
|
14991
|
+
context = new AudioContextConstructor();
|
|
14992
|
+
this.#audioContext = context;
|
|
14993
|
+
const audioBuffer = await context.decodeAudioData(arrayBuffer);
|
|
14994
|
+
if (generation !== this.#generation || !this.isConnected) return;
|
|
14995
|
+
|
|
14996
|
+
this.#levels = this.#extractLevels(audioBuffer);
|
|
14997
|
+
this.setAttribute("data-waveform-state", "ready");
|
|
14998
|
+
this.#queueWaveformLayout();
|
|
14999
|
+
} catch (error) {
|
|
15000
|
+
if (generation !== this.#generation || error?.name === "AbortError") return;
|
|
15001
|
+
this.#levels = new Array(256).fill(0);
|
|
15002
|
+
this.setAttribute("data-waveform-state", "unavailable");
|
|
15003
|
+
this.#queueWaveformLayout();
|
|
15004
|
+
} finally {
|
|
15005
|
+
if (context) {
|
|
15006
|
+
try {
|
|
15007
|
+
await context.close();
|
|
15008
|
+
} catch {
|
|
15009
|
+
// Closing is best-effort in browsers with suspended audio contexts.
|
|
15010
|
+
}
|
|
15011
|
+
}
|
|
15012
|
+
if (this.#audioContext === context) this.#audioContext = null;
|
|
15013
|
+
if (this.#abortController === controller) this.#abortController = null;
|
|
15014
|
+
if (generation === this.#generation) this.removeAttribute("aria-busy");
|
|
15015
|
+
}
|
|
15016
|
+
}
|
|
15017
|
+
|
|
15018
|
+
#extractLevels(audioBuffer) {
|
|
15019
|
+
const binCount = 256;
|
|
15020
|
+
const levels = new Array(binCount).fill(0);
|
|
15021
|
+
const channelCount = audioBuffer.numberOfChannels;
|
|
15022
|
+
const frameCount = audioBuffer.length;
|
|
15023
|
+
const sampleStride = Math.max(1, Math.ceil(frameCount / 500000));
|
|
15024
|
+
let maximum = 0;
|
|
15025
|
+
|
|
15026
|
+
for (let bin = 0; bin < binCount; bin += 1) {
|
|
15027
|
+
const start = Math.floor((bin / binCount) * frameCount);
|
|
15028
|
+
const end = Math.max(
|
|
15029
|
+
start + 1,
|
|
15030
|
+
Math.floor(((bin + 1) / binCount) * frameCount),
|
|
15031
|
+
);
|
|
15032
|
+
let sumSquares = 0;
|
|
15033
|
+
let sampleCount = 0;
|
|
15034
|
+
for (let channel = 0; channel < channelCount; channel += 1) {
|
|
15035
|
+
const samples = audioBuffer.getChannelData(channel);
|
|
15036
|
+
for (let index = start; index < end; index += sampleStride) {
|
|
15037
|
+
const sample = samples[index] || 0;
|
|
15038
|
+
if (!Number.isFinite(sample)) continue;
|
|
15039
|
+
sumSquares += sample * sample;
|
|
15040
|
+
sampleCount += 1;
|
|
15041
|
+
}
|
|
15042
|
+
}
|
|
15043
|
+
const level = sampleCount > 0 ? Math.sqrt(sumSquares / sampleCount) : 0;
|
|
15044
|
+
levels[bin] = level;
|
|
15045
|
+
maximum = Math.max(maximum, level);
|
|
15046
|
+
}
|
|
15047
|
+
|
|
15048
|
+
if (!Number.isFinite(maximum) || maximum <= 0) return levels.fill(0);
|
|
15049
|
+
const minimum = Math.min(...levels);
|
|
15050
|
+
const range = maximum - minimum;
|
|
15051
|
+
if (!Number.isFinite(range) || range <= Number.EPSILON) {
|
|
15052
|
+
return levels.fill(0);
|
|
15053
|
+
}
|
|
15054
|
+
return levels.map((level) => {
|
|
15055
|
+
const normalized = (level - minimum) / range;
|
|
15056
|
+
return Number.isFinite(normalized) ? normalized : 0;
|
|
15057
|
+
});
|
|
15058
|
+
}
|
|
15059
|
+
|
|
15060
|
+
#clearWaveform() {
|
|
15061
|
+
this.#cancelDecode();
|
|
15062
|
+
this.#levels = [];
|
|
15063
|
+
this.removeAttribute("data-waveform-state");
|
|
15064
|
+
this.removeAttribute("aria-busy");
|
|
15065
|
+
this.#queueWaveformLayout();
|
|
15066
|
+
}
|
|
15067
|
+
|
|
15068
|
+
#polarizeLevel(level) {
|
|
15069
|
+
const configured = Number.parseFloat(this.getAttribute("polarization"));
|
|
15070
|
+
const factor = Number.isFinite(configured)
|
|
15071
|
+
? Math.max(0, Math.min(configured, 4))
|
|
15072
|
+
: 1.5;
|
|
15073
|
+
return Math.max(0, Math.min(1, 0.5 + (level - 0.5) * factor));
|
|
15074
|
+
}
|
|
15075
|
+
|
|
15076
|
+
#progress() {
|
|
15077
|
+
const configured = Number.parseFloat(this.getAttribute("progress"));
|
|
15078
|
+
return Number.isFinite(configured)
|
|
15079
|
+
? Math.max(0, Math.min(configured, 1))
|
|
15080
|
+
: 0;
|
|
15081
|
+
}
|
|
15082
|
+
|
|
15083
|
+
#syncProgress() {
|
|
15084
|
+
if (!this.#svg || !this.#progressClipRect) return;
|
|
15085
|
+
const viewBox = this.#svg.viewBox.baseVal;
|
|
15086
|
+
const bounds = this.#svg.getBoundingClientRect();
|
|
15087
|
+
const width = viewBox.width || Math.max(1, Math.round(bounds.width));
|
|
15088
|
+
const height = viewBox.height || Math.max(1, Math.round(bounds.height));
|
|
15089
|
+
this.#progressClipRect.setAttribute("x", "0");
|
|
15090
|
+
this.#progressClipRect.setAttribute("y", "0");
|
|
15091
|
+
this.#progressClipRect.setAttribute("width", String(width * this.#progress()));
|
|
15092
|
+
this.#progressClipRect.setAttribute("height", String(height));
|
|
15093
|
+
}
|
|
15094
|
+
|
|
15095
|
+
#cancelDecode() {
|
|
15096
|
+
this.#generation += 1;
|
|
15097
|
+
this.#abortController?.abort();
|
|
15098
|
+
this.#abortController = null;
|
|
15099
|
+
if (this.#audioContext) {
|
|
15100
|
+
try {
|
|
15101
|
+
this.#audioContext.close();
|
|
15102
|
+
} catch {
|
|
15103
|
+
// Closing is best-effort during teardown and source replacement.
|
|
15104
|
+
}
|
|
15105
|
+
this.#audioContext = null;
|
|
15106
|
+
}
|
|
15107
|
+
}
|
|
15108
|
+
|
|
15109
|
+
#queueWaveformLayout() {
|
|
15110
|
+
if (this.#layoutFrame) return;
|
|
15111
|
+
this.#layoutFrame = requestAnimationFrame(() => {
|
|
15112
|
+
this.#layoutFrame = 0;
|
|
15113
|
+
this.#layoutWaveform();
|
|
15114
|
+
});
|
|
15115
|
+
}
|
|
15116
|
+
|
|
15117
|
+
#layoutWaveform() {
|
|
15118
|
+
if (
|
|
15119
|
+
!this.#svg ||
|
|
15120
|
+
!this.#waveformPath ||
|
|
15121
|
+
!this.#progressPath ||
|
|
15122
|
+
!this.#progressClipRect
|
|
15123
|
+
) {
|
|
15124
|
+
return;
|
|
15125
|
+
}
|
|
15126
|
+
const bounds = this.#svg.getBoundingClientRect();
|
|
15127
|
+
const width = Math.max(1, Math.round(bounds.width));
|
|
15128
|
+
const height = Math.max(1, Math.round(bounds.height));
|
|
15129
|
+
this.#svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
15130
|
+
this.#syncProgress();
|
|
15131
|
+
|
|
15132
|
+
if (this.#levels.length === 0) {
|
|
15133
|
+
this.#waveformPath.setAttribute("d", "");
|
|
15134
|
+
this.#progressPath.setAttribute("d", "");
|
|
15135
|
+
return;
|
|
15136
|
+
}
|
|
15137
|
+
|
|
15138
|
+
const barCount = Math.min(
|
|
15139
|
+
this.#levels.length,
|
|
15140
|
+
Math.max(16, Math.floor(width / 3)),
|
|
15141
|
+
);
|
|
15142
|
+
const cellWidth = width / barCount;
|
|
15143
|
+
const configuredStrokeWidth = Number.parseFloat(
|
|
15144
|
+
getComputedStyle(this).getPropertyValue(
|
|
15145
|
+
"--fig-audio-waveform-stroke-width",
|
|
15146
|
+
),
|
|
15147
|
+
);
|
|
15148
|
+
const strokeWidth =
|
|
15149
|
+
Number.isFinite(configuredStrokeWidth) && configuredStrokeWidth > 0
|
|
15150
|
+
? configuredStrokeWidth
|
|
15151
|
+
: 2;
|
|
15152
|
+
const barWidth = Math.max(0.5, Math.min(strokeWidth, cellWidth * 0.75));
|
|
15153
|
+
const halfWidth = barWidth / 2;
|
|
15154
|
+
const centerY = height / 2;
|
|
15155
|
+
const maxBarHeight = Math.max(2, height - 12);
|
|
15156
|
+
const commands = [];
|
|
15157
|
+
|
|
15158
|
+
for (let index = 0; index < barCount; index += 1) {
|
|
15159
|
+
const levelIndex = Math.min(
|
|
15160
|
+
this.#levels.length - 1,
|
|
15161
|
+
Math.floor((index / barCount) * this.#levels.length),
|
|
15162
|
+
);
|
|
15163
|
+
const polarizedLevel = this.#polarizeLevel(this.#levels[levelIndex]);
|
|
15164
|
+
const barHeight = Math.max(barWidth, polarizedLevel * maxBarHeight);
|
|
15165
|
+
const x = (index + 0.5) * cellWidth;
|
|
15166
|
+
const top = centerY - barHeight / 2;
|
|
15167
|
+
const bottom = centerY + barHeight / 2;
|
|
15168
|
+
const left = x - halfWidth;
|
|
15169
|
+
const right = x + halfWidth;
|
|
15170
|
+
commands.push(
|
|
15171
|
+
`M ${x} ${top}` +
|
|
15172
|
+
` Q ${right} ${top} ${right} ${top + halfWidth}` +
|
|
15173
|
+
` V ${bottom - halfWidth}` +
|
|
15174
|
+
` Q ${right} ${bottom} ${x} ${bottom}` +
|
|
15175
|
+
` Q ${left} ${bottom} ${left} ${bottom - halfWidth}` +
|
|
15176
|
+
` V ${top + halfWidth}` +
|
|
15177
|
+
` Q ${left} ${top} ${x} ${top} Z`,
|
|
15178
|
+
);
|
|
15179
|
+
}
|
|
15180
|
+
|
|
15181
|
+
const path = commands.join(" ");
|
|
15182
|
+
this.#waveformPath.setAttribute("d", path);
|
|
15183
|
+
this.#progressPath.setAttribute("d", path);
|
|
15184
|
+
}
|
|
15185
|
+
}
|
|
15186
|
+
figDefineElement("fig-audio-waveform", FigAudioWaveform);
|
|
15187
|
+
|
|
14867
15188
|
/* File Upload Input */
|
|
15189
|
+
const FIG_INPUT_FILE_IMAGE_EXTENSIONS = [
|
|
15190
|
+
".png",
|
|
15191
|
+
".jpg",
|
|
15192
|
+
".jpeg",
|
|
15193
|
+
".gif",
|
|
15194
|
+
".webp",
|
|
15195
|
+
".svg",
|
|
15196
|
+
".avif",
|
|
15197
|
+
];
|
|
15198
|
+
const FIG_INPUT_FILE_AUDIO_EXTENSIONS = [
|
|
15199
|
+
".mp3",
|
|
15200
|
+
".wav",
|
|
15201
|
+
".aac",
|
|
15202
|
+
".m4a",
|
|
15203
|
+
".ogg",
|
|
15204
|
+
".flac",
|
|
15205
|
+
];
|
|
15206
|
+
|
|
15207
|
+
/**
|
|
15208
|
+
* File upload input with file-aware icons, image previews, and audio playback waveforms.
|
|
15209
|
+
* @attr {string} accepts - Native file chooser filter.
|
|
15210
|
+
* @attr {boolean} preview - Preview selected files with a supported image extension.
|
|
15211
|
+
* @attr {string} label - Upload button label. Defaults by known file type.
|
|
15212
|
+
*/
|
|
14868
15213
|
class FigInputFile extends HTMLElement {
|
|
14869
|
-
static observedAttributes = ["accepts", "label", "disabled", "multiple", "variant", "url", "filename"];
|
|
15214
|
+
static observedAttributes = ["accepts", "preview", "label", "disabled", "multiple", "variant", "url", "filename"];
|
|
14870
15215
|
|
|
14871
15216
|
#fileInput = null;
|
|
14872
15217
|
#filenameEl = null;
|
|
@@ -14874,6 +15219,14 @@ class FigInputFile extends HTMLElement {
|
|
|
14874
15219
|
#tooltipEl = null;
|
|
14875
15220
|
#uploadBtn = null;
|
|
14876
15221
|
#files = null;
|
|
15222
|
+
#previewImage = null;
|
|
15223
|
+
#audioPreview = null;
|
|
15224
|
+
#audioIcon = null;
|
|
15225
|
+
#audioWaveform = null;
|
|
15226
|
+
#audioPlaying = false;
|
|
15227
|
+
#audioBlobUrl = null;
|
|
15228
|
+
#syncingPreview = false;
|
|
15229
|
+
#suppressAttributeRender = false;
|
|
14877
15230
|
|
|
14878
15231
|
get files() {
|
|
14879
15232
|
return this.#files;
|
|
@@ -14901,6 +15254,180 @@ class FigInputFile extends HTMLElement {
|
|
|
14901
15254
|
return this.#urlFilename;
|
|
14902
15255
|
}
|
|
14903
15256
|
|
|
15257
|
+
get #effectiveAccepts() {
|
|
15258
|
+
return this.getAttribute("accepts") || "";
|
|
15259
|
+
}
|
|
15260
|
+
|
|
15261
|
+
get #previewEnabled() {
|
|
15262
|
+
return (
|
|
15263
|
+
this.hasAttribute("preview") &&
|
|
15264
|
+
this.getAttribute("preview") !== "false"
|
|
15265
|
+
);
|
|
15266
|
+
}
|
|
15267
|
+
|
|
15268
|
+
#hasKnownExtension(filename, extensions) {
|
|
15269
|
+
const lowerName = filename.toLowerCase();
|
|
15270
|
+
return extensions.some((extension) => lowerName.endsWith(extension));
|
|
15271
|
+
}
|
|
15272
|
+
|
|
15273
|
+
#isKnownFile(file, extensions, mimePrefix) {
|
|
15274
|
+
return (
|
|
15275
|
+
this.#hasKnownExtension(file.name, extensions) ||
|
|
15276
|
+
file.type.toLowerCase().startsWith(mimePrefix)
|
|
15277
|
+
);
|
|
15278
|
+
}
|
|
15279
|
+
|
|
15280
|
+
#generatedLabel() {
|
|
15281
|
+
if (this.#files?.[0]) {
|
|
15282
|
+
if (
|
|
15283
|
+
this.#isKnownFile(
|
|
15284
|
+
this.#files[0],
|
|
15285
|
+
FIG_INPUT_FILE_AUDIO_EXTENSIONS,
|
|
15286
|
+
"audio/",
|
|
15287
|
+
)
|
|
15288
|
+
) {
|
|
15289
|
+
return "Upload audio";
|
|
15290
|
+
}
|
|
15291
|
+
if (
|
|
15292
|
+
this.#isKnownFile(
|
|
15293
|
+
this.#files[0],
|
|
15294
|
+
FIG_INPUT_FILE_IMAGE_EXTENSIONS,
|
|
15295
|
+
"image/",
|
|
15296
|
+
)
|
|
15297
|
+
) {
|
|
15298
|
+
return "Upload image";
|
|
15299
|
+
}
|
|
15300
|
+
}
|
|
15301
|
+
|
|
15302
|
+
if (
|
|
15303
|
+
this.#hasKnownExtension(
|
|
15304
|
+
this.#urlFilename,
|
|
15305
|
+
FIG_INPUT_FILE_AUDIO_EXTENSIONS,
|
|
15306
|
+
)
|
|
15307
|
+
) {
|
|
15308
|
+
return "Upload audio";
|
|
15309
|
+
}
|
|
15310
|
+
if (
|
|
15311
|
+
this.#hasKnownExtension(
|
|
15312
|
+
this.#urlFilename,
|
|
15313
|
+
FIG_INPUT_FILE_IMAGE_EXTENSIONS,
|
|
15314
|
+
)
|
|
15315
|
+
) {
|
|
15316
|
+
return "Upload image";
|
|
15317
|
+
}
|
|
15318
|
+
|
|
15319
|
+
const acceptedTypes = this.#effectiveAccepts
|
|
15320
|
+
.split(",")
|
|
15321
|
+
.map((value) => value.trim().toLowerCase())
|
|
15322
|
+
.filter(Boolean)
|
|
15323
|
+
.map((value) => {
|
|
15324
|
+
if (
|
|
15325
|
+
value.startsWith("audio/") ||
|
|
15326
|
+
FIG_INPUT_FILE_AUDIO_EXTENSIONS.includes(value)
|
|
15327
|
+
) {
|
|
15328
|
+
return "audio";
|
|
15329
|
+
}
|
|
15330
|
+
if (
|
|
15331
|
+
value.startsWith("image/") ||
|
|
15332
|
+
FIG_INPUT_FILE_IMAGE_EXTENSIONS.includes(value)
|
|
15333
|
+
) {
|
|
15334
|
+
return "image";
|
|
15335
|
+
}
|
|
15336
|
+
return "file";
|
|
15337
|
+
});
|
|
15338
|
+
if (
|
|
15339
|
+
acceptedTypes.length > 0 &&
|
|
15340
|
+
acceptedTypes.every((type) => type === "audio")
|
|
15341
|
+
) {
|
|
15342
|
+
return "Upload audio";
|
|
15343
|
+
}
|
|
15344
|
+
if (
|
|
15345
|
+
acceptedTypes.length > 0 &&
|
|
15346
|
+
acceptedTypes.every((type) => type === "image")
|
|
15347
|
+
) {
|
|
15348
|
+
return "Upload image";
|
|
15349
|
+
}
|
|
15350
|
+
return "Upload file";
|
|
15351
|
+
}
|
|
15352
|
+
|
|
15353
|
+
#hasPreviewableImage() {
|
|
15354
|
+
if (this.#files?.length) {
|
|
15355
|
+
return this.#hasKnownExtension(
|
|
15356
|
+
this.#files[0].name,
|
|
15357
|
+
FIG_INPUT_FILE_IMAGE_EXTENSIONS,
|
|
15358
|
+
);
|
|
15359
|
+
}
|
|
15360
|
+
return (
|
|
15361
|
+
!!this.getAttribute("url") &&
|
|
15362
|
+
this.#hasKnownExtension(
|
|
15363
|
+
this.#urlFilename,
|
|
15364
|
+
FIG_INPUT_FILE_IMAGE_EXTENSIONS,
|
|
15365
|
+
)
|
|
15366
|
+
);
|
|
15367
|
+
}
|
|
15368
|
+
|
|
15369
|
+
#hasPreviewableAudio() {
|
|
15370
|
+
if (this.#files?.length) {
|
|
15371
|
+
return this.#isKnownFile(
|
|
15372
|
+
this.#files[0],
|
|
15373
|
+
FIG_INPUT_FILE_AUDIO_EXTENSIONS,
|
|
15374
|
+
"audio/",
|
|
15375
|
+
);
|
|
15376
|
+
}
|
|
15377
|
+
return (
|
|
15378
|
+
!!this.getAttribute("url") &&
|
|
15379
|
+
this.#hasKnownExtension(
|
|
15380
|
+
this.#urlFilename,
|
|
15381
|
+
FIG_INPUT_FILE_AUDIO_EXTENSIONS,
|
|
15382
|
+
)
|
|
15383
|
+
);
|
|
15384
|
+
}
|
|
15385
|
+
|
|
15386
|
+
#selectedFileIcon() {
|
|
15387
|
+
if (this.#files && this.#files.length > 0) {
|
|
15388
|
+
const files = Array.from(this.#files);
|
|
15389
|
+
if (
|
|
15390
|
+
files.every((file) =>
|
|
15391
|
+
this.#isKnownFile(
|
|
15392
|
+
file,
|
|
15393
|
+
FIG_INPUT_FILE_AUDIO_EXTENSIONS,
|
|
15394
|
+
"audio/",
|
|
15395
|
+
),
|
|
15396
|
+
)
|
|
15397
|
+
) {
|
|
15398
|
+
return "sound";
|
|
15399
|
+
}
|
|
15400
|
+
if (
|
|
15401
|
+
files.every((file) =>
|
|
15402
|
+
this.#isKnownFile(
|
|
15403
|
+
file,
|
|
15404
|
+
FIG_INPUT_FILE_IMAGE_EXTENSIONS,
|
|
15405
|
+
"image/",
|
|
15406
|
+
),
|
|
15407
|
+
)
|
|
15408
|
+
) {
|
|
15409
|
+
return "image";
|
|
15410
|
+
}
|
|
15411
|
+
}
|
|
15412
|
+
if (
|
|
15413
|
+
this.#hasKnownExtension(
|
|
15414
|
+
this.#urlFilename,
|
|
15415
|
+
FIG_INPUT_FILE_AUDIO_EXTENSIONS,
|
|
15416
|
+
)
|
|
15417
|
+
) {
|
|
15418
|
+
return "sound";
|
|
15419
|
+
}
|
|
15420
|
+
if (
|
|
15421
|
+
this.#hasKnownExtension(
|
|
15422
|
+
this.#urlFilename,
|
|
15423
|
+
FIG_INPUT_FILE_IMAGE_EXTENSIONS,
|
|
15424
|
+
)
|
|
15425
|
+
) {
|
|
15426
|
+
return "image";
|
|
15427
|
+
}
|
|
15428
|
+
return "upload";
|
|
15429
|
+
}
|
|
15430
|
+
|
|
14904
15431
|
connectedCallback() {
|
|
14905
15432
|
this.#render();
|
|
14906
15433
|
this.addEventListener("dragover", this.#onDragOver);
|
|
@@ -14911,6 +15438,8 @@ class FigInputFile extends HTMLElement {
|
|
|
14911
15438
|
disconnectedCallback() {
|
|
14912
15439
|
this.#fileInput?.removeEventListener("change", this.#onFileChange);
|
|
14913
15440
|
this.#clearBtn?.removeEventListener("click", this.#onClear);
|
|
15441
|
+
this.#disconnectPreview();
|
|
15442
|
+
this.#disconnectAudioPreview();
|
|
14914
15443
|
this.removeEventListener("dragover", this.#onDragOver);
|
|
14915
15444
|
this.removeEventListener("dragleave", this.#onDragLeave);
|
|
14916
15445
|
this.removeEventListener("drop", this.#onDrop);
|
|
@@ -14918,6 +15447,7 @@ class FigInputFile extends HTMLElement {
|
|
|
14918
15447
|
|
|
14919
15448
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
14920
15449
|
if (oldValue === newValue) return;
|
|
15450
|
+
if (this.#suppressAttributeRender) return;
|
|
14921
15451
|
this.#render();
|
|
14922
15452
|
}
|
|
14923
15453
|
|
|
@@ -14951,6 +15481,7 @@ class FigInputFile extends HTMLElement {
|
|
|
14951
15481
|
};
|
|
14952
15482
|
|
|
14953
15483
|
#onDragOver = (e) => {
|
|
15484
|
+
if (this.#previewImage) return;
|
|
14954
15485
|
if (
|
|
14955
15486
|
this.hasAttribute("disabled") &&
|
|
14956
15487
|
this.getAttribute("disabled") !== "false"
|
|
@@ -14967,6 +15498,7 @@ class FigInputFile extends HTMLElement {
|
|
|
14967
15498
|
};
|
|
14968
15499
|
|
|
14969
15500
|
#onDragLeave = () => {
|
|
15501
|
+
if (this.#previewImage) return;
|
|
14970
15502
|
this.removeAttribute("dragover");
|
|
14971
15503
|
if (this.#uploadBtn && this.#uploadBtn.dataset.prevText !== undefined) {
|
|
14972
15504
|
this.#uploadBtn.textContent = this.#uploadBtn.dataset.prevText;
|
|
@@ -14975,6 +15507,7 @@ class FigInputFile extends HTMLElement {
|
|
|
14975
15507
|
};
|
|
14976
15508
|
|
|
14977
15509
|
#onDrop = (e) => {
|
|
15510
|
+
if (this.#previewImage) return;
|
|
14978
15511
|
e.preventDefault();
|
|
14979
15512
|
this.removeAttribute("dragover");
|
|
14980
15513
|
if (this.#uploadBtn && this.#uploadBtn.dataset.prevText !== undefined) {
|
|
@@ -14987,7 +15520,7 @@ class FigInputFile extends HTMLElement {
|
|
|
14987
15520
|
)
|
|
14988
15521
|
return;
|
|
14989
15522
|
|
|
14990
|
-
const accepts = this
|
|
15523
|
+
const accepts = this.#effectiveAccepts;
|
|
14991
15524
|
let dropped = Array.from(e.dataTransfer.files);
|
|
14992
15525
|
if (accepts) {
|
|
14993
15526
|
const allowed = accepts.split(",").map((s) => s.trim().toLowerCase());
|
|
@@ -15021,24 +15554,210 @@ class FigInputFile extends HTMLElement {
|
|
|
15021
15554
|
this.#emitEvents();
|
|
15022
15555
|
};
|
|
15023
15556
|
|
|
15557
|
+
#stopPreviewInputEvent = (e) => {
|
|
15558
|
+
e.stopPropagation();
|
|
15559
|
+
};
|
|
15560
|
+
|
|
15561
|
+
#removeExternalFileAttributes() {
|
|
15562
|
+
this.#suppressAttributeRender = true;
|
|
15563
|
+
this.removeAttribute("url");
|
|
15564
|
+
this.removeAttribute("filename");
|
|
15565
|
+
this.#suppressAttributeRender = false;
|
|
15566
|
+
}
|
|
15567
|
+
|
|
15568
|
+
#onPreviewLoaded = (e) => {
|
|
15569
|
+
if (e.currentTarget !== this.#previewImage) return;
|
|
15570
|
+
e.stopPropagation();
|
|
15571
|
+
const file = e.detail?.file;
|
|
15572
|
+
if (!file || this.#files?.[0] === file) return;
|
|
15573
|
+
|
|
15574
|
+
const dt = new DataTransfer();
|
|
15575
|
+
const selectedFiles =
|
|
15576
|
+
this.#previewImage.querySelector("fig-input-file[data-generated]")?.files;
|
|
15577
|
+
if (selectedFiles?.length) {
|
|
15578
|
+
Array.from(selectedFiles).forEach((selectedFile) =>
|
|
15579
|
+
dt.items.add(selectedFile),
|
|
15580
|
+
);
|
|
15581
|
+
} else {
|
|
15582
|
+
dt.items.add(file);
|
|
15583
|
+
}
|
|
15584
|
+
this.#files = dt.files;
|
|
15585
|
+
this.#removeExternalFileAttributes();
|
|
15586
|
+
if (!this.#syncingPreview) this.#emitEvents();
|
|
15587
|
+
};
|
|
15588
|
+
|
|
15589
|
+
#onPreviewChange = (e) => {
|
|
15590
|
+
if (e.currentTarget !== this.#previewImage) return;
|
|
15591
|
+
e.stopPropagation();
|
|
15592
|
+
if (this.#previewImage.file || (!this.#files && !this.#urlFilename)) return;
|
|
15593
|
+
|
|
15594
|
+
this.#files = null;
|
|
15595
|
+
this.#removeExternalFileAttributes();
|
|
15596
|
+
this.#render();
|
|
15597
|
+
if (!this.#syncingPreview) this.#emitEvents({ cleared: true });
|
|
15598
|
+
};
|
|
15599
|
+
|
|
15600
|
+
#disconnectPreview() {
|
|
15601
|
+
if (!this.#previewImage) return;
|
|
15602
|
+
this.#previewImage.removeEventListener("loaded", this.#onPreviewLoaded);
|
|
15603
|
+
this.#previewImage.removeEventListener("change", this.#onPreviewChange);
|
|
15604
|
+
this.#previewImage = null;
|
|
15605
|
+
}
|
|
15606
|
+
|
|
15607
|
+
#disconnectAudioPreview() {
|
|
15608
|
+
this.#audioPreview?.pause();
|
|
15609
|
+
if (this.#audioBlobUrl) {
|
|
15610
|
+
URL.revokeObjectURL(this.#audioBlobUrl);
|
|
15611
|
+
this.#audioBlobUrl = null;
|
|
15612
|
+
}
|
|
15613
|
+
this.#audioPreview = null;
|
|
15614
|
+
this.#audioIcon = null;
|
|
15615
|
+
this.#audioWaveform = null;
|
|
15616
|
+
this.#audioPlaying = false;
|
|
15617
|
+
}
|
|
15618
|
+
|
|
15619
|
+
#setAudioPlaybackUi(playing) {
|
|
15620
|
+
this.#audioPlaying = playing;
|
|
15621
|
+
this.#audioIcon?.setAttribute("name", playing ? "pause" : "play");
|
|
15622
|
+
this.#uploadBtn?.toggleAttribute("selected", playing);
|
|
15623
|
+
this.#uploadBtn?.setAttribute(
|
|
15624
|
+
"aria-label",
|
|
15625
|
+
playing ? "Pause audio" : "Play audio",
|
|
15626
|
+
);
|
|
15627
|
+
}
|
|
15628
|
+
|
|
15629
|
+
#syncAudioWaveformProgress() {
|
|
15630
|
+
if (!this.#audioPreview || !this.#audioWaveform) return;
|
|
15631
|
+
const duration = this.#audioPreview.duration;
|
|
15632
|
+
const currentTime = this.#audioPreview.currentTime;
|
|
15633
|
+
const progress =
|
|
15634
|
+
Number.isFinite(duration) && duration > 0 && Number.isFinite(currentTime)
|
|
15635
|
+
? Math.max(0, Math.min(currentTime / duration, 1))
|
|
15636
|
+
: 0;
|
|
15637
|
+
this.#audioWaveform.setAttribute("progress", String(progress));
|
|
15638
|
+
}
|
|
15639
|
+
|
|
15640
|
+
#onAudioPlaybackToggle = (event) => {
|
|
15641
|
+
event.preventDefault();
|
|
15642
|
+
if (!this.#audioPreview || this.#uploadBtn?.hasAttribute("disabled")) return;
|
|
15643
|
+
if (this.#audioPlaying) {
|
|
15644
|
+
this.#audioPreview.pause();
|
|
15645
|
+
return;
|
|
15646
|
+
}
|
|
15647
|
+
const playback = this.#audioPreview.play();
|
|
15648
|
+
if (playback && typeof playback.catch === "function") {
|
|
15649
|
+
playback.catch(() => this.#setAudioPlaybackUi(false));
|
|
15650
|
+
}
|
|
15651
|
+
};
|
|
15652
|
+
|
|
15653
|
+
#renderAudioPreview() {
|
|
15654
|
+
const audio = document.createElement("audio");
|
|
15655
|
+
audio.hidden = true;
|
|
15656
|
+
audio.preload = "metadata";
|
|
15657
|
+
if (this.#files?.[0]) {
|
|
15658
|
+
this.#audioBlobUrl = URL.createObjectURL(this.#files[0]);
|
|
15659
|
+
audio.src = this.#audioBlobUrl;
|
|
15660
|
+
} else {
|
|
15661
|
+
audio.src = this.getAttribute("url") || "";
|
|
15662
|
+
}
|
|
15663
|
+
|
|
15664
|
+
audio.addEventListener("play", () => this.#setAudioPlaybackUi(true));
|
|
15665
|
+
audio.addEventListener("pause", () => this.#setAudioPlaybackUi(false));
|
|
15666
|
+
audio.addEventListener("ended", () => {
|
|
15667
|
+
this.#setAudioPlaybackUi(false);
|
|
15668
|
+
this.#audioWaveform?.setAttribute("progress", "0");
|
|
15669
|
+
});
|
|
15670
|
+
audio.addEventListener("timeupdate", () =>
|
|
15671
|
+
this.#syncAudioWaveformProgress(),
|
|
15672
|
+
);
|
|
15673
|
+
audio.addEventListener("durationchange", () =>
|
|
15674
|
+
this.#syncAudioWaveformProgress(),
|
|
15675
|
+
);
|
|
15676
|
+
|
|
15677
|
+
this.#audioPreview = audio;
|
|
15678
|
+
this.#audioWaveform = document.createElement("fig-audio-waveform");
|
|
15679
|
+
this.#audioWaveform.className = "fig-input-file-audio-waveform";
|
|
15680
|
+
this.#audioWaveform.setAttribute("url", audio.src);
|
|
15681
|
+
this.#audioWaveform.setAttribute("polarization", "3");
|
|
15682
|
+
this.#audioWaveform.setAttribute("progress", "0");
|
|
15683
|
+
this.#uploadBtn?.append(this.#audioWaveform);
|
|
15684
|
+
this.#uploadBtn?.addEventListener("click", this.#onAudioPlaybackToggle);
|
|
15685
|
+
this.#setAudioPlaybackUi(false);
|
|
15686
|
+
this.append(audio);
|
|
15687
|
+
}
|
|
15688
|
+
|
|
15689
|
+
#renderImagePreview({ accepts, label, disabled, multiple }) {
|
|
15690
|
+
const image = document.createElement("fig-image");
|
|
15691
|
+
image.setAttribute("full", "");
|
|
15692
|
+
image.setAttribute("checkerboard", "");
|
|
15693
|
+
image.setAttribute("upload", "");
|
|
15694
|
+
image.setAttribute("aspect-ratio", "4/3");
|
|
15695
|
+
image.setAttribute("label", label);
|
|
15696
|
+
const url = this.getAttribute("url");
|
|
15697
|
+
if (url) image.setAttribute("src", url);
|
|
15698
|
+
|
|
15699
|
+
this.#previewImage = image;
|
|
15700
|
+
image.addEventListener("loaded", this.#onPreviewLoaded);
|
|
15701
|
+
image.addEventListener("change", this.#onPreviewChange);
|
|
15702
|
+
this.appendChild(image);
|
|
15703
|
+
|
|
15704
|
+
const nestedInput = image.querySelector("fig-input-file[data-generated]");
|
|
15705
|
+
if (!nestedInput) return;
|
|
15706
|
+
nestedInput.addEventListener("input", this.#stopPreviewInputEvent);
|
|
15707
|
+
nestedInput.addEventListener("change", this.#stopPreviewInputEvent);
|
|
15708
|
+
nestedInput.setAttribute("accepts", accepts);
|
|
15709
|
+
if (disabled) nestedInput.setAttribute("disabled", "");
|
|
15710
|
+
if (multiple) nestedInput.setAttribute("multiple", "");
|
|
15711
|
+
|
|
15712
|
+
if (this.#files?.length) {
|
|
15713
|
+
const nativeInput = nestedInput.querySelector('input[type="file"]');
|
|
15714
|
+
if (nativeInput) {
|
|
15715
|
+
this.#syncingPreview = true;
|
|
15716
|
+
nativeInput.files = this.#files;
|
|
15717
|
+
nativeInput.dispatchEvent(new Event("change", { bubbles: true }));
|
|
15718
|
+
this.#syncingPreview = false;
|
|
15719
|
+
}
|
|
15720
|
+
}
|
|
15721
|
+
}
|
|
15722
|
+
|
|
15024
15723
|
#render() {
|
|
15025
|
-
const accepts = this
|
|
15026
|
-
const
|
|
15724
|
+
const accepts = this.#effectiveAccepts;
|
|
15725
|
+
const customLabel = this.getAttribute("label");
|
|
15726
|
+
const label = customLabel?.trim() ? customLabel : this.#generatedLabel();
|
|
15027
15727
|
const disabled =
|
|
15028
15728
|
this.hasAttribute("disabled") &&
|
|
15029
15729
|
this.getAttribute("disabled") !== "false";
|
|
15030
15730
|
const multiple =
|
|
15031
15731
|
this.hasAttribute("multiple") && this.getAttribute("multiple") !== "false";
|
|
15032
|
-
const variant = this.getAttribute("variant") || "
|
|
15732
|
+
const variant = this.getAttribute("variant") || "property";
|
|
15033
15733
|
const hasFile =
|
|
15034
15734
|
(this.#files && this.#files.length > 0) ||
|
|
15035
15735
|
!!this.getAttribute("url") ||
|
|
15036
15736
|
!!this.getAttribute("filename");
|
|
15737
|
+
const showsImagePreview =
|
|
15738
|
+
this.#previewEnabled && this.#hasPreviewableImage();
|
|
15739
|
+
const showsAudioPreview =
|
|
15740
|
+
this.#previewEnabled && this.#hasPreviewableAudio();
|
|
15037
15741
|
|
|
15742
|
+
this.#disconnectPreview();
|
|
15743
|
+
this.#disconnectAudioPreview();
|
|
15038
15744
|
this.replaceChildren();
|
|
15745
|
+
this.#fileInput = null;
|
|
15746
|
+
this.#uploadBtn = null;
|
|
15747
|
+
this.#clearBtn = null;
|
|
15748
|
+
this.#tooltipEl = null;
|
|
15749
|
+
this.toggleAttribute("data-previewing", showsImagePreview);
|
|
15750
|
+
|
|
15751
|
+
if (showsImagePreview) {
|
|
15752
|
+
this.#renderImagePreview({ accepts, label, disabled, multiple });
|
|
15753
|
+
return;
|
|
15754
|
+
}
|
|
15039
15755
|
|
|
15040
15756
|
if (hasFile) {
|
|
15041
|
-
const
|
|
15757
|
+
const filename = this.value;
|
|
15758
|
+
const tooltipText = showsAudioPreview
|
|
15759
|
+
? filename
|
|
15760
|
+
: accepts
|
|
15042
15761
|
? `Accepts ${accepts
|
|
15043
15762
|
.split(",")
|
|
15044
15763
|
.map((s) => s.trim())
|
|
@@ -15047,26 +15766,38 @@ class FigInputFile extends HTMLElement {
|
|
|
15047
15766
|
|
|
15048
15767
|
this.#uploadBtn = document.createElement("fig-button");
|
|
15049
15768
|
this.#uploadBtn.setAttribute("variant", variant);
|
|
15050
|
-
this.#uploadBtn.setAttribute("
|
|
15769
|
+
if (variant === "property") this.#uploadBtn.setAttribute("full", "");
|
|
15051
15770
|
this.#uploadBtn.className = "fig-input-file-filename";
|
|
15052
15771
|
if (disabled) this.#uploadBtn.setAttribute("disabled", "");
|
|
15053
|
-
const
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
15067
|
-
|
|
15068
|
-
|
|
15069
|
-
|
|
15772
|
+
const fileIcon = createFigIcon(
|
|
15773
|
+
showsAudioPreview ? "play" : this.#selectedFileIcon(),
|
|
15774
|
+
);
|
|
15775
|
+
if (showsAudioPreview) {
|
|
15776
|
+
this.#audioIcon = fileIcon;
|
|
15777
|
+
fileIcon.setAttribute("slot", "prepend");
|
|
15778
|
+
this.#uploadBtn.append(fileIcon);
|
|
15779
|
+
} else {
|
|
15780
|
+
const truncEl = document.createElement("fig-truncate");
|
|
15781
|
+
truncEl.setAttribute("position", "middle");
|
|
15782
|
+
truncEl.setAttribute("tooltip", "");
|
|
15783
|
+
const dotIdx = filename.lastIndexOf(".");
|
|
15784
|
+
if (dotIdx > 0) truncEl.setAttribute("tail", filename.slice(dotIdx));
|
|
15785
|
+
truncEl.textContent = filename;
|
|
15786
|
+
fileIcon.setAttribute("slot", "prepend");
|
|
15787
|
+
this.#uploadBtn.append(fileIcon, truncEl);
|
|
15788
|
+
}
|
|
15789
|
+
|
|
15790
|
+
if (!showsAudioPreview) {
|
|
15791
|
+
this.#uploadBtn.setAttribute("type", "upload");
|
|
15792
|
+
this.#fileInput = document.createElement("input");
|
|
15793
|
+
this.#fileInput.type = "file";
|
|
15794
|
+
this.#fileInput.title = "";
|
|
15795
|
+
this.#fileInput.disabled = disabled;
|
|
15796
|
+
if (accepts) this.#fileInput.setAttribute("accept", accepts);
|
|
15797
|
+
if (multiple) this.#fileInput.setAttribute("multiple", "");
|
|
15798
|
+
this.#fileInput.addEventListener("change", this.#onFileChange);
|
|
15799
|
+
this.#uploadBtn.appendChild(this.#fileInput);
|
|
15800
|
+
}
|
|
15070
15801
|
|
|
15071
15802
|
if (tooltipText) {
|
|
15072
15803
|
this.#tooltipEl = document.createElement("fig-tooltip");
|
|
@@ -15089,6 +15820,7 @@ class FigInputFile extends HTMLElement {
|
|
|
15089
15820
|
this.#clearBtn.addEventListener("click", this.#onClear);
|
|
15090
15821
|
clearTooltip.appendChild(this.#clearBtn);
|
|
15091
15822
|
this.appendChild(clearTooltip);
|
|
15823
|
+
if (showsAudioPreview) this.#renderAudioPreview();
|
|
15092
15824
|
} else {
|
|
15093
15825
|
const tooltipText = accepts
|
|
15094
15826
|
? `Accepts ${accepts
|
|
@@ -15104,8 +15836,12 @@ class FigInputFile extends HTMLElement {
|
|
|
15104
15836
|
|
|
15105
15837
|
this.#uploadBtn = document.createElement("fig-button");
|
|
15106
15838
|
this.#uploadBtn.setAttribute("variant", variant);
|
|
15839
|
+
if (variant === "property") this.#uploadBtn.setAttribute("full", "");
|
|
15107
15840
|
this.#uploadBtn.setAttribute("type", "upload");
|
|
15108
15841
|
this.#uploadBtn.textContent = label;
|
|
15842
|
+
const uploadIcon = createFigIcon("upload");
|
|
15843
|
+
uploadIcon.setAttribute("slot", "prepend");
|
|
15844
|
+
this.#uploadBtn.prepend(uploadIcon);
|
|
15109
15845
|
if (disabled) this.#uploadBtn.setAttribute("disabled", "");
|
|
15110
15846
|
|
|
15111
15847
|
this.#fileInput = document.createElement("input");
|
|
@@ -19458,6 +20194,12 @@ const FIG_ICON_TOKENS = {
|
|
|
19458
20194
|
search: "--icon-24-search",
|
|
19459
20195
|
download: "--icon-24-download",
|
|
19460
20196
|
upload: "--icon-24-upload",
|
|
20197
|
+
image: "--icon-24-image",
|
|
20198
|
+
sound: { medium: "--icon-24-sound", small: "--icon-16-sound" },
|
|
20199
|
+
"sound-off": {
|
|
20200
|
+
medium: "--icon-24-sound-off",
|
|
20201
|
+
small: "--icon-16-sound-off",
|
|
20202
|
+
},
|
|
19461
20203
|
sun: "--icon-24-sun",
|
|
19462
20204
|
moon: "--icon-24-moon",
|
|
19463
20205
|
edit: { medium: "--icon-24-edit", small: "--icon-16-edit" },
|