ajo 0.1.8 → 0.1.9

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/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const{isArray:S,prototype:{slice:A}}=Array,{assign:w,setPrototypeOf:N,hasOwn:E}=Object,v=e=>typeof e!="string"&&typeof(e==null?void 0:e[Symbol.iterator])=="function",C=({children:e})=>e,F=function(e,t){const{length:i}=arguments;return(t??(t={})).nodeName=e,"children"in t||i<3||(t.children=i===3?arguments[2]:A.call(arguments,2)),t},g=(e,t,i=t.namespaceURI)=>{let o=t.firstChild;for(e of $(e)){let n=o;if(typeof e=="string"){for(;n&&n.nodeType!=3;)n=n.nextSibling;n?n.data!=e&&(n.data=e):n=document.createTextNode(e)}else if(e instanceof Node)n=e;else{const{nodeName:c,xmlns:u=c==="svg"?"http://www.w3.org/2000/svg":i,key:a,skip:r,memo:d,ref:m,children:p}=e;for(;n&&!(n.localName===c&&(n.$key??(n.$key=a))==a);)n=n.nextSibling;if(n??(n=w(document.createElementNS(u,c),{$key:a})),d==null||M(n.$memo,n.$memo=d)){const{$attrs:y}=n,x={},h={};for(const s in w({},y,e)){if(s.startsWith("arg:")){h[s.slice(4)]=e[s];continue}if(O.has(s))continue;const f=x[s]=e[s];f!==(y==null?void 0:y[s])&&(s.startsWith("set:")?n[s.slice(4)]=f:f==null||f===!1?n.removeAttribute(s):n.setAttribute(s,f===!0?"":f))}n.$attrs=x,r||g(p,n,u),typeof m=="function"&&(n.$ref=m)(n,h)}}n===o?o=o.nextSibling:q(t,n,o)}for(;o;){const n=o.nextSibling;o.nodeType===1&&k(o),t.removeChild(o),o=n}},$=function*(e,t={value:""},i=!0){for(e of v(e)?e:[e])if(!(e==null||typeof e=="boolean"))if(E(e,"nodeName")){const{value:o}=t,{nodeName:n}=e,c=typeof n;if(o&&(yield o,t.value=""),c==="function")if(n.constructor.name==="GeneratorFunction"){const{is:u=n.is??"div",ref:a}=e;e.ref=(r,d)=>r&&(r.$gen??(r.$gen=(new I(r,u),n)),w(r,{$ref:(m,p)=>(m??r.return(),typeof a=="function"&&a(m,p)),$args:d}).next()),e.skip=!0,e.nodeName=u,delete e.is,"children"in e&&(e["arg:children"]=e.children,delete e.children),yield e}else delete e.nodeName,yield*$(n(e),t,!1);else c==="string"&&(yield e)}else v(e)?yield*$(e,t,!1):t.value+=e;i&&t.value&&(yield t.value)},M=(e,t)=>S(e)&&S(t)?e.some((i,o)=>i!==t[o]):e!==t,O=new Set("nodeName,xmlns,key,skip,memo,ref,children".split(",")),q=(e,t,i)=>{if(t.contains(document.activeElement)){const o=t.nextSibling;for(;i&&i!=t;){const n=i.nextSibling;e.insertBefore(i,o),i=n}}else e.insertBefore(t,i)},k=({children:e,$ref:t})=>{for(const i of e)k(i);typeof t=="function"&&t(null)};class I{constructor(t,i){N(t,N(this.constructor.prototype,U(i,t.namespaceURI).prototype))}*[Symbol.iterator](){for(;;)yield this.$args}refresh(){j(this)}next(){try{g((this.$it??(this.$it=this.$gen.call(this,this.$args))).next().value,this),typeof this.$ref=="function"&&this.$ref(this)}catch(t){this.throw(t)}}throw(t){var i;for(let o=this;o;o=o.parentNode)if(typeof((i=o.$it)==null?void 0:i.throw)=="function")try{return g(o.$it.throw(t).value,o)}catch{}throw t}return(){var t;try{(t=this.$it)==null||t.return()}catch(i){this.throw(i)}finally{this.$it=null}}}let b,l,T;const U=(e,t)=>{let i=(b??(b=new Map)).get(e);return i||({constructor:i}=document.createElementNS(t,e),b.set(e,i===HTMLUnknownElement?i=HTMLElement:i)),i},j=e=>{(l??(l=new Set)).has(e)&&l.delete(e);for(const t of l){if(t.contains(e))return;e.contains(t)&&l.delete(t)}l.add(e),T??(T=requestAnimationFrame(B))},B=()=>{for(const e of l)e.isConnected&&e.next();l.clear(),T=null};exports.Fragment=C;exports.h=F;exports.render=g;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const{isArray:S,prototype:{slice:A}}=Array,{assign:w,setPrototypeOf:N,hasOwn:E}=Object,v=e=>typeof e!="string"&&typeof(e==null?void 0:e[Symbol.iterator])=="function",C=({children:e})=>e,F=function(e,t){const{length:i}=arguments;return(t??(t={})).nodeName=e,"children"in t||i<3||(t.children=i===3?arguments[2]:A.call(arguments,2)),t},g=(e,t,i=t.namespaceURI)=>{let o=t.firstChild;for(e of $(e)){let n=o;if(typeof e=="string"){for(;n&&n.nodeType!=3;)n=n.nextSibling;n?n.data!=e&&(n.data=e):n=document.createTextNode(e)}else if(e instanceof Node)n=e;else{const{nodeName:c,xmlns:u=c==="svg"?"http://www.w3.org/2000/svg":i,key:a,skip:s,memo:d,ref:m,children:p}=e;for(;n&&!(n.localName===c&&(n.$key??(n.$key=a))==a);)n=n.nextSibling;if(n??(n=w(document.createElementNS(u,c),{$key:a})),d==null||M(n.$memo,n.$memo=d)){const{$attrs:y}=n,x={},h={};for(const r in w({},y,e)){if(r.startsWith("arg:")){h[r.slice(4)]=e[r];continue}if(O.has(r))continue;const f=x[r]=e[r];f!==(y==null?void 0:y[r])&&(r.startsWith("set:")?n[r.slice(4)]=f:f==null||f===!1?n.removeAttribute(r):n.setAttribute(r,f===!0?"":f))}n.$attrs=x,s||g(p,n,u),typeof m=="function"&&(n.$ref=m)(n,h)}}n===o?o=o.nextSibling:q(t,n,o)}for(;o;){const n=o.nextSibling;o.nodeType===1&&k(o),t.removeChild(o),o=n}},$=function*(e,t={value:""},i=!0){for(e of v(e)?e:[e])if(!(e==null||typeof e=="boolean"))if(E(e,"nodeName")){const{value:o}=t,{nodeName:n}=e,c=typeof n;if(o&&(yield o,t.value=""),c==="function")if(n.constructor.name==="GeneratorFunction"){const{is:u=n.is??"div",ref:a}=e;e.ref=(s,d)=>s&&(s.$gen??(s.$gen=(new I(s,u),n)),w(s,{$ref:(m,p)=>(m??s.return(),typeof a=="function"&&a(m,p)),$args:d}).next()),e.skip=!0,e.nodeName=u,delete e.is,"children"in e&&(e["arg:children"]=e.children,delete e.children),yield e}else delete e.nodeName,yield*$(n(e),t,!1);else c==="string"&&(yield e)}else v(e)?yield*$(e,t,!1):t.value+=e;i&&t.value&&(yield t.value)},M=(e,t)=>S(e)&&S(t)?e.some((i,o)=>i!==t[o]):e!==t,O=new Set("nodeName,xmlns,key,skip,memo,ref,children".split(",")),q=(e,t,i)=>{if(t.contains(document.activeElement)){const o=t.nextSibling;for(;i&&i!=t;){const n=i.nextSibling;e.insertBefore(i,o),i=n}}else e.insertBefore(t,i)},k=({children:e,$ref:t})=>{for(const i of e)k(i);typeof t=="function"&&t(null)};class I{constructor(t,i){return N(t,N(this.constructor.prototype,U(i,t.namespaceURI).prototype))}*[Symbol.iterator](){for(;;)yield this.$args}refresh(){j(this)}next(){try{g((this.$it??(this.$it=this.$gen.call(this,this.$args))).next().value,this),typeof this.$ref=="function"&&this.$ref(this)}catch(t){this.throw(t)}}throw(t){var i;for(let o=this;o;o=o.parentNode)if(typeof((i=o.$it)==null?void 0:i.throw)=="function")try{return g(o.$it.throw(t).value,o)}catch{}throw t}return(){var t;try{(t=this.$it)==null||t.return()}catch(i){this.throw(i)}finally{this.$it=null}}}let b,l,T;const U=(e,t)=>{let i=(b??(b=new Map)).get(e);return i||({constructor:i}=document.createElementNS(t,e),b.set(e,i===HTMLUnknownElement?i=HTMLElement:i)),i},j=e=>{(l??(l=new Set)).has(e)&&l.delete(e);for(const t of l){if(t.contains(e))return;e.contains(t)&&l.delete(t)}l.add(e),T??(T=requestAnimationFrame(B))},B=()=>{for(const e of l)e.isConnected&&e.next();l.clear(),T=null};exports.Fragment=C;exports.h=F;exports.render=g;
package/dist/index.js CHANGED
@@ -68,7 +68,7 @@ const { isArray: N, prototype: { slice: E } } = Array, { assign: p, setPrototype
68
68
  };
69
69
  class I {
70
70
  constructor(t, i) {
71
- S(t, S(this.constructor.prototype, M(i, t.namespaceURI).prototype));
71
+ return S(t, S(this.constructor.prototype, M(i, t.namespaceURI).prototype));
72
72
  }
73
73
  *[Symbol.iterator]() {
74
74
  for (; ; )
package/license CHANGED
@@ -1,6 +1,6 @@
1
1
  ISC License
2
2
 
3
- Copyright (c) 2022, Cristian Falcone, @cristianfalcone
3
+ Copyright (c) 2023, Cristian Falcone
4
4
 
5
5
  Permission to use, copy, modify, and/or distribute this software for any
6
6
  purpose with or without fee is hereby granted, provided that the above
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ajo",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "ajo is a JavaScript view library for building user interfaces",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
@@ -19,8 +19,8 @@
19
19
  "dist"
20
20
  ],
21
21
  "devDependencies": {
22
- "jsdom": "^22.1.0",
23
- "vite": "^4.5.0",
22
+ "jsdom": "^23.0.1",
23
+ "vite": "^5.0.4",
24
24
  "vitest": "^0.34.6"
25
25
  },
26
26
  "keywords": [
package/readme.md CHANGED
@@ -1,13 +1,40 @@
1
- # ajo
2
- ajo is a JavaScript view library for building user interfaces
1
+ <div align="center">
2
+ <img src="https://github.com/cristianfalcone/ajo/raw/main/ajo.png" alt="ajo" width="372" />
3
+ </div>
3
4
 
4
- ## install
5
+ <br />
6
+
7
+ <div align="center">
8
+ <a href="https://npmjs.org/package/ajo">
9
+ <img src="https://badgen.now.sh/npm/v/ajo" alt="version" />
10
+ </a>
11
+ <a href="https://npmjs.org/package/ajo">
12
+ <img src="https://badgen.now.sh/npm/dm/ajo" alt="downloads" />
13
+ </a>
14
+ </div>
15
+
16
+ # Ajo
17
+
18
+ Ajo is a cutting-edge library designed for building dynamic UI components using JSX. Integrating ideas from Incremental DOM and Crank.js, Ajo offers a unique approach in the landscape of UI libraries.
19
+
20
+ Key features:
21
+
22
+ - **Efficient In-Place DOM Updating**: Ajo executes in-place DOM updates, directly applying the generated vDOM tree to the DOM. This approach avoids the need to store and diff a previous vDOM tree, leading to a reduced memory footprint.
23
+ - **Generator-Based State Management**: Leverages JavaScript Generators for managing component states and effects, offering developers a robust tool for controlling UI lifecycle events.
24
+ - **Minimalistic Rendering Approach**: Ajo’s rendering system is optimized for minimal overhead, enhancing the speed of DOM updates and overall application performance.
25
+ - **JSX Syntax for Intuitive Development**: Supports JSX, making it easy for developers familiar with React or similar libraries to adopt and use Ajo effectively.
26
+ - **Lifecycle Management for Components**: Provides a full suite of lifecycle methods for stateful components, facilitating precise control over component behaviors during their lifecycle.
27
+ - **Flexibility and Lightweight Design**: Ajo is designed to be both adaptable for various use cases and lightweight, ensuring minimal impact on project size.
28
+
29
+ ## Install
5
30
 
6
31
  ```sh
7
32
  npm install ajo
8
33
  ```
9
34
 
10
- ## render JSX into DOM
35
+ ## Usage
36
+
37
+ Render JSX into DOM element:
11
38
 
12
39
  ```jsx
13
40
  /** @jsx h */
@@ -18,7 +45,7 @@ document.body.innerHTML = '<div>Hello World</div>'
18
45
  render(<div>Goodbye World</div>, document.body)
19
46
  ```
20
47
 
21
- ## stateless component
48
+ Stateless component:
22
49
 
23
50
  ```jsx
24
51
  /** @jsx h */
@@ -29,30 +56,536 @@ const Greet = ({ name }) => <div>Hello {name}</div>
29
56
  render(<Greet name="World" />, document.body)
30
57
  ```
31
58
 
32
- ## stateful component
59
+ Stateful component:
60
+
61
+ ```jsx
62
+ /** @jsx h */
63
+ import { h, render } from 'ajo'
64
+
65
+ function* Counter() {
66
+
67
+ let count = 0
68
+
69
+ const handleClick = () => {
70
+ count++
71
+ this.refresh()
72
+ }
73
+
74
+ while (true) yield (
75
+ <button set:onclick={handleClick}>
76
+ Current: {count}
77
+ </button>
78
+ )
79
+ }
80
+
81
+ render(<Counter />, document.body)
82
+ ```
83
+
84
+ ## API
85
+
86
+ ### render(vnode, element, [namespace])
87
+
88
+ Renders a virtual DOM node (`vnode`) created by the `h` function into the actual DOM `element`. It is the primary method used for updating the DOM with new content.
89
+
90
+ When called, it efficiently updates the parent `element` with the new content, adding, updating, or removing DOM nodes as needed.
91
+
92
+ This function enables the declarative description of the UI to be transformed into actual UI elements in the browser. It's designed to be efficient, minimizing updates to the actual DOM to improve performance and user experience.
93
+
94
+ #### Parameters:
95
+
96
+ - **vnode** (Any): The virtual DOM node (created by `h`) to render. This can be any value, a simple string, a JSX element, or a more complex component.
97
+ - **element** (HTMLElement): The DOM element into which the `vnode` should be rendered. This is typically a container element in your application.
98
+ - **namespace** (String, optional): An optional XML namespace URI. If specified, it allows for the creation of elements within a certain XML namespace, useful for SVG elements and other XML-based documents.
99
+
100
+ #### Returns:
101
+
102
+ - There is no return value for this function as its primary purpose is side-effect (DOM manipulation).
103
+
104
+ #### Example Usage:
105
+
106
+ ```jsx
107
+ /** @jsx h */
108
+ import { h, render } from 'ajo'
109
+
110
+ // Create a simple, stateless component
111
+ const App = () => <div>Hello, World!</div>
112
+
113
+ // Render the App component into the #root element
114
+ render(<App />, document.getElementById('root'))
115
+ ```
116
+ In this example, the `App` component is a simple function returning a `div` with text content. The `render` function then mounts this component into the DOM element with the ID `root`.
117
+
118
+ ### h(name, [attributes], [...children])
119
+
120
+ Creates a virtual DOM element (vnode) for rendering. It's the core function for defining UI components in JSX syntax. The `h` function is a hyperscript function that returns a virtual DOM node representing the UI component. This object can then be rendered to the actual DOM using the `render` function.
121
+
122
+ #### Parameters:
123
+
124
+ - **name** (String | Function | Generator Function): The name of the tag for the DOM node you want to create. If it's a function, it's treated as a stateless component, and if it's a generator function, it's treated as a stateful component.
125
+ - **attributes** (Object, optional): An object containing the attributes you want to set on the element.
126
+ - **children** (Any, optional): Child nodes. Can be a nested array of children, a string, or any other renderable JSX elements. Booleans, null, and undefined children will be ignored, which is useful for conditional rendering.
127
+
128
+ #### Returns:
129
+
130
+ - **Object**: A virtual DOM node (vnode).
131
+
132
+ #### Example Usage:
33
133
 
34
134
  ```jsx
35
135
  /** @jsx h */
36
- import { h, render, component } from 'ajo'
136
+ import { h } from 'ajo'
137
+
138
+ // Creating a simple vnode
139
+ const myElement = h('div', { id: 'my-div' }, 'Hello World')
140
+
141
+ // or using JSX syntax
142
+ const myElementJSX = <div id="my-div">Hello World</div>
143
+
144
+ // Creating a vnode for a stateless component with children
145
+ const MyComponent = ({ class: className }) => h('div', { class: className },
146
+ h('h1', null, 'Header'),
147
+ 'Text Content',
148
+ h('p', null, 'Paragraph'),
149
+ )
150
+
151
+ // or using JSX syntax
152
+ const MyComponentJSX = ({ class: className }) => (
153
+ <div class={className}>
154
+ <h1>Header</h1>
155
+ Text Content
156
+ <p>Paragraph</p>
157
+ </div>
158
+ )
159
+
160
+ // Composing
161
+ const MyApp = () => h(MyComponent, { class: 'my-class' })
162
+
163
+ // or using JSX syntax
164
+ const MyAppJSX = () => <MyComponent class="my-class" />
165
+
166
+ // Render into a DOM element
167
+ render(h(MyApp), document.body)
168
+
169
+ // or using JSX syntax
170
+ render(<MyAppJSX />, document.body)
171
+ ```
172
+ You won't typically use the `h` function, it's automatically used when you write JSX code. The previous examples demonstrate how to use the `h` function directly if you need to.
173
+
174
+ ### `Fragment({ children })`
175
+
176
+ A utility component for grouping multiple elements without adding extra nodes to the DOM. It's particularly useful for returning multiple elements from components.
177
+
178
+ In JSX, fragments are typically represented with empty tags (`<>...</>`), but this function provides an alternative way to create them, especially useful in environments where the shorthand syntax might not be supported.
179
+
180
+ #### Example Usage:
181
+
182
+ ```jsx
183
+ //* @jsx h */
184
+ //* @jsxFrag Fragment */
185
+ import { h, Fragment } from 'ajo'
186
+
187
+ const MyComponent = () => {
188
+ return h(Fragment, null,
189
+ h('h1', null, 'Hello'),
190
+ h('h2', null, 'World')
191
+ )
192
+ }
193
+
194
+ // Or using JSX syntax
195
+ const MyComponentJSX = () => (
196
+ <>
197
+ <h1>Hello</h1>
198
+ <h2>World</h2>
199
+ </>
200
+ )
201
+ ```
202
+
203
+ ### `set:`
204
+
205
+ The `set:` prefix in Ajo allows you to directly set properties on DOM element nodes from within your JSX. This is distinct from simply setting attributes, as it interacts with the properties of the DOM elements, much like how you would in plain JavaScript.
206
+
207
+ #### Purpose:
208
+
209
+ - **Direct DOM Property Manipulation:** The `set:` prefix is used for directly setting properties on DOM nodes. This is crucial for cases where you need to interact with the DOM API, or when a property does not have a direct attribute equivalent.
210
+
211
+ #### Usage:
212
+
213
+ - **Versatile Property Assignment:** Use `set:` to assign various types of properties to DOM elements, including but not limited to event handlers. It can be used for properties like `textContent`, `scrollTop`, custom data properties, and more.
214
+ - **JavaScript-centric DOM Interaction:** Ideal for situations where setting a DOM property is more appropriate or efficient than setting an HTML attribute.
215
+
216
+ #### Examples:
217
+
218
+ **Assigning Text Content:**
219
+ ```jsx
220
+ function* MyComponent() {
221
+ const text = "Hello, Ajo!"
222
+ yield <div set:textContent={text} skip></div>
223
+ }
224
+ ```
225
+ Here, `set:textContent` directly sets the `textContent` property of the `div`'s DOM node. `skip` is used to prevent Ajo from overriding the `div`'s children.
226
+
227
+ **Setting inner HTML:**
228
+ ```jsx
229
+ function* MyComponent() {
230
+ const html = "<p>Hello, Ajo!</p>"
231
+ yield <div set:innerHTML={html} skip></div>
232
+ }
233
+ ```
234
+ In this case, `set:innerHTML` is used to set the `innerHTML` property of the `div`'s DOM node. `skip` is used to prevent Ajo from overriding the `div`'s children.
235
+
236
+ **Event Handlers (e.g., onclick):**
237
+ ```jsx
238
+ function* MyComponent() {
239
+ const handleClick = () => console.log('Clicked')
240
+ yield <button set:onclick={handleClick}>Click Me</button>
241
+ }
242
+ ```
243
+ `set:onclick` assigns the `handleClick` function as the click event listener for the button.
244
+
245
+ ### Special Attributes in Ajo
246
+
247
+ In Ajo, there are several special attributes (`key`, `skip`, `memo`, and `ref`) that have specific purposes and behaviors. Understanding these attributes is crucial for optimizing rendering and managing component lifecycle and references in your applications.
248
+
249
+ #### `key` Attribute:
250
+ - **Purpose:** The `key` attribute is used to track the identity of elements in lists or sequences. It's crucial for optimizing the rendering process, especially when dealing with dynamic lists where items can be added, removed, or reordered.
251
+ - **Behavior:** When a list of elements is rendered, Ajo uses the `key` attribute to efficiently update the DOM. It identifies which elements have changed, need to be added, or can be reused. This minimizes DOM manipulations, leading to better performance.
252
+ - **Example:** In a list of items rendered by a map function, each item should have a unique `key` prop, like `h('li', { key: item.id }, item.text)`.
253
+
254
+ #### `skip` Attribute:
255
+ - **Purpose:** The `skip` attribute is used to instruct Ajo to skip rendering for a specific element child nodes. It's useful for preventing certain parts of the DOM from being updated, like when using a third-party library that manipulates the DOM directly.
256
+ - **Behavior:** When `skip` is set to `true` on an element, Ajo will not render or update that element's child nodes.
257
+ - **Example:** `h('div', { skip: shouldSkip })` - here, if `shouldSkip` is `true`, Ajo will not render or update the `div`'s child nodes.
258
+
259
+ #### `memo` Attribute:
260
+ - **Purpose:** The `memo` attribute is used for memorization. It's a performance optimization technique to prevent unnecessary renders.
261
+ - **Behavior:** When the `memo` attribute is provided, Ajo will shallow compare the memoized values with the new ones. If they are the same, Ajo will skip rendering the element attributes and child nodes. For stateful components, this also prevents the component from re-rendering.
262
+ - **Example:** `h(div, { memo: [dependency1, dependency2] })` - the element (and all its child nodes) will re-render only if `dependency1` or `dependency2` change.
263
+
264
+ #### `ref` Attribute:
265
+ - **Purpose:** The `ref` attribute provides a way to access the underlying DOM node or component element instance.
266
+ - **Behavior:** When an element is mounted or updated, the `ref` callback is called with the DOM node or component instance as an argument. This allows you to store a reference to it for later use, such as focusing an input or measuring dimensions.
267
+ - **Example:** `h('input', { ref: node => (this.inputNode = node) })` - stores a reference to the input node.
268
+
269
+ These special attributes in Ajo offer powerful ways to manage rendering performance and interact with DOM elements and components elements directly. They provide developers with finer control over the update behavior and lifecycle of components in their applications.
270
+
271
+ ## Stateful components
272
+
273
+ Stateful components in Ajo are defined using generator functions. These components are designed with a minimalistic API for controlling rendering and state updates. They are equipped with several lifecycle methods that allow for advanced control over component behavior, error handling, and rendering processes.
274
+
275
+ The following example demonstrates all the key features of stateful components in Ajo:
276
+
277
+ ```jsx
278
+ function* ChatComponent({ userName = 'Anonymous', chatRoom }) { // Receive arguments initial values.
279
+
280
+ // Define mutable state variables.
281
+ let messageToSend = '', isConnected = false
282
+
283
+ // WebSocket connection setup.
284
+ const chatServerURL = `ws://chatserver.com/${chatRoom}`
285
+ const chatConnection = new WebSocket(chatServerURL)
286
+
287
+ // Define event handlers.
288
+ const handleMessageChange = event => {
289
+
290
+ messageToSend = event.target.value
291
+
292
+ // Render the updated messageToSend (synchronously)
293
+ this.next()
294
+ }
37
295
 
38
- function* Counter({ init = 0 }) {
296
+ const sendMessage = () => {
39
297
 
40
- let count = init
298
+ // Logic to send a message.
299
+ if (messageToSend) {
41
300
 
42
- const handleClick = () => {
43
- count++
44
- this.next()
45
- }
301
+ chatConnection.send(JSON.stringify({ user: this.$args.userName, message: messageToSend }))
46
302
 
47
- while (true) yield (
48
- <button set:onclick={handleClick}>
49
- Current: {count}
50
- </button>
51
- )
303
+ // Reset message input after sending.
304
+ messageToSend = ''
305
+
306
+ // Refresh to clear input field (asynchronously).
307
+ this.refresh()
308
+ }
309
+ }
310
+
311
+ const handleConnectionOpen = () => {
312
+
313
+ isConnected = true
314
+
315
+ // Refresh to update connection status.
316
+ this.refresh()
317
+ }
318
+
319
+ const handleConnectionError = error => {
320
+ this.throw(new Error('Connection error: ' + error.message))
321
+ }
322
+
323
+ // Attach WebSocket event listeners.
324
+ chatConnection.onopen = handleConnectionOpen
325
+ chatConnection.onerror = handleConnectionError
326
+
327
+ // 'this' is a DOM element, so we can add a class to it.
328
+ this.classList.add('chat-component')
329
+
330
+ try { // Optional try/finally block for cleanup logic.
331
+
332
+ for ({ userName } of this) { // Iterates over generator, optionally receiving updated arguments.
333
+
334
+ try { // Optional try/catch block for error handling.
335
+
336
+ // Compute derived values.
337
+ const statusMessage = isConnected ? `You are connected as ${userName}.` : "Connecting to chat..."
338
+
339
+ // Render the chat component UI.
340
+ // Use set: prefix to set properties on DOM nodes, like event handlers.
341
+ yield (
342
+ <>
343
+ <div class="status-message">{statusMessage}</div>
344
+ <div class="connection-status">{isConnected ? 'Connected' : 'Connecting...'}</div>
345
+ <input type="text" value={messageToSend} set:onchange={handleMessageChange} />
346
+ <button set:onclick={sendMessage}>Send</button>
347
+ </>
348
+ )
349
+ } catch (e) {
350
+ // Handle any errors that occur during rendering or state updates.
351
+ yield <pre>Error: {e.message}</pre>
352
+ }
353
+ }
354
+ } finally {
355
+ // Cleanup logic: close WebSocket connection.
356
+ chatConnection.close()
357
+ }
358
+ }
359
+ ```
360
+
361
+ ## Lifecycle methods
362
+
363
+ Stateful components in Ajo are equipped with several methods that allow for advanced control over component behavior, error handling, and rendering processes. These methods are called lifecycle methods and are invoked at different stages of the component's lifecycle.
364
+
365
+ ### `this.refresh()`
366
+
367
+ The `refresh` method is used to asynchronously trigger a re-render of a stateful component in Ajo. It schedules a render using `requestAnimationFrame`, ensuring that the rendering aligns with the browser's paint cycle.
368
+
369
+ #### Purpose:
370
+
371
+ - **Asynchronous Rendering:** `this.refresh()` queues a render of the component in the next animation frame, making it asynchronous.
372
+ - **Single Render:** If called multiple times before the browser paints, `this.refresh()` schedules only one render, ensuring that the component is rendered only once.
373
+
374
+ #### Usage:
375
+
376
+ - **For Performance Optimization:** Ideal in scenarios where multiple state updates occur in quick succession.
377
+ - **In Event Handlers and Async Operations:** Useful in event handlers or after asynchronous operations where you need to update the UI in response to changes.
378
+
379
+ #### Example:
380
+
381
+ ```jsx
382
+ function* DataFetcher() {
383
+
384
+ let data = null
385
+
386
+ const fetchData = async () => {
387
+
388
+ data = await fetchSomeData()
389
+
390
+ // Queue a re-render to update the component with the fetched data:
391
+ this.refresh()
392
+ }
393
+
394
+ while (true) {
395
+ yield (
396
+ <div>
397
+ <button set:onclick={fetchData}>Fetch Data</button>
398
+ {data && <DisplayData data={data} />}
399
+ </div>
400
+ )
401
+ }
402
+ }
403
+ ```
404
+ In this example, `DataFetcher` uses `this.refresh()` to update its display after data is fetched. The use of `this.refresh()` ensures that the rendering is efficient and aligned with the browser's rendering cycle.
405
+
406
+ ### `this.next()`
407
+
408
+ The `next` method is used within stateful components in Ajo to manually advance the component's generator function to its next yield point. This method is crucial for synchronously rendering the next state of the component.
409
+
410
+ #### Purpose:
411
+
412
+ - **Synchronous Rendering:** `this.next()` is used to immediately render the next state of the component. It advances the generator function to the next yield, reflecting any changes in state or props right away.
413
+
414
+ #### Usage:
415
+
416
+ - **In Response to State Changes:** Typically, `this.next()` is called in scenarios where the component's state has changed and an immediate update to the DOM is required.
417
+ - **For Controlled Updates:** It allows for more controlled and predictable updates, as it bypasses the asynchronous rendering cycle from `this.refresh()`.
418
+
419
+ #### Example:
420
+
421
+ ```jsx
422
+ function* Counter() {
423
+
424
+ let count = 0
425
+
426
+ const increment = () => {
427
+
428
+ count++
429
+
430
+ // Immediately render the updated count
431
+ this.next()
432
+ }
433
+
434
+ while (true) {
435
+ yield <button set:onclick={increment}>{count}</button>
436
+ }
437
+ }
438
+ ```
439
+ In this example, `Counter` uses `this.next()` in its `increment` function to immediately render the updated count whenever the button is clicked.
440
+
441
+ ### `this.throw()`
442
+
443
+ The `throw` method in Ajo stateful components is designed for error propagation within the component hierarchy. It allows developers to throw errors from a child component to be caught and handled by itself or a parent component, facilitating a structured approach to error management.
444
+
445
+ #### Purpose:
446
+
447
+ - **Error Propagation:** `this.throw()` is used to send errors from the current component up to its parents component, akin to creating an error boundary.
448
+
449
+ #### Usage:
450
+
451
+ - **Handling Uncaught Exceptions:** Typically used within event handlers or asynchronous operations where errors might occur. Instead of handling these errors locally within the component, `this.throw()` sends them to the parent component for a more centralized handling approach.
452
+ - **Creating Error Boundaries:** Useful in scenarios where a parent component is designed to handle errors from its child components, maintaining separation of concerns and cleaner code.
453
+
454
+ #### Example:
455
+
456
+ ```jsx
457
+ function* ChildComponent() {
458
+
459
+ const handleErrorProneOperation = async () => {
460
+ try {
461
+
462
+ // operation that might throw an error
463
+ await doSomething()
464
+
465
+ } catch (err) {
466
+
467
+ // Propagate error to parent component
468
+ this.throw(err)
469
+ }
470
+ }
471
+
472
+ while (true) {
473
+ yield <button set:onclick={handleErrorProneOperation}>Click Me</button>
474
+ }
475
+ }
476
+
477
+ function* ParentComponent() {
478
+ while (true) {
479
+ try {
480
+ yield <ChildComponent />
481
+ } catch (err) {
482
+ yield <div>Error: {err.message}</div>
483
+ }
484
+ }
485
+ }
486
+ ```
487
+
488
+ In this example, `ChildComponent` uses `this.throw()` within an event handler to propagate errors upwards to its parent component, `ParentComponent`. The parent component then catches the error and renders it to the DOM.
489
+
490
+ ### `this.return()`
491
+
492
+ The `return` method in Ajo is used to reset and restart the generator function of a stateful component. It effectively ends the current execution of the component's generator function, and optionally re-execute it from scratch allowing for a complete reset of the component's state and behavior.
493
+
494
+ #### Purpose:
495
+
496
+ - **Component Reset:** `this.return()` is used to restart a component's generator function from the beginning, resetting its internal state and re-initializing it as needed.
497
+
498
+ #### Usage:
499
+
500
+ - **Re-initializing Components:** Ideal for use cases where the component needs to reset its state completely, such as in response to significant prop changes or to reinitialize after certain user interactions.
501
+ - **Refreshing Component State:** Helps in scenarios where the existing state and logic of a component need to be discarded and started afresh.
502
+
503
+ #### Example:
504
+
505
+ ```jsx
506
+ function* MultiStepForm({ initialData }) {
507
+
508
+ let currentStep = 0
509
+ let formData = { ...initialData }
510
+
511
+ const handleNextStep = () => {
512
+
513
+ // Logic to move to the next step
514
+ currentStep++
515
+
516
+ // Re-render with the next step
517
+ this.refresh()
518
+ }
519
+
520
+ const handleRestart = () => {
521
+
522
+ // Reset the generator function
523
+ this.return()
524
+
525
+ // Re-render the component in its initial state
526
+ this.refresh()
527
+ }
528
+
529
+ while (true) {
530
+ switch(currentStep) {
531
+ case 0:
532
+ yield <StepOne
533
+ arg:data={formData}
534
+ arg:onNext={handleNextStep}
535
+ arg:onRestart={handleRestart}
536
+ />
537
+ break
538
+ case 1:
539
+ yield <StepTwo
540
+ arg:data={formData}
541
+ arg:onNext={handleNextStep}
542
+ arg:onRestart={handleRestart}
543
+ />
544
+ break
545
+ default:
546
+ yield <FinalStep
547
+ arg:data={formData}
548
+ arg:onRestart={handleRestart}
549
+ />
550
+ }
551
+ }
552
+ }
553
+ ```
554
+ In `handleRestart`, `this.return()` is first called to reset the generator function. This effectively ends the current execution of the component's generator function and prepares it to start from the beginning. Immediately after, `this.refresh()` is called to trigger a re-render of the component. This ensures that after the state is reset, the component's UI is also updated to reflect its initial state.
555
+
556
+ ### `arg:`
557
+
558
+ - **Purpose:** The `arg:` prefix is used in Ajo to explicitly pass arguments to generator functions. This prefix distinguishes component arguments from regular HTML attributes and other special properties.
559
+
560
+ - **Behavior:**
561
+ - When a stateful component is rendered in Ajo, any attribute on it that starts with `arg:` is treated as an argument to be passed to the component's generator function.
562
+ - This mechanism ensures that the arguments are clearly identified and separated from other attributes or DOM properties.
563
+
564
+ - **Usage:**
565
+ - Use `arg:` prefixed attributes when you need to pass data or event handlers to a component's generator function.
566
+ - This approach is particularly useful in maintaining a clear separation between component-specific props and other attributes that might be used for styling or DOM manipulation.
567
+
568
+ - **Example:**
569
+ ```jsx
570
+ function* ParentComponent() {
571
+
572
+ const someData = { /* ... */ }
573
+ const handleEvent = () => { /* ... */ }
574
+
575
+ yield <ChildComponent arg:data={someData} arg:onEvent={handleEvent} class="my-class" />
52
576
  }
53
577
 
54
- render(<Counter arg:init={5} />, document.body)
578
+ function* ChildComponent({ data, onEvent }) {
579
+ // ...
580
+ }
55
581
  ```
582
+ In this example, `ParentComponent` renders `ChildComponent`, passing `someData` and `handleEvent` as arguments using the `arg:` prefix. `class` is a regular HTML attribute and is not passed to the component's generator function, it is applied to the DOM element associated with the component.
583
+
584
+ This `arg:` prefixed attribute system in Ajo enhances the clarity and readability of component composition. It makes the intent of passing down arguments more explicit, reducing confusion between HTML attributes, and other special properties. This is especially beneficial in complex applications where components have multiple responsibilities and interact with both their children and the DOM.
585
+
586
+ ## Acknowledgments
587
+ Ajo takes heavy inspiration from [Incremental DOM](https://github.com/google/incremental-dom) and [Crank.js](https://github.com/bikeshaving/crank)
588
+
589
+ ## License
56
590
 
57
- ## acknowledgments
58
- ajo takes heavy inspiration from [Incremental DOM](https://github.com/google/incremental-dom) and [Crank.js](https://github.com/bikeshaving/crank)
591
+ ISC © [Cristian Falcone](cristianfalcone.com)