aboutsummaryrefslogtreecommitdiffstats
path: root/app/dom.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/dom.js')
-rw-r--r--app/dom.js706
1 files changed, 391 insertions, 315 deletions
diff --git a/app/dom.js b/app/dom.js
index 6e968bb..b1a8e8a 100644
--- a/app/dom.js
+++ b/app/dom.js
@@ -1,12 +1,28 @@
-/**
- * @typedef {Object} BaseElementOptions
- * @property {Partial<CSSStyleDeclaration>} [styles]
- * @property {string} [id]
- * @property {string[]} [classes]
- * @property {string} [textContent]
- * @property {HTMLElement[]} [children]
- * @property {Record<string, string>} [attributes]
- */
+import { House } from "models";
+
+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
@@ -25,129 +41,74 @@ export const ToastType = {
export class Dom {
/**
* Create a `<div>`
- * @param {BaseElementOptions} [options={}]
+ * @param {DomOptions} options
* @returns {HTMLDivElement}
*/
- static div(options = {}) {
- const {
- styles = {},
- id = "",
- classes = [],
- textContent = "",
- children = [],
- attributes = {},
- } = options;
-
+ static div(options) {
const div = document.createElement("div");
- Object.assign(div.style, styles);
- if (id) div.id = id;
- for (const cls of classes) div.classList.add(cls);
- if (textContent) div.textContent = textContent;
- for (const [k, v] of Object.entries(attributes)) div.setAttribute(k, v);
- if (children) div.append(...children);
+ 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);
return div;
}
/**
* Create a `<span>`
- * @param {BaseElementOptions} [options={}]
+ * @param {string} text
+ * @param {DomOptions} options
* @returns {HTMLSpanElement}
*/
- static span(options = {}) {
- const {
- styles = {},
- id = "",
- classes = [],
- textContent = "",
- children = [],
- attributes = {},
- } = options;
-
+ static span(text, options = new DomOptions()) {
const span = document.createElement("span");
- Object.assign(span.style, styles);
- if (id) span.id = id;
- for (const cls of classes) span.classList.add(cls);
- if (textContent) span.textContent = textContent;
- for (const [k, v] of Object.entries(attributes)) span.setAttribute(k, v);
- if (children) span.append(...children);
+ 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);
return span;
}
/**
* Create a `<button>`
- * @param {BaseElementOptions & { onClick?: (e: MouseEvent) => void }} [options={}]
+ * @param { string} text
+ * @param { (e: Event) => void } onClick
+ * @param {DomOptions} o
* @returns {HTMLButtonElement}
*/
- static button(options = {}) {
- const {
- styles = {},
- id = "",
- classes = [],
- textContent = "",
- children = [],
- attributes = {},
- onClick,
- } = options;
-
+ static button(text, onClick, o = new DomOptions()) {
const button = document.createElement("button");
- Object.assign(button.style, styles);
- if (id) button.id = id;
- for (const cls of classes) button.classList.add(cls);
- if (textContent) button.textContent = textContent;
- for (const [k, v] of Object.entries(attributes)) button.setAttribute(k, v);
- if (children) button.append(...children);
+ 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.children) button.append(...o.children);
if (onClick) button.addEventListener("click", onClick);
return button;
}
/**
- * @typedef {BaseElementOptions & {
- * type?: string,
- * placeholder?: string,
- * value?: string,
- * onInput?: (e: InputEvent) => void,
- * onChange?: (e: Event) => void
- * }} InputOptions
- */
-
- /**
* Create an `<input>`
- * @param {InputOptions} [options={}]
+ * @param { string} type
+ * @param { (e: Event) => void } onChange
+ * @param { string} value
+ * @param { string} placeholder
+ * @param {DomOptions} [o]
* @returns {HTMLInputElement}
*/
- static input(options = {}) {
- const {
- styles = {},
- id = "",
- classes = [],
- textContent = "",
- attributes = {},
- type = "text",
- placeholder = "",
- value = "",
- onInput,
- onChange,
- } = options;
-
+ static input(type, onChange, value = "", placeholder = "", o = new DomOptions()) {
const input = document.createElement("input");
- Object.assign(input.style, styles);
- if (id) input.id = id;
- for (const cls of classes) input.classList.add(cls);
- if (textContent) input.textContent = textContent;
- for (const [k, v] of Object.entries(attributes)) input.setAttribute(k, v);
+ 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;
-
- if (onInput) {
- input.addEventListener(
- "input",
- /** @param {Event} e */ (e) => {
- onInput(/** @type {InputEvent} */ (e));
- },
- );
- }
if (onChange) {
input.addEventListener("change", onChange);
}
@@ -155,112 +116,71 @@ export class Dom {
}
/**
- * @typedef {BaseElementOptions & { for?: string }} LabelOptions
- */
-
- /**
* Create a `<label>`
- * @param {LabelOptions} [options={}]
+ * @param {string} to
+ * @param {string} text
+ * @param {DomOptions} o
* @returns {HTMLLabelElement}
*/
- static label(options = {}) {
- const {
- styles = {},
- id = "",
- classes = [],
- textContent = "",
- children = [],
- attributes = {},
- for: htmlFor = "",
- } = options;
-
+ static label(to, text, o = new DomOptions()) {
const label = document.createElement("label");
- Object.assign(label.style, styles);
- if (id) label.id = id;
- for (const cls of classes) label.classList.add(cls);
- if (textContent) label.textContent = textContent;
- for (const [k, v] of Object.entries(attributes)) label.setAttribute(k, v);
- if (children) label.append(...children);
- label.htmlFor = htmlFor;
+ 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.children) label.append(...o.children);
+ label.textContent = text;
+ label.htmlFor = to;
return label;
}
/**
- * @typedef {BaseElementOptions & { level: 1|2|3|4|5|6 }} HeadingOptions
- */
-
- /**
* Create a heading `<h1>`–`<h6>`
- * @param {HeadingOptions["level"]} level
- * @param {Omit<HeadingOptions, "level">} [options={}]
+ * @param {1|2|3|4|5|6} level
+ * @param {string} text
+ * @param {DomOptions} o
* @returns {HTMLHeadingElement}
*/
- static heading(level, options = {}) {
- const {
- styles = {},
- id = "",
- classes = [],
- textContent = "",
- children = [],
- attributes = {},
- } = options;
-
+ static heading(level, text, o = new DomOptions()) {
const heading = document.createElement(`h${level}`);
- Object.assign(heading.style, styles);
- if (id) heading.id = id;
- for (const cls of classes) heading.classList.add(cls);
- if (textContent) heading.textContent = textContent;
- for (const [k, v] of Object.entries(attributes)) heading.setAttribute(k, v);
- if (children) heading.append(...children);
+ 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.children) heading.append(...o.children);
return heading;
}
/**
- * @typedef {BaseElementOptions & { src?: string }} ImgOptions
- */
-
- /**
* Create an `<img>`
- * @param {ImgOptions} [options={}]
+ * @param {string} src
+ * @param {DomOptions} o
* @returns {HTMLImageElement}
*/
- static img(options = {}) {
- const { styles = {}, id = "", classes = [], attributes = {}, src = "" } = options;
-
+ static img(src, o = new DomOptions()) {
const img = document.createElement("img");
- Object.assign(img.style, styles);
- if (id) img.id = id;
- for (const cls of classes) img.classList.add(cls);
- for (const [k, v] of Object.entries(attributes)) img.setAttribute(k, v);
+ 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;
return img;
}
/**
- * @typedef {BaseElementOptions & { onChange?: (e: Event) => void }} SelectOptions
- */
-
- /**
* Create a `<select>`
- * @param {SelectOptions} [options={}]
+ * @param {DomOptions} o
+ * @param { (e: Event) => void } onChange
* @returns {HTMLSelectElement}
*/
- static select(options = {}) {
- const {
- styles = {},
- id = "",
- classes = [],
- children = [],
- attributes = {},
- onChange,
- } = options;
-
+ static select(onChange, o = new DomOptions()) {
const select = document.createElement("select");
- Object.assign(select.style, styles);
- if (id) select.id = id;
- for (const cls of classes) select.classList.add(cls);
- for (const [k, v] of Object.entries(attributes)) select.setAttribute(k, v);
- if (children) select.append(...children);
+ Object.assign(select.style, o.styles);
+ 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.children) select.append(...o.children);
if (onChange) select.addEventListener("change", onChange);
return select;
}
@@ -282,26 +202,18 @@ export class Dom {
/**
* Create a `<p>`
- * @param {BaseElementOptions} [options={}]
+ * @param {string} text
+ * @param {DomOptions} o
* @returns {HTMLParagraphElement}
*/
- static p(options = {}) {
- const {
- styles = {},
- id = "",
- classes = [],
- textContent = "",
- children = [],
- attributes = {},
- } = options;
-
+ static p(text, o = new DomOptions()) {
const p = document.createElement("p");
- Object.assign(p.style, styles);
- if (id) p.id = id;
- for (const cls of classes) p.classList.add(cls);
- if (textContent) p.textContent = textContent;
- for (const [k, v] of Object.entries(attributes)) p.setAttribute(k, v);
- if (children) p.append(...children);
+ 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.children) p.append(...o.children);
return p;
}
@@ -328,20 +240,22 @@ export class Dom {
zIndex: "1000",
});
- const closeBtn = Dom.button({
- onClick: onClose,
- styles: {
- background: "none",
- border: "none",
- color: "#666",
- cursor: "pointer",
- fontSize: "24px",
- position: "absolute",
- right: "10px",
- top: "10px",
- },
- textContent: "×",
- });
+ const closeBtn = Dom.button(
+ "x",
+ onClose,
+ new DomOptions({
+ styles: {
+ background: "none",
+ border: "none",
+ color: "#666",
+ cursor: "pointer",
+ fontSize: "24px",
+ position: "absolute",
+ right: "10px",
+ top: "10px",
+ },
+ }),
+ );
modal.appendChild(closeBtn);
modal.addEventListener("close", onClose);
@@ -357,41 +271,50 @@ export class Dom {
const frag = document.createDocumentFragment();
/* Header */
- const header = Dom.div({
- styles: {
- alignItems: "center",
- display: "flex",
- justifyContent: "space-between",
- marginBottom: "20px",
- },
- });
- const title = Dom.heading(2, {
- styles: { color: "#333", fontSize: "20px", margin: "0" },
- textContent: house.address,
- });
- const score = Dom.span({
- styles: {
- background: "#e8f5e9",
- borderRadius: "4px",
- color: "#2e7d32",
- fontSize: "16px",
- fontWeight: "bold",
- padding: "4px 8px",
- },
- textContent: `Score: ${house.scores.current.toFixed(1)}`,
- });
+ const header = Dom.div(
+ new DomOptions({
+ styles: {
+ alignItems: "center",
+ display: "flex",
+ justifyContent: "space-between",
+ marginBottom: "20px",
+ },
+ }),
+ );
+
+ const title = Dom.heading(
+ 2,
+ house.address,
+ new DomOptions({
+ styles: { color: "#333", fontSize: "20px", margin: "0" },
+ }),
+ );
+ const score = Dom.span(
+ `Score: ${house.scores.current.toFixed(1)}`,
+ new DomOptions({
+ styles: {
+ background: "#e8f5e9",
+ borderRadius: "4px",
+ color: "#2e7d32",
+ fontSize: "16px",
+ fontWeight: "bold",
+ padding: "4px 8px",
+ },
+ }),
+ );
header.append(title, score);
frag.appendChild(header);
- /* Details grid */
- const grid = Dom.div({
- styles: {
- display: "grid",
- gap: "15px",
- gridTemplateColumns: "repeat(2,1fr)",
- marginBottom: "20px",
- },
- });
+ const grid = Dom.div(
+ new DomOptions({
+ styles: {
+ display: "grid",
+ gap: "15px",
+ gridTemplateColumns: "repeat(2,1fr)",
+ marginBottom: "20px",
+ },
+ }),
+ );
const details = [
{ label: "Price", value: `${house.price} €` },
{ label: "Building Type", value: house.buildingType },
@@ -407,49 +330,62 @@ export class Dom {
{ label: "Rooms", value: house.rooms?.toString() ?? "N/A" },
];
for (const { label, value } of details) {
- const item = Dom.div({
- children: [
- Dom.div({
- styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "4px" },
- textContent: label,
- }),
- Dom.div({ styles: { color: "#333", fontSize: "14px" }, textContent: value }),
- ],
- });
+ const item = Dom.div(
+ new DomOptions({
+ children: [
+ Dom.span(
+ label,
+ new DomOptions({
+ styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "4px" },
+ }),
+ ),
+ Dom.span(value, new DomOptions({ styles: { color: "#333", fontSize: "14px" } })),
+ ],
+ }),
+ );
grid.appendChild(item);
}
frag.appendChild(grid);
/* Description */
- const descSect = Dom.div({ styles: { marginBottom: "20px" } });
- const descTitle = Dom.div({
- styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "5px" },
- textContent: "Description",
- });
- const descText = Dom.p({
- styles: { color: "#333", fontSize: "14px", lineHeight: "1.4", marginTop: "5px" },
- textContent: house.description ?? "No description available.",
- });
+ const descSect = Dom.div(new DomOptions({ styles: { marginBottom: "20px" } }));
+ const descTitle = Dom.span(
+ "Description",
+ new DomOptions({
+ styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "5px" },
+ }),
+ );
+ const descText = Dom.p(
+ house.description ?? "No description available.",
+ new DomOptions({
+ styles: { color: "#333", fontSize: "14px", lineHeight: "1.4", marginTop: "5px" },
+ }),
+ );
descSect.append(descTitle, descText);
frag.appendChild(descSect);
/* Images */
if (house.images?.length) {
- const imgSect = Dom.div({ styles: { marginBottom: "20px" } });
- const imgTitle = Dom.div({
- styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "10px" },
- textContent: "Images",
- });
- const imgCont = Dom.div({
- styles: { display: "flex", gap: "10px", overflowX: "auto", paddingBottom: "5px" },
- });
+ const imgSect = Dom.div(new DomOptions({ styles: { marginBottom: "20px" } }));
+ const imgTitle = Dom.div(
+ new DomOptions({
+ styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "10px" },
+ }),
+ );
+ const imgCont = Dom.div(
+ new DomOptions({
+ styles: { display: "flex", gap: "10px", overflowX: "auto", paddingBottom: "5px" },
+ }),
+ );
for (const src of house.images.slice(0, 3)) {
imgCont.appendChild(
- Dom.img({
- attributes: { loading: "lazy" },
+ Dom.img(
src,
- styles: { borderRadius: "4px", flexShrink: "0", height: "100px" },
- }),
+ new DomOptions({
+ attributes: { loading: "lazy" },
+ styles: { borderRadius: "4px", flexShrink: "0", height: "100px" },
+ }),
+ ),
);
}
imgSect.append(imgTitle, imgCont);
@@ -471,26 +407,28 @@ export class Dom {
const bg =
type === ToastType.error ? "#f44336" : type === ToastType.warning ? "#ff9800" : "#4caf50";
- const toast = Dom.div({
- classes: ["toast", `toast-${type}`],
- id: "app-toast",
- styles: {
- background: bg,
- borderRadius: "4px",
- boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
- color: "white",
- fontSize: "14px",
- fontWeight: "500",
- maxWidth: "300px",
- padding: "12px 20px",
- position: "fixed",
- right: "20px",
- top: "20px",
- transition: "all .3s ease",
- zIndex: "10000",
- },
- textContent: message,
- });
+ const toast = Dom.div(
+ new DomOptions({
+ children: [Dom.p(message)],
+ classes: ["toast", `toast-${type}`],
+ id: "app-toast",
+ styles: {
+ background: bg,
+ borderRadius: "4px",
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
+ color: "white",
+ fontSize: "14px",
+ fontWeight: "500",
+ maxWidth: "300px",
+ padding: "12px 20px",
+ position: "fixed",
+ right: "20px",
+ top: "20px",
+ transition: "all .3s ease",
+ zIndex: "10000",
+ },
+ }),
+ );
document.body.appendChild(toast);
@@ -520,41 +458,179 @@ export class Dom {
* @returns {HTMLElement}
*/
static slider(id, labelText, weightKey, initialValue, onChange) {
- const group = Dom.div({
- styles: { display: "flex", flexDirection: "column", marginBottom: "1rem" },
- });
-
- const label = Dom.label({ for: id });
- const output = Dom.span({
- id: `${id}-value`,
- styles: { color: "#0066cc", fontSize: "0.85rem", fontWeight: "bold", textAlign: "center" },
- textContent: initialValue.toFixed(1),
- });
-
- const labelTextSpan = Dom.span({
- styles: { fontSize: "0.85rem" },
- textContent: labelText,
- });
+ const group = Dom.div(
+ new DomOptions({
+ styles: { display: "flex", flexDirection: "column", marginBottom: "1rem" },
+ }),
+ );
+
+ const label = Dom.label(id, labelText);
+ const output = Dom.span(
+ initialValue.toFixed(1),
+ new DomOptions({
+ id: `${id}-value`,
+ styles: { color: "#0066cc", fontSize: "0.85rem", fontWeight: "bold", textAlign: "center" },
+ }),
+ );
+
+ const labelTextSpan = Dom.span(
+ labelText,
+ new DomOptions({
+ styles: { fontSize: "0.85rem" },
+ }),
+ );
label.append(labelTextSpan, " ", output);
- const slider = Dom.input({
- attributes: { max: "1", min: "0", step: "0.1", value: initialValue.toString() },
- id,
- onInput: /** @param {Event} e */ (e) => {
+ const slider = Dom.input(
+ "range",
+ (e) => {
const target = /** @type {HTMLInputElement} */ (e.target);
const val = Number(target.value);
output.textContent = val.toFixed(1);
onChange(weightKey, val);
},
- styles: {
- margin: "0.5rem 0",
- width: "100%",
- },
- type: "range",
- });
+ "",
+ "",
+ new DomOptions({
+ attributes: { max: "1", min: "0", step: "0.1", value: initialValue.toString() },
+ id,
+ styles: {
+ margin: "0.5rem 0",
+ width: "100%",
+ },
+ }),
+ );
group.append(label, slider);
return group;
}
+
+ /**
+ * Build modal content for a house
+ * @param {House} house
+ * @returns {DocumentFragment}
+ */
+ static buildHouseModalContent(house) {
+ const frag = document.createDocumentFragment();
+
+ /* Header */
+ const header = Dom.div(
+ new DomOptions({
+ styles: {
+ alignItems: "center",
+ display: "flex",
+ justifyContent: "space-between",
+ marginBottom: "20px",
+ },
+ }),
+ );
+ const title = Dom.heading(
+ 2,
+ house.address,
+ new DomOptions({
+ styles: { color: "#333", fontSize: "20px", margin: "0" },
+ }),
+ );
+ const score = Dom.span(
+ `Score: ${house.scores.current}`,
+ new DomOptions({
+ styles: {
+ background: "#e8f5e9",
+ borderRadius: "4px",
+ color: "#2e7d32",
+ fontSize: "16px",
+ fontWeight: "bold",
+ padding: "4px 8px",
+ },
+ }),
+ );
+ header.append(title, score);
+ frag.appendChild(header);
+
+ /* Details grid */
+ const grid = Dom.div(
+ new DomOptions({
+ styles: {
+ display: "grid",
+ gap: "15px",
+ gridTemplateColumns: "repeat(2,1fr)",
+ marginBottom: "20px",
+ },
+ }),
+ );
+ const details = [
+ { label: "Price", value: `€${house.price.toLocaleString()}` },
+ { label: "Building Type", value: house.buildingType },
+ { label: "Construction Year", value: house.constructionYear?.toString() ?? "N/A" },
+ { label: "Living Area", value: `${house.livingArea} m²` },
+ { label: "District", value: house.district },
+ { label: "Rooms", value: house.rooms?.toString() ?? "N/A" },
+ ];
+ for (const { label, value } of details) {
+ const item = Dom.div(
+ new DomOptions({
+ children: [
+ Dom.span(
+ label,
+ new DomOptions({
+ styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "4px" },
+ }),
+ ),
+ Dom.span(value, new DomOptions({ styles: { color: "#333", fontSize: "14px" } })),
+ ],
+ }),
+ );
+ grid.appendChild(item);
+ }
+ frag.appendChild(grid);
+
+ /* Description */
+ const descSect = Dom.div(new DomOptions({ styles: { marginBottom: "20px" } }));
+ const descTitle = Dom.span(
+ "Description",
+ new DomOptions({
+ styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "5px" },
+ }),
+ );
+ const descText = Dom.p(
+ house.description || "No description available.",
+ new DomOptions({
+ styles: { color: "#333", fontSize: "14px", lineHeight: "1.4", marginTop: "5px" },
+ }),
+ );
+ descSect.append(descTitle, descText);
+ frag.appendChild(descSect);
+
+ /* Images */
+ if (house.images?.length) {
+ const imgSect = Dom.div(new DomOptions({ styles: { marginBottom: "20px" } }));
+ const imgTitle = Dom.span(
+ "Images",
+ new DomOptions({
+ styles: { fontSize: "14px", fontWeight: "bold", marginBottom: "10px" },
+ }),
+ );
+ const imgCont = Dom.div(
+ new DomOptions({
+ styles: { display: "flex", gap: "10px", overflowX: "auto", paddingBottom: "5px" },
+ }),
+ );
+ for (const src of house.images.slice(0, 3)) {
+ imgCont.appendChild(
+ Dom.img(
+ src,
+ new DomOptions({
+ attributes: { loading: "lazy" },
+ styles: { borderRadius: "4px", flexShrink: "0", height: "100px" },
+ }),
+ ),
+ );
+ }
+ imgSect.append(imgTitle, imgCont);
+ frag.appendChild(imgSect);
+ }
+
+ return frag;
+ }
}