|
|
@ -29,11 +29,16 @@ const NON_KEYBOARD_INPUT_TYPES = new Set([ |
|
|
"submit", |
|
|
"submit", |
|
|
]); |
|
|
]); |
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_TOP_GAP = 28; |
|
|
|
|
|
const DEFAULT_BOTTOM_GAP = 16; |
|
|
|
|
|
|
|
|
export type QuestionLiftGeometry = { |
|
|
export type QuestionLiftGeometry = { |
|
|
baseTop: number; |
|
|
baseTop: number; |
|
|
baseBottom: number; |
|
|
baseBottom: number; |
|
|
visibleTop: number; |
|
|
visibleTop: number; |
|
|
visibleBottom: number; |
|
|
visibleBottom: number; |
|
|
|
|
|
topGap?: number; |
|
|
|
|
|
bottomGap?: number; |
|
|
gap?: number; |
|
|
gap?: number; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
@ -42,18 +47,28 @@ export function computeQuestionLift({ |
|
|
baseBottom, |
|
|
baseBottom, |
|
|
visibleTop, |
|
|
visibleTop, |
|
|
visibleBottom, |
|
|
visibleBottom, |
|
|
|
|
|
topGap, |
|
|
|
|
|
bottomGap, |
|
|
gap = QUESTION_GAP, |
|
|
gap = QUESTION_GAP, |
|
|
}: QuestionLiftGeometry) { |
|
|
}: QuestionLiftGeometry) { |
|
|
|
|
|
const actualTopGap = topGap ?? gap ?? DEFAULT_TOP_GAP; |
|
|
|
|
|
const actualBottomGap = bottomGap ?? gap ?? DEFAULT_BOTTOM_GAP; |
|
|
const contentHeight = baseBottom - baseTop; |
|
|
const contentHeight = baseBottom - baseTop; |
|
|
const availableTop = visibleTop + gap; |
|
|
|
|
|
const availableBottom = visibleBottom - gap; |
|
|
|
|
|
|
|
|
const availableTop = visibleTop + actualTopGap; |
|
|
|
|
|
const availableBottom = visibleBottom - actualBottomGap; |
|
|
const availableCenter = (availableTop + availableBottom) / 2; |
|
|
const availableCenter = (availableTop + availableBottom) / 2; |
|
|
const contentCenter = baseTop + contentHeight / 2; |
|
|
const contentCenter = baseTop + contentHeight / 2; |
|
|
const centerShift = Math.max(0, contentCenter - availableCenter); |
|
|
const centerShift = Math.max(0, contentCenter - availableCenter); |
|
|
const overlapShift = Math.max(0, baseBottom - availableBottom); |
|
|
const overlapShift = Math.max(0, baseBottom - availableBottom); |
|
|
const maxShift = Math.max(0, baseTop - availableTop); |
|
|
const maxShift = Math.max(0, baseTop - availableTop); |
|
|
|
|
|
|
|
|
return Math.min(Math.max(centerShift, overlapShift), maxShift); |
|
|
|
|
|
|
|
|
// When content is taller than available space, don't shove top to the extreme limit;
|
|
|
|
|
|
// prioritize keeping badge and title readable with comfortable top clearance.
|
|
|
|
|
|
const idealShift = contentHeight > (availableBottom - availableTop) |
|
|
|
|
|
? Math.min(overlapShift, maxShift) |
|
|
|
|
|
: Math.max(centerShift, overlapShift); |
|
|
|
|
|
|
|
|
|
|
|
return Math.min(idealShift, maxShift); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function isKeyboardInputTarget( |
|
|
function isKeyboardInputTarget( |
|
|
@ -158,17 +173,30 @@ function updateLift() { |
|
|
const safeTop = Number.parseFloat( |
|
|
const safeTop = Number.parseFloat( |
|
|
getComputedStyle(document.documentElement).getPropertyValue("--safe-top"), |
|
|
getComputedStyle(document.documentElement).getPropertyValue("--safe-top"), |
|
|
); |
|
|
); |
|
|
|
|
|
const visibleTop = Math.max( |
|
|
|
|
|
Number.isFinite(safeTop) ? safeTop : 0, |
|
|
|
|
|
snapList?.getBoundingClientRect().top ?? 0, |
|
|
|
|
|
); |
|
|
|
|
|
const visibleBottom = Math.min(...visibleBottomCandidates); |
|
|
|
|
|
|
|
|
|
|
|
const badgeEl = content.querySelector<HTMLElement>( |
|
|
|
|
|
'.rounded-full.bg-\\[\\#F8D7DA\\]', |
|
|
|
|
|
); |
|
|
|
|
|
const badgeTop = badgeEl ? badgeEl.getBoundingClientRect().top : null; |
|
|
|
|
|
const inputRect = activeQuestionInput?.getBoundingClientRect(); |
|
|
|
|
|
|
|
|
const computed = computeQuestionLift({ |
|
|
const computed = computeQuestionLift({ |
|
|
baseTop, |
|
|
baseTop, |
|
|
baseBottom, |
|
|
baseBottom, |
|
|
visibleTop: Math.max( |
|
|
|
|
|
Number.isFinite(safeTop) ? safeTop : 0, |
|
|
|
|
|
snapList?.getBoundingClientRect().top ?? 0, |
|
|
|
|
|
), |
|
|
|
|
|
visibleBottom: Math.min(...visibleBottomCandidates), |
|
|
|
|
|
|
|
|
visibleTop, |
|
|
|
|
|
visibleBottom, |
|
|
|
|
|
topGap: DEFAULT_TOP_GAP, |
|
|
|
|
|
bottomGap: DEFAULT_BOTTOM_GAP, |
|
|
}); |
|
|
}); |
|
|
console.log(`[Coord] computed lift: ${computed}px (baseTop: ${baseTop.toFixed(0)}, baseBottom: ${baseBottom.toFixed(0)}, kTop: ${keyboardTop?.toFixed(0)})`); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log( |
|
|
|
|
|
`[Coord Geometry] lift: ${computed}px | content: [${baseTop.toFixed(0)}..${baseBottom.toFixed(0)}] h=${(baseBottom - baseTop).toFixed(0)} | vis: [${visibleTop.toFixed(0)}..${visibleBottom.toFixed(0)}] (kTop: ${keyboardTop?.toFixed(0)}) | badgeTop: ${badgeTop?.toFixed(0) ?? "none"} | input: [${inputRect?.top.toFixed(0) ?? "?"}..${inputRect?.bottom.toFixed(0) ?? "?"}]`, |
|
|
|
|
|
); |
|
|
setLift(computed); |
|
|
setLift(computed); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|