@pfern/elements 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -127
- package/elements.js +0 -28
- package/package.json +1 -1
- package/types/elements.d.ts +0 -9
package/README.md
CHANGED
|
@@ -1,63 +1,16 @@
|
|
|
1
1
|
# Elements.js
|
|
2
2
|
|
|
3
|
-
Elements.js is a
|
|
4
|
-
|
|
3
|
+
Elements.js is a tiny, functional UI toolkit for building DOM trees with plain
|
|
4
|
+
functions. Components are just functions; updates are just calling the function
|
|
5
|
+
again with new arguments.
|
|
5
6
|
|
|
6
|
-
##
|
|
7
|
+
## Install
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* HTML element functions with JSDoc and TypeScript-friendly signatures
|
|
12
|
-
* No hooks, no classes, no virtual DOM heuristics
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Why Elements.js?
|
|
17
|
-
|
|
18
|
-
Modern frameworks introduced declarative UI—but buried it beneath lifecycle
|
|
19
|
-
hooks, mutable state, and complex diffing algorithms.
|
|
20
|
-
|
|
21
|
-
**Elements.js goes further:**
|
|
22
|
-
|
|
23
|
-
* Pure functions represent both logic and view
|
|
24
|
-
* The DOM *is* your state model
|
|
25
|
-
* Re-rendering is *recursion*, not reconciliation
|
|
26
|
-
|
|
27
|
-
> Can UI be defined as a tree of pure function calls—nothing more?
|
|
28
|
-
|
|
29
|
-
Yes. Elements.js proves it.
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Philosophy
|
|
34
|
-
|
|
35
|
-
### Declarative from top to bottom
|
|
36
|
-
|
|
37
|
-
* No internal component state
|
|
38
|
-
* No lifecycle methods or effects
|
|
39
|
-
* Every component is a function
|
|
40
|
-
|
|
41
|
-
To update a view: just **call the function again** with new arguments. The DOM
|
|
42
|
-
subtree is replaced in place.
|
|
43
|
-
|
|
44
|
-
### State lives in the DOM
|
|
45
|
-
|
|
46
|
-
There is no observer graph, no `useState`, and no memory of previous renders.
|
|
47
|
-
The DOM node *is the history*. Input state is passed as an argument.
|
|
48
|
-
|
|
49
|
-
### Minimal abstraction
|
|
50
|
-
|
|
51
|
-
* No keys, refs, proxies, or context systems
|
|
52
|
-
* No transpilation step
|
|
53
|
-
* No reactive graph to debug
|
|
54
|
-
|
|
55
|
-
Elements.js embraces the full truth of each function call as the only valid
|
|
56
|
-
state.
|
|
57
|
-
|
|
58
|
-
---
|
|
9
|
+
```bash
|
|
10
|
+
npm install @pfern/elements
|
|
11
|
+
```
|
|
59
12
|
|
|
60
|
-
## Example: Counter
|
|
13
|
+
## Quick Example: Counter
|
|
61
14
|
|
|
62
15
|
```js
|
|
63
16
|
import { button, component, div, output } from '@pfern/elements'
|
|
@@ -70,13 +23,7 @@ export const counter = component((count = 0) =>
|
|
|
70
23
|
'Increment')))
|
|
71
24
|
```
|
|
72
25
|
|
|
73
|
-
|
|
74
|
-
* The old DOM node is replaced with the new one
|
|
75
|
-
* No virtual DOM, no diffing
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## Form Example: Todos App
|
|
26
|
+
## Example: Todos App
|
|
80
27
|
|
|
81
28
|
```js
|
|
82
29
|
|
|
@@ -111,18 +58,6 @@ export const todos = component(
|
|
|
111
58
|
|
|
112
59
|
```
|
|
113
60
|
|
|
114
|
-
This is a complete MVC-style app:
|
|
115
|
-
|
|
116
|
-
* Stateless
|
|
117
|
-
* Immutable
|
|
118
|
-
* Pure
|
|
119
|
-
|
|
120
|
-
You can view these examples live on [Github
|
|
121
|
-
Pages](https://pfernandez.github.io/elements/) or by running them locally with
|
|
122
|
-
`npm run dev`.
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
61
|
## Root Rendering Shortcut
|
|
127
62
|
|
|
128
63
|
If you use `html`, `head`, or `body` as the top-level tag, `render()` will
|
|
@@ -152,21 +87,15 @@ render(
|
|
|
152
87
|
todos())))))
|
|
153
88
|
```
|
|
154
89
|
|
|
155
|
-
---
|
|
156
|
-
|
|
157
90
|
## Declarative Events
|
|
158
91
|
|
|
159
|
-
All event listeners in Elements.js are pure functions. You can return a vnode
|
|
160
|
-
from a listener to declaratively update the component tree—- no mutation or
|
|
161
|
-
imperative logic required.
|
|
162
|
-
|
|
163
92
|
### General Behavior
|
|
164
93
|
|
|
165
94
|
* Any event handler (e.g. `onclick`, `onsubmit`, `oninput`) may return a new
|
|
166
95
|
vnode to trigger a subtree replacement.
|
|
167
96
|
* If the handler returns `undefined`, the event is treated as passive (no update
|
|
168
97
|
occurs).
|
|
169
|
-
* Returned vnodes are
|
|
98
|
+
* Returned vnodes are applied at the closest component boundary.
|
|
170
99
|
|
|
171
100
|
### Form Events
|
|
172
101
|
|
|
@@ -195,8 +124,6 @@ form({
|
|
|
195
124
|
If the handler returns nothing, `preventDefault()` is skipped and the form
|
|
196
125
|
submits natively.
|
|
197
126
|
|
|
198
|
-
---
|
|
199
|
-
|
|
200
127
|
## API
|
|
201
128
|
|
|
202
129
|
### `component(fn)`
|
|
@@ -232,48 +159,10 @@ Elements are data-in, data-out only, so mocking and headless browsers like
|
|
|
232
159
|
`jsdom` are unnecessary out of the box. See the tests [in this
|
|
233
160
|
repository](test/README.md) for some examples.
|
|
234
161
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
## Status
|
|
238
|
-
|
|
239
|
-
* 🧪 Fully tested (data-in/data-out behavior)
|
|
240
|
-
* ⚡ Under 2kB min+gzip
|
|
241
|
-
* ✅ Node and browser compatible
|
|
242
|
-
|
|
243
|
-
---
|
|
244
|
-
|
|
245
|
-
## Installation
|
|
246
|
-
|
|
247
|
-
```bash
|
|
248
|
-
npm install @pfern/elements
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Or clone the repo and use as an ES module:
|
|
252
|
-
|
|
253
|
-
```js
|
|
254
|
-
import { render, div, component, ... } from './elements.js';
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
---
|
|
258
|
-
|
|
259
|
-
## Summary
|
|
260
|
-
|
|
261
|
-
Elements.js is a thought experiment turned practical:
|
|
262
|
-
|
|
263
|
-
> Can UI be nothing but functions?
|
|
264
|
-
|
|
265
|
-
Turns out, yes.
|
|
266
|
-
|
|
267
|
-
* No diffing
|
|
268
|
-
* No state hooks
|
|
269
|
-
* No lifecycle
|
|
270
|
-
* No reconciliation heuristics
|
|
271
|
-
|
|
272
|
-
Just pure declarative HTML—rewritten in JavaScript.
|
|
273
|
-
|
|
274
|
-
---
|
|
275
|
-
|
|
276
|
-
**Lightweight. Immutable. Composable.**
|
|
277
|
-
|
|
278
|
-
Give it a try. You might never go back.
|
|
162
|
+
## Notes
|
|
279
163
|
|
|
164
|
+
- Elements.js is intended to be small and easy to reason about.
|
|
165
|
+
- For a starter app template, use `@pfern/create-elements`:
|
|
166
|
+
- https://github.com/pfernandez/create-elements
|
|
167
|
+
- `npx @pfern/create-elements my-app`
|
|
168
|
+
- More examples live in `examples/`.
|
package/elements.js
CHANGED
|
@@ -309,34 +309,6 @@ export const render = (vtree, container = null) => {
|
|
|
309
309
|
rootMap.set(vtree, target)
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
/**
|
|
313
|
-
* Updates the browser URL via the History API (no full page load).
|
|
314
|
-
* No-ops outside the browser.
|
|
315
|
-
*
|
|
316
|
-
* @param {string} to - The target URL (path/search/hash).
|
|
317
|
-
* @param {Object} [options]
|
|
318
|
-
* @param {boolean} [options.replace=false] - Use replaceState instead of pushState.
|
|
319
|
-
* @param {boolean} [options.force=false] - Update even if URL is already `to`.
|
|
320
|
-
* @param {any} [options.state={}] - History state.
|
|
321
|
-
* @param {string} [options.title=''] - History title (mostly ignored by browsers).
|
|
322
|
-
*/
|
|
323
|
-
export const navigate = (to, {
|
|
324
|
-
replace = false,
|
|
325
|
-
force = false,
|
|
326
|
-
state = {},
|
|
327
|
-
title = ''
|
|
328
|
-
} = {}) => {
|
|
329
|
-
if (typeof window === 'undefined') return
|
|
330
|
-
if (typeof to !== 'string' || !to.length) return
|
|
331
|
-
|
|
332
|
-
const { pathname, search, hash } = window.location
|
|
333
|
-
const current = `${pathname}${search}${hash}`
|
|
334
|
-
if (!force && current === to) return
|
|
335
|
-
|
|
336
|
-
const method = replace ? 'replaceState' : 'pushState'
|
|
337
|
-
window.history[method](state, title, to)
|
|
338
|
-
}
|
|
339
|
-
|
|
340
312
|
/**
|
|
341
313
|
* Wraps a function component so that it participates in reconciliation.
|
|
342
314
|
*
|
package/package.json
CHANGED
package/types/elements.d.ts
CHANGED
|
@@ -12,15 +12,6 @@
|
|
|
12
12
|
export const DEBUG: boolean;
|
|
13
13
|
export function render(vtree: any, container?: any): void;
|
|
14
14
|
export function component(fn: (...args: any[]) => any): (...args: any[]) => any;
|
|
15
|
-
export function navigate(
|
|
16
|
-
to: string,
|
|
17
|
-
options?: {
|
|
18
|
-
replace?: boolean;
|
|
19
|
-
force?: boolean;
|
|
20
|
-
state?: any;
|
|
21
|
-
title?: string;
|
|
22
|
-
}
|
|
23
|
-
): void;
|
|
24
15
|
/**
|
|
25
16
|
* @typedef {Record<string, any>} Props
|
|
26
17
|
* @typedef {any[] | string | number | boolean | null | undefined | Node} Child
|