UNPKG

@wordpress/block-library

Version:
428 lines (427 loc) 12.9 kB
// packages/block-library/src/utils/waveform-utils.js import { colord } from "colord"; import WaveformPlayerLib from "@arraypress/waveform-player"; var DEFAULT_WAVEFORM_HEIGHT = 100; var DEFAULT_SEEK_LABEL = "Seek"; function getComputedStyle(element) { return element.ownerDocument.defaultView.getComputedStyle(element); } function getTopLevelGradientParts(gradientValue) { const match = gradientValue?.trim().match(/^[\w-]+-gradient\((.*)\)$/i); if (!match) { return []; } const parts = []; let depth = 0; let current = ""; for (const character of match[1]) { if (character === "(") { ++depth; } else if (character === ")") { --depth; } if (character === "," && depth === 0) { parts.push(current.trim()); current = ""; continue; } current += character; } if (current.trim()) { parts.push(current.trim()); } return parts; } function getLeadingColorFunction(value) { const match = value.match(/^([\w-]+)\(/); if (!match) { return; } const supportedFunctions = [ "color", "color-mix", "hsl", "hsla", "hwb", "lab", "lch", "oklab", "oklch", "rgb", "rgba", "var" ]; if (!supportedFunctions.includes(match[1].toLowerCase())) { return; } let depth = 0; let foundOpeningParenthesis = false; for (let index = 0; index < value.length; index++) { const character = value[index]; if (character === "(") { foundOpeningParenthesis = true; ++depth; } else if (character === ")") { --depth; } if (foundOpeningParenthesis && depth === 0) { return value.slice(0, index + 1); } } } function getColorStopValue(gradientPart) { const colorFunction = getLeadingColorFunction(gradientPart); if (colorFunction) { return colorFunction; } const [possibleColor] = gradientPart.split(/\s+/); if (colord(possibleColor).isValid()) { return possibleColor; } } function getWaveformGradientDirection(gradientValue) { const parts = getTopLevelGradientParts(gradientValue); const direction = parts[0]; const angleMatch = direction?.match(/^(-?\d+(?:\.\d+)?)deg$/i); if (angleMatch) { const angle = (Number(angleMatch[1]) % 360 + 360) % 360; if (angle === 90 || angle === 270) { return "horizontal"; } if (angle === 0 || angle === 180) { return "vertical"; } return "diagonal"; } if (!direction?.startsWith("to ")) { return void 0; } const sideOrCorner = direction.toLowerCase().replace(/^to\s+/, ""); const hasHorizontalSide = sideOrCorner.includes("left") || sideOrCorner.includes("right"); const hasVerticalSide = sideOrCorner.includes("top") || sideOrCorner.includes("bottom"); if (hasHorizontalSide && hasVerticalSide) { return "diagonal"; } if (hasHorizontalSide) { return "horizontal"; } if (hasVerticalSide) { return "vertical"; } } function resolveColorValue(element, colorValue) { if (!colorValue || colord(colorValue).isValid()) { return colorValue; } const colorResolver = element.ownerDocument.createElement("span"); colorResolver.style.color = colorValue; if (!colorResolver.style.color) { return colorValue; } element.appendChild(colorResolver); const resolvedColor = getComputedStyle(colorResolver).color; colorResolver.remove(); return resolvedColor && colord(resolvedColor).isValid() ? resolvedColor : colorValue; } function getResolvedGradientStops(element, gradientValue) { const stops = getWaveformGradientStops(gradientValue)?.map((colorValue) => resolveColorValue(element, colorValue)).filter((colorValue) => colord(colorValue).isValid()); return stops?.length > 1 ? stops : void 0; } function applyAlpha(colorValue, alpha) { if (Array.isArray(colorValue)) { return colorValue.map( (color) => colord(color).alpha(alpha).toRgbString() ); } return colord(colorValue).alpha(alpha).toRgbString(); } function getRepresentativeColor(colorValue) { if (Array.isArray(colorValue)) { return colorValue[colorValue.length - 1]; } return colorValue; } function getWaveformGradientStops(gradientValue) { const stops = getTopLevelGradientParts(gradientValue).map(getColorStopValue).filter(Boolean); return stops.length > 1 ? stops : void 0; } function serializeColorValue(colorValue) { return Array.isArray(colorValue) ? JSON.stringify(colorValue) : colorValue; } function getWaveformColors(element, waveformColorValue, textColorValue, waveformGradientValue) { const textColor = textColorValue || getComputedStyle(element).color; const waveformGradientStops = getResolvedGradientStops( element, waveformGradientValue ); const waveformBaseColor = waveformGradientStops || waveformColorValue || textColor; const waveformColor = applyAlpha(waveformBaseColor, 0.3); const progressColor = applyAlpha(waveformBaseColor, 0.6); const waveformGradient = waveformGradientStops ? getWaveformGradientDirection(waveformGradientValue) : void 0; return { textColor, waveformColor, progressColor, ...waveformGradient && { waveformGradient } }; } function createWaveformContainer({ url, title, artist, artwork, waveformColor, progressColor, waveformGradient, buttonColor, seekLabel, seekValueText, height = DEFAULT_WAVEFORM_HEIGHT, waveformStyle = "bars" }) { const container = document.createElement("div"); container.setAttribute("data-waveform-player", ""); container.setAttribute("data-url", url); container.setAttribute("data-height", String(height)); container.setAttribute("data-waveform-style", waveformStyle); container.setAttribute( "data-waveform-color", serializeColorValue(waveformColor) ); container.setAttribute( "data-progress-color", serializeColorValue(progressColor) ); if (waveformGradient) { container.setAttribute("data-waveform-gradient", waveformGradient); } container.setAttribute("data-button-color", buttonColor); container.setAttribute( "data-seek-label", getSeekControlLabel(seekLabel) ); if (seekValueText) { container.setAttribute("data-seek-value-text", seekValueText); } container.setAttribute("data-text-color", buttonColor); container.setAttribute("data-text-secondary-color", buttonColor); if (title) { container.setAttribute("data-title", title); } if (artist) { container.setAttribute("data-artist", artist); } if (artwork) { container.setAttribute("data-artwork", artwork); } return container; } function applyWaveformPlayerStyles(container, { backgroundColor, backgroundGradient, textColor, playButtonColor, playButtonGradient } = {}) { const waveformContainer = container.querySelector(".waveform-container"); const playButton = container.querySelector(".waveform-btn"); const playButtonBaseColor = getRepresentativeColor( getResolvedGradientStops(container, playButtonGradient) || playButtonColor ); if (playButtonBaseColor) { container.style.setProperty( "--wfp-button-color", playButtonBaseColor ); } else { container.style.removeProperty("--wfp-button-color"); } if (textColor) { container.style.setProperty("--wfp-text-color", textColor); container.style.setProperty("--wfp-text-secondary-color", textColor); } else { container.style.removeProperty("--wfp-text-color"); container.style.removeProperty("--wfp-text-secondary-color"); } if (playButton) { if (playButtonGradient) { playButton.style.background = playButtonGradient; } else { playButton.style.removeProperty("background"); } } if (waveformContainer) { if (backgroundGradient) { waveformContainer.style.background = backgroundGradient; } else if (backgroundColor) { waveformContainer.style.removeProperty("background"); waveformContainer.style.backgroundColor = backgroundColor; } else { waveformContainer.style.removeProperty("background"); waveformContainer.style.removeProperty("background-color"); } } } function styleSvgIcons(container, buttonColor) { const isButtonDark = colord(buttonColor).isDark(); const iconColor = isButtonDark ? "#ffffff" : "#000000"; const svgPaths = container.querySelectorAll("svg path"); svgPaths.forEach((path) => { path.style.fill = iconColor; }); } function setupPlayButtonAccessibility(container, { play: playLabel = "Play", pause: pauseLabel = "Pause" } = {}) { const playBtn = container.querySelector(".waveform-btn"); if (!playBtn) { return; } playBtn.setAttribute("aria-label", playLabel); const onPlay = () => playBtn.setAttribute("aria-label", pauseLabel); const onPause = () => playBtn.setAttribute("aria-label", playLabel); container.addEventListener("waveformplayer:play", onPlay); container.addEventListener("waveformplayer:pause", onPause); container.addEventListener("waveformplayer:ended", onPause); return () => { container.removeEventListener("waveformplayer:play", onPlay); container.removeEventListener("waveformplayer:pause", onPause); container.removeEventListener("waveformplayer:ended", onPause); }; } function getSeekControlLabel(label) { return label || DEFAULT_SEEK_LABEL; } function updateSeekControlLabel(instance, label) { const seekLabel = getSeekControlLabel(label); instance.options.seekLabel = seekLabel; instance.applySeekLabel?.(seekLabel); const seekControl = instance?.container?.querySelector( ".waveform-container" ); if (seekControl) { seekControl.setAttribute("aria-label", seekLabel); } } function setupPlayButtonArtwork(container, artworkUrl) { if (!artworkUrl) { container.classList.remove("has-play-button-artwork"); container.style.removeProperty("--wp--playlist--play-button-artwork"); return; } container.classList.add("has-play-button-artwork"); container.style.setProperty( "--wp--playlist--play-button-artwork", `url(${JSON.stringify(artworkUrl)})` ); } function logPlayError(error) { if (error.name === "AbortError") { return; } console.error("Playlist play error:", error); } function initWaveformPlayer(element, { src, title, artist, image, imageAlt, waveformColor: waveformColorValue, waveformGradient: waveformGradientValue, textColor: textColorValue, backgroundColor, backgroundGradient, autoPlay, onEnded, labels, waveformStyle, showPlayButtonArtwork = false }) { const playerArtwork = showPlayButtonArtwork ? void 0 : image; const { textColor, waveformColor, progressColor, waveformGradient } = getWaveformColors( element, waveformColorValue, textColorValue, waveformGradientValue ); const waveformGradientStops = getResolvedGradientStops( element, waveformGradientValue ); const waveformButtonColor = getRepresentativeColor( waveformGradientStops || waveformColorValue ); const container = createWaveformContainer({ url: src, title, artist, artwork: playerArtwork, waveformColor, progressColor, waveformGradient, buttonColor: textColor, seekLabel: title || labels?.seek, seekValueText: labels?.seekValueText, waveformStyle }); element.appendChild(container); const instance = new WaveformPlayerLib(container); if (instance.artworkEl) { instance.artworkEl.alt = imageAlt || ""; } applyWaveformPlayerStyles(container, { backgroundColor, backgroundGradient, textColor, playButtonColor: showPlayButtonArtwork ? void 0 : waveformButtonColor, playButtonGradient: showPlayButtonArtwork ? void 0 : waveformGradientValue }); let cleanupPlayButtonAccessibility; const handlers = { ready: () => { styleSvgIcons(container, waveformButtonColor || textColor); if (showPlayButtonArtwork) { setupPlayButtonArtwork(container, image); } cleanupPlayButtonAccessibility = setupPlayButtonAccessibility( container, labels ); if (autoPlay) { instance.play()?.catch(logPlayError); } }, ended: () => onEnded?.() }; container.addEventListener("waveformplayer:ready", handlers.ready); container.addEventListener("waveformplayer:ended", handlers.ended); return { instance, container, destroy: () => { cleanupPlayButtonAccessibility?.(); container.removeEventListener( "waveformplayer:ready", handlers.ready ); container.removeEventListener( "waveformplayer:ended", handlers.ended ); instance.destroy(); container.remove(); } }; } export { applyWaveformPlayerStyles, createWaveformContainer, getWaveformColors, getWaveformGradientStops, initWaveformPlayer, logPlayError, setupPlayButtonAccessibility, setupPlayButtonArtwork, styleSvgIcons, updateSeekControlLabel }; //# sourceMappingURL=waveform-utils.mjs.map