fixup! (feat) connect PiP to room and options menu

This commit is contained in:
Cyril
2026-01-23 11:24:19 +01:00
parent 2bdd8ddbaf
commit 99dcd34482
3 changed files with 82 additions and 23 deletions
@@ -36,25 +36,63 @@ const syncThemeAttribute = (source: Document, target: Document) => {
} }
} }
const cssVarNameCacheByElement = new WeakMap<HTMLElement, string[]>()
const cssVarNameCacheByUri = new Map<string, string[]>()
const syncCssVariables = (source: Document, target: Document) => { const syncCssVariables = (source: Document, target: Document) => {
const sourceView = source.defaultView const sourceView = source.defaultView
if (!sourceView) return if (!sourceView) return
const applyVarsFrom = (element: HTMLElement | null) => { const getCachedVarNames = () => {
if (!element) return const docEl = source.documentElement
const styles = sourceView.getComputedStyle(element) if (!docEl) return []
for (let i = 0; i < styles.length; i += 1) {
const property = styles[i] const cachedByElement = cssVarNameCacheByElement.get(docEl)
if (!property.startsWith('--')) continue if (cachedByElement) return cachedByElement
const value = styles.getPropertyValue(property)
if (value) { const cachedByUri = source.baseURI
target.documentElement.style.setProperty(property, value) ? cssVarNameCacheByUri.get(source.baseURI)
: undefined
if (cachedByUri) return cachedByUri
const varNames = new Set<string>()
const collectVarsFrom = (element: HTMLElement | null) => {
if (!element) return
const styles = sourceView.getComputedStyle(element)
for (let i = 0; i < styles.length; i += 1) {
const property = styles[i]
if (property.startsWith('--')) {
varNames.add(property)
}
} }
} }
collectVarsFrom(source.documentElement)
collectVarsFrom(source.body)
const result = Array.from(varNames)
cssVarNameCacheByElement.set(docEl, result)
if (source.baseURI) {
cssVarNameCacheByUri.set(source.baseURI, result)
}
return result
} }
applyVarsFrom(source.documentElement) const varNames = getCachedVarNames()
applyVarsFrom(source.body) if (!varNames.length) return
const rootStyles = sourceView.getComputedStyle(source.documentElement)
const bodyStyles = source.body
? sourceView.getComputedStyle(source.body)
: null
varNames.forEach((property) => {
const bodyValue = bodyStyles?.getPropertyValue(property)
const value = bodyValue || rootStyles.getPropertyValue(property)
if (value) {
target.documentElement.style.setProperty(property, value)
}
})
} }
/** /**
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
type DocumentPictureInPicture = { type DocumentPictureInPicture = {
requestWindow: (options?: { requestWindow: (options?: {
@@ -19,24 +19,44 @@ export const useDocumentPiP = ({
height?: number height?: number
} = {}) => { } = {}) => {
const [pipWindow, setPipWindow] = useState<Window | null>(null) const [pipWindow, setPipWindow] = useState<Window | null>(null)
const pipWindowRef = useRef<Window | null>(null)
const pendingPiPRef = useRef<Promise<Window | null> | null>(null)
const isSupported = useMemo(() => { const [isSupported] = useState(() => {
if (typeof window === 'undefined') return false if (typeof window === 'undefined') return false
return 'documentPictureInPicture' in window return 'documentPictureInPicture' in window
}, []) })
const openPiP = useCallback(async () => { const openPiP = useCallback(async () => {
if (!isSupported) return null if (!isSupported) return null
if (pipWindow && !pipWindow.closed) return pipWindow const existingWindow = pipWindowRef.current
if (existingWindow && !existingWindow.closed) return existingWindow
if (pendingPiPRef.current) return pendingPiPRef.current
// Request a new PiP window from the browser API. // Request a new PiP window from the browser API.
const pip = (window as WindowWithDocumentPiP).documentPictureInPicture const pip = (window as WindowWithDocumentPiP).documentPictureInPicture
if (!pip) return null if (!pip) return null
const win = await pip.requestWindow({ width, height }) const requestPromise = (async () => {
setPipWindow(win) try {
return win const win = await pip.requestWindow({ width, height })
}, [height, isSupported, pipWindow, width]) const currentWindow = pipWindowRef.current
if (currentWindow && !currentWindow.closed) return currentWindow
setPipWindow(win)
return win
} catch (error) {
// Avoid unhandled rejections if the user blocks or closes the request.
console.error('Failed to open Picture-in-Picture window', error)
return null
} finally {
pendingPiPRef.current = null
}
})()
pendingPiPRef.current = requestPromise
return requestPromise
}, [height, isSupported, width])
const closePiP = useCallback(() => { const closePiP = useCallback(() => {
if (!pipWindow) return if (!pipWindow) return
@@ -46,6 +66,10 @@ export const useDocumentPiP = ({
setPipWindow(null) setPipWindow(null)
}, [pipWindow]) }, [pipWindow])
useEffect(() => {
pipWindowRef.current = pipWindow
}, [pipWindow])
useEffect(() => { useEffect(() => {
if (!pipWindow) return if (!pipWindow) return
@@ -17,8 +17,5 @@ export const useOverlayPortalContainer = () => {
*/ */
export const useOverlayBoundaryElement = () => { export const useOverlayBoundaryElement = () => {
const portalContainer = useOverlayPortalContainer() const portalContainer = useOverlayPortalContainer()
return useMemo(() => { return portalContainer
if (portalContainer) return portalContainer
return undefined
}, [portalContainer])
} }