mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-07-02 04:32:14 +02:00
Compare commits
11 Commits
main
...
a1d2c69049
Author | SHA1 | Date | |
---|---|---|---|
a1d2c69049
|
|||
7472cea880
|
|||
7664932041
|
|||
ad255e3e19
|
|||
74ed180919
|
|||
a459c0d786
|
|||
a1831d2cc4
|
|||
a8fa048522
|
|||
4dba3a292a
|
|||
8415f5cf6f
|
|||
7e3b54153d
|
12
README.md
12
README.md
@ -1,5 +1,5 @@
|
|||||||
**Alpha**
|
**Alpha**
|
||||||
| [Docs&Examples](https://jaandrle.github.io/deka-dom-el "Official documentation and guide site")
|
| [Docs](https://jaandrle.github.io/deka-dom-el "Official documentation and guide site")
|
||||||
| [NPM](https://www.npmjs.com/package/deka-dom-el "Official NPM package page")
|
| [NPM](https://www.npmjs.com/package/deka-dom-el "Official NPM package page")
|
||||||
| [GitHub](https://github.com/jaandrle/deka-dom-el "Official GitHub repository")
|
| [GitHub](https://github.com/jaandrle/deka-dom-el "Official GitHub repository")
|
||||||
([*Gitea*](https://gitea.jaandrle.cz/jaandrle/deka-dom-el "GitHub repository mirror on my own Gitea instance"))
|
([*Gitea*](https://gitea.jaandrle.cz/jaandrle/deka-dom-el "GitHub repository mirror on my own Gitea instance"))
|
||||||
@ -9,7 +9,7 @@
|
|||||||
```javascript
|
```javascript
|
||||||
// 🌟 Reactive component with clear separation of concerns
|
// 🌟 Reactive component with clear separation of concerns
|
||||||
document.body.append(
|
document.body.append(
|
||||||
el(EmojiCounter, { initial: "🚀" }),
|
el(EmojiCounter, { initial: "🚀" })
|
||||||
);
|
);
|
||||||
|
|
||||||
function EmojiCounter({ initial }) {
|
function EmojiCounter({ initial }) {
|
||||||
@ -34,7 +34,7 @@ function EmojiCounter({ initial }) {
|
|||||||
el(Option, "🎉"),
|
el(Option, "🎉"),
|
||||||
el(Option, "🚀"),
|
el(Option, "🚀"),
|
||||||
el(Option, "💖"),
|
el(Option, "💖"),
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function Option({ textContent }){
|
function Option({ textContent }){
|
||||||
@ -56,10 +56,10 @@ Creating reactive elements, components, and Web Components using the native
|
|||||||
## Features at a Glance
|
## Features at a Glance
|
||||||
|
|
||||||
- ✅ **No build step required** — use directly in browsers or Node.js
|
- ✅ **No build step required** — use directly in browsers or Node.js
|
||||||
- ✅ **Minimalized footprint** — ~10-15kB minified bundle (original goal 10kB), **zero**/minimal dependencies and
|
- ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with **zero**/minimal dependencies
|
||||||
small in-memory size (auto-releasing resources as much as possible)
|
- ✅ **Declarative & functional approach** for clean, maintainable code
|
||||||
- ✅ **Declarative & functional approach support** for clean, maintainable code
|
|
||||||
- ✅ **Signals and events** for reactive UI
|
- ✅ **Signals and events** for reactive UI
|
||||||
|
- ✅ **Auto-releasing resources** for memory management but nice development experience
|
||||||
- ✅ **Memoization for performance** — optimize rendering with intelligent caching
|
- ✅ **Memoization for performance** — optimize rendering with intelligent caching
|
||||||
- ☑️ **Optional build-in signals** with support for custom reactive implementations (#39)
|
- ☑️ **Optional build-in signals** with support for custom reactive implementations (#39)
|
||||||
- ☑️ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
|
- ☑️ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
|
||||||
|
@ -9,4 +9,3 @@ npx editorconfig-checker -format gcc ${additional}
|
|||||||
npx jshint index.js src ${additional}
|
npx jshint index.js src ${additional}
|
||||||
[ "$one" = 'vim' ] && exit 0
|
[ "$one" = 'vim' ] && exit 0
|
||||||
npx size-limit
|
npx size-limit
|
||||||
npx publint
|
|
||||||
|
5
dist/esm-with-signals.js
vendored
5
dist/esm-with-signals.js
vendored
@ -430,10 +430,9 @@ function createElement(tag, attributes, ...addons) {
|
|||||||
scoped = 1;
|
scoped = 1;
|
||||||
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
||||||
scope.push({ scope: tag, host });
|
scope.push({ scope: tag, host });
|
||||||
el = /** @type {Element} */
|
el = tag(attributes || void 0);
|
||||||
tag(attributes || void 0);
|
|
||||||
if (el.nodeName === "#comment") break;
|
|
||||||
const is_fragment = isInstance(el, enviroment.F);
|
const is_fragment = isInstance(el, enviroment.F);
|
||||||
|
if (el.nodeName === "#comment") break;
|
||||||
const el_mark = createElement.mark({
|
const el_mark = createElement.mark({
|
||||||
type: "component",
|
type: "component",
|
||||||
name: tag.name,
|
name: tag.name,
|
||||||
|
2
dist/esm-with-signals.min.js
vendored
2
dist/esm-with-signals.min.js
vendored
File diff suppressed because one or more lines are too long
5
dist/esm.js
vendored
5
dist/esm.js
vendored
@ -414,10 +414,9 @@ function createElement(tag, attributes, ...addons) {
|
|||||||
scoped = 1;
|
scoped = 1;
|
||||||
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
||||||
scope.push({ scope: tag, host });
|
scope.push({ scope: tag, host });
|
||||||
el = /** @type {Element} */
|
el = tag(attributes || void 0);
|
||||||
tag(attributes || void 0);
|
|
||||||
if (el.nodeName === "#comment") break;
|
|
||||||
const is_fragment = isInstance(el, enviroment.F);
|
const is_fragment = isInstance(el, enviroment.F);
|
||||||
|
if (el.nodeName === "#comment") break;
|
||||||
const el_mark = createElement.mark({
|
const el_mark = createElement.mark({
|
||||||
type: "component",
|
type: "component",
|
||||||
name: tag.name,
|
name: tag.name,
|
||||||
|
2
dist/esm.min.js
vendored
2
dist/esm.min.js
vendored
File diff suppressed because one or more lines are too long
5
dist/iife-with-signals.js
vendored
5
dist/iife-with-signals.js
vendored
@ -475,10 +475,9 @@ var DDE = (() => {
|
|||||||
scoped = 1;
|
scoped = 1;
|
||||||
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
||||||
scope.push({ scope: tag, host });
|
scope.push({ scope: tag, host });
|
||||||
el = /** @type {Element} */
|
el = tag(attributes || void 0);
|
||||||
tag(attributes || void 0);
|
|
||||||
if (el.nodeName === "#comment") break;
|
|
||||||
const is_fragment = isInstance(el, enviroment.F);
|
const is_fragment = isInstance(el, enviroment.F);
|
||||||
|
if (el.nodeName === "#comment") break;
|
||||||
const el_mark = createElement.mark({
|
const el_mark = createElement.mark({
|
||||||
type: "component",
|
type: "component",
|
||||||
name: tag.name,
|
name: tag.name,
|
||||||
|
2
dist/iife-with-signals.min.js
vendored
2
dist/iife-with-signals.min.js
vendored
File diff suppressed because one or more lines are too long
5
dist/iife.js
vendored
5
dist/iife.js
vendored
@ -456,10 +456,9 @@ var DDE = (() => {
|
|||||||
scoped = 1;
|
scoped = 1;
|
||||||
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
const host = (...c) => !c.length ? el_host : (scoped === 1 ? addons.unshift(...c) : c.forEach((c2) => c2(el_host)), void 0);
|
||||||
scope.push({ scope: tag, host });
|
scope.push({ scope: tag, host });
|
||||||
el = /** @type {Element} */
|
el = tag(attributes || void 0);
|
||||||
tag(attributes || void 0);
|
|
||||||
if (el.nodeName === "#comment") break;
|
|
||||||
const is_fragment = isInstance(el, enviroment.F);
|
const is_fragment = isInstance(el, enviroment.F);
|
||||||
|
if (el.nodeName === "#comment") break;
|
||||||
const el_mark = createElement.mark({
|
const el_mark = createElement.mark({
|
||||||
type: "component",
|
type: "component",
|
||||||
name: tag.name,
|
name: tag.name,
|
||||||
|
2
dist/iife.min.js
vendored
2
dist/iife.min.js
vendored
File diff suppressed because one or more lines are too long
@ -82,10 +82,10 @@ export function ImageGallery(images= imagesSample) {
|
|||||||
closeLightbox();
|
closeLightbox();
|
||||||
break;
|
break;
|
||||||
case 'ArrowLeft':
|
case 'ArrowLeft':
|
||||||
onPrevImage(e);
|
document.querySelector('.lightbox-prev-btn').click();
|
||||||
break;
|
break;
|
||||||
case 'ArrowRight':
|
case 'ArrowRight':
|
||||||
onNextImage(e);
|
document.querySelector('.lightbox-next-btn').click();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,12 +126,12 @@ export function ImageGallery(images= imagesSample) {
|
|||||||
el("div", {
|
el("div", {
|
||||||
className: "gallery-item",
|
className: "gallery-item",
|
||||||
dataTag: image.alt.toLowerCase()
|
dataTag: image.alt.toLowerCase()
|
||||||
}, onImageClick(image.id)).append(
|
}).append(
|
||||||
el("img", {
|
el("img", {
|
||||||
src: image.src,
|
src: image.src,
|
||||||
alt: image.alt,
|
alt: image.alt,
|
||||||
loading: "lazy"
|
loading: "lazy"
|
||||||
}),
|
}, onImageClick(image.id)),
|
||||||
el("div", { className: "gallery-item-caption" }).append(
|
el("div", { className: "gallery-item-caption" }).append(
|
||||||
el("h3", image.title),
|
el("h3", image.title),
|
||||||
el("p", image.alt)
|
el("p", image.alt)
|
||||||
@ -146,41 +146,41 @@ export function ImageGallery(images= imagesSample) {
|
|||||||
S.el(isLightboxOpen, open => !open
|
S.el(isLightboxOpen, open => !open
|
||||||
? el()
|
? el()
|
||||||
: el("div", { className: "lightbox-overlay" }, on("click", closeLightbox)).append(
|
: el("div", { className: "lightbox-overlay" }, on("click", closeLightbox)).append(
|
||||||
el("div", {
|
el("div", {
|
||||||
className: "lightbox-content",
|
className: "lightbox-content",
|
||||||
onClick: e => e.stopPropagation() // Prevent closing when clicking inside
|
onClick: e => e.stopPropagation() // Prevent closing when clicking inside
|
||||||
}).append(
|
}).append(
|
||||||
el("button", {
|
el("button", {
|
||||||
className: "lightbox-close-btn",
|
className: "lightbox-close-btn",
|
||||||
ariaLabel: "Close lightbox",
|
"aria-label": "Close lightbox"
|
||||||
}, on("click", closeLightbox)).append("×"),
|
}, on("click", closeLightbox)).append("×"),
|
||||||
|
|
||||||
el("button", {
|
el("button", {
|
||||||
className: "lightbox-prev-btn",
|
className: "lightbox-prev-btn",
|
||||||
ariaLabel: "Previous image",
|
"aria-label": "Previous image"
|
||||||
}, on("click", onPrevImage)).append("❮"),
|
}, on("click", onPrevImage)).append("❮"),
|
||||||
|
|
||||||
el("button", {
|
el("button", {
|
||||||
className: "lightbox-next-btn",
|
className: "lightbox-next-btn",
|
||||||
ariaLabel: "Next image",
|
"aria-label": "Next image"
|
||||||
}, on("click", onNextImage)).append("❯"),
|
}, on("click", onNextImage)).append("❯"),
|
||||||
|
|
||||||
S.el(selectedImage, img => !img
|
S.el(selectedImage, img => !img
|
||||||
? el()
|
? el()
|
||||||
: el("div", { className: "lightbox-image-container" }).append(
|
: el("div", { className: "lightbox-image-container" }).append(
|
||||||
el("img", {
|
el("img", {
|
||||||
src: img.src,
|
src: img.src,
|
||||||
alt: img.alt,
|
alt: img.alt,
|
||||||
className: "lightbox-image",
|
className: "lightbox-image"
|
||||||
}),
|
}),
|
||||||
el("div", { className: "lightbox-caption" }).append(
|
el("div", { className: "lightbox-caption" }).append(
|
||||||
el("h2", img.title),
|
el("h2", img.title),
|
||||||
el("p", img.alt),
|
el("p", img.alt)
|
||||||
),
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
),
|
)
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,509 +0,0 @@
|
|||||||
import { el, on, scope } from "deka-dom-el";
|
|
||||||
import { S } from "deka-dom-el/signals";
|
|
||||||
|
|
||||||
export function ProductCatalog() {
|
|
||||||
const { signal }= scope;
|
|
||||||
|
|
||||||
const itemsPerPage = 5;
|
|
||||||
const products = asyncSignal(S,
|
|
||||||
fetchProducts,
|
|
||||||
{ initial: [], keepLast: true, signal });
|
|
||||||
const searchTerm = S("");
|
|
||||||
const handleSearch = (e) => searchTerm.set(e.target.value);
|
|
||||||
const sortOrder = S("default");
|
|
||||||
const handleSort = (e) => sortOrder.set(e.target.value);
|
|
||||||
const page = S(1);
|
|
||||||
const handlePageChange = (newPage) => page.set(newPage);
|
|
||||||
const resetFilters = () => {
|
|
||||||
searchTerm.set("");
|
|
||||||
sortOrder.set("default");
|
|
||||||
page.set(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredProducts = S(() => {
|
|
||||||
if (products.status.get() !== "resolved") return [];
|
|
||||||
|
|
||||||
const results = products.result.get().filter(product =>
|
|
||||||
product.title.toLowerCase().includes(searchTerm.get().toLowerCase()) ||
|
|
||||||
product.description.toLowerCase().includes(searchTerm.get().toLowerCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
return [...results].sort((a, b) => {
|
|
||||||
const order = sortOrder.get();
|
|
||||||
if (order === "price-asc") return a.price - b.price;
|
|
||||||
if (order === "price-desc") return b.price - a.price;
|
|
||||||
if (order === "rating") return b.rating - a.rating;
|
|
||||||
return 0; // default: no sorting
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const totalPages = S(() => Math.ceil(filteredProducts.get().length / itemsPerPage));
|
|
||||||
const paginatedProducts = S(() => {
|
|
||||||
const currentPage = page.get();
|
|
||||||
const filtered = filteredProducts.get();
|
|
||||||
const start = (currentPage - 1) * itemsPerPage;
|
|
||||||
return filtered.slice(start, start + itemsPerPage);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Component structure
|
|
||||||
return el("div", { className: "product-catalog" }).append(
|
|
||||||
el("header", { className: "catalog-header" }).append(
|
|
||||||
el("h2", "Product Catalog"),
|
|
||||||
el("div", { className: "toolbar" }).append(
|
|
||||||
el("button", {
|
|
||||||
className: "refresh-btn",
|
|
||||||
textContent: "Refresh Products",
|
|
||||||
type: "button",
|
|
||||||
onclick: () => products.invoke(),
|
|
||||||
}),
|
|
||||||
el("button", {
|
|
||||||
className: "reset-btn",
|
|
||||||
textContent: "Reset Filters",
|
|
||||||
type: "button",
|
|
||||||
onclick: resetFilters,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
// Search and filter controls
|
|
||||||
el("div", { className: "controls" }).append(
|
|
||||||
el("div", { className: "search-box" }).append(
|
|
||||||
el("input", {
|
|
||||||
type: "search",
|
|
||||||
placeholder: "Search products...",
|
|
||||||
value: searchTerm,
|
|
||||||
oninput: handleSearch,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
el("div", { className: "sort-options" }).append(
|
|
||||||
el("label", "Sort by: "),
|
|
||||||
el("select", { onchange: handleSort }, on.defer(el => el.value = sortOrder.get())).append(
|
|
||||||
el("option", { value: "default", textContent: "Default" }),
|
|
||||||
el("option", { value: "price-asc", textContent: "Price: Low to High" }),
|
|
||||||
el("option", { value: "price-desc", textContent: "Price: High to Low" }),
|
|
||||||
el("option", { value: "rating", textContent: "Top Rated" })
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
// Status indicators
|
|
||||||
el("div", { className: "status-container" }).append(
|
|
||||||
S.el(products.status, status =>
|
|
||||||
status === "pending" ?
|
|
||||||
el("div", { className: "loader" }).append(
|
|
||||||
el("div", { className: "spinner" }),
|
|
||||||
el("p", "Loading products...")
|
|
||||||
)
|
|
||||||
: status === "rejected" ?
|
|
||||||
el("div", { className: "error-message" }).append(
|
|
||||||
el("p", products.error.get().message),
|
|
||||||
el("button", {
|
|
||||||
textContent: "Try Again",
|
|
||||||
onclick: () => products.invoke()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
: el()
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
// Results count
|
|
||||||
S.el(S(()=> [filteredProducts.get(), searchTerm.get()]), ([filtered, term]) =>
|
|
||||||
products.status.get() === "resolved"
|
|
||||||
? el("div", {
|
|
||||||
className: "results-info",
|
|
||||||
textContent: term ?
|
|
||||||
`Found ${filtered.length} products matching "${term}"`
|
|
||||||
: `Showing all ${filtered.length} products`
|
|
||||||
})
|
|
||||||
: el()
|
|
||||||
),
|
|
||||||
|
|
||||||
// Products grid
|
|
||||||
el("div", { className: "products-grid" }).append(
|
|
||||||
S.el(paginatedProducts, paginatedItems =>
|
|
||||||
products.status.get() === "resolved" && paginatedItems.length > 0 ?
|
|
||||||
paginatedItems.map(product => el(ProductCard, { product }))
|
|
||||||
: products.status.get() === "resolved" && paginatedItems.length === 0 ?
|
|
||||||
el("p", { className: "no-results", textContent: "No products found matching your criteria." })
|
|
||||||
: el()
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
// Pagination
|
|
||||||
S.el(S(()=> [totalPages.get(), page.get()]), ([total, current]) =>
|
|
||||||
products.status.get() === "resolved" && total > 1 ?
|
|
||||||
el("div", { className: "pagination" }).append(
|
|
||||||
el("button", {
|
|
||||||
textContent: "Previous",
|
|
||||||
disabled: current === 1,
|
|
||||||
onclick: () => handlePageChange(current - 1)
|
|
||||||
}),
|
|
||||||
...Array.from({ length: total }, (_, i) => i + 1).map(num =>
|
|
||||||
el("button", {
|
|
||||||
className: num === current ? "current-page" : "",
|
|
||||||
textContent: num,
|
|
||||||
onclick: () => handlePageChange(num)
|
|
||||||
})
|
|
||||||
),
|
|
||||||
el("button", {
|
|
||||||
textContent: "Next",
|
|
||||||
disabled: current === total,
|
|
||||||
onclick: () => handlePageChange(current + 1)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
: el()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Product card component
|
|
||||||
function ProductCard({ product }) {
|
|
||||||
const showDetails = S(false);
|
|
||||||
|
|
||||||
return el("div", { className: "product-card" }).append(
|
|
||||||
el("div", { className: "product-image" }).append(
|
|
||||||
el("img", { src: product.thumbnail, alt: product.title })
|
|
||||||
),
|
|
||||||
el("div", { className: "product-info" }).append(
|
|
||||||
el("h3", { className: "product-title", textContent: product.title }),
|
|
||||||
el("div", { className: "product-price-rating" }).append(
|
|
||||||
el("span", { className: "product-price", textContent: `$${product.price.toFixed(2)}` }),
|
|
||||||
el("span", { className: "product-rating" }).append(
|
|
||||||
el("span", { className: "stars", textContent: "★".repeat(Math.round(product.rating)) }),
|
|
||||||
el("span", { className: "rating-value", textContent: `(${product.rating})` }),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
el("p", { className: "product-category", textContent: `Category: ${product.category}` }),
|
|
||||||
S.el(showDetails, details =>
|
|
||||||
details ?
|
|
||||||
el("div", { className: "product-details" }).append(
|
|
||||||
el("p", { className: "product-description", textContent: product.description }),
|
|
||||||
el("div", { className: "product-meta" }).append(
|
|
||||||
el("p", `Brand: ${product.brand}`),
|
|
||||||
el("p", `Stock: ${product.stock} units`),
|
|
||||||
el("p", `Discount: ${product.discountPercentage}%`)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: el()
|
|
||||||
),
|
|
||||||
el("div", { className: "product-actions" }).append(
|
|
||||||
el("button", {
|
|
||||||
className: "details-btn",
|
|
||||||
textContent: S(() => showDetails.get() ? "Hide Details" : "Show Details"),
|
|
||||||
onclick: () => showDetails.set(!showDetails.get())
|
|
||||||
}),
|
|
||||||
el("button", {
|
|
||||||
className: "add-to-cart-btn",
|
|
||||||
textContent: "Add to Cart"
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data fetching function
|
|
||||||
async function fetchProducts({ signal }) {
|
|
||||||
await simulateNetworkDelay();
|
|
||||||
// Simulate random errors for demonstration
|
|
||||||
if (Math.random() > 0.9) throw new Error("Failed to load products. Network error.");
|
|
||||||
|
|
||||||
const response = await fetch("https://dummyjson.com/products", { signal });
|
|
||||||
if (!response.ok) throw new Error(`API error: ${response.status}`);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
return data.products.slice(0, 20); // Limit to 20 products for the demo
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility for simulating network latency
|
|
||||||
function simulateNetworkDelay(min = 300, max = 1200) {
|
|
||||||
const delay = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
||||||
return new Promise(resolve => setTimeout(resolve, delay));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom hook for async data fetching with signals
|
|
||||||
* @template T
|
|
||||||
* @param {typeof S} S - Signal constructor
|
|
||||||
* @param {(params: { signal: AbortSignal }) => Promise<T>} invoker - Async function to execute
|
|
||||||
* @param {{ initial?: T, keepLast?: boolean, signal?: AbortSignal }} options - Configuration options
|
|
||||||
* @returns {Object} Status signals and control methods
|
|
||||||
*/
|
|
||||||
export function asyncSignal(S, invoker, { initial, keepLast, signal } = {}) {
|
|
||||||
/** @type {(s: AbortSignal) => AbortSignal} */
|
|
||||||
const anySignal = !signal || !AbortSignal.any // TODO: make better
|
|
||||||
? s=> s
|
|
||||||
: s=> AbortSignal.any([s, signal]);
|
|
||||||
// Status tracking signals
|
|
||||||
const status = S("pending");
|
|
||||||
const result = S(initial);
|
|
||||||
const error = S(null);
|
|
||||||
let controller = null;
|
|
||||||
|
|
||||||
// Function to trigger data fetching
|
|
||||||
async function invoke() {
|
|
||||||
// Cancel any in-flight request
|
|
||||||
if (controller) controller.abort();
|
|
||||||
controller = new AbortController();
|
|
||||||
|
|
||||||
status.set("pending");
|
|
||||||
error.set(null);
|
|
||||||
if (!keepLast) result.set(initial);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await invoker({
|
|
||||||
signal: anySignal(controller.signal),
|
|
||||||
});
|
|
||||||
if (!controller.signal.aborted) {
|
|
||||||
status.set("resolved");
|
|
||||||
result.set(data);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e.name !== "AbortError") {
|
|
||||||
error.set(e);
|
|
||||||
status.set("rejected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial data fetch
|
|
||||||
invoke();
|
|
||||||
|
|
||||||
return { status, result, error, invoke };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the component
|
|
||||||
document.body.append(
|
|
||||||
el(ProductCatalog),
|
|
||||||
el("style", `
|
|
||||||
.product-catalog {
|
|
||||||
font-family: system-ui, -apple-system, sans-serif;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar button {
|
|
||||||
margin-left: 10px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
background: #4a6cf7;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
gap: 15px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box input {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
width: 300px;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sort-options select {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
display: inline-block;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
|
||||||
border-left-color: #4a6cf7;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
background: #ffebee;
|
|
||||||
color: #c62828;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 20px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-info {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.products-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card {
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-image img {
|
|
||||||
width: 100%;
|
|
||||||
height: 180px;
|
|
||||||
object-fit: cover;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-info {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-title {
|
|
||||||
margin: 0 0 10px;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
height: 2.4rem;
|
|
||||||
overflow: hidden;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-price-rating {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-price {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #4a6cf7;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stars {
|
|
||||||
color: gold;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-category {
|
|
||||||
color: #666;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-details {
|
|
||||||
margin: 15px 0;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-description {
|
|
||||||
line-height: 1.5;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-meta {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-actions button {
|
|
||||||
flex: 1;
|
|
||||||
padding: 8px 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-btn {
|
|
||||||
background: #eee;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-btn {
|
|
||||||
background: #4a6cf7;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 5px;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination button {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
background: white;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination button.current-page {
|
|
||||||
background: #4a6cf7;
|
|
||||||
color: white;
|
|
||||||
border-color: #4a6cf7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination button:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-results {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.controls {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.products-grid {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
);
|
|
@ -74,11 +74,13 @@ export function h3({ textContent, id }){
|
|||||||
if(!id) id= "h-"+textContent.toLowerCase().replaceAll(/\s/g, "-").replaceAll(/[^a-z-]/g, "");
|
if(!id) id= "h-"+textContent.toLowerCase().replaceAll(/\s/g, "-").replaceAll(/[^a-z-]/g, "");
|
||||||
return el("h3", { id }).append(
|
return el("h3", { id }).append(
|
||||||
el("a", {
|
el("a", {
|
||||||
className: "heading-anchor",
|
className: "heading-anchor",
|
||||||
href: "#"+id,
|
href: "#"+id,
|
||||||
title: `Link to this section: ${textContent}`,
|
textContent: "#",
|
||||||
|
title: `Link to this section: ${textContent}`,
|
||||||
|
"aria-label": `Link to section ${textContent}`
|
||||||
}),
|
}),
|
||||||
"# ",
|
" ",
|
||||||
textContent,
|
textContent,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -41,12 +41,7 @@ export function page({ pkg, info }){
|
|||||||
el("h4", t`Key Benefits of dd<el>`),
|
el("h4", t`Key Benefits of dd<el>`),
|
||||||
el("ul").append(
|
el("ul").append(
|
||||||
el("li", t`No build step required — use directly in the browser`),
|
el("li", t`No build step required — use directly in the browser`),
|
||||||
el("li", t`Minimalized footprint:`),
|
el("li", t`Lightweight core (~10–15kB minified) without unnecessary dependencies (0 at now 😇)`),
|
||||||
el("ul").append(
|
|
||||||
el("li", t`lightweight core (~10–15kB minified)`),
|
|
||||||
el("li", t`…without unnecessary dependencies (0 at now 😇)`),
|
|
||||||
el("li", t`auto-releasing resources with focus on performance and development experience`),
|
|
||||||
),
|
|
||||||
el("li", t`Natural DOM API — work with real DOM nodes, not abstractions`),
|
el("li", t`Natural DOM API — work with real DOM nodes, not abstractions`),
|
||||||
el("li", t`Built-in (but optional) reactivity with simplified but powerful signals system`),
|
el("li", t`Built-in (but optional) reactivity with simplified but powerful signals system`),
|
||||||
el("li", t`Clean code organization with the 3PS pattern`)
|
el("li", t`Clean code organization with the 3PS pattern`)
|
||||||
|
@ -151,7 +151,7 @@ export function header({ info: { href, title, description }, pkg }){
|
|||||||
),
|
),
|
||||||
el("span", {
|
el("span", {
|
||||||
className: "version-badge",
|
className: "version-badge",
|
||||||
ariaLabel: "Version",
|
"aria-label": "Version",
|
||||||
textContent: pkg.version || ""
|
textContent: pkg.version || ""
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@ -165,13 +165,13 @@ export function header({ info: { href, title, description }, pkg }){
|
|||||||
function nav({ href, pkg }){
|
function nav({ href, pkg }){
|
||||||
return el("nav", {
|
return el("nav", {
|
||||||
role: "navigation",
|
role: "navigation",
|
||||||
ariaLabel: "Main navigation",
|
"aria-label": "Main navigation",
|
||||||
className: nav.name
|
className: nav.name
|
||||||
}).append(
|
}).append(
|
||||||
el("a", {
|
el("a", {
|
||||||
href: pkg.homepage,
|
href: pkg.homepage,
|
||||||
className: "github-link",
|
className: "github-link",
|
||||||
ariaLabel: "View on GitHub",
|
"aria-label": "View on GitHub",
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
rel: "noopener noreferrer",
|
rel: "noopener noreferrer",
|
||||||
}).append(
|
}).append(
|
||||||
@ -185,11 +185,11 @@ function nav({ href, pkg }){
|
|||||||
return el("a", {
|
return el("a", {
|
||||||
href: isIndex ? "./" : p.href,
|
href: isIndex ? "./" : p.href,
|
||||||
title: p.description || `Go to ${p.title}`,
|
title: p.description || `Go to ${p.title}`,
|
||||||
ariaCurrent: isCurrent ? "page" : null,
|
"aria-current": isCurrent ? "page" : null,
|
||||||
}).append(
|
}).append(
|
||||||
el("span", {
|
el("span", {
|
||||||
className: "nav-number",
|
className: "nav-number",
|
||||||
ariaHidden: "true",
|
"aria-hidden": "true",
|
||||||
textContent: `${i+1}. `
|
textContent: `${i+1}. `
|
||||||
}),
|
}),
|
||||||
p.title
|
p.title
|
||||||
|
@ -168,22 +168,6 @@ export function page({ pkg, info }){
|
|||||||
el("li", t`name - The name of the component function`),
|
el("li", t`name - The name of the component function`),
|
||||||
el("li", t`host - Indicates whether the host is "this" (for DocumentFragments) or "parentElement"`),
|
el("li", t`host - Indicates whether the host is "this" (for DocumentFragments) or "parentElement"`),
|
||||||
),
|
),
|
||||||
el("div", { className: "warning" }).append(
|
|
||||||
el("p").append(T`
|
|
||||||
There are edge case when the mark can be missing. For example, the (utility) components with reactive
|
|
||||||
keys such as ${el("code", ".textContent")}, ${el("code", ".innerText")} or ${el("code", ".innerHTML")}.
|
|
||||||
As they change the content of the host element.
|
|
||||||
`),
|
|
||||||
el(code, { content: `
|
|
||||||
function Counter() {
|
|
||||||
const count = S(0);
|
|
||||||
return el("button",
|
|
||||||
{ textContent: count, type: "button" },
|
|
||||||
on("click", () => count.set(count.get() + 1)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
`, language: "js" }),
|
|
||||||
),
|
|
||||||
|
|
||||||
el("h4", t`Identifying reactive elements in the DOM`),
|
el("h4", t`Identifying reactive elements in the DOM`),
|
||||||
el("p").append(T`
|
el("p").append(T`
|
||||||
|
@ -33,6 +33,7 @@ export function page({ pkg, info }){
|
|||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/case-studies/interactive-form.js"), variant: "big" }),
|
el(example, { src: fileURL("./components/examples/case-studies/interactive-form.js"), variant: "big" }),
|
||||||
|
|
||||||
|
|
||||||
el(h3, t`Interactive Image Gallery`),
|
el(h3, t`Interactive Image Gallery`),
|
||||||
el("p").append(T`
|
el("p").append(T`
|
||||||
Responsive image gallery with lightbox, keyboard navigation, and filtering. Dynamic loading of content,
|
Responsive image gallery with lightbox, keyboard navigation, and filtering. Dynamic loading of content,
|
||||||
@ -40,6 +41,7 @@ export function page({ pkg, info }){
|
|||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/case-studies/image-gallery.js"), variant: "big" }),
|
el(example, { src: fileURL("./components/examples/case-studies/image-gallery.js"), variant: "big" }),
|
||||||
|
|
||||||
|
|
||||||
el(h3, t`Task Manager`),
|
el(h3, t`Task Manager`),
|
||||||
el("p").append(T`
|
el("p").append(T`
|
||||||
Kanban-style task management app with drag-and-drop and localStorage persistence. Complex state management
|
Kanban-style task management app with drag-and-drop and localStorage persistence. Complex state management
|
||||||
@ -48,27 +50,6 @@ export function page({ pkg, info }){
|
|||||||
`),
|
`),
|
||||||
el(example, { src: fileURL("./components/examples/case-studies/task-manager.js"), variant: "big" }),
|
el(example, { src: fileURL("./components/examples/case-studies/task-manager.js"), variant: "big" }),
|
||||||
|
|
||||||
el(h3, t`Product Catalog with asyncSignal`),
|
|
||||||
el("p").append(T`
|
|
||||||
Interactive product catalog with search, sorting, and pagination. Features include dynamic product filtering,
|
|
||||||
responsive UI with detailed view toggles, error handling with retry capability, and proper resource cleanup.
|
|
||||||
Demonstrates advanced signal usage, including derived signals, abortable async data fetching, and optimized
|
|
||||||
rendering patterns.
|
|
||||||
`),
|
|
||||||
el("div", { className: "callout" }).append(
|
|
||||||
el("h4", t`asyncSignal Utility`),
|
|
||||||
el("p").append(T`
|
|
||||||
This example showcases the asyncSignal utility, which is a powerful abstraction for handling async data
|
|
||||||
fetching with proper state management. It provides:
|
|
||||||
`),
|
|
||||||
el("ul").append(
|
|
||||||
el("li", t`Automatic tracking of loading, success, and error states`),
|
|
||||||
el("li", t`AbortController integration for request cancellation`),
|
|
||||||
el("li", t`Error handling and recovery`),
|
|
||||||
el("li", t`Options for caching previous data during loading states`)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
el(example, { src: fileURL("./components/examples/case-studies/products.js"), variant: "big" }),
|
|
||||||
|
|
||||||
el(h3, t`TodoMVC`),
|
el(h3, t`TodoMVC`),
|
||||||
el("p").append(T`
|
el("p").append(T`
|
||||||
|
65
package-lock.json
generated
65
package-lock.json
generated
@ -15,8 +15,7 @@
|
|||||||
"esbuild": "~0.25",
|
"esbuild": "~0.25",
|
||||||
"jsdom": "~26.0",
|
"jsdom": "~26.0",
|
||||||
"jshint": "~2.13",
|
"jshint": "~2.13",
|
||||||
"nodejsscript": "^1.0",
|
"nodejsscript": "^1.0.2",
|
||||||
"publint": "^0.3",
|
|
||||||
"size-limit-node-esbuild": "~0.3"
|
"size-limit-node-esbuild": "~0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -615,19 +614,6 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@publint/pack": {
|
|
||||||
"version": "0.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.2.tgz",
|
|
||||||
"integrity": "sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://bjornlu.com/sponsor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sindresorhus/merge-streams": {
|
"node_modules/@sindresorhus/merge-streams": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
|
||||||
@ -2243,16 +2229,6 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/package-manager-detector": {
|
|
||||||
"version": "0.2.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz",
|
|
||||||
"integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"quansync": "^0.2.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/parse5": {
|
"node_modules/parse5": {
|
||||||
"version": "7.2.1",
|
"version": "7.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
|
||||||
@ -2330,28 +2306,6 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/publint": {
|
|
||||||
"version": "0.3.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/publint/-/publint-0.3.9.tgz",
|
|
||||||
"integrity": "sha512-irTwfRfYW38vomkxxoiZQtFtUOQKpz5m0p9Z60z4xpXrl1KmvSrX1OMARvnnolB5usOXeNfvLj6d/W3rwXKfBQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@publint/pack": "^0.1.2",
|
|
||||||
"package-manager-detector": "^0.2.9",
|
|
||||||
"picocolors": "^1.1.1",
|
|
||||||
"sade": "^1.8.1"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"publint": "src/cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://bjornlu.com/sponsor"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@ -2362,23 +2316,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/quansync": {
|
|
||||||
"version": "0.2.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz",
|
|
||||||
"integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://github.com/sponsors/sxzz"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
|
23
package.json
23
package.json
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "deka-dom-el",
|
"name": "deka-dom-el",
|
||||||
"version": "0.9.5-alpha",
|
"version": "0.9.4-alpha",
|
||||||
"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.",
|
"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.",
|
||||||
"author": "Jan Andrle <andrle.jan@centrum.cz>",
|
"author": "Jan Andrle <andrle.jan@centrum.cz>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/jaandrle/deka-dom-el",
|
"homepage": "https://jaandrle.github.io/deka-dom-el/",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+ssh://git@github.com/jaandrle/deka-dom-el.git"
|
"url": "git+ssh://git@github.com/jaandrle/deka-dom-el.git"
|
||||||
@ -17,20 +17,20 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./index.d.ts",
|
"import": "./index.js",
|
||||||
"import": "./index.js"
|
"types": "./index.d.ts"
|
||||||
},
|
},
|
||||||
"./signals": {
|
"./signals": {
|
||||||
"types": "./signals.d.ts",
|
"import": "./signals.js",
|
||||||
"import": "./signals.js"
|
"types": "./signals.d.ts"
|
||||||
},
|
},
|
||||||
"./jsdom": {
|
"./jsdom": {
|
||||||
"types": "./jsdom.d.ts",
|
"import": "./jsdom.js",
|
||||||
"import": "./jsdom.js"
|
"types": "./jsdom.d.ts"
|
||||||
},
|
},
|
||||||
"./src/signals-lib": {
|
"./src/signals-lib": {
|
||||||
"types": "./src/signals-lib/signals-lib.d.ts",
|
"import": "./src/signals-lib/signals-lib.js",
|
||||||
"import": "./src/signals-lib/signals-lib.js"
|
"types": "./src/signals-lib/signals-lib.d.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@ -103,8 +103,7 @@
|
|||||||
"esbuild": "~0.25",
|
"esbuild": "~0.25",
|
||||||
"jsdom": "~26.0",
|
"jsdom": "~26.0",
|
||||||
"jshint": "~2.13",
|
"jshint": "~2.13",
|
||||||
"nodejsscript": "^1.0",
|
"nodejsscript": "^1.0.2",
|
||||||
"publint": "^0.3",
|
|
||||||
"size-limit-node-esbuild": "~0.3"
|
"size-limit-node-esbuild": "~0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,9 +51,9 @@ export function createElement(tag, attributes, ...addons){
|
|||||||
const host= (...c)=> !c.length ? el_host :
|
const host= (...c)=> !c.length ? el_host :
|
||||||
(scoped===1 ? addons.unshift(...c) : c.forEach(c=> c(el_host)), undefined);
|
(scoped===1 ? addons.unshift(...c) : c.forEach(c=> c(el_host)), undefined);
|
||||||
scope.push({ scope: tag, host });
|
scope.push({ scope: tag, host });
|
||||||
el= /** @type {Element} */(tag(attributes || undefined));
|
el= tag(attributes || undefined);
|
||||||
if(el.nodeName==="#comment") break;
|
|
||||||
const is_fragment= isInstance(el, env.F);
|
const is_fragment= isInstance(el, env.F);
|
||||||
|
if(el.nodeName==="#comment") break;
|
||||||
const el_mark= createElement.mark({
|
const el_mark= createElement.mark({
|
||||||
type: "component",
|
type: "component",
|
||||||
name: tag.name,
|
name: tag.name,
|
||||||
|
Reference in New Issue
Block a user