1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-07-01 20:32:13 +02:00

6 Commits

Author SHA1 Message Date
a459c0d786 📺 version 2025-03-18 17:32:41 +01:00
a1831d2cc4 🔤 corrects irland page headers 2025-03-18 17:30:42 +01:00
a8fa048522 📺 requestIdleCallback doesn need to be global 2025-03-18 17:24:01 +01:00
4dba3a292a 🔤 2025-03-18 17:23:23 +01:00
8415f5cf6f adjust package size limits 2025-03-17 15:14:56 +01:00
7e3b54153d 🐛 fixes #41 2025-03-17 15:14:39 +01:00
18 changed files with 94 additions and 90 deletions

View File

@ -1,7 +1,7 @@
**Alpha** **Alpha**
| [source code on GitHub](https://github.com/jaandrle/deka-dom-el) | [source code on GitHub](https://github.com/jaandrle/deka-dom-el)
| [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el) | [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el)
| [![git3moji](https://img.shields.io/badge/git3moji%E2%80%93v1-%E2%9A%A1%EF%B8%8F%F0%9F%90%9B%F0%9F%93%BA%F0%9F%91%AE%F0%9F%94%A4-fffad8.svg?style=flat-square)](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line --> | [npm package](https://www.npmjs.com/package/deka-dom-el)
<p align="center"> <p align="center">
<img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180"> <img src="docs/assets/logo.svg" alt="Deka DOM Elements Logo" width="180" height="180">
@ -24,9 +24,6 @@ function EmojiCounter({ initial }) {
const count = S(0); const count = S(0);
const emoji = S(initial); const emoji = S(initial);
/** @param {HTMLOptionElement} el */
const isSelected= el=> (el.selected= el.value===initial);
// 🔄 View - UI updates automatically when signals change // 🔄 View - UI updates automatically when signals change
return el().append( return el().append(
el("p", { el("p", {
@ -87,7 +84,7 @@ npm install deka-dom-el --save
…or via CDN / Direct Script: …or via CDN / Direct Script:
For CDN links and various build formats (ESM/IIFE, with/without signals, minified/unminified), see the [interactive For CDN links and various build formats (ESM/IIFE, with/without signals, minified/unminified), see the [interactive
format selector](https://jaandrle.github.io/deka-dom-el/) on the documentation site. format selector](https://jaandrle.github.io/deka-dom-el/#h-getting-started) on the documentation site.
```html ```html
<!-- Example with IIFE build (creates a global DDE object) --> <!-- Example with IIFE build (creates a global DDE object) -->

View File

@ -41,6 +41,11 @@ function observedAttributes(instance, observedAttribute2) {
function kebabToCamel(name) { function kebabToCamel(name) {
return name.replace(/-./g, (x) => x[1].toUpperCase()); return name.replace(/-./g, (x) => x[1].toUpperCase());
} }
function requestIdle() {
return new Promise(function(resolve) {
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
});
}
// src/dom-lib/common.js // src/dom-lib/common.js
var enviroment = { var enviroment = {
@ -179,11 +184,6 @@ function connectionsChangesObserverConstructor() {
is_observing = false; is_observing = false;
observer.disconnect(); observer.disconnect();
} }
function requestIdle() {
return new Promise(function(resolve) {
(requestIdleCallback || requestAnimationFrame)(resolve);
});
}
async function collectChildren(element) { async function collectChildren(element) {
if (store.size > 30) if (store.size > 30)
await requestIdle(); await requestIdle();
@ -824,7 +824,7 @@ signal.el = function(s, map) {
}; };
function requestCleanUpReactives(host) { function requestCleanUpReactives(host) {
if (!host || !host[key_reactive]) return; if (!host || !host[key_reactive]) return;
(requestIdleCallback || setTimeout)(function() { requestIdle().then(function() {
host[key_reactive] = host[key_reactive].filter(([s, el]) => el.isConnected ? true : (removeSignalListener(...s), false)); host[key_reactive] = host[key_reactive].filter(([s, el]) => el.isConnected ? true : (removeSignalListener(...s), false));
}); });
} }

File diff suppressed because one or more lines are too long

10
dist/esm.js vendored
View File

@ -25,6 +25,11 @@ function onAbort(signal, listener) {
signal.removeEventListener("abort", listener); signal.removeEventListener("abort", listener);
}; };
} }
function requestIdle() {
return new Promise(function(resolve) {
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
});
}
// src/dom-lib/common.js // src/dom-lib/common.js
var enviroment = { var enviroment = {
@ -163,11 +168,6 @@ function connectionsChangesObserverConstructor() {
is_observing = false; is_observing = false;
observer.disconnect(); observer.disconnect();
} }
function requestIdle() {
return new Promise(function(resolve) {
(requestIdleCallback || requestAnimationFrame)(resolve);
});
}
async function collectChildren(element) { async function collectChildren(element) {
if (store.size > 30) if (store.size > 30)
await requestIdle(); await requestIdle();

2
dist/esm.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -86,6 +86,11 @@ var DDE = (() => {
function kebabToCamel(name) { function kebabToCamel(name) {
return name.replace(/-./g, (x) => x[1].toUpperCase()); return name.replace(/-./g, (x) => x[1].toUpperCase());
} }
function requestIdle() {
return new Promise(function(resolve) {
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
});
}
// src/dom-lib/common.js // src/dom-lib/common.js
var enviroment = { var enviroment = {
@ -224,11 +229,6 @@ var DDE = (() => {
is_observing = false; is_observing = false;
observer.disconnect(); observer.disconnect();
} }
function requestIdle() {
return new Promise(function(resolve) {
(requestIdleCallback || requestAnimationFrame)(resolve);
});
}
async function collectChildren(element) { async function collectChildren(element) {
if (store.size > 30) if (store.size > 30)
await requestIdle(); await requestIdle();
@ -869,7 +869,7 @@ var DDE = (() => {
}; };
function requestCleanUpReactives(host) { function requestCleanUpReactives(host) {
if (!host || !host[key_reactive]) return; if (!host || !host[key_reactive]) return;
(requestIdleCallback || setTimeout)(function() { requestIdle().then(function() {
host[key_reactive] = host[key_reactive].filter(([s, el]) => el.isConnected ? true : (removeSignalListener(...s), false)); host[key_reactive] = host[key_reactive].filter(([s, el]) => el.isConnected ? true : (removeSignalListener(...s), false));
}); });
} }

File diff suppressed because one or more lines are too long

10
dist/iife.js vendored
View File

@ -67,6 +67,11 @@ var DDE = (() => {
signal.removeEventListener("abort", listener); signal.removeEventListener("abort", listener);
}; };
} }
function requestIdle() {
return new Promise(function(resolve) {
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
});
}
// src/dom-lib/common.js // src/dom-lib/common.js
var enviroment = { var enviroment = {
@ -205,11 +210,6 @@ var DDE = (() => {
is_observing = false; is_observing = false;
observer.disconnect(); observer.disconnect();
} }
function requestIdle() {
return new Promise(function(resolve) {
(requestIdleCallback || requestAnimationFrame)(resolve);
});
}
async function collectChildren(element) { async function collectChildren(element) {
if (store.size > 30) if (store.size > 30)
await requestIdle(); await requestIdle();

2
dist/iife.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -81,11 +81,11 @@ function Todos(){
) )
) )
), ),
S.el(todosS, todos => !todos.length S.el(todosS, ({ length }) => !length
? el() ? el()
: el("footer", { className: "footer" }).append( : el("footer", { className: "footer" }).append(
el("span", { className: "todo-count" }).append( el("span", { className: "todo-count" }).append(
noOfLeft() el("strong", length + " " + (length === 1 ? "item" : "items")),
), ),
memo("filters", ()=> memo("filters", ()=>
el("ul", { className: "filters" }).append( el("ul", { className: "filters" }).append(
@ -100,7 +100,7 @@ function Todos(){
) )
), ),
), ),
todos.length - todosRemainingS.get() === 0 length - todosRemainingS.get() === 0
? el() ? el()
: memo("delete", () => : memo("delete", () =>
el("button", el("button",
@ -110,13 +110,6 @@ function Todos(){
) )
) )
); );
function noOfLeft(){
const length = todosRemainingS.get();
return el("strong").append(
length + " ",
length === 1 ? "item left" : "items left"
)
}
} }
/** /**

View File

@ -16,7 +16,11 @@ import { getLibraryUrl } from "./components/getLibraryUrl.html.js";
/** @param {string} url */ /** @param {string} url */
const fileURL= url=> new URL(url, import.meta.url); const fileURL= url=> new URL(url, import.meta.url);
const references= { const references= {
w_mvv:{ npm: {
title: t`NPM package page for dd<el>`,
href: "https://www.npmjs.com/package/deka-dom-el",
},
w_mvv: {
title: t`Wikipedia: Modelviewviewmodel`, title: t`Wikipedia: Modelviewviewmodel`,
href: "https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel", href: "https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel",
}, },
@ -106,6 +110,10 @@ export function page({ pkg, info }){
`), `),
el("h4", "npm installation"), el("h4", "npm installation"),
el(code, { content: "npm install deka-dom-el --save", language: "shell", page_id }), el(code, { content: "npm install deka-dom-el --save", language: "shell", page_id }),
el("p").append(T`
…see ${el("a", { textContent: "package page", ...references.npm, target: "_blank" })}.
`),
el("h4", "CDN / Direct Script Usage"), el("h4", "CDN / Direct Script Usage"),
el("p").append(T` el("p").append(T`
Use the interactive selector below to choose your preferred format: Use the interactive selector below to choose your preferred format:

View File

@ -311,9 +311,9 @@ export function page({ pkg, info }){
el(code, { content: ` el(code, { content: `
// Dynamic class attributes // Dynamic class attributes
el("a", { el("a", {
textContent: "All", textContent,
className: S(()=> pageS.get() === "all" ? "selected" : ""), classList: { selected: S(()=> pageS.get() === textContent.toLowerCase()) },
href: "#" href: \`#\${textContent.toLowerCase()}\`
}) })
// Reactive classList // Reactive classList
@ -355,18 +355,25 @@ export function page({ pkg, info }){
el("h4", t`Memoizing UI Sections`), el("h4", t`Memoizing UI Sections`),
el(code, { content: ` el(code, { content: `
S.el(todosS, todos => memo(todos.length, length=> length S.el(todosS, ({ length }) => !length
? el("footer", { className: "footer" }).append( ? el()
// Footer content... : el("footer", { className: "footer" }).append(
// …
memo("filters", ()=>
// …
el("a", {
textContent,
classList: { selected: S(()=> pageS.get() === textContent.toLowerCase()) },
href: \`#\${textContent.toLowerCase()}\`
})
// …
) )
: el()
)) ))
`, page_id }), `, page_id }),
el("p").append(T` el("p").append(T`
By memoizing based on the todos length, the entire footer component is only re-rendered We memoize the UI section and uses derived signal for the classList. Re-rendering this part is therefore
when todos are added or removed, not when their properties change. This improves performance unnecessary when the number of todos changes.
by avoiding unnecessary DOM operations.
`), `),
el("div", { className: "tip" }).append( el("div", { className: "tip" }).append(
@ -389,8 +396,10 @@ export function page({ pkg, info }){
`), `),
el(code, { content: ` el(code, { content: `
// Event handlers in the main component // Event handlers in the main component
const onDelete = on("todo:delete", ev => S.action(todosS, "delete", ev.detail)); const onDelete = on("todo:delete", ev =>
const onEdit = on("todo:edit", ev => S.action(todosS, "edit", ev.detail)); S.action(todosS, "delete", /** @type {{ detail: Todo["id"] }} */(ev).detail));
const onEdit = on("todo:edit", ev =>
S.action(todosS, "edit", /** @type {{ detail: Partial<Todo> & { id: Todo["id"] } }} */(ev).detail));
`, page_id }), `, page_id }),
el("h4", t`2. The TodoItem Component with Scopes and Local State`), el("h4", t`2. The TodoItem Component with Scopes and Local State`),
@ -522,25 +531,24 @@ export function page({ pkg, info }){
el("h4", t`Conditional Todo List`), el("h4", t`Conditional Todo List`),
el(code, { content: ` el(code, { content: `
S.el(todosS, todos => todos.length S.el(todosS, todos => todos.length
? el("main", { className: "main" }).append( ? el()
: el("main", { className: "main" }).append(
// Main content with toggle all and todo list // Main content with toggle all and todo list
) )
: el()
) )
`, page_id }), `, page_id }),
el("h4", t`Conditional Edit Form`), el("h4", t`Conditional Edit Form`),
el(code, { content: ` el(code, { content: `
S.el(isEditing, editing => editing S.el(isEditing, editing => !editing
? el("form", null, onSubmitEdit).append( ? el()
: el("form", null, onSubmitEdit).append(
el("input", { el("input", {
className: "edit", className: "edit",
name: "edit", name: formEdit,
value: title, value: title,
"data-id": id
}, onBlurEdit, onKeyDown, addFocus) }, onBlurEdit, onKeyDown, addFocus)
) )
: el()
) )
`, page_id }), `, page_id }),
@ -630,7 +638,7 @@ export function page({ pkg, info }){
${el("strong", "Declarative Class Management:")} Using the classList property for cleaner class handling ${el("strong", "Declarative Class Management:")} Using the classList property for cleaner class handling
`), `),
el("li").append(T` el("li").append(T`
${el("strong", "Focus Management:")} Reliable input focus with setTimeout ${el("strong", "Focus Management:")} Reliable input focus with requestAnimationFrame
`), `),
el("li").append(T` el("li").append(T`
${el("strong", "Persistent Storage:")} Automatically saving application state with signal listeners ${el("strong", "Persistent Storage:")} Automatically saving application state with signal listeners

View File

@ -1,9 +1,8 @@
import { T, t } from "./utils/index.js"; import { T, t } from "./utils/index.js";
export const info= { export const info= {
title: t`Ireland Components`, title: t`Ireland Components`,
fullTitle: t`Interactive Demo Components with Server-Side Pre-Rendering`, fullTitle: t`Server-Side Pre-Rendering and Client-Side Rehydration`,
description: t`Creating live, interactive component examples in documentation with server-side description: t`Using Ireland components for server-side pre-rendering and client-side rehydration`,
rendering and client-side hydration.`,
}; };
import { el } from "deka-dom-el"; import { el } from "deka-dom-el";

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "deka-dom-el", "name": "deka-dom-el",
"version": "0.9.3-alpha", "version": "0.9.4-alpha",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "deka-dom-el", "name": "deka-dom-el",
"version": "0.9.3-alpha", "version": "0.9.4-alpha",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@size-limit/preset-small-lib": "~11.2", "@size-limit/preset-small-lib": "~11.2",

View File

@ -1,10 +1,10 @@
{ {
"name": "deka-dom-el", "name": "deka-dom-el",
"version": "0.9.3-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"
@ -52,7 +52,6 @@
"maxdepth": 3, "maxdepth": 3,
"maxcomplexity": 14, "maxcomplexity": 14,
"globals": { "globals": {
"requestIdleCallback": false,
"AbortController": false, "AbortController": false,
"AbortSignal": false, "AbortSignal": false,
"FinalizationRegistry": false "FinalizationRegistry": false
@ -61,25 +60,25 @@
"size-limit": [ "size-limit": [
{ {
"path": "./index.js", "path": "./index.js",
"limit": "10.5 kB", "limit": "10 kB",
"gzip": false, "gzip": false,
"brotli": false "brotli": false
}, },
{ {
"path": "./signals.js", "path": "./signals.js",
"limit": "12.5 kB", "limit": "12.2 kB",
"gzip": false, "gzip": false,
"brotli": false "brotli": false
}, },
{ {
"path": "./index-with-signals.js", "path": "./index-with-signals.js",
"limit": "15 kB", "limit": "14.75 kB",
"gzip": false, "gzip": false,
"brotli": false "brotli": false
}, },
{ {
"path": "./index-with-signals.js", "path": "./index-with-signals.js",
"limit": "5.5 kB" "limit": "5.25 kB"
} }
], ],
"modifyEsbuildConfig": { "modifyEsbuildConfig": {

View File

@ -1,5 +1,5 @@
import { enviroment as env, evc, evd } from './common.js'; import { enviroment as env, evc, evd } from './common.js';
import { isInstance } from "../helpers.js"; import { isInstance, requestIdle } from "../helpers.js";
/** /**
* Connection changes observer for tracking element connection/disconnection * Connection changes observer for tracking element connection/disconnection
@ -149,15 +149,6 @@ function connectionsChangesObserverConstructor(){
observer.disconnect(); observer.disconnect();
} }
//TODO: remount support?
/**
* Schedule a task during browser idle time
* @returns {Promise<void>} Promise that resolves when browser is idle
*/
function requestIdle(){ return new Promise(function(resolve){
(requestIdleCallback || requestAnimationFrame)(resolve);
}); }
/** /**
* Collects child elements from the store that are contained by the given element * Collects child elements from the store that are contained by the given element
* @param {Element} element - Parent element * @param {Element} element - Parent element

View File

@ -68,3 +68,12 @@ export function observedAttributes(instance, observedAttribute){
* @returns {string} The camelCase string * @returns {string} The camelCase string
*/ */
function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); } function kebabToCamel(name){ return name.replace(/-./g, x=> x[1].toUpperCase()); }
/**
* Schedule a task during browser idle time
* @returns {Promise<void>} Promise that resolves when browser is idle
*/
export function requestIdle(){ return new Promise(function(resolve){
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
});
}

View File

@ -1,6 +1,6 @@
import { queueSignalWrite, mark } from "./helpers.js"; import { queueSignalWrite, mark } from "./helpers.js";
export { mark }; export { mark };
import { hasOwn, oCreate, oAssign } from "../helpers.js"; import { hasOwn, oCreate, oAssign, requestIdle } from "../helpers.js";
const Signal = oCreate(null, { const Signal = oCreate(null, {
get: { value(){ return read(this); } }, get: { value(){ return read(this); } },
@ -214,7 +214,7 @@ signal.el= function(s, map){
*/ */
function requestCleanUpReactives(host){ function requestCleanUpReactives(host){
if(!host || !host[key_reactive]) return; if(!host || !host[key_reactive]) return;
(requestIdleCallback || setTimeout)(function(){ requestIdle().then(function(){
host[key_reactive]= host[key_reactive] host[key_reactive]= host[key_reactive]
.filter(([ s, el ])=> el.isConnected ? true : (removeSignalListener(...s), false)); .filter(([ s, el ])=> el.isConnected ? true : (removeSignalListener(...s), false));
}); });