Moves renderFormWrapped (sticky save bar + header + dirty-state wiring), the SAVE_HINT/isMac consts, and the __test__ export out of vault.ts into vault-form-wrapper.ts, taking the VaultController ctx. Repoints the source-text form-wrapper test to read the new module. No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
73 lines
2.9 KiB
TypeScript
73 lines
2.9 KiB
TypeScript
// Fullscreen form wrapper for the vault tab: sticky save bar + scrollable
|
|
// content + header with a live dirty-state subtitle. Receives the
|
|
// VaultController (`ctx`) for the item-type read; imports only from shared/,
|
|
// the popup item-form component, and vault-context.
|
|
|
|
import { renderItemForm } from '../popup/components/item-form';
|
|
import { type VaultController } from './vault-context';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Platform-aware save hint
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const isMac = navigator.platform.toLowerCase().includes('mac');
|
|
const SAVE_HINT = isMac ? '⌘+S to save' : 'Ctrl+S to save';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Fullscreen form wrapper — sticky save bar + scrollable content + header
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function renderFormWrapped(ctx: VaultController, app: HTMLElement, mode: 'add' | 'edit'): void {
|
|
const itemType = ctx.state.selectedItem?.type ?? ctx.state.newType ?? 'login';
|
|
const typeLabelText = itemType.replace('_', ' ');
|
|
const titleText = mode === 'add' ? `new ${typeLabelText}` : `edit ${typeLabelText}`;
|
|
const wrapper = document.createElement('div');
|
|
wrapper.className = 'form-pane';
|
|
wrapper.innerHTML = `
|
|
<div class="fullscreen-form-header">
|
|
<div>
|
|
<div class="title">${titleText}</div>
|
|
<div class="sub" id="form-dirty-sub">no changes</div>
|
|
</div>
|
|
<div class="hint">${SAVE_HINT}</div>
|
|
</div>
|
|
<div class="form-scroll" id="form-scroll"></div>
|
|
<div class="sticky-save-bar">
|
|
<button class="btn-secondary" id="form-cancel">cancel</button>
|
|
<button class="btn-primary" id="form-save">save</button>
|
|
</div>
|
|
`;
|
|
// Remove pane padding so form-pane can fill height cleanly
|
|
app.style.padding = '0';
|
|
app.style.overflow = 'hidden';
|
|
app.replaceChildren(wrapper);
|
|
|
|
const scrollEl = wrapper.querySelector('#form-scroll') as HTMLElement;
|
|
renderItemForm(scrollEl, mode);
|
|
|
|
const subEl = wrapper.querySelector('#form-dirty-sub') as HTMLElement;
|
|
let isDirty = false;
|
|
const markDirty = () => {
|
|
if (isDirty) return;
|
|
isDirty = true;
|
|
subEl.textContent = 'unsaved · esc to cancel';
|
|
};
|
|
const markClean = () => {
|
|
isDirty = false;
|
|
subEl.textContent = 'no changes';
|
|
};
|
|
scrollEl.addEventListener('input', markDirty, true);
|
|
scrollEl.addEventListener('change', markDirty, true);
|
|
|
|
wrapper.querySelector('#form-cancel')?.addEventListener('click', () => {
|
|
markClean();
|
|
(scrollEl.querySelector('#cancel-btn') as HTMLButtonElement | null)?.click();
|
|
});
|
|
wrapper.querySelector('#form-save')?.addEventListener('click', () => {
|
|
markClean();
|
|
(scrollEl.querySelector('#save-btn') as HTMLButtonElement | null)?.click();
|
|
});
|
|
}
|
|
|
|
export const __test__ = { renderFormWrapped };
|