diff options
Diffstat (limited to '')
| -rw-r--r-- | app/dom.js | 558 |
1 files changed, 295 insertions, 263 deletions
@@ -1,27 +1,3 @@ -export class DomOptions { - attributes; - children; - classes; - id; - styles; - - /** - * @param {Object} [options] - * @param {Partial<CSSStyleDeclaration>} [options.styles] - * @param {string|null} [options.id] - * @param {string[]} [options.classes] - * @param {HTMLElement[]} [options.children] - * @param {Record<string, string>} [options.attributes] - */ - constructor({ id = "", styles = {}, classes = [], children = [], attributes = {} } = {}) { - this.attributes = attributes; - this.children = children; - this.classes = classes; - this.id = id; - this.styles = styles; - } -} - /** * Toast notification types * @enum {string} @@ -35,351 +11,432 @@ export const ToastType = { export class Dom { /** * Create a `<div>` - * @param {DomOptions} options + * @param {Object} [o] + * @param {Partial<CSSStyleDeclaration>} [o.styles] + * @param {string} [o.id] + * @param {string[]} [o.classes] + * @param {HTMLElement[]} [o.children] + * @param {Record<string, string>} [o.attributes] * @returns {HTMLDivElement} */ - static div(options = new DomOptions()) { + static div(o = {}) { const div = document.createElement("div"); - Object.assign(div.style, options.styles); - if (options.id) div.id = options.id; - for (const cls of options.classes) div.classList.add(cls); - for (const [k, v] of Object.entries(options.attributes)) div.setAttribute(k, v); - if (options.children) div.append(...options.children); + if (o.styles) Object.assign(div.style, o.styles); + if (o.id) div.id = o.id; + if (o.classes) for (const cls of o.classes) div.classList.add(cls); + if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) div.setAttribute(k, v); + if (o.children) div.append(...o.children); return div; } /** * Create a `<span>` - * @param {string} text - * @param {DomOptions} options + * @param {Object} o + * @param {string} o.text + * @param {Partial<CSSStyleDeclaration>} [o.styles] + * @param {string} [o.id] + * @param {string[]} [o.classes] + * @param {HTMLElement[]} [o.children] + * @param {Record<string, string>} [o.attributes] * @returns {HTMLSpanElement} */ - static span(text, options = new DomOptions()) { + static span(o) { const span = document.createElement("span"); - Object.assign(span.style, options.styles); - if (options.id) span.id = options.id; - for (const cls of options.classes) span.classList.add(cls); - span.textContent = text; - for (const [k, v] of Object.entries(options.attributes)) span.setAttribute(k, v); - if (options.children) span.append(...options.children); + if (o.styles) Object.assign(span.style, o.styles); + if (o.id) span.id = o.id; + if (o.classes) for (const cls of o.classes) span.classList.add(cls); + span.textContent = o.text; + if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) span.setAttribute(k, v); + if (o.children) span.append(...o.children); return span; } /** * Create a `<button>` - * @param { string} text - * @param { (e: Event) => void } onClick - * @param {DomOptions} o + * @param {Object} o + * @param {string} [o.text] + * @param {(e: Event) => void} [o.onClick] + * @param {Partial<CSSStyleDeclaration>} [o.styles] + * @param {string} [o.id] + * @param {string[]} [o.classes] + * @param {HTMLElement[]} [o.children] + * @param {Record<string, string>} [o.attributes] * @returns {HTMLButtonElement} */ - static button(text, onClick, o = new DomOptions()) { + static button(o = {}) { const button = document.createElement("button"); - Object.assign(button.style, o.styles); + if (o.styles) Object.assign(button.style, o.styles); if (o.id) button.id = o.id; - for (const cls of o.classes) button.classList.add(cls); - if (text) button.textContent = text; - for (const [k, v] of Object.entries(o.attributes)) button.setAttribute(k, v); + if (o.classes) for (const cls of o.classes) button.classList.add(cls); + if (o.text) button.textContent = o.text; + if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) button.setAttribute(k, v); if (o.children) button.append(...o.children); - if (onClick) button.addEventListener("pointerdown", onClick); + if (o.onClick) button.addEventListener("pointerdown", o.onClick); return button; } /** * Create an `<input>` - * @param { string} type - * @param { (e: Event) => void } onChange - * @param { string|number} value - * @param { string} placeholder - * @param {DomOptions} [o] + * @param {Object} o + * @param {string} o.type + * @param {(e: Event) => void} [o.onChange] + * @param {(e: Event) => void} [o.onInput] + * @param {string|number} [o.value] + * @param {string} [o.placeholder] + * @param {string} [o.min] + * @param {string} [o.max] + * @param {string} [o.step] + * @param {Partial<CSSStyleDeclaration>} [o.styles] + * @param {string} [o.id] + * @param {string[]} [o.classes] + * @param {Record<string, string>} [o.attributes] * @returns {HTMLInputElement} */ - static input(type, onChange, value = "", placeholder = "", o = new DomOptions()) { + static input(o) { const input = document.createElement("input"); - Object.assign(input.style, o.styles); + if (o.styles) Object.assign(input.style, o.styles); if (o.id) input.id = o.id; - for (const cls of o.classes) input.classList.add(cls); - for (const [k, v] of Object.entries(o.attributes)) input.setAttribute(k, v); - - input.type = type; - input.placeholder = placeholder; - input.value = value.toString(); - if (onChange) { - input.addEventListener("change", onChange); + if (o.classes) for (const cls of o.classes) input.classList.add(cls); + if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) input.setAttribute(k, v); + + input.type = o.type; + if (o.placeholder) input.placeholder = o.placeholder; + if (o.value !== undefined) input.value = o.value.toString(); + if (o.min !== undefined) input.min = o.min; + if (o.max !== undefined) input.max = o.max; + if (o.step !== undefined) input.step = o.step; + + if (o.onChange) { + input.addEventListener("change", o.onChange); + } + if (o.onInput) { + input.addEventListener("input", o.onInput); } return input; } /** * Create a `<strong>` - * @param {string} text + * @param {Object} o + * @param {string} o.text + * @returns {HTMLElement} */ - static strong(text) { + static strong(o) { const strong = document.createElement("strong"); - strong.textContent = text; + strong.textContent = o.text; return strong; } /** * Create a `<a>` - * @param {string} url - * @param {DomOptions} o - * @param {string|undefined} text + * @param {Object} o + * @param {string} o.url + * @param {string} [o.text] + * @param {Partial<CSSStyleDeclaration>} [o.styles] + * @param {string} [o.id] + * @param {string[]} [o.classes] + * @param {HTMLElement[]} [o.children] + * @param {Record<string, string>} [o.attributes] + * @returns {HTMLAnchorElement} */ - static a(url, o, text = undefined) { + static a(o) { const link = document.createElement("a"); - if (text) link.text = text; - link.href = url; - Object.assign(link.style, o.styles); + if (o.text) link.text = o.text; + link.href = o.url; + if (o.styles) Object.assign(link.style, o.styles); if (o.id) link.id = o.id; - for (const cls of o.classes) link.classList.add(cls); - for (const [k, v] of Object.entries(o.attributes)) link.setAttribute(k, v); + if (o.classes) for (const cls of o.classes) link.classList.add(cls); + if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) link.setAttribute(k, v); if (o.children) link.append(...o.children); return link; } /** * Create a `<label>` - * @param {string} to - * @param {string} text - * @param {DomOptions} o + * @param {Object} o + * @param {string} o.to + * @param {string} [o.text] + * @param {Partial<CSSStyleDeclaration>} [o.styles] + * @param {string} [o.id] + * @param {string[]} [o.classes] + * @param {HTMLElement[]} [o.children] + * @param {Record<string, string>} [o.attributes] * @returns {HTMLLabelElement} */ - static label(to, text, o = new DomOptions()) { + static label(o) { const label = document.createElement("label"); - Object.assign(label.style, o.styles); + if (o.styles) Object.assign(label.style, o.styles); if (o.id) label.id = o.id; - for (const cls of o.classes) label.classList.add(cls); - for (const [k, v] of Object.entries(o.attributes)) label.setAttribute(k, v); + if (o.classes) for (const cls of o.classes) label.classList.add(cls); + if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) label.setAttribute(k, v); if (o.children) label.append(...o.children); - label.textContent = text; - label.htmlFor = to; + if (o.text) label.textContent = o.text; + label.htmlFor = o.to; return label; } /** * Create a heading `<h1>`–`<h6>` - * @param {1|2|3|4|5|6} level - * @param {string} text - * @param {DomOptions} o + * @param {Object} o + * @param {1|2|3|4|5|6} o.level + * @param {string} [o.text] + * @param {Partial<CSSStyleDeclaration>} [o.styles] + * @param {string} [o.id] + * @param {string[]} [o.classes] + * @param {HTMLElement[]} [o.children] + * @param {Record<string, string>} [o.attributes] * @returns {HTMLHeadingElement} */ - static heading(level, text, o = new DomOptions()) { - const heading = document.createElement(`h${level}`); - Object.assign(heading.style, o.styles); + static heading(o) { + const heading = document.createElement(`h${o.level}`); + if (o.styles) Object.assign(heading.style, o.styles); if (o.id) heading.id = o.id; - for (const cls of o.classes) heading.classList.add(cls); - if (text) heading.textContent = text; - for (const [k, v] of Object.entries(o.attributes)) heading.setAttribute(k, v); + if (o.classes) for (const cls of o.classes) heading.classList.add(cls); + if (o.text) heading.textContent = o.text; + if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) heading.setAttribute(k, v); if (o.children) heading.append(...o.children); return heading; } /** * Create an `<img>` - * @param {string} src - * @param {DomOptions} o + * @param {Object} o + * @param {string} o.src + * @param {Partial<CSSStyleDeclaration>} [o.styles] + * @param {string} [o.id] + * @param {string[]} [o.classes] + * @param {Record<string, string>} [o.attributes] * @returns {HTMLImageElement} */ - static img(src, o = new DomOptions()) { + static img(o) { const img = document.createElement("img"); - Object.assign(img.style, o.styles); + if (o.styles) Object.assign(img.style, o.styles); if (o.id) img.id = o.id; - for (const cls of o.classes) img.classList.add(cls); - for (const [k, v] of Object.entries(o.attributes)) img.setAttribute(k, v); - img.src = src; + if (o.classes) for (const cls of o.classes) img.classList.add(cls); + if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) img.setAttribute(k, v); + img.src = o.src; return img; } /** * Create a `<select>` - * @param {string|undefined} selected - * @param {DomOptions} o - * @param { (e: Event) => void } onChange + * @param {Object} o + * @param {string} [o.selected] + * @param {(e: Event) => void} [o.onChange] + * @param {Partial<CSSStyleDeclaration>} [o.styles] + * @param {string} [o.id] + * @param {string[]} [o.classes] + * @param {HTMLElement[]} [o.children] + * @param {Record<string, string>} [o.attributes] * @returns {HTMLSelectElement} */ - static select(selected = undefined, onChange, o = new DomOptions()) { + static select(o = {}) { const select = document.createElement("select"); - Object.assign(select.style, o.styles); - if (selected !== undefined) select.value = selected; + if (o.styles) Object.assign(select.style, o.styles); + if (o.selected !== undefined) select.value = o.selected; if (o.id) select.id = o.id; - for (const cls of o.classes) select.classList.add(cls); - for (const [k, v] of Object.entries(o.attributes)) select.setAttribute(k, v); + if (o.classes) for (const cls of o.classes) select.classList.add(cls); + if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) select.setAttribute(k, v); if (o.children) select.append(...o.children); - if (onChange) select.addEventListener("change", onChange); + if (o.onChange) select.addEventListener("change", o.onChange); return select; } /** * Create an `<option>` - * @param {string} value - * @param {string} text - * @param {boolean} [selected=false] + * @param {Object} o + * @param {string} o.value + * @param {string} o.text + * @param {boolean} [o.selected] * @returns {HTMLOptionElement} */ - static option(value, text, selected = false) { + static option(o) { const opt = document.createElement("option"); - opt.value = value; - opt.textContent = text; - opt.selected = selected; + opt.value = o.value; + opt.textContent = o.text; + opt.selected = o.selected || false; return opt; } /** * Create a `<p>` - * @param {string} text - * @param {DomOptions} o + * @param {Object} o + * @param {string} [o.text] + * @param {Partial<CSSStyleDeclaration>} [o.styles] + * @param {string} [o.id] + * @param {string[]} [o.classes] + * @param {HTMLElement[]} [o.children] + * @param {Record<string, string>} [o.attributes] * @returns {HTMLParagraphElement} */ - static p(text, o = new DomOptions()) { + static p(o = {}) { const p = document.createElement("p"); - Object.assign(p.style, o.styles); + if (o.styles) Object.assign(p.style, o.styles); if (o.id) p.id = o.id; - for (const cls of o.classes) p.classList.add(cls); - if (text) p.textContent = text; - for (const [k, v] of Object.entries(o.attributes)) p.setAttribute(k, v); + if (o.classes) for (const cls of o.classes) p.classList.add(cls); + if (o.text) p.textContent = o.text; + if (o.attributes) for (const [k, v] of Object.entries(o.attributes)) p.setAttribute(k, v); if (o.children) p.append(...o.children); return p; } /** * Create a dual range input (min and max) - * @param {number} min - Minimum possible value - * @param {number} max - Maximum possible value - * @param {number} currentMin - Current minimum value - * @param {number} currentMax - Current maximum value - * @param {number} step - Step size - * @param {(min: number, max: number) => void} onChange - Callback when range changes - * @param {DomOptions} options - DOM options + * @param {Object} o + * @param {number} o.min - Minimum possible value + * @param {number} o.max - Maximum possible value + * @param {number} o.currentMin - Current minimum value + * @param {number} o.currentMax - Current maximum value + * @param {number} o.step - Step size + * @param {(min: number, max: number) => void} o.onChange - Callback when range changes + * @param {Partial<CSSStyleDeclaration>} [o.styles] + * @param {string} [o.id] + * @param {string[]} [o.classes] + * @param {HTMLElement[]} [o.children] + * @param {Record<string, string>} [o.attributes] * @returns {HTMLDivElement} */ - static range(min, max, currentMin, currentMax, step, onChange, options = new DomOptions()) { + static range(o) { const container = document.createElement("div"); - Object.assign(container.style, options.styles); - if (options.id) container.id = options.id; - for (const cls of options.classes) container.classList.add(cls); - for (const [k, v] of Object.entries(options.attributes)) container.setAttribute(k, v); + if (o.styles) Object.assign(container.style, o.styles); + if (o.id) container.id = o.id; + if (o.classes) for (const cls of o.classes) container.classList.add(cls); + if (o.attributes) + for (const [k, v] of Object.entries(o.attributes)) container.setAttribute(k, v); // Ensure current values are within bounds - const safeCurrentMin = Math.max(min, Math.min(currentMin, max)); - const safeCurrentMax = Math.max(min, Math.min(currentMax, max)); + const safeCurrentMin = Math.max(o.min, Math.min(o.currentMin, o.max)); + const safeCurrentMax = Math.max(o.min, Math.min(o.currentMax, o.max)); // Create track container - const trackContainer = Dom.div( - new DomOptions({ - styles: { - alignItems: "center", - display: "flex", - height: "20px", - margin: "10px 0", - position: "relative", - }, - }), - ); + const trackContainer = Dom.div({ + styles: { + alignItems: "center", + display: "flex", + height: "20px", + margin: "10px 0", + position: "relative", + }, + }); // Create track - const track = Dom.div( - new DomOptions({ - styles: { - background: "#e0e0e0", - borderRadius: "4px", - height: "6px", - position: "relative", - width: "100%", - }, - }), - ); + const track = Dom.div({ + styles: { + background: "#e0e0e0", + borderRadius: "4px", + height: "6px", + position: "relative", + width: "100%", + }, + }); // Create active range - const activeRange = Dom.div( - new DomOptions({ - styles: { - background: "#4caf50", - borderRadius: "4px", - height: "100%", - left: "0%", - position: "absolute", - width: "100%", - }, - }), - ); - - // Create min slider - const minSlider = document.createElement("input"); - minSlider.type = "range"; - minSlider.min = min.toString(); - minSlider.max = max.toString(); - minSlider.step = step.toString(); - minSlider.value = safeCurrentMin.toString(); - Object.assign(minSlider.style, { - appearance: "none", - background: "transparent", - height: "100%", - pointerEvents: "none", - position: "absolute", - width: "100%", - zIndex: "2", + const activeRange = Dom.div({ + styles: { + background: "#4caf50", + borderRadius: "4px", + height: "100%", + left: "0%", + position: "absolute", + width: "100%", + }, + }); + + // Create min slider using Dom.input + const minSlider = Dom.input({ + max: o.max.toString(), + min: o.min.toString(), + onInput: () => { + const minVal = parseInt(minSlider.value, 10); + const maxVal = parseInt(maxSlider.value, 10); + + if (minVal > maxVal) { + minSlider.value = maxVal.toString(); + } + + updateActiveRange(); + o.onChange(parseInt(minSlider.value, 10), parseInt(maxSlider.value, 10)); + }, + step: o.step.toString(), + styles: { + appearance: "none", + background: "transparent", + height: "100%", + pointerEvents: "none", + position: "absolute", + width: "100%", + zIndex: "2", + }, + type: "range", + value: safeCurrentMin, }); minSlider.style.pointerEvents = "auto"; - // Create max slider - const maxSlider = document.createElement("input"); - maxSlider.type = "range"; - maxSlider.min = min.toString(); - maxSlider.max = max.toString(); - maxSlider.step = step.toString(); - maxSlider.value = safeCurrentMax.toString(); - Object.assign(maxSlider.style, { - appearance: "none", - background: "transparent", - height: "100%", - pointerEvents: "none", - position: "absolute", - width: "100%", - zIndex: "2", + // Create max slider using Dom.input + const maxSlider = Dom.input({ + max: o.max.toString(), + min: o.min.toString(), + onInput: () => { + const minVal = parseInt(minSlider.value, 10); + const maxVal = parseInt(maxSlider.value, 10); + + if (maxVal < minVal) { + maxSlider.value = minVal.toString(); + } + + updateActiveRange(); + o.onChange(parseInt(minSlider.value, 10), parseInt(maxSlider.value, 10)); + }, + step: o.step.toString(), + styles: { + appearance: "none", + background: "transparent", + height: "100%", + pointerEvents: "none", + position: "absolute", + width: "100%", + zIndex: "2", + }, + type: "range", + value: safeCurrentMax, }); maxSlider.style.pointerEvents = "auto"; // Value displays - const minValueDisplay = Dom.span( - safeCurrentMin.toString(), - new DomOptions({ - styles: { - color: "#0066cc", - fontSize: "0.85rem", - fontWeight: "bold", - }, - }), - ); - - const maxValueDisplay = Dom.span( - safeCurrentMax.toString(), - new DomOptions({ - styles: { - color: "#0066cc", - fontSize: "0.85rem", - fontWeight: "bold", - }, - }), - ); - - const valueDisplay = Dom.div( - new DomOptions({ - children: [minValueDisplay, maxValueDisplay], - styles: { - display: "flex", - justifyContent: "space-between", - marginBottom: "5px", - }, - }), - ); + const minValueDisplay = Dom.span({ + styles: { + color: "#0066cc", + fontSize: "0.85rem", + fontWeight: "bold", + }, + text: safeCurrentMin.toString(), + }); + + const maxValueDisplay = Dom.span({ + styles: { + color: "#0066cc", + fontSize: "0.85rem", + fontWeight: "bold", + }, + text: safeCurrentMax.toString(), + }); + + const valueDisplay = Dom.div({ + children: [minValueDisplay, maxValueDisplay], + styles: { + display: "flex", + justifyContent: "space-between", + marginBottom: "5px", + }, + }); // Update active range position const updateActiveRange = () => { const minVal = parseInt(minSlider.value, 10); const maxVal = parseInt(maxSlider.value, 10); - const minPercent = ((minVal - min) / (max - min)) * 100; - const maxPercent = ((maxVal - min) / (max - min)) * 100; + const minPercent = ((minVal - o.min) / (o.max - o.min)) * 100; + const maxPercent = ((maxVal - o.min) / (o.max - o.min)) * 100; activeRange.style.left = `${minPercent}%`; activeRange.style.width = `${maxPercent - minPercent}%`; @@ -388,31 +445,6 @@ export class Dom { maxValueDisplay.textContent = maxVal.toString(); }; - // Event listeners - minSlider.addEventListener("input", () => { - const minVal = parseInt(minSlider.value, 10); - const maxVal = parseInt(maxSlider.value, 10); - - if (minVal > maxVal) { - minSlider.value = maxVal.toString(); - } - - updateActiveRange(); - onChange(parseInt(minSlider.value, 10), parseInt(maxSlider.value, 10)); - }); - - maxSlider.addEventListener("input", () => { - const minVal = parseInt(minSlider.value, 10); - const maxVal = parseInt(maxSlider.value, 10); - - if (maxVal < minVal) { - maxSlider.value = minVal.toString(); - } - - updateActiveRange(); - onChange(parseInt(minSlider.value, 10), parseInt(maxSlider.value, 10)); - }); - // Initial update updateActiveRange(); @@ -423,7 +455,7 @@ export class Dom { // Assemble container container.append(valueDisplay, trackContainer); - if (options.children) container.append(...options.children); + if (o.children) container.append(...o.children); return container; } |
