diff --git a/extension/src/popup/components/item-form.ts b/extension/src/popup/components/item-form.ts index c9956ae..f0e72be 100644 --- a/extension/src/popup/components/item-form.ts +++ b/extension/src/popup/components/item-form.ts @@ -41,7 +41,7 @@ export function renderItemForm(app: HTMLElement, mode: 'add' | 'edit'): void { const type: ItemType = existing?.type ?? state.newType ?? 'login'; switch (type) { - case 'login': return login.renderForm(app, mode, existing); + case 'login': return login.renderForm(app, mode, existing, { surface: isInTab() ? 'fullscreen' : 'popup' }); case 'secure_note': return secureNote.renderForm(app, mode, existing); case 'identity': return identity.renderForm(app, mode, existing); case 'card': return card.renderForm(app, mode, existing); diff --git a/extension/src/popup/components/types/__tests__/login.test.ts b/extension/src/popup/components/types/__tests__/login.test.ts index 50400bb..7077847 100644 --- a/extension/src/popup/components/types/__tests__/login.test.ts +++ b/extension/src/popup/components/types/__tests__/login.test.ts @@ -63,6 +63,40 @@ describe('login form smart inputs', () => { }); }); +describe('renderForm surface flag', () => { + let app: HTMLElement; + beforeEach(() => { + document.body.innerHTML = '
'; + app = document.getElementById('app')!; + (globalThis as any).chrome = { + storage: { + local: { + get: vi.fn().mockImplementation((_keys: any, cb: any) => cb({})), + set: vi.fn().mockImplementation((_obj: any, cb: any) => cb && cb()), + }, + }, + runtime: { + sendMessage: vi.fn(), + }, + }; + vi.mocked(sendMessage).mockReset(); + vi.mocked(sendMessage).mockResolvedValue({ ok: true, data: { groups: [] } }); + }); + + it('renders single-column when surface is "popup" (default)', () => { + renderForm(app, 'add', null); + expect(app.querySelector('.form-grid')).toBeNull(); + }); + + it('renders two-column .form-grid wrapper when surface is "fullscreen"', () => { + renderForm(app, 'add', null, { surface: 'fullscreen' }); + const grid = app.querySelector('.form-grid'); + expect(grid).toBeTruthy(); + expect(grid!.querySelector('[data-form-section="identity"]')).toBeTruthy(); + expect(grid!.querySelector('[data-form-section="credentials"]')).toBeTruthy(); + }); +}); + describe('Login save shape', () => { beforeEach(() => { document.body.innerHTML = ''; diff --git a/extension/src/popup/components/types/login.ts b/extension/src/popup/components/types/login.ts index aff7dff..f923bb6 100644 --- a/extension/src/popup/components/types/login.ts +++ b/extension/src/popup/components/types/login.ts @@ -235,7 +235,20 @@ function startTotpTicker(id: ItemId): void { // Form (add / edit) // ---------------------------------------------------------------------- -export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Item | null): void { +export interface RenderFormOptions { + surface?: 'popup' | 'fullscreen'; + /** When true, renderForm skips its own save/cancel buttons (caller provides them in a sticky bar). */ + externalActions?: boolean; +} + +export function renderForm( + app: HTMLElement, + mode: 'add' | 'edit', + existing: Item | null, + opts: RenderFormOptions = {} +): void { + const surface = opts.surface ?? 'popup'; + const externalActions = opts.externalActions ?? false; const state = getState(); const existingCore = (existing?.core.type === 'login') ? (existing.core as LoginCore & { type: 'login' }) @@ -254,58 +267,86 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite : []; let attachmentsDraft: AttachmentRef[] = existing?.attachments ?? []; + const titleFieldHtml = ` +