mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 17:23:25 +00:00
FIX - stabilize homescreen app dragging
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { bindPointerDragSession } from '@/utils/pointerDragSession'
|
||||
|
||||
function pointerEvent(type: string, pointerId: number): PointerEvent {
|
||||
const event = new Event(type)
|
||||
Object.defineProperty(event, 'pointerId', { value: pointerId })
|
||||
return event as PointerEvent
|
||||
}
|
||||
|
||||
describe('pointer drag session', () => {
|
||||
it('keeps tracking the active pointer at window scope until pointerup', () => {
|
||||
const target = new EventTarget()
|
||||
const move = vi.fn()
|
||||
const up = vi.fn()
|
||||
const cancel = vi.fn()
|
||||
|
||||
bindPointerDragSession(target as Window, 7, { cancel, move, up })
|
||||
target.dispatchEvent(pointerEvent('pointermove', 8))
|
||||
target.dispatchEvent(pointerEvent('pointermove', 7))
|
||||
target.dispatchEvent(new Event('lostpointercapture'))
|
||||
target.dispatchEvent(pointerEvent('pointerup', 7))
|
||||
target.dispatchEvent(pointerEvent('pointermove', 7))
|
||||
|
||||
expect(move).toHaveBeenCalledTimes(1)
|
||||
expect(up).toHaveBeenCalledTimes(1)
|
||||
expect(cancel).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('cancels and removes listeners when the NUI window loses focus', () => {
|
||||
const target = new EventTarget()
|
||||
const move = vi.fn()
|
||||
const up = vi.fn()
|
||||
const cancel = vi.fn()
|
||||
|
||||
bindPointerDragSession(target as Window, 3, { cancel, move, up })
|
||||
target.dispatchEvent(new Event('blur'))
|
||||
target.dispatchEvent(pointerEvent('pointermove', 3))
|
||||
target.dispatchEvent(pointerEvent('pointerup', 3))
|
||||
|
||||
expect(cancel).toHaveBeenCalledTimes(1)
|
||||
expect(move).not.toHaveBeenCalled()
|
||||
expect(up).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
type PointerDragSessionTarget = Pick<
|
||||
Window,
|
||||
'addEventListener' | 'removeEventListener'
|
||||
>
|
||||
|
||||
type PointerDragSessionHandlers = {
|
||||
cancel: () => void
|
||||
move: (event: PointerEvent) => void
|
||||
up: (event: PointerEvent) => void
|
||||
}
|
||||
|
||||
export function bindPointerDragSession(
|
||||
target: PointerDragSessionTarget,
|
||||
pointerId: number,
|
||||
handlers: PointerDragSessionHandlers,
|
||||
): () => void {
|
||||
let active = true
|
||||
|
||||
const cleanup = (): void => {
|
||||
if (!active) return
|
||||
active = false
|
||||
target.removeEventListener('pointermove', onPointerMove, true)
|
||||
target.removeEventListener('pointerup', onPointerUp, true)
|
||||
target.removeEventListener('pointercancel', onPointerCancel, true)
|
||||
target.removeEventListener('blur', onBlur, true)
|
||||
}
|
||||
const matchesPointer = (event: PointerEvent): boolean =>
|
||||
active && event.pointerId === pointerId
|
||||
const onPointerMove = (event: PointerEvent): void => {
|
||||
if (matchesPointer(event)) handlers.move(event)
|
||||
}
|
||||
const onPointerUp = (event: PointerEvent): void => {
|
||||
if (!matchesPointer(event)) return
|
||||
cleanup()
|
||||
handlers.up(event)
|
||||
}
|
||||
const onPointerCancel = (event: PointerEvent): void => {
|
||||
if (!matchesPointer(event)) return
|
||||
cleanup()
|
||||
handlers.cancel()
|
||||
}
|
||||
const onBlur = (): void => {
|
||||
if (!active) return
|
||||
cleanup()
|
||||
handlers.cancel()
|
||||
}
|
||||
|
||||
target.addEventListener('pointermove', onPointerMove, true)
|
||||
target.addEventListener('pointerup', onPointerUp, true)
|
||||
target.addEventListener('pointercancel', onPointerCancel, true)
|
||||
target.addEventListener('blur', onBlur, true)
|
||||
|
||||
return cleanup
|
||||
}
|
||||
@@ -113,4 +113,23 @@ describe('springboard app page layout', () => {
|
||||
targetPage: 2,
|
||||
})
|
||||
})
|
||||
|
||||
it('does not reserve a visible slot for an unavailable shortcut', () => {
|
||||
const grid = new Array<HomeSlot>(HOME_GRID_PAGE_SIZE).fill(null)
|
||||
grid[0] = app('available-one')
|
||||
grid[1] = app('unavailable')
|
||||
grid[2] = app('available-two')
|
||||
|
||||
const pages = layoutSpringboardHomePages(
|
||||
grid,
|
||||
new Map(),
|
||||
1,
|
||||
(item) => typeof item !== 'string' || item !== 'unavailable',
|
||||
)
|
||||
|
||||
expect(
|
||||
pages[0]?.cells.slice(0, 3).map((cell) => cell?.item ?? null),
|
||||
).toEqual(['available-one', 'available-two', null])
|
||||
expect(pages[0]?.cells[1]).toMatchObject({ sourceIndex: 2 })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -38,6 +38,7 @@ export function layoutSpringboardHomePages(
|
||||
grid: readonly HomeSlot[],
|
||||
occupiedByPage: ReadonlyMap<number, ReadonlySet<number>>,
|
||||
minimumPageCount: number,
|
||||
isRenderable: (item: HomeItem) => boolean = () => true,
|
||||
): SpringboardHomePage[] {
|
||||
const pageCount = Math.max(
|
||||
1,
|
||||
@@ -55,7 +56,7 @@ export function layoutSpringboardHomePages(
|
||||
for (let offset = 0; offset < HOME_GRID_PAGE_SIZE; offset += 1) {
|
||||
const sourceIndex = pageStart + offset
|
||||
const item = grid[sourceIndex]
|
||||
if (!item) continue
|
||||
if (!item || !isRenderable(item)) continue
|
||||
pending.push({
|
||||
item,
|
||||
renderKey: homeItemRenderKey(item, appOccurrences),
|
||||
|
||||
Reference in New Issue
Block a user