mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 16:59:43 +02:00
Two pieces the #428 merge left out or lost: - Spacing: the removed #shellCount stepper's 0.25rem margins were the only space between Run Shell and the case picker (the desktop .toolbar-group has gap: 0), so the two touched. The case picker now keeps the same 4px itself above 768px; at 768px and below mobile.css already spaces the group with gap. - The phone case sheet's keyboard lift from #428's search commit. #488 shipped the sheet's search field without it, so on iOS (which does not shrink the layout viewport) the keyboard opens over the sheet and hides its own search box. KeyboardHandler now lifts an open sheet like the toolbar (translateY on phones, bottom on iPads), resetLayout() clears any sheet, and the list is capped while the keyboard is up so the search row and Create stay on screen. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
98 lines
4.1 KiB
TypeScript
98 lines
4.1 KiB
TypeScript
// Port: none (pure logic in a vm context — no browser, no server).
|
|
//
|
|
// The phone "Select Case" bottom sheet (#mobileCasePickerModal) has a search
|
|
// field (#488), so the on-screen keyboard can open over it. The sheet is a third
|
|
// `position: fixed` bottom-anchored surface beside the toolbar and the keyboard
|
|
// accessory bar, and iOS does not shrink the layout viewport for the keyboard, so
|
|
// an unlifted sheet sits BEHIND the keyboard with its own search box out of
|
|
// sight. KeyboardHandler therefore lifts an OPEN sheet the same way it lifts the
|
|
// toolbar (translateY on phones, `bottom` on iPads), and resetLayout() clears the
|
|
// offset on ANY sheet, open or not, so a sheet closed with the keyboard still up
|
|
// does not slide in already displaced next time (#428).
|
|
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import vm from 'node:vm';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
const SOURCE = readFileSync(resolve(import.meta.dirname, '../src/web/public/mobile-handlers.js'), 'utf8');
|
|
|
|
interface Styled {
|
|
style: Record<string, string>;
|
|
}
|
|
|
|
function loadHandler(opts: { width: number; layoutHeight: number; visualHeight: number; sheetOpen: boolean }) {
|
|
const el = (): Styled => ({ style: { transform: '', bottom: '', paddingBottom: '' } });
|
|
const sheet = el();
|
|
const toolbar = el();
|
|
const accessoryBar = el();
|
|
const main = el();
|
|
const context = vm.createContext({
|
|
console,
|
|
navigator: { userAgent: 'Mozilla/5.0 (iPhone) Mobile', maxTouchPoints: 5 },
|
|
window: {
|
|
innerWidth: opts.width,
|
|
innerHeight: opts.layoutHeight,
|
|
visualViewport: { height: opts.visualHeight, offsetTop: 0 },
|
|
addEventListener: () => {},
|
|
matchMedia: () => ({ matches: true }),
|
|
scrollTo: () => {},
|
|
},
|
|
document: {
|
|
body: { classList: { add: () => {}, remove: () => {}, contains: () => false } },
|
|
addEventListener: () => {},
|
|
getElementById: () => null,
|
|
querySelector: (sel: string) => {
|
|
if (sel === '.mobile-case-picker.active .mobile-case-picker-sheet') return opts.sheetOpen ? sheet : null;
|
|
if (sel === '.mobile-case-picker-sheet') return sheet;
|
|
if (sel === '.toolbar') return toolbar;
|
|
if (sel === '.keyboard-accessory-bar') return accessoryBar;
|
|
if (sel === '.main') return main;
|
|
return null;
|
|
},
|
|
},
|
|
setTimeout: () => 1,
|
|
clearTimeout: () => {},
|
|
});
|
|
vm.runInContext(`${SOURCE}\nglobalThis.__KH = KeyboardHandler;`, context, { filename: 'mobile-handlers.js' });
|
|
const handler = (context as { __KH: any }).__KH;
|
|
handler.keyboardVisible = true;
|
|
handler.initialViewportHeight = opts.layoutHeight;
|
|
return { handler, sheet, toolbar };
|
|
}
|
|
|
|
describe('mobile case sheet keyboard lift', () => {
|
|
it('lifts an open sheet above the keyboard on a phone, by the same offset as the toolbar', () => {
|
|
const { handler, sheet, toolbar } = loadHandler({
|
|
width: 390,
|
|
layoutHeight: 844,
|
|
visualHeight: 500,
|
|
sheetOpen: true,
|
|
});
|
|
handler.updateLayoutForKeyboard();
|
|
expect(sheet.style.transform).toBe('translateY(-344px)');
|
|
expect(sheet.style.transform).toBe(toolbar.style.transform);
|
|
});
|
|
|
|
it('positions an open sheet by `bottom` on an iPad-sized screen', () => {
|
|
const { handler, sheet } = loadHandler({ width: 1024, layoutHeight: 1366, visualHeight: 966, sheetOpen: true });
|
|
handler.updateLayoutForKeyboard();
|
|
expect(sheet.style.bottom).toBe('400px');
|
|
expect(sheet.style.transform).toBe('');
|
|
});
|
|
|
|
it('leaves a closed sheet alone while the keyboard is up', () => {
|
|
const { handler, sheet } = loadHandler({ width: 390, layoutHeight: 844, visualHeight: 500, sheetOpen: false });
|
|
handler.updateLayoutForKeyboard();
|
|
expect(sheet.style.transform).toBe('');
|
|
});
|
|
|
|
it('resetLayout clears the offset even on a sheet that is no longer open', () => {
|
|
const { handler, sheet } = loadHandler({ width: 390, layoutHeight: 844, visualHeight: 500, sheetOpen: false });
|
|
sheet.style.transform = 'translateY(-344px)';
|
|
sheet.style.bottom = '400px';
|
|
handler.resetLayout();
|
|
expect(sheet.style.transform).toBe('');
|
|
expect(sheet.style.bottom).toBe('');
|
|
});
|
|
});
|