Initial commit

This commit is contained in:
2025-09-06 17:22:25 -04:00
commit dc96e8570c
27 changed files with 2248 additions and 0 deletions

170
dist/demo.html vendored Normal file
View File

@@ -0,0 +1,170 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>fluent-dom-esm Demo</title>
<link rel="stylesheet" href="/styles/main.css" />
</head>
<body>
<script type="module">
import $d from "/fluent-dom-esm.js";
const $article = $d
.c("article")
.id("fde-article")
.cls("cool-article")
.app($d.c("h1").cls("cool-heading").t("fluent-dom-esm"))
.app(
$d
.c("p")
.s("background-color", "#db7c17")
.s("color", "var(--main-dark)")
.t("Why is it so cool?"),
);
const $ul = $d.c("ul").id("fluentDom-example-list");
[
"Remarkably simple syntax",
"Surprisingly powerful features",
"Easy to use and remember",
].forEach((reason, idx) => {
$ul.app(
$d
.c("li")
.id(`fluentDom-example-list-item${idx + 1}`)
.t(reason),
);
});
$d(document.body).app(
$article
.app($ul)
.app(
$d.c("em").t("Try it today!").style("color", "#c43a19"),
),
);
</script>
<script type="module">
import $d from "/fluent-dom-esm.js";
const preDom = $d
.c("pre")
.t(
`
\<script type="module">
import $d from "/src/fluent-dom-esm.ts";
const $article = $d
.c("article")
.id("fde-article")
.cls("cool-article")
.app($d.c("h1").cls("cool-heading").t("fluent-dom-esm"))
.app(
$d
.c("p")
.s("background-color", "#db7c17")
.s("color", "var(--main-dark)")
.t("Why is it so cool?"),
);
const $ul = $d.c("ul").id("fluentDom-example-list");
[
"Remarkably simple syntax",
"Surprisingly powerful features",
"Easy to use and remember",
].forEach((reason, idx) => {
$ul.app(
$d
.c("li")
.id(\`fluentDom-example-list-item$\{idx + 1}\`)
.t(reason),
);
});
$d(document.body).app(
$article
.app($ul)
.app(
$d.c("em").t("Try it today!").style("color", "#c43a19"),
),
);
<\/script>
`
.trim()
.split("\n")
.map((val) => {
return val.replace(" ", "");
})
.join("\n"),
)
.toDom();
preDom.innerHTML = preDom.innerHTML
// strRegEx must be applied first to prevent false positives
.replace(/\"[^"]+\"/g, (val) =>
val !== '"module"'
? `<span class="code-str">${val}</span>`
: val,
)
.replace(
/\`[^`]+\`/g,
(val) => `<span class="code-str">${val}</span>`,
)
.replace(/\.\w+/g, (val) =>
val !== ".ts"
? `<span class="code-func">${val}</span>`
: val,
)
.replace(/(\$\w+)/g, '<span class="code-dom">$1</span>')
.replace("document", '<span class="code-var">document</span>')
.replaceAll("reason", '<span class="code-var">reason</span>')
.replace("idx", '<span class="code-var">idx</span>')
.replace(
/&lt;\/?script[^&]*&gt;/g,
(val) => `<span class="code-cmd">${val}</span>`,
);
const themeToggleButton = $d
.c("button")
.cls("themeToggle-button")
.id("themeToggle-button")
// .t("☀︎ / ☾")
.listen("click", () => {
document.body.classList.toggle("invertedTheme");
})
.html(
`
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
width="1em"
height="1em"
class="themeToggle-svgIndicator"
fill="currentColor"
viewBox="0 0 32 32"
>
<path
d="M16 .5C7.4.5.5 7.4.5 16S7.4 31.5 16 31.5 31.5 24.6 31.5 16 24.6.5
16 .5zm0 28.1V3.4C23 3.4 28.6 9 28.6 16S23 28.6 16 28.6z"
/>
</svg>
`.trim(),
);
$d(document.body)
.app(
$d
.c("figure")
.app($d.c("figcaption").t("Source"))
.app(preDom),
)
.app(themeToggleButton);
</script>
</body>
</html>

165
dist/fluent-dom-esm.js vendored Normal file
View File

@@ -0,0 +1,165 @@
// @license magnet:?xt=urn:btih:723febf9f6185544f57f0660a41489c7d6b4931b&dn=wtfpl.txt
const version = "1.1.0";
const isFluentDomObject = (value) => !!value.fluentDom;
const isHTMLElement = (value) => !!value.nodeType;
const isNumber = (value) => typeof value === "number";
const isString = (value) => typeof value === "string";
/**
* fluent-dom-esm v1.1.0
*
* Fluent DOM Manipulation, adapted to ESM and cranked up to v1.1(.0).
*
* https://git.itsericwoodward.com/eric/fluent-dom-esm
*
* v1.1.0 Copyright (c) 2025 Eric Woodward
* Original copyright (c) 2009 Tommy Montgomery (https://glacius.tmont.com/articles/fluent-dom-manipulation-in-javascript)
*
* Released under the WTFPL (Do What the Fuck You Want to Public License)
*
* @author Eric Woodward (v1.1.0 update)
* @author Tommy Montgomery (original)
* @license http://sam.zoy.org/wtfpl/
*/
const fluentDomEsm = (function() {
const FluentDom = function(node) {
return new FluentDomInternal(node);
};
FluentDom.create = FluentDom.c = function(tagName) {
const f = new FluentDomInternal();
f.create(tagName);
return f;
};
const FluentDomInternal = function(node) {
let root = node || null;
this.fluentDom = version;
this.a = this.attr = function(name, value) {
if (!root || value && !root.setAttribute) {
throw new Error("Cannot set an attribute on a non-element");
}
if (!value) return root.getAttribute(name);
root.setAttribute(name, value);
return this;
};
this.app = this.append = function(value) {
if (!root || !root?.appendChild) {
throw new Error("Cannot append to a non-element");
}
const type = typeof value;
if (type === "object") {
if (isFluentDomObject(value)) {
const domVal = value.toDom();
if (domVal !== null) root.appendChild(domVal);
} else if (isHTMLElement(value)) {
root.appendChild(value);
} else {
throw new Error(
"Invalid argument: not an HTMLElement or FluentDom object"
);
}
} else if (isNumber(value) || isString(value)) {
root.appendChild(document.createTextNode(`${value}`));
} else {
throw new Error(
`Invalid argument: not a valid type (${typeof value})`
);
}
return this;
};
this.c = this.create = function(tagName) {
root = document.createElement(tagName);
return this;
};
this.className = this.cls = function(value) {
return this.attr("class", value);
};
this.clear = function() {
root = null;
return this;
};
this.h = this.href = function(link) {
return this.attr("href", link);
};
this.html = function(content) {
if (!root) {
throw new Error(
"Cannot get or set innerHTML for a non-element"
);
}
if (!content) return root.innerHTML;
root.innerHTML = content;
return this;
};
this.id = function(value) {
return this.attr("id", value);
};
this.l = this.listen = function(...props) {
if (!root || !root.addEventListener) {
throw new Error("Cannot addEventListener to a non-element");
}
root.addEventListener(...props);
return this;
};
this.ohtml = function(content) {
if (!root) {
throw new Error(
"Cannot get or set outerHTML for a non-element"
);
}
if (!content) return root.outerHTML;
root.outerHTML = content;
return this;
};
this.s = this.style = function(prop, value) {
if (!root) {
throw new Error("Cannot get or set style for a non-element");
}
if (typeof prop === "undefined") return root.style;
if (typeof value !== "undefined") {
root.style[prop] = value;
return this;
}
if (typeof prop === "string") return root.style[prop];
if (typeof prop !== "object") {
throw new Error(
`Invalid argument: "prop" must be string or object (found ${typeof prop})`
);
}
Object.keys(prop).forEach((key) => {
root.style[key] = prop[key];
});
return this;
};
this.t = this.text = function(text) {
if (!root) {
throw new Error(
"Cannot get or set innerText for a non-element"
);
}
if (typeof text === "undefined") return root.innerText;
return this.append(text);
};
this.title = function(value) {
if (!root) {
throw new Error(
"Cannot get or set outerHTML for a non-element"
);
}
return this.attr("title", value);
};
this.toDom = function() {
return root;
};
this.unlisten = function(...props) {
if (!root || !root.removeEventListener) {
throw new Error("Cannot removeEventListener on a non-element");
}
root.removeEventListener(...props);
return this;
};
};
return FluentDom;
})();
export {
fluentDomEsm as default
};
// @license-end

35
dist/package.json.d.ts vendored Normal file
View File

@@ -0,0 +1,35 @@
declare const _default: {
"name": "fluent-dom-esm",
"version": "1.1.0",
"description": "",
"license": "WTFPL",
"exports": {
".": {
"import": "./dist/fluent-dom-esm.js"
}
},
"type": "module",
"files": [
"dist"
],
"module": "./dist/fluent-dom-esm.js",
"scripts": {
"add-license": "cat ./src/license-begin.txt ./dist/fluent-dom-esm.js ./src/license-end.txt > .temp && mv .temp ./dist/fluent-dom-esm.js",
"build": "tsc && vite build && yarn add-license",
"ci": "yarn build",
"dev": "vite",
"preview": "vite preview",
"prepublishOnly": "yarn build",
"postpublish": "git push && git push --tags",
"test": "echo 'No tests yet!'"
},
"devDependencies": {
"prettier": "^3.6.2",
"typescript": "^5.9.2",
"unplugin-dts": "1.0.0-beta.6",
"vite": "^7.1.4"
}
}
;
export default _default;

28
dist/src/fluent-dom-esm.d.ts vendored Normal file
View File

@@ -0,0 +1,28 @@
/**
* fluent-dom-esm v1.1.0
*
* Fluent DOM Manipulation, adapted to ESM and cranked up to v1.1(.0).
*
* https://git.itsericwoodward.com/eric/fluent-dom-esm
*
* v1.1.0 Copyright (c) 2025 Eric Woodward
* Original copyright (c) 2009 Tommy Montgomery (https://glacius.tmont.com/articles/fluent-dom-manipulation-in-javascript)
*
* Released under the WTFPL (Do What the Fuck You Want to Public License)
*
* @author Eric Woodward (v1.1.0 update)
* @author Tommy Montgomery (original)
* @license http://sam.zoy.org/wtfpl/
*/
/**
* IIFE that creates the FluentDomObject as default export
*/
declare const _default: {
(node: HTMLElement): any;
/**
* Creates a new HTML element which is wrapped in a FluentDomObject and returned
*/
create: (tagName: string) => any;
c(tagName: string): any;
};
export default _default;

View File

@@ -0,0 +1,5 @@
import { FluentDomObject } from './fluent-dom-esm.types';
export declare const isFluentDomObject: (value: any) => value is FluentDomObject;
export declare const isHTMLElement: (value: any) => value is HTMLElement;
export declare const isNumber: (value: any) => value is number;
export declare const isString: (value: any) => value is string;

27
dist/src/fluent-dom-esm.types.d.ts vendored Normal file
View File

@@ -0,0 +1,27 @@
export interface FluentDomObject {
fluentDom: string;
a: (name: string, value?: string) => FluentDomObject | string | null;
app: (obj: FluentDomObject | HTMLElement | string) => FluentDomObject;
append: (obj: FluentDomObject | HTMLElement | string) => FluentDomObject;
attr: (name: string, value?: string) => FluentDomObject | string | null;
c: (tagName: string) => FluentDomObject;
create: (tagName: string) => FluentDomObject;
className: (className?: string) => FluentDomObject | string | null;
clear: () => FluentDomObject;
cls: (className?: string) => FluentDomObject | string | null;
clr: () => FluentDomObject;
h: (url?: string) => FluentDomObject | string | null;
href: (url?: string) => FluentDomObject | string | null;
html: (content?: string) => string | FluentDomObject;
id: (id?: string) => FluentDomObject | string | null;
l: (type: keyof HTMLElementEventMap, listener: () => {}, optionsOrUseCapture?: boolean | object) => FluentDomObject;
listen: (type: keyof HTMLElementEventMap, listener: () => {}, optionsOrUseCapture?: boolean | object) => FluentDomObject;
ohtml: (content?: string) => string | FluentDomObject;
s: (prop?: CSSStyleDeclaration | string, value?: string) => FluentDomObject | CSSStyleDeclaration | string | undefined;
style: (prop?: CSSStyleDeclaration | string, value?: string) => FluentDomObject | CSSStyleDeclaration | string | undefined;
t: (text?: string) => FluentDomObject | string;
text: (text?: string) => FluentDomObject | string;
title: (title?: string) => FluentDomObject | string | null;
toDom: () => HTMLElement | null;
unlisten: (type: keyof HTMLElementEventMap, listener: () => {}, optionsOrUseCapture?: boolean | object) => FluentDomObject;
}

140
dist/styles/main.css vendored Normal file
View File

@@ -0,0 +1,140 @@
* {
box-sizing: content-box;
}
:root {
--main-light: #4f9a97;
--main-dark: #1a2737;
--gray-light: #eee;
--gray-bg-60: rgba(10, 10, 10, .6);
--gray-bg-80: rgba(10, 10, 10, .8);
}
body {
background-color: var(--main-dark);
color: var(--main-light);
font-size: 18px;
transition: color .5s;
}
h2 {
margin-top: 1rem;
}
hr {
margin: 2rem 0;
}
figcaption {
border-bottom: 1px dashed var(--main-light);
font-style: italic;
font-weight: bold;
margin: -1rem -1rem 1rem -1rem;
padding: 0.5rem 1rem;
}
figure {
background-color: var(--gray-bg-60);
border: 1px solid var(--main-light);
color: #285559;
margin: 1rem;
padding: 1rem;
max-width: 100%;
}
.code-cmd {
color: #347c7a;
}
.code-dom {
color: var(--main-light);
}
.code-func {
color: #db7c17;
}
.code-str {
color: #c43a19;
}
.code-var {
color: var(--main-light);
}
.themeToggle-button {
background-color: #5d27a1;
border: 1px solid var(--gray-light);
border-radius: 1rem;
color: var(--gray-light);
display: flex;
font-size: 1.5rem;
padding: .5rem;
position: absolute;
right: 1rem;
top: 1rem;
z-index: 100;
}
.themeToggle-svgIndicator {
rotate: 180deg;
transition: rotate .5s;
}
body.invertedTheme {
background-color: var(--main-light);
color: var(--main-dark);
}
body.invertedTheme figcaption {
border-color: var(--main-dark);
}
body.invertedTheme figure {
background-color: var(--gray-bg-80);
border-color: var(--main-dark);
}
body.invertedTheme .themeToggle-svgIndicator {
rotate: 0deg;
}
@media (prefers-color-scheme: light) {
body {
background-color: var(--main-light);
color: var(--main-dark);
}
figure {
background-color: var(--gray-bg-80);
border-color: var(--main-dark);
color: var(--main-light);
}
.themeToggle-svgIndicator {
rotate: 0deg;
}
body.invertedTheme {
background-color: var(--main-dark);
color: var(--main-light);
}
body.invertedTheme figcaption {
border-color: var(--main-light);
}
body.invertedTheme figure {
background-color: var(--gray-bg-60);
border-color: var(--main-light);
}
body.invertedTheme .themeToggle-svgIndicator {
rotate: 180deg;
}
}