fix(glass): preserve collapsed interaction clips (#601)

This commit is contained in:
InfinityPacer
2026-07-30 21:01:15 +08:00
committed by GitHub
parent 097beb6f63
commit 6d5550f198
2 changed files with 73 additions and 5 deletions

View File

@@ -1960,6 +1960,64 @@ describe('glass optical surface discovery', () => {
scope.stop()
})
it('restores a temporarily collapsed interaction clip without rebuilding registry membership', async () => {
vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(1200)
vi.spyOn(window, 'innerHeight', 'get').mockReturnValue(800)
const three = await import('three')
const render = vi.spyOn(three.WebGLRenderer.prototype, 'render')
const outerSurface = appendOpticalSurface('v-card', { height: 500, width: 900, x: 80, y: 100 })
outerSurface.dataset.glassOpticalSurface = ''
const card = document.createElement('article')
card.className = 'app-hover-lift-card'
setOpticalSurfaceBounds(card, { height: 180, width: 360, x: 100, y: 140 })
outerSurface.append(card)
const querySelectorAll = vi.spyOn(outerSurface, 'querySelectorAll')
const scope = effectScope()
const renderer = scope.run(() =>
useGlassOpticalRenderer({
active: ref(true),
appearance: ref('clear'),
canvas: ref(document.createElement('canvas')),
quality: ref('balanced'),
routeKey: ref('/dashboard'),
surfaceSpace: 'scroll',
tintColor: ref('#8D51F9'),
wallpaperUrl: ref('https://example.com/wallpaper.jpg'),
}),
)
await vi.waitFor(() => expect(renderer?.state.value).toBe('ready'))
window.dispatchEvent(new MouseEvent('pointermove', { clientX: 160, clientY: 180 }))
await vi.waitFor(() => expect(render).toHaveBeenCalled())
const getInteractionRectCount = () => {
const scene = render.mock.calls.at(-1)?.[0] as unknown as {
children: Array<{
material: {
uniforms: {
uInteractionRectCount: { value: number }
}
}
}>
}
return scene.children[0].material.uniforms.uInteractionRectCount.value
}
expect(getInteractionRectCount()).toBe(1)
querySelectorAll.mockClear()
setOpticalSurfaceBounds(card, { height: 12, width: 12, x: 100, y: 140 })
ResizeObserverMock.instances.forEach(observer => observer.trigger())
await vi.waitFor(() => expect(getInteractionRectCount()).toBe(0))
setOpticalSurfaceBounds(card, { height: 180, width: 360, x: 100, y: 140 })
ResizeObserverMock.instances.forEach(observer => observer.trigger())
await vi.waitFor(() => expect(getInteractionRectCount()).toBe(1))
expect(querySelectorAll).not.toHaveBeenCalled()
scope.stop()
})
it('keeps the active texture and ready state when a replacement wallpaper fails', async () => {
const three = await import('three')
const canvas = document.createElement('canvas')

View File

@@ -1930,7 +1930,6 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions)
if (seen.has(element) || !element.isConnected || resolveGlassOpticalSurfaceMode(element) !== mode) return
const rect = committedRect ?? getElementPresentationRect(element)
if (rect.width < 24 || rect.height < 24) return
seen.add(element)
candidates.push({ key: element, mode, owner, rect })
@@ -1968,12 +1967,14 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions)
}
const rect = committedSurfaces.get(clip.key)?.rect ?? getElementPresentationRect(clip.key)
if (rect.width < 24 || rect.height < 24) return []
return [{ ...clip, rect }]
})
}
function isInteractionClipRenderable(rect: GlassOpticalRect) {
return rect.width >= 24 && rect.height >= 24
}
function updateInteractionClips() {
const slotKeys = new Set(surfaceSlots.map(slot => slot.key))
const viewportX = presentationSpace === 'scroll' ? window.scrollX : 0
@@ -2003,7 +2004,11 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions)
inOverscan,
}
})
.filter(entry => entry.candidate.key === activeInteractionClip || entry.inOverscan)
.filter(
entry =>
isInteractionClipRenderable(entry.candidate.rect) &&
(entry.candidate.key === activeInteractionClip || entry.inOverscan),
)
.sort((left, right) => {
const leftActive = left.candidate.key === activeInteractionClip
const rightActive = right.candidate.key === activeInteractionClip
@@ -2445,7 +2450,12 @@ export function useGlassOpticalRenderer(options: UseGlassOpticalRendererOptions)
if (!surface) return null
const matchingClips = interactionClipRegistry
.filter(candidate => candidate.owner === surface.key && rectContainsPoint(candidate.rect, x, y))
.filter(
candidate =>
candidate.owner === surface.key &&
isInteractionClipRenderable(candidate.rect) &&
rectContainsPoint(candidate.rect, x, y),
)
.sort((left, right) => left.rect.width * left.rect.height - right.rect.width * right.rect.height)
return {