feat(ext/popup): renderSections helper for custom-field detail rendering

This commit is contained in:
adlee-was-taken
2026-04-24 10:28:10 -04:00
parent 2ca563a8cd
commit 3f12543c81
3 changed files with 161 additions and 0 deletions

View File

@@ -6,6 +6,7 @@
/// copy click handlers on any rendered rows.
import { escapeHtml } from '../popup';
import type { Item } from '../../shared/types';
export interface RowOpts {
label: string;
@@ -117,3 +118,42 @@ export function wireFieldHandlers(scope: HTMLElement): void {
});
});
}
/// Render an Item's sections as read-only field rows. Each section with
/// ≥1 field emits a header (if named) or thin separator (if anonymous)
/// plus field rows via renderRow / renderConcealedRow. Sections with
/// 0 fields are skipped. Fields with unsupported kinds are silently
/// skipped (β₂ supports text, password, concealed only).
///
/// `idPrefix` uniquifies concealed-row IDs (`${idPrefix}-s{i}-f{j}`)
/// so multiple typed-item detail views rendered in sequence don't
/// collide on wireFieldHandlers lookups.
export function renderSections(item: Item, idPrefix: string): string {
let out = '';
item.sections.forEach((section, sIdx) => {
const visibleFields = section.fields.filter(
(f) => f.value.kind === 'text' || f.value.kind === 'password' || f.value.kind === 'concealed',
);
if (visibleFields.length === 0) return;
if (section.name) {
out += `<div class="section-header">${escapeHtml(section.name)}</div>`;
} else {
out += `<hr class="section-separator">`;
}
visibleFields.forEach((field, fIdx) => {
if (field.value.kind === 'text') {
out += renderRow({ label: field.label, value: field.value.value, copyable: true });
} else {
// password or concealed
out += renderConcealedRow({
id: `${idPrefix}-s${sIdx}-f${fIdx}`,
label: field.label,
value: field.value.value,
});
}
});
});
return out;
}