creo 0.0.4-dev → 0.2.0
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 +276 -1
- package/dist/functional/assert.d.ts +1 -0
- package/dist/functional/key.d.ts +2 -0
- package/dist/functional/lis.d.ts +11 -0
- package/dist/functional/maybe.d.ts +8 -0
- package/dist/functional/maybe_promise.d.ts +1 -0
- package/dist/functional/shallow_equal.d.ts +2 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +1357 -0
- package/dist/index.js.map +25 -0
- package/dist/internal/engine.d.ts +24 -0
- package/dist/internal/internal_view.d.ts +29 -0
- package/dist/internal/orchestrator.d.ts +16 -0
- package/dist/public/app.d.ts +19 -0
- package/dist/public/event_handle.d.ts +32 -0
- package/dist/public/primitive.d.ts +20 -0
- package/dist/public/primitives/primitives.d.ts +318 -0
- package/dist/public/state.d.ts +37 -0
- package/dist/public/store.d.ts +35 -0
- package/dist/public/view.d.ts +33 -0
- package/dist/render/canvas_render.d.ts +1 -0
- package/dist/render/html_render.d.ts +22 -0
- package/dist/render/json_render.d.ts +19 -0
- package/dist/render/render_interface.d.ts +9 -0
- package/dist/render/stream_render.d.ts +1 -0
- package/dist/render/string_render.d.ts +22 -0
- package/dist/structures/indexed_list.d.ts +46 -0
- package/dist/structures/list.d.ts +68 -0
- package/package.json +23 -11
- package/.env.development +0 -1
- package/.github/workflows/main.yml +0 -24
- package/TODOS.md +0 -2
- package/index.html +0 -13
- package/index.ts +0 -1
- package/src/DOM/Context.ts +0 -36
- package/src/DOM/DomEngine.ts +0 -106
- package/src/DOM/IRenderCycle.ts +0 -9
- package/src/DOM/Key.ts +0 -1
- package/src/DOM/Node.ts +0 -472
- package/src/DOM/Registry.ts +0 -53
- package/src/creo.ts +0 -134
- package/src/data-structures/assert/assert.ts +0 -12
- package/src/data-structures/indexed-map/IndexedMap.ts +0 -281
- package/src/data-structures/linked-map/LinkedMap.spec.ts +0 -67
- package/src/data-structures/linked-map/LinkedMap.ts +0 -198
- package/src/data-structures/list/List.spec.ts +0 -181
- package/src/data-structures/list/List.ts +0 -195
- package/src/data-structures/maybe/Maybe.ts +0 -25
- package/src/data-structures/null/null.ts +0 -3
- package/src/data-structures/record/IsRecordLike.spec.ts +0 -29
- package/src/data-structures/record/IsRecordLike.ts +0 -3
- package/src/data-structures/record/Record.spec.ts +0 -240
- package/src/data-structures/record/Record.ts +0 -145
- package/src/data-structures/shalllowEqual/shallowEqual.ts +0 -26
- package/src/data-structures/simpleKey/simpleKey.ts +0 -8
- package/src/examples/SimpleTodoList/SimpleTodoList.ts +0 -53
- package/src/examples/simple.ts +0 -0
- package/src/globals.d.ts +0 -1
- package/src/main.ts +0 -24
- package/src/style.css +0 -41
- package/src/ui/html/Block.ts +0 -10
- package/src/ui/html/Button.ts +0 -12
- package/src/ui/html/HStack.ts +0 -10
- package/src/ui/html/Inline.ts +0 -12
- package/src/ui/html/List.ts +0 -10
- package/src/ui/html/Text.ts +0 -9
- package/src/ui/html/VStack.ts +0 -11
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -23
- package/vite.config.js +0 -10
- /package/{src/data-structures/wildcard/wildcard.ts → dist/internal/wildcard.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1 +1,276 @@
|
|
|
1
|
-
|
|
1
|
+
# Creo
|
|
2
|
+
|
|
3
|
+
> _"There is a point of no return"_
|
|
4
|
+
|
|
5
|
+
- A "streaming" UI framework.
|
|
6
|
+
- No JSX, no templates, no compiler — views are function calls that flow top-down, rendered as they execute.
|
|
7
|
+
- Simplicity over tons of features
|
|
8
|
+
|
|
9
|
+
## Philosophy
|
|
10
|
+
|
|
11
|
+
### Streaming the UI
|
|
12
|
+
|
|
13
|
+
Most frameworks build a tree and diff it. Creo streams it. When `render()` runs, each `div()`, `text()`, `span()` call immediately registers a child in sequence — there is no intermediate representation between your code and the virtual DOM. The render function IS the tree, read top to bottom. What you call is what you get, in the order you call it.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
render() {
|
|
17
|
+
h1({}, () => text("Title")); // first child
|
|
18
|
+
p({}, () => text("Paragraph")); // second child
|
|
19
|
+
ul({}, () => { // third child, with its own children streamed inside
|
|
20
|
+
li({}, () => text("One"));
|
|
21
|
+
li({}, () => text("Two"));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Native control flow
|
|
27
|
+
|
|
28
|
+
No `v-if`, no `{condition && <X/>}`, no `.map()` wrappers. Creo renders imperatively — use `if`, `for`, `while`, `switch`, or any JavaScript you want. The language IS the template language.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
render() {
|
|
32
|
+
if (loading.get()) {
|
|
33
|
+
Spinner();
|
|
34
|
+
} else if (error.get()) {
|
|
35
|
+
ErrorBanner({ message: error.get() });
|
|
36
|
+
} else {
|
|
37
|
+
for (const item of items.get()) {
|
|
38
|
+
ListItem({ key: item.id, data: item });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Say _NO_ to:
|
|
45
|
+
|
|
46
|
+
- special syntax to learn.
|
|
47
|
+
- framework-specific iteration helpers.
|
|
48
|
+
- ternary gymnastics.
|
|
49
|
+
|
|
50
|
+
### Minimal model
|
|
51
|
+
|
|
52
|
+
The entire reactivity model is three concepts:
|
|
53
|
+
|
|
54
|
+
- **`use(value)`** — local state. Call `.get()`, `.set()`, `.update()`. That's it.
|
|
55
|
+
- **`store.new(value)`** — global state. Same interface. Any view can subscribe with `use(store)`.
|
|
56
|
+
- **`props()`** — read-only, passed by parent. A function call, not a magic object.
|
|
57
|
+
|
|
58
|
+
No:
|
|
59
|
+
|
|
60
|
+
- Computed properties
|
|
61
|
+
- Watchers
|
|
62
|
+
- Dependency arrays
|
|
63
|
+
- Selectors
|
|
64
|
+
|
|
65
|
+
State is explicit: you set it, you read it, you control when things update via `shouldUpdate`.
|
|
66
|
+
|
|
67
|
+
One of major ideas under the framework "if user can do it with same efficiency, let it outside framework zone"
|
|
68
|
+
|
|
69
|
+
### Pluggable renderers
|
|
70
|
+
|
|
71
|
+
Creo allows any renderer overrides.
|
|
72
|
+
|
|
73
|
+
- **`HtmlRender`** — DOM output for browsers
|
|
74
|
+
- **`JsonRender`** — JSON AST for testing and serialization
|
|
75
|
+
- **`StringRender`** — HTML strings for SSR
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// Browser
|
|
79
|
+
createApp(() => App(), new HtmlRender(document.getElementById("app")!)).mount();
|
|
80
|
+
|
|
81
|
+
// Server
|
|
82
|
+
const renderer = new StringRender();
|
|
83
|
+
const engine = new Engine(renderer);
|
|
84
|
+
engine.createRoot(() => App(), {});
|
|
85
|
+
engine.render();
|
|
86
|
+
return renderer.renderToString();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Quick Start
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { createApp, view, div, text, button } from "creo";
|
|
93
|
+
import { HtmlRender } from "creo";
|
|
94
|
+
|
|
95
|
+
const Counter = view<{ initial: number }>(({ props, use }) => {
|
|
96
|
+
const count = use(props().initial);
|
|
97
|
+
const increment = () => count.update((n) => n + 1);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
render() {
|
|
101
|
+
button({ onClick: increment }, () => {
|
|
102
|
+
text(count.get());
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
createApp(
|
|
109
|
+
() => Counter({ initial: 0 }),
|
|
110
|
+
new HtmlRender(document.getElementById("app")!),
|
|
111
|
+
).mount();
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Core Concepts
|
|
115
|
+
|
|
116
|
+
### Views
|
|
117
|
+
|
|
118
|
+
Views are components. Define them with `view<Props>()`, which takes a setup function called once per lifecycle. The setup returns a `ViewBody` with a `render()` function called on every update.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const Greeting = view<{ name: string }>(({ props }) => ({
|
|
122
|
+
render() {
|
|
123
|
+
div({ class: "greeting" }, () => {
|
|
124
|
+
text(`Hello, ${props().name}!`);
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
// Usage in another view's render:
|
|
130
|
+
Greeting({ name: "world" });
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### State
|
|
134
|
+
|
|
135
|
+
Local reactive state via `use()`. Called once in the setup function, read via `.get()` in render.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const Toggle = view(({ use }) => {
|
|
139
|
+
const on = use(false);
|
|
140
|
+
const toggle = () => on.update((v) => !v);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
render() {
|
|
144
|
+
button({ onClick: toggle }, () => {
|
|
145
|
+
text(on.get() ? "ON" : "OFF");
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Store
|
|
153
|
+
|
|
154
|
+
Global shared state. Create outside views, subscribe inside with `use(store)`.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { store } from "creo";
|
|
158
|
+
|
|
159
|
+
const ThemeStore = store.new<"light" | "dark">("light");
|
|
160
|
+
|
|
161
|
+
// Any view can subscribe:
|
|
162
|
+
const ThemeDisplay = view(({ use }) => {
|
|
163
|
+
const theme = use(ThemeStore);
|
|
164
|
+
return {
|
|
165
|
+
render() {
|
|
166
|
+
text(`Theme: ${theme.get()}`);
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Update from anywhere:
|
|
172
|
+
ThemeStore.set("dark");
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Slots (Children)
|
|
176
|
+
|
|
177
|
+
Pass children via a slot callback — the second argument to any view or primitive.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
const Card = view<{ title: string }>(({ props, slot }) => ({
|
|
181
|
+
render() {
|
|
182
|
+
div({ class: "card" }, () => {
|
|
183
|
+
h1({}, () => text(props().title));
|
|
184
|
+
div({ class: "card-body" }, slot);
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
}));
|
|
188
|
+
|
|
189
|
+
// Usage:
|
|
190
|
+
Card({ title: "Hello" }, () => {
|
|
191
|
+
p({}, () => text("Card content here."));
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Lifecycle Hooks
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
const MyView = view<{ value: number }>(({ props }) => ({
|
|
199
|
+
onMount() {
|
|
200
|
+
/* after first render */
|
|
201
|
+
},
|
|
202
|
+
onUpdateBefore() {
|
|
203
|
+
/* before re-render */
|
|
204
|
+
},
|
|
205
|
+
onUpdateAfter() {
|
|
206
|
+
/* after re-render */
|
|
207
|
+
},
|
|
208
|
+
shouldUpdate(nextProps) {
|
|
209
|
+
return nextProps.value !== props().value;
|
|
210
|
+
},
|
|
211
|
+
render() {
|
|
212
|
+
text(props().value);
|
|
213
|
+
},
|
|
214
|
+
}));
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Keyed Lists
|
|
218
|
+
|
|
219
|
+
Use `key` in props for efficient list reconciliation:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
for (const item of items.get()) {
|
|
223
|
+
TodoItem({ key: item.id, text: item.text, done: item.done });
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Conditional Rendering
|
|
228
|
+
|
|
229
|
+
Standard JavaScript control flow — no special syntax:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
render() {
|
|
233
|
+
if (editing.get()) {
|
|
234
|
+
Editor({ key: id, value });
|
|
235
|
+
} else {
|
|
236
|
+
Display({ key: id, value });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Router
|
|
242
|
+
|
|
243
|
+
Hash-based router available as `creo-router` (separate package):
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { createRouter } from "creo-router";
|
|
247
|
+
|
|
248
|
+
const { routeStore, navigate, RouterView, Link } = createRouter({
|
|
249
|
+
routes: [
|
|
250
|
+
{ path: "/", view: () => HomePage() },
|
|
251
|
+
{ path: "/users/:id", view: () => UserPage() },
|
|
252
|
+
],
|
|
253
|
+
fallback: () => NotFoundPage(),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Navigation:
|
|
257
|
+
Link({ href: "/users/42" }, () => text("User 42"));
|
|
258
|
+
navigate("/users/42"); // programmatic
|
|
259
|
+
|
|
260
|
+
// Read params:
|
|
261
|
+
const route = use(routeStore);
|
|
262
|
+
route.get().params.id; // "42"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Development
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
bun install
|
|
269
|
+
bun test src/ # Run tests
|
|
270
|
+
bun tsc # Type-check
|
|
271
|
+
bun run build # Build to dist/
|
|
272
|
+
|
|
273
|
+
# Run examples:
|
|
274
|
+
cd examples/todo && bun install && bun run dev
|
|
275
|
+
cd examples/router && bun install && bun run dev
|
|
276
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function assertNever(x: never): never;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Longest Increasing Subsequence — O(n log n).
|
|
3
|
+
*
|
|
4
|
+
* Given an array of numbers, returns the Set of *indices* into that array
|
|
5
|
+
* whose elements form the longest strictly increasing subsequence.
|
|
6
|
+
* Entries equal to -1 are skipped (treated as "not present").
|
|
7
|
+
*
|
|
8
|
+
* Used by the keyed reconciler to identify the maximal set of children
|
|
9
|
+
* that are already in correct relative order and don't need DOM moves.
|
|
10
|
+
*/
|
|
11
|
+
export declare function lis(arr: number[]): Set<number>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type None = null | undefined;
|
|
2
|
+
export type Just<T> = T;
|
|
3
|
+
export type Maybe<T> = Just<T> | None;
|
|
4
|
+
export declare function just<T>(maybe: Maybe<T>, errorMessage?: string): asserts maybe is Just<T>;
|
|
5
|
+
export declare function isJust<T>(maybe: Maybe<T>): maybe is Just<T>;
|
|
6
|
+
export declare function isNone<T>(maybe: Maybe<T>): maybe is None;
|
|
7
|
+
export declare function withDefault<T, K>(v: Maybe<T>, alternative: K): K | NonNullable<T>;
|
|
8
|
+
export declare const _: undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type MaybePromise<T> = T | Promise<T>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { createApp } from "./public/app";
|
|
2
|
+
export { view } from "./public/view";
|
|
3
|
+
export type { ViewBody, ViewFn, Slot, PublicView } from "./public/view";
|
|
4
|
+
export type { Reactive, Use } from "./public/state";
|
|
5
|
+
export { State } from "./public/state";
|
|
6
|
+
export { Store, store, isStore } from "./public/store";
|
|
7
|
+
export { $primitive } from "./public/primitive";
|
|
8
|
+
export type { PrimitiveProps, EventHandlerProps } from "./public/primitive";
|
|
9
|
+
export { html, text, div, span, section, article, aside, nav, header, footer, main, address, hgroup, search, p, h1, h2, h3, h4, h5, h6, pre, code, em, strong, small, br, hr, a, blockquote, label, abbr, b, bdi, bdo, cite, data, dfn, i, kbd, mark, q, rp, rt, ruby, s, samp, sub, sup, time, u, varEl, wbr, del, ins, ul, ol, li, dl, dt, dd, table, thead, tbody, tfoot, tr, th, td, caption, colgroup, col, form, button, input, textarea, select, option, fieldset, legend, datalist, optgroup, output, progress, meter, img, video, audio, canvas, source, track, map, area, picture, iframe, embed, object, portal, svg, details, summary, dialog, menu, figure, figcaption, script, noscript, template, slot, } from "./public/primitives/primitives";
|
|
10
|
+
export type { BaseEventData, PointerEventData, KeyEventData, InputEventData, FocusEventData, ContainerEvents, FormEvents, HtmlAttrs, } from "./public/primitives/primitives";
|
|
11
|
+
export type { IRender } from "./render/render_interface";
|
|
12
|
+
export { HtmlRender } from "./render/html_render";
|
|
13
|
+
export { JsonRender } from "./render/json_render";
|
|
14
|
+
export type { JsonNode } from "./render/json_render";
|
|
15
|
+
export { HtmlStringRender, StringRender } from "./render/string_render";
|
|
16
|
+
export { Engine, type Scheduler } from "./internal/engine";
|
|
17
|
+
export { type Maybe, type None, type Just, just, withDefault, _ } from "./functional/maybe";
|
|
18
|
+
export type { Key } from "./functional/key";
|
|
19
|
+
export { shallowEqual } from "./functional/shallow_equal";
|
|
20
|
+
export { assertNever } from "./functional/assert";
|