nuclo 0.1.30 → 0.1.32

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.
Files changed (2) hide show
  1. package/README.md +570 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,570 @@
1
+ # nuclo
2
+
3
+ **A DOM library for people who peaked in the jQuery era and never looked back.**
4
+
5
+ _Yes, this is yet another JavaScript library. We're aware. The ecosystem now has more libraries than actual developers. We're sorry. Or you're welcome? We're still figuring it out._
6
+
7
+ Build reactive UIs without the magic tricks. Just functions, mutations, and a single `update()` call when you feel like it.
8
+
9
+ ```ts
10
+ import 'nuclo';
11
+
12
+ let count = 0;
13
+
14
+ const counter = div(
15
+ h1(() => `Count: ${count}`),
16
+ button('Increment', on('click', () => {
17
+ count++;
18
+ update();
19
+ }))
20
+ );
21
+
22
+ render(counter);
23
+ ```
24
+
25
+ ## Why nuclo?
26
+
27
+ - **Zero magic** – Like a microwave with just one button. You press `update()`, stuff happens.
28
+ - **No virtual DOM** – Why simulate the DOM when you can just... use the DOM? *taps forehead*
29
+ - **Tiny footprint** – Smaller than your average React component's prop types
30
+ - **Global API** – `div()` is everywhere, like that one friend who shows up uninvited but makes everything fun
31
+ - **TypeScript-first** – All 140+ tags typed. Yes, even `<bdi>`. You're welcome.
32
+ - **Real reactivity** – Updates only what changed. Your browser's repaint budget will thank you.
33
+
34
+ ---
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ npm install nuclo
40
+ ```
41
+
42
+ ### Usage
43
+
44
+ Simply import once to register all global functions:
45
+
46
+ ```ts
47
+ import 'nuclo';
48
+
49
+ // Now use div(), update(), on(), list(), when(), render(), etc. globally
50
+ let count = 0;
51
+ const app = div(
52
+ h1(() => `Count: ${count}`),
53
+ button('Click', on('click', () => { count++; update(); }))
54
+ );
55
+ render(app);
56
+ ```
57
+
58
+ ### TypeScript Setup
59
+
60
+ Add to your `tsconfig.json`:
61
+
62
+ ```json
63
+ {
64
+ "compilerOptions": {
65
+ "types": ["nuclo/types"]
66
+ }
67
+ }
68
+ ```
69
+
70
+ Or in your `vite-env.d.ts`:
71
+
72
+ ```ts
73
+ /// <reference types="nuclo/types" />
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Quick Examples
79
+
80
+ ### Counter
81
+
82
+ ```ts
83
+ import 'nuclo';
84
+
85
+ let count = 0;
86
+
87
+ const app = div(
88
+ h1(() => `Count: ${count}`),
89
+ button('Increment', on('click', () => { count++; update(); })),
90
+ button('Reset', on('click', () => { count = 0; update(); }))
91
+ );
92
+
93
+ render(app);
94
+ ```
95
+
96
+ ### Todo List
97
+
98
+ ```ts
99
+ import 'nuclo';
100
+
101
+ type Todo = { id: number; text: string; done: boolean };
102
+
103
+ let todos: Todo[] = [];
104
+ let nextId = 1;
105
+ let input = '';
106
+
107
+ function addTodo() {
108
+ if (!input.trim()) return;
109
+ todos.push({ id: nextId++, text: input, done: false });
110
+ input = '';
111
+ update();
112
+ }
113
+
114
+ const app = div(
115
+ { className: 'todo-app' },
116
+
117
+ // Input
118
+ div(
119
+ input({ value: () => input },
120
+ on('input', e => { input = e.target.value; update(); }),
121
+ on('keydown', e => e.key === 'Enter' && addTodo())
122
+ ),
123
+ button('Add', on('click', addTodo))
124
+ ),
125
+
126
+ // List
127
+ when(() => todos.length > 0,
128
+ list(() => todos, (todo) =>
129
+ div(
130
+ { className: () => todo.done ? 'done' : '' },
131
+ input({ type: 'checkbox', checked: () => todo.done },
132
+ on('change', () => { todo.done = !todo.done; update(); })
133
+ ),
134
+ span(() => todo.text),
135
+ button('×', on('click', () => {
136
+ todos = todos.filter(t => t.id !== todo.id);
137
+ update();
138
+ }))
139
+ )
140
+ )
141
+ ).else(
142
+ p('No todos yet!')
143
+ )
144
+ );
145
+
146
+ render(app);
147
+ ```
148
+
149
+ ### Real-time Search Filter
150
+
151
+ ```ts
152
+ import 'nuclo';
153
+
154
+ const users = [
155
+ { id: 1, name: 'Alice Johnson', email: 'alice@example.com' },
156
+ { id: 2, name: 'Bob Smith', email: 'bob@example.com' },
157
+ { id: 3, name: 'Charlie Brown', email: 'charlie@example.com' }
158
+ ];
159
+
160
+ let searchQuery = '';
161
+
162
+ function filteredUsers() {
163
+ const q = searchQuery.toLowerCase();
164
+ return users.filter(u =>
165
+ u.name.toLowerCase().includes(q) ||
166
+ u.email.toLowerCase().includes(q)
167
+ );
168
+ }
169
+
170
+ const app = div(
171
+ h1('User Directory'),
172
+
173
+ input(
174
+ {
175
+ type: 'search',
176
+ placeholder: 'Search users...',
177
+ value: () => searchQuery
178
+ },
179
+ on('input', e => {
180
+ searchQuery = e.target.value;
181
+ update();
182
+ })
183
+ ),
184
+
185
+ when(() => filteredUsers().length > 0,
186
+ list(() => filteredUsers(), user =>
187
+ div(
188
+ { className: 'user-card' },
189
+ h3(user.name),
190
+ p(user.email)
191
+ )
192
+ )
193
+ ).else(
194
+ p(() => `No users found for "${searchQuery}"`)
195
+ )
196
+ );
197
+
198
+ render(app);
199
+ ```
200
+
201
+ ### Loading States & Async
202
+
203
+ ```ts
204
+ import 'nuclo';
205
+
206
+ type State = { status: 'idle' | 'loading' | 'error'; data: any[]; error?: string };
207
+
208
+ let state: State = { status: 'idle', data: [] };
209
+
210
+ async function fetchData() {
211
+ state.status = 'loading';
212
+ update();
213
+
214
+ try {
215
+ const response = await fetch('/api/data');
216
+ state.data = await response.json();
217
+ state.status = 'idle';
218
+ } catch (err) {
219
+ state.status = 'error';
220
+ state.error = err.message;
221
+ }
222
+ update();
223
+ }
224
+
225
+ const app = div(
226
+ button('Load Data', on('click', fetchData)),
227
+
228
+ when(() => state.status === 'loading',
229
+ div('Loading...')
230
+ ).when(() => state.status === 'error',
231
+ div({ className: 'error' }, () => `Error: ${state.error}`)
232
+ ).when(() => state.data.length > 0,
233
+ list(() => state.data, item => div(item.name))
234
+ ).else(
235
+ div('No data loaded')
236
+ )
237
+ );
238
+
239
+ render(app);
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Core Concepts
245
+
246
+ ### 1. **Explicit Updates**
247
+
248
+ Unlike React or Vue, nuclo doesn't auto-detect changes. You call `update()` when ready:
249
+
250
+ ```ts
251
+ let name = 'World';
252
+
253
+ // Mutate freely
254
+ name = 'Alice';
255
+ name = name.toUpperCase();
256
+
257
+ // Update once when ready
258
+ update();
259
+ ```
260
+
261
+ **Advantages of explicit `update()`:**
262
+
263
+ - **Performance**: Batch mutations like a responsible adult. One `update()` > ten thousand proxy getters watching your every variable assignment like helicopter parents.
264
+ - **Control**: You're the boss of when pixels change. Perfect for animations, async chaos, or coordinating changes without asking a framework permission.
265
+ - **Predictability**: Zero surprise re-renders. No "why did this component update 47 times?!" sessions in DevTools.
266
+ - **Simplicity**: No proxies, no dependency graphs, no PhD required. Just objects and a function named `update()`. Revolutionary, we know.
267
+ - **Debugging**: Put a breakpoint at `update()`. That's it. That's the whole debugging strategy.
268
+
269
+ ```ts
270
+ // Example: Batch updates for better performance
271
+ items.push(item1);
272
+ items.push(item2);
273
+ items.sort();
274
+ user.name = 'Alice';
275
+ update(); // One update for all changes
276
+
277
+ // vs. automatic tracking (hypothetical)
278
+ items.push(item1); // triggers update
279
+ items.push(item2); // triggers update
280
+ items.sort(); // triggers update
281
+ user.name = 'Alice'; // triggers update
282
+ // 4 updates instead of 1!
283
+ ```
284
+
285
+ ### 2. **Reactive Functions**
286
+
287
+ Zero-arg functions become reactive:
288
+
289
+ ```ts
290
+ let count = 0;
291
+
292
+ div(
293
+ () => `Count: ${count}`, // Updates when update() is called
294
+ { title: () => `Current: ${count}` } // Attributes too
295
+ )
296
+ ```
297
+
298
+ ### 3. **Conditional Rendering with `when`**
299
+
300
+ First matching condition wins:
301
+
302
+ ```ts
303
+ when(() => user.isAdmin,
304
+ div('Admin Panel')
305
+ ).when(() => user.isLoggedIn,
306
+ div('User Dashboard')
307
+ ).else(
308
+ div('Please log in')
309
+ )
310
+ ```
311
+
312
+ DOM is preserved if the active branch doesn't change.
313
+
314
+ ### 4. **List Synchronization**
315
+
316
+ Lists use object identity (not keys) to track items:
317
+
318
+ ```ts
319
+ list(() => items, (item, index) =>
320
+ div(() => `${index}: ${item.name}`)
321
+ )
322
+ ```
323
+
324
+ Mutate the array (push, splice, reverse), then call `update()`. Elements are reused if the item reference is the same.
325
+
326
+ ## API Reference
327
+
328
+ ### Core Functions
329
+
330
+ #### `update()`
331
+
332
+ Triggers all reactive updates. Call this after mutating state:
333
+
334
+ ```ts
335
+ count++;
336
+ items.push(newItem);
337
+ update();
338
+ ```
339
+
340
+ #### `list(provider, renderer)`
341
+
342
+ Synchronizes an array to DOM elements:
343
+
344
+ ```ts
345
+ list(
346
+ () => items, // Provider function
347
+ (item, index) => div( // Renderer
348
+ () => `${index}: ${item.name}`
349
+ )
350
+ )
351
+ ```
352
+
353
+ Items are tracked by object identity. Mutate the array and call `update()` to sync.
354
+
355
+ #### `when(condition, ...content)`
356
+
357
+ Conditional rendering with chaining:
358
+
359
+ ```ts
360
+ when(() => count > 10,
361
+ div('High')
362
+ ).when(() => count > 0,
363
+ div('Low')
364
+ ).else(
365
+ div('Zero')
366
+ )
367
+ ```
368
+
369
+ First matching condition wins. DOM is preserved if the active branch doesn't change.
370
+
371
+ #### `on(event, handler, options?)`
372
+
373
+ Attach event listeners:
374
+
375
+ ```ts
376
+ button('Click me',
377
+ on('click', () => console.log('clicked')),
378
+ on('mouseenter', handleHover, { passive: true })
379
+ )
380
+ ```
381
+
382
+ ### Tag Builders
383
+
384
+ All HTML and SVG tags are available globally:
385
+
386
+ ```ts
387
+ div(), span(), button(), input(), h1(), p(), ul(), li()
388
+ svg(), circle(), path(), rect(), g()
389
+ // ... and 140+ more
390
+ ```
391
+
392
+ ### Attributes
393
+
394
+ Pass attributes as objects:
395
+
396
+ ```ts
397
+ div('Hello', {
398
+ className: 'container',
399
+ id: 'main',
400
+ 'data-test': 'value',
401
+ style: { color: 'red', fontSize: '16px' }
402
+ })
403
+ ```
404
+
405
+ Reactive attributes use functions:
406
+
407
+ ```ts
408
+ div({
409
+ className: () => isActive ? 'active' : '',
410
+ disabled: () => !isValid,
411
+ style: () => ({ opacity: isVisible ? 1 : 0 })
412
+ })
413
+ ```
414
+
415
+ ---
416
+
417
+ ## Best Practices
418
+
419
+ ### Batch Updates
420
+
421
+ Mutate like you're stress-testing the array, then update once like you meant to do that:
422
+
423
+ ```ts
424
+ // Galaxy brain
425
+ items.push(item1);
426
+ items.push(item2);
427
+ items.sort();
428
+ update();
429
+
430
+ // Smooth brain (but hey, it works)
431
+ items.push(item1);
432
+ update();
433
+ items.push(item2);
434
+ update();
435
+ ```
436
+
437
+ ### Object Identity for Lists
438
+
439
+ Lists track items by reference. Mutate the object, not your soul:
440
+
441
+ ```ts
442
+ // The way
443
+ todos[0].done = true;
444
+ update();
445
+
446
+ // The dark side (RIP that DOM element, we hardly knew ye)
447
+ todos[0] = { ...todos[0], done: true };
448
+ update();
449
+ ```
450
+
451
+ ### Use `.else()` for Clarity
452
+
453
+ Even if not initially needed:
454
+
455
+ ```ts
456
+ when(() => isLoading,
457
+ div('Loading...')
458
+ ).else(
459
+ div('Ready') // Clear intent
460
+ )
461
+ ```
462
+
463
+ ---
464
+
465
+ ## Advanced Patterns
466
+
467
+ ### Nested Structures
468
+
469
+ Combine `when` and `list`:
470
+
471
+ ```ts
472
+ when(() => user.isLoggedIn,
473
+ div(
474
+ h1(() => `Welcome, ${user.name}`),
475
+ list(() => user.notifications, n =>
476
+ div(n.message, { className: () => n.read ? 'read' : 'unread' })
477
+ )
478
+ )
479
+ ).else(
480
+ div('Please log in')
481
+ )
482
+ ```
483
+
484
+ ### Component-like Functions
485
+
486
+ ```ts
487
+ function UserCard(user: User) {
488
+ return div(
489
+ { className: 'user-card' },
490
+ img({ src: user.avatar }),
491
+ h3(user.name),
492
+ p(user.bio)
493
+ );
494
+ }
495
+
496
+ list(() => users, user => UserCard(user))
497
+ ```
498
+
499
+ ### Computed Values
500
+
501
+ ```ts
502
+ function activeCount() {
503
+ return todos.filter(t => !t.done).length;
504
+ }
505
+
506
+ div(
507
+ () => `${activeCount()} remaining`
508
+ )
509
+ ```
510
+
511
+ ---
512
+
513
+ ## Performance
514
+
515
+ - **No virtual DOM diffing** – We skip the middle-manager and talk directly to the DOM
516
+ - **Fine-grained updates** – Only updates what changed. Like a surgeon, not a bulldozer.
517
+ - **Element reuse** – Lists are smart enough to move elements instead of yeeting them into the void
518
+ - **Branch preservation** – `when` branches stay alive unless conditions change. Low-key immortal.
519
+
520
+ For high-frequency updates (animations, game loops, existential crises), batch mutations before calling `update()`.
521
+
522
+ ---
523
+
524
+ ## Debugging
525
+
526
+ ### Inspect Markers
527
+
528
+ Open DevTools, stare at the DOM like it owes you money:
529
+
530
+ ```html
531
+ <!-- when-start-1 -->
532
+ <div>Content</div>
533
+ <!-- when-end -->
534
+
535
+ <!-- list-start-2 -->
536
+ <div>Item 1</div>
537
+ <div>Item 2</div>
538
+ <!-- list-end -->
539
+ ```
540
+
541
+ These comment markers are your breadcrumbs. Follow them to victory.
542
+
543
+ ### Common Issues
544
+
545
+ **Content not updating?**
546
+ - Did you call `update()`? (Asking because 80% of the time, you didn't)
547
+ - Are your conditions/functions returning what you think they are? `console.log()` is your friend.
548
+
549
+ **List items not reusing elements?**
550
+ - Stop spreading objects like it's 2018. Mutate them.
551
+ - Item references need to be stable, not having an identity crisis on every render.
552
+
553
+ ---
554
+
555
+ ## Roadmap
556
+
557
+ - Keyed list variant (for when object identity isn't your thing)
558
+ - Transition/animation helpers (make things swoosh)
559
+ - Dev mode diagnostics (we'll yell at you when you forget `update()`)
560
+ - SSR support (because apparently servers need to render HTML now)
561
+
562
+ ---
563
+
564
+ ## License
565
+
566
+ MIT License - see [LICENSE.md](LICENSE.md) for details.
567
+
568
+ This library is free and open source. When using nuclo, please include attribution in your documentation, application, or source code. See the [LICENSE.md](LICENSE.md) file for attribution examples.
569
+
570
+ **TL;DR:** Use it freely, just give credit. It helps others discover this library!
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuclo",
3
3
  "private": false,
4
- "version": "0.1.30",
4
+ "version": "0.1.32",
5
5
  "type": "module",
6
6
  "main": "./dist/nuclo.cjs",
7
7
  "module": "./dist/nuclo.js",