@faber1999/axon.js 0.1.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/LICENSE +21 -0
- package/README.md +292 -0
- package/dist/chunk-HYNBHCGU.js +227 -0
- package/dist/index.d.ts +463 -0
- package/dist/index.js +515 -0
- package/dist/jsx-KOQXGMp1.d.ts +64 -0
- package/dist/jsx.d.ts +1 -0
- package/dist/jsx.js +8 -0
- package/package.json +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Faber Grajales Hincapié
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# axon.js
|
|
2
|
+
|
|
3
|
+
A fine-grained reactive frontend framework built from scratch.
|
|
4
|
+
JSX syntax · Signals reactivity · Router · Store · No Virtual DOM · Zero dependencies.
|
|
5
|
+
|
|
6
|
+
```tsx
|
|
7
|
+
import { signal, createApp } from 'axon.js'
|
|
8
|
+
|
|
9
|
+
function Counter() {
|
|
10
|
+
const [count, setCount] = signal(0)
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div>
|
|
14
|
+
<p>Count: {count}</p>
|
|
15
|
+
<button onClick={() => setCount((c) => c + 1)}>+1</button>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
createApp(Counter).mount('#app')
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Components run **once**. Only the exact DOM nodes that depend on a signal update — no diffing, no re-renders.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install axon.js
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Vite setup
|
|
34
|
+
|
|
35
|
+
**`vite.config.ts`**
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { defineConfig } from 'vite'
|
|
39
|
+
|
|
40
|
+
export default defineConfig({
|
|
41
|
+
esbuild: {
|
|
42
|
+
jsxFactory: 'h',
|
|
43
|
+
jsxFragment: 'Fragment',
|
|
44
|
+
jsxInject: `import { h, Fragment } from 'axon.js/jsx'`
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**`tsconfig.json`**
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"compilerOptions": {
|
|
54
|
+
"jsx": "preserve",
|
|
55
|
+
"jsxFactory": "h",
|
|
56
|
+
"jsxFragmentFactory": "Fragment",
|
|
57
|
+
"strict": true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
That's it. No plugins, no Babel, no extra config.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Core API
|
|
67
|
+
|
|
68
|
+
### Reactivity
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { signal, effect, computed, batch, untrack } from 'axon.js'
|
|
72
|
+
|
|
73
|
+
// signal — reactive value
|
|
74
|
+
const [count, setCount] = signal(0)
|
|
75
|
+
count() // read
|
|
76
|
+
setCount(1) // write
|
|
77
|
+
setCount((c) => c + 1) // update with function
|
|
78
|
+
|
|
79
|
+
// effect — runs immediately and re-runs when dependencies change
|
|
80
|
+
effect(() => {
|
|
81
|
+
console.log('count is', count())
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// computed — derived reactive value
|
|
85
|
+
const double = computed(() => count() * 2)
|
|
86
|
+
double() // 0, 2, 4...
|
|
87
|
+
|
|
88
|
+
// batch — group multiple updates into one notification
|
|
89
|
+
batch(() => {
|
|
90
|
+
setFirstName('John')
|
|
91
|
+
setLastName('Doe')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// untrack — read a signal without subscribing
|
|
95
|
+
effect(() => {
|
|
96
|
+
const a = count() // subscribes
|
|
97
|
+
const b = untrack(() => x()) // does NOT subscribe
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### JSX & Components
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { onMount, onCleanup, createApp } from 'axon.js'
|
|
105
|
+
|
|
106
|
+
function Timer() {
|
|
107
|
+
const [seconds, setSeconds] = signal(0)
|
|
108
|
+
|
|
109
|
+
onMount(() => {
|
|
110
|
+
const id = setInterval(() => setSeconds((s) => s + 1), 1000)
|
|
111
|
+
onCleanup(() => clearInterval(id))
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return <p>Elapsed: {seconds}s</p>
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
createApp(Timer).mount('#app')
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Control flow
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { Show, For, Dynamic, Portal } from 'axon.js'
|
|
124
|
+
|
|
125
|
+
// Conditional rendering
|
|
126
|
+
<Show when={isLoggedIn} fallback={<Login />}>
|
|
127
|
+
<Dashboard />
|
|
128
|
+
</Show>
|
|
129
|
+
|
|
130
|
+
// List rendering
|
|
131
|
+
<For each={todos}>
|
|
132
|
+
{(todo, index) => <li>{todo.text}</li>}
|
|
133
|
+
</For>
|
|
134
|
+
|
|
135
|
+
// Dynamic component
|
|
136
|
+
<Dynamic component={currentView} />
|
|
137
|
+
|
|
138
|
+
// Render outside the tree (e.g. modals)
|
|
139
|
+
<Portal mount={document.body}>
|
|
140
|
+
<Modal />
|
|
141
|
+
</Portal>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Router
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import { createRouter, RouterView, Link, useRouter, useParams } from 'axon.js'
|
|
148
|
+
|
|
149
|
+
createRouter(
|
|
150
|
+
[
|
|
151
|
+
{ path: '/', component: Home },
|
|
152
|
+
{ path: '/about', component: About },
|
|
153
|
+
|
|
154
|
+
// Route groups — shared layout and/or guard
|
|
155
|
+
{
|
|
156
|
+
layout: DashboardLayout,
|
|
157
|
+
guard: () => isLoggedIn() || '/login',
|
|
158
|
+
fallbackPath: '/login',
|
|
159
|
+
children: [
|
|
160
|
+
{ path: '/dashboard', component: Dashboard },
|
|
161
|
+
{ path: '/settings', component: Settings }
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// Catch-all 404
|
|
166
|
+
{ path: '*', component: NotFound }
|
|
167
|
+
],
|
|
168
|
+
{ viewTransitions: true }
|
|
169
|
+
) // optional animated transitions
|
|
170
|
+
|
|
171
|
+
function App() {
|
|
172
|
+
return (
|
|
173
|
+
<div>
|
|
174
|
+
<nav>
|
|
175
|
+
<Link href="/">Home</Link>
|
|
176
|
+
<Link href="/about" activeClass="active">
|
|
177
|
+
About
|
|
178
|
+
</Link>
|
|
179
|
+
</nav>
|
|
180
|
+
<main>
|
|
181
|
+
<RouterView />
|
|
182
|
+
</main>
|
|
183
|
+
</div>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Guard return values:**
|
|
189
|
+
|
|
190
|
+
| Returns | Behavior |
|
|
191
|
+
| --------- | --------------------------------------------------------- |
|
|
192
|
+
| `true` | Allow access, render the component |
|
|
193
|
+
| `false` | Navigate back to the previous route, or to `fallbackPath` |
|
|
194
|
+
| `"/path"` | Redirect to that path |
|
|
195
|
+
|
|
196
|
+
**Router hooks:**
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
const router = useRouter() // full router instance
|
|
200
|
+
const params = useParams() // { id: '42' }
|
|
201
|
+
const navigate = useNavigate() // navigate('/path')
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Store
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { createStore, select } from 'axon.js'
|
|
208
|
+
|
|
209
|
+
interface AppState {
|
|
210
|
+
theme: 'dark' | 'light'
|
|
211
|
+
count: number
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const [store, setStore] = createStore<AppState>({
|
|
215
|
+
theme: 'dark',
|
|
216
|
+
count: 0
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
store.theme // read (reactive)
|
|
220
|
+
setStore('theme', 'light') // set one property
|
|
221
|
+
setStore('count', (c) => c + 1) // update with function
|
|
222
|
+
setStore({ theme: 'light', count: 5 }) // merge update
|
|
223
|
+
|
|
224
|
+
// Derived value from store
|
|
225
|
+
const label = select(store, (s) => `Theme: ${s.theme}`)
|
|
226
|
+
label() // reactive getter
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Multiple independent stores are supported — just call `createStore` multiple times.
|
|
230
|
+
|
|
231
|
+
### Context
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
import { createContext } from 'axon.js'
|
|
235
|
+
|
|
236
|
+
const ThemeContext = createContext<'dark' | 'light'>('dark')
|
|
237
|
+
|
|
238
|
+
function App() {
|
|
239
|
+
return (
|
|
240
|
+
<ThemeContext.Provider value="light">
|
|
241
|
+
<Page />
|
|
242
|
+
</ThemeContext.Provider>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function Page() {
|
|
247
|
+
const theme = ThemeContext.use()
|
|
248
|
+
return <div class={theme}>...</div>
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### View Transitions
|
|
253
|
+
|
|
254
|
+
Pass `{ viewTransitions: true }` to `createRouter` and name your content area:
|
|
255
|
+
|
|
256
|
+
```css
|
|
257
|
+
main {
|
|
258
|
+
view-transition-name: page;
|
|
259
|
+
}
|
|
260
|
+
::view-transition-old(page) {
|
|
261
|
+
animation: 120ms ease-out fade-out both;
|
|
262
|
+
}
|
|
263
|
+
::view-transition-new(page) {
|
|
264
|
+
animation: 180ms ease-in fade-in both;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The framework handles the rest automatically. Falls back gracefully in unsupported browsers.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## How it works
|
|
273
|
+
|
|
274
|
+
axon.js uses **fine-grained reactivity**: a global effect stack tracks which signals are read during execution, creating subscriptions automatically. No compiler magic — pure runtime JavaScript.
|
|
275
|
+
|
|
276
|
+
- Components execute **once** to build their initial DOM.
|
|
277
|
+
- Only `effect()` callbacks re-execute when signals change.
|
|
278
|
+
- DOM updates are surgical — only the exact node that depends on a signal is touched.
|
|
279
|
+
|
|
280
|
+
For a deep dive into the internals, see [INTERNALS.md](INTERNALS.md).
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## License
|
|
285
|
+
|
|
286
|
+
MIT
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
<div align="center">
|
|
291
|
+
<sub>Built with ❤️ by <a href="https://github.com/faber1999">faber1999</a></sub>
|
|
292
|
+
</div>
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// src/reactivity/effect.ts
|
|
2
|
+
var effectStack = [];
|
|
3
|
+
function getCurrentEffect() {
|
|
4
|
+
return effectStack[effectStack.length - 1] ?? null;
|
|
5
|
+
}
|
|
6
|
+
function effect(fn) {
|
|
7
|
+
let cleanup = null;
|
|
8
|
+
const run = (() => {
|
|
9
|
+
if (typeof cleanup === "function") {
|
|
10
|
+
cleanup();
|
|
11
|
+
cleanup = null;
|
|
12
|
+
}
|
|
13
|
+
effectStack.push(run);
|
|
14
|
+
try {
|
|
15
|
+
cleanup = fn() ?? null;
|
|
16
|
+
} finally {
|
|
17
|
+
effectStack.pop();
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
run._subscriptions = /* @__PURE__ */ new Set();
|
|
21
|
+
run._disposed = false;
|
|
22
|
+
run();
|
|
23
|
+
const dispose = () => {
|
|
24
|
+
run._disposed = true;
|
|
25
|
+
run._subscriptions.forEach((unsub) => unsub());
|
|
26
|
+
run._subscriptions.clear();
|
|
27
|
+
if (typeof cleanup === "function") {
|
|
28
|
+
cleanup();
|
|
29
|
+
cleanup = null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return dispose;
|
|
33
|
+
}
|
|
34
|
+
function untrack(fn) {
|
|
35
|
+
const saved = effectStack.splice(0);
|
|
36
|
+
try {
|
|
37
|
+
return fn();
|
|
38
|
+
} finally {
|
|
39
|
+
effectStack.push(...saved);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/component/lifecycle.ts
|
|
44
|
+
var ownerStack = [];
|
|
45
|
+
function getCurrentOwner() {
|
|
46
|
+
return ownerStack[ownerStack.length - 1] ?? null;
|
|
47
|
+
}
|
|
48
|
+
function runOwned(fn) {
|
|
49
|
+
const owner = {
|
|
50
|
+
_onMount: [],
|
|
51
|
+
_onCleanup: [],
|
|
52
|
+
_children: [],
|
|
53
|
+
_mounted: false
|
|
54
|
+
};
|
|
55
|
+
const parent = getCurrentOwner();
|
|
56
|
+
if (parent) parent._children.push(owner);
|
|
57
|
+
ownerStack.push(owner);
|
|
58
|
+
let result;
|
|
59
|
+
try {
|
|
60
|
+
result = fn();
|
|
61
|
+
} finally {
|
|
62
|
+
ownerStack.pop();
|
|
63
|
+
}
|
|
64
|
+
queueMicrotask(() => {
|
|
65
|
+
if (!owner._mounted) {
|
|
66
|
+
owner._mounted = true;
|
|
67
|
+
owner._onMount.forEach((cb) => cb());
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return [result, owner];
|
|
71
|
+
}
|
|
72
|
+
function runWithOwner(fn, props) {
|
|
73
|
+
const [result] = runOwned(() => fn(props));
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
function onMount(fn) {
|
|
77
|
+
const owner = getCurrentOwner();
|
|
78
|
+
if (owner) owner._onMount.push(fn);
|
|
79
|
+
else console.warn("[axon] onMount called outside of a component");
|
|
80
|
+
}
|
|
81
|
+
function onCleanup(fn) {
|
|
82
|
+
const owner = getCurrentOwner();
|
|
83
|
+
if (owner) owner._onCleanup.push(fn);
|
|
84
|
+
else console.warn("[axon] onCleanup called outside of a component");
|
|
85
|
+
}
|
|
86
|
+
function disposeOwner(owner) {
|
|
87
|
+
owner._children.forEach(disposeOwner);
|
|
88
|
+
owner._onCleanup.forEach((cb) => cb());
|
|
89
|
+
owner._onMount = [];
|
|
90
|
+
owner._onCleanup = [];
|
|
91
|
+
owner._children = [];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/dom/h.ts
|
|
95
|
+
var Fragment = /* @__PURE__ */ Symbol("Fragment");
|
|
96
|
+
var BOOLEAN_ATTRS = /* @__PURE__ */ new Set([
|
|
97
|
+
"checked",
|
|
98
|
+
"disabled",
|
|
99
|
+
"readonly",
|
|
100
|
+
"multiple",
|
|
101
|
+
"selected",
|
|
102
|
+
"autofocus",
|
|
103
|
+
"autoplay",
|
|
104
|
+
"controls",
|
|
105
|
+
"default",
|
|
106
|
+
"defer",
|
|
107
|
+
"formnovalidate",
|
|
108
|
+
"hidden",
|
|
109
|
+
"ismap",
|
|
110
|
+
"loop",
|
|
111
|
+
"novalidate",
|
|
112
|
+
"open",
|
|
113
|
+
"required",
|
|
114
|
+
"reversed",
|
|
115
|
+
"scoped",
|
|
116
|
+
"seamless"
|
|
117
|
+
]);
|
|
118
|
+
function applyProp(el, key, value) {
|
|
119
|
+
if (key === "class" || key === "className") {
|
|
120
|
+
el.className = value ?? "";
|
|
121
|
+
} else if (key === "style") {
|
|
122
|
+
if (typeof value === "string") {
|
|
123
|
+
el.style.cssText = value;
|
|
124
|
+
} else if (value && typeof value === "object") {
|
|
125
|
+
Object.assign(el.style, value);
|
|
126
|
+
}
|
|
127
|
+
} else if (key === "ref") {
|
|
128
|
+
if (typeof value === "function") {
|
|
129
|
+
value(el);
|
|
130
|
+
} else if (value && typeof value === "object") {
|
|
131
|
+
value.current = el;
|
|
132
|
+
}
|
|
133
|
+
} else if (key.startsWith("on") && key.length > 2) {
|
|
134
|
+
const event = key.slice(2).toLowerCase();
|
|
135
|
+
el.addEventListener(event, value);
|
|
136
|
+
} else if (BOOLEAN_ATTRS.has(key)) {
|
|
137
|
+
if (value) el.setAttribute(key, "");
|
|
138
|
+
else el.removeAttribute(key);
|
|
139
|
+
} else if (key === "innerHTML") {
|
|
140
|
+
el.innerHTML = value;
|
|
141
|
+
} else {
|
|
142
|
+
if (value == null || value === false) {
|
|
143
|
+
el.removeAttribute(key);
|
|
144
|
+
} else {
|
|
145
|
+
el.setAttribute(key, value === true ? "" : String(value));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function toNode(value) {
|
|
150
|
+
if (value == null || value === false) return document.createTextNode("");
|
|
151
|
+
if (value instanceof Node) return value;
|
|
152
|
+
if (typeof value === "function") return document.createTextNode("");
|
|
153
|
+
return document.createTextNode(String(value));
|
|
154
|
+
}
|
|
155
|
+
function appendChild(parent, child) {
|
|
156
|
+
if (child == null || child === false) return;
|
|
157
|
+
if (Array.isArray(child)) {
|
|
158
|
+
child.forEach((c) => appendChild(parent, c));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (child instanceof Node) {
|
|
162
|
+
parent.appendChild(child);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (typeof child === "function") {
|
|
166
|
+
const startMarker = document.createComment("");
|
|
167
|
+
const endMarker = document.createComment("");
|
|
168
|
+
parent.appendChild(startMarker);
|
|
169
|
+
parent.appendChild(endMarker);
|
|
170
|
+
effect(() => {
|
|
171
|
+
const result = child();
|
|
172
|
+
let node = startMarker.nextSibling;
|
|
173
|
+
while (node && node !== endMarker) {
|
|
174
|
+
const next = node.nextSibling;
|
|
175
|
+
parent.removeChild(node);
|
|
176
|
+
node = next;
|
|
177
|
+
}
|
|
178
|
+
const nodes = Array.isArray(result) ? result : [result];
|
|
179
|
+
nodes.forEach((n) => {
|
|
180
|
+
if (n != null && n !== false) {
|
|
181
|
+
parent.insertBefore(toNode(n), endMarker);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
parent.appendChild(document.createTextNode(String(child)));
|
|
188
|
+
}
|
|
189
|
+
function h(type, props, ...children) {
|
|
190
|
+
if (type === Fragment) {
|
|
191
|
+
return children.flat();
|
|
192
|
+
}
|
|
193
|
+
if (typeof type === "function") {
|
|
194
|
+
const componentProps = { ...props ?? {} };
|
|
195
|
+
if (children.length === 1) componentProps.children = children[0];
|
|
196
|
+
else if (children.length > 1) componentProps.children = children.flat();
|
|
197
|
+
return runWithOwner(type, componentProps);
|
|
198
|
+
}
|
|
199
|
+
const el = document.createElement(type);
|
|
200
|
+
if (props) {
|
|
201
|
+
for (const key of Object.keys(props)) {
|
|
202
|
+
const value = props[key];
|
|
203
|
+
if (key === "children") continue;
|
|
204
|
+
if (typeof value === "function" && !key.startsWith("on")) {
|
|
205
|
+
effect(() => applyProp(el, key, value()));
|
|
206
|
+
} else {
|
|
207
|
+
applyProp(el, key, value);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const flatChildren = children.flat();
|
|
212
|
+
flatChildren.forEach((child) => appendChild(el, child));
|
|
213
|
+
return el;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export {
|
|
217
|
+
getCurrentEffect,
|
|
218
|
+
effect,
|
|
219
|
+
untrack,
|
|
220
|
+
runOwned,
|
|
221
|
+
runWithOwner,
|
|
222
|
+
onMount,
|
|
223
|
+
onCleanup,
|
|
224
|
+
disposeOwner,
|
|
225
|
+
Fragment,
|
|
226
|
+
h
|
|
227
|
+
};
|