1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-07-01 04:12:14 +02:00

💥 📝

This commit is contained in:
2023-11-10 17:15:59 +01:00
parent 4d3a513713
commit 2569b9cd45
30 changed files with 420 additions and 361 deletions

View File

@ -0,0 +1,59 @@
import { registerClientFile, styles } from "../ssr.js";
styles.scope(code).css`
:host{
--shiki-color-text: #e9eded;
--shiki-color-background: #212121;
--shiki-token-constant: #82b1ff;
--shiki-token-string: #c3e88d;
--shiki-token-comment: #546e7a;
--shiki-token-keyword: #c792ea;
--shiki-token-parameter: #AA0000;
--shiki-token-function: #80cbae;
--shiki-token-string-expression: #c3e88d;
--shiki-token-punctuation: var(--code);
--shiki-token-link: #EE0000;
white-space: pre;
}
:host[data-js=todo]{
border: 1px solid var(--border);
border-radius: var(--standard-border-radius);
margin-bottom: 1rem;
${/* to fix shift when → dataJS=done */""}
margin-top: 18.4px;
padding: 1rem 1.4rem;
}
`;
import { el } from "deka-dom-el";
/**
* Prints code to the page and registers flems to make it interactive.
* @param {object} attrs
* @param {string} [attrs.id]
* @param {string} [attrs.className]
* @param {string} attrs.content Example code file path
* @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code
* @param {string} [attrs.page_id] ID of the page, if setted it registers shiki
* */
export function code({ id, content, language= "js", className= "code", page_id }){
let dataJS;
if(page_id){
registerClientPart(page_id);
dataJS= "todo";
}
return el("div", { id, className, dataJS }).append(
el("code", { className: "language-"+language, textContent: content })
);
}
let is_registered= {};
/** @param {string} page_id */
function registerClientPart(page_id){
if(is_registered[page_id]) return;
document.head.append(
el("script", { src: "https://cdn.jsdelivr.net/npm/shiki", defer: true }),
);
registerClientFile(
new URL("./code.js.js", import.meta.url),
el("script", { type: "module" })
);
is_registered[page_id]= true;
}

View File

@ -0,0 +1,12 @@
const highlighter= await shiki.getHighlighter({
theme: "css-variables",
langs: ["js", "ts", "css", "html", "shell"],
});
const codeBlocks= document.querySelectorAll('code[class*="language-"]');
codeBlocks.forEach((block)=> {
const lang= block.className.replace("language-", "");
block.parentElement.dataset.js= "done";
const html= highlighter.codeToHtml(block.textContent, { lang });
block.innerHTML= html;
});

View File

@ -1,59 +1,50 @@
let is_registered= {};
import { styles } from "../index.css.js";
export const css= styles().scope(example).css`
import { styles } from "../ssr.js";
styles.scope(example).css`
:host{
grid-column: 1/-1;
grid-column: full-main;
width: 100%;
max-width: calc(9/5 * var(--body-max-width));
height: calc(3/5 * var(--body-max-width));
margin-inline: auto;
}
.CodeMirror, .CodeMirror-gutters {
background: #212121 !important;
border: 1px solid white;
}
`
import { el } from "deka-dom-el";
import { code } from "./code.html.js";
/**
* 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 {"js"|"ts"|"html"|"css"} [attrs.language="js"] 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()
export function example({ src, language= "js", page_id }){
registerClientPart(page_id);
const content= 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(
el("div", { id, className: example.name }).append(
el("pre").append(
el("code", { className: "language-"+language, textContent: code })
)
),
elCode({ id, content: code })
el(code, { id, content, language, className: example.name }),
elCode({ id, content, extension: "."+language })
);
}
function elCode({ id, content }){
function elCode({ id, content, extension: name }){
const options= JSON.stringify({
files: [{ name: ".js", content }],
files: [{ name, content }],
toolbar: false
});
return el("script", `Flems(document.getElementById("${id}"), JSON.parse(${JSON.stringify(options)}));`);
}
function registerClientPart(page_id, registerClientFile){
let is_registered= {};
/** @param {string} page_id */
function registerClientPart(page_id){
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://cdn.jsdelivr.net/npm/shiki", defer: true }),
);
const d= el("div");
/** @param {HTMLDivElement} a */
const f= (a)=> a;
f(d); //←
//★ egisterClientFile(
//★ new URL("./example.js.js", import.meta.url),
//★ el("script", { type: "module" })
//★ ;
is_registered[page_id]= true;
}

View File

@ -1,19 +0,0 @@
const langs= {
javascript: 'js',
js: 'js',
html: 'html',
};
const highlighter= await shiki.getHighlighter({
theme: 'css-variables',
langs: ['js', 'html', 'shell'],
});
const codeBlocks= document.querySelectorAll('pre code[class*="language-"]');
codeBlocks.forEach((block)=> {
const lang= block.className.replace('language-', '');
const html= highlighter.codeToHtml(block.textContent, {
lang: langs[lang] || lang,
});
block.innerHTML= html;
});

View File

@ -0,0 +1,41 @@
import { pages, styles } from "../ssr.js";
styles.scope(prevNext).css`
:host{
display: grid;
grid-template-columns: 1fr 2fr 1fr;
margin-top: 1rem;
border-top: 1px solid var(--border);
}
:host [rel=prev]{
grid-column: 1;
}
:host [rel=next]{
grid-column: 3;
text-align: right;
}
`;
import { el } from "../../index.js";
/**
* @param {import("../types.d.ts").Info} page
* */
export function prevNext(page){
const page_index= pages.indexOf(page);
return el("div", { className: prevNext.name }).append(
el(pageLink, { rel: "prev", page: pages[page_index-1] }),
el(pageLink, { rel: "next", page: pages[page_index+1] })
);
}
/**
* @param {Object} attrs
* @param {"prev"|"next"} attrs.rel
* @param {import("../types.d.ts").Info} [attrs.page]
* */
function pageLink({ rel, page }){
if(!page) return el();
let { href, title, description }= page;
return el("a", { rel, href, title: description }).append(
rel==="next" ?"(next) " : "",
title,
rel==="prev" ? " (previous)" : "",
);
}

View File

@ -1,22 +1,16 @@
import "./global.css.js";
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 { example } from "./components/example.html.js";
/** @param {string} url */
const fileURL= url=> new URL(url, import.meta.url);
import { header } from "./layout/head.html.js";
import { prevNext } from "./components/prevNext.html.js";
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info, path_target, pages, registerClientFile }){
export function page({ pkg, info }){
const page_id= info.id;
return el().append(
el(header, { info, pkg, path_target, pages }),
el(header, { info, pkg }),
el("main").append(
el("h2", "Native JavaScript DOM elements creations"),
el("p", "Lets go through all patterns we would like to use and what needs to be improved for better experience."),
@ -36,12 +30,12 @@ export function page({ pkg, info, path_target, pages, registerClientFile }){
el("abbr", { textContent: "IDL", title: "Interface Description Language" })
), "."
),
el(example, { src: new URL("./components/examples/nativeCreateElement.js", import.meta.url), page_id, registerClientFile }),
el(example, { src: fileURL("./components/examples/nativeCreateElement.js"), page_id }),
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(example, { src: fileURL("./components/examples/dekaCreateElement.js"), page_id }),
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. ",
@ -82,27 +76,30 @@ export function page({ pkg, info, path_target, pages, registerClientFile }){
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(example, { src: fileURL("./components/examples/dekaAssign.js"), page_id }),
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(example, { src: fileURL("./components/examples/nativeAppend.js"), page_id }),
el("p").append(
"This library therefore overwrites the ", el("code", "append"), " method of created elements to always return parent element."
),
el(example, { src: new URL("./components/examples/dekaAppend.js", import.meta.url), page_id, registerClientFile }),
el(example, { src: fileURL("./components/examples/dekaAppend.js"), page_id }),
el("h2", "Creating advanced (reactive) templates and components"),
el("h3", "Basic components"),
el("h3", "Basic (state-less) 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.")
el(example, { src: fileURL("./components/examples/dekaBasicComponent.js"), page_id }),
el("p", "It is nice to use similar naming convention as native DOM API."),
el("h3", "Creating non-HTML elements"),
// example & notes
el(prevNext, info)
)
);
}

View File

@ -1,33 +1,5 @@
const scopes= new WeakSet();
const s= {
scope(s){
if(!scopes.has(s)){
scopes.add(s);
this.host= s.name;
}
return this;
},
css(...a){
let c= css(...a);
if(this.host){
c= c.replaceAll(":host", "."+this.host);
this.host= "";
}
if(this.content) this.content+= "\n";
this.content+= c;
return this;
},
include(...i){
if(this.content) this.content+= "\n";
this.content+= i.map(i=> typeof i === "string" ? i : i.content).join("\n");
return this;
}
};
export function css(...a){
return String.raw(...a).trim();
}
export function styles(){ return Object.assign(Object.create(s), { content: "" }); }
export const common= css`
import { styles } from "./ssr.js";
styles.scope(import.meta.url).css`
@import url(https://cdn.simplecss.org/simple.min.css);
:root{
--body-max-width: 45rem;
@ -68,21 +40,21 @@ body > nav{
flex-flow: column nowrap;
}
body > nav {
font-size:1rem;
line-height:2;
padding:1rem 0 0 0
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
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 {
@ -126,10 +98,13 @@ body > nav a:hover{
main{
grid-area: content;
display: grid;
grid-template-columns: 1fr min(var(--body-max-width), 90%) 1fr;
grid-template-columns:
[full-main-start] 1fr
[main-start] min(var(--body-max-width), 90%) [main-end]
1fr [full-main-end];
}
main > *{
grid-column: 2;
grid-column: main;
}
.icon {
vertical-align: sub;
@ -142,4 +117,8 @@ main > *{
stroke: currentColor;
fill: currentColor;
}
.note{
font-size: .9rem;
font-style: italic;
}
`;

View File

@ -1,25 +1,18 @@
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 "./global.css.js";
import { example } from "./components/example.html.js";
import { header } from "./layout/head.html.js";
import { prevNext } from "./components/prevNext.html.js";
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info, path_target, pages, registerClientFile }){
export function page({ pkg, info }){
const page_id= info.id;
return el().append(
el(header, { info, pkg, path_target, pages }),
el(header, { info, pkg }),
el("main").append(
el("p", "The library tries to provide pure JavaScript tool(s) to create reactive interfaces. "),
el(example, { src: new URL("./components/examples/helloWorld.js", import.meta.url), page_id, registerClientFile }),
el("p", "The library tries to provide pure JavaScript tool(s) to create reactive interfaces."),
el(example, { src: new URL("./components/examples/helloWorld.js", import.meta.url), page_id }),
el(prevNext, info)
)
);
}

View File

@ -1,4 +1,5 @@
import { el, elNS, scope } from "deka-dom-el";
import { el, elNS } from "deka-dom-el";
import { pages } from "../ssr.js";
/**
* @param {object} def
* @param {import("../types.d.ts").Info} def.info
@ -6,12 +7,12 @@ import { el, elNS, scope } from "deka-dom-el";
* @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 {import("../types.js").Pages} def.pages
* */
export function header({ info: { id, title, description }, pkg, path_target, pages }){
export function header({ info: { href, title, description }, pkg }){
title= `\`${pkg.name}\`${title}`;
document.head.append(head({ id, title, description, pkg, path_target }));
document.head.append(
head({ title, description, pkg })
);
return el().append(
el("header").append(
el("h1", title),
@ -23,23 +24,29 @@ export function header({ info: { id, title, description }, pkg, path_target, pag
"GitHub"
),
...pages.map((p, i)=> el("a", {
href: p.id==="index" ? "./" : p.id,
href: p.href==="index" ? "./" : p.href,
textContent: (i+1) + ". " + p.title,
title: p.description,
classList: { current: p.id===id }
classList: { current: p.href===href }
}))
)
);
}
function head({ id, title, description, pkg, path_target }){
function head({ title, description, pkg }){
return el().append(
el("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
el("link", { rel: "stylesheet", href: stylesheetHref(path_target, id) }),
el("meta", { name: "description", content: description }),
el("title", title),
el(metaAuthor),
el(metaTwitter, pkg),
el(metaFacebook, pkg),
el("title", title),
);
}
function metaAuthor(){
return el().append(
el("meta", { name: "author", content: "Jan Andrle" }),
el("link", { type: "text/plain", rel: "author", href: "https://jaandrle.github.io/humans.txt" }),
el("meta", { name: "generator", content: "deka-dom-el" }),
);
}
function metaTwitter({ name, description, homepage }){
@ -62,9 +69,6 @@ function metaFacebook({ name, description, homepage }){
el("meta", { name: "og:creator", content: "@jaandrle" }),
);
}
function stylesheetHref(path_target, name){
return path_target.css.replace(path_target.root, "")+name+".css";
}
function iconGitHub(){
const el= elNS("http://www.w3.org/2000/svg");
return el("svg", { className: "icon", viewBox: "0 0 32 32" }).append(

77
docs_src/ssr.js Normal file
View File

@ -0,0 +1,77 @@
export const path_target= {
root: "docs/",
css: "docs/"
};
export const pages= [
{ id: "index", href: "./", title: "Introduction", description: "Introducing a library and motivations." },
{ id: "elements", href: "elements", title: "Elements", description: "Basic concepts of elements modifications and creations." }
];
/**
* @typedef registerClientFile
* @type {function}
* @param {URL} url
* @param {HTMLScriptElement|HTMLLinkElement} [element_head]
* */
export 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
);
}
const events= {
oneachrender: new Set(),
onssrend: new Set()
};
/** @param {keyof typeof events} name @param {function} listener */
export function addEventListener(name, listener){
events[name].add(listener);
}
/** @param {keyof typeof events} name @param {any} a */
export function dispatchEvent(name, a){
const ls= events[name];
ls.forEach(l=> l(a));
if(name!=="oneachrender")
ls.clear();
}
const scopes= new Set();
export const styles= {
element: null,
name: "global.css",
get location(){ return path_target.css.replace(path_target.root, "")+this.name; },
content: "",
skip: false,
/** @param {function|string} s */
scope(s){
if(scopes.has(s)){ this.skip= true; return this; }
scopes.add(s);
if(typeof s==="function") this.host= s.name;
return this;
},
/** @param {Parameters<typeof String.raw>} a */
css(...a){
if(this.skip){ this.skip= false; return this; }
let c= css(...a);
if(this.host){
c= c.replaceAll(":host", "."+this.host);
this.host= "";
}
if(this.content) this.content+= "\n";
this.content+= c;
return this;
}
};
addEventListener("onssrend", ()=> scopes.clear());
addEventListener("oneachrender", ()=> document.head.append(
Object.assign(document.createElement("link"), { rel: "stylesheet", href: styles.location })
));
/** @type {typeof String.raw} */
function css(...a){ return String.raw(...a).trim(); }

1
docs_src/types.d.ts vendored
View File

@ -2,6 +2,7 @@
export type Pkg= Record<string, string>
export type Info= {
id: string,
href: string,
title: string,
description: string,
}