Skip to content

Commit f7758d1

Browse files
committed
feat(schema2ui): add schema-to-DOM library 🎉
- Add CI and JSR publish workflows - Add browser example (image, list, table, teal theme) - Add create/render API, types, Validator, Constant, Create, Render - Add package config (deno, npm, unbuild) and tests - Add README, USAGE, and examples README
0 parents  commit f7758d1

21 files changed

Lines changed: 1856 additions & 0 deletions

.github/workflows/ci.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build-check:
11+
name: Build Check
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
18+
- name: Setup Deno
19+
uses: denoland/setup-deno@v2
20+
with:
21+
deno-version: v2.5.4
22+
23+
- name: Format check
24+
run: deno fmt --check src/
25+
26+
- name: Lint
27+
run: deno lint src/
28+
29+
- name: Type check
30+
run: deno check src/index.ts
31+
32+
- name: Test
33+
run: deno test --allow-read --allow-write --allow-env

.github/workflows/publish.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Publish
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
deno-publish:
10+
name: Deno (JSR)
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
id-token: write
15+
16+
steps:
17+
- uses: actions/checkout@v6
18+
- uses: denoland/setup-deno@v2
19+
with:
20+
deno-version: v2.5.4
21+
22+
- name: Check package
23+
run: deno check src/index.ts
24+
25+
- name: Publish to JSR
26+
run: deno publish --allow-dirty

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Deno
2+
.deno/
3+
coverage/
4+
deno.lock
5+
6+
# IDE
7+
.vscode/
8+
.idea/
9+
10+
# OS
11+
.DS_Store
12+
Thumbs.db
13+
14+
# Node
15+
dist/
16+
node_modules/
17+
package-lock.json

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 NeaByteLab
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<div align="center">
2+
3+
# Schema2UI
4+
5+
Turns a JSON schema into low-level customizable DOM.
6+
7+
[![Node](https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white)](https://nodejs.org) [![Deno](https://img.shields.io/badge/deno-compatible-ffcb00?logo=deno&logoColor=000000)](https://deno.com) [![Bun](https://img.shields.io/badge/bun-compatible-f9f1e1?logo=bun&logoColor=000000)](https://bun.sh) [![Browser](https://img.shields.io/badge/browser-compatible-4285F4?logo=googlechrome&logoColor=white)](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
8+
9+
[![Module type: Deno/ESM](https://img.shields.io/badge/module%20type-deno%2Fesm-brightgreen)](https://github.com/NeaByteLab/Schema2UI) [![npm version](https://img.shields.io/npm/v/@neabyte/schema2ui.svg)](https://www.npmjs.org/package/@neabyte/schema2ui) [![JSR](https://jsr.io/badges/@neabyte/schema2ui)](https://jsr.io/@neabyte/schema2ui) [![CI](https://github.com/NeaByteLab/Schema2UI/actions/workflows/ci.yaml/badge.svg)](https://github.com/NeaByteLab/Schema2UI/actions/workflows/ci.yaml) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
10+
11+
</div>
12+
13+
## Features
14+
15+
- **Schema-first** — UI is defined as a tree of nodes (data), so you don’t write imperative DOM code.
16+
- **HTML-native** — Nodes map to real HTML tags and CSS, so you stay close to the platform.
17+
- **Create, then render** — Validate the definition to get a schema, then pass that schema and a container to render.
18+
- **Platform-aware** — The renderer follows HTML rules (e.g. void elements, template, SVG) so the result is valid.
19+
20+
## Installation
21+
22+
> [!NOTE]
23+
> **Prerequisites:** For **Deno** use [deno.com](https://deno.com/). For **npm** use Node.js (e.g. [nodejs.org](https://nodejs.org/)).
24+
25+
**Deno (JSR):**
26+
27+
```bash
28+
deno add jsr:@neabyte/schema2ui
29+
```
30+
31+
**Node (npm):**
32+
33+
```bash
34+
npm install @neabyte/schema2ui
35+
```
36+
37+
### CDN (browser / any ESM)
38+
39+
```html
40+
<script type="module">
41+
import { Database } from 'https://esm.sh/jsr/@neabyte/schema2ui'
42+
// or pin version: .../schema2ui@1.0.0
43+
</script>
44+
```
45+
46+
- Latest: `https://esm.sh/jsr/@neabyte/schema2ui`
47+
- Pinned: `https://esm.sh/jsr/@neabyte/schema2ui@<version>`
48+
49+
## Quick Start
50+
51+
Define a tree with `root` (array of nodes). Each node has `type` (HTML tag name), optional `layout`, `style`, `attrs`, `content`, and `children`. Call `create(definition)` then `render(schema, container)`.
52+
53+
```typescript
54+
import { create, render } from '@neabyte/schema2ui'
55+
56+
// 1. Define the UI tree: root is an array of nodes
57+
const schema = create({
58+
root: [
59+
// 2. First node: header with layout, style, and a child (h1)
60+
{
61+
type: 'header',
62+
id: 'header',
63+
layout: { width: '100%', height: 56 },
64+
style: { fill: '#1a1a2e', stroke: '1px solid #16213e' },
65+
children: [
66+
{
67+
type: 'h1',
68+
id: 'title',
69+
content: 'Hello',
70+
style: { font: '20px sans-serif', fill: '#eee' }
71+
}
72+
]
73+
},
74+
// 3. Second node: main with a link and a table
75+
{
76+
type: 'main',
77+
layout: { width: '100%', flex: 1 },
78+
children: [
79+
{ type: 'a', attrs: { href: '/' }, content: 'Home' },
80+
{
81+
type: 'table',
82+
layout: { width: '100%' },
83+
children: [
84+
// 4. Table head: one row, two headers
85+
{
86+
type: 'thead',
87+
children: [
88+
{
89+
type: 'tr',
90+
children: [
91+
{ type: 'th', content: 'Name' },
92+
{ type: 'th', content: 'Value' }
93+
]
94+
}
95+
]
96+
},
97+
// 5. Table body: one row, two cells
98+
{
99+
type: 'tbody',
100+
children: [
101+
{
102+
type: 'tr',
103+
children: [
104+
{ type: 'td', content: 'Foo' },
105+
{ type: 'td', content: '1' }
106+
]
107+
}
108+
]
109+
}
110+
]
111+
}
112+
]
113+
}
114+
]
115+
})
116+
117+
// 6. Validate (create) then render into a container
118+
render(schema, document.getElementById('app'))
119+
```
120+
121+
## Documentation
122+
123+
- **[USAGE.md](USAGE.md)** — Full API and type reference.
124+
- **[examples/](examples/)** — Browser demo: build then open `examples/index.html`.
125+
126+
## Build & Test
127+
128+
From the repo root (requires [Deno](https://deno.com/)).
129+
130+
**Check** — format, lint, and typecheck:
131+
132+
```bash
133+
deno task check
134+
```
135+
136+
**Tests** — format/lint tests and run:
137+
138+
```bash
139+
deno task test
140+
```
141+
142+
Tests live under `tests/` (Create, Constant, Validator, Render).
143+
144+
## License
145+
146+
This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for details.

USAGE.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Usage
2+
3+
API, node shape, layout and style — create schema from definition, render to DOM.
4+
5+
## Table of Contents
6+
7+
- [Quick Start](#quick-start)
8+
- [Flow Overview](#flow-overview)
9+
- [API Overview](#api-overview)
10+
- [Node Shape](#node-shape)
11+
- [Layout and Style](#layout-and-style)
12+
- [Void Tags and Special Elements](#void-tags-and-special-elements)
13+
- [API Reference](#api-reference)
14+
- [Type Reference](#type-reference)
15+
- [Reference](#reference)
16+
17+
## Quick Start
18+
19+
**Flow:** definition (`{ root: Node[] }`) → `create(definition)` → schema → `render(schema, container)`.
20+
21+
```typescript
22+
import { create, render } from '@neabyte/schema2ui'
23+
24+
// 1. Define root: array of nodes (here: a div and a link)
25+
const schema = create({
26+
root: [
27+
{ type: 'div', id: 'box', layout: { width: 200, height: 100 }, content: 'Hello' },
28+
{ type: 'a', attrs: { href: '/about' }, content: 'About' }
29+
]
30+
})
31+
32+
// 2. Render the schema into a container element
33+
render(schema, document.getElementById('app'))
34+
```
35+
36+
## Flow Overview
37+
38+
1. **Definition** — Plain object `{ root: Node[] }`. Each node has at least `type` (HTML tag name); optional: `id`, `layout`, `style`, `attrs`, `content`, `src`/`alt` (img), `children`.
39+
2. **Create**`create(definition)` validates the definition (root array, node shape, allowed keys, void tags must not have children). Returns a frozen, normalized `Schema`. Throws on invalid input.
40+
3. **Render**`render(schema, container)` walks `schema.root`, creates DOM elements (or SVG via `createElementNS` where needed), applies `attrs`, `layout`, `style`, sets `content`/`src`/`alt`, appends children. Results are appended to `container` (HTMLElement).
41+
42+
## API Overview
43+
44+
| Function / export | Returns | Description |
45+
| :----------------------------------------------------- | :------- | :--------------------------------------------- |
46+
| [`create(definition)`](#createdefinition) | `Schema` | Validate and freeze definition; return schema. |
47+
| [`render(schema, container)`](#renderschema-container) | `void` | Build DOM from schema and append to container. |
48+
49+
Named exports: `create`, `render`, and all types from `Types` (re-exported). Default export: `{ create, render }`.
50+
51+
## Node Shape
52+
53+
Each node is an object with:
54+
55+
| Key | Type | Required | Description |
56+
| :--------- | :------- | :------- | :---------------------------------------------------------- |
57+
| `type` | `string` | yes | HTML tag name (lowercase), e.g. `div`, `a`, `table`, `svg`. |
58+
| `id` | `string` | no | Element `id` attribute. |
59+
| `layout` | `Layout` | no | Width, height, position, flex, gap → CSS. |
60+
| `style` | `Style` | no | Fill, stroke, font, border → CSS. |
61+
| `attrs` | `Attrs` | no | Arbitrary HTML attributes (href, class, etc.). |
62+
| `content` | `string` | no | Text content for the node. |
63+
| `src` | `string` | no | Image (or similar) source URL; use with `type: 'img'`. |
64+
| `alt` | `string` | no | Alt text for img. |
65+
| `children` | `Node[]` | no | Child nodes. Not allowed on void tags. |
66+
67+
Allowed keys are exactly the above. Extra keys cause validation to throw.
68+
69+
## Layout and Style
70+
71+
- **Layout**`width`, `height`, `x`, `y`, `flex`, `gap`. Number values are converted to `Npx`; strings (e.g. `'100%'`, `'auto'`) are used as-is. Applied to the element’s `style` (width, height, left, top, flex, gap). Setting `flex` or `gap` may set `display` to `block` or `flex` when needed.
72+
- **Style**`fill``backgroundColor`, `stroke``border`, `font``font`, `border``border`. All string values.
73+
74+
## Void Tags and Special Elements
75+
76+
- **Void tags** — No `children`. List includes: `area`, `base`, `br`, `col`, `embed`, `hr`, `img`, `input`, `link`, `meta`, `param`, `source`, `track`, `wbr`. If a void tag has non-empty `children`, `create()` throws.
77+
- **template** — Child nodes are appended to `element.content` (DocumentFragment), not to the template element itself.
78+
- **svg** — SVG elements are created with `createElementNS(SVG_NS, type)`; descendants stay in SVG namespace when under an `svg` node.
79+
80+
## API Reference
81+
82+
### create(definition)
83+
84+
Validate the definition and return a frozen schema. Normalizes node keys (e.g. lowercases `type`). Deep-freezes the schema and all nested nodes.
85+
86+
- `definition` `<Definition | unknown>`: Object with `root: Node[]`. Can be a plain object; validation ensures shape.
87+
- **Returns:** `<Schema>` Frozen object `{ root: readonly Node[] }`.
88+
- **Throws:** When `definition` is not an object, `root` is not an array, or any node is invalid (unknown key, wrong type for a field, void tag with children).
89+
90+
### render(schema, container)
91+
92+
Build DOM from `schema.root` and append each resulting node into `container`. Uses the container’s `ownerDocument` (or `document`). Handles `template` (children into `template.content`) and SVG (namespace). Does not clear `container` before appending.
93+
94+
- `schema` `<Schema>`: Frozen schema from `create()`.
95+
- `container` `<HTMLElement>`: Target element to append to.
96+
- **Returns:** `void`.
97+
98+
## Type Reference
99+
100+
Types are re-exported from the package: `import type { Attrs, Definition, Layout, Node, Schema, Style } from '@neabyte/schema2ui'`.
101+
102+
- **Definition:** `{ root: readonly Node[] }` — Input to `create`.
103+
- **Schema:** `{ readonly root: readonly Node[] }` — Output of `create`; frozen.
104+
- **Node:** Object with `type` (string) and optional `id`, `layout`, `style`, `attrs`, `content`, `src`, `alt`, `children` (readonly array of Node). Void tags must not have `children`.
105+
- **Layout:** Optional `width`, `height`, `x`, `y`, `flex`, `gap` — each `number | string`.
106+
- **Style:** Optional `fill`, `stroke`, `font`, `border` — each `string`.
107+
- **Attrs:** `Record<string, string | number | boolean>` — HTML attributes. Special handling for `class`/`className`, `style` (string), `value`, `checked`, `disabled` on form elements.
108+
109+
## Reference
110+
111+
- [README](README.md) — Installation and quick start.
112+
- Tests under `tests/` — Create, Constant, Validator, Render.

build.config.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { defineBuildConfig } from 'unbuild'
2+
import { resolve } from 'node:path'
3+
4+
/**
5+
* Unbuild config for package bundle.
6+
* @description Entry, alias, CJS/ESM, declarations, no sourcemaps.
7+
*/
8+
export default defineBuildConfig({
9+
/** Entry module path(s) for build. */
10+
entries: ['src/index'],
11+
/** Emit TypeScript declaration files. */
12+
declaration: true,
13+
/** Remove output directory before build. */
14+
clean: true,
15+
/** Path alias for imports (e.g. @app → src). */
16+
alias: {
17+
'@app': resolve(__dirname, 'src')
18+
},
19+
/** Rollup options: emit CJS and inline runtime deps. */
20+
rollup: {
21+
emitCJS: true,
22+
inlineDependencies: true
23+
},
24+
/** Disable source map generation. */
25+
sourcemap: false,
26+
/** Do not fail build on Rollup warnings. */
27+
failOnWarn: false
28+
})

0 commit comments

Comments
 (0)