subay 0.0.7 → 0.0.8

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 CHANGED
@@ -1,377 +1,681 @@
1
- # 🐣 Subay
1
+ # Subay
2
2
 
3
- A < 700 LOC declarative, reactive UI library.
3
+ Subay is a lightweight reactive programming library that provides state management and DOM manipulation capabilities.
4
4
 
5
- ## ✨ Features
5
+ ## API
6
6
 
7
- - **Lightweight**: Less than 700 lines of code, small size, fast loading
8
- - **Declarative Rendering**: Use template literals for declarative UI construction
9
- - **Reactive State**: Simple yet powerful reactive state management
10
- - **Efficient Updates**: Optimized DOM updates with minimal DOM operations
11
- - **TypeScript Support**: Complete TypeScript type definitions
12
- - **Zero Dependencies**: No external library dependencies
7
+ ### root
13
8
 
14
- ## 📦 Installation
9
+ **Introduction**
10
+ Creates a root update context for managing the lifecycle of reactive states.
15
11
 
16
- ### NPM
12
+ **Syntax**
17
13
 
18
- ```bash
19
- npm install subay
14
+ ```typescript
15
+ root<T>(fn: (destroy: () => void) => T): T
20
16
  ```
21
17
 
22
- ### Yarn
18
+ **Parameters**
23
19
 
24
- ```bash
25
- yarn add subay
26
- ```
20
+ - `fn`: A function that receives a `destroy` function as a parameter, used to manually destroy the root context.
27
21
 
28
- ### Direct Import
22
+ **Return Value**
29
23
 
30
- ```html
31
- <script type="module">
32
- import { html, o, S } from 'https://cdn.jsdelivr.net/npm/subay@latest/lib/index.js';
33
- // Use Subay
34
- </script>
35
- ```
24
+ - `T`: The return value of the `fn` function.
36
25
 
37
- ## 🔧 Basic Usage
26
+ **Example**
38
27
 
39
- ### 🔄 State Management
28
+ ```typescript
29
+ import { root, o, S } from 'subay';
40
30
 
41
- ```javascript
42
- import { o, S } from 'subay';
31
+ root((destroy) => {
32
+ const count = o(0);
33
+ const doubled = S(() => count() * 2);
43
34
 
44
- // Create an observable value
45
- const count = o(0);
35
+ console.log(doubled()); // 0
36
+ count(1);
37
+ console.log(doubled()); // 2
46
38
 
47
- // Create a computed property
48
- const doubled = S(() => count() * 2);
39
+ // Manual destruction
40
+ destroy();
41
+ });
42
+ ```
43
+
44
+ **Note**
45
+
46
+ The only way to destroy a context created with `root` is to call the `destroy` callback.
47
+
48
+ ### S
49
+
50
+ **Introduction**
51
+ Creates a computed value that automatically recalculates when its dependent observables change.
49
52
 
50
- // Read value
51
- console.log(count()); // Output: 0
52
- console.log(doubled()); // Output: 0
53
+ **Syntax**
53
54
 
54
- // Update value
55
- count(5);
56
- console.log(count()); // Output: 5
57
- console.log(doubled()); // Output: 10
55
+ ```typescript
56
+ S<T>(fn: (pv: T | undefined) => T, value?: T): IS<T>
58
57
  ```
59
58
 
60
- ### 🎨 Rendering
59
+ **Parameters**
61
60
 
62
- ```javascript
63
- import { html, o } from 'subay';
61
+ - `fn`: A computation function that receives the previous computed value as a parameter.
62
+ - `value`: Optional initial value.
63
+
64
+ **Return Value**
65
+
66
+ - `IS<T>`: A function that returns the computed value when called.
67
+
68
+ **Example**
69
+
70
+ ```typescript
71
+ import { S, o } from 'subay';
64
72
 
65
- const name = o('World');
66
73
  const count = o(0);
74
+ const doubled = S(() => count() * 2);
67
75
 
68
- document.body.append(
69
- ...html`
70
- <div>
71
- <h1>Hello, ${name}!</h1>
72
- <p>Count: ${count}</p>
73
- <button onclick="${() => count(count() + 1)}">Increment</button>
74
- <button onclick="${() => name('Subay')}">Change Name</button>
75
- </div>
76
- `,
77
- );
76
+ console.log(doubled()); // 0
77
+ count(1);
78
+ console.log(doubled()); // 2
78
79
  ```
79
80
 
80
- ### 📋 List
81
+ ### o / observable
81
82
 
82
- ```javascript
83
- import { html, o, map } from 'subay';
83
+ **Introduction**
84
+ Creates an observable value for storing and updating state.
84
85
 
85
- const items = o(['Apple', 'Banana', 'Cherry']);
86
+ **Syntax**
86
87
 
87
- document.body.append(
88
- ...html`
89
- <ul>
90
- ${map(
91
- () => items(),
92
- (item) => html` <li>${item}</li> `,
93
- )}
94
- </ul>
95
- <button onclick="${() => items([...items(), 'Date'])}">Add Item</button>
96
- `,
97
- );
88
+ ```typescript
89
+ o<T>(value: T): IO<T>
98
90
  ```
99
91
 
100
- ## 📚 API
92
+ **Parameters**
93
+
94
+ - `value`: Initial value.
101
95
 
102
- ### ⚙️ State
96
+ **Return Value**
103
97
 
104
- #### `o(value)`
98
+ - `IO<T>`: A function that returns the current value when called without arguments, and updates the value and returns the new value when called with arguments.
105
99
 
106
- Create an observable value.
100
+ **Example**
107
101
 
108
- - **Parameters**: `value` - Initial value
109
- - **Return**: A function that returns the current value when called, and updates the value when passed a parameter
102
+ ```typescript
103
+ import { o } from 'subay';
110
104
 
111
- ```javascript
112
105
  const count = o(0);
113
- count(); // Read value
114
- count(5); // Update value
106
+ console.log(count()); // 0
107
+ count(1);
108
+ console.log(count()); // 1
115
109
  ```
116
110
 
117
- #### `S(fn, initialValue?)`
111
+ ### cleanup
118
112
 
119
- Create a computed property.
113
+ **Introduction**
114
+ Registers a cleanup function that executes when the current update context is destroyed.
120
115
 
121
- - **Parameters**:
122
- - `fn` - Computation function, receives the previous value as a parameter
123
- - `initialValue` - Optional initial value
124
- - **Return**: A function that returns the computation result when called
116
+ **Syntax**
125
117
 
126
- ```javascript
127
- const doubled = S(() => count() * 2);
128
- doubled(); // Read computation result
118
+ ```typescript
119
+ cleanup<T extends () => void>(f: T): T
129
120
  ```
130
121
 
131
- #### `root(fn)`
122
+ **Parameters**
132
123
 
133
- Create a root context for managing the lifecycle of computed properties.
124
+ - `f`: Cleanup function.
134
125
 
135
- - **Parameters**: `fn` - Function that receives a destroy function as a parameter
136
- - **Return**: The result of the function execution
126
+ **Return Value**
137
127
 
138
- ```javascript
139
- root((destroy) => {
128
+ - `T`: The passed cleanup function.
129
+
130
+ **Example**
131
+
132
+ ```typescript
133
+ import { root, S, o, cleanup } from 'subay';
134
+
135
+ root(() => {
140
136
  const count = o(0);
141
- const doubled = S(() => count() * 2);
142
137
 
143
- // Use count and doubled
138
+ S(() => {
139
+ console.log(count());
140
+ cleanup(() => {
141
+ console.log('Cleanup called');
142
+ });
143
+ });
144
+
145
+ count(1);
146
+ });
147
+ // Output: 0, 1, Cleanup called
148
+ ```
149
+
150
+ ### transaction
144
151
 
145
- // Optional: Call destroy() to clean up resources when appropriate
152
+ **Introduction**
153
+ Creates a transaction for batch updating observables, avoiding intermediate state calculations.
154
+
155
+ **Syntax**
156
+
157
+ ```typescript
158
+ transaction<T>(f: () => T): T
159
+ ```
160
+
161
+ **Parameters**
162
+
163
+ - `f`: Transaction function where multiple observables can be updated.
164
+
165
+ **Return Value**
166
+
167
+ - `T`: The return value of the `f` function.
168
+
169
+ **Example**
170
+
171
+ ```typescript
172
+ import { transaction, o, S } from 'subay';
173
+
174
+ const a = o(1);
175
+ const b = o(2);
176
+ const sum = S(() => a() + b());
177
+
178
+ S(() => console.log(sum())); // 3
179
+
180
+ transaction(() => {
181
+ a(2);
182
+ b(3);
146
183
  });
184
+ // Output: 5 (calculated only once)
147
185
  ```
148
186
 
149
- #### `cleanup(fn)`
187
+ ### sample
150
188
 
151
- Register a cleanup function that executes when the computed property is recomputed or destroyed.
189
+ **Introduction**
190
+ Gets the value of an observable without establishing a dependency relationship.
152
191
 
153
- - **Parameters**: `fn` - Cleanup function
154
- - **Return**: The passed cleanup function
192
+ **Syntax**
193
+
194
+ ```typescript
195
+ sample<T>(f: () => T): T
196
+ ```
197
+
198
+ **Parameters**
199
+
200
+ - `f`: A function where observables can be accessed without establishing dependency relationships.
201
+
202
+ **Return Value**
203
+
204
+ - `T`: The return value of the `f` function.
205
+
206
+ **Example**
207
+
208
+ ```typescript
209
+ import { sample, o, S } from 'subay';
210
+
211
+ const count = o(0);
212
+ let cachedValue;
155
213
 
156
- ```javascript
157
214
  S(() => {
158
- const timer = setInterval(() => {
159
- console.log('tick');
160
- }, 1000);
215
+ // No dependency relationship established
216
+ cachedValue = sample(() => count());
217
+ console.log(cachedValue);
218
+ });
219
+
220
+ count(1); // Will not trigger recalculation
221
+ console.log(cachedValue); // 0
222
+ ```
223
+
224
+ ### isListening
161
225
 
162
- cleanup(() => clearInterval(timer));
226
+ **Introduction**
227
+ Checks if the current state is in reactive listening mode.
163
228
 
164
- return count();
229
+ **Syntax**
230
+
231
+ ```typescript
232
+ isListening(): boolean
233
+ ```
234
+
235
+ **Parameters**
236
+
237
+ - None
238
+
239
+ **Return Value**
240
+
241
+ - `boolean`: Whether the current state is in reactive listening mode.
242
+
243
+ **Example**
244
+
245
+ ```typescript
246
+ import { isListening, S } from 'subay';
247
+
248
+ console.log(isListening()); // false
249
+
250
+ S(() => {
251
+ console.log(isListening()); // true
165
252
  });
166
253
  ```
167
254
 
168
- #### `transaction(fn)`
255
+ ### subscribe
169
256
 
170
- Batch execute multiple updates to reduce unnecessary computations and rendering.
257
+ **Introduction**
258
+ Subscribes to a function that automatically executes when dependent observables change.
171
259
 
172
- - **Parameters**: `fn` - Function containing update operations
173
- - **Return**: The result of the function execution
260
+ **Syntax**
174
261
 
175
- ```javascript
176
- transaction(() => {
177
- count(1);
178
- name('Subay');
179
- items(['a', 'b', 'c']);
262
+ ```typescript
263
+ subscribe(f: () => void): () => void
264
+ ```
265
+
266
+ **Parameters**
267
+
268
+ - `f`: Subscription function.
269
+
270
+ **Return Value**
271
+
272
+ - `() => void`: A function to unsubscribe.
273
+
274
+ **Example**
275
+
276
+ ```typescript
277
+ import { subscribe, o } from 'subay';
278
+
279
+ const count = o(0);
280
+ const unsubscribe = subscribe(() => {
281
+ console.log(count());
180
282
  });
283
+
284
+ count(1); // Output: 1
285
+ count(2); // Output: 2
286
+
287
+ unsubscribe();
288
+ count(3); // No output
181
289
  ```
182
290
 
183
- ### 🖼️ Rendering
291
+ ### unsubscribe
184
292
 
185
- #### `html`
293
+ **Introduction**
294
+ Unsubscribes a function.
186
295
 
187
- Template literal tag for creating HTML elements.
296
+ **Syntax**
188
297
 
189
- - **Usage**: `html\`...\``
190
- - **Return**: An array of DOM nodes
298
+ ```typescript
299
+ unsubscribe(f: () => void): void
300
+ ```
191
301
 
192
- ```javascript
193
- const element = html`
194
- <div class="container">
195
- <h1>Hello ${name}!</h1>
196
- </div>
197
- `;
198
- document.body.append(...element);
302
+ **Parameters**
303
+
304
+ - `f`: The function to unsubscribe.
305
+
306
+ **Return Value**
307
+
308
+ - None
309
+
310
+ **Example**
311
+
312
+ ```typescript
313
+ import { subscribe, unsubscribe, o } from 'subay';
314
+
315
+ const count = o(0);
316
+ const fn = () => console.log(count());
317
+
318
+ subscribe(fn);
319
+ count(1); // Output: 1
320
+
321
+ unsubscribe(fn);
322
+ count(2); // No output
199
323
  ```
200
324
 
201
- #### `svg`
325
+ ### isObservable
202
326
 
203
- Template literal tag for creating SVG elements.
327
+ **Introduction**
328
+ Checks if a value is an observable.
204
329
 
205
- - **Usage**: `svg\`...\``
206
- - **Return**: An array of DOM nodes
330
+ **Syntax**
207
331
 
208
- ```javascript
209
- const icon = svg /*html*/ `
210
- <svg width="24" height="24" viewBox="0 0 24 24">
211
- <path d="M12 2L2 7l10 5 10-5-10-5z"/>
212
- </svg>
213
- `;
214
- document.body.append(...icon);
332
+ ```typescript
333
+ isObservable(o: any): o is IO<any>
215
334
  ```
216
335
 
217
- #### `map(arrayFn, itemFn)`
336
+ **Parameters**
337
+
338
+ - `o`: The value to check.
218
339
 
219
- Function for rendering lists.
340
+ **Return Value**
220
341
 
221
- - **Parameters**:
222
- - `arrayFn` - Function that returns an array
223
- - `itemFn` - Function that creates DOM nodes for each array item
224
- - **Return**: An array of DOM nodes
342
+ - `boolean`: Whether it is an observable.
225
343
 
226
- ```javascript
344
+ **Example**
345
+
346
+ ```typescript
347
+ import { isObservable, o, S } from 'subay';
348
+
349
+ const count = o(0);
350
+ const doubled = S(() => count() * 2);
351
+
352
+ console.log(isObservable(count)); // true
353
+ console.log(isObservable(doubled)); // false
354
+ ```
355
+
356
+ ### isComputed
357
+
358
+ **Introduction**
359
+ Checks if a value is a computed value.
360
+
361
+ **Syntax**
362
+
363
+ ```typescript
364
+ isComputed(o: any): o is IS<any>
365
+ ```
366
+
367
+ **Parameters**
368
+
369
+ - `o`: The value to check.
370
+
371
+ **Return Value**
372
+
373
+ - `boolean`: Whether it is a computed value.
374
+
375
+ **Example**
376
+
377
+ ```typescript
378
+ import { isComputed, o, S } from 'subay';
379
+
380
+ const count = o(0);
381
+ const doubled = S(() => count() * 2);
382
+
383
+ console.log(isComputed(count)); // false
384
+ console.log(isComputed(doubled)); // true
385
+ ```
386
+
387
+ ### isReactive
388
+
389
+ **Introduction**
390
+ Checks if a value is a reactive value (observable or computed value).
391
+
392
+ **Syntax**
393
+
394
+ ```typescript
395
+ isReactive(o: any): o is IO<any> | IS<any>
396
+ ```
397
+
398
+ **Parameters**
399
+
400
+ - `o`: The value to check.
401
+
402
+ **Return Value**
403
+
404
+ - `boolean`: Whether it is a reactive value.
405
+
406
+ **Example**
407
+
408
+ ```typescript
409
+ import { isReactive, o, S } from 'subay';
410
+
411
+ const count = o(0);
412
+ const doubled = S(() => count() * 2);
413
+
414
+ console.log(isReactive(count)); // true
415
+ console.log(isReactive(doubled)); // true
416
+ console.log(isReactive(42)); // false
417
+ ```
418
+
419
+ ### map
420
+
421
+ **Introduction**
422
+ Maps an array to a DOM node array, automatically updating when the array changes.
423
+
424
+ **Syntax**
425
+
426
+ ```typescript
427
+ map<T>(array: () => T[], f: (item: T) => Node[]): Node[]
428
+ ```
429
+
430
+ **Parameters**
431
+
432
+ - `array`: A function that returns an array.
433
+ - `f`: A mapping function that converts array items to DOM node arrays.
434
+
435
+ **Return Value**
436
+
437
+ - `Node[]`: DOM node array.
438
+
439
+ **Example**
440
+
441
+ ```typescript
442
+ import { map, o, html } from 'subay';
443
+
444
+ const items = o(['a', 'b', 'c']);
227
445
  const list = map(
228
446
  () => items(),
229
- (item, index) => html` <li key="${index}">${item}</li> `,
447
+ (item) => html`<li>${item}</li>`,
230
448
  );
449
+
450
+ document.body.append(...list);
451
+ // Renders: <li>a</li><li>b</li><li>c</li>
452
+
453
+ items(['a', 'b', 'c', 'd']);
454
+ // Automatically updates: <li>a</li><li>b</li><li>c</li><li>d</li>
231
455
  ```
232
456
 
233
- ### 🛠️ Utilities
457
+ ### svg
234
458
 
235
- #### `isObservable(value)`
459
+ **Introduction**
460
+ Template tag function for creating SVG elements.
236
461
 
237
- Check if a value is an observable.
462
+ **Syntax**
238
463
 
239
- - **Parameters**: `value` - Value to check
240
- - **Return**: Boolean
464
+ ```typescript
465
+ svg(segments: TemplateStringsArray, ...values: any[]): Node[]
466
+ ```
241
467
 
242
- #### `isComputation(value)`
468
+ **Parameters**
243
469
 
244
- Check if a value is a computed property.
470
+ - `segments`: Static parts of the template string.
471
+ - `values`: Dynamic parts of the template string.
245
472
 
246
- - **Parameters**: `value` - Value to check
247
- - **Return**: Boolean
473
+ **Return Value**
248
474
 
249
- #### `isReactive(value)`
475
+ - `Node[]`: SVG element node array.
250
476
 
251
- Check if a value is reactive (observable or computed property).
477
+ **Example**
252
478
 
253
- - **Parameters**: `value` - Value to check
254
- - **Return**: Boolean
479
+ ```typescript
480
+ import { svg, o } from 'subay';
255
481
 
256
- #### `sample(fn)`
482
+ const radius = o(50);
483
+ const circle = svg`<circle cx="100" cy="100" r="${radius}" fill="red"/>`;
257
484
 
258
- Run a function without tracking its dependencies.
485
+ document.getElementById('svg').append(...circle);
486
+ ```
487
+
488
+ ### html
259
489
 
260
- - **Parameters**: `fn` - Function to run
261
- - **Return**: The result of the function execution
490
+ **Introduction**
491
+ Template tag function for creating HTML elements.
262
492
 
263
- ```javascript
264
- const value = sample(() => count()); // Read count's value without establishing dependency
493
+ **Syntax**
494
+
495
+ ```typescript
496
+ html(segments: TemplateStringsArray, ...values: any[]): Node[]
265
497
  ```
266
498
 
267
- ## ⚠️ Caveats
499
+ **Parameters**
268
500
 
269
- ### 1. Tag Position Interpolation
501
+ - `segments`: Static parts of the template string.
502
+ - `values`: Dynamic parts of the template string.
270
503
 
271
- In template strings, if interpolation is used in the tag position, the interpolation should be a string or a function that returns a string, where the returned string is the name of the HTML/SVG element. If you need to use a component, you need to call the method directly.
504
+ **Return Value**
272
505
 
273
- **Correct Usage**:
506
+ - `Node[]`: HTML element node array.
274
507
 
275
- ```javascript
276
- // Static tag
277
- html`<div>Hello</div>`;
508
+ **Example**
278
509
 
279
- // Dynamic tag (using string)
280
- const tagName = o('div');
281
- html`<${tagName}>Hello</${tagName}>`;
510
+ ```typescript
511
+ import { html, o } from 'subay';
282
512
 
283
- // Dynamic tag (using function that returns string)
284
- const getTagName = () => 'div';
285
- html`<${getTagName}>Hello</${getTagName}>`;
513
+ const count = o(0);
514
+ const counter = html`
515
+ <div>
516
+ <p>Count: ${count}</p>
517
+ <button onclick=${() => count(count() + 1)}>Increment</button>
518
+ </div>
519
+ `;
286
520
 
287
- // Using component (calling method directly)
288
- const MyComponent = () => html`<div>Component</div>`;
289
- html`<div>${MyComponent()}</div>`;
521
+ document.body.append(...counter);
522
+ // Clicking the button will automatically update the count value
290
523
  ```
291
524
 
292
- **Incorrect Usage**:
525
+ **Using HTML**
293
526
 
294
- ```javascript
295
- // Error: Component not called directly
296
- const MyComponent = () => html`<div>Component</div>`;
297
- html`<${MyComponent}>Hello</${MyComponent}>`; // This will try to create an element named "function MyComponent() {...}"
527
+ Subay uses `DOMParser` to parse HTML templates. The `html` parameter must be a valid HTML string (similarly, the `svg` parameter must be a valid SVG string), which is different from JSX.
528
+
529
+ **Example**
530
+
531
+ ```typescript
532
+ import { html } from 'subay';
533
+
534
+ // Valid HTML string
535
+ const validHtml = html`<div><p>Hello</p></div>`;
536
+
537
+ document.body.append(...validHtml);
298
538
  ```
299
539
 
300
- ### 2. Dynamic Attribute Values
540
+ **Don't Do This**
541
+
542
+ ```typescript
543
+ import { html } from 'subay';
301
544
 
302
- If you need to dynamically set HTML/SVG element attribute values, you need to pass the return value of `S()` or `o()`. If you directly pass a regular function, this function will be treated as the attribute value, not the return value of the function.
545
+ // Self-closing tags
546
+ const invalidHtml = html`<div>
547
+ <span />
548
+ <span />
549
+ </div>`; // Will be parsed as <div><span><span></span></span></div>
550
+ ```
303
551
 
304
- **Correct Usage**:
552
+ **Passing Objects as Element Attributes**
305
553
 
306
- ```javascript
307
- // Using observable
308
- const disabled = o(false);
309
- html`<button disabled="${disabled}">Click</button>`;
554
+ If you need to pass an object as element attributes, you can use the `...` syntax.
310
555
 
311
- // Using computed property
312
- const isDisabled = S(() => count() > 5);
313
- html`<button disabled="${isDisabled}">Click</button>`;
556
+ **Syntax**
314
557
 
315
- // Event binding (passing function directly)
316
- html`<button onclick="${() => count(count() + 1)}">Increment</button>`;
558
+ ```typescript
559
+ html`<tag ...${props}></tag>`;
317
560
  ```
318
561
 
319
- **Incorrect Usage**:
562
+ **Example**
320
563
 
321
- ```javascript
322
- // Error: Regular function treated as attribute value
323
- const getDisabled = () => false;
324
- html`<button disabled="${getDisabled}">Click</button>`; // This will set "function getDisabled() {...}" as the attribute value
564
+ ```typescript
565
+ import { html, o } from 'subay';
566
+
567
+ const props = o({
568
+ className: 'container',
569
+ style: 'color: red;',
570
+ });
571
+
572
+ const element = html`<div ...${props}></div>`;
573
+
574
+ document.body.append(...element);
325
575
  ```
326
576
 
327
- ## 💡 Motivation
577
+ **Component Declaration**
328
578
 
329
- The motivation behind Subay is to provide a lightweight, easy-to-use reactive UI library that avoids the complexity and size of large frameworks. Its design philosophy is:
579
+ A component is a function that returns Node[], and can receive parameters. Unlike React, Vue, etc., component parameters are a list.
330
580
 
331
- - **Simplicity First**: Keep the API simple and intuitive, easy to learn and use
332
- - **Performance Priority**: Optimize DOM updates, reduce unnecessary computations
333
- - **Minimal Dependencies**: No external library dependencies, keep size small
334
- - **TypeScript Support**: Provide complete type definitions, improve development experience
581
+ **Syntax**
335
582
 
336
- ### Relationship with Sinuous
583
+ ```typescript
584
+ const Component = (text, children: () => Node[]) => {
585
+ return html`<div>${text}</div>`;
586
+ };
587
+ ```
337
588
 
338
- Subay references the design ideas of the [Sinuous](https://github.com/luwes/sinuous) library. The `o` and `S` APIs are almost identical to Sinuous, but there are the following differences in implementation details:
589
+ **Example**
339
590
 
340
- 1. **S Execution Timing**: In Subay, even if the result of an S function is not used, it will still be executed. In Sinuous, an S function is only executed when its result is actually used.
591
+ ```typescript
592
+ import { html } from 'subay';
341
593
 
342
- **Example**:
594
+ const Greeting = (name: string, children: () => Node[]) => {
595
+ return html`
596
+ <div>
597
+ <h1>Hello, ${name}!</h1>
598
+ ${children()}
599
+ </div>
600
+ `;
601
+ };
602
+ ```
343
603
 
344
- ```javascript
345
- const userId = o('');
346
- const defaultThemeId = o('rainbow');
604
+ **Component Usage**
347
605
 
348
- const themeId = S(() => {
349
- if (!userId()) {
350
- return defaultThemeId();
351
- }
352
- return userTheme();
353
- });
354
- const userTheme = S(() => {
355
- // if !userId(), sinuous won't execute here
356
- // but subay will execute
357
- console.log('side effect on ', userId());
358
- return `${userId()}'s theme`;
359
- });
360
- S(() => {
361
- console.log('now the theme is ', themeId());
362
- });
606
+ When using a component in a template, reference the component at the tag position.
607
+ Because the template is an HTML string, self-closing tags are not supported when referencing.
608
+ The parameters passed to the component must be in the same order as the component's declared parameters.
609
+ The parameters received by the component are always consistent with those passed in.
610
+
611
+ **Example**
612
+
613
+ ```typescript
614
+ import { html, o } from 'subay';
615
+
616
+ const Greeting = (greet: string, name: () => string) => {
617
+ return html`<h1>${greet}, ${name}!</h1>`;
618
+ };
619
+
620
+ const name = o('World');
621
+ const app = html` <div><${Greeting} greet=${'Hello'} name=${name}></${Greeting}></div> `;
622
+
623
+ document.body.append(...app);
624
+ ```
625
+
626
+ **Don't Do This**
627
+
628
+ ```typescript
629
+ import { html, o } from 'subay';
363
630
 
364
- userId('John');
365
- userId('');
366
- userId('Mary');
367
- ```
631
+ const Greeting = (greet: string, name: () => string) => {
632
+ return html`<h1>${greet}, ${name}!</h1>`;
633
+ };
368
634
 
369
- In this example, when `userId()` is an empty string, `themeId()` will return `defaultThemeId()` and not use the result of `userTheme()`. In Sinuous, the `userTheme()` function will not be executed; but in Subay, the `userTheme()` function will still be executed, producing a side effect.
635
+ const name = o('World');
636
+ // Error, the following span will be treated as a child node of Greeting.
637
+ const app = html`
638
+ <div>
639
+ <${Greeting}
640
+ greet=${'Hello'}
641
+ name=${name}
642
+ />
643
+ <span></span>
644
+ </div>
645
+ `;
646
+ // Error, parameter order is inconsistent with Greeting's parameters
647
+ const app = html` <div><${Greeting} name=${name} greet=${'Hi'} ></${Greeting}></div> `;
648
+
649
+ document.body.append(...app);
650
+ ```
651
+
652
+ **Dynamic Components**
653
+
654
+ The interpolation at the tag position can be the return value of `S` or `o`, and components can be dynamically selected for rendering based on conditions.
370
655
 
371
- 2. **o's Reference Management for S**: When S is recomputed, the o's that S depended on during the last computation won't immediately forget S. Instead, they will filter out the "forgotten" S only when o is updated next time. Since o updates are usually less frequent than S recomputations, this approach slightly improves performance.
656
+ **Example**
372
657
 
373
- Subay is suitable for projects that need lightweight UI solutions, especially small applications, prototype development, or scenarios with high performance requirements.
658
+ ```typescript
659
+ import { html, o, S } from 'subay';
374
660
 
375
- ## 📄 License
661
+ const Greeting = (name: () => string) => {
662
+ return html`<h1>Hello, ${name}!</h1>`;
663
+ };
664
+
665
+ const Farewell = (name: () => string) => {
666
+ return html`<h1>Goodbye, ${name}!</h1>`;
667
+ };
668
+
669
+ const showGreeting = o(true);
670
+ const name = o('World');
671
+ const Component = S(() => (showGreeting() ? Greeting : Farewell));
672
+
673
+ const app = html`
674
+ <div>
675
+ <${Component} name=${name} ></${Component}>
676
+ <button onclick=${() => showGreeting(!showGreeting())}>Toggle</button>
677
+ </div>
678
+ `;
376
679
 
377
- MIT License - see the [LICENSE](LICENSE) file for details.
680
+ document.body.append(...app);
681
+ ```