Skip to content

Commit ccdb622

Browse files
Merge pull request #52 from zeixcom/docs/data-flow
chore(dependencies): update Cause & Effect to v0.13.2 for a signal propagation bugfix
2 parents f6cb4df + 0c4d553 commit ccdb622

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+993
-784
lines changed

.prettierrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"semi": false,
3+
"useTabs": true,
4+
"tabWidth": 4,
5+
"singleQuote": true,
6+
"arrowParens": "avoid"
7+
}

.zed/settings.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"languages": {
3+
"JavaScript": {
4+
"formatter": {
5+
"external": {
6+
"command": "eslint",
7+
"arguments": ["--fix", "--no-warn-ignored"]
8+
}
9+
},
10+
"code_actions_on_format": {
11+
"source.fixAll.eslint": true
12+
}
13+
}
14+
},
15+
"format_on_save": "off",
16+
"eslint.rules.ignore": ["index.js", "docs/assets/main.js", "**/*.min.js"]
17+
}

bun.lockb

759 Bytes
Binary file not shown.
Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<product-catalog>
22
<header>
33
<p>Shop</p>
4-
<input-button>
5-
<button type="button">
4+
<input-button disabled>
5+
<button type="button" disabled>
66
<span class="label">🛒 Shopping Cart</span>
77
<span class="badge"></span>
88
</button>
@@ -11,27 +11,60 @@
1111
<ul>
1212
<li>
1313
<p>Product 1</p>
14-
<spin-button value="0" zero-label="Add to Cart" increment-label="Increment">
15-
<button type="button" class="decrement" aria-label="Decrement" hidden></button>
14+
<spin-button
15+
value="0"
16+
zero-label="Add to Cart"
17+
increment-label="Increment"
18+
>
19+
<button
20+
type="button"
21+
class="decrement"
22+
aria-label="Decrement"
23+
hidden
24+
>
25+
26+
</button>
1627
<p class="value" hidden>0</p>
1728
<button type="button" class="increment">Add to Cart</button>
1829
</spin-button>
1930
</li>
2031
<li>
2132
<p>Product 2</p>
22-
<spin-button value="0" zero-label="Add to Cart" increment-label="Increment">
23-
<button type="button" class="decrement" aria-label="Decrement" hidden></button>
33+
<spin-button
34+
value="0"
35+
zero-label="Add to Cart"
36+
increment-label="Increment"
37+
>
38+
<button
39+
type="button"
40+
class="decrement"
41+
aria-label="Decrement"
42+
hidden
43+
>
44+
45+
</button>
2446
<p class="value" hidden>0</p>
2547
<button type="button" class="increment">Add to Cart</button>
2648
</spin-button>
2749
</li>
2850
<li>
2951
<p>Product 3</p>
30-
<spin-button value="0" zero-label="Add to Cart" increment-label="Increment">
31-
<button type="button" class="decrement" aria-label="Decrement" hidden></button>
52+
<spin-button
53+
value="0"
54+
zero-label="Add to Cart"
55+
increment-label="Increment"
56+
>
57+
<button
58+
type="button"
59+
class="decrement"
60+
aria-label="Decrement"
61+
hidden
62+
>
63+
64+
</button>
3265
<p class="value" hidden>0</p>
3366
<button type="button" class="increment">Add to Cart</button>
3467
</spin-button>
3568
</li>
3669
</ul>
37-
</product-catalog>
70+
</product-catalog>

docs-src/components/product-catalog/product-catalog.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,29 @@ import {
44
component,
55
first,
66
selection,
7-
setProperty,
7+
pass,
88
} from "../../../";
99
import { SpinButtonProps } from "../spin-button/spin-button";
10-
import { InputButtonProps } from "../input-button/input-button";
1110

1211
export type ProductCatalogProps = {
13-
quantities: Component<SpinButtonProps>[];
12+
total: number;
1413
};
1514

1615
export default component(
1716
"product-catalog",
1817
{
19-
quantities: ((el) =>
20-
selection<Component<SpinButtonProps>>(
21-
el,
22-
"spin-button",
23-
)) as SignalProducer<HTMLElement, Component<SpinButtonProps>[]>,
18+
total: ((el) => () =>
19+
selection<Component<SpinButtonProps>>(el, "spin-button")
20+
.get()
21+
.reduce((sum, item) => sum + item.value, 0)
22+
) as SignalProducer<HTMLElement, number>,
2423
},
2524
(el) => [
26-
first<ProductCatalogProps, Component<InputButtonProps>>(
25+
first(
2726
"input-button",
28-
setProperty("badge", () => {
29-
const total = el.quantities.reduce(
30-
(sum, item) => sum + item.value,
31-
0,
32-
);
33-
return total > 0 ? String(total) : "";
27+
pass({
28+
badge: () => (el.total > 0 ? String(el.total) : ""),
29+
disabled: () => !el.total,
3430
}),
3531
),
3632
],

docs-src/pages/data-flow.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Let's consider a **product catalog** where users can add items to a shopping car
2121
- **Tracks all `SpinButton` components** in its subtree and calculates the total count of items in the shopping cart.
2222
- **Passes that total** to a `InputButton`.
2323
* `InputButton` **(Child)**:
24-
- Displays a **badge** in the top-right corner when the `'badge'` signal is set.
24+
- Displays a **badge** in the top-right corner when the `badge` property is set.
2525
- **Does not track any state** – it simply renders whatever value is passed to it.
2626
* `SpinButton` **(Child)**:
2727
- Displays an **Add to Cart** button initially.
@@ -32,28 +32,34 @@ So `ProductCatalog` **coordinates the data flow between them**.
3232

3333
### Parent Component: ProductCatalog
3434

35-
The **parent component (`ProductCatalog`) knows about its children**, meaning it can **observe and pass state** to them.
35+
The **parent component (`ProductCatalog`) knows about its children**, meaning it can **retrieve state from and pass state to** them.
3636

3737
First, we need to observe the quantities of all `SpinButton` components. For this, we create a signal of all children matching the `spin-button` selector:
3838

3939
```js
4040
component("product-catalog", {
41-
quantities: (el) => selection(el, "spin-button"),
41+
total: (el) => () =>
42+
selection(el, "spin-button")
43+
.get()
44+
.reduce((sum, item) => sum + item.value, 0),
4245
}, () => []);
4346
```
4447

4548
The `selection()` function returns a signal that emits an array of all matching elements. In contrast to a static `querySelectorAll()` call, the `selection()` function is reactive and updates whenever new elements are added or removed from the DOM.
4649

47-
Then, we need to calculate the total of all product quantities and pass it on to the `InputButton` component. As states in UIElement are accessed through normal properties we use the `setProperty()` effect to send values to child components:
50+
Then, we need to calculate the total of all product quantities and pass it on to the `InputButton` component. In UIElement we use the `pass()` function to share state across components:
4851

4952
```js
5053
component("product-catalog", {
51-
quantities: (el) => selection(el, "spin-button"),
54+
total: (el) => () =>
55+
selection(el, "spin-button")
56+
.get()
57+
.reduce((sum, item) => sum + item.value, 0),
5258
}, (el) => [
5359
first("input-button",
54-
setProperty("badge", () => {
55-
const total = el.quantities.reduce((sum, item) => sum + item.value, 0);
56-
return total > 0 ? String(total) : "";
60+
pass({
61+
badge: () => (el.total > 0 ? String(el.total) : ""),
62+
disabled: () => !el.total,
5763
}),
5864
),
5965
]);
@@ -77,7 +83,7 @@ component("input-button", {
7783
```
7884

7985
* ✅ Whenever the `badge` property is updated by a parent component, the badge text updates.
80-
* ✅ If badge is an empty string, the badge is hidden (via CSS).
86+
* ✅ If `badge` is an empty string, the badge indicator is hidden (via CSS).
8187

8288
### ChildComponent: SpinButton
8389

@@ -139,8 +145,8 @@ Here's how everything comes together:
139145
<product-catalog>
140146
<header>
141147
<p>Shop</p>
142-
<input-button>
143-
<button type="button">
148+
<input-button disabled>
149+
<button type="button" disabled>
144150
<span class="label">🛒 Shopping Cart</span>
145151
<span class="badge"></span>
146152
</button>
@@ -209,16 +215,16 @@ Let's consider a Todo App, where users can add tasks:
209215

210216
* `TodoApp` **(Parent)**:
211217
- Holds the list of todos as a state signal.
212-
- Listens for an `'add-todo'` event from the child (`TodoForm`).
218+
- Listens for an `add-todo` event from the child (`TodoForm`).
213219
- Updates the state when a new todo is submitted.
214220
* `TodoForm` **(Child)**:
215221
- Handles **user input** but does **not** store todos.
216-
- Emits an `'add-todo'` event when the user submits the form.
222+
- Emits an `add-todo` event when the user submits the form.
217223
- Lets the parent decide **what to do with the data**.
218224

219225
### Why use events here?
220226

221-
* The child **doesnt need to know where the data goes** – it just **emits an event**.
227+
* The child **doesn't need to know where the data goes** – it just **emits an event**.
222228
* The parent **decides what to do** with the new todo (e.g., adding it to a list).
223229
* This keeps `TodoForm` **reusable** – it could work in different apps without modification.
224230

0 commit comments

Comments
 (0)