bitwrench 2.0.32 → 2.1.1
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 +211 -125
- package/dist/bitwrench-bccl.cjs.js +349 -188
- package/dist/bitwrench-bccl.cjs.min.js +2 -39
- package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
- package/dist/bitwrench-bccl.esm.js +349 -188
- package/dist/bitwrench-bccl.esm.min.js +2 -39
- package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
- package/dist/bitwrench-bccl.umd.js +349 -188
- package/dist/bitwrench-bccl.umd.min.js +2 -39
- package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
- package/dist/bitwrench-code-edit.cjs.js +17 -6
- package/dist/bitwrench-code-edit.cjs.min.js +2 -20
- package/dist/bitwrench-code-edit.es5.js +8 -3
- package/dist/bitwrench-code-edit.es5.min.js +2 -19
- package/dist/bitwrench-code-edit.esm.js +17 -6
- package/dist/bitwrench-code-edit.esm.min.js +2 -19
- package/dist/bitwrench-code-edit.umd.js +17 -6
- package/dist/bitwrench-code-edit.umd.min.js +2 -19
- package/dist/bitwrench-code-edit.umd.min.js.gz +0 -0
- package/dist/bitwrench-debug.js +1 -1
- package/dist/bitwrench-debug.min.js +1 -1
- package/dist/bitwrench-lean.cjs.js +2492 -1628
- package/dist/bitwrench-lean.cjs.min.js +2 -80
- package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
- package/dist/bitwrench-lean.es5.js +2740 -1838
- package/dist/bitwrench-lean.es5.min.js +2 -49
- package/dist/bitwrench-lean.es5.min.js.gz +0 -0
- package/dist/bitwrench-lean.esm.js +2492 -1628
- package/dist/bitwrench-lean.esm.min.js +2 -80
- package/dist/bitwrench-lean.esm.min.js.gz +0 -0
- package/dist/bitwrench-lean.umd.js +2492 -1628
- package/dist/bitwrench-lean.umd.min.js +2 -80
- package/dist/bitwrench-lean.umd.min.js.gz +0 -0
- package/dist/bitwrench-util-color.cjs.js +251 -0
- package/dist/bitwrench-util-color.cjs.min.js +3 -0
- package/dist/bitwrench-util-color.es5.js +256 -0
- package/dist/bitwrench-util-color.es5.min.js +3 -0
- package/dist/bitwrench-util-color.esm.js +241 -0
- package/dist/bitwrench-util-color.esm.min.js +3 -0
- package/dist/bitwrench-util-color.umd.js +257 -0
- package/dist/bitwrench-util-color.umd.min.js +3 -0
- package/dist/bitwrench-util-css.cjs.js +2 -1
- package/dist/bitwrench-util-css.cjs.min.js +2 -21
- package/dist/bitwrench-util-css.es5.js +2 -1
- package/dist/bitwrench-util-css.es5.min.js +2 -20
- package/dist/bitwrench-util-css.esm.js +2 -1
- package/dist/bitwrench-util-css.esm.min.js +1 -19
- package/dist/bitwrench-util-css.umd.js +2 -1
- package/dist/bitwrench-util-css.umd.min.js +2 -20
- package/dist/bitwrench-util-css.umd.min.js.gz +0 -0
- package/dist/bitwrench.cjs.js +2826 -1801
- package/dist/bitwrench.cjs.min.js +2 -99
- package/dist/bitwrench.cjs.min.js.gz +0 -0
- package/dist/bitwrench.css +403 -479
- package/dist/bitwrench.d.ts +70 -73
- package/dist/bitwrench.es5.js +3106 -2020
- package/dist/bitwrench.es5.min.js +2 -67
- package/dist/bitwrench.es5.min.js.gz +0 -0
- package/dist/bitwrench.esm.js +2826 -1801
- package/dist/bitwrench.esm.min.js +2 -99
- package/dist/bitwrench.esm.min.js.gz +0 -0
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +2826 -1801
- package/dist/bitwrench.umd.min.js +2 -99
- package/dist/bitwrench.umd.min.js.gz +0 -0
- package/dist/builds.json +222 -134
- package/dist/bwserve.cjs.js +289 -282
- package/dist/bwserve.d.ts +157 -0
- package/dist/bwserve.esm.js +290 -283
- package/dist/sri.json +54 -46
- package/docs/README.md +6 -3
- package/docs/app-patterns.md +7 -6
- package/docs/bitwrench-for-wasm.md +53 -54
- package/docs/bitwrench-mcp.md +2 -2
- package/docs/bitwrench-northstar-principles.md +406 -0
- package/docs/bitwrench-taco-schema-discussion.md +2 -2
- package/docs/bitwrench_api.md +191 -106
- package/docs/bitwrench_typescript_usage.md +5 -5
- package/docs/bw-attach.md +29 -75
- package/docs/bwserve.md +200 -168
- package/docs/cli.md +36 -12
- package/docs/component-cheatsheet.md +2 -2
- package/docs/component-library.md +4 -4
- package/docs/component-lifecycle.md +234 -0
- package/docs/drift-lint.md +268 -0
- package/docs/framework-translation-table.md +4 -4
- package/docs/llm-bitwrench-guide.md +60 -50
- package/docs/routing.md +11 -13
- package/docs/state-management.md +110 -109
- package/docs/taco-format.md +13 -14
- package/docs/theming.md +13 -3
- package/docs/thinking-in-bitwrench.md +858 -983
- package/docs/tutorial-bwserve.md +37 -36
- package/docs/tutorial-embedded.md +10 -21
- package/docs/tutorial-website.md +2 -2
- package/package.json +38 -7
- package/readme.html +262 -161
- package/src/bitwrench-bccl-entry.js +2 -2
- package/src/bitwrench-bccl.js +346 -185
- package/src/bitwrench-code-edit.js +16 -5
- package/src/bitwrench-color-utils.js +117 -181
- package/src/bitwrench-file-ops.js +2 -2
- package/src/bitwrench-lean.js +4 -3
- package/src/bitwrench-router.js +5 -2
- package/src/bitwrench-styles.js +420 -504
- package/src/bitwrench-util-color.js +240 -0
- package/src/bitwrench-util-css.js +1 -0
- package/src/bitwrench-utils.js +4 -0
- package/src/bitwrench.d.ts +70 -73
- package/src/bitwrench.h +5 -0
- package/src/bitwrench.js +1939 -933
- package/src/bwserve/attach.js +0 -1
- package/src/bwserve/bwclient.js +172 -32
- package/src/bwserve/bwshell.js +0 -4
- package/src/bwserve/client.js +59 -220
- package/src/bwserve/index.js +59 -26
- package/src/bwserve.d.ts +157 -0
- package/src/bwserve.h +5 -0
- package/src/cli/attach.js +12 -75
- package/src/cli/convert.js +2 -2
- package/src/cli/serve.js +37 -35
- package/src/generate-css.js +1 -1
- package/src/mcp/knowledge.js +4 -4
- package/src/mcp/live.js +21 -13
- package/src/mcp/tools.js +0 -1
- package/src/version.js +3 -7
package/docs/state-management.md
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
# State Management
|
|
2
2
|
|
|
3
|
-
Bitwrench has a
|
|
3
|
+
Bitwrench has a progressive component model. Each stage adds capability on top of the one below it. You choose the stage that fits your use case — there is no single "right" way.
|
|
4
4
|
|
|
5
|
-
|
|
|
5
|
+
| Stage | What you get | When to use it |
|
|
6
6
|
|-------|-------------|---------------|
|
|
7
|
-
|
|
|
8
|
-
|
|
|
9
|
-
|
|
|
10
|
-
|
|
|
7
|
+
| TACO as data | A plain JavaScript object describing UI | Static content, server rendering, serialization |
|
|
8
|
+
| Mounting and rendering | A live DOM element with optional lifecycle hooks | Render-once UI, manual state management |
|
|
9
|
+
| Component handles | `o.handle` and `o.slots` for imperative control of rendered elements | Update parts of a component without re-rendering |
|
|
10
|
+
| Stateful components | A TACO with `o.state` + `o.render` and `bw.refresh()` for re-rendering | Interactive components with changing state |
|
|
11
11
|
|
|
12
|
-
This guide covers all
|
|
12
|
+
This guide covers all stages, from simplest to most capable.
|
|
13
13
|
|
|
14
|
-
> **Coming from React?**
|
|
14
|
+
> **Coming from React?** A static TACO is like calling `React.createElement()` to get a virtual element. Mounting is like `ReactDOM.render()` with no state. A stateful TACO is like a class component with `this.state` and `this.setState()`, where you call `bw.refresh(el)` instead of `setState`.
|
|
15
15
|
|
|
16
|
-
> **Coming from Vue?**
|
|
16
|
+
> **Coming from Vue?** A static TACO is like a render function's return value. Mounting is like `createApp().mount()`. A stateful TACO is like a component with a `setup()` function that manages its own reactivity, where the render function re-runs on `bw.refresh()`.
|
|
17
17
|
|
|
18
|
-
> **Coming from Svelte?**
|
|
18
|
+
> **Coming from Svelte?** A static TACO is like the compiled component descriptor. Mounting is like `new Component({ target })`. A stateful TACO is a live component with state variables, where `bw.refresh(el)` triggers the re-render.
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## TACO as Data
|
|
23
23
|
|
|
24
24
|
Every UI element in bitwrench starts as a plain object:
|
|
25
25
|
|
|
@@ -29,7 +29,7 @@ var greeting = { t: 'h1', c: 'Hello World' };
|
|
|
29
29
|
|
|
30
30
|
This is a TACO object — **T**ag, **A**ttributes, **C**ontent, **O**ptions. It describes what you want, not how to render it. See [TACO Format](taco-format.md) for the full specification.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
As data, a TACO object is inert. You can:
|
|
33
33
|
|
|
34
34
|
- Store it in a variable
|
|
35
35
|
- Put it in an array with other TACOs
|
|
@@ -47,7 +47,7 @@ var list = { t: 'ul', c: items };
|
|
|
47
47
|
|
|
48
48
|
### The `make*()` factories
|
|
49
49
|
|
|
50
|
-
The component library provides over 50 factory functions that return
|
|
50
|
+
The component library provides over 50 factory functions that return static TACO objects:
|
|
51
51
|
|
|
52
52
|
```javascript
|
|
53
53
|
var card = bw.makeCard({ title: 'Users', content: '1,234 active' });
|
|
@@ -72,9 +72,9 @@ var page = {
|
|
|
72
72
|
|
|
73
73
|
See [Component Library](component-library.md) for all available factories.
|
|
74
74
|
|
|
75
|
-
### When
|
|
75
|
+
### When static TACOs are enough
|
|
76
76
|
|
|
77
|
-
Use
|
|
77
|
+
Use static TACOs when the content does not change after rendering:
|
|
78
78
|
|
|
79
79
|
- Static pages and reports
|
|
80
80
|
- Server-rendered HTML (`bw.html(taco)` in Node.js)
|
|
@@ -84,7 +84,7 @@ Use Level 0 when the content does not change after rendering:
|
|
|
84
84
|
|
|
85
85
|
---
|
|
86
86
|
|
|
87
|
-
##
|
|
87
|
+
## Mounting and Rendering
|
|
88
88
|
|
|
89
89
|
To turn a TACO into something visible, pass it to a rendering function:
|
|
90
90
|
|
|
@@ -94,7 +94,7 @@ var html = bw.html({ t: 'div', c: 'Hello' });
|
|
|
94
94
|
// '<div>Hello</div>'
|
|
95
95
|
|
|
96
96
|
// Create a detached DOM element (browser only)
|
|
97
|
-
var el = bw.
|
|
97
|
+
var el = bw.create({ t: 'div', c: 'Hello' });
|
|
98
98
|
|
|
99
99
|
// Mount into an existing DOM element (browser only)
|
|
100
100
|
bw.DOM('#app', { t: 'div', c: 'Hello' });
|
|
@@ -102,9 +102,9 @@ bw.DOM('#app', { t: 'div', c: 'Hello' });
|
|
|
102
102
|
|
|
103
103
|
`bw.DOM()` finds the element matching the selector, cleans up any previous content (running unmount hooks, clearing subscriptions), and mounts the new TACO as live DOM.
|
|
104
104
|
|
|
105
|
-
### Adding interactivity
|
|
105
|
+
### Adding interactivity to mounted components
|
|
106
106
|
|
|
107
|
-
You can make
|
|
107
|
+
You can make mounted components interactive using closures, `o.state`, and `o.render`:
|
|
108
108
|
|
|
109
109
|
```javascript
|
|
110
110
|
function makeCounter() {
|
|
@@ -112,14 +112,14 @@ function makeCounter() {
|
|
|
112
112
|
t: 'div',
|
|
113
113
|
o: {
|
|
114
114
|
state: { count: 0 },
|
|
115
|
-
render: function(el) {
|
|
115
|
+
render: function(el, state) {
|
|
116
116
|
bw.DOM(el, {
|
|
117
117
|
t: 'div', c: [
|
|
118
|
-
{ t: 'span', c: 'Count: ' +
|
|
118
|
+
{ t: 'span', c: 'Count: ' + state.count },
|
|
119
119
|
{ t: 'button', c: '+1', a: {
|
|
120
120
|
onclick: function() {
|
|
121
|
-
|
|
122
|
-
bw.
|
|
121
|
+
state.count++;
|
|
122
|
+
bw.refresh(el);
|
|
123
123
|
}
|
|
124
124
|
}}
|
|
125
125
|
]
|
|
@@ -136,13 +136,13 @@ This is the **manual render pump** pattern:
|
|
|
136
136
|
|
|
137
137
|
1. Define state in `o.state`
|
|
138
138
|
2. Define a render function in `o.render`
|
|
139
|
-
3. When state changes, call `bw.
|
|
139
|
+
3. When state changes, call `bw.refresh(el)` to re-invoke the render function
|
|
140
140
|
|
|
141
141
|
This pattern works and is fully supported. It gives you direct control over when and how re-rendering happens.
|
|
142
142
|
|
|
143
143
|
### Lifecycle hooks
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
Mounted components can respond to mount and unmount events:
|
|
146
146
|
|
|
147
147
|
```javascript
|
|
148
148
|
{
|
|
@@ -161,7 +161,7 @@ Level 1 components can respond to mount and unmount events:
|
|
|
161
161
|
}
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
> **Warning: Never use `o.mounted` to attach event handlers.** When a stateful component re-renders (after `bw.
|
|
164
|
+
> **Warning: Never use `o.mounted` to attach event handlers.** When a stateful component re-renders (after `bw.refresh()`), the old DOM content is replaced and any listeners attached via `addEventListener` in `mounted` are silently lost. Always put event handlers in `a: { onclick: fn }` -- bitwrench re-attaches them on every render. Use `o.mounted` only for non-event setup: timers, observers, third-party library init, measuring dimensions.
|
|
165
165
|
|
|
166
166
|
### Targeted updates with `bw.patch()`
|
|
167
167
|
|
|
@@ -185,21 +185,21 @@ bw.patchAll({
|
|
|
185
185
|
});
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
### When
|
|
188
|
+
### When mounting is enough
|
|
189
189
|
|
|
190
|
-
Use
|
|
190
|
+
Use this approach when:
|
|
191
191
|
|
|
192
192
|
- You need interactivity but want full control over the render cycle
|
|
193
193
|
- You are building a one-off interactive widget
|
|
194
194
|
- You are integrating with external libraries that manage their own state
|
|
195
|
-
- You prefer explicit `bw.
|
|
195
|
+
- You prefer explicit `bw.refresh()` calls over automatic re-rendering
|
|
196
196
|
- You are building the transport layer for server-driven UI (bwserve)
|
|
197
197
|
|
|
198
|
-
> **Coming from jQuery?**
|
|
198
|
+
> **Coming from jQuery?** Mounting with `o.render` + `bw.refresh()` is conceptually similar to jQuery's manual DOM updates, but structured. Instead of scattered `$('.count').text(val)` calls, you have a single render function that produces the complete UI from state. When state changes, you call `bw.refresh()` and the render function runs again.
|
|
199
199
|
|
|
200
200
|
---
|
|
201
201
|
|
|
202
|
-
##
|
|
202
|
+
## Component Handles
|
|
203
203
|
|
|
204
204
|
When you need imperative control of a rendered element -- updating a title, advancing a carousel, or reading a form value -- without re-rendering the entire component, use `o.handle` and `o.slots`.
|
|
205
205
|
|
|
@@ -231,7 +231,7 @@ Declare named content areas with CSS selectors. Bitwrench auto-generates `el.bw.
|
|
|
231
231
|
|
|
232
232
|
```javascript
|
|
233
233
|
var card = bw.makeCard({ title: 'Stats', content: '0' });
|
|
234
|
-
// makeCard declares o.slots: { title: '.
|
|
234
|
+
// makeCard declares o.slots: { title: '.bw_bccl_card_title', content: '.bw_bccl_card_body', footer: '.bw_bccl_card_footer' }
|
|
235
235
|
var el = bw.mount('#app', card);
|
|
236
236
|
el.bw.setTitle('Updated Title');
|
|
237
237
|
el.bw.setContent({ t: 'strong', c: '42' }); // accepts TACO objects
|
|
@@ -240,9 +240,9 @@ var text = el.bw.getTitle(); // returns text content
|
|
|
240
240
|
|
|
241
241
|
Slot setters accept strings or TACO objects. They update just the targeted element -- no full re-render, so input focus, scroll position, and animation state are preserved.
|
|
242
242
|
|
|
243
|
-
### bw.mount() -- get the element back
|
|
243
|
+
### bw.mount() / bw.DOM() -- get the element back
|
|
244
244
|
|
|
245
|
-
`bw.mount()`
|
|
245
|
+
`bw.mount()` and `bw.DOM()` are identical (aliases). Both return the created root element, which gives you access to `el.bw`:
|
|
246
246
|
|
|
247
247
|
```javascript
|
|
248
248
|
var el = bw.mount('#app', bw.makeCarousel({ items: slides }));
|
|
@@ -273,35 +273,34 @@ All BCCL factories include `o.handle` and/or `o.slots`. Examples:
|
|
|
273
273
|
| makeCard | setTitle/getTitle, setContent/getContent, setFooter/getFooter (slots) |
|
|
274
274
|
| makeStatCard | setValue/getValue, setLabel/getLabel (slots) |
|
|
275
275
|
|
|
276
|
-
### When to use handles vs
|
|
276
|
+
### When to use handles vs stateful components
|
|
277
277
|
|
|
278
278
|
| Situation | Use |
|
|
279
279
|
|-----------|-----|
|
|
280
280
|
| Update a label, badge, or slot text | **Handles** -- `el.bw.setTitle('new')` |
|
|
281
281
|
| Advance a carousel or toggle an accordion | **Handles** -- `el.bw.next()`, `el.bw.toggle(0)` |
|
|
282
|
-
| Component has complex state that triggers full UI rebuild | **
|
|
282
|
+
| Component has complex state that triggers full UI rebuild | **Stateful** -- `o.state` + `o.render` + `bw.refresh()` |
|
|
283
283
|
| Need to preserve input focus during updates | **Handles** -- slot setters don't re-render siblings |
|
|
284
284
|
| External code needs to control an embedded widget | **Handles** -- `bw.mount()` + `el.bw.method()` |
|
|
285
285
|
|
|
286
286
|
---
|
|
287
287
|
|
|
288
|
-
##
|
|
288
|
+
## Stateful Components
|
|
289
289
|
|
|
290
|
-
|
|
290
|
+
Adding `o.state` and `o.render` to a TACO gives it managed state and a render pump. When state changes, you call `bw.refresh(el)` to re-invoke the render function. This is the recommended pattern for interactive components.
|
|
291
291
|
|
|
292
292
|
```javascript
|
|
293
293
|
var counter = {
|
|
294
294
|
t: 'div',
|
|
295
295
|
o: {
|
|
296
296
|
state: { count: 0 },
|
|
297
|
-
render: function(el) {
|
|
298
|
-
var s = el._bw_state;
|
|
297
|
+
render: function(el, state) {
|
|
299
298
|
bw.DOM(el, {
|
|
300
299
|
t: 'div', c: [
|
|
301
|
-
{ t: 'h3', c: 'Count: ' +
|
|
300
|
+
{ t: 'h3', c: 'Count: ' + state.count },
|
|
302
301
|
bw.makeButton({ text: '+1', onclick: function() {
|
|
303
|
-
|
|
304
|
-
bw.
|
|
302
|
+
state.count++;
|
|
303
|
+
bw.refresh(el);
|
|
305
304
|
}})
|
|
306
305
|
]
|
|
307
306
|
});
|
|
@@ -314,9 +313,9 @@ bw.DOM('#app', counter);
|
|
|
314
313
|
|
|
315
314
|
### How it works
|
|
316
315
|
|
|
317
|
-
1. `bw.
|
|
316
|
+
1. `bw.create()` (called internally by `bw.DOM()`) sees `o.state` and copies it to `el._bw_state`
|
|
318
317
|
2. If `o.render` is defined, it is stored as `el._bw_render` and called immediately: `o.render(el, el._bw_state)`
|
|
319
|
-
3. When state changes, you call `bw.
|
|
318
|
+
3. When state changes, you call `bw.refresh(el)` which re-invokes `el._bw_render(el, el._bw_state)` and emits a `bw:refresh` event
|
|
320
319
|
4. The render function produces new content via `bw.DOM(el, ...)`, replacing the old children
|
|
321
320
|
|
|
322
321
|
### Accessing state
|
|
@@ -328,10 +327,9 @@ State lives directly on the DOM element as `el._bw_state`:
|
|
|
328
327
|
t: 'div',
|
|
329
328
|
o: {
|
|
330
329
|
state: { count: 0, label: 'Clicks' },
|
|
331
|
-
render: function(el) {
|
|
332
|
-
var s = el._bw_state;
|
|
330
|
+
render: function(el, state) {
|
|
333
331
|
bw.DOM(el, {
|
|
334
|
-
t: 'div', c:
|
|
332
|
+
t: 'div', c: state.label + ': ' + state.count
|
|
335
333
|
});
|
|
336
334
|
}
|
|
337
335
|
}
|
|
@@ -343,12 +341,12 @@ To read or modify state from outside, get a reference to the element:
|
|
|
343
341
|
```javascript
|
|
344
342
|
var el = bw.$('#my-component')[0];
|
|
345
343
|
el._bw_state.count = 42;
|
|
346
|
-
bw.
|
|
344
|
+
bw.refresh(el);
|
|
347
345
|
```
|
|
348
346
|
|
|
349
347
|
### Lifecycle hooks
|
|
350
348
|
|
|
351
|
-
Stateful
|
|
349
|
+
Stateful components support two primary lifecycle hooks:
|
|
352
350
|
|
|
353
351
|
| Hook | When it fires | Typical use |
|
|
354
352
|
|------|--------------|-------------|
|
|
@@ -363,20 +361,20 @@ var timer = {
|
|
|
363
361
|
mounted: function(el) {
|
|
364
362
|
el._interval = setInterval(function() {
|
|
365
363
|
el._bw_state.seconds++;
|
|
366
|
-
bw.
|
|
364
|
+
bw.refresh(el);
|
|
367
365
|
}, 1000);
|
|
368
366
|
},
|
|
369
367
|
unmount: function(el) {
|
|
370
368
|
clearInterval(el._interval);
|
|
371
369
|
},
|
|
372
|
-
render: function(el) {
|
|
373
|
-
bw.DOM(el, { t: 'span', c: 'Elapsed: ' +
|
|
370
|
+
render: function(el, state) {
|
|
371
|
+
bw.DOM(el, { t: 'span', c: 'Elapsed: ' + state.seconds + 's' });
|
|
374
372
|
}
|
|
375
373
|
}
|
|
376
374
|
};
|
|
377
375
|
```
|
|
378
376
|
|
|
379
|
-
> **Warning: Never attach event handlers in `o.mounted`.** When `bw.
|
|
377
|
+
> **Warning: Never attach event handlers in `o.mounted`.** When `bw.refresh()` re-renders a component, the old DOM children are replaced. Any listeners attached via `addEventListener` in `mounted` are silently lost. Always put event handlers in `a: { onclick: fn }` -- bitwrench re-attaches them on every render.
|
|
380
378
|
|
|
381
379
|
### Targeted updates with `bw.patch()`
|
|
382
380
|
|
|
@@ -387,11 +385,10 @@ var dashboard = {
|
|
|
387
385
|
t: 'div',
|
|
388
386
|
o: {
|
|
389
387
|
state: { temp: 0 },
|
|
390
|
-
render: function(el) {
|
|
391
|
-
var s = el._bw_state;
|
|
388
|
+
render: function(el, state) {
|
|
392
389
|
bw.DOM(el, {
|
|
393
390
|
t: 'div', c: [
|
|
394
|
-
{ t: 'span', a: { class: bw.uuid('temp') }, c:
|
|
391
|
+
{ t: 'span', a: { class: bw.uuid('temp') }, c: state.temp + ' C' },
|
|
395
392
|
{ t: 'span', a: { class: bw.uuid('status') }, c: 'OK' }
|
|
396
393
|
]
|
|
397
394
|
});
|
|
@@ -406,40 +403,40 @@ bw.patch('temp', '23.5 C');
|
|
|
406
403
|
bw.patch('status', 'Warning');
|
|
407
404
|
```
|
|
408
405
|
|
|
409
|
-
### When to use
|
|
406
|
+
### When to use stateful components
|
|
410
407
|
|
|
411
|
-
Use
|
|
408
|
+
Use stateful components when:
|
|
412
409
|
|
|
413
410
|
- The component has state that changes after initial render
|
|
414
411
|
- You need a render function that re-runs on state changes
|
|
415
412
|
- You need lifecycle management (mount, unmount)
|
|
416
413
|
- You want explicit control over what triggers a re-render
|
|
417
414
|
|
|
418
|
-
> **Coming from React?**
|
|
415
|
+
> **Coming from React?** A stateful TACO is like a class component with `this.state` and a manual `forceUpdate()`. The render function rebuilds the component from state each time `bw.refresh()` is called.
|
|
419
416
|
|
|
420
|
-
> **Coming from jQuery?**
|
|
417
|
+
> **Coming from jQuery?** A stateful TACO with `o.render` + `bw.refresh()` is conceptually similar to jQuery's manual DOM updates, but structured. Instead of scattered `$('.count').text(val)` calls, you have a single render function that produces the complete UI from state.
|
|
421
418
|
|
|
422
419
|
---
|
|
423
420
|
|
|
424
|
-
##
|
|
421
|
+
## Progressing Between Stages
|
|
425
422
|
|
|
426
|
-
You can start at any
|
|
423
|
+
You can start at any stage and progress when you need more capability.
|
|
427
424
|
|
|
428
|
-
###
|
|
425
|
+
### From Data to DOM
|
|
429
426
|
|
|
430
427
|
Pass a TACO to a rendering function:
|
|
431
428
|
|
|
432
429
|
```javascript
|
|
433
|
-
var taco = bw.makeCard({ title: 'Hello' }); //
|
|
434
|
-
bw.DOM('#app', taco); //
|
|
430
|
+
var taco = bw.makeCard({ title: 'Hello' }); // static TACO
|
|
431
|
+
bw.DOM('#app', taco); // mounted — now it's in the DOM
|
|
435
432
|
```
|
|
436
433
|
|
|
437
|
-
###
|
|
434
|
+
### From Data to Stateful
|
|
438
435
|
|
|
439
436
|
Add `o.state` and `o.render` to make a static TACO stateful:
|
|
440
437
|
|
|
441
438
|
```javascript
|
|
442
|
-
// Start with
|
|
439
|
+
// Start with a static TACO
|
|
443
440
|
var card = bw.makeCard({ title: 'Hello' });
|
|
444
441
|
|
|
445
442
|
// Wrap in a stateful container
|
|
@@ -447,19 +444,19 @@ var statefulCard = {
|
|
|
447
444
|
t: 'div',
|
|
448
445
|
o: {
|
|
449
446
|
state: { title: 'Hello' },
|
|
450
|
-
render: function(el) {
|
|
451
|
-
bw.DOM(el, bw.makeCard({ title:
|
|
447
|
+
render: function(el, state) {
|
|
448
|
+
bw.DOM(el, bw.makeCard({ title: state.title }));
|
|
452
449
|
}
|
|
453
450
|
}
|
|
454
451
|
};
|
|
455
452
|
bw.DOM('#app', statefulCard);
|
|
456
453
|
```
|
|
457
454
|
|
|
458
|
-
###
|
|
455
|
+
### From Mounted to Stateful
|
|
459
456
|
|
|
460
|
-
If you have a
|
|
457
|
+
If you have a mounted component using manual `bw.DOM()` calls, add `o.state` and `o.render`:
|
|
461
458
|
|
|
462
|
-
**Before (
|
|
459
|
+
**Before (manual re-render):**
|
|
463
460
|
```javascript
|
|
464
461
|
var count = 0;
|
|
465
462
|
function renderCounter() {
|
|
@@ -475,19 +472,18 @@ function renderCounter() {
|
|
|
475
472
|
renderCounter();
|
|
476
473
|
```
|
|
477
474
|
|
|
478
|
-
**After (
|
|
475
|
+
**After (stateful TACO):**
|
|
479
476
|
```javascript
|
|
480
477
|
bw.DOM('#app', {
|
|
481
478
|
t: 'div',
|
|
482
479
|
o: {
|
|
483
480
|
state: { count: 0 },
|
|
484
|
-
render: function(el) {
|
|
485
|
-
var s = el._bw_state;
|
|
481
|
+
render: function(el, state) {
|
|
486
482
|
bw.DOM(el, {
|
|
487
483
|
t: 'div', c: [
|
|
488
|
-
{ t: 'span', c: 'Count: ' +
|
|
484
|
+
{ t: 'span', c: 'Count: ' + state.count },
|
|
489
485
|
{ t: 'button', c: '+1', a: {
|
|
490
|
-
onclick: function() {
|
|
486
|
+
onclick: function() { state.count++; bw.refresh(el); }
|
|
491
487
|
}}
|
|
492
488
|
]
|
|
493
489
|
});
|
|
@@ -496,7 +492,7 @@ bw.DOM('#app', {
|
|
|
496
492
|
});
|
|
497
493
|
```
|
|
498
494
|
|
|
499
|
-
The
|
|
495
|
+
The stateful version encapsulates state inside the component. No external variable, no standalone render function. The render function is called automatically on mount and on each `bw.refresh(el)` call.
|
|
500
496
|
|
|
501
497
|
---
|
|
502
498
|
|
|
@@ -506,7 +502,7 @@ Bitwrench provides three mechanisms for components to communicate, each suited t
|
|
|
506
502
|
|
|
507
503
|
### Shared state (parent-child)
|
|
508
504
|
|
|
509
|
-
Multiple components can share the same state object. When either calls `bw.
|
|
505
|
+
Multiple components can share the same state object. When either calls `bw.refresh()`, it re-renders with the current shared state:
|
|
510
506
|
|
|
511
507
|
```javascript
|
|
512
508
|
var appState = { user: { name: 'Alice' }, items: [] };
|
|
@@ -515,8 +511,8 @@ var header = {
|
|
|
515
511
|
t: 'header',
|
|
516
512
|
o: {
|
|
517
513
|
state: appState,
|
|
518
|
-
render: function(el) {
|
|
519
|
-
bw.DOM(el, { t: 'span', c: 'Hello, ' +
|
|
514
|
+
render: function(el, state) {
|
|
515
|
+
bw.DOM(el, { t: 'span', c: 'Hello, ' + state.user.name });
|
|
520
516
|
}
|
|
521
517
|
}
|
|
522
518
|
};
|
|
@@ -525,10 +521,9 @@ var main = {
|
|
|
525
521
|
t: 'main',
|
|
526
522
|
o: {
|
|
527
523
|
state: appState,
|
|
528
|
-
render: function(el) {
|
|
529
|
-
var s = el._bw_state;
|
|
524
|
+
render: function(el, state) {
|
|
530
525
|
bw.DOM(el, {
|
|
531
|
-
t: 'div', c:
|
|
526
|
+
t: 'div', c: state.items.map(function(item) {
|
|
532
527
|
return { t: 'div', c: item.text };
|
|
533
528
|
})
|
|
534
529
|
});
|
|
@@ -555,17 +550,17 @@ var results = {
|
|
|
555
550
|
mounted: function(el) {
|
|
556
551
|
bw.sub('search:changed', function(detail) {
|
|
557
552
|
el._bw_state.query = detail.query;
|
|
558
|
-
bw.
|
|
553
|
+
bw.refresh(el);
|
|
559
554
|
}, el);
|
|
560
555
|
},
|
|
561
|
-
render: function(el) {
|
|
562
|
-
bw.DOM(el, { t: 'span', c: 'Results for: ' +
|
|
556
|
+
render: function(el, state) {
|
|
557
|
+
bw.DOM(el, { t: 'span', c: 'Results for: ' + state.query });
|
|
563
558
|
}
|
|
564
559
|
}
|
|
565
560
|
};
|
|
566
561
|
```
|
|
567
562
|
|
|
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.
|
|
563
|
+
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.unmount()`).
|
|
569
564
|
|
|
570
565
|
Wildcard subscriptions let you listen to a group of related topics at once:
|
|
571
566
|
|
|
@@ -596,15 +591,14 @@ bw.DOM('#app', {
|
|
|
596
591
|
mounted: function(el) {
|
|
597
592
|
bw.sub('upload:progress', function(d) {
|
|
598
593
|
el._bw_state.pct = d.pct;
|
|
599
|
-
bw.
|
|
594
|
+
bw.refresh(el);
|
|
600
595
|
}, el);
|
|
601
596
|
},
|
|
602
|
-
render: function(el) {
|
|
603
|
-
var s = el._bw_state;
|
|
597
|
+
render: function(el, state) {
|
|
604
598
|
bw.DOM(el, {
|
|
605
599
|
t: 'div', c: [
|
|
606
600
|
{ t: 'h2', c: 'Upload Progress' },
|
|
607
|
-
bw.makeProgress({ value:
|
|
601
|
+
bw.makeProgress({ value: state.pct, label: state.pct + '%' }),
|
|
608
602
|
bw.makeButton({ text: 'Start', onclick: function() {
|
|
609
603
|
var pct = 0;
|
|
610
604
|
var interval = setInterval(function() {
|
|
@@ -630,7 +624,8 @@ These primitives are the building blocks of the stateful TACO model. They are al
|
|
|
630
624
|
|
|
631
625
|
| Function | Purpose |
|
|
632
626
|
|----------|---------|
|
|
633
|
-
| `bw.
|
|
627
|
+
| `bw.refresh(ref)` | Re-invoke `el._bw_render(el, state)` to re-render |
|
|
628
|
+
| `bw.update(ref, data)` | Dispatch to `el.bw.update(data)` |
|
|
634
629
|
| `bw.patch(uuid, content, attr)` | Update a single UUID-addressed element |
|
|
635
630
|
| `bw.patchAll(patches)` | Batch-update multiple UUID-addressed elements |
|
|
636
631
|
| `bw.uuid(prefix)` | Generate a UUID class for addressing |
|
|
@@ -640,7 +635,14 @@ These primitives are the building blocks of the stateful TACO model. They are al
|
|
|
640
635
|
| `bw.sub(topic, handler, el?)` | Subscribe to topic (supports wildcard `'ns:*'` patterns) |
|
|
641
636
|
| `bw.once(topic, handler, el?)` | One-shot subscribe (auto-unsub after first fire) |
|
|
642
637
|
| `bw.unsub(topic, handler)` | Unsubscribe from topic |
|
|
643
|
-
| `bw.
|
|
638
|
+
| `bw.unmount(el)` | Tear down subtree lifecycle |
|
|
639
|
+
| `bw.mountTree(el)` | Register an inserted subtree |
|
|
640
|
+
| `bw.unmountChildren(el)` | Unmount descendants only |
|
|
641
|
+
| `bw.detach(el)` | Keep-alive disconnect |
|
|
642
|
+
| `bw.hydrate(el, taco)` | Wire lifecycle onto existing DOM |
|
|
643
|
+
| `bw.append(target, taco)` | Add child without removing existing |
|
|
644
|
+
| `bw.replace(ref, taco)` | Swap element at DOM position |
|
|
645
|
+
| `bw.remove(ref)` | Unmount + remove from DOM |
|
|
644
646
|
|
|
645
647
|
### `bw.emit()` / `bw.on()` vs `bw.pub()` / `bw.sub()`
|
|
646
648
|
|
|
@@ -651,7 +653,7 @@ Bitwrench has two event systems that serve different purposes:
|
|
|
651
653
|
| Scope | DOM element and its ancestors (bubbles) | App-wide (all subscribers) |
|
|
652
654
|
| Addressing | By DOM element reference | By topic string |
|
|
653
655
|
| Use case | Parent-child DOM communication | Decoupled cross-component messaging |
|
|
654
|
-
| Cleanup | Manual or via bw.
|
|
656
|
+
| Cleanup | Manual or via bw.unmount() | Auto-cleanup via `handle.sub()` or element lifecycle |
|
|
655
657
|
|
|
656
658
|
---
|
|
657
659
|
|
|
@@ -690,12 +692,11 @@ function renderTodoView(target) {
|
|
|
690
692
|
mounted: function(el) {
|
|
691
693
|
bw.sub('store:todos', function(todos) {
|
|
692
694
|
el._bw_state.items = todos;
|
|
693
|
-
bw.
|
|
695
|
+
bw.refresh(el);
|
|
694
696
|
}, el); // auto-unsubscribes when view is removed
|
|
695
697
|
},
|
|
696
|
-
render: function(el) {
|
|
697
|
-
|
|
698
|
-
bw.DOM(el, { t: 'ul', c: s.items.map(function(item) {
|
|
698
|
+
render: function(el, state) {
|
|
699
|
+
bw.DOM(el, { t: 'ul', c: state.items.map(function(item) {
|
|
699
700
|
return { t: 'li', c: item.text };
|
|
700
701
|
})});
|
|
701
702
|
}
|
|
@@ -712,10 +713,10 @@ function renderProjectView(target) {
|
|
|
712
713
|
mounted: function(el) {
|
|
713
714
|
bw.sub('store:projects', function(projects) {
|
|
714
715
|
el._bw_state.projects = projects;
|
|
715
|
-
bw.
|
|
716
|
+
bw.refresh(el);
|
|
716
717
|
}, el);
|
|
717
718
|
},
|
|
718
|
-
render: function(el) {
|
|
719
|
+
render: function(el, state) {
|
|
719
720
|
// ... render projects
|
|
720
721
|
}
|
|
721
722
|
}
|
|
@@ -755,7 +756,7 @@ bw.sub('store:*', function(data, topic) {
|
|
|
755
756
|
- Dashboard panels that react to shared metrics
|
|
756
757
|
- Any app with >1 view reading from the same data source
|
|
757
758
|
|
|
758
|
-
For surgical updates within a view (changing a title, updating a counter), use [
|
|
759
|
+
For surgical updates within a view (changing a title, updating a counter), use [component handles](state-management.md#component-handles) instead of a full re-render.
|
|
759
760
|
|
|
760
761
|
For URL-driven view switching, combine with [bw.router()](routing.md):
|
|
761
762
|
|
|
@@ -775,11 +776,11 @@ bw.router({
|
|
|
775
776
|
|
|
776
777
|
| Situation | Recommended approach |
|
|
777
778
|
|-----------|---------------------|
|
|
778
|
-
| Static content, server rendering |
|
|
779
|
-
| Interactive widget, full control |
|
|
780
|
-
| Update a slot, label, or control a widget |
|
|
781
|
-
| Stateful component with changing data |
|
|
782
|
-
| Server pushes UI updates |
|
|
779
|
+
| Static content, server rendering | Static TACO data, `bw.html()` |
|
|
780
|
+
| Interactive widget, full control | Mounted -- manual `bw.DOM()` re-renders |
|
|
781
|
+
| Update a slot, label, or control a widget | Component handles -- `o.handle` / `o.slots` via `bw.mount()` + `el.bw` |
|
|
782
|
+
| Stateful component with changing data | Stateful -- `o.state` + `o.render` + `bw.refresh()` |
|
|
783
|
+
| Server pushes UI updates | Mounted -- `bw.patch()` / `bw.DOM()` |
|
|
783
784
|
| Components need to talk to each other | `bw.pub()`/`bw.sub()` |
|
|
784
785
|
| URL-driven views (SPA) | `bw.router()` -- see [Routing](routing.md) |
|
|
785
786
|
| Debugging component state | `el._bw_state` in the console, or `bw.inspect(selector, 0)` |
|
|
@@ -788,4 +789,4 @@ bw.router({
|
|
|
788
789
|
|
|
789
790
|
## Removed: bw.component() (v2.0.19)
|
|
790
791
|
|
|
791
|
-
`bw.component()`, `bw.compile()`, `bw.when()`, and `bw.each()` were removed in v2.0.19.
|
|
792
|
+
`bw.component()`, `bw.compile()`, `bw.when()`, and `bw.each()` were removed in v2.0.19. These functions are now `undefined`. Their functionality is replaced by `o.handle`, `o.slots`, and `bw.mount()` -- see [Component Handles](#component-handles) above.
|