bitwrench 2.0.25 → 2.0.30

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 (75) hide show
  1. package/README.md +10 -4
  2. package/dist/bitwrench-bccl.cjs.js +1 -1
  3. package/dist/bitwrench-bccl.cjs.min.js +1 -1
  4. package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
  5. package/dist/bitwrench-bccl.esm.js +1 -1
  6. package/dist/bitwrench-bccl.esm.min.js +1 -1
  7. package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
  8. package/dist/bitwrench-bccl.umd.js +1 -1
  9. package/dist/bitwrench-bccl.umd.min.js +1 -1
  10. package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
  11. package/dist/bitwrench-code-edit.cjs.js +1 -1
  12. package/dist/bitwrench-code-edit.cjs.min.js +1 -1
  13. package/dist/bitwrench-code-edit.es5.js +1 -1
  14. package/dist/bitwrench-code-edit.es5.min.js +1 -1
  15. package/dist/bitwrench-code-edit.esm.js +1 -1
  16. package/dist/bitwrench-code-edit.esm.min.js +1 -1
  17. package/dist/bitwrench-code-edit.umd.js +1 -1
  18. package/dist/bitwrench-code-edit.umd.min.js +1 -1
  19. package/dist/bitwrench-code-edit.umd.min.js.gz +0 -0
  20. package/dist/bitwrench-debug.js +1 -1
  21. package/dist/bitwrench-debug.min.js +1 -1
  22. package/dist/bitwrench-lean.cjs.js +623 -155
  23. package/dist/bitwrench-lean.cjs.min.js +7 -7
  24. package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
  25. package/dist/bitwrench-lean.es5.js +650 -157
  26. package/dist/bitwrench-lean.es5.min.js +5 -5
  27. package/dist/bitwrench-lean.es5.min.js.gz +0 -0
  28. package/dist/bitwrench-lean.esm.js +623 -155
  29. package/dist/bitwrench-lean.esm.min.js +6 -6
  30. package/dist/bitwrench-lean.esm.min.js.gz +0 -0
  31. package/dist/bitwrench-lean.umd.js +623 -155
  32. package/dist/bitwrench-lean.umd.min.js +7 -7
  33. package/dist/bitwrench-lean.umd.min.js.gz +0 -0
  34. package/dist/bitwrench-util-css.cjs.js +1 -1
  35. package/dist/bitwrench-util-css.cjs.min.js +1 -1
  36. package/dist/bitwrench-util-css.es5.js +1 -1
  37. package/dist/bitwrench-util-css.es5.min.js +1 -1
  38. package/dist/bitwrench-util-css.esm.js +1 -1
  39. package/dist/bitwrench-util-css.esm.min.js +1 -1
  40. package/dist/bitwrench-util-css.umd.js +1 -1
  41. package/dist/bitwrench-util-css.umd.min.js +1 -1
  42. package/dist/bitwrench-util-css.umd.min.js.gz +0 -0
  43. package/dist/bitwrench.cjs.js +621 -153
  44. package/dist/bitwrench.cjs.min.js +6 -6
  45. package/dist/bitwrench.cjs.min.js.gz +0 -0
  46. package/dist/bitwrench.css +1 -1
  47. package/dist/bitwrench.d.ts +18 -11
  48. package/dist/bitwrench.es5.js +647 -154
  49. package/dist/bitwrench.es5.min.js +6 -6
  50. package/dist/bitwrench.es5.min.js.gz +0 -0
  51. package/dist/bitwrench.esm.js +621 -153
  52. package/dist/bitwrench.esm.min.js +5 -5
  53. package/dist/bitwrench.esm.min.js.gz +0 -0
  54. package/dist/bitwrench.umd.js +621 -153
  55. package/dist/bitwrench.umd.min.js +6 -6
  56. package/dist/bitwrench.umd.min.js.gz +0 -0
  57. package/dist/builds.json +95 -95
  58. package/dist/bwserve.cjs.js +140 -7
  59. package/dist/bwserve.esm.js +141 -8
  60. package/dist/sri.json +45 -45
  61. package/docs/bitwrench-for-wasm.md +851 -0
  62. package/docs/bitwrench_api.md +133 -23
  63. package/docs/llm-bitwrench-guide.md +6 -5
  64. package/docs/state-management.md +27 -3
  65. package/docs/thinking-in-bitwrench.md +3 -2
  66. package/package.json +11 -9
  67. package/readme.html +17 -8
  68. package/src/bitwrench.d.ts +18 -11
  69. package/src/bitwrench.js +617 -148
  70. package/src/bwserve/bwclient.js +3 -3
  71. package/src/bwserve/client.js +26 -0
  72. package/src/bwserve/index.js +110 -3
  73. package/src/cli/attach.js +7 -5
  74. package/src/cli/serve.js +53 -10
  75. package/src/version.js +3 -3
@@ -4,27 +4,29 @@
4
4
 
5
5
  | Field | Value |
6
6
  |-------|-------|
7
- | Version | 2.0.25 |
8
- | Generated | 2026-03-31 |
9
- | Total APIs | 100 |
10
- | Categories | 12 |
11
- | bitwrench.js | 3631 lines |
7
+ | Version | 2.0.30 |
8
+ | Generated | 2026-04-12 |
9
+ | Total APIs | 105 |
10
+ | Categories | 14 |
11
+ | bitwrench.js | 4100 lines |
12
12
  | bitwrench-bccl.js | 3793 lines |
13
13
 
14
14
  ## Table of Contents
15
15
 
16
16
  - [Core](#core) (5)
17
17
  - [DOM Generation](#dom-generation) (10)
18
+ - [DOM Selection](#dom-selection) (1)
18
19
  - [Identifiers](#identifiers) (4)
19
20
  - [State Management](#state-management) (3)
20
21
  - [Events (DOM)](#events-dom-) (2)
21
- - [Pub/Sub](#pub-sub) (3)
22
+ - [Pub/Sub](#pub-sub) (4)
22
23
  - [CSS & Styling](#css-styling) (10)
23
24
  - [Component Builders](#component-builders) (50)
24
25
  - [Browser Utilities](#browser-utilities) (4)
25
26
  - [Utilities](#utilities) (1)
26
27
  - [Function Registry](#function-registry) (5)
27
- - [Component](#component) (3)
28
+ - [Component](#component) (5)
29
+ - [Data Utilities](#data-utilities) (1)
28
30
 
29
31
  ---
30
32
 
@@ -293,6 +295,28 @@ Get all registered component handles as a Map.
293
295
 
294
296
  ---
295
297
 
298
+ ## DOM Selection
299
+
300
+ ### `bw.el(target, apply)`
301
+
302
+ Look up a single DOM element by ID, CSS selector, UUID, or element ref. Optionally apply content or a function to the resolved element. Resolution order for string targets: 1. Check `bw._nodeMap[id]` cache (O(1), stale entries auto-pruned) 2. `document.getElementById(id)` 3. `document.querySelector(id)` for selectors starting with # or . 4. Class-based lookup for `bw_uuid_*` tokens With one argument, returns the element (or null). With two arguments, applies the second argument to the element and returns the element: - string/number: sets `el.textContent` - function: calls `apply(el)`, returns el - TACO object: clears children, mounts TACO via `bw.createDOM()` - array: clears children, appends each item (string -> text node, TACO -> element)
303
+
304
+ **Parameters:**
305
+
306
+ | Name | Type | Description |
307
+ |------|------|-------------|
308
+ | `target` | `string|Element` | - Element ref, ID, CSS selector, or bw_uuid_* class |
309
+ | `apply` | `string|number|Function|Object|Array` | - Content or function to apply |
310
+
311
+ **Returns:** `Element|null` — DOM element, or null if not found
312
+
313
+ **Example:**
314
+ ```javascript
315
+ bw.el('#title') // lookup bw.el('#title', 'Hello') // set text content bw.el('#app', { t: 'h1', c: 'Hi' }) // mount TACO bw.el('.card', function(el) { // apply function el.style.opacity = '0.5'; })
316
+ ```
317
+
318
+ ---
319
+
296
320
  ## Identifiers
297
321
 
298
322
  ### `bw.uuid(prefix)`
@@ -488,32 +512,32 @@ Publish to a topic, calling all subscribers in registration order. Application-s
488
512
  | `topic` | `string` | - Topic name (plain string, no prefix) |
489
513
  | `detail` | `*` | - Data to pass to subscribers |
490
514
 
491
- **Returns:** `number` — of successfully called subscribers
515
+ **Returns:** `number` — of successfully called subscribers (including wildcard matches)
492
516
 
493
517
  **Example:**
494
518
  ```javascript
495
- bw.pub('score:updated', { player: 'X', score: 10 });
519
+ bw.pub('score:updated', { player: 'X', score: 10 }); // Wildcard subscribers matching 'score:*' will also fire
496
520
  ```
497
521
 
498
522
  ---
499
523
 
500
524
  ### `bw.sub(topic, handler, el)`
501
525
 
502
- Subscribe to a topic. Returns an unsub() function. Optional third argument ties the subscription to a DOM element's lifecycle when `bw.cleanup()` is called on that element, the subscription is automatically removed, preventing memory leaks.
526
+ Subscribe to a topic. Returns an unsub() function. Supports wildcard patterns: a topic ending in `*` matches any published topic that starts with the prefix before the `*`. For example, `'agui:*'` matches `'agui:ready'`, `'agui:error'`, etc. The handler receives `(detail, topic)` so it can distinguish which topic fired. Optional third argument ties the subscription to a DOM element's lifecycle -- when `bw.cleanup()` is called on that element, the subscription is automatically removed, preventing memory leaks.
503
527
 
504
528
  **Parameters:**
505
529
 
506
530
  | Name | Type | Description |
507
531
  |------|------|-------------|
508
- | `topic` | `string` | - Topic name |
509
- | `handler` | `Function` | - Called with (detail) on each publish |
532
+ | `topic` | `string` | - Topic name, or wildcard pattern ending in '*' |
533
+ | `handler` | `Function` | - Called with (detail, topic) on each publish |
510
534
  | `el` | `Element` | - Optional DOM element to tie lifecycle to |
511
535
 
512
536
  **Returns:** `Function` — to unsubscribe
513
537
 
514
538
  **Example:**
515
539
  ```javascript
516
- var unsub = bw.sub('score:updated', function(detail) { console.log(detail.player, 'scored', detail.score); }); // Later: unsub() to stop listening
540
+ var unsub = bw.sub('score:updated', function(detail) { console.log(detail.player, 'scored', detail.score); }); // Later: unsub() to stop listening // Wildcard: listen to all 'agui:' topics bw.sub('agui:*', function(detail, topic) { console.log('Got', topic, detail); });
517
541
  ```
518
542
 
519
543
  ---
@@ -533,6 +557,27 @@ Unsubscribe a handler by reference from a topic. Removes ALL instances of the gi
533
557
 
534
558
  ---
535
559
 
560
+ ### `bw.once(topic, handler, el)`
561
+
562
+ Subscribe to a topic for a single event only. The subscription is automatically removed after the first publish. Equivalent to manually calling unsub() inside a bw.sub() handler, but avoids the common bug of forgetting to unsubscribe.
563
+
564
+ **Parameters:**
565
+
566
+ | Name | Type | Description |
567
+ |------|------|-------------|
568
+ | `topic` | `string` | - Topic name |
569
+ | `handler` | `Function` | - Called once with (detail) on the next publish |
570
+ | `el` | `Element` | - Optional DOM element to tie lifecycle to |
571
+
572
+ **Returns:** `Function` — to cancel the subscription before it fires
573
+
574
+ **Example:**
575
+ ```javascript
576
+ bw.once('data:loaded', function(detail) { console.log('Received:', detail); // No need to unsubscribe -- already done automatically }); // Cancel before it fires: var cancel = bw.once('timeout', handler); cancel(); // handler will never be called
577
+ ```
578
+
579
+ ---
580
+
536
581
  ## CSS & Styling
537
582
 
538
583
  ### `bw.css(rules, options = {})`
@@ -694,21 +739,21 @@ bw.loadReset(); // inject once, safe to call multiple times
694
739
 
695
740
  ---
696
741
 
697
- ### `bw.toggleStyles(scope)`
742
+ ### `bw.toggleThemeMode(scope)`
698
743
 
699
- Toggle between primary and alternate palettes. Adds/removes the `bw_theme_alt` class on the scoping element. Without a scope, toggles on `<html>` (global). With a scope, toggles on the first matching element.
744
+ Toggle between primary and alternate theme palettes. Adds/removes the `bw_theme_alt` class on the scoping element(s). Without a scope, toggles on `<html>` (global). With a scope, toggles on ALL matching elements.
700
745
 
701
746
  **Parameters:**
702
747
 
703
748
  | Name | Type | Description |
704
749
  |------|------|-------------|
705
- | `scope` | `string` | - Scope selector (e.g. '#my-dashboard'). Omit for global. |
750
+ | `scope` | `string|Element` | - Selector or element. Omit for global. |
706
751
 
707
- **Returns:** `string` — mode after toggle: 'primary' or 'alternate'
752
+ **Returns:** `string` — mode after toggle: 'primary' or 'alternate' (based on first element)
708
753
 
709
754
  **Example:**
710
755
  ```javascript
711
- bw.toggleStyles(); // global toggle on <html> bw.toggleStyles('#my-dashboard'); // scoped toggle
756
+ bw.toggleThemeMode(); // global toggle on <html> bw.toggleThemeMode('#my-dashboard'); // scoped toggle bw.toggleThemeMode('.panel'); // toggle on ALL .panel elements
712
757
  ```
713
758
 
714
759
  ---
@@ -2212,21 +2257,86 @@ bw.message('my_carousel', 'goToSlide', 2); // Or from SSE handler: es.onmessage
2212
2257
 
2213
2258
  ---
2214
2259
 
2215
- ### `bw.inspect(target)`
2260
+ ### `bw.formData(target)`
2261
+
2262
+ Collect form data from all input, select, and textarea elements within a container. Each element's `name` attribute (or `id` if no name) becomes a key in the returned object. This provides a lightweight alternative to the browser FormData API that returns a plain object suitable for JSON serialization or bw.pub(). Handles all standard HTML form controls: - text/number/email/etc inputs: string value - checkboxes: boolean (true/false) - radio buttons: string value of the checked radio (unchecked groups omitted) - multi-select: array of selected option values - textarea: string value Elements without both `name` and `id` attributes are silently skipped.
2263
+
2264
+ **Parameters:**
2265
+
2266
+ | Name | Type | Description |
2267
+ |------|------|-------------|
2268
+ | `target` | `string|Element` | - CSS selector, UUID string, or DOM element |
2269
+
2270
+ **Returns:** `Object` — object mapping field names to values
2271
+
2272
+ **Example:**
2273
+ ```javascript
2274
+ // Given a form with name="email" input and name="agree" checkbox: var data = bw.formData('#signup-form'); // => { email: 'user@example.com', agree: true } // Collect and publish in one step: bw.pub('form:submit', bw.formData('#my-form')); // Works with any container, not just <form>: bw.pub('settings:changed', bw.formData('.settings-panel'));
2275
+ ```
2276
+
2277
+ ---
2278
+
2279
+ ### `bw.inspect(target, depth)`
2280
+
2281
+ Inspect a DOM element and its subtree, returning a plain-object representation with bitwrench metadata at each node. Useful for debugging, devtools, MCP/AG-UI tool discovery, and automated testing. Each node in the returned tree includes: - `tag` -- lowercase tag name (or '#text' for text nodes) - `id` -- element id (if set) - `uuid` -- bitwrench UUID class (if lifecycle-managed) - `type` -- component type from o.type (if set, e.g. 'card', 'tabs') - `classes` -- first 5 CSS classes (string, space-separated) - `handles` -- array of el.bw method names (if any) - `state` -- copy of _bw_state (if any) - `hasRender` -- true if _bw_render is set - `hasSubs` -- true if element has pub/sub subscriptions - `refs` -- copy of _bw_refs keys (if any) - `children` -- array of child node trees (up to depth limit, max 50 per level)
2282
+
2283
+ **Parameters:**
2284
+
2285
+ | Name | Type | Description |
2286
+ |------|------|-------------|
2287
+ | `target` | `string|Element` | - CSS selector, UUID, or DOM element |
2288
+ | `depth` | `number` | - Maximum recursion depth (0 = target only, no children) |
2289
+
2290
+ **Returns:** `Object|null` — object tree, or null if element not found
2291
+
2292
+ **Example:**
2293
+ ```javascript
2294
+ // Get full tree from #app, 3 levels deep (default): var info = bw.inspect('#app'); // Shallow inspection (just the element, no children): var info = bw.inspect('#my-carousel', 0); console.log(info.handles); // ['next', 'prev', 'goToSlide'] console.log(info.type); // 'carousel' // Deep inspection for debugging: console.log(JSON.stringify(bw.inspect('#app', 5), null, 2));
2295
+ ```
2296
+
2297
+ ---
2298
+
2299
+ ### `bw.catalog(type)`
2300
+
2301
+ Query the BCCL component registry. Returns metadata about registered component types -- their names and factory function names. Useful for tooling, introspection, documentation generators, and auto-complete systems (including MCP/AG-UI tool discovery). With no arguments, returns an array of all registered component types. With a type name, returns metadata for that single type (or null if the type is not registered).
2302
+
2303
+ **Parameters:**
2304
+
2305
+ | Name | Type | Description |
2306
+ |------|------|-------------|
2307
+ | `type` | `string` | - Optional component type name to look up |
2308
+
2309
+ **Returns:** `Array<Object>|Object|null` — of {type, factory} objects, a single {type, factory} object, or null if the type is not found
2310
+
2311
+ **Example:**
2312
+ ```javascript
2313
+ // List all available component types: bw.catalog(); // => [{ type: 'card', factory: 'makeCard' }, // { type: 'button', factory: 'makeButton' }, ...] // Look up a specific type: bw.catalog('accordion'); // => { type: 'accordion', factory: 'makeAccordion' } // Check if a type exists: if (bw.catalog('chart')) { ... } // Get just the type names: bw.catalog().map(function(c) { return c.type; }); // => ['card', 'button', 'container', 'row', ...]
2314
+ ```
2315
+
2316
+ ---
2317
+
2318
+ ## Data Utilities
2319
+
2320
+ ### `bw.jsonPatch(obj, ops)`
2216
2321
 
2217
- Inspect a DOM element's bitwrench state, handle methods, and metadata. Works with DOM elements or CSS selectors.
2322
+ Apply RFC 6902 JSON Patch operations to a plain object. Supported operations: add, remove, replace, move, copy, test. Paths use JSON Pointer (RFC 6901) notation: `/foo/bar/0`. Mutates the target object in place and returns it.
2218
2323
 
2219
2324
  **Parameters:**
2220
2325
 
2221
2326
  | Name | Type | Description |
2222
2327
  |------|------|-------------|
2223
- | `target` | `string|Element` | - Selector or DOM element |
2328
+ | `obj` | `Object` | - Target object to patch |
2329
+ | `ops` | `Array<Object>` | - Array of patch operations |
2330
+ | `ops[].op` | `string` | - Operation: 'add', 'remove', 'replace', 'move', 'copy', 'test' |
2331
+ | `ops[].path` | `string` | - JSON Pointer path (e.g. '/a/b/0') |
2332
+ | `ops[].value` | `*` | - Value for add/replace/test |
2333
+ | `ops[].from` | `string` | - Source path for move/copy |
2224
2334
 
2225
- **Returns:** `Element|null` — element, or null if not found
2335
+ **Returns:** `Object` — patched object (same reference)
2226
2336
 
2227
2337
  **Example:**
2228
2338
  ```javascript
2229
- bw.inspect('#my-carousel'); bw.inspect($0);
2339
+ var obj = { a: 1, b: { c: 2 } }; bw.jsonPatch(obj, [ { op: 'replace', path: '/a', value: 10 }, { op: 'add', path: '/b/d', value: 3 }, { op: 'remove', path: '/b/c' } ]); // obj => { a: 10, b: { d: 3 } }
2230
2340
  ```
2231
2341
 
2232
2342
  ---
@@ -406,7 +406,7 @@ card.bw.setContent({ t: 'b', c: '$42k' });
406
406
  var el = bw.$('#app')[0];
407
407
  el._bw_state; // current state
408
408
  el._bw_render; // render function
409
- bw.inspect(el); // formatted debug output (el.bw methods, state, classes)
409
+ bw.inspect(el, 0); // introspect element (state, handles, type, classes)
410
410
  ```
411
411
 
412
412
  ### bwcli attach -- remote debugging REPL
@@ -612,13 +612,14 @@ bwcli serve # dev server (port 7902)
612
612
  | `bw.mount(sel, taco)` | Mount + return root element |
613
613
  | `bw.cleanup(el)` | Run unmount hooks, clear subscriptions |
614
614
  | `bw.patch(id, content)` | Update element by id or UUID |
615
- | `bw.inspect(el)` | Debug: log el.bw methods, state, classes |
615
+ | `bw.inspect(el, depth)` | Introspect DOM subtree with bitwrench metadata |
616
616
 
617
617
  ### Communication
618
618
  | Function | Description |
619
619
  |----------|-------------|
620
- | `bw.pub(topic, data)` | App-wide publish |
621
- | `bw.sub(topic, fn, el?)` | Subscribe (optional lifecycle tie to element) |
620
+ | `bw.pub(topic, data)` | App-wide publish (fires exact + wildcard matches) |
621
+ | `bw.sub(topic, fn, el?)` | Subscribe (supports wildcard `'ns:*'`; optional lifecycle tie) |
622
+ | `bw.once(topic, fn, el?)` | One-shot subscribe (auto-unsub after first fire) |
622
623
  | `bw.message(target, action, data)` | Dispatch to `el.bw[action](data)` |
623
624
  | `bw.emit(el, event, detail)` | DOM-scoped CustomEvent |
624
625
 
@@ -658,7 +659,7 @@ bwcli serve # dev server (port 7902)
658
659
  9. **CSS classes use `bw-` prefix**: `bw-card`, `bw-btn`, `bw-container`.
659
660
  10. **Routing is built in** -- `bw.router()` for SPAs. Hash mode by default, history mode optional.
660
661
  11. **Use `bw.mount()` + `el.bw`** for targeted updates. `o.handle` for methods, `o.slots` for content areas. Avoids re-render side effects (lost focus, scroll reset).
661
- 12. **Debug**: `bw.inspect(el)`, `el._bw_state`, `bwcli attach` for remote REPL.
662
+ 12. **Debug**: `bw.inspect(el, 0)`, `el._bw_state`, `bwcli attach` for remote REPL.
662
663
 
663
664
  ---
664
665
 
@@ -567,6 +567,20 @@ var results = {
567
567
 
568
568
  Pub/sub is app-scoped -- publishers and subscribers do not need to know about each other. Pass the element as the third argument to `bw.sub()` to tie the subscription's lifetime to the element (auto-cleaned on `bw.cleanup()`).
569
569
 
570
+ Wildcard subscriptions let you listen to a group of related topics at once:
571
+
572
+ ```javascript
573
+ // Listen to ALL search-related topics
574
+ bw.sub('search:*', function(detail, topic) {
575
+ console.log('Search event:', topic, detail);
576
+ }, el);
577
+
578
+ // These all fire the wildcard handler above:
579
+ bw.pub('search:changed', { query: 'foo' });
580
+ bw.pub('search:cleared');
581
+ bw.pub('search:submitted', { query: 'foo' });
582
+ ```
583
+
570
584
  ### Updating child widgets within a parent component
571
585
 
572
586
  When a parent component contains child sub-components (like a progress bar inside a dashboard card), use pub/sub to update the child:
@@ -622,8 +636,9 @@ These primitives are the building blocks of the stateful TACO model. They are al
622
636
  | `bw.uuid(prefix)` | Generate a UUID class for addressing |
623
637
  | `bw.emit(el, event, detail)` | Dispatch a CustomEvent on a DOM element |
624
638
  | `bw.on(el, event, handler)` | Listen for a CustomEvent on a DOM element |
625
- | `bw.pub(topic, detail)` | Publish to app-wide topic |
626
- | `bw.sub(topic, handler, el?)` | Subscribe to app-wide topic |
639
+ | `bw.pub(topic, detail)` | Publish to app-wide topic (fires exact + wildcard matches) |
640
+ | `bw.sub(topic, handler, el?)` | Subscribe to topic (supports wildcard `'ns:*'` patterns) |
641
+ | `bw.once(topic, handler, el?)` | One-shot subscribe (auto-unsub after first fire) |
627
642
  | `bw.unsub(topic, handler)` | Unsubscribe from topic |
628
643
  | `bw.cleanup(el)` | Run unmount hooks and clear subscriptions |
629
644
 
@@ -725,6 +740,15 @@ bw.sub('store:todos', renderTodos, todosEl);
725
740
  bw.sub('store:projects', renderProjects, projectsEl);
726
741
  ```
727
742
 
743
+ If you need a global listener (e.g. for logging or debug), use a wildcard:
744
+
745
+ ```javascript
746
+ // OK for debug/logging -- not for rendering
747
+ bw.sub('store:*', function(data, topic) {
748
+ console.log('[store]', topic, data);
749
+ });
750
+ ```
751
+
728
752
  ### When to use
729
753
 
730
754
  - Multi-view SPAs where views share data
@@ -758,7 +782,7 @@ bw.router({
758
782
  | Server pushes UI updates | Level 1 -- `bw.patch()` / `bw.DOM()` |
759
783
  | Components need to talk to each other | `bw.pub()`/`bw.sub()` |
760
784
  | URL-driven views (SPA) | `bw.router()` -- see [Routing](routing.md) |
761
- | Debugging component state | `el._bw_state` in the console, or `bw.inspect(selector)` |
785
+ | Debugging component state | `el._bw_state` in the console, or `bw.inspect(selector, 0)` |
762
786
 
763
787
  ---
764
788
 
@@ -1381,9 +1381,10 @@ Key things this example proves:
1381
1381
 
1382
1382
  | Function | What it does |
1383
1383
  |----------|-------------|
1384
- | `bw.pub(topic, data)` | Publish to all subscribers |
1385
- | `bw.sub(topic, fn)` | Subscribe (returns unsub function) |
1384
+ | `bw.pub(topic, data)` | Publish to all subscribers (exact + wildcard) |
1385
+ | `bw.sub(topic, fn)` | Subscribe (returns unsub function; supports wildcard `'ns:*'`) |
1386
1386
  | `bw.sub(topic, fn, owner)` | Subscribe with auto-cleanup when owner is removed |
1387
+ | `bw.once(topic, fn, el?)` | One-shot subscribe (auto-unsub after first fire) |
1387
1388
 
1388
1389
  ### Routing
1389
1390
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitwrench",
3
- "version": "2.0.25",
3
+ "version": "2.0.30",
4
4
  "description": "A library for javascript UI functions.",
5
5
  "main": "./dist/bitwrench.umd.js",
6
6
  "repository": {
@@ -63,32 +63,34 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@babel/core": "^7.25.2",
66
- "@babel/preset-env": "^7.25.3",
66
+ "@babel/preset-env": "^7.29.2",
67
67
  "@babel/preset-react": "^7.24.7",
68
- "@playwright/test": "^1.54.1",
68
+ "@eslint/js": "^10.0.1",
69
+ "@playwright/test": "^1.59.1",
69
70
  "@rollup/plugin-babel": "^6.0.4",
70
71
  "@rollup/plugin-commonjs": "^26.0.1",
71
72
  "@rollup/plugin-node-resolve": "^15.2.3",
72
73
  "@rollup/plugin-terser": "^1.0.0",
73
74
  "c8": "^8.0.1",
74
75
  "chai": "^3.0.0",
75
- "comment-parser": "^1.4.5",
76
- "eslint": "^8.28.0",
77
- "jsdom": "^25.0.0",
76
+ "comment-parser": "^1.4.6",
77
+ "eslint": "^10.2.0",
78
+ "globals": "^17.4.0",
79
+ "jsdom": "^26.1.0",
78
80
  "jsdom-global": "3.0.2",
79
81
  "karma": "^6.3.16",
80
82
  "karma-chai": "^0.1.0",
81
83
  "karma-chrome-launcher": "^3.1.0",
82
84
  "karma-cli": "^2.0.0",
83
85
  "karma-coverage": "^2.0.3",
84
- "karma-coverage-istanbul-reporter": "^2.1.1",
86
+ "karma-coverage-istanbul-reporter": "^3.0.3",
85
87
  "karma-firefox-launcher": "^1.3.0",
86
88
  "karma-ie-launcher": "^1.0.0",
87
89
  "karma-mocha": "^2.0.1",
88
90
  "mocha": "^11.0.0",
89
91
  "pixelmatch": "^7.1.0",
90
92
  "pngjs": "^7.0.0",
91
- "rollup": "^4.29.1",
93
+ "rollup": "^4.60.1",
92
94
  "rollup-plugin-css-only": "^4.5.2",
93
95
  "rollup-plugin-postcss": "^4.0.2",
94
96
  "uglify-js": "^3.19.3",
@@ -105,7 +107,7 @@
105
107
  "update_rm": "echo 'DEPRECATED: use build:index instead'",
106
108
  "cleanbuild": "npm run clean && npm run build && npm run build:generated",
107
109
  "oldtest": "./node_modules/mocha/bin/mocha test/bitwrench_test.js --reporter spec",
108
- "test": "c8 --reporter=text --reporter=json-summary mocha ./test/bitwrench_ci.js ./test/bitwrench_test_coverage.js ./test/bitwrench_test_pubsub.js ./test/bitwrench_test_theme.js ./test/bitwrench_test_nodemap.js ./test/bitwrench_test_components.js ./test/bitwrench_test_coverage_gaps.js ./test/bitwrench_test_bwserve.js ./test/bitwrench_test_attach.js ./test/bitwrench_test_serve.js ./test/bitwrench_test_code_edit.js ./test/bitwrench_test_html_page.js ./test/bitwrench_test_util_css.js ./test/bitwrench_test_handle.js ./test/bitwrench_test_debug.js ./test/bitwrench_test_router.js ./test/bitwrench_test_mcp_transport.js ./test/bitwrench_test_mcp_server.js ./test/bitwrench_test_mcp_tools.js ./test/bitwrench_test_mcp_knowledge.js ./test/bitwrench_test_mcp_live.js -r jsdom-global/register --exit",
110
+ "test": "c8 --reporter=text --reporter=json-summary mocha ./test/bitwrench_ci.js ./test/bitwrench_test_coverage.js ./test/bitwrench_test_pubsub.js ./test/bitwrench_test_theme.js ./test/bitwrench_test_nodemap.js ./test/bitwrench_test_components.js ./test/bitwrench_test_coverage_gaps.js ./test/bitwrench_test_bwserve.js ./test/bitwrench_test_attach.js ./test/bitwrench_test_serve.js ./test/bitwrench_test_code_edit.js ./test/bitwrench_test_html_page.js ./test/bitwrench_test_util_css.js ./test/bitwrench_test_handle.js ./test/bitwrench_test_debug.js ./test/bitwrench_test_router.js ./test/bitwrench_test_mcp_transport.js ./test/bitwrench_test_mcp_server.js ./test/bitwrench_test_mcp_tools.js ./test/bitwrench_test_mcp_knowledge.js ./test/bitwrench_test_mcp_live.js ./test/bitwrench_test_new_apis.js -r jsdom-global/register --exit",
109
111
  "test:bwserve": "mocha ./test/bitwrench_test_bwserve.js -r jsdom-global/register",
110
112
  "test:attach": "mocha ./test/bitwrench_test_attach.js -r jsdom-global/register",
111
113
  "test:serve": "mocha ./test/bitwrench_test_serve.js -r jsdom-global/register --exit",
package/readme.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta name="generator" content="bitwrench v2.0.25">
6
+ <meta name="generator" content="bitwrench v2.0.30">
7
7
  <title>bitwrench.js - README</title>
8
8
  <link rel="icon" type="image/x-icon" href="images/favicon.ico">
9
9
  <script src="dist/bitwrench.umd.min.js"></script>
@@ -72,7 +72,7 @@
72
72
  <a class="quikdown-a" href="https://opensource.org/licenses/BSD-2-Clause" rel="noopener noreferrer"><img class="quikdown-img" src="https://img.shields.io/badge/License-BSD%202--Clause-blue.svg" alt="License"></a>
73
73
  <a class="quikdown-a" href="https://www.npmjs.com/package/bitwrench" rel="noopener noreferrer"><img class="quikdown-img" src="https://img.shields.io/npm/v/bitwrench.svg?style=flat-square" alt="NPM version"></a>
74
74
  <a class="quikdown-a" href="https://github.com/deftio/bitwrench/actions/workflows/ci.yml" rel="noopener noreferrer"><img class="quikdown-img" src="https://github.com/deftio/bitwrench/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
75
- <a class="quikdown-a" href="https://github.com/deftio/bitwrench" rel="noopener noreferrer"><img class="quikdown-img" src="https://img.shields.io/badge/coverage-97.3%25-brightgreen.svg" alt="Coverage"></a></p><p><a class="quikdown-a" href="https://deftio.github.io/bitwrench/pages/" rel="noopener noreferrer"><img class="quikdown-img" src="./images/bitwrench-logo-med.png" alt="bitwrench"></a></p><p>Bitwrench builds UI from plain JavaScript objects -- one format for components, styling, state, and server rendering, with no build step and zero dependencies.</p><pre class="quikdown-pre"><code class="language-javascript">// Describe UI as a JavaScript object (a &quot;TACO&quot;)
75
+ <a class="quikdown-a" href="https://github.com/deftio/bitwrench" rel="noopener noreferrer"><img class="quikdown-img" src="https://img.shields.io/badge/coverage-97.6%25-brightgreen.svg" alt="Coverage"></a></p><p><a class="quikdown-a" href="https://deftio.github.io/bitwrench/pages/" rel="noopener noreferrer"><img class="quikdown-img" src="./images/bitwrench-logo-med.png" alt="bitwrench"></a></p><p>Bitwrench builds UI from plain JavaScript objects -- one format for components, styling, state, and server rendering, with no build step and zero dependencies.</p><pre class="quikdown-pre"><code class="language-javascript">// Describe UI as a JavaScript object (a &quot;TACO&quot;)
76
76
  var page = {
77
77
  t: &#39;div&#39;, a: { class: &#39;card&#39; },
78
78
  c: [
@@ -208,7 +208,12 @@ bw.DOM(&#39;#app&#39;, counter);</code></pre><blockquote class="quikdown-blockqu
208
208
  console.log(&#39;New item:&#39;, detail.name);
209
209
  });
210
210
 
211
- bw.pub(&#39;item-added&#39;, { name: &#39;Widget&#39; });</code></pre><h2 class="quikdown-h2">CSS from JavaScript</h2>
211
+ bw.pub(&#39;item-added&#39;, { name: &#39;Widget&#39; });
212
+
213
+ // Wildcard: listen to a group of related topics
214
+ bw.sub(&#39;item:*&#39;, function(detail, topic) {
215
+ console.log(topic, detail); // e.g. &#39;item:added&#39;, &#39;item:removed&#39;
216
+ });</code></pre><h2 class="quikdown-h2">CSS from JavaScript</h2>
212
217
  <p><code class="quikdown-code">bw.css()</code> generates CSS from objects. <code class="quikdown-code">bw.s()</code> composes inline styles from reusable utility objects:</p><pre class="quikdown-pre"><code class="language-javascript">// Generate and inject a stylesheet
213
218
  bw.injectCSS(bw.css({
214
219
  &#39;.my-card&#39;: { padding: &#39;1rem&#39;, borderRadius: &#39;8px&#39; }
@@ -298,15 +303,19 @@ bw.toggleStyles(); // switch between primary and alternate palettes</code></pre
298
303
  </tr>
299
304
  <tr class="quikdown-tr">
300
305
  <td class="quikdown-td"><code class="quikdown-code">bw.pub(topic, detail)</code></td>
301
- <td class="quikdown-td">Publish a message to subscribers</td>
306
+ <td class="quikdown-td">Publish to subscribers (exact + wildcard matches)</td>
307
+ </tr>
308
+ <tr class="quikdown-tr">
309
+ <td class="quikdown-td"><code class="quikdown-code">bw.sub(topic, handler, el?)</code></td>
310
+ <td class="quikdown-td">Subscribe to topic (supports wildcard <code class="quikdown-code">&#39;ns:*&#39;</code>); returns unsub function</td>
302
311
  </tr>
303
312
  <tr class="quikdown-tr">
304
- <td class="quikdown-td"><code class="quikdown-code">bw.sub(topic, handler)</code></td>
305
- <td class="quikdown-td">Subscribe to a topic; returns an unsub function</td>
313
+ <td class="quikdown-td"><code class="quikdown-code">bw.once(topic, handler, el?)</code></td>
314
+ <td class="quikdown-td">One-shot subscribe; auto-unsub after first fire</td>
306
315
  </tr>
307
316
  <tr class="quikdown-tr">
308
- <td class="quikdown-td"><code class="quikdown-code">bw.inspect(target)</code></td>
309
- <td class="quikdown-td">Debug a component&#39;s state, handles, and metadata in the console</td>
317
+ <td class="quikdown-td"><code class="quikdown-code">bw.inspect(target, depth)</code></td>
318
+ <td class="quikdown-td">Introspect a DOM subtree with bitwrench metadata (state, handles, type)</td>
310
319
  </tr>
311
320
  <tr class="quikdown-tr">
312
321
  <td class="quikdown-td"><code class="quikdown-code">bw.apply(msg)</code></td>
@@ -315,8 +315,8 @@ export interface Bitwrench {
315
315
  html(taco: Taco | TacoContent, options?: { raw?: boolean; state?: Record<string, any> }): string;
316
316
  /** Generate complete HTML page string */
317
317
  htmlPage(opts: { title?: string; css?: string; content?: TacoContent; favicon?: string; [key: string]: any }): string;
318
- /** Create DOM element from TACO (browser only) */
319
- createDOM(taco: Taco | TacoContent, options?: Record<string, any>): HTMLElement | DocumentFragment;
318
+ /** Create DOM element from TACO (browser only). SVG TACOs ({t:'svg',...}) use createElementNS. */
319
+ createDOM(taco: Taco | TacoContent, options?: Record<string, any>): HTMLElement | SVGElement | DocumentFragment;
320
320
  /** Mount TACO into target, replacing contents */
321
321
  DOM(target: string | HTMLElement, taco: Taco | TacoContent, options?: Record<string, any>): void;
322
322
  /** Mount TACO and return root element (for el.bw access) */
@@ -331,24 +331,28 @@ export interface Bitwrench {
331
331
  update(target: string | HTMLElement): void;
332
332
  /** Quick-patch element content or attribute */
333
333
  patch(id: string | HTMLElement, content?: TacoContent, attr?: string): HTMLElement | null;
334
+ /** RFC 6902 JSON Patch on plain objects. Mutates and returns obj. @see bw.patch */
335
+ jsonPatch(obj: object, ops: Array<{ op: string; path: string; value?: any; from?: string }>): object;
334
336
  /** Batch patch multiple elements */
335
337
  patchAll(patches: Record<string, TacoContent> | Array<{ id: string; content?: TacoContent; attr?: string }>): Record<string, HTMLElement>;
336
338
  /** Clean up lifecycle hooks, subscriptions, cache */
337
339
  cleanup(element: HTMLElement): void;
338
340
 
339
341
  // -- DOM Selection --------------------------------------------------------
340
- /** CSS selector to array of elements */
341
- $(selector: string | HTMLElement | HTMLElement[]): HTMLElement[];
342
+ /** Resolve target to first matching element. Optional apply: string (textContent), function, TACO (mount), or array. @see bw.$ */
343
+ el(target: string | HTMLElement | null, apply?: string | number | boolean | Function | object | any[]): HTMLElement | null;
344
+ /** CSS selector to array of elements. Optional apply applied to each. @see bw.el */
345
+ $(selector: string | HTMLElement | HTMLElement[], apply?: string | number | boolean | Function | object | any[]): HTMLElement[];
342
346
  /** Dispatch DOM event */
343
347
  emit(target: string | HTMLElement, eventName: string, detail?: any): void;
344
348
  /** Add event listener */
345
349
  on(target: string | HTMLElement, eventName: string, handler: (e: Event) => void): void;
346
350
 
347
351
  // -- Pub/Sub --------------------------------------------------------------
348
- /** Publish to topic */
352
+ /** Publish to topic. Fires exact-match and wildcard subscribers. */
349
353
  pub(topic: string, detail?: any): number;
350
- /** Subscribe to topic; returns unsub() */
351
- sub(topic: string, handler: (detail: any) => void, el?: HTMLElement): () => void;
354
+ /** Subscribe to topic (or wildcard pattern ending in '*'); returns unsub(). Handler receives (detail, topic). */
355
+ sub(topic: string, handler: (detail: any, topic?: string) => void, el?: HTMLElement): () => void;
352
356
  /** Unsubscribe handler from topic */
353
357
  unsub(topic: string, handler: Function): number;
354
358
 
@@ -365,8 +369,8 @@ export interface Bitwrench {
365
369
  message(target: string | HTMLElement, action: string, data?: any): any;
366
370
  /** Execute wire-protocol message object */
367
371
  apply(msg: Record<string, any>): any;
368
- /** Inspect element properties */
369
- inspect(target: string | HTMLElement): Record<string, any>;
372
+ /** Inspect DOM element and subtree, returning plain-object tree with bitwrench metadata */
373
+ inspect(target: string | HTMLElement, depth?: number): Record<string, any> | null;
370
374
 
371
375
  // -- Function Registry ----------------------------------------------------
372
376
  funcRegister(fn: Function, name?: string): string;
@@ -395,8 +399,10 @@ export interface Bitwrench {
395
399
  loadStyles(config?: StyleConfig, scope?: string): StylesResult | void;
396
400
  /** Load CSS reset */
397
401
  loadReset(): void;
398
- /** Switch primary/alternate palette */
399
- toggleStyles(scope?: string): void;
402
+ /** Toggle between primary/alternate theme palettes on all matching elements. @see bw.applyStyles */
403
+ toggleThemeMode(scope?: string | HTMLElement): string;
404
+ /** @deprecated Use bw.toggleThemeMode() instead. Alias kept for one release cycle. */
405
+ toggleStyles(scope?: string | HTMLElement): string;
400
406
  /** Remove injected styles */
401
407
  clearStyles(scope?: string): void;
402
408
  /** Generate type scale from base + ratio */
@@ -586,6 +592,7 @@ export interface Bitwrench {
586
592
 
587
593
  // -- Internal (access at own risk) ----------------------------------------
588
594
  _nodeMap: Record<string, HTMLElement>;
595
+ /** @deprecated Use bw.el() instead. Alias kept for one release cycle. */
589
596
  _el(id: string): HTMLElement | null;
590
597
  _registerNode(el: HTMLElement, uuid: string): void;
591
598
  _deregisterNode(el: HTMLElement, uuid: string): void;