diff --git a/bs/docs.js b/bs/docs.js index 7e80fbd..4c89dd1 100755 --- a/bs/docs.js +++ b/bs/docs.js @@ -1,23 +1,49 @@ #!/usr/bin/env nodejsscript /* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */ +echo("Building static documentation files…"); +echo("Preparing…"); const path_target= { root: "docs/", - html: "docs/index.html", css: "docs/" }; import { createHTMl } from "./docs/jsdom.js"; -const ssr= createHTMl(""); import { register } from "../jsdom.js"; -const { el }= await register(ssr.dom); - -echo("Loading components…"); const pkg= s.cat("package.json").xargs(JSON.parse); -const { page, css }= await import("../docs_src/index.html.js"); //→ TODO: important to mention in docs!!! -document.body.append( - el(page, { pkg, path_target }), - el("script", { src: "https://cdn.jsdelivr.net/npm/shiki" }), - el("script", { src: "index.js", type: "module" }) -); +const pages= [ + { id: "index", title: "Introduction", description: "Introducing a library and motivations." }, + { id: "elements", title: "Elements", description: "Basic concepts of elements modifications and creations." } +]; -s.echo(ssr.serialize()).to(path_target.html); -s.echo(css.content).to(path_target.css+"index.css"); +for(const info of pages){ + const { id }= info; + echo(`Generating ${id}.html…`); + const ssr= createHTMl(""); + const { el }= await register(ssr.dom); + const { page, css }= await import(`../docs_src/${id}.html.js`); //→ TODO: important to mention in docs!!! + document.body.append( + el(page, { pkg, info, path_target, pages, registerClientFile }), + ); + + echo.use("-R", `Writing ${id}.html…`); + s.echo(ssr.serialize()).to(path_target.root+id+".html"); + s.echo(css.content).to(path_target.css+id+".css"); +} +echo("Done"); + + +/** + * @typedef registerClientFile + * @type {function} + * @param {URL} url + * @param {HTMLScriptElement|HTMLLinkElement} [element_head] + * */ +function registerClientFile(url, element_head){ + const file_name= url.pathname.split("/").pop(); + s.cat(url).to(path_target.root+file_name); + + if(!element_head) return; + element_head[element_head instanceof HTMLScriptElement ? "src" : "href"]= file_name; + document.head.append( + element_head + ); +} diff --git a/docs/elements.css b/docs/elements.css new file mode 100644 index 0000000..2ebc7e0 --- /dev/null +++ b/docs/elements.css @@ -0,0 +1,125 @@ +@import url(https://cdn.simplecss.org/simple.min.css); +:root{ + --body-max-width: 45rem; + --marked: #fb3779; + --code: #0d47a1; + --accent: #d81b60; +} +@media (prefers-color-scheme:dark) { + :root { + --accent: #f06292; + --code: #62c1f0; + } +} +body { + grid-template-columns: 100%; + grid-template-areas: "header" "sidebar" "content"; +} +@media (min-width:768px) { + body{ + grid-template-rows: auto auto; + grid-template-columns: calc(var(--body-max-width) / 3) auto; + grid-template-areas: + "header header" + "sidebar content" + } +} +body > *{ + grid-column: unset; +} +body > header{ + grid-area: header; + padding: 0; +} +body > nav{ + grid-area: sidebar; + background-color: var(--accent-bg); + display: flex; + flex-flow: column nowrap; +} +body > nav { + font-size:1rem; + line-height:2; + padding:1rem 0 0 0 +} +body > nav ol, +body > nav ul { + align-content:space-around; + align-items:center; + display:flex; + flex-direction:row; + flex-wrap:wrap; + justify-content:center; + list-style-type:none; + margin:0; + padding:0 +} +body > nav ol li, +body > nav ul li { + display:inline-block +} +body > nav a, +body > nav a:visited { + margin: 0 .5rem 1rem .5rem; + border: 1px solid currentColor; + border-radius: var(--standard-border-radius); + color: var(--text-light); + display: inline-block; + padding: .1rem 1rem; + text-decoration: none; + cursor: pointer; + transition: all .15s; +} +body > nav a.current, +body > nav a[aria-current=page] { + background-color: var(--bg); + color: var(--text); +} +body > nav a:hover{ + background-color: var(--bg); + color: var(--accent); +} +@media only screen and (max-width:720px) { + body > nav{ + flex-flow: row wrap; + padding-block: .5rem; + } + body > nav a { + border:none; + text-decoration:underline; + margin-block: .1rem; + padding-block:.1rem; + line-height: 1rem; + font-size: .9rem; + } +} +main{ + grid-area: content; + display: grid; + grid-template-columns: 1fr min(var(--body-max-width), 90%) 1fr; +} +main > *{ + grid-column: 2; +} +.icon { + vertical-align: sub; + padding-right: .25rem; + display: inline-block; + width: 1em; + height: 1.3em; + margin-right: 0.2rem; + stroke-width: 0; + stroke: currentColor; + fill: currentColor; +} +.note{ + font-size: .9rem; + font-style: italic; +} +.example{ + grid-column: 1/-1; + width: 100%; + max-width: calc(9/5 * var(--body-max-width)); + height: calc(3/5 * var(--body-max-width)); + margin-inline: auto; +} \ No newline at end of file diff --git a/docs/elements.html b/docs/elements.html new file mode 100644 index 0000000..21bdd1c --- /dev/null +++ b/docs/elements.html @@ -0,0 +1,103 @@ +`deka-dom-el` — Elements

`deka-dom-el` — Elements

Basic concepts of elements modifications and creations.

Native JavaScript DOM elements creations

Let’s go through all patterns we would like to use and what needs to be improved for better experience.

Creating element(s) (with custom attributes)

You can create a native DOM element by using the document.createElement(). It is also possible to provide a some attribute(s) declaratively using Object.assign(). More precisely, this way you can sets some IDL.

document.body.append(
+	document.createElement("div")
+);
+console.log(
+	"Emty div is generated inside <body>:",
+	document.body.innerHTML.includes("<div></div>")
+);
+
+document.body.append(
+	Object.assign(
+		document.createElement("p"),
+		{ textContent: "Element’s text content.", style: "color: coral;" }
+	)
+);
+

To make this easier, you can use the el function. Internally in basic examples, it is wrapper around assign(document.createElement(…), { … }).

import { el, assign } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
+const color= "lightcoral";
+document.body.append(
+	el("p", { textContent: "Hello (first time)", style: { color } })
+);
+document.body.append(
+	assign(
+		document.createElement("p"),
+		{ textContent: "Hello (second time)", style: { color } }
+	)
+);
+

The assign function provides improved behaviour of Object.assign(). You can declaratively sets any IDL and attribute of the given element. Function prefers IDL and fallback to the element.setAttribute if there is no writable property in the element prototype.

You can study all JavaScript elements interfaces to the corresponding HTML elements. All HTML elements inherits from HTMLElement. To see all available IDLs for example for paragraphs, see HTMLParagraphElement. Moreover, the assign provides a way to sets declaratively some convenient properties:

For processing, the assign internally uses assignAttribute and classListDeclarative.

import { assignAttribute, classListDeclarative } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
+const paragraph= document.createElement("p");
+
+assignAttribute(paragraph, "textContent", "Hello, world!");
+assignAttribute(paragraph, "style", { color: "navy" });
+
+assignAttribute(paragraph, "dataTest1", "v1");
+assignAttribute(paragraph, "dataset", { test2: "v2" });
+
+assignAttribute(paragraph, "ariaLabel", "v1");
+assignAttribute(paragraph, "ariaset", { role: "none" });
+
+
+classListDeclarative(paragraph, {
+	classAdd: true,
+	classRemove: false,
+	classAdd1: 1,
+	classRemove1: 0,
+	classToggle: -1
+});
+
+console.log(paragraph.outerHTML);
+document.body.append(
+	paragraph
+);
+

Native JavaScript templating

By default, the native JS has no good way to define HTML template using DOM API:

document.body.append(
+	document.createElement("div"),
+	document.createElement("span"),
+	document.createElement("main")
+);
+console.log(document.body.innerHTML.includes("<div></div><span></span><main></main>"));
+const template= document.createElement("main").append(
+	document.createElement("div"),
+	document.createElement("span"),
+);
+console.log(typeof template==="undefined");
+

This library therefore ooverwrites the append method to always return parent element.

import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
+document.head.append(
+	el("style").append(
+		"tr, td{ border: 1px solid red; padding: 1em; }",
+		"table{ border-collapse: collapse; }"
+	)
+);
+document.body.append(
+	el("p", "Example of a complex template. Using for example nesting lists:"),
+	el("ul").append(
+		el("li", "List item 1"),
+		el("li").append(
+			el("ul").append(
+				el("li", "Nested list item 1"),
+			)
+		)
+	),
+	el("table").append(
+		el("tr").append(
+			el("td", "Row 1 – Col 1"),
+			el("td", "Row 1 – Col 2")
+		)
+	)
+);
+

Creating advanced (reactive) templates and components

Basic components

You can use functions for encapsulation (repeating) logic. The el accepts function as first argument. In that case, the function should return dom elements and the second argument for el is argument for given element.

import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
+document.head.append(
+	el("style").append(
+		".class1{ font-weight: bold; }",
+		".class2{ color: purple; }"
+	)
+);
+document.body.append(
+	el(component, { className: "class2", textContent: "Hello World!" }),
+	component({ className: "class2", textContent: "Hello World!" })
+);
+
+function component({ className, textContent }){
+	return el("div", { className: [ "class1", className ] }).append(
+		el("p", textContent)
+	);
+}
+

It is nice to use similar naming convention as native DOM API.

\ No newline at end of file diff --git a/docs/index.css b/docs/index.css index 50640a7..2ebc7e0 100644 --- a/docs/index.css +++ b/docs/index.css @@ -6,17 +6,100 @@ --accent: #d81b60; } @media (prefers-color-scheme:dark) { - ::backdrop, :root { + :root { --accent: #f06292; --code: #62c1f0; } } body { + grid-template-columns: 100%; + grid-template-areas: "header" "sidebar" "content"; +} +@media (min-width:768px) { + body{ + grid-template-rows: auto auto; + grid-template-columns: calc(var(--body-max-width) / 3) auto; + grid-template-areas: + "header header" + "sidebar content" + } +} +body > *{ + grid-column: unset; +} +body > header{ + grid-area: header; + padding: 0; +} +body > nav{ + grid-area: sidebar; + background-color: var(--accent-bg); + display: flex; + flex-flow: column nowrap; +} +body > nav { + font-size:1rem; + line-height:2; + padding:1rem 0 0 0 +} +body > nav ol, +body > nav ul { + align-content:space-around; + align-items:center; + display:flex; + flex-direction:row; + flex-wrap:wrap; + justify-content:center; + list-style-type:none; + margin:0; + padding:0 +} +body > nav ol li, +body > nav ul li { + display:inline-block +} +body > nav a, +body > nav a:visited { + margin: 0 .5rem 1rem .5rem; + border: 1px solid currentColor; + border-radius: var(--standard-border-radius); + color: var(--text-light); + display: inline-block; + padding: .1rem 1rem; + text-decoration: none; + cursor: pointer; + transition: all .15s; +} +body > nav a.current, +body > nav a[aria-current=page] { + background-color: var(--bg); + color: var(--text); +} +body > nav a:hover{ + background-color: var(--bg); + color: var(--accent); +} +@media only screen and (max-width:720px) { + body > nav{ + flex-flow: row wrap; + padding-block: .5rem; + } + body > nav a { + border:none; + text-decoration:underline; + margin-block: .1rem; + padding-block:.1rem; + line-height: 1rem; + font-size: .9rem; + } +} +main{ + grid-area: content; + display: grid; grid-template-columns: 1fr min(var(--body-max-width), 90%) 1fr; } -nav a.current { - color: var(--accent) !important; - border-color: var(--accent) !important; +main > *{ + grid-column: 2; } .icon { vertical-align: sub; @@ -34,7 +117,7 @@ nav a.current { font-style: italic; } .example{ - grid-column: 1/4; + grid-column: 1/-1; width: 100%; max-width: calc(9/5 * var(--body-max-width)); height: calc(3/5 * var(--body-max-width)); diff --git a/docs/index.html b/docs/index.html index 64568dd..79c9d68 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,4 +1,4 @@ -`deka-dom-el` — Introduction/Guide

`deka-dom-el` — Introduction/Guide

The library tries to provide pure JavaScript tool(s) to create reactive interfaces.

The main goals are:

It is, in fact, an reimplementation of jaandrle/dollar_dom_component.

import { el, S } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
+`deka-dom-el` — Introduction

`deka-dom-el` — Introduction

Introducing a library and motivations.

The library tries to provide pure JavaScript tool(s) to create reactive interfaces.

The main goals are:

  • provide a small wrappers/utilities around the native JavaScript DOM
  • keep library size around 10kB at maximum (if possible)
  • zero dependencies (if possible)
  • prefer a declarative/functional approach
  • unopinionated (allow alternative methods)

It is, in fact, an reimplementation of jaandrle/dollar_dom_component.

import { el, S } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
 const clicks= S(0);
 document.body.append(
 	el().append(
@@ -12,106 +12,4 @@ document.body.append(
 		})
 	)
 );
-

Native JavaScript DOM elements creations

Let’s go through all patterns we would like to use and what needs to be improved for better experience.

Creating element(s) (with custom attributes)

You can create a native DOM element by using the document.createElement(). It is also possible to provide a some attribute(s) declaratively using Object.assign(). More precisely, this way you can sets some IDL.

document.body.append(
-	document.createElement("div")
-);
-console.log(
-	"Emty div is generated inside <body>:",
-	document.body.innerHTML.includes("<div></div>")
-);
-
-document.body.append(
-	Object.assign(
-		document.createElement("p"),
-		{ textContent: "Element’s text content.", style: "color: coral;" }
-	)
-);
-

To make this easier, you can use the el function. Internally in basic examples, it is wrapper around assign(document.createElement(…), { … }).

import { el, assign } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
-const color= "lightcoral";
-document.body.append(
-	el("p", { textContent: "Hello (first time)", style: { color } })
-);
-document.body.append(
-	assign(
-		document.createElement("p"),
-		{ textContent: "Hello (second time)", style: { color } }
-	)
-);
-

The assign function provides improved behaviour of Object.assign(). You can declaratively sets any IDL and attribute of the given element. Function prefers IDL and fallback to the element.setAttribute if there is no writable property in the element prototype.

You can study all JavaScript elements interfaces to the corresponding HTML elements. All HTML elements inherits from HTMLElement. To see all available IDLs for example for paragraphs, see HTMLParagraphElement. Moreover, the assign provides a way to sets declaratively some convenient properties:

  • It is possible to sets data-*/aria-* attributes using object notation.
  • In opposite, it is also possible to sets data-*/aria-* attribute using camelCase notation.
  • You can use string or object as a value for style property.
  • className (IDL – preffered)/class are ways to add CSS class to the element. You can use string (similarly to class="…" syntax in HTML) or array of strings. This is handy to concat conditional classes.
  • Use classList to toggle specific classes. This will be handy later when the reactivity via signals is beeing introduced.
  • The assign also accepts the undefined as a value for any property to remove it from the element declaratively. Also for some IDL the corresponding attribute is removed as it can be confusing. For example, natievly the element’s id is removed by setting the IDL to an empty string.

For processing, the assign internally uses assignAttribute and classListDeclarative.

import { assignAttribute, classListDeclarative } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
-const paragraph= document.createElement("p");
-
-assignAttribute(paragraph, "textContent", "Hello, world!");
-assignAttribute(paragraph, "style", { color: "navy" });
-
-assignAttribute(paragraph, "dataTest1", "v1");
-assignAttribute(paragraph, "dataset", { test2: "v2" });
-
-assignAttribute(paragraph, "ariaLabel", "v1");
-assignAttribute(paragraph, "ariaset", { role: "none" });
-
-
-classListDeclarative(paragraph, {
-	classAdd: true,
-	classRemove: false,
-	classAdd1: 1,
-	classRemove1: 0,
-	classToggle: -1
-});
-
-console.log(paragraph.outerHTML);
-document.body.append(
-	paragraph
-);
-

Native JavaScript templating

By default, the native JS has no good way to define HTML template using DOM API:

document.body.append(
-	document.createElement("div"),
-	document.createElement("span"),
-	document.createElement("main")
-);
-console.log(document.body.innerHTML.includes("<div></div><span></span><main></main>"));
-const template= document.createElement("main").append(
-	document.createElement("div"),
-	document.createElement("span"),
-);
-console.log(typeof template==="undefined");
-

This library therefore ooverwrites the append method to always return parent element.

import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
-document.head.append(
-	el("style").append(
-		"tr, td{ border: 1px solid red; padding: 1em; }",
-		"table{ border-collapse: collapse; }"
-	)
-);
-document.body.append(
-	el("p", "Example of a complex template. Using for example nesting lists:"),
-	el("ul").append(
-		el("li", "List item 1"),
-		el("li").append(
-			el("ul").append(
-				el("li", "Nested list item 1"),
-			)
-		)
-	),
-	el("table").append(
-		el("tr").append(
-			el("td", "Row 1 – Col 1"),
-			el("td", "Row 1 – Col 2")
-		)
-	)
-);
-

Creating advanced (reactive) templates and components

Basic components

You can use functions for encapsulation (repeating) logic. The el accepts function as first argument. In that case, the function should return dom elements and the second argument for el is argument for given element.

import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";
-document.head.append(
-	el("style").append(
-		".class1{ font-weight: bold; }",
-		".class2{ color: purple; }"
-	)
-);
-document.body.append(
-	el(component, { className: "class2", textContent: "Hello World!" }),
-	component({ className: "class2", textContent: "Hello World!" })
-);
-
-function component({ className, textContent }){
-	return el("div", { className: [ "class1", className ] }).append(
-		el("p", textContent)
-	);
-}
-

It is nice to use similar naming convention as native DOM API.

\ No newline at end of file +
\ No newline at end of file diff --git a/docs_src/components/example.html.js b/docs_src/components/example.html.js index 23aaafe..00bd57f 100644 --- a/docs_src/components/example.html.js +++ b/docs_src/components/example.html.js @@ -1,8 +1,8 @@ -let is_registered= false; +let is_registered= {}; import { styles } from "../index.css.js"; export const css= styles().scope(example).css` :host{ - grid-column: 1/4; + grid-column: 1/-1; width: 100%; max-width: calc(9/5 * var(--body-max-width)); height: calc(3/5 * var(--body-max-width)); @@ -10,12 +10,17 @@ export const css= styles().scope(example).css` } ` import { el } from "deka-dom-el"; -export function example({ src, language= "javascript" }){ - register(); - const cwd= "components"; - src= "."+src.slice(src.indexOf(cwd)+cwd.length); - const code= s.cat(new URL(src, import.meta.url)) - .toString() +/** + * Prints code to the page and registers flems to make it interactive. + * @param {object} attrs + * @param {URL} attrs.src Example code file path + * @param {string} [attrs.language="javascript"] Language of the code + * @param {string} attrs.page_id ID of the page + * @param {import("../types.d.ts").registerClientFile} attrs.registerClientFile + * */ +export function example({ src, language= "javascript", page_id, registerClientFile }){ + registerClientPart(page_id, registerClientFile); + const code= s.cat(src).toString() .replaceAll(' from "../../../index-with-signals.js";', ' from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js";'); const id= "code-"+Math.random().toString(36).slice(2, 7); return el().append( @@ -34,10 +39,17 @@ function elCode({ id, content }){ }); return el("script", `Flems(document.getElementById("${id}"), JSON.parse(${JSON.stringify(options)}));`); } -function register(){ - if(is_registered) return; +function registerClientPart(page_id, registerClientFile){ + if(is_registered[page_id]) return; + + //★ Highlite code when no flems? document.head.append( - el("script", { src: "https://flems.io/flems.html", type: "text/javascript", charset: "utf-8" }) + el("script", { src: "https://flems.io/flems.html", type: "text/javascript", charset: "utf-8" }), + //★ el("script", { src: "https://cdn.jsdelivr.net/npm/shiki", defer: true }), ); - is_registered= true; + //★ egisterClientFile( + //★ new URL("./example.js.js", import.meta.url), + //★ el("script", { type: "module" }) + //★ ; + is_registered[page_id]= true; } diff --git a/docs/index.js b/docs_src/components/example.js.js similarity index 100% rename from docs/index.js rename to docs_src/components/example.js.js diff --git a/docs_src/elements.html.js b/docs_src/elements.html.js new file mode 100644 index 0000000..39c7df3 --- /dev/null +++ b/docs_src/elements.html.js @@ -0,0 +1,108 @@ +import { el } from "deka-dom-el"; +import { styles, common } from "./index.css.js"; +import { example, css as example_css } from "./components/example.html.js"; +export const css= styles() +.include(common) +.css` +.note{ + font-size: .9rem; + font-style: italic; +} +` +.include(example_css); + +import { header } from "./layout/head.html.js"; +/** @param {import("./types.d.ts").PageAttrs} attrs */ +export function page({ pkg, info, path_target, pages, registerClientFile }){ + const page_id= info.id; + return el().append( + el(header, { info, pkg, path_target, pages }), + el("main").append( + el("h2", "Native JavaScript DOM elements creations"), + el("p", "Let’s go through all patterns we would like to use and what needs to be improved for better experience."), + + el("h3", "Creating element(s) (with custom attributes)"), + el("p").append( + "You can create a native DOM element by using the ", + el("a", { href: "https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement", title: "MDN documentation for document.createElement()" }).append( + el("code", "document.createElement()") + ), ". ", + "It is also possible to provide a some attribute(s) declaratively using ", el("code", "Object.assign()"), ". ", + "More precisely, this way you can sets some ", + el("a", { + href: "https://developer.mozilla.org/en-US/docs/Glossary/IDL", + title: "MDN page about Interface Description Language" + }).append( + el("abbr", { textContent: "IDL", title: "Interface Description Language" }) + ), "." + ), + el(example, { src: new URL("./components/examples/nativeCreateElement.js", import.meta.url), page_id, registerClientFile }), + el("p").append( + "To make this easier, you can use the ", el("code", "el"), " function. ", + "Internally in basic examples, it is wrapper around ", el("code", "assign(document.createElement(…), { … })"), "." + ), + el(example, { src: new URL("./components/examples/dekaCreateElement.js", import.meta.url), page_id, registerClientFile }), + el("p").append( + "The ", el("code", "assign"), " function provides improved behaviour of ", el("code", "Object.assign()"), ". ", + "You can declaratively sets any IDL and attribute of the given element. ", + "Function prefers IDL and fallback to the ", el("code", "element.setAttribute"), " if there is no writable property in the element prototype." + ), + el("p").append( + "You can study all JavaScript elements interfaces to the corresponding HTML elements. ", + "All HTML elements inherits from ", el("a", { textContent: "HTMLElement", href: "https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" }), ". ", + "To see all available IDLs for example for paragraphs, see ", el("a", { textContent: "HTMLParagraphElement", href: "https://developer.mozilla.org/en-US/docs/Web/API/HTMLParagraphElement" }), ". ", + "Moreover, the ", el("code", "assign"), " provides a way to sets declaratively some convenient properties:" + ), + el("ul").append( + el("li").append( + "It is possible to sets ", el("code", "data-*"), "/", el("code", "aria-*"), " attributes using object notation." + ), + el("li").append( + "In opposite, it is also possible to sets ", el("code", "data-*"), "/", el("code", "aria-*"), " attribute using camelCase notation." + ), + el("li").append( + "You can use string or object as a value for ", el("code", "style"), " property." + ), + el("li").append( + el("code", "className"), " (IDL – preffered)/", el("code", "class"), " are ways to add CSS class to the element. ", + "You can use string (similarly to ", el("code", "class=\"…\"") ," syntax in HTML) or array of strings. ", + "This is handy to concat conditional classes." + ), + el("li").append( + "Use ", el("code", "classList"), " to toggle specific classes. This will be handy later when the reactivity via signals is beeing introduced.", + ), + el("li").append( + "The ", el("code", "assign"), " also accepts the ", el("code", "undefined"), " as a value for any property to remove it from the element declaratively. ", + "Also for some IDL the corresponding attribute is removed as it can be confusing. ", + el("em").append( + "For example, natievly the element’s ", el("code", "id"), " is removed by setting the IDL to an empty string." + ) + ) + ), + el("p").append( + "For processing, the ", el("code", "assign"), " internally uses ", el("code", "assignAttribute"), " and ", el("code", "classListDeclarative"), "." + ), + el(example, { src: new URL("./components/examples/dekaAssign.js", import.meta.url), page_id, registerClientFile }), + + el("h3", "Native JavaScript templating"), + el("p", "By default, the native JS has no good way to define HTML template using DOM API:"), + el(example, { src: new URL("./components/examples/nativeAppend.js", import.meta.url), page_id, registerClientFile }), + el("p").append( + "This library therefore ooverwrites the ", el("code", "append"), " method to always return parent element." + ), + el(example, { src: new URL("./components/examples/dekaAppend.js", import.meta.url), page_id, registerClientFile }), + + + el("h2", "Creating advanced (reactive) templates and components"), + + el("h3", "Basic components"), + el("p").append( + "You can use functions for encapsulation (repeating) logic. ", + "The ", el("code", "el"), " accepts function as first argument. ", + "In that case, the function should return dom elements and the second argument for ", el("code", "el"), " is argument for given element." + ), + el(example, { src: new URL("./components/examples/dekaBasicComponent.js", import.meta.url), page_id, registerClientFile }), + el("p", "It is nice to use similar naming convention as native DOM API.") + ) + ); +} diff --git a/docs_src/index.css.js b/docs_src/index.css.js index 36c9dbb..4ca22cc 100644 --- a/docs_src/index.css.js +++ b/docs_src/index.css.js @@ -36,17 +36,100 @@ export const common= css` --accent: #d81b60; } @media (prefers-color-scheme:dark) { - ::backdrop, :root { + :root { --accent: #f06292; --code: #62c1f0; } } body { + grid-template-columns: 100%; + grid-template-areas: "header" "sidebar" "content"; +} +@media (min-width:768px) { + body{ + grid-template-rows: auto auto; + grid-template-columns: calc(var(--body-max-width) / 3) auto; + grid-template-areas: + "header header" + "sidebar content" + } +} +body > *{ + grid-column: unset; +} +body > header{ + grid-area: header; + padding: 0; +} +body > nav{ + grid-area: sidebar; + background-color: var(--accent-bg); + display: flex; + flex-flow: column nowrap; +} +body > nav { + font-size:1rem; + line-height:2; + padding:1rem 0 0 0 +} +body > nav ol, +body > nav ul { + align-content:space-around; + align-items:center; + display:flex; + flex-direction:row; + flex-wrap:wrap; + justify-content:center; + list-style-type:none; + margin:0; + padding:0 +} +body > nav ol li, +body > nav ul li { + display:inline-block +} +body > nav a, +body > nav a:visited { + margin: 0 .5rem 1rem .5rem; + border: 1px solid currentColor; + border-radius: var(--standard-border-radius); + color: var(--text-light); + display: inline-block; + padding: .1rem 1rem; + text-decoration: none; + cursor: pointer; + transition: all .15s; +} +body > nav a.current, +body > nav a[aria-current=page] { + background-color: var(--bg); + color: var(--text); +} +body > nav a:hover{ + background-color: var(--bg); + color: var(--accent); +} +@media only screen and (max-width:720px) { + body > nav{ + flex-flow: row wrap; + padding-block: .5rem; + } + body > nav a { + border:none; + text-decoration:underline; + margin-block: .1rem; + padding-block:.1rem; + line-height: 1rem; + font-size: .9rem; + } +} +main{ + grid-area: content; + display: grid; grid-template-columns: 1fr min(var(--body-max-width), 90%) 1fr; } -nav a.current { - color: var(--accent) !important; - border-color: var(--accent) !important; +main > *{ + grid-column: 2; } .icon { vertical-align: sub; diff --git a/docs_src/index.html.js b/docs_src/index.html.js index fa6cb7c..1506104 100644 --- a/docs_src/index.html.js +++ b/docs_src/index.html.js @@ -12,118 +12,31 @@ export const css= styles() .include(example_css); import { header } from "./layout/head.html.js"; -export function page({ pkg, path_target }){ +/** @param {import("./types.d.ts").PageAttrs} attrs */ +export function page({ pkg, info, path_target, pages, registerClientFile }){ + const page_id= info.id; return el().append( - el(header, { - id: "index", - title: "Introduction/Guide", - description: "Introducing basic concepts.", - pkg, path_target - }), - el("p", "The main goals are:"), - el("ul").append( - el("li", "provide a small wrappers/utilities around the native JavaScript DOM"), - el("li", "keep library size around 10kB at maximum (if possible)"), - el("li", "zero dependencies (if possible)"), - el("li", "prefer a declarative/functional approach"), - el("li", "unopinionated (allow alternative methods)"), - ), - el("p", { className: "note" }).append( - "It is, in fact, an reimplementation of ", - el("a", { - href: "https://github.com/jaandrle/dollar_dom_component", - title: "GitHub repository of library. Motto: Functional DOM components without JSX and virtual DOM.", - textContent: "jaandrle/dollar_dom_component" - }), - ".", - ), - el(example, { src: "./components/examples/helloWorld.js" }), - - - el("h2", "Native JavaScript DOM elements creations"), - el("p", "Let’s go through all patterns we would like to use and what needs to be improved for better experience."), - - el("h3", "Creating element(s) (with custom attributes)"), - el("p").append( - "You can create a native DOM element by using the ", - el("a", { href: "https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement", title: "MDN documentation for document.createElement()" }).append( - el("code", "document.createElement()") - ), ". ", - "It is also possible to provide a some attribute(s) declaratively using ", el("code", "Object.assign()"), ". ", - "More precisely, this way you can sets some ", - el("a", { - href: "https://developer.mozilla.org/en-US/docs/Glossary/IDL", - title: "MDN page about Interface Description Language" - }).append( - el("abbr", { textContent: "IDL", title: "Interface Description Language" }) - ), "." - ), - el(example, { src: "./components/examples/nativeCreateElement.js" }), - el("p").append( - "To make this easier, you can use the ", el("code", "el"), " function. ", - "Internally in basic examples, it is wrapper around ", el("code", "assign(document.createElement(…), { … })"), "." - ), - el(example, { src: "./components/examples/dekaCreateElement.js" }), - el("p").append( - "The ", el("code", "assign"), " function provides improved behaviour of ", el("code", "Object.assign()"), ". ", - "You can declaratively sets any IDL and attribute of the given element. ", - "Function prefers IDL and fallback to the ", el("code", "element.setAttribute"), " if there is no writable property in the element prototype." - ), - el("p").append( - "You can study all JavaScript elements interfaces to the corresponding HTML elements. ", - "All HTML elements inherits from ", el("a", { textContent: "HTMLElement", href: "https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" }), ". ", - "To see all available IDLs for example for paragraphs, see ", el("a", { textContent: "HTMLParagraphElement", href: "https://developer.mozilla.org/en-US/docs/Web/API/HTMLParagraphElement" }), ". ", - "Moreover, the ", el("code", "assign"), " provides a way to sets declaratively some convenient properties:" - ), - el("ul").append( - el("li").append( - "It is possible to sets ", el("code", "data-*"), "/", el("code", "aria-*"), " attributes using object notation." + el(header, { info, pkg, path_target, pages }), + el("main").append( + el("p", "The library tries to provide pure JavaScript tool(s) to create reactive interfaces. "), + el("p", "The main goals are:"), + el("ul").append( + el("li", "provide a small wrappers/utilities around the native JavaScript DOM"), + el("li", "keep library size around 10kB at maximum (if possible)"), + el("li", "zero dependencies (if possible)"), + el("li", "prefer a declarative/functional approach"), + el("li", "unopinionated (allow alternative methods)"), ), - el("li").append( - "In opposite, it is also possible to sets ", el("code", "data-*"), "/", el("code", "aria-*"), " attribute using camelCase notation." + el("p", { className: "note" }).append( + "It is, in fact, an reimplementation of ", + el("a", { + href: "https://github.com/jaandrle/dollar_dom_component", + title: "GitHub repository of library. Motto: Functional DOM components without JSX and virtual DOM.", + textContent: "jaandrle/dollar_dom_component" + }), + ".", ), - el("li").append( - "You can use string or object as a value for ", el("code", "style"), " property." - ), - el("li").append( - el("code", "className"), " (IDL – preffered)/", el("code", "class"), " are ways to add CSS class to the element. ", - "You can use string (similarly to ", el("code", "class=\"…\"") ," syntax in HTML) or array of strings. ", - "This is handy to concat conditional classes." - ), - el("li").append( - "Use ", el("code", "classList"), " to toggle specific classes. This will be handy later when the reactivity via signals is beeing introduced.", - ), - el("li").append( - "The ", el("code", "assign"), " also accepts the ", el("code", "undefined"), " as a value for any property to remove it from the element declaratively. ", - "Also for some IDL the corresponding attribute is removed as it can be confusing. ", - el("em").append( - "For example, natievly the element’s ", el("code", "id"), " is removed by setting the IDL to an empty string." - ) - ) - ), - el("p").append( - "For processing, the ", el("code", "assign"), " internally uses ", el("code", "assignAttribute"), " and ", el("code", "classListDeclarative"), "." - ), - el(example, { src: "./components/examples/dekaAssign.js" }), - - el("h3", "Native JavaScript templating"), - el("p", "By default, the native JS has no good way to define HTML template using DOM API:"), - el(example, { src: "./components/examples/nativeAppend.js" }), - el("p").append( - "This library therefore ooverwrites the ", el("code", "append"), " method to always return parent element." - ), - el(example, { src: "./components/examples/dekaAppend.js" }), - - - el("h2", "Creating advanced (reactive) templates and components"), - - el("h3", "Basic components"), - el("p").append( - "You can use functions for encapsulation (repeating) logic. ", - "The ", el("code", "el"), " accepts function as first argument. ", - "In that case, the function should return dom elements and the second argument for ", el("code", "el"), " is argument for given element." - ), - el(example, { src: "./components/examples/dekaBasicComponent.js" }), - el("p", "It is nice to use similar naming convention as native DOM API.") + el(example, { src: new URL("./components/examples/helloWorld.js", import.meta.url), page_id, registerClientFile }), + ) ); } diff --git a/docs_src/jsconfig.json b/docs_src/jsconfig.json new file mode 100644 index 0000000..10bbd46 --- /dev/null +++ b/docs_src/jsconfig.json @@ -0,0 +1,11 @@ +{ + "include": [ "*.js", "**/*.js", "../node_modules/nodejsscript/index.d.ts" ], + "compilerOptions": { + "module": "NodeNext", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "downlevelIteration": true, + "maxNodeModuleJsDepth": 1 + } +} diff --git a/docs_src/layout/head.html.js b/docs_src/layout/head.html.js index cee7df3..b892615 100644 --- a/docs_src/layout/head.html.js +++ b/docs_src/layout/head.html.js @@ -1,62 +1,34 @@ import { el, scope } from "deka-dom-el"; /** * @param {object} def - * @param {string} def.id Page `id` is used as stylesheet name. - * @param {string} def.title Page `title` is also used as `h1` - * @param {string} def.description Page 'description'. + * @param {import("../types.d.ts").Info} def.info * @param {object} def.pkg Package information. * @param {string} def.pkg.name * @param {string} def.pkg.description * @param {string} def.pkg.homepage * @param {{ root: string, css: string}} def.path_target Final URL where the page will be rendered. - * @param {object} def + * @param {import("../types.js").Pages} def.pages * */ -export function header({ id, title, description, pkg, path_target }){ +export function header({ info: { id, title, description }, pkg, path_target, pages }){ title= `\`${pkg.name}\` — ${title}`; document.head.append(head({ id, title, description, pkg, path_target })); - return el("header").append( + return el().append( + el("header").append( + el("h1", title), + el("p", description) + ), el("nav").append( el("a", { href: pkg.homepage }).append( el(iconGitHub), "GitHub" - ) - ), - el("h1", title), - el("p", "The library tries to provide pure JavaScript tool(s) to create reactive interfaces. ") - ); -} -function iconGitHub(){ - scope.namespace= "svg"; - return el("svg", { className: "icon", viewBox: "0 0 32 32" }).append( - el("path", { d: [ //see https://svg-path-visualizer.netlify.app/#M16%200.395c-8.836%200-16%207.163-16%2016%200%207.069%204.585%2013.067%2010.942%2015.182%200.8%200.148%201.094-0.347%201.094-0.77%200-0.381-0.015-1.642-0.022-2.979-4.452%200.968-5.391-1.888-5.391-1.888-0.728-1.849-1.776-2.341-1.776-2.341-1.452-0.993%200.11-0.973%200.11-0.973%201.606%200.113%202.452%201.649%202.452%201.649%201.427%202.446%203.743%201.739%204.656%201.33%200.143-1.034%200.558-1.74%201.016-2.14-3.554-0.404-7.29-1.777-7.29-7.907%200-1.747%200.625-3.174%201.649-4.295-0.166-0.403-0.714-2.030%200.155-4.234%200%200%201.344-0.43%204.401%201.64%201.276-0.355%202.645-0.532%204.005-0.539%201.359%200.006%202.729%200.184%204.008%200.539%203.054-2.070%204.395-1.64%204.395-1.64%200.871%202.204%200.323%203.831%200.157%204.234%201.026%201.12%201.647%202.548%201.647%204.295%200%206.145-3.743%207.498-7.306%207.895%200.574%200.497%201.085%201.47%201.085%202.963%200%202.141-0.019%203.864-0.019%204.391%200%200.426%200.288%200.925%201.099%200.768%206.354-2.118%2010.933-8.113%2010.933-15.18%200-8.837-7.164-16-16-16z - "M 16,0.395", - "c -8.836,0 -16,7.163 -16,16", - "c 0,7.069 4.585,13.067 10.942,15.182", - "c 0.8,0.148 1.094,-0.347 1.094,-0.77", - "c 0,-0.381 -0.015,-1.642 -0.022,-2.979", - "c -4.452,0.968 -5.391,-1.888 -5.391,-1.888", - "c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341", - "c -1.452,-0.993 0.11,-0.973 0.11,-0.973", - "c 1.606,0.113 2.452,1.649 2.452,1.649", - "c 1.427,2.446 3.743,1.739 4.656,1.33", - "c 0.143,-1.034 0.558,-1.74 1.016,-2.14", - "c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907", - "c 0,-1.747 0.625,-3.174 1.649,-4.295", - "c -0.166,-0.403 -0.714,-2.03 0.155,-4.234", - "c 0,0 1.344,-0.43 4.401,1.64", - "c 1.276,-0.355 2.645,-0.532 4.005,-0.539", - "c 1.359,0.006 2.729,0.184 4.008,0.539", - "c 3.054,-2.07 4.395,-1.64 4.395,-1.64", - "c 0.871,2.204 0.323,3.831 0.157,4.234", - "c 1.026,1.12 1.647,2.548 1.647,4.295", - "c 0,6.145 -3.743,7.498 -7.306,7.895", - "c 0.574,0.497 1.085,1.47 1.085,2.963", - "c 0,2.141 -0.019,3.864 -0.019,4.391", - "c 0,0.426 0.288,0.925 1.099,0.768", - "c 6.354,-2.118 10.933,-8.113 10.933,-15.18", - "c 0,-8.837 -7.164,-16 -16,-16", - "Z" - ].join("") }) + ), + ...pages.map((p, i)=> el("a", { + href: p.id==="index" ? "./" : p.id, + textContent: (i+1) + ". " + p.title, + title: p.description, + classList: { current: p.id===id } + })) + ) ); } function head({ id, title, description, pkg, path_target }){ @@ -93,3 +65,37 @@ function metaFacebook({ name, description, homepage }){ function stylesheetHref(path_target, name){ return path_target.css.replace(path_target.root, "")+name+".css"; } +function iconGitHub(){ + scope.namespace= "svg"; + return el("svg", { className: "icon", viewBox: "0 0 32 32" }).append( + el("path", { d: [ //see https://svg-path-visualizer.netlify.app/#M16%200.395c-8.836%200-16%207.163-16%2016%200%207.069%204.585%2013.067%2010.942%2015.182%200.8%200.148%201.094-0.347%201.094-0.77%200-0.381-0.015-1.642-0.022-2.979-4.452%200.968-5.391-1.888-5.391-1.888-0.728-1.849-1.776-2.341-1.776-2.341-1.452-0.993%200.11-0.973%200.11-0.973%201.606%200.113%202.452%201.649%202.452%201.649%201.427%202.446%203.743%201.739%204.656%201.33%200.143-1.034%200.558-1.74%201.016-2.14-3.554-0.404-7.29-1.777-7.29-7.907%200-1.747%200.625-3.174%201.649-4.295-0.166-0.403-0.714-2.030%200.155-4.234%200%200%201.344-0.43%204.401%201.64%201.276-0.355%202.645-0.532%204.005-0.539%201.359%200.006%202.729%200.184%204.008%200.539%203.054-2.070%204.395-1.64%204.395-1.64%200.871%202.204%200.323%203.831%200.157%204.234%201.026%201.12%201.647%202.548%201.647%204.295%200%206.145-3.743%207.498-7.306%207.895%200.574%200.497%201.085%201.47%201.085%202.963%200%202.141-0.019%203.864-0.019%204.391%200%200.426%200.288%200.925%201.099%200.768%206.354-2.118%2010.933-8.113%2010.933-15.18%200-8.837-7.164-16-16-16z + "M 16,0.395", + "c -8.836,0 -16,7.163 -16,16", + "c 0,7.069 4.585,13.067 10.942,15.182", + "c 0.8,0.148 1.094,-0.347 1.094,-0.77", + "c 0,-0.381 -0.015,-1.642 -0.022,-2.979", + "c -4.452,0.968 -5.391,-1.888 -5.391,-1.888", + "c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341", + "c -1.452,-0.993 0.11,-0.973 0.11,-0.973", + "c 1.606,0.113 2.452,1.649 2.452,1.649", + "c 1.427,2.446 3.743,1.739 4.656,1.33", + "c 0.143,-1.034 0.558,-1.74 1.016,-2.14", + "c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907", + "c 0,-1.747 0.625,-3.174 1.649,-4.295", + "c -0.166,-0.403 -0.714,-2.03 0.155,-4.234", + "c 0,0 1.344,-0.43 4.401,1.64", + "c 1.276,-0.355 2.645,-0.532 4.005,-0.539", + "c 1.359,0.006 2.729,0.184 4.008,0.539", + "c 3.054,-2.07 4.395,-1.64 4.395,-1.64", + "c 0.871,2.204 0.323,3.831 0.157,4.234", + "c 1.026,1.12 1.647,2.548 1.647,4.295", + "c 0,6.145 -3.743,7.498 -7.306,7.895", + "c 0.574,0.497 1.085,1.47 1.085,2.963", + "c 0,2.141 -0.019,3.864 -0.019,4.391", + "c 0,0.426 0.288,0.925 1.099,0.768", + "c 6.354,-2.118 10.933,-8.113 10.933,-15.18", + "c 0,-8.837 -7.164,-16 -16,-16", + "Z" + ].join("") }) + ); +} diff --git a/docs_src/types.d.ts b/docs_src/types.d.ts new file mode 100644 index 0000000..13168fb --- /dev/null +++ b/docs_src/types.d.ts @@ -0,0 +1,21 @@ +/** See `package.json` */ +export type Pkg= Record +export type Info= { + id: string, + title: string, + description: string, +} +export type Pages=Info[]; +export type PathTarget= { + root: string, + css: string +} +export type registerClientFile= import("../bs/docs.js").registerClientFile; +export type PageAttrs= { + pkg: Pkg, + info: Info, + pages: Pages, + path_target: PathTarget, + registerClientFile: registerClientFile +} + diff --git a/index.d.ts b/index.d.ts index ccffb6c..57ca496 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,8 +1,8 @@ declare global { type ddeComponentAttributes= Record | undefined | string; - type ddeElementExtender= (element: El)=> El; + type ddeElementExtender= (element: El)=> El; } -type ElementTagNameMap= HTMLElementTagNameMap & SVGElementTagNameMap & { +type ElementTagNameMap= HTMLElementTagNameMap & { // & SVGElementTagNameMap '#text': Text } type Element= ElementTagNameMap[keyof ElementTagNameMap]; @@ -14,7 +14,7 @@ type AttrsModified= { /** * Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1. In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))` for others. */ - classList: Record, + classList: Record, /** * By default simiral to `className`, but also supports `string[]` * */ @@ -49,12 +49,18 @@ export function el( export function el< A extends ddeComponentAttributes, - C extends (attr: A)=> Element>( + C extends (attr: A)=> Element | DocumentFragment>( fComponent: C, attrs?: A, ...extenders: ddeElementExtender>[] ): ReturnType +export function el( + tag_name: string, + attrs?: Record, + ...extenders: ddeElementExtender[] +): HTMLElement + export function dispatchEvent(element: HTMLElement, name: keyof DocumentEventMap): void; export function dispatchEvent(element: HTMLElement, name: string, data: any): void; interface On{ @@ -83,6 +89,12 @@ interface On{ } export const on: On; +export const scope: { + namespace: string, + host: ddeElementExtender, + elNamespace: (ns: string)=> ({ append(...els: (HTMLElement | SVGElement)[]): HTMLElement | SVGElement | DocumentFragment }) +}; + //TODO for SVG declare global{ interface HTMLDivElement{ append(...nodes: (Node | string)[]): HTMLDivElement; } diff --git a/index.js b/index.js index 52c6e66..3f8cada 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ -[ HTMLElement, SVGElement, DocumentFragment ].forEach(c=> { - const { append }= c.prototype; - c.prototype.append= function(...els){ append.apply(this, els); return this; }; -}); +if(typeof document.createDocumentFragment().append()==="undefined") + [ HTMLElement, SVGElement, DocumentFragment ].forEach(c=> { + const { append }= c.prototype; + c.prototype.append= function(...els){ append.apply(this, els); return this; }; + }); export * from "./src/dom.js"; export * from "./src/events.js"; diff --git a/jsdom.js b/jsdom.js index 484c142..34cf428 100644 --- a/jsdom.js +++ b/jsdom.js @@ -19,6 +19,12 @@ export function register(dom, keys_aditional= []){ globalThis.window= w; w.console= globalThis.console; } + if(typeof document.createDocumentFragment().append()==="undefined") + [ HTMLElement, SVGElement, DocumentFragment ].forEach(c=> { + const { append }= c.prototype; + c.prototype.append= function(...els){ append.apply(this, els); return this; }; + }); + dom_last= dom; return import("./index.js"); }