@rxxuzi/gumi
Version:
Clean & minimal design system with delightful interactions
285 lines (247 loc) • 6.92 kB
text/typescript
// core/dom.ts
// Gumi.js v1.0.0 - DOM Utilities
import { GumiElement } from '../types';
/**
* Query selector helper
*/
export function $(selector: GumiElement): HTMLElement | null {
if (typeof selector === 'string') {
return document.querySelector(selector);
}
return selector as HTMLElement;
}
/**
* Query selector all helper
*/
export function $$(selector: string): NodeListOf<HTMLElement> {
return document.querySelectorAll(selector);
}
/**
* Ready function - fires when DOM is ready
*/
export function ready(fn: () => void): void {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', fn);
} else {
fn();
}
}
/**
* Create element with options
*/
export interface CreateElementOptions {
className?: string;
id?: string;
text?: string;
html?: string;
attributes?: Record<string, string>;
style?: Partial<CSSStyleDeclaration>;
events?: Record<string, EventListener>;
children?: (HTMLElement | string)[];
}
export function createElement<K extends keyof HTMLElementTagNameMap>(
tag: K,
options: CreateElementOptions = {}
): HTMLElementTagNameMap[K] {
const el = document.createElement(tag);
if (options.className) el.className = options.className;
if (options.id) el.id = options.id;
if (options.text) el.textContent = options.text;
if (options.html) el.innerHTML = options.html;
if (options.attributes) {
Object.entries(options.attributes).forEach(([key, value]) => {
el.setAttribute(key, value);
});
}
if (options.style) {
Object.assign(el.style, options.style);
}
if (options.events) {
Object.entries(options.events).forEach(([event, handler]) => {
el.addEventListener(event, handler);
});
}
if (options.children) {
options.children.forEach(child => {
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
el.appendChild(child);
}
});
}
return el;
}
/**
* Add event listener with delegation
*/
export function on(
element: GumiElement | Document | Window,
event: string,
selectorOrHandler: string | EventListener | ((e: Event) => void),
handler?: EventListener | ((e: Event) => void)
): void {
const el = element instanceof Document || element instanceof Window
? element
: $(element);
if (!el) return;
if (typeof selectorOrHandler === 'function') {
el.addEventListener(event, selectorOrHandler as EventListener);
} else {
el.addEventListener(event, (e: Event) => {
const target = e.target as HTMLElement;
const delegateTarget = target.closest(selectorOrHandler);
if (delegateTarget && handler) {
(handler as Function).call(delegateTarget, e);
}
});
}
}
/**
* Remove event listener
*/
export function off(
element: GumiElement | Document | Window,
event: string,
handler: EventListener | ((e: Event) => void)
): void {
const el = element instanceof Document || element instanceof Window
? element
: $(element);
if (!el) return;
el.removeEventListener(event, handler as EventListener);
}
/**
* Trigger custom event
*/
export function trigger(
element: GumiElement,
eventName: string,
detail?: any
): void {
const el = $(element);
if (!el) return;
const event = new CustomEvent(eventName, {
bubbles: true,
cancelable: true,
detail
});
el.dispatchEvent(event);
}
/**
* Get or set attribute
*/
export function attr(
element: GumiElement,
name: string,
value?: string
): string | null | void {
const el = $(element);
if (!el) return;
if (value === undefined) {
return el.getAttribute(name);
} else {
el.setAttribute(name, value);
}
}
/**
* Check if element has class
*/
export function hasClass(element: GumiElement, className: string): boolean {
const el = $(element);
return el ? el.classList.contains(className) : false;
}
/**
* Add class(es) to element
*/
export function addClass(element: GumiElement, ...classNames: string[]): void {
const el = $(element);
if (!el) return;
el.classList.add(...classNames);
}
/**
* Remove class(es) from element
*/
export function removeClass(element: GumiElement, ...classNames: string[]): void {
const el = $(element);
if (!el) return;
el.classList.remove(...classNames);
}
/**
* Toggle class on element
*/
export function toggleClass(element: GumiElement, className: string, force?: boolean): boolean {
const el = $(element);
if (!el) return false;
return el.classList.toggle(className, force);
}
/**
* Get element's offset relative to document
*/
export function offset(element: GumiElement): { top: number; left: number } | null {
const el = $(element);
if (!el) return null;
const rect = el.getBoundingClientRect();
return {
top: rect.top + window.pageYOffset,
left: rect.left + window.pageXOffset
};
}
/**
* Get or set element's inner HTML
*/
export function html(element: GumiElement, content?: string): string | void {
const el = $(element);
if (!el) return;
if (content === undefined) {
return el.innerHTML;
} else {
el.innerHTML = content;
}
}
/**
* Get or set element's text content
*/
export function text(element: GumiElement, content?: string): string | void {
const el = $(element);
if (!el) return;
if (content === undefined) {
return el.textContent || '';
} else {
el.textContent = content;
}
}
/**
* Show element
*/
export function show(element: GumiElement): void {
const el = $(element);
if (!el) return;
const display = el.style.display;
if (display === 'none') {
el.style.display = '';
}
if (window.getComputedStyle(el).display === 'none') {
el.style.display = 'block';
}
}
/**
* Hide element
*/
export function hide(element: GumiElement): void {
const el = $(element);
if (!el) return;
el.style.display = 'none';
}
/**
* Toggle element visibility
*/
export function toggle(element: GumiElement): void {
const el = $(element);
if (!el) return;
if (window.getComputedStyle(el).display === 'none') {
show(el);
} else {
hide(el);
}
}