Files
bookhoard/web/src/dom.ts
T
john-okeefe 60c5a093b5 feat(typescript): add core infrastructure modules
- Add centralized API type definitions (types/api.d.ts)
  - Interfaces for all API responses matching Go handler JSON
  - Snake_case field names matching actual API responses
  - Source file references in comments for verification

- Add API client module (api.ts)
  - Procedural get/post/put/delete functions
  - Automatic auth header injection
  - Exported to window for cross-module access

- Add DOM utilities (dom.ts)
  - escapeHtml for safe HTML rendering
  - querySelector wrappers with null checks
  - Element creation helpers

- Add event delegation helpers (events.ts)
  - Reusable event delegation pattern
  - Data attribute selectors for dynamic content

- Add localStorage wrapper (storage.ts)
  - Type-safe token management
  - Theme persistence helpers
2026-02-18 16:40:29 -05:00

138 lines
3.3 KiB
TypeScript

function escapeHtml(text: string): string {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function querySelector<T extends Element>(selector: string): T | null {
return document.querySelector<T>(selector);
}
function querySelectorAll<T extends Element>(selector: string): NodeListOf<T> {
return document.querySelectorAll<T>(selector);
}
function getElementById<T extends HTMLElement>(id: string): T | null {
return document.getElementById(id) as T | null;
}
function createElement<K extends keyof HTMLElementTagNameMap>(
tagName: K,
attributes?: Record<string, string>,
children?: (string | Node)[]
): HTMLElementTagNameMap[K] {
const element = document.createElement(tagName);
if (attributes) {
Object.entries(attributes).forEach(([key, value]) => {
if (key === 'className') {
element.className = value;
} else if (key === 'dataset') {
Object.entries(JSON.parse(value)).forEach(([dataKey, dataValue]) => {
element.dataset[dataKey] = String(dataValue);
});
} else {
element.setAttribute(key, value);
}
});
}
if (children) {
children.forEach(child => {
if (typeof child === 'string') {
element.appendChild(document.createTextNode(child));
} else {
element.appendChild(child);
}
});
}
return element;
}
function showElement(element: HTMLElement | null): void {
if (element) {
element.classList.remove('hidden');
}
}
function hideElement(element: HTMLElement | null): void {
if (element) {
element.classList.add('hidden');
}
}
function toggleElement(element: HTMLElement | null): void {
if (element) {
element.classList.toggle('hidden');
}
}
function setTextContent(element: HTMLElement | null, text: string): void {
if (element) {
element.textContent = text;
}
}
function setInnerHTML(element: HTMLElement | null, html: string): void {
if (element) {
element.innerHTML = html;
}
}
function addClass(element: HTMLElement | null, className: string): void {
if (element) {
element.classList.add(className);
}
}
function removeClass(element: HTMLElement | null, className: string): void {
if (element) {
element.classList.remove(className);
}
}
function toggleClass(element: HTMLElement | null, className: string): void {
if (element) {
element.classList.toggle(className);
}
}
function hasClass(element: HTMLElement | null, className: string): boolean {
return element ? element.classList.contains(className) : false;
}
(window as any).dom = {
escapeHtml,
querySelector,
querySelectorAll,
getElementById,
createElement,
showElement,
hideElement,
toggleElement,
setTextContent,
setInnerHTML,
addClass,
removeClass,
toggleClass,
hasClass
};
export {
escapeHtml,
querySelector,
querySelectorAll,
getElementById,
createElement,
showElement,
hideElement,
toggleElement,
setTextContent,
setInnerHTML,
addClass,
removeClass,
toggleClass,
hasClass
};