first pass

This commit is contained in:
Geoff Doty 2024-05-04 20:34:27 -04:00
parent 76e2d94b7f
commit e79e241236
6 changed files with 182 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.vscode/
node_modules/
.scratch/

65
README.md Normal file
View File

@ -0,0 +1,65 @@
# Tagged
> Minimal Javascript UI Library
This is an experimental composable ui library that takes ideas from Elm Architecture, but without the doctrine - this is Javascript!
## Features
- No Virtual Dom
- No Build System
- No Over Engineering
- ~100 lines of code
- Totally inefficient rendering (at scale)
## Overview
the `app` builder takes an `opts` object that expects it to include:
- `data` as initial data `{object}`
- `methods` as `{object}` with functions definitions
- `view` as `{function}` that returns valid dom
and a querySelector compatible `selector` to mount the ui.
`app` returns:
- a object that returns a `state` object that you can use to update in other components
- `methods` you can call outside the app component
### Example
```html
<script type="module">
import {app, h} from "./tagged.js";
const myapp = app({
data: {name: "[Your Name Here]", job: "Developer"},
view(state, methods) {
return h("main", [
h("strong", `Greeting from ${state.name}`),
h("div", `Your local ${state.job}`),
h("div", {id: "test"}, [
h("h1", "Hello Tagged"),
h("p", 21),
h("hr")
])
]);
}
}, "#main");
</script>
<main id="app"></main>
```
### Inspired By
- hyperapp
- mithril
- Elm Architecture
## Todo
- Methods
- Update API: state, actions
> WORK-IN-PROGRESS

52
src/app.js Normal file
View File

@ -0,0 +1,52 @@
export default function app(opts, selector = "body") {
// initial setup
let data = {};
let view = () => null;
let methods = {};
// query helper
const $ = document.querySelector.bind(document);
// state helper
const state = (state) => {
if(typeof state === "object") {
data = {...data, ...state};
}
// update ui
render();
// return current state
return data;
}
const render = () => {
$(selector).replaceChildren(view(data, methods));
}
// setup view
if (opts.view && typeof opts.view === "function") {
view = opts?.view;
}
// setup data
if (opts.data && typeof opts.data === "object") {
// wrap data in state object
data = state(opts.data);
}
// setup methods
if (opts.methods && typeof opts.methods === "object") {
methods = opts.methods;
}
// mount view
if (opts.view && selector) {
render();
}
return {
state,
methods,
};
}

5
src/index.js Normal file
View File

@ -0,0 +1,5 @@
import tag from "./tag.js";
import app from "./app.js";
export {app};
export const h = tag;

23
src/tag.js Normal file
View File

@ -0,0 +1,23 @@
/**
* Creates new DOM element(s) from tag name(s) and attributes
*
* @param {string} tag - tag to create
* @param {...any} args - attributes and/or child tag elements
*
* @returns {HTMLElement} The created DOM element(s)
*/
export default function tag(tag, ...args) {
const el = document.createElement(tag);
args.forEach((arg) => {
if (typeof arg === "string" || typeof arg === "number") {
el.appendChild(document.createTextNode(arg));
} else if (Array.isArray(arg)) {
el.append(...arg);
} else {
Object.assign(el, arg);
}
});
return el;
}

32
test/index.html Normal file
View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tagged UI Creation Lib</title>
</head>
<body>
<div id="app">
Tagged Loading...
</div>
<script type="module">
import {app, h} from "../src/index.js";
const myapp = app({
data: {name: "[Your Name Here]", job: "Developer"},
view(state, methods) {
return h("main", [
h("strong", `Greeting from ${state.name}`),
h("div", `Your local ${state.job}`),
h("div", {id: "test"}, [
h("h1", "Hello Tagged"),
h("p", 21),
h("hr")
])
]);
}
}, "#app");
</script>
</body>
</html>