mirror of
https://github.com/jaandrle/deka-dom-el
synced 2026-01-09 15:46:28 +01:00
Compare commits
2 Commits
v0.9.4-alp
...
v0.9.6-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
fcef903836
|
|||
| f2c85ec983 |
5
.github/workflows/npm-publish.yml
vendored
5
.github/workflows/npm-publish.yml
vendored
@@ -6,9 +6,10 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment: default
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: '20.16'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
1
.npmrc
1
.npmrc
@@ -1,3 +1,2 @@
|
||||
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
|
||||
registry=https://registry.npmjs.org/
|
||||
always-auth=true
|
||||
|
||||
12
README.md
12
README.md
@@ -1,5 +1,5 @@
|
||||
**Alpha**
|
||||
| [Docs](https://jaandrle.github.io/deka-dom-el "Official documentation and guide site")
|
||||
| [Docs&Examples](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")
|
||||
| [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"))
|
||||
@@ -9,7 +9,7 @@
|
||||
```javascript
|
||||
// 🌟 Reactive component with clear separation of concerns
|
||||
document.body.append(
|
||||
el(EmojiCounter, { initial: "🚀" })
|
||||
el(EmojiCounter, { initial: "🚀" }),
|
||||
);
|
||||
|
||||
function EmojiCounter({ initial }) {
|
||||
@@ -34,7 +34,7 @@ function EmojiCounter({ initial }) {
|
||||
el(Option, "🎉"),
|
||||
el(Option, "🚀"),
|
||||
el(Option, "💖"),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
function Option({ textContent }){
|
||||
@@ -56,10 +56,10 @@ Creating reactive elements, components, and Web Components using the native
|
||||
## Features at a Glance
|
||||
|
||||
- ✅ **No build step required** — use directly in browsers or Node.js
|
||||
- ☑️ **Lightweight** — ~10-15kB minified (original goal 10kB) with **zero**/minimal dependencies
|
||||
- ✅ **Declarative & functional approach** for clean, maintainable code
|
||||
- ✅ **Minimalized footprint** — ~10-15kB minified bundle (original goal 10kB), **zero**/minimal dependencies and
|
||||
small in-memory size (auto-releasing resources as much as possible)
|
||||
- ✅ **Declarative & functional approach support** for clean, maintainable code
|
||||
- ✅ **Signals and events** for reactive UI
|
||||
- ✅ **Auto-releasing resources** for memory management but nice development experience
|
||||
- ✅ **Memoization for performance** — optimize rendering with intelligent caching
|
||||
- ☑️ **Optional build-in signals** with support for custom reactive implementations (#39)
|
||||
- ☑️ **Server-side rendering** support via [jsdom](https://github.com/jsdom/jsdom)
|
||||
|
||||
@@ -9,3 +9,4 @@ npx editorconfig-checker -format gcc ${additional}
|
||||
npx jshint index.js src ${additional}
|
||||
[ "$one" = 'vim' ] && exit 0
|
||||
npx size-limit
|
||||
npx publint
|
||||
|
||||
7
dist/esm-with-signals.js
vendored
7
dist/esm-with-signals.js
vendored
@@ -430,9 +430,10 @@ function createElement(tag, attributes, ...addons) {
|
||||
scoped = 1;
|
||||
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 });
|
||||
el = tag(attributes || void 0);
|
||||
const is_fragment = isInstance(el, enviroment.F);
|
||||
el = /** @type {Element} */
|
||||
tag(attributes || void 0);
|
||||
if (el.nodeName === "#comment") break;
|
||||
const is_fragment = isInstance(el, enviroment.F);
|
||||
const el_mark = createElement.mark({
|
||||
type: "component",
|
||||
name: tag.name,
|
||||
@@ -759,7 +760,7 @@ signal.on = function on2(s, listener, options = {}) {
|
||||
};
|
||||
signal.symbols = {
|
||||
//signal: mark,
|
||||
onclear: Symbol.for("Signal.onclear")
|
||||
onclear: /* @__PURE__ */ Symbol.for("Signal.onclear")
|
||||
};
|
||||
signal.clear = function(...signals2) {
|
||||
for (const s of signals2) {
|
||||
|
||||
4
dist/esm-with-signals.min.js
vendored
4
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,9 +414,10 @@ function createElement(tag, attributes, ...addons) {
|
||||
scoped = 1;
|
||||
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 });
|
||||
el = tag(attributes || void 0);
|
||||
const is_fragment = isInstance(el, enviroment.F);
|
||||
el = /** @type {Element} */
|
||||
tag(attributes || void 0);
|
||||
if (el.nodeName === "#comment") break;
|
||||
const is_fragment = isInstance(el, enviroment.F);
|
||||
const el_mark = createElement.mark({
|
||||
type: "component",
|
||||
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
7
dist/iife-with-signals.js
vendored
7
dist/iife-with-signals.js
vendored
@@ -475,9 +475,10 @@ var DDE = (() => {
|
||||
scoped = 1;
|
||||
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 });
|
||||
el = tag(attributes || void 0);
|
||||
const is_fragment = isInstance(el, enviroment.F);
|
||||
el = /** @type {Element} */
|
||||
tag(attributes || void 0);
|
||||
if (el.nodeName === "#comment") break;
|
||||
const is_fragment = isInstance(el, enviroment.F);
|
||||
const el_mark = createElement.mark({
|
||||
type: "component",
|
||||
name: tag.name,
|
||||
@@ -804,7 +805,7 @@ var DDE = (() => {
|
||||
};
|
||||
signal.symbols = {
|
||||
//signal: mark,
|
||||
onclear: Symbol.for("Signal.onclear")
|
||||
onclear: /* @__PURE__ */ Symbol.for("Signal.onclear")
|
||||
};
|
||||
signal.clear = function(...signals2) {
|
||||
for (const s of signals2) {
|
||||
|
||||
4
dist/iife-with-signals.min.js
vendored
4
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,9 +456,10 @@ var DDE = (() => {
|
||||
scoped = 1;
|
||||
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 });
|
||||
el = tag(attributes || void 0);
|
||||
const is_fragment = isInstance(el, enviroment.F);
|
||||
el = /** @type {Element} */
|
||||
tag(attributes || void 0);
|
||||
if (el.nodeName === "#comment") break;
|
||||
const is_fragment = isInstance(el, enviroment.F);
|
||||
const el_mark = createElement.mark({
|
||||
type: "component",
|
||||
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();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
document.querySelector('.lightbox-prev-btn').click();
|
||||
onPrevImage(e);
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
document.querySelector('.lightbox-next-btn').click();
|
||||
onNextImage(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -126,12 +126,12 @@ export function ImageGallery(images= imagesSample) {
|
||||
el("div", {
|
||||
className: "gallery-item",
|
||||
dataTag: image.alt.toLowerCase()
|
||||
}).append(
|
||||
}, onImageClick(image.id)).append(
|
||||
el("img", {
|
||||
src: image.src,
|
||||
alt: image.alt,
|
||||
loading: "lazy"
|
||||
}, onImageClick(image.id)),
|
||||
}),
|
||||
el("div", { className: "gallery-item-caption" }).append(
|
||||
el("h3", image.title),
|
||||
el("p", image.alt)
|
||||
@@ -146,41 +146,41 @@ export function ImageGallery(images= imagesSample) {
|
||||
S.el(isLightboxOpen, open => !open
|
||||
? el()
|
||||
: el("div", { className: "lightbox-overlay" }, on("click", closeLightbox)).append(
|
||||
el("div", {
|
||||
className: "lightbox-content",
|
||||
onClick: e => e.stopPropagation() // Prevent closing when clicking inside
|
||||
}).append(
|
||||
el("button", {
|
||||
className: "lightbox-close-btn",
|
||||
"aria-label": "Close lightbox"
|
||||
}, on("click", closeLightbox)).append("×"),
|
||||
el("div", {
|
||||
className: "lightbox-content",
|
||||
onClick: e => e.stopPropagation() // Prevent closing when clicking inside
|
||||
}).append(
|
||||
el("button", {
|
||||
className: "lightbox-close-btn",
|
||||
ariaLabel: "Close lightbox",
|
||||
}, on("click", closeLightbox)).append("×"),
|
||||
|
||||
el("button", {
|
||||
className: "lightbox-prev-btn",
|
||||
"aria-label": "Previous image"
|
||||
}, on("click", onPrevImage)).append("❮"),
|
||||
el("button", {
|
||||
className: "lightbox-prev-btn",
|
||||
ariaLabel: "Previous image",
|
||||
}, on("click", onPrevImage)).append("❮"),
|
||||
|
||||
el("button", {
|
||||
className: "lightbox-next-btn",
|
||||
"aria-label": "Next image"
|
||||
}, on("click", onNextImage)).append("❯"),
|
||||
el("button", {
|
||||
className: "lightbox-next-btn",
|
||||
ariaLabel: "Next image",
|
||||
}, on("click", onNextImage)).append("❯"),
|
||||
|
||||
S.el(selectedImage, img => !img
|
||||
? el()
|
||||
: el("div", { className: "lightbox-image-container" }).append(
|
||||
el("img", {
|
||||
src: img.src,
|
||||
alt: img.alt,
|
||||
className: "lightbox-image"
|
||||
}),
|
||||
el("div", { className: "lightbox-caption" }).append(
|
||||
el("h2", img.title),
|
||||
el("p", img.alt)
|
||||
)
|
||||
)
|
||||
S.el(selectedImage, img => !img
|
||||
? el()
|
||||
: el("div", { className: "lightbox-image-container" }).append(
|
||||
el("img", {
|
||||
src: img.src,
|
||||
alt: img.alt,
|
||||
className: "lightbox-image",
|
||||
}),
|
||||
el("div", { className: "lightbox-caption" }).append(
|
||||
el("h2", img.title),
|
||||
el("p", img.alt),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { el, on } from "deka-dom-el";
|
||||
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 });
|
||||
const products = asyncSignal(S,
|
||||
fetchProducts,
|
||||
{ initial: [], keepLast: true, signal });
|
||||
const searchTerm = S("");
|
||||
const handleSearch = (e) => searchTerm.set(e.target.value);
|
||||
const sortOrder = S("default");
|
||||
@@ -220,10 +224,14 @@ function simulateNetworkDelay(min = 300, max = 1200) {
|
||||
* @template T
|
||||
* @param {typeof S} S - Signal constructor
|
||||
* @param {(params: { signal: AbortSignal }) => Promise<T>} invoker - Async function to execute
|
||||
* @param {{ initial?: T, keepLast?: boolean }} options - Configuration options
|
||||
* @param {{ initial?: T, keepLast?: boolean, signal?: AbortSignal }} options - Configuration options
|
||||
* @returns {Object} Status signals and control methods
|
||||
*/
|
||||
export function asyncSignal(S, invoker, { initial, keepLast } = {}) {
|
||||
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);
|
||||
@@ -242,7 +250,7 @@ export function asyncSignal(S, invoker, { initial, keepLast } = {}) {
|
||||
|
||||
try {
|
||||
const data = await invoker({
|
||||
signal: controller.signal,
|
||||
signal: anySignal(controller.signal),
|
||||
});
|
||||
if (!controller.signal.aborted) {
|
||||
status.set("resolved");
|
||||
|
||||
@@ -74,13 +74,11 @@ export function h3({ textContent, id }){
|
||||
if(!id) id= "h-"+textContent.toLowerCase().replaceAll(/\s/g, "-").replaceAll(/[^a-z-]/g, "");
|
||||
return el("h3", { id }).append(
|
||||
el("a", {
|
||||
className: "heading-anchor",
|
||||
href: "#"+id,
|
||||
textContent: "#",
|
||||
title: `Link to this section: ${textContent}`,
|
||||
"aria-label": `Link to section ${textContent}`
|
||||
className: "heading-anchor",
|
||||
href: "#"+id,
|
||||
title: `Link to this section: ${textContent}`,
|
||||
}),
|
||||
" ",
|
||||
"# ",
|
||||
textContent,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,12 @@ export function page({ pkg, info }){
|
||||
el("h4", t`Key Benefits of dd<el>`),
|
||||
el("ul").append(
|
||||
el("li", t`No build step required — use directly in the browser`),
|
||||
el("li", t`Lightweight core (~10–15kB minified) without unnecessary dependencies (0 at now 😇)`),
|
||||
el("li", t`Minimalized footprint:`),
|
||||
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`Built-in (but optional) reactivity with simplified but powerful signals system`),
|
||||
el("li", t`Clean code organization with the 3PS pattern`)
|
||||
|
||||
@@ -151,7 +151,7 @@ export function header({ info: { href, title, description }, pkg }){
|
||||
),
|
||||
el("span", {
|
||||
className: "version-badge",
|
||||
"aria-label": "Version",
|
||||
ariaLabel: "Version",
|
||||
textContent: pkg.version || ""
|
||||
})
|
||||
),
|
||||
@@ -165,13 +165,13 @@ export function header({ info: { href, title, description }, pkg }){
|
||||
function nav({ href, pkg }){
|
||||
return el("nav", {
|
||||
role: "navigation",
|
||||
"aria-label": "Main navigation",
|
||||
ariaLabel: "Main navigation",
|
||||
className: nav.name
|
||||
}).append(
|
||||
el("a", {
|
||||
href: pkg.homepage,
|
||||
className: "github-link",
|
||||
"aria-label": "View on GitHub",
|
||||
ariaLabel: "View on GitHub",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
}).append(
|
||||
@@ -185,11 +185,11 @@ function nav({ href, pkg }){
|
||||
return el("a", {
|
||||
href: isIndex ? "./" : p.href,
|
||||
title: p.description || `Go to ${p.title}`,
|
||||
"aria-current": isCurrent ? "page" : null,
|
||||
ariaCurrent: isCurrent ? "page" : null,
|
||||
}).append(
|
||||
el("span", {
|
||||
className: "nav-number",
|
||||
"aria-hidden": "true",
|
||||
ariaHidden: "true",
|
||||
textContent: `${i+1}. `
|
||||
}),
|
||||
p.title
|
||||
|
||||
@@ -168,6 +168,22 @@ export function page({ pkg, info }){
|
||||
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("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("p").append(T`
|
||||
|
||||
1455
package-lock.json
generated
1455
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "deka-dom-el",
|
||||
"version": "0.9.4-alpha",
|
||||
"version": "0.9.6-alpha",
|
||||
"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>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://jaandrle.github.io/deka-dom-el/",
|
||||
"homepage": "https://github.com/jaandrle/deka-dom-el",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/jaandrle/deka-dom-el.git"
|
||||
@@ -17,20 +17,20 @@
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"types": "./index.d.ts"
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.js"
|
||||
},
|
||||
"./signals": {
|
||||
"import": "./signals.js",
|
||||
"types": "./signals.d.ts"
|
||||
"types": "./signals.d.ts",
|
||||
"import": "./signals.js"
|
||||
},
|
||||
"./jsdom": {
|
||||
"import": "./jsdom.js",
|
||||
"types": "./jsdom.d.ts"
|
||||
"types": "./jsdom.d.ts",
|
||||
"import": "./jsdom.js"
|
||||
},
|
||||
"./src/signals-lib": {
|
||||
"import": "./src/signals-lib/signals-lib.js",
|
||||
"types": "./src/signals-lib/signals-lib.d.ts"
|
||||
"types": "./src/signals-lib/signals-lib.d.ts",
|
||||
"import": "./src/signals-lib/signals-lib.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
@@ -97,13 +97,14 @@
|
||||
"typescript"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@size-limit/preset-small-lib": "~11.2",
|
||||
"@size-limit/preset-small-lib": "~12.0",
|
||||
"dts-bundle-generator": "~9.5",
|
||||
"editorconfig-checker": "~6.0",
|
||||
"esbuild": "~0.25",
|
||||
"jsdom": "~26.0",
|
||||
"editorconfig-checker": "~6.1",
|
||||
"esbuild": "~0.27",
|
||||
"jsdom": "~26.1",
|
||||
"jshint": "~2.13",
|
||||
"nodejsscript": "^1.0.2",
|
||||
"size-limit-node-esbuild": "~0.3"
|
||||
"nodejsscript": "^1.1",
|
||||
"publint": "^0.3",
|
||||
"size-limit-node-esbuild": "~0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ export function createElement(tag, attributes, ...addons){
|
||||
const host= (...c)=> !c.length ? el_host :
|
||||
(scoped===1 ? addons.unshift(...c) : c.forEach(c=> c(el_host)), undefined);
|
||||
scope.push({ scope: tag, host });
|
||||
el= tag(attributes || undefined);
|
||||
const is_fragment= isInstance(el, env.F);
|
||||
el= /** @type {Element} */(tag(attributes || undefined));
|
||||
if(el.nodeName==="#comment") break;
|
||||
const is_fragment= isInstance(el, env.F);
|
||||
const el_mark= createElement.mark({
|
||||
type: "component",
|
||||
name: tag.name,
|
||||
|
||||
Reference in New Issue
Block a user