subay 0.0.6 → 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,103 +1,681 @@
1
1
  # Subay
2
2
 
3
- A lightweight reactive UI library with state management and template rendering.
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
- - **Reactive State Management**: Create observable values and computed properties
8
- - **Template Rendering**: Use tagged template literals for HTML and SVG
9
- - **Efficient DOM Updates**: Optimized rendering with minimal DOM operations
10
- - **TypeScript Support**: Full TypeScript integration
7
+ ### root
11
8
 
12
- ## 📦 Installation
9
+ **Introduction**
10
+ Creates a root update context for managing the lifecycle of reactive states.
13
11
 
14
- ```bash
15
- npm install subay
12
+ **Syntax**
13
+
14
+ ```typescript
15
+ root<T>(fn: (destroy: () => void) => T): T
16
+ ```
17
+
18
+ **Parameters**
19
+
20
+ - `fn`: A function that receives a `destroy` function as a parameter, used to manually destroy the root context.
21
+
22
+ **Return Value**
23
+
24
+ - `T`: The return value of the `fn` function.
25
+
26
+ **Example**
27
+
28
+ ```typescript
29
+ import { root, o, S } from 'subay';
30
+
31
+ root((destroy) => {
32
+ const count = o(0);
33
+ const doubled = S(() => count() * 2);
34
+
35
+ console.log(doubled()); // 0
36
+ count(1);
37
+ console.log(doubled()); // 2
38
+
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.
52
+
53
+ **Syntax**
54
+
55
+ ```typescript
56
+ S<T>(fn: (pv: T | undefined) => T, value?: T): IS<T>
57
+ ```
58
+
59
+ **Parameters**
60
+
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';
72
+
73
+ const count = o(0);
74
+ const doubled = S(() => count() * 2);
75
+
76
+ console.log(doubled()); // 0
77
+ count(1);
78
+ console.log(doubled()); // 2
79
+ ```
80
+
81
+ ### o / observable
82
+
83
+ **Introduction**
84
+ Creates an observable value for storing and updating state.
85
+
86
+ **Syntax**
87
+
88
+ ```typescript
89
+ o<T>(value: T): IO<T>
90
+ ```
91
+
92
+ **Parameters**
93
+
94
+ - `value`: Initial value.
95
+
96
+ **Return Value**
97
+
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.
99
+
100
+ **Example**
101
+
102
+ ```typescript
103
+ import { o } from 'subay';
104
+
105
+ const count = o(0);
106
+ console.log(count()); // 0
107
+ count(1);
108
+ console.log(count()); // 1
109
+ ```
110
+
111
+ ### cleanup
112
+
113
+ **Introduction**
114
+ Registers a cleanup function that executes when the current update context is destroyed.
115
+
116
+ **Syntax**
117
+
118
+ ```typescript
119
+ cleanup<T extends () => void>(f: T): T
120
+ ```
121
+
122
+ **Parameters**
123
+
124
+ - `f`: Cleanup function.
125
+
126
+ **Return Value**
127
+
128
+ - `T`: The passed cleanup function.
129
+
130
+ **Example**
131
+
132
+ ```typescript
133
+ import { root, S, o, cleanup } from 'subay';
134
+
135
+ root(() => {
136
+ const count = o(0);
137
+
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
151
+
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);
183
+ });
184
+ // Output: 5 (calculated only once)
185
+ ```
186
+
187
+ ### sample
188
+
189
+ **Introduction**
190
+ Gets the value of an observable without establishing a dependency relationship.
191
+
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;
213
+
214
+ S(() => {
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
225
+
226
+ **Introduction**
227
+ Checks if the current state is in reactive listening mode.
228
+
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
252
+ });
253
+ ```
254
+
255
+ ### subscribe
256
+
257
+ **Introduction**
258
+ Subscribes to a function that automatically executes when dependent observables change.
259
+
260
+ **Syntax**
261
+
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());
282
+ });
283
+
284
+ count(1); // Output: 1
285
+ count(2); // Output: 2
286
+
287
+ unsubscribe();
288
+ count(3); // No output
289
+ ```
290
+
291
+ ### unsubscribe
292
+
293
+ **Introduction**
294
+ Unsubscribes a function.
295
+
296
+ **Syntax**
297
+
298
+ ```typescript
299
+ unsubscribe(f: () => void): void
300
+ ```
301
+
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
323
+ ```
324
+
325
+ ### isObservable
326
+
327
+ **Introduction**
328
+ Checks if a value is an observable.
329
+
330
+ **Syntax**
331
+
332
+ ```typescript
333
+ isObservable(o: any): o is IO<any>
16
334
  ```
17
335
 
18
- ## 🔧 Basic Usage
336
+ **Parameters**
19
337
 
20
- ### 🔄 Reactive State
338
+ - `o`: The value to check.
21
339
 
22
- ```javascript
23
- import { o, S } from 'subay';
340
+ **Return Value**
341
+
342
+ - `boolean`: Whether it is an observable.
343
+
344
+ **Example**
345
+
346
+ ```typescript
347
+ import { isObservable, o, S } from 'subay';
24
348
 
25
- // Create an observable value
26
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).
27
391
 
28
- // Create a computed property
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);
29
412
  const doubled = S(() => count() * 2);
30
413
 
31
- // Update the value
32
- count(5);
33
- console.log(doubled()); // Output: 10
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[]
34
428
  ```
35
429
 
36
- ### 🎨 Template Rendering
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']);
445
+ const list = map(
446
+ () => items(),
447
+ (item) => html`<li>${item}</li>`,
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>
455
+ ```
456
+
457
+ ### svg
458
+
459
+ **Introduction**
460
+ Template tag function for creating SVG elements.
461
+
462
+ **Syntax**
463
+
464
+ ```typescript
465
+ svg(segments: TemplateStringsArray, ...values: any[]): Node[]
466
+ ```
467
+
468
+ **Parameters**
469
+
470
+ - `segments`: Static parts of the template string.
471
+ - `values`: Dynamic parts of the template string.
472
+
473
+ **Return Value**
37
474
 
38
- ```javascript
475
+ - `Node[]`: SVG element node array.
476
+
477
+ **Example**
478
+
479
+ ```typescript
480
+ import { svg, o } from 'subay';
481
+
482
+ const radius = o(50);
483
+ const circle = svg`<circle cx="100" cy="100" r="${radius}" fill="red"/>`;
484
+
485
+ document.getElementById('svg').append(...circle);
486
+ ```
487
+
488
+ ### html
489
+
490
+ **Introduction**
491
+ Template tag function for creating HTML elements.
492
+
493
+ **Syntax**
494
+
495
+ ```typescript
496
+ html(segments: TemplateStringsArray, ...values: any[]): Node[]
497
+ ```
498
+
499
+ **Parameters**
500
+
501
+ - `segments`: Static parts of the template string.
502
+ - `values`: Dynamic parts of the template string.
503
+
504
+ **Return Value**
505
+
506
+ - `Node[]`: HTML element node array.
507
+
508
+ **Example**
509
+
510
+ ```typescript
39
511
  import { html, o } from 'subay';
40
512
 
41
- const name = o('World');
42
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
+ `;
520
+
521
+ document.body.append(...counter);
522
+ // Clicking the button will automatically update the count value
523
+ ```
524
+
525
+ **Using HTML**
526
+
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);
538
+ ```
539
+
540
+ **Don't Do This**
541
+
542
+ ```typescript
543
+ import { html } from 'subay';
544
+
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
+ ```
551
+
552
+ **Passing Objects as Element Attributes**
553
+
554
+ If you need to pass an object as element attributes, you can use the `...` syntax.
555
+
556
+ **Syntax**
43
557
 
44
- document.body.append(
45
- ...html`
558
+ ```typescript
559
+ html`<tag ...${props}></tag>`;
560
+ ```
561
+
562
+ **Example**
563
+
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);
575
+ ```
576
+
577
+ **Component Declaration**
578
+
579
+ A component is a function that returns Node[], and can receive parameters. Unlike React, Vue, etc., component parameters are a list.
580
+
581
+ **Syntax**
582
+
583
+ ```typescript
584
+ const Component = (text, children: () => Node[]) => {
585
+ return html`<div>${text}</div>`;
586
+ };
587
+ ```
588
+
589
+ **Example**
590
+
591
+ ```typescript
592
+ import { html } from 'subay';
593
+
594
+ const Greeting = (name: string, children: () => Node[]) => {
595
+ return html`
46
596
  <div>
47
- <h1>Hello ${name}!</h1>
48
- <p>Count: ${count}</p>
49
- <button onclick="${() => count(count() + 1)}">Increment</button>
50
- <button onclick="${() => name('Subay')}">Change Name</button>
597
+ <h1>Hello, ${name}!</h1>
598
+ ${children()}
51
599
  </div>
52
- `,
53
- );
600
+ `;
601
+ };
54
602
  ```
55
603
 
56
- ### 📋 List Rendering
604
+ **Component Usage**
57
605
 
58
- ```javascript
59
- import { html, o, map } from 'subay';
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.
60
610
 
61
- const items = o(['Apple', 'Banana', 'Cherry']);
611
+ **Example**
62
612
 
63
- document.body.append(
64
- ...html`
65
- <ul>
66
- ${map(
67
- () => items(),
68
- (item) => html` <li>${item}</li> `,
69
- )}
70
- </ul>
71
- <button onclick="${() => items([...items(), 'Date'])}">Add Item</button>
72
- `,
73
- );
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';
630
+
631
+ const Greeting = (greet: string, name: () => string) => {
632
+ return html`<h1>${greet}, ${name}!</h1>`;
633
+ };
634
+
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);
74
650
  ```
75
651
 
76
- ## 📚 Core API
652
+ **Dynamic Components**
77
653
 
78
- ### ⚙️ State Management
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.
79
655
 
80
- - **`o(value)`**: Create an observable value
81
- - **`S(fn, initialValue?)`**: Create a computed property
82
- - **`root(fn)`**: Create a root context for reactive computations
83
- - **`cleanup(fn)`**: Register a cleanup function
84
- - **`transaction(fn)`**: Batch multiple updates
85
- - **`sample(fn)`**: Run a function without tracking dependencies
656
+ **Example**
86
657
 
87
- ### 🖼️ Rendering
658
+ ```typescript
659
+ import { html, o, S } from 'subay';
88
660
 
89
- - **`html`**: Tagged template literal for HTML
90
- - **`svg`**: Tagged template literal for SVG
91
- - **`map(arrayFn, itemFn)`**: Render a list of items
661
+ const Greeting = (name: () => string) => {
662
+ return html`<h1>Hello, ${name}!</h1>`;
663
+ };
92
664
 
93
- ### 🛠️ Utilities
665
+ const Farewell = (name: () => string) => {
666
+ return html`<h1>Goodbye, ${name}!</h1>`;
667
+ };
94
668
 
95
- - **`isObservable(value)`**: Check if a value is an observable
96
- - **`isComputed(value)`**: Check if a value is a computed property
97
- - **`isReactive(value)`**: Check if a value is reactive
98
- - **`subscribe(fn)`**: Subscribe to changes
99
- - **`unsubscribe(fn)`**: Unsubscribe from changes
669
+ const showGreeting = o(true);
670
+ const name = o('World');
671
+ const Component = S(() => (showGreeting() ? Greeting : Farewell));
100
672
 
101
- ## 📄 License
673
+ const app = html`
674
+ <div>
675
+ <${Component} name=${name} ></${Component}>
676
+ <button onclick=${() => showGreeting(!showGreeting())}>Toggle</button>
677
+ </div>
678
+ `;
102
679
 
103
- MIT License - see the [LICENSE](LICENSE) file for details.
680
+ document.body.append(...app);
681
+ ```