olova 2.0.5 → 2.0.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/README.md +33 -237
- package/dist/hooks/callback.js +4 -0
- package/dist/hooks/context.js +21 -0
- package/dist/hooks/effect.js +11 -0
- package/dist/hooks/memo.js +15 -0
- package/dist/hooks/reducer.js +11 -0
- package/dist/hooks/ref.js +5 -0
- package/dist/hooks/state.js +19 -0
- package/dist/olova.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,258 +1,54 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Olova JavaScript Framework
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Olova is a lightweight and reactive JavaScript framework that simplifies UI
|
|
4
|
+
development with a clean, intuitive syntax. It provides a reactivity system and
|
|
5
|
+
hooks to manage state and side effects, allowing developers to build modern web
|
|
6
|
+
applications with ease.
|
|
4
7
|
|
|
5
|
-
##
|
|
8
|
+
## Features
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
- **State Management**: Use the `State` hook to manage reactive state in your
|
|
11
|
+
components.
|
|
12
|
+
- **Side Effects**: Use the `Effect` hook to run side effects in response to
|
|
13
|
+
state changes.
|
|
14
|
+
- **JSX-style Syntax**: Write UI components using a simple, declarative
|
|
15
|
+
JSX-style syntax.
|
|
16
|
+
- **Reactivity**: Automatically re-render components when state changes.
|
|
10
17
|
|
|
11
|
-
##
|
|
18
|
+
## Installation
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Get started with a single command:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm create vilo@latest
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
This command will set up a new Olova project, similar to how Vite works. You can
|
|
22
|
-
use it to create an Olova template or install Olova in an existing project.
|
|
23
|
-
|
|
24
|
-
Alternatively, you can install Olova directly:
|
|
20
|
+
To get started with Olova, first install the core library via npm or yarn.
|
|
25
21
|
|
|
26
22
|
```bash
|
|
27
23
|
npm install olova
|
|
28
24
|
```
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
```jsx
|
|
33
|
-
import Olova from "olova";
|
|
34
|
-
|
|
35
|
-
const App = () => {
|
|
36
|
-
return (
|
|
37
|
-
<>
|
|
38
|
-
<h1>Welcome to Olova! 🎉</h1>
|
|
39
|
-
<p>Building web apps made simple.</p>
|
|
40
|
-
</>
|
|
41
|
-
);
|
|
42
|
-
};
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## 🎯 Core Concepts
|
|
26
|
+
or
|
|
46
27
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Olova provides several essential hooks for building dynamic applications:
|
|
50
|
-
|
|
51
|
-
```jsx
|
|
52
|
-
import {
|
|
53
|
-
$signal, // Manage component state
|
|
54
|
-
$effect, // Handle side effects
|
|
55
|
-
$memo, // Memoize values
|
|
56
|
-
$ref, // Reference DOM elements
|
|
57
|
-
$context, // Share data between components
|
|
58
|
-
$callback, // Memoize functions
|
|
59
|
-
} from "olova";
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Default Exports
|
|
63
|
-
|
|
64
|
-
Olova comes with two built-in utilities:
|
|
65
|
-
|
|
66
|
-
```jsx
|
|
67
|
-
import Olova, { h, Fragment } from "olova";
|
|
68
|
-
|
|
69
|
-
// h: Element generator (automatically used for JSX transformation)
|
|
70
|
-
// Fragment: Wrapper for multiple elements, can be used as <></> or <Fragment></Fragment>
|
|
28
|
+
```bash
|
|
29
|
+
yarn add olova
|
|
71
30
|
```
|
|
72
31
|
|
|
73
|
-
##
|
|
32
|
+
## Example Usage
|
|
74
33
|
|
|
75
|
-
|
|
34
|
+
Here is an example of a basic component in Olova:
|
|
76
35
|
|
|
77
|
-
```
|
|
78
|
-
import Olova, {
|
|
36
|
+
```js
|
|
37
|
+
import Olova, { State, Effect } from "olova";
|
|
79
38
|
|
|
80
|
-
|
|
39
|
+
export default function Home() {
|
|
81
40
|
const [count, setCount] = State(0);
|
|
82
41
|
|
|
83
|
-
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
|
|
84
|
-
};
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Side Effects
|
|
88
|
-
|
|
89
|
-
```jsx
|
|
90
|
-
import Olova, { $effect, $signal } from "olova";
|
|
91
|
-
|
|
92
|
-
const DataFetcher = () => {
|
|
93
|
-
const [data, setData] = State(null);
|
|
94
|
-
|
|
95
|
-
Effect(() => {
|
|
96
|
-
fetch("/api/data")
|
|
97
|
-
.then((res) => res.json())
|
|
98
|
-
.then(setData);
|
|
99
|
-
}, []); // Empty array means run once on mount
|
|
100
|
-
|
|
101
|
-
return <div>{data ? "Data loaded!" : "Loading..."}</div>;
|
|
102
|
-
};
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Using References
|
|
106
|
-
|
|
107
|
-
```jsx
|
|
108
|
-
import Olova, { $ref } from "olova";
|
|
109
|
-
|
|
110
|
-
const FocusInput = () => {
|
|
111
|
-
const inputRef = $ref(null);
|
|
112
|
-
|
|
113
|
-
return <input ref={inputRef} onFocus={() => console.log("Input focused!")} />;
|
|
114
|
-
};
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Memoization
|
|
118
|
-
|
|
119
|
-
```jsx
|
|
120
|
-
import Olova, { $memo, $signal } from "olova";
|
|
121
|
-
|
|
122
|
-
const ExpensiveComponent = ({ data }) => {
|
|
123
|
-
const processedData = Memo(() => {
|
|
124
|
-
// Expensive computation here
|
|
125
|
-
return data.map((item) => item * 2);
|
|
126
|
-
}, [data]);
|
|
127
|
-
|
|
128
|
-
return <div>{processedData.join(", ")}</div>;
|
|
129
|
-
};
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## 🎨 Styling in Olova
|
|
133
|
-
|
|
134
|
-
Olova supports all standard CSS approaches:
|
|
135
|
-
|
|
136
|
-
### CSS Imports
|
|
137
|
-
|
|
138
|
-
```jsx
|
|
139
|
-
import "./styles.css";
|
|
140
|
-
|
|
141
|
-
const StyledComponent = () => (
|
|
142
|
-
<div className="my-component">Styled content</div>
|
|
143
|
-
);
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### CSS Modules
|
|
147
|
-
|
|
148
|
-
```jsx
|
|
149
|
-
import styles from "./Component.module.css";
|
|
150
|
-
|
|
151
|
-
const ModuleStyledComponent = () => (
|
|
152
|
-
<div className={styles.container}>Module styled content</div>
|
|
153
|
-
);
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Inline Styles
|
|
157
|
-
|
|
158
|
-
```jsx
|
|
159
|
-
const InlineStyledComponent = () => (
|
|
160
|
-
<div style={{ padding: "20px", color: "blue" }}>Inline styled content</div>
|
|
161
|
-
);
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
## 🔮 How Olova Works
|
|
165
|
-
|
|
166
|
-
1. **Direct DOM Manipulation**
|
|
167
|
-
|
|
168
|
-
1. Olova directly updates the DOM without a Virtual DOM
|
|
169
|
-
1. Efficient updates when state changes
|
|
170
|
-
1. Lightweight and fast performance
|
|
171
|
-
|
|
172
|
-
1. **JSX Transformation**
|
|
173
|
-
|
|
174
|
-
1. Uses the `h` function to transform JSX into DOM elements
|
|
175
|
-
1. Handles event binding automatically
|
|
176
|
-
1. Manages component lifecycle efficiently
|
|
177
|
-
|
|
178
|
-
## 📚 Best Practices
|
|
179
|
-
|
|
180
|
-
### Component Structure
|
|
181
|
-
|
|
182
|
-
```jsx
|
|
183
|
-
// Good: Single responsibility component
|
|
184
|
-
const UserProfile = ({ user }) => (
|
|
185
|
-
<div className="profile">
|
|
186
|
-
<img src={user.avatar || "/placeholder.svg"} alt={user.name} />
|
|
187
|
-
<h2>{user.name}</h2>
|
|
188
|
-
</div>
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
// Better: Using Fragment for multiple elements
|
|
192
|
-
const UserCard = ({ user }) => (
|
|
193
|
-
<>
|
|
194
|
-
<UserProfile user={user} />
|
|
195
|
-
<UserStats stats={user.stats} />
|
|
196
|
-
</>
|
|
197
|
-
);
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### Hook Usage
|
|
201
|
-
|
|
202
|
-
```jsx
|
|
203
|
-
// Effective use of multiple hooks
|
|
204
|
-
const UserDashboard = () => {
|
|
205
|
-
const [user, setUser] = State(null);
|
|
206
|
-
const userCache = $ref({});
|
|
207
|
-
|
|
208
42
|
Effect(() => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
};
|
|
213
|
-
}, []);
|
|
214
|
-
|
|
215
|
-
return <div>Dashboard Content</div>;
|
|
216
|
-
};
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
## 🚀 Coming Soon
|
|
220
|
-
|
|
221
|
-
- More built-in hooks
|
|
222
|
-
- Enhanced development tools
|
|
223
|
-
- Additional utility functions
|
|
224
|
-
- Performance optimizations
|
|
225
|
-
|
|
226
|
-
## 🤝 Contributing
|
|
227
|
-
|
|
228
|
-
We welcome contributions! Whether it's:
|
|
229
|
-
|
|
230
|
-
- Bug reports
|
|
231
|
-
- Feature requests
|
|
232
|
-
- Documentation improvements
|
|
233
|
-
- Pull requests
|
|
234
|
-
|
|
235
|
-
## 📖 Examples
|
|
236
|
-
|
|
237
|
-
Find more examples in our [GitHub repository](https://github.com/olovajs/olova).
|
|
238
|
-
|
|
239
|
-
## 🛠 Using Vilo
|
|
43
|
+
console.log("Home is rendered");
|
|
44
|
+
console.log(count);
|
|
45
|
+
}, [count]);
|
|
240
46
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
cd my-olova-app
|
|
249
|
-
npm install
|
|
250
|
-
npm run dev
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<h1>{count}</h1>
|
|
50
|
+
<button onClick={() => setCount(count + 1)}>Increment</button>
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
251
54
|
```
|
|
252
|
-
|
|
253
|
-
This will set up a new Olova project with a basic template, ready for you to
|
|
254
|
-
start developing.
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
Made with simplicity in mind 🌟
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default function e(e, r) {
|
|
2
|
+
const n = e.getCurrentInstance(),
|
|
3
|
+
o = n.currentHook++;
|
|
4
|
+
if (!n.hooks[o]) {
|
|
5
|
+
n.hooks[o] = r._currentValue;
|
|
6
|
+
const e = { instance: n, hookIndex: o, version: r._version };
|
|
7
|
+
r._subscribers.add(e),
|
|
8
|
+
n.contextSubscriptions.add(() => {
|
|
9
|
+
r._subscribers.delete(e);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
const s = Array.from(r._subscribers).find(
|
|
13
|
+
(e) => e.instance === n && e.hookIndex === o
|
|
14
|
+
);
|
|
15
|
+
return (
|
|
16
|
+
s &&
|
|
17
|
+
s.version !== r._version &&
|
|
18
|
+
((n.hooks[o] = r._currentValue), (s.version = r._version)),
|
|
19
|
+
r._currentValue
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default function e(e, n, t) {
|
|
2
|
+
const o = e.getCurrentInstance(),
|
|
3
|
+
s = o.currentHook++,
|
|
4
|
+
c = o.hooks[s];
|
|
5
|
+
(!c || !t || t.length !== c.length || t.some((e, n) => e !== c[n])) &&
|
|
6
|
+
o.pendingEffects.push(() => {
|
|
7
|
+
o.cleanups.has(s) && o.cleanups.get(s)();
|
|
8
|
+
const e = n();
|
|
9
|
+
(o.hooks[s] = t), "function" == typeof e && o.cleanups.set(s, e);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default function o(o, t, n) {
|
|
2
|
+
const e = o.getCurrentInstance(),
|
|
3
|
+
r = e.currentHook++,
|
|
4
|
+
[s, u] = e.hooks[r] || [void 0, void 0];
|
|
5
|
+
if (
|
|
6
|
+
!u ||
|
|
7
|
+
!n ||
|
|
8
|
+
n.length !== u.length ||
|
|
9
|
+
n.some((t, n) => !o.shallowEqual(t, u[n]))
|
|
10
|
+
) {
|
|
11
|
+
const o = t();
|
|
12
|
+
return (e.hooks[r] = [o, n]), o;
|
|
13
|
+
}
|
|
14
|
+
return s;
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default function e(e, o) {
|
|
2
|
+
const t = e.getCurrentInstance(),
|
|
3
|
+
s = t.currentHook++;
|
|
4
|
+
return (
|
|
5
|
+
t.hooks[s] ||
|
|
6
|
+
(t.hooks[s] = {
|
|
7
|
+
value: o,
|
|
8
|
+
setValue: (o) => {
|
|
9
|
+
const n = "function" == typeof o ? o(t.hooks[s].value) : o;
|
|
10
|
+
n !== t.hooks[s].value &&
|
|
11
|
+
((t.hooks[s].value = n),
|
|
12
|
+
(e.dirtyInstances = e.dirtyInstances || new Set()),
|
|
13
|
+
e.dirtyInstances.add(t),
|
|
14
|
+
e.scheduleUpdate());
|
|
15
|
+
},
|
|
16
|
+
}),
|
|
17
|
+
[t.hooks[s].value, t.hooks[s].setValue]
|
|
18
|
+
);
|
|
19
|
+
}
|
package/dist/olova.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const memoize=(e,{resolver:t=((...e)=>JSON.stringify(e))}={})=>{const r=new Map;return(...n)=>{const o=t(...n);return r.has(o)||r.set(o,e(...n)),r.get(o)}};let currentObserver=null,batchQueue=new Set,isBatching=!1;function batch(e){isBatching=!0,e(),isBatching=!1,batchQueue.forEach((e=>e())),batchQueue.clear()}const perf={enabled:!0,entries:{},wrap:(e,t)=>function(...r){if(!this.enabled)return e(...r);const n=performance.now(),o=e(...r);return this.entries[t]=(this.entries[t]||0)+(performance.now()-n),o}};function ErrorBoundary({fallback:e,children:t}){const[r,n]=$signal(null),o=t=>(console.error("Error caught by boundary:",t),n(t),e);try{if(r())return e;try{return t}catch(e){return o(e)}}catch(e){return o(e)}}function diffProps(e={},t={},r){if(!(r&&r instanceof Element))throw new TypeError("diffProps requires a valid DOM element");try{void 0!==t.className&&(t.class=t.className,delete t.className),void 0!==e.className&&(e.class=e.className,delete e.className);const n={remove:[],set:[],events:[],styles:[]};for(const[r,o]of Object.entries(e))r in t||n.remove.push([r,o]);for(const[r,o]of Object.entries(t))null!=o&&e[r]!==o&&(r.startsWith("on")?n.events.push([r,o,e[r]]):"style"===r?n.styles.push(o):n.set.push([r,o]));n.remove.forEach((([e,t])=>{e.startsWith("on")?r.removeEventListener(e.slice(2).toLowerCase(),t):"style"===e?r.style.cssText="":r.removeAttribute(e)})),n.events.forEach((([e,t,n])=>{if("function"!=typeof t)throw new TypeError(`Event handler for ${e} must be a function`);const o=e.slice(2).toLowerCase();n&&r.removeEventListener(o,n),r.addEventListener(o,t)})),n.styles.forEach((e=>{if("object"==typeof e)Object.assign(r.style,e);else{if("string"!=typeof e)throw new TypeError("style must be an object or string");r.style.cssText=e}})),n.set.forEach((([e,t])=>{if("ref"===e)if(t&&"object"==typeof t&&"current"in t)t.current=r;else{if("function"!=typeof t)throw new TypeError("ref must be a ref object or function");t(r)}else"boolean"==typeof t&&e in r?r[e]=t:r.setAttribute(e,t)}))}catch(e){throw new Error(`Failed to diff props: ${e.message}`)}}const diffChildren=perf.wrap(memoize(((e,t,r)=>{if(!(r&&r instanceof Node))throw new TypeError("diffChildren requires a valid parent node");try{const n=Array.isArray(e)?e.flat():[e],o=Array.isArray(t)?t.flat():[t],s=new Map,i=new Map,a=new Set,c=[];n.forEach(((e,t)=>{e?.key&&s.set(e.key,{node:e,index:t,originalIndex:t})})),o.forEach(((e,t)=>{e?.key&&(a.add(e.key),i.set(e.key,{node:e,index:t,originalIndex:s.get(e.key)?.originalIndex??-1}))})),n.forEach(((e,t)=>{e?.key&&!a.has(e.key)&&r.removeChild(e)}));let l=0;o.forEach(((e,t)=>{const r=e?.key;if(!r)return;const n=s.get(r);i.get(r);n?(n.index<l&&c.push({node:n.node,index:t,type:"move"}),l=Math.max(l,n.index)):c.push({node:e,index:t,type:"insert"})})),c.sort(((e,t)=>e.index-t.index)),c.forEach((({node:e,index:t,type:n})=>{if("move"===n)r.insertBefore(e,r.childNodes[t]||null);else{const n=e instanceof Node?e:document.createTextNode(String(e));r.insertBefore(n,r.childNodes[t]||null)}}));for(let e=0;e<o.length;e++){const t=o[e],n=r.childNodes[e];if(n)if(t){if(n instanceof Element&&t instanceof Element)n.nodeType!==t.nodeType||n.nodeName!==t.nodeName?r.replaceChild(t,n):(diffProps(n.attributes,t.attributes,n),diffChildren(Array.from(n.childNodes),Array.from(t.childNodes),n));else if(n.nodeType===Node.TEXT_NODE&&"object"!=typeof t)n.textContent=String(t);else if(n!==t){const e=t instanceof Node?t:document.createTextNode(String(t));r.replaceChild(e,n)}}else r.removeChild(n);else{const e=t instanceof Node?t:document.createTextNode(String(t));r.appendChild(e)}}for(;r.childNodes.length>o.length;)r.removeChild(r.lastChild)}catch(e){throw new Error(`Failed to diff children: ${e.message}`)}})),"diffChildren");class Signal{constructor(e){this._value=e,this.observers=new Map,this.pending=new Set,this.isBatching=!1,this.isLoading=!1,this.error=null}get value(){return currentObserver&&(this.observers.has("_root")||this.observers.set("_root",new Set),this.observers.get("_root").add(currentObserver)),this._value}set value(e){try{if(this._value===e)return;const t=this._value;if(e instanceof Promise)return this.isLoading=!0,this.error=null,this._value=void 0,this.notifyObservers(),void e.then((e=>(this.isLoading=!1,this._value=e,this.notifyObservers(),e))).catch((e=>{throw this.isLoading=!1,this.error=e,this._value=void 0,this.notifyObservers(),e}));if(this._value=e,this.observers.has("_root")){const e=this.observers.get("_root");for(const t of e)t&&this.pending.add(t)}if("object"==typeof t&&"object"==typeof e){const r=new Set([...Object.keys(t),...Object.keys(e)]);for(const n of r)if(t[n]!==e[n]&&this.observers.has(n))for(const e of this.observers.get(n))this.pending.add(e)}this.batchUpdate()}catch(e){throw new Error(`Failed to update signal value: ${e.message}`)}}notifyObservers(){if(this.observers.has("_root")){const e=this.observers.get("_root");for(const t of e)t&&this.pending.add(t)}this.batchUpdate()}batchUpdate(){isBatching?batchQueue.add((()=>this.runObservers())):this.isBatching||(this.isBatching=!0,Promise.resolve().then((()=>{try{this.pending.forEach((e=>{try{e()}catch(e){console.error(`Observer execution failed: ${e.message}`)}})),this.pending.clear()}catch(e){console.error(`Batch update failed: ${e.message}`)}finally{this.isBatching=!1}})))}runObservers(){try{this.pending.forEach((e=>{try{e()}catch(e){console.error(`Observer execution failed: ${e.message}`)}})),this.pending.clear()}catch(e){console.error(`Run observers failed: ${e.message}`)}}observe(e,t){if("function"!=typeof t)throw new TypeError("Observer must be a function");this.observers.has(e)||this.observers.set(e,new Set),this.observers.get(e).add(t)}unobserve(e,t){this.observers.has(e)&&this.observers.get(e).delete(t)}cleanup(){try{this.observers.clear(),this.pending.clear(),this._value=void 0,this.isLoading=!1,this.error=null}catch(e){throw new Error(`Failed to cleanup signal: ${e.message}`)}}}function $signal(e,t={}){if(t&&"object"!=typeof t)throw new TypeError("Options must be an object");const{equals:r=((e,t)=>e===t),persistence:n,lazy:o=!1}=t;try{const t=new Signal(o?()=>e:"function"==typeof e?e():e),s=()=>(o&&!t.initialized&&(t.value="function"==typeof e?e():e,t.initialized=!0),t.value);s.loading=()=>t.isLoading,s.error=()=>t.error,s.peek=()=>t._value,s.toString=()=>t.value,s.toJSON=()=>t.value,s.derive=(e,r={})=>{if("function"!=typeof e)throw new TypeError("Derive requires a function");const n=$signal((()=>e(t.value)),r);return t.observe("_derived",(()=>n[1](e(t.value)))),n},s.subscribe=(e,r={})=>{if("function"!=typeof e)throw new TypeError("Subscribe requires a callback function");const{immediate:n=!0,once:o=!1,filter:s}=r,i=()=>{const r=t.value;s&&!s(r)||(e({value:r,loading:t.isLoading,error:t.error}),o&&a())},a=()=>t.unobserve("_root",i);return t.observe("_root",i),n&&i(),a},s.observe=(e,r)=>t.observe(e,r),s.unobserve=(e,r)=>t.unobserve(e,r),s.effect=(e,t={})=>{if("function"!=typeof e)throw new TypeError("Effect requires a function");let r;return s.subscribe((({value:t,loading:n,error:o})=>{r&&r(),r=e(t,{loading:n,error:o})}),t)};const i=e=>{try{return"function"==typeof e?i(e(t.value)):e instanceof Promise?(t.isLoading=!0,t.error=null,e.then((e=>(n&&n.save(e),t.isLoading=!1,t.value=e,e))).catch((e=>{throw t.isLoading=!1,t.error=e,e}))):(r(t.value,e)||(n&&n.save(e),t.value=e),t.value)}catch(e){throw new Error(`Failed to set signal value: ${e.message}`)}};if(i.reset=()=>{try{t.value="function"==typeof e?e():e,n&&n.remove()}catch(e){throw new Error(`Failed to reset signal: ${e.message}`)}},i.transaction=e=>{if("function"!=typeof e)throw new TypeError("Transaction requires a function");const r=t.value;try{t.isBatching=!0;const n=e(r);return t.value=n,n}catch(e){throw t.value=r,e}finally{t.isBatching=!1,t.batchUpdate()}},i.batch=e=>{if(!Array.isArray(e))throw new TypeError("Batch updates must be an array");return i.transaction((()=>e.reduce(((e,t)=>"function"==typeof t?t(e):t),t.value)))},n)try{const e=n.load();void 0!==e&&(t.value=e)}catch(e){console.error(`Failed to load persisted value: ${e.message}`)}return"development"===process.env.NODE_ENV&&(s.debug={listeners:()=>[...t.observers.entries()],dependencies:()=>t.dependencies,trace:()=>console.log("Signal trace:",{value:t.value,loading:t.isLoading,error:t.error,observers:t.observers})}),[s,i]}catch(e){throw new Error(`Failed to create signal: ${e.message}`)}}function createPersistence(e,t=localStorage){if(!e||"string"!=typeof e)throw new TypeError("Persistence requires a valid string key");return{save:r=>{try{t.setItem(e,JSON.stringify(r))}catch(e){throw new Error(`Failed to save to storage: ${e.message}`)}},load:()=>{try{return JSON.parse(t.getItem(e))}catch(e){throw new Error(`Failed to load from storage: ${e.message}`)}},remove:()=>{try{t.removeItem(e)}catch(e){throw new Error(`Failed to remove from storage: ${e.message}`)}},listen:t=>{const r=r=>{r.key===e&&t(r.newValue)};return window.addEventListener("storage",r),()=>window.removeEventListener("storage",r)}}}function $effect(e,t){if("function"!=typeof e)throw new TypeError("Effect requires a function");let r,n=!0,o=null;const s=()=>{try{"function"==typeof r&&(r(),r=void 0),currentObserver=o;const t=e();currentObserver=null,"function"==typeof t&&(r=t)}catch(e){throw new Error(`Effect execution failed: ${e.message}`)}};return o=()=>{if(n||!t)return s(),void(n=!1);Array.isArray(t)&&0===t.length?n&&(s(),n=!1):s()},o(),()=>{if(r)try{r()}catch(e){console.error(`Effect cleanup failed: ${e.message}`)}}}function $memo(e){if("function"!=typeof e)throw new TypeError("Memo requires a function");try{const[t,r]=$signal(e());return $effect((()=>r(e()))),t}catch(e){throw new Error(`Failed to create memo: ${e.message}`)}}function $ref(e){try{const[t,r]=$signal({current:e,toString(){return this.current},valueOf(){return this.current}});return{get current(){return t().current},set current(e){r((t=>t.current===e?t:{...t,current:e}))},toString(){return this.current.toString()},valueOf(){return this.current}}}catch(e){throw new Error(`Failed to create ref: ${e.message}`)}}function h(e,t,...r){try{if(e===Fragment||Array.isArray(e)){const n=document.createDocumentFragment(),o=e===Fragment?t?.children||r:e;return Array.isArray(o)&&o.flat().forEach((e=>{null!=e&&n.appendChild(e instanceof Node?e:document.createTextNode(String(e)))})),n}if(!e)return null;const n="function"==typeof e?e(t):document.createElement(e);if("string"==typeof e&&n&&t&&diffProps({},t,n),r.length){const e=document.createDocumentFragment(),t=r=>{if(null!=r)if("function"==typeof r){const t=document.createTextNode("");$effect((()=>{try{const e=r();if(Array.isArray(e)){const r=document.createDocumentFragment();e.forEach((e=>{r.appendChild(e instanceof Node?e:document.createTextNode(String(e)))})),t.parentNode&&t.parentNode.replaceChild(r,t)}else t.textContent=String(e)}catch(e){console.error(`Failed to render dynamic content: ${e.message}`),t.textContent=""}})),e.appendChild(t)}else r instanceof Node?e.appendChild(r):Array.isArray(r)?r.flat().forEach(t):e.appendChild(document.createTextNode(String(r)))};r.forEach(t),n.appendChild(e)}return n}catch(e){throw new Error(`Failed to create element: ${e.message}`)}}const Component=(e,t)=>{if("function"!=typeof e)throw new TypeError("Invalid Component: must be a function");try{return e(t)}catch(e){throw new Error(`Component rendering failed: ${e.message}`)}},Fragment=e=>e.children;window.Fragment=Fragment;const Olova={render(e,t){if(!(t&&t instanceof Element))throw new TypeError("Container must be a valid DOM element");try{const r="function"==typeof e?e():e;return t.firstChild?diffChildren([t.firstChild],[r],t):t.appendChild(r),r}catch(e){throw new Error(`Render failed: ${e.message}`)}},mount(e,t){return this.render(e,t)},unmount(e){if(!(e&&e instanceof Element))throw new TypeError("Container must be a valid DOM element");try{e.innerHTML=""}catch(e){throw new Error(`Unmount failed: ${e.message}`)}},Fragment:Fragment},contextStack=[];function $context(e){const t=Symbol("context");return{Provider:({value:e,children:r})=>{contextStack.push({id:t,value:e});const n=r;return contextStack.pop(),n},use:()=>{const r=contextStack.findLast((e=>e.id===t));return r?.value??e}}}function $callback(e,t){if("function"!=typeof e)throw new TypeError("Callback requires a function");try{const[r,n]=$signal((()=>({fn:e,deps:t,memoized:(...t)=>e(...t)})));return $effect((()=>{const o=r();t&&(o.deps&&t.length===o.deps.length&&!t.some(((e,t)=>e!==o.deps[t]))||n({fn:e,deps:t,memoized:(...t)=>e(...t)}))})),()=>r().memoized}catch(e){throw new Error(`Failed to create callback: ${e.message}`)}}export{$signal,$effect,$memo,$ref,$context,$callback,Component,h,Fragment,ErrorBoundary,batch};export default Olova;
|
|
1
|
+
import stateHook from"./hooks/state.js";import effectHook from"./hooks/effect.js";import memoHook from"./hooks/memo.js";import callbackHook from"./hooks/callback.js";import reducerHook from"./hooks/reducer.js";import refHook from"./hooks/ref.js";import contextHook from"./hooks/context.js";class Olovav2{constructor(){this.rootElement=null,this.components=new WeakMap,this.componentStack=[],this.componentInstances=new WeakMap,this.pendingUpdates=new Set,this.pendingEffects=[],this.contextSubscriptions=new WeakMap,this.isBatchingUpdates=!1,this.hasScheduledFlush=!1,this.dirtyInstances=new Set,this.virtualDOM=null,this.errorBoundaries=new WeakMap,this.batchQueue=new Set,this.isProcessingBatch=!1,this.debugEnabled=!1}createElement(e,t,...n){return null==e?(console.error("Element type cannot be null or undefined"),null):"function"==typeof e||"string"==typeof e?{type:e,props:{...t,children:this.flattenChildren(n)}}:(console.error("Invalid element type:",e),null)}flattenChildren(e){const t=[],n=[...e];for(;n.length;){const e=n.pop();Array.isArray(e)?n.push(...e):t.push(e)}return t.reverse()}render(e,t){try{if(null==e)return;const n=this.createVirtualDOM(e);this.virtualDOM?this.updateDOM(t,this.virtualDOM,n):(t.innerHTML="",t.appendChild(this.createDOM(n))),this.virtualDOM=n}catch(e){console.error("Render error:",e)}}createVirtualDOM(e){if("string"==typeof e||"number"==typeof e)return e;if(Array.isArray(e))return e.map((e=>this.createVirtualDOM(e)));if("function"==typeof e.type){const t=e.type,n=this.getComponentInstance(t);this.componentStack.push(n),n.currentHook=0,n.lastProps=e.props;try{const o=t(e.props);return n.lastResult=o,this.createVirtualDOM(o)}finally{this.componentStack.pop()}}return"string"!=typeof e.type?(console.error("Invalid element type:",e.type),null):{type:e.type,props:e.props,children:(e.props.children||[]).map((e=>this.createVirtualDOM(e)))}}createDOM(e){if("string"==typeof e||"number"==typeof e)return document.createTextNode(e);if(Array.isArray(e)){const t=document.createDocumentFragment();return e.forEach((e=>t.appendChild(this.createDOM(e)))),t}const t=document.createElement(e.type);return this.applyProps(t,e.props),e.children.forEach((e=>t.appendChild(this.createDOM(e)))),t}updateDOM(e,t,n){if(!e)return void this.debug("Container is undefined in updateDOM");if(t===n)return;if("string"==typeof t||"number"==typeof t)return void(t!==n&&e.textContent!==String(n)&&(e.textContent=n));if(Array.isArray(t)&&Array.isArray(n))return void this.reconcileChildren(e,t,n);if(t?.type!==n?.type){const t=this.createDOM(n);return void e.parentNode?.replaceChild(t,e)}t?.props!==n?.props&&this.updateProps(e,t?.props||{},n?.props||{});const o=t?.children||[],r=n?.children||[];this.updateChildren(e,o,r)}reconcileChildren(e,t,n){const o=new Map,r=new Map;t.forEach(((e,t)=>{e?.props?.key&&o.set(e.props.key,t)})),n.forEach(((e,t)=>{e?.props?.key&&r.set(e.props.key,t)}));const s=[];n.forEach(((e,t)=>{const n=e?.props?.key;if(n&&o.has(n)){const e=o.get(n);e!==t&&s.push({from:e,to:t})}})),s.forEach((({from:t,to:n})=>{const o=e.childNodes[t],r=e.childNodes[n];e.insertBefore(o,r)})),this.updateChildNodes(e,t,n)}updateChildNodes(e,t,n){const o=Math.max(t.length,n.length);for(let r=0;r<o;r++){const o=e.childNodes[r];!o&&n[r]?e.appendChild(this.createDOM(n[r])):n[r]?this.updateDOM(o,t[r],n[r]):e.removeChild(o)}}updateProps(e,t,n){Object.keys(t).forEach((t=>{if(!(t in n))if(t.startsWith("on")){const n=t.toLowerCase().substring(2);e.removeEventListener(n,e[`_${n}`]),delete e[`_${n}`]}else"children"!==t&&(e[t]="")})),Object.keys(n).forEach((o=>{n[o]!==t[o]&&this.applyProp(e,o,n[o])}))}applyProp(e,t,n){if("ref"===t)n.current=e;else if(t.startsWith("on")){const o=t.toLowerCase().substring(2),r=e[`_${o}`];r&&e.removeEventListener(o,r);const s=n;e[`_${o}`]=s,e.addEventListener(o,s)}else"children"!==t&&("className"===t?e.setAttribute("class",n):e[t]=n)}updateChildren(e,t,n){const o=Math.max(t.length,n.length);for(let r=0;r<o;r++){const o=e.childNodes[r];!o&&n[r]?e.appendChild(this.createDOM(n[r])):n[r]?this.updateDOM(o,t[r],n[r]):e.removeChild(o)}}applyProps(e,t){Object.keys(t||{}).forEach((n=>{if("ref"===n&&t[n])t[n].current=e;else if(n.startsWith("on")){const o=n.toLowerCase().substring(2),r=e[`_${o}`];r&&e.removeEventListener(o,r);const s=e=>{t[n](e)};e[`_${o}`]=s,e.addEventListener(o,s)}else"children"!==n&&("className"===n?e.setAttribute("class",t[n]):e[n]=t[n])}))}getComponentInstance(e){return this.componentInstances.has(e)||this.componentInstances.set(e,{hooks:[],currentHook:0,effects:[],cleanups:new Map,pendingEffects:[],contextSubscriptions:new Set,lastProps:null,lastResult:null}),this.componentInstances.get(e)}renderComponent(e,t){const n=this.getComponentInstance(e);this.componentStack.push(n);try{n.currentHook=0;const o=e();n.lastResult=o;this.createVirtualDOM(o);return t&&this.render(o,t),n.pendingEffects.length>0&&(this.pendingEffects.push(...n.pendingEffects),n.pendingEffects=[]),o}finally{this.componentStack.pop()}}shouldComponentUpdate(e,t){return!t.__isMemoized||!e.lastProps||!this.deepEqual(e.lastProps,t.props)}getCurrentInstance(){return this.componentStack[this.componentStack.length-1]||null}createContext(e){const t={_currentValue:e,_defaultValue:e,_subscribers:new Set,_version:0,Provider:({value:e,children:n})=>{const o=this.getCurrentInstance(),r=o.currentHook++,s=o.hooks[r];return this.shallowEqual(s,e)||(t._currentValue=e,t._version++,t._subscribers.forEach((t=>{t.instance.hooks[t.hookIndex]=e,this.scheduleUpdate()}))),o.hooks[r]=e,n},Consumer:({children:e})=>{if("function"!=typeof e)throw new Error("Context.Consumer expects a function as a child");return e(t._currentValue)}};return t}scheduleUpdate(){this.isBatchingUpdates||(this.isBatchingUpdates=!0,queueMicrotask((()=>{if(this.rootElement){const e=this.components.get(this.rootElement);if(e){const t=this.createVirtualDOM(this.renderComponent(e));this.updateDOM(this.rootElement,this.virtualDOM,t),this.virtualDOM=t}}for(;this.pendingEffects.length>0;){this.pendingEffects.shift()()}this.isBatchingUpdates=!1})))}findComponentByInstance(e){for(const[t,n]of this.components.entries())if(this.componentInstances.get(n)===e)return n;return null}flushUpdates(){this.pendingUpdates.forEach((e=>e())),this.pendingUpdates.clear(),this.hasScheduledFlush=!1}mount(e,t){for(this.rootElement=t,this.components.set(t,e),this.renderComponent(e,t);this.pendingEffects.length>0;){this.pendingEffects.shift()()}}unmount(e){const t=this.components.get(e);if(t){const n=this.componentInstances.get(t);n&&(n.contextSubscriptions.forEach((e=>e())),n.contextSubscriptions.clear(),n.cleanups.forEach((e=>e())),n.cleanups.clear(),this.componentInstances.delete(t)),this.components.delete(e)}e.innerHTML=""}memo(e){const t=t=>{const n=this.getCurrentInstance();if(!n)return e(t);const o=n.currentHook++,r=n.hooks[o]||{props:null,result:null},s=!r.props||!this.deepEqual(t,r.props);if(!r.result||s){const r=e(t);return n.hooks[o]={props:t,result:r},r}return r.result};return t.__isMemoized=!0,t.__original=e,t}shallowEqual(e,t){if(e===t)return!0;if(!e||!t)return!1;if("object"!=typeof e||"object"!=typeof t)return e===t;const n=Object.keys(e),o=Object.keys(t);return n.length===o.length&&n.every((n=>t.hasOwnProperty(n)&&e[n]===t[n]))}deepEqual(e,t){if(e===t)return!0;if(!e||!t)return!1;if(typeof e!=typeof t)return!1;if("object"!=typeof e)return e===t;if(Array.isArray(e))return!(!Array.isArray(t)||e.length!==t.length)&&e.every(((e,n)=>this.deepEqual(e,t[n])));const n=Object.keys(e),o=Object.keys(t);return n.length===o.length&&n.every((n=>t.hasOwnProperty(n)&&this.deepEqual(e[n],t[n])))}findComponentElement(e){const t=document.querySelectorAll("*");return Array.from(t).find((t=>{const n=this.components.get(t);return n&&this.componentInstances.get(n)===e}))||null}setState(e){this.getCurrentInstance()&&this.scheduleUpdate()}setErrorBoundary(e,t){this.errorBoundaries.set(e,t)}handleError(e,t){let n=t;for(;n;){const t=this.errorBoundaries.get(n);if(t)return void t(e);n=this.findParentComponent(n)}console.error("Unhandled error:",e)}batchUpdates(e){const t=this.isBatchingUpdates;this.isBatchingUpdates=!0;try{e()}finally{t||(this.isBatchingUpdates=!1,this.flushBatchQueue())}}flushBatchQueue(){if(!this.isProcessingBatch){this.isProcessingBatch=!0;try{for(const e of this.batchQueue)e();this.batchQueue.clear()}finally{this.isProcessingBatch=!1}}}componentDidMount(e){e.onComponentDidMount&&e.onComponentDidMount()}componentWillUnmount(e){e.onComponentWillUnmount&&e.onComponentWillUnmount()}debug(...e){this.debugEnabled&&console.log("[Olova Debug]:",...e)}enableDebug(){this.debugEnabled=!0}disableDebug(){this.debugEnabled=!1}}const Olova=new Olovav2;export const h=Olova.createElement.bind(Olova);export const Fragment=e=>e?e.children:null;export const $state=e=>stateHook(Olova,e);export const $effect=(e,t)=>effectHook(Olova,e,t);export const $memo=(e,t)=>memoHook(Olova,e,t);export const $callback=(e,t)=>callbackHook(Olova,e,t);export const $reducer=(e,t)=>reducerHook(Olova,e,t);export const $ref=e=>refHook(Olova,e);export const $context=e=>contextHook(Olova,e);export const createContext=Olova.createContext.bind(Olova);export const memo=Olova.memo.bind(Olova);export default Olova;function isFunctionComponent(e){return"function"==typeof e}function renderComponent(e,t){try{return"function"==typeof e.type?e.type(e.props):"string"==typeof e?e:Array.isArray(e)?e.map((e=>renderComponent(e,t))):null==e?"":"object"==typeof e?e:String(e)}catch(e){return console.error("Error rendering component:",e),null}}export const enableDebug=Olova.enableDebug.bind(Olova);export const disableDebug=Olova.disableDebug.bind(Olova);export const setErrorBoundary=Olova.setErrorBoundary.bind(Olova);
|