creo 0.2.6 → 0.2.7

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/docs/store.md ADDED
@@ -0,0 +1,135 @@
1
+ # Store
2
+
3
+ Creo's store system provides globally visible reactive data. A store is created outside views and can be read/written from any view via `use()`.
4
+
5
+ ## Creating a store
6
+
7
+ Use `store.new(initial)` at module scope:
8
+
9
+ ```ts
10
+ import { store } from "creo";
11
+
12
+ const ThemeStore = store.new<"light" | "dark">("light");
13
+ const UserStore = store.new<{ name: string; role: string } | null>(null);
14
+ ```
15
+
16
+ Store instances are typically defined at module scope and imported where needed.
17
+
18
+ ## Reading from a view
19
+
20
+ Use `use(store)` inside a view function to subscribe and get a reactive accessor:
21
+
22
+ ```ts
23
+ import { view, div, text } from "creo";
24
+
25
+ const ThemedButton = view(({ use }) => {
26
+ const theme = use(ThemeStore); // re-renders when ThemeStore changes
27
+
28
+ return {
29
+ render() {
30
+ button({ class: theme.get() === "dark" ? "btn-dark" : "btn-light" }, () => {
31
+ text("Click me");
32
+ });
33
+ },
34
+ };
35
+ });
36
+ ```
37
+
38
+ `use(store)` subscribes the view to changes. When the store value is updated via `.set()` or `.update()`, all subscribed views are scheduled for re-render. Subscriptions are automatically cleaned up when the view is disposed.
39
+
40
+ ## Setting values
41
+
42
+ Call `.set()` or `.update()` on the store directly -- from anywhere, including outside views:
43
+
44
+ ```ts
45
+ // From a handler
46
+ const toggle = () => {
47
+ const current = ThemeStore.get();
48
+ ThemeStore.set(current === "light" ? "dark" : "light");
49
+ };
50
+
51
+ // From outside any view
52
+ ThemeStore.set("dark");
53
+
54
+ // Using update
55
+ ThemeStore.update(current => current === "light" ? "dark" : "light");
56
+ ```
57
+
58
+ ## Complete example
59
+
60
+ ```ts
61
+ import { createApp, store, view, div, button, span, text, HtmlRender } from "creo";
62
+
63
+ // 1. Create the store
64
+ const CounterStore = store.new(0);
65
+
66
+ // 2. Writer component
67
+ const IncrementButton = view(({ use }) => {
68
+ const counter = use(CounterStore);
69
+ const increment = () => counter.update(n => n + 1);
70
+
71
+ return {
72
+ render() {
73
+ button({ on: { click: increment } }, () => { text("+1"); });
74
+ },
75
+ };
76
+ });
77
+
78
+ // 3. Reader component
79
+ const DisplayCount = view(({ use }) => {
80
+ const counter = use(CounterStore);
81
+
82
+ return {
83
+ render() {
84
+ span({}, () => {
85
+ text(`Count: ${counter.get()}`);
86
+ });
87
+ },
88
+ };
89
+ });
90
+
91
+ // 4. App
92
+ const App = view(() => ({
93
+ render() {
94
+ div({}, () => {
95
+ IncrementButton();
96
+ DisplayCount();
97
+ });
98
+ },
99
+ }));
100
+
101
+ // 5. Mount
102
+ createApp(() => App(), new HtmlRender(document.getElementById("app")!)).mount();
103
+ ```
104
+
105
+ ## Store vs State
106
+
107
+ Both store and local state use the same `use()` function and return the same `Reactive<T>` interface (`get`, `set`, `update`). The difference:
108
+
109
+ | | Store | State |
110
+ |---|---|---|
111
+ | Created with | `store.new(initial)` | `use(initial)` inside a view |
112
+ | Scope | Global -- shared across views | Local -- private to the view |
113
+ | Setting from outside | `MyStore.set(value)` | Not possible |
114
+ | Subscribes view | Yes -- `use(store)` re-renders on change | Yes -- `use(value)` re-renders on change |
115
+
116
+ ## Store type
117
+
118
+ The `Store<T>` class is exported for type annotations:
119
+
120
+ ```ts
121
+ import type { Store } from "creo";
122
+
123
+ function resetStore<T>(s: Store<T>, value: T): void {
124
+ s.set(value);
125
+ }
126
+ ```
127
+
128
+ ### Full API
129
+
130
+ | Method | Description |
131
+ |--------|-------------|
132
+ | `.get(): T` | Read the current value |
133
+ | `.set(value: T): void` | Set a new value, re-render all subscribers |
134
+ | `.update(fn: (current: T) => T): void` | Apply a sync transform, re-render subscribers |
135
+ | `.update(fn: (current: T) => Promise<T>): void` | Apply an async transform, re-render on resolve |
package/docs/view.md ADDED
@@ -0,0 +1,205 @@
1
+ # view()
2
+
3
+ `view()` is the core API for defining components in Creo.
4
+
5
+ ## Signature
6
+
7
+ ```ts
8
+ function view<Props = void, Api = void>(
9
+ viewFn: ViewFn<Props, Api>
10
+ ): (props: Props & { key?: Key }, slot?: Slot) => void;
11
+ ```
12
+
13
+ When `Props` is `void` (no props), the returned function can be called with no arguments:
14
+
15
+ ```ts
16
+ const App = view((ctx) => ({
17
+ render() { /* ... */ },
18
+ }));
19
+
20
+ App(); // no args needed
21
+ ```
22
+
23
+ ## ViewFn and context
24
+
25
+ The function passed to `view()` receives a **context object** (`ctx`) with three fields:
26
+
27
+ ```ts
28
+ const MyComponent = view<{ title: string }>((ctx) => {
29
+ // ctx.props -- function that returns the current props object
30
+ // ctx.use -- factory to create reactive state or subscribe to stores
31
+ // ctx.children -- pre-collected PendingView[] from the parent slot
32
+
33
+ return {
34
+ render() { /* ... */ },
35
+ };
36
+ });
37
+ ```
38
+
39
+ ### ctx.props
40
+
41
+ A function that returns the current props passed by the parent. Call `ctx.props()` to read:
42
+
43
+ ```ts
44
+ const Label = view<{ text: string }>((ctx) => ({
45
+ render() {
46
+ span({}, () => { text(ctx.props().text); });
47
+ },
48
+ }));
49
+ ```
50
+
51
+ Because `props` is a function, calling it always returns the latest values -- whether inside `render()`, lifecycle hooks, or event handlers:
52
+
53
+ ```ts
54
+ const Good = view<{ title: string }>((ctx) => ({
55
+ render() { text(ctx.props().title); }, // always current
56
+ }));
57
+ ```
58
+
59
+ ### ctx.use
60
+
61
+ A factory function to create reactive state or subscribe to stores. See [State](./state.md) and [Store](./store.md).
62
+
63
+ ```ts
64
+ const count = ctx.use(0); // Reactive<number> — local state
65
+ const items = ctx.use<string[]>([]); // Reactive<string[]> — local state
66
+ const theme = ctx.use(ThemeStore); // Reactive<string> — store subscription
67
+ ```
68
+
69
+ ### ctx.children
70
+
71
+ An array of `PendingView` objects representing the children passed by the caller's slot. Pass `ctx.children` directly to a primitive's second argument to render them:
72
+
73
+ ```ts
74
+ const Wrapper = view((ctx) => ({
75
+ render() {
76
+ div({ class: "wrapper" }, ctx.children);
77
+ },
78
+ }));
79
+ ```
80
+
81
+ If the caller provided no slot, `ctx.children` is an empty array.
82
+
83
+ ## ViewBody
84
+
85
+ The viewFn must return a `ViewBody` object. The only required field is `render`:
86
+
87
+ ```ts
88
+ type ViewBody<Props, Api> = {
89
+ render: () => void;
90
+ mount?: {
91
+ before?: () => void;
92
+ after?: () => void;
93
+ };
94
+ update?: {
95
+ should?: (nextProps: Props) => boolean;
96
+ before?: () => void;
97
+ after?: () => void;
98
+ };
99
+ };
100
+ ```
101
+
102
+ When `Api` is provided (not `void`), ViewBody also includes an `api` field. See [Exposing an API](#exposing-an-api) below.
103
+
104
+ ### render()
105
+
106
+ Called on every render cycle. Inside `render()`, call primitives and child components imperatively. The order of calls defines the virtual DOM structure:
107
+
108
+ ```ts
109
+ render() {
110
+ div({ class: "header" }, () => {
111
+ h1({}, () => { text("Title"); });
112
+ });
113
+ div({ class: "body" }, () => {
114
+ text("Content");
115
+ });
116
+ }
117
+ ```
118
+
119
+ All standard JavaScript control flow works inside render:
120
+
121
+ ```ts
122
+ render() {
123
+ if (isEditing.get()) {
124
+ input({ value: draft.get(), on: { input: handleInput } });
125
+ } else {
126
+ span({}, () => { text(value.get()); });
127
+ }
128
+
129
+ for (const item of items.get()) {
130
+ ListItem({ key: item.id, data: item });
131
+ }
132
+ }
133
+ ```
134
+
135
+ ### Lifecycle hooks
136
+
137
+ See [Lifecycle](./lifecycle.md) for details on `mount` and `update`.
138
+
139
+ ## Calling components
140
+
141
+ Components are called as functions. The first argument is props, the second is an optional slot:
142
+
143
+ ```ts
144
+ // No props, no children
145
+ MyComponent();
146
+
147
+ // With props
148
+ Counter({ initial: 5 });
149
+
150
+ // With children (slot)
151
+ Card({}, () => {
152
+ text("Inside the card");
153
+ });
154
+
155
+ // With props and children
156
+ Section({ title: "Info" }, () => {
157
+ Paragraph({ text: "Details here" });
158
+ });
159
+ ```
160
+
161
+ ### Keys
162
+
163
+ Pass `key` in the props object to help reconciliation identify items across re-renders:
164
+
165
+ ```ts
166
+ for (const user of users.get()) {
167
+ UserCard({ key: user.id, name: user.name });
168
+ }
169
+ ```
170
+
171
+ ## Exposing an API via `ref`
172
+
173
+ Both primitives and composite views accept a `ref` prop on the call site, and
174
+ both fill it through the same `Ref<T>` machinery. The difference is on the
175
+ provider side: the renderer pushes the DOM `Element` into a primitive's ref;
176
+ inside a composite, you push an API object yourself by calling `ctx.ref(...)`.
177
+
178
+ ```ts
179
+ import { view, type RefObject } from "creo";
180
+
181
+ const TextInput = view<{ placeholder: string }, { focus: () => void }>(
182
+ ({ props, ref }) => {
183
+ const inputRef: RefObject<HTMLInputElement> = { current: null };
184
+ ref({
185
+ focus: () => inputRef.current?.focus(),
186
+ });
187
+ return {
188
+ render() {
189
+ input({ placeholder: props().placeholder, ref: inputRef });
190
+ },
191
+ };
192
+ },
193
+ );
194
+
195
+ // Consumer:
196
+ const myInput: RefObject<{ focus: () => void }> = { current: null };
197
+ TextInput({ placeholder: "Email", ref: myInput });
198
+ // After mount:
199
+ myInput.current?.focus();
200
+ ```
201
+
202
+ `ref` works the same on primitives — `div({ ref })` populates `.current` (or
203
+ fires the callback) with the underlying `Element` after mount, and `null` on
204
+ unmount. The provider verb is `ctx.ref(...)` on a composite; the consumer
205
+ noun is `ref` in props on either side.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "creo",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -12,7 +12,10 @@
12
12
  }
13
13
  },
14
14
  "files": [
15
- "dist"
15
+ "dist",
16
+ "AGENTS.md",
17
+ "CHANGELOG.md",
18
+ "docs"
16
19
  ],
17
20
  "scripts": {
18
21
  "build": "bun run build.ts",