mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-07-01 12:22:15 +02:00
Compare commits
7 Commits
v0.9.3-alp
...
a459c0d786
Author | SHA1 | Date | |
---|---|---|---|
a459c0d786
|
|||
a1831d2cc4
|
|||
a8fa048522
|
|||
4dba3a292a
|
|||
8415f5cf6f
|
|||
7e3b54153d
|
|||
04f93345f8
|
@ -1,7 +1,7 @@
|
||||
**Alpha**
|
||||
| [source code on GitHub](https://github.com/jaandrle/deka-dom-el)
|
||||
| [*mirrored* on Gitea](https://gitea.jaandrle.cz/jaandrle/deka-dom-el)
|
||||
| [](https://robinpokorny.github.io/git3moji/) <!-- editorconfig-checker-disable-line -->
|
||||
| [npm package](https://www.npmjs.com/package/deka-dom-el)
|
||||
|
||||
<p align="center">
|
||||
<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 emoji = S(initial);
|
||||
|
||||
/** @param {HTMLOptionElement} el */
|
||||
const isSelected= el=> (el.selected= el.value===initial);
|
||||
|
||||
// 🔄 View - UI updates automatically when signals change
|
||||
return el().append(
|
||||
el("p", {
|
||||
@ -87,7 +84,7 @@ npm install deka-dom-el --save
|
||||
…or via CDN / Direct Script:
|
||||
|
||||
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
|
||||
<!-- Example with IIFE build (creates a global DDE object) -->
|
||||
|
12
dist/esm-with-signals.js
vendored
12
dist/esm-with-signals.js
vendored
@ -41,6 +41,11 @@ function observedAttributes(instance, observedAttribute2) {
|
||||
function kebabToCamel(name) {
|
||||
return name.replace(/-./g, (x) => x[1].toUpperCase());
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
// src/dom-lib/common.js
|
||||
var enviroment = {
|
||||
@ -179,11 +184,6 @@ function connectionsChangesObserverConstructor() {
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
@ -824,7 +824,7 @@ signal.el = function(s, map) {
|
||||
};
|
||||
function requestCleanUpReactives(host) {
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
6
dist/esm-with-signals.min.js
vendored
6
dist/esm-with-signals.min.js
vendored
File diff suppressed because one or more lines are too long
10
dist/esm.js
vendored
10
dist/esm.js
vendored
@ -25,6 +25,11 @@ function onAbort(signal, listener) {
|
||||
signal.removeEventListener("abort", listener);
|
||||
};
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
// src/dom-lib/common.js
|
||||
var enviroment = {
|
||||
@ -163,11 +168,6 @@ function connectionsChangesObserverConstructor() {
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
|
2
dist/esm.min.js
vendored
2
dist/esm.min.js
vendored
File diff suppressed because one or more lines are too long
12
dist/iife-with-signals.js
vendored
12
dist/iife-with-signals.js
vendored
@ -86,6 +86,11 @@ var DDE = (() => {
|
||||
function kebabToCamel(name) {
|
||||
return name.replace(/-./g, (x) => x[1].toUpperCase());
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
// src/dom-lib/common.js
|
||||
var enviroment = {
|
||||
@ -224,11 +229,6 @@ var DDE = (() => {
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
@ -869,7 +869,7 @@ var DDE = (() => {
|
||||
};
|
||||
function requestCleanUpReactives(host) {
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
6
dist/iife-with-signals.min.js
vendored
6
dist/iife-with-signals.min.js
vendored
File diff suppressed because one or more lines are too long
10
dist/iife.js
vendored
10
dist/iife.js
vendored
@ -67,6 +67,11 @@ var DDE = (() => {
|
||||
signal.removeEventListener("abort", listener);
|
||||
};
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(globalThis.requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
// src/dom-lib/common.js
|
||||
var enviroment = {
|
||||
@ -205,11 +210,6 @@ var DDE = (() => {
|
||||
is_observing = false;
|
||||
observer.disconnect();
|
||||
}
|
||||
function requestIdle() {
|
||||
return new Promise(function(resolve) {
|
||||
(requestIdleCallback || requestAnimationFrame)(resolve);
|
||||
});
|
||||
}
|
||||
async function collectChildren(element) {
|
||||
if (store.size > 30)
|
||||
await requestIdle();
|
||||
|
2
dist/iife.min.js
vendored
2
dist/iife.min.js
vendored
File diff suppressed because one or more lines are too long
@ -81,11 +81,11 @@ function Todos(){
|
||||
)
|
||||
)
|
||||
),
|
||||
S.el(todosS, todos => !todos.length
|
||||
S.el(todosS, ({ length }) => !length
|
||||
? el()
|
||||
: el("footer", { className: "footer" }).append(
|
||||
el("span", { className: "todo-count" }).append(
|
||||
noOfLeft()
|
||||
el("strong", length + " " + (length === 1 ? "item" : "items")),
|
||||
),
|
||||
memo("filters", ()=>
|
||||
el("ul", { className: "filters" }).append(
|
||||
@ -100,7 +100,7 @@ function Todos(){
|
||||
)
|
||||
),
|
||||
),
|
||||
todos.length - todosRemainingS.get() === 0
|
||||
length - todosRemainingS.get() === 0
|
||||
? el()
|
||||
: memo("delete", () =>
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,7 +16,11 @@ import { getLibraryUrl } from "./components/getLibraryUrl.html.js";
|
||||
/** @param {string} url */
|
||||
const fileURL= url=> new URL(url, import.meta.url);
|
||||
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: Model–view–viewmodel`,
|
||||
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(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("p").append(T`
|
||||
Use the interactive selector below to choose your preferred format:
|
||||
|
@ -311,9 +311,9 @@ export function page({ pkg, info }){
|
||||
el(code, { content: `
|
||||
// Dynamic class attributes
|
||||
el("a", {
|
||||
textContent: "All",
|
||||
className: S(()=> pageS.get() === "all" ? "selected" : ""),
|
||||
href: "#"
|
||||
textContent,
|
||||
classList: { selected: S(()=> pageS.get() === textContent.toLowerCase()) },
|
||||
href: \`#\${textContent.toLowerCase()}\`
|
||||
})
|
||||
|
||||
// Reactive classList
|
||||
@ -355,18 +355,25 @@ export function page({ pkg, info }){
|
||||
|
||||
el("h4", t`Memoizing UI Sections`),
|
||||
el(code, { content: `
|
||||
S.el(todosS, todos => memo(todos.length, length=> length
|
||||
? el("footer", { className: "footer" }).append(
|
||||
// Footer content...
|
||||
S.el(todosS, ({ length }) => !length
|
||||
? el()
|
||||
: el("footer", { className: "footer" }).append(
|
||||
// …
|
||||
memo("filters", ()=>
|
||||
// …
|
||||
el("a", {
|
||||
textContent,
|
||||
classList: { selected: S(()=> pageS.get() === textContent.toLowerCase()) },
|
||||
href: \`#\${textContent.toLowerCase()}\`
|
||||
})
|
||||
// …
|
||||
)
|
||||
: el()
|
||||
))
|
||||
`, page_id }),
|
||||
|
||||
el("p").append(T`
|
||||
By memoizing based on the todos length, the entire footer component is only re-rendered
|
||||
when todos are added or removed, not when their properties change. This improves performance
|
||||
by avoiding unnecessary DOM operations.
|
||||
We memoize the UI section and uses derived signal for the classList. Re-rendering this part is therefore
|
||||
unnecessary when the number of todos changes.
|
||||
`),
|
||||
|
||||
el("div", { className: "tip" }).append(
|
||||
@ -389,8 +396,10 @@ export function page({ pkg, info }){
|
||||
`),
|
||||
el(code, { content: `
|
||||
// Event handlers in the main component
|
||||
const onDelete = on("todo:delete", ev => S.action(todosS, "delete", ev.detail));
|
||||
const onEdit = on("todo:edit", ev => S.action(todosS, "edit", ev.detail));
|
||||
const onDelete = on("todo:delete", ev =>
|
||||
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 }),
|
||||
|
||||
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(code, { content: `
|
||||
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
|
||||
)
|
||||
: el()
|
||||
)
|
||||
`, page_id }),
|
||||
|
||||
el("h4", t`Conditional Edit Form`),
|
||||
el(code, { content: `
|
||||
S.el(isEditing, editing => editing
|
||||
? el("form", null, onSubmitEdit).append(
|
||||
S.el(isEditing, editing => !editing
|
||||
? el()
|
||||
: el("form", null, onSubmitEdit).append(
|
||||
el("input", {
|
||||
className: "edit",
|
||||
name: "edit",
|
||||
name: formEdit,
|
||||
value: title,
|
||||
"data-id": id
|
||||
}, onBlurEdit, onKeyDown, addFocus)
|
||||
)
|
||||
: el()
|
||||
)
|
||||
`, 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("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("strong", "Persistent Storage:")} Automatically saving application state with signal listeners
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { T, t } from "./utils/index.js";
|
||||
export const info= {
|
||||
title: t`Ireland Components`,
|
||||
fullTitle: t`Interactive Demo Components with Server-Side Pre-Rendering`,
|
||||
description: t`Creating live, interactive component examples in documentation with server-side
|
||||
rendering and client-side hydration.`,
|
||||
fullTitle: t`Server-Side Pre-Rendering and Client-Side Rehydration`,
|
||||
description: t`Using Ireland components for server-side pre-rendering and client-side rehydration`,
|
||||
};
|
||||
|
||||
import { el } from "deka-dom-el";
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "deka-dom-el",
|
||||
"version": "0.9.3-alpha",
|
||||
"version": "0.9.4-alpha",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "deka-dom-el",
|
||||
"version": "0.9.3-alpha",
|
||||
"version": "0.9.4-alpha",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@size-limit/preset-small-lib": "~11.2",
|
||||
|
15
package.json
15
package.json
@ -1,13 +1,13 @@
|
||||
{
|
||||
"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.",
|
||||
"author": "Jan Andrle <andrle.jan@centrum.cz>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/jaandrle/deka-dom-el",
|
||||
"homepage": "https://jaandrle.github.io/deka-dom-el/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:jaandrle/deka-dom-el.git"
|
||||
"url": "git+ssh://git@github.com/jaandrle/deka-dom-el.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jaandrle/deka-dom-el/issues"
|
||||
@ -52,7 +52,6 @@
|
||||
"maxdepth": 3,
|
||||
"maxcomplexity": 14,
|
||||
"globals": {
|
||||
"requestIdleCallback": false,
|
||||
"AbortController": false,
|
||||
"AbortSignal": false,
|
||||
"FinalizationRegistry": false
|
||||
@ -61,25 +60,25 @@
|
||||
"size-limit": [
|
||||
{
|
||||
"path": "./index.js",
|
||||
"limit": "10.5 kB",
|
||||
"limit": "10 kB",
|
||||
"gzip": false,
|
||||
"brotli": false
|
||||
},
|
||||
{
|
||||
"path": "./signals.js",
|
||||
"limit": "12.5 kB",
|
||||
"limit": "12.2 kB",
|
||||
"gzip": false,
|
||||
"brotli": false
|
||||
},
|
||||
{
|
||||
"path": "./index-with-signals.js",
|
||||
"limit": "15 kB",
|
||||
"limit": "14.75 kB",
|
||||
"gzip": false,
|
||||
"brotli": false
|
||||
},
|
||||
{
|
||||
"path": "./index-with-signals.js",
|
||||
"limit": "5.5 kB"
|
||||
"limit": "5.25 kB"
|
||||
}
|
||||
],
|
||||
"modifyEsbuildConfig": {
|
||||
|
@ -1,5 +1,5 @@
|
||||
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
|
||||
@ -149,15 +149,6 @@ function connectionsChangesObserverConstructor(){
|
||||
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
|
||||
* @param {Element} element - Parent element
|
||||
|
@ -68,3 +68,12 @@ export function observedAttributes(instance, observedAttribute){
|
||||
* @returns {string} The camelCase string
|
||||
*/
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { queueSignalWrite, mark } from "./helpers.js";
|
||||
export { mark };
|
||||
import { hasOwn, oCreate, oAssign } from "../helpers.js";
|
||||
import { hasOwn, oCreate, oAssign, requestIdle } from "../helpers.js";
|
||||
|
||||
const Signal = oCreate(null, {
|
||||
get: { value(){ return read(this); } },
|
||||
@ -214,7 +214,7 @@ signal.el= function(s, map){
|
||||
*/
|
||||
function requestCleanUpReactives(host){
|
||||
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));
|
||||
});
|
||||
|
Reference in New Issue
Block a user